From d0cb1eee5979b093b44ca337444e824d3d32bf61 Mon Sep 17 00:00:00 2001 From: Hugo Peixoto Date: Fri, 22 Dec 2023 19:40:32 +0000 Subject: [PATCH] Add freescout source 1.8.114 --- freescout-dist/.editorconfig | 12 + freescout-dist/.env.example | 47 + freescout-dist/.env.travis | 10 + freescout-dist/.gitattributes | 5 + freescout-dist/.gitcommit | 1 + .../ISSUE_TEMPLATE/general_help_request.md | 23 + .../.github/PULL_REQUEST_TEMPLATE.md | 3 + freescout-dist/.github/workflows/lint-php.yml | 20 + .../.github/workflows/test-pgsql.yml | 59 + freescout-dist/.github/workflows/test.yml | 45 + freescout-dist/.gitignore | 36 + freescout-dist/.htaccess | 10 + freescout-dist/.travis.yml | 15 + freescout-dist/LICENSE | 661 + freescout-dist/README.md | 150 + freescout-dist/SECURITY.md | 5 + freescout-dist/app/ActivityLog.php | 135 + freescout-dist/app/Attachment.php | 450 + .../Broadcasters/PolycastBroadcaster.php | 127 + .../app/Channels/RealtimeBroadcastChannel.php | 29 + .../app/Console/Commands/AfterAppUpdate.php | 44 + freescout-dist/app/Console/Commands/Build.php | 43 + .../app/Console/Commands/CheckConvViewers.php | 117 + .../Console/Commands/CheckRequirements.php | 63 + .../Commands/CleanNotificationsTable.php | 48 + .../app/Console/Commands/CleanSendLog.php | 46 + .../app/Console/Commands/CleanTmp.php | 46 + .../app/Console/Commands/ClearCache.php | 62 + .../app/Console/Commands/CreateUser.php | 102 + .../app/Console/Commands/FetchEmails.php | 1503 ++ .../app/Console/Commands/FetchMonitor.php | 73 + .../app/Console/Commands/GenerateVars.php | 77 + .../app/Console/Commands/LogoutUsers.php | 61 + .../app/Console/Commands/LogsMonitor.php | 96 + .../app/Console/Commands/ModuleBuild.php | 118 + .../Console/Commands/ModuleCheckLicenses.php | 102 + .../app/Console/Commands/ModuleInstall.php | 140 + .../app/Console/Commands/ModuleLaroute.php | 171 + .../app/Console/Commands/ModuleUpdate.php | 105 + .../app/Console/Commands/SendMonitor.php | 66 + .../app/Console/Commands/Update.php | 64 + .../Console/Commands/UpdateFolderCounters.php | 46 + freescout-dist/app/Console/Kernel.php | 273 + freescout-dist/app/Conversation.php | 2424 +++ freescout-dist/app/ConversationFolder.php | 17 + freescout-dist/app/Customer.php | 1533 ++ freescout-dist/app/CustomerChannel.php | 58 + freescout-dist/app/Email.php | 87 + .../Events/ConversationCustomerChanged.php | 28 + .../app/Events/ConversationStatusChanged.php | 20 + .../app/Events/ConversationUserChanged.php | 23 + .../Events/CustomerCreatedConversation.php | 23 + freescout-dist/app/Events/CustomerReplied.php | 23 + .../RealtimeBroadcastNotificationCreated.php | 96 + freescout-dist/app/Events/RealtimeChat.php | 102 + .../app/Events/RealtimeConvNewThread.php | 118 + .../app/Events/RealtimeConvView.php | 113 + .../app/Events/RealtimeConvViewFinish.php | 62 + .../app/Events/RealtimeMailboxNewThread.php | 110 + freescout-dist/app/Events/UserAddedNote.php | 23 + .../app/Events/UserCreatedConversation.php | 23 + .../Events/UserCreatedConversationDraft.php | 23 + .../app/Events/UserCreatedThreadDraft.php | 23 + freescout-dist/app/Events/UserDeleted.php | 24 + freescout-dist/app/Events/UserReplied.php | 23 + freescout-dist/app/Exceptions/Handler.php | 55 + freescout-dist/app/FailedJob.php | 36 + freescout-dist/app/Folder.php | 456 + freescout-dist/app/Follower.php | 10 + .../Auth/ForgotPasswordController.php | 32 + .../Http/Controllers/Auth/LoginController.php | 79 + .../Controllers/Auth/RegisterController.php | 87 + .../Auth/ResetPasswordController.php | 62 + .../app/Http/Controllers/Controller.php | 13 + .../Controllers/ConversationsController.php | 3231 ++++ .../Http/Controllers/CustomersController.php | 414 + .../Http/Controllers/MailboxesController.php | 918 + .../Http/Controllers/ModulesController.php | 570 + .../app/Http/Controllers/OpenController.php | 228 + .../app/Http/Controllers/SecureController.php | 236 + .../Http/Controllers/SettingsController.php | 431 + .../app/Http/Controllers/SystemController.php | 416 + .../Http/Controllers/TranslateController.php | 94 + .../app/Http/Controllers/UsersController.php | 660 + freescout-dist/app/Http/Kernel.php | 70 + .../app/Http/Middleware/CheckRole.php | 43 + .../app/Http/Middleware/CustomHandle.php | 29 + .../app/Http/Middleware/EncryptCookies.php | 17 + .../app/Http/Middleware/FrameGuard.php | 32 + .../app/Http/Middleware/HttpsRedirect.php | 48 + .../app/Http/Middleware/Localize.php | 29 + .../app/Http/Middleware/LogoutIfDeleted.php | 30 + .../Middleware/RedirectIfAuthenticated.php | 27 + .../app/Http/Middleware/ResponseHeaders.php | 21 + .../app/Http/Middleware/TerminateHandler.php | 20 + .../app/Http/Middleware/TokenAuth.php | 34 + .../app/Http/Middleware/TrimStrings.php | 18 + .../app/Http/Middleware/TrustProxies.php | 29 + .../app/Http/Middleware/VerifyCsrfToken.php | 17 + freescout-dist/app/Job.php | 58 + .../app/Jobs/RestartQueueWorker.php | 41 + freescout-dist/app/Jobs/SendAlert.php | 105 + freescout-dist/app/Jobs/SendAutoReply.php | 148 + .../app/Jobs/SendEmailReplyError.php | 95 + .../app/Jobs/SendNotificationToUsers.php | 223 + .../app/Jobs/SendReplyToCustomer.php | 552 + freescout-dist/app/Jobs/TriggerAction.php | 44 + .../app/Jobs/UpdateFolderCounters.php | 42 + freescout-dist/app/Listeners/ActivateUser.php | 35 + .../app/Listeners/LogFailedLogin.php | 34 + freescout-dist/app/Listeners/LogLockout.php | 34 + .../app/Listeners/LogPasswordReset.php | 34 + .../app/Listeners/LogRegisteredUser.php | 34 + .../app/Listeners/LogSuccessfulLogin.php | 34 + .../app/Listeners/LogSuccessfulLogout.php | 34 + .../app/Listeners/LogUserDeletion.php | 34 + .../app/Listeners/ProcessSwiftMessage.php | 25 + .../app/Listeners/RefreshConversations.php | 30 + .../app/Listeners/RememberUserLocale.php | 31 + .../app/Listeners/RestartSwiftMailer.php | 30 + .../app/Listeners/SendAutoReply.php | 108 + .../app/Listeners/SendNotificationToUsers.php | 75 + .../app/Listeners/SendPasswordChanged.php | 30 + .../app/Listeners/SendReplyToCustomer.php | 67 + .../app/Listeners/UpdateMailboxCounters.php | 28 + freescout-dist/app/Mail/Alert.php | 50 + freescout-dist/app/Mail/AutoReply.php | 96 + freescout-dist/app/Mail/PasswordChanged.php | 37 + freescout-dist/app/Mail/ReplyToCustomer.php | 188 + freescout-dist/app/Mail/Test.php | 44 + .../app/Mail/UserEmailReplyError.php | 33 + freescout-dist/app/Mail/UserInvite.php | 38 + freescout-dist/app/Mail/UserNotification.php | 105 + freescout-dist/app/Mailbox.php | 978 + freescout-dist/app/MailboxUser.php | 27 + freescout-dist/app/Misc/Helper.php | 2122 +++ freescout-dist/app/Misc/Mail.php | 1044 ++ .../app/Misc/SwiftGetSmtpQueueId.php | 19 + freescout-dist/app/Misc/WpApi.php | 176 + freescout-dist/app/Module.php | 544 + .../Notifications/BroadcastNotification.php | 127 + .../app/Notifications/WebsiteNotification.php | 158 + .../app/Observers/AttachmentObserver.php | 18 + .../app/Observers/ConversationObserver.php | 49 + .../app/Observers/CustomerObserver.php | 33 + .../DatabaseNotificationObserver.php | 18 + .../app/Observers/EmailObserver.php | 18 + .../app/Observers/FollowerObserver.php | 18 + .../app/Observers/MailboxObserver.php | 42 + .../app/Observers/SendLogObserver.php | 20 + .../app/Observers/ThreadObserver.php | 98 + freescout-dist/app/Observers/UserObserver.php | 46 + freescout-dist/app/Option.php | 305 + .../app/Policies/ConversationPolicy.php | 118 + freescout-dist/app/Policies/FolderPolicy.php | 33 + freescout-dist/app/Policies/MailboxPolicy.php | 184 + freescout-dist/app/Policies/ThreadPolicy.php | 42 + freescout-dist/app/Policies/UserPolicy.php | 121 + .../app/Providers/AppServiceProvider.php | 96 + .../app/Providers/AuthServiceProvider.php | 33 + .../Providers/BroadcastServiceProvider.php | 27 + .../app/Providers/EventServiceProvider.php | 110 + .../app/Providers/PolycastServiceProvider.php | 197 + .../app/Providers/RouteServiceProvider.php | 81 + freescout-dist/app/SendLog.php | 187 + freescout-dist/app/Sendmail.php | 34 + freescout-dist/app/Subscription.php | 471 + freescout-dist/app/Thread.php | 1541 ++ freescout-dist/app/User.php | 1229 ++ freescout-dist/artisan | 78 + freescout-dist/bootstrap/app.php | 55 + freescout-dist/bootstrap/cache/.gitignore | 2 + freescout-dist/composer.json | 347 + freescout-dist/composer.lock | 7233 ++++++++ freescout-dist/config/activitylog.php | 44 + freescout-dist/config/app.php | 610 + freescout-dist/config/auth.php | 103 + freescout-dist/config/broadcasting.php | 65 + freescout-dist/config/cache.php | 94 + freescout-dist/config/database.php | 153 + freescout-dist/config/filesystems.php | 78 + freescout-dist/config/installer.php | 171 + freescout-dist/config/laroute.php | 60 + freescout-dist/config/mail.php | 132 + freescout-dist/config/minify.config.php | 84 + freescout-dist/config/modules.php | 186 + freescout-dist/config/purifier.php | 116 + freescout-dist/config/queue.php | 86 + freescout-dist/config/self-update.php | 134 + freescout-dist/config/services.php | 38 + freescout-dist/config/session.php | 197 + freescout-dist/config/subscriptions.php | 31 + freescout-dist/config/translation-manager.php | 88 + freescout-dist/config/trustedproxy.php | 74 + freescout-dist/config/view.php | 33 + freescout-dist/database/.gitignore | 1 + .../factories/ConversationFactory.php | 44 + .../database/factories/CustomerFactory.php | 13 + .../database/factories/EmailFactory.php | 9 + .../database/factories/FolderFactory.php | 21 + .../database/factories/MailboxFactory.php | 16 + .../database/factories/ThreadFactory.php | 39 + .../database/factories/UserFactory.php | 25 + ...04_02_193005_create_translations_table.php | 35 + .../2018_06_10_000000_create_users_table.php | 54 + ...10_100000_create_password_resets_table.php | 34 + ...18_06_25_065719_create_mailboxes_table.php | 65 + ...06_29_041002_create_mailbox_user_table.php | 38 + ...7_07_071443_create_activity_logs_table.php | 35 + .../2018_07_09_052314_create_emails_table.php | 38 + ...18_07_09_053559_create_customers_table.php | 59 + ...7_11_010333_create_conversations_table.php | 91 + ...2018_07_11_074558_create_folders_table.php | 37 + ...81928_create_conversation_folder_table.php | 37 + ...2018_07_12_003318_create_threads_table.php | 96 + .../2018_07_30_153206_create_jobs_table.php | 36 + ..._07_30_165237_create_failed_jobs_table.php | 35 + ..._08_04_063414_create_attachments_table.php | 41 + ...2018_08_05_045458_create_options_table.php | 32 + ...8_05_153518_create_subscriptions_table.php | 37 + ...18_08_06_114901_create_send_logs_table.php | 55 + ...9_05_024109_create_notifications_table.php | 37 + ...05_033609_create_polycast_events_table.php | 36 + ...2018_11_04_113009_create_modules_table.php | 35 + ..._11_13_143000_encrypt_mailbox_password.php | 48 + ...22617_add_locale_column_to_users_table.php | 32 + ...30728_add_status_column_to_users_table.php | 34 + ...nd_status_data_column_to_threads_table.php | 33 + ...alidate_cert_column_to_mailboxes_table.php | 32 + ..._meta_subtype_columns_to_threads_table.php | 35 + ...atus_message_column_in_send_logs_table.php | 32 + ...imap_folders_column_to_mailboxes_table.php | 32 + ...add_auto_bcc_column_to_mailboxes_table.php | 32 + ...before_reply_column_to_mailboxes_table.php | 42 + ...83015_add_meta_column_to_folders_table.php | 33 + ...nge_passwords_types_in_mailboxes_table.php | 34 + ...19_12_24_155120_create_followers_table.php | 34 + ..._add_hide_column_to_mailbox_user_table.php | 33 + ..._add_mute_column_to_mailbox_user_table.php | 33 + ...add_public_column_to_attachments_table.php | 45 + ...ate_in_imap_folders_in_mailboxes_table.php | 37 + ..._sent_folder_column_to_mailboxes_table.php | 32 + ...00_drop_slug_column_in_mailboxes_table.php | 34 + ..._history_column_to_conversations_table.php | 32 + ...dd_access_column_to_mailbox_user_table.php | 32 + ..._history_column_in_conversations_table.php | 33 + ...11_04_140000_change_foreign_keys_types.php | 73 + ...20_11_19_070000_update_customers_table.php | 36 + ...22_070000_move_user_permissions_to_env.php | 35 + ..._add_permissions_column_to_users_table.php | 32 + ...0_add_imported_column_to_threads_table.php | 32 + ...101_add_meta_column_to_mailboxes_table.php | 38 + ..._hash_column_to_ltm_translations_table.php | 32 + ...ange_string_columns_in_mailboxes_table.php | 33 + ..._channel_column_to_conversations_table.php | 32 + ...add_channel_columns_to_customers_table.php | 43 + ...101_add_meta_column_to_customers_table.php | 40 + ...21_090000_encrypt_mailbox_out_password.php | 41 + ...021_05_21_105200_encrypt_mail_password.php | 34 + ...101_add_indexes_to_conversations_table.php | 34 + ...1_remove_unique_index_in_folders_table.php | 34 + ...01_change_emails_column_in_users_table.php | 30 + ...add_meta_column_to_conversations_table.php | 33 + .../2022_12_18_010101_set_user_type_field.php | 32 + ..._set_numeric_phones_in_customers_table.php | 39 + ..._14_010101_change_deleted_folder_index.php | 30 + ...liases_reply_column_to_mailboxes_table.php | 33 + ...9_010101_create_customer_channel_table.php | 37 + ...020202_populate_customer_channel_table.php | 53 + ...dd_id_column_to_customer_channel_table.php | 32 + ...mtp_queue_id_column_to_send_logs_table.php | 32 + ...ange_aliases_column_in_mailboxes_table.php | 31 + .../database/seeds/CustomersTableSeeder.php | 19 + .../database/seeds/DatabaseSeeder.php | 47 + .../database/seeds/MailboxesTableSeeder.php | 16 + .../database/seeds/UsersTableSeeder.php | 16 + .../laravel-laroute/src/Routes/Collection.php | 75 + .../src/DataFormatter/QueryFormatter.php | 76 + .../src/JavascriptRenderer.php | 142 + .../src/Controller.php | 230 + .../src/Manager.php | 720 + .../Zipper/Repositories/ZipRepository.php | 190 + .../src/AbstractRepositoryType.php | 166 + .../GithubRepositoryType.php | 354 + .../minify/src/Providers/BaseProvider.php | 360 + .../Doctrine/DBAL/Driver/PDOConnection.php | 142 + .../DBAL/Driver/PDOQueryImplementation.php | 41 + .../lib/Doctrine/DBAL/Driver/PDOStatement.php | 312 + .../Driver/PDOStatementImplementations.php | 73 + .../DBAL/Platforms/PostgreSqlPlatform.php | 1297 ++ .../DBAL/Schema/PostgreSqlSchemaManager.php | 538 + .../library/HTMLPurifier/AttrDef/URI/Host.php | 146 + .../HTMLPurifier/AttrTransform/NameSync.php | 42 + .../library/HTMLPurifier/AttrValidator.php | 205 + .../library/HTMLPurifier/ChildDef/List.php | 94 + .../library/HTMLPurifier/Lexer.php | 383 + .../faker/src/Faker/Provider/Base.php | 616 + .../guzzlehttp/guzzle/src/Client.php | 501 + .../guzzle/src/Cookie/CookieJar.php | 321 + .../guzzlehttp/psr7/src/LazyOpenStream.php | 43 + .../src/MacroableModels.php | 95 + .../src/Illuminate/Auth/Events/Validated.php | 37 + .../Auth/Middleware/Authenticate.php | 70 + .../src/Illuminate/Auth/SessionGuard.php | 775 + .../Broadcasting/Broadcasters/Broadcaster.php | 204 + .../Illuminate/Cache/Console/ClearCommand.php | 137 + .../src/Illuminate/Cache/Repository.php | 588 + .../src/Illuminate/Config/Repository.php | 180 + .../src/Illuminate/Container/BoundMethod.php | 174 + .../src/Illuminate/Container/Container.php | 1248 ++ .../Illuminate/Cookie/CookieValuePrefix.php | 50 + .../Cookie/Middleware/EncryptCookies.php | 181 + .../Eloquent/Concerns/GuardsAttributes.php | 226 + .../Illuminate/Database/Eloquent/Factory.php | 256 + .../Illuminate/Database/Eloquent/Model.php | 1509 ++ .../src/Illuminate/Filesystem/Filesystem.php | 567 + .../Http/Middleware/VerifyCsrfToken.php | 167 + .../Illuminate/Foundation/PackageManifest.php | 174 + .../Foundation/ProviderRepository.php | 281 + .../Foundation/Testing/TestCase.php | 200 + .../framework/src/Illuminate/Http/Request.php | 626 + .../src/Illuminate/Mail/TransportManager.php | 209 + .../Pagination/AbstractPaginator.php | 591 + .../Pagination/LengthAwarePaginator.php | 201 + .../src/Illuminate/Pagination/Paginator.php | 179 + .../src/Illuminate/Queue/Listener.php | 248 + .../src/Illuminate/Routing/Controller.php | 70 + .../Illuminate/Routing/RouteCollection.php | 351 + .../Routing/RouteDependencyResolverTrait.php | 111 + .../Routing/RouteSignatureParameters.php | 45 + .../src/Illuminate/Routing/Router.php | 1218 ++ .../src/Illuminate/Routing/UrlGenerator.php | 657 + .../Illuminate/Session/FileSessionHandler.php | 119 + .../src/Illuminate/Support/Carbon.php | 50 + .../src/Illuminate/Support/Collection.php | 1802 ++ .../src/Illuminate/Support/Fluent.php | 196 + .../src/Illuminate/Support/MessageBag.php | 397 + .../src/Illuminate/Support/Optional.php | 112 + .../framework/src/Illuminate/Support/Str.php | 683 + .../src/Illuminate/Support/ViewErrorBag.php | 130 + .../Validation/Concerns/FormatsMessages.php | 384 + .../Concerns/ValidatesAttributes.php | 1478 ++ .../Illuminate/View/Compilers/Compiler.php | 74 + .../Compilers/Concerns/CompilesLayouts.php | 118 + .../View/Concerns/ManagesLayouts.php | 243 + .../framework/src/Illuminate/View/View.php | 416 + .../lord/laroute/src/Routes/Collection.php | 99 + .../DebugBar/DataFormatter/DataFormatter.php | 84 + .../debugbar/src/DebugBar/DebugBar.php | 495 + .../src/DebugBar/JavascriptRenderer.php | 1152 ++ .../overrides/natxet/cssmin/src/CssMin.php | 5157 ++++++ .../nesbot/carbon/src/Carbon/Carbon.php | 4944 +++++ .../src/Commands/stubs/command.stub | 68 + .../src/Commands/stubs/composer.stub | 25 + .../src/Commands/stubs/controller-plain.stub | 9 + .../src/Commands/stubs/controller.stub | 72 + .../src/Commands/stubs/event.stub | 30 + .../src/Commands/stubs/factory.stub | 9 + .../src/Commands/stubs/job-queued.stub | 34 + .../src/Commands/stubs/job.stub | 31 + .../src/Commands/stubs/json.stub | 22 + .../src/Commands/stubs/listener-duck.stub | 30 + .../Commands/stubs/listener-queued-duck.stub | 32 + .../src/Commands/stubs/listener-queued.stub | 33 + .../src/Commands/stubs/listener.stub | 31 + .../src/Commands/stubs/mail.stub | 33 + .../src/Commands/stubs/middleware.stub | 21 + .../src/Commands/stubs/migration/add.stub | 32 + .../src/Commands/stubs/migration/create.stub | 32 + .../src/Commands/stubs/migration/delete.stub | 32 + .../src/Commands/stubs/migration/drop.stub | 32 + .../src/Commands/stubs/migration/plain.stub | 28 + .../src/Commands/stubs/model.stub | 10 + .../src/Commands/stubs/notification.stub | 61 + .../src/Commands/stubs/policy.plain.stub | 20 + .../src/Commands/stubs/provider.stub | 35 + .../src/Commands/stubs/request.stub | 30 + .../Commands/stubs/resource-collection.stub | 19 + .../src/Commands/stubs/resource.stub | 19 + .../src/Commands/stubs/route-provider.stub | 40 + .../src/Commands/stubs/routes.stub | 6 + .../src/Commands/stubs/rule.stub | 40 + .../src/Commands/stubs/scaffold/config.stub | 5 + .../src/Commands/stubs/scaffold/provider.stub | 114 + .../src/Commands/stubs/seeder.stub | 21 + .../src/Commands/stubs/start.stub | 17 + .../src/Commands/stubs/unit-test.stub | 19 + .../src/Commands/stubs/views/index.stub | 9 + .../src/Commands/stubs/views/master.stub | 12 + .../nwidart/laravel-modules/src/Json.php | 256 + .../nwidart/laravel-modules/src/Module.php | 510 + .../laravel-modules/src/Repository.php | 843 + .../src/Controllers/EnvironmentController.php | 185 + .../src/Controllers/FinalController.php | 38 + .../src/Events/EnvironmentSaved.php | 30 + .../src/Helpers/DatabaseManager.php | 99 + .../src/Helpers/EnvironmentManager.php | 160 + .../src/Helpers/FinalInstallManager.php | 112 + .../src/Helpers/InstalledFileManager.php | 40 + .../src/Helpers/PermissionsChecker.php | 114 + .../src/Helpers/RequirementsChecker.php | 125 + .../src/Middleware/canInstall.php | 92 + .../LaravelInstallerServiceProvider.php | 64 + .../laravel-installer/src/Routes/web.php | 86 + .../overrides/ramsey/uuid/src/Uuid.php | 782 + .../LaravelLogViewer/LaravelLogViewer.php | 376 + .../overrides/spatie/string/src/Str.php | 460 + .../lib/classes/Swift/Attachment.php | 56 + .../lib/classes/Swift/EmbeddedFile.php | 55 + .../lib/classes/Swift/MailTransport.php | 48 + .../swiftmailer/lib/classes/Swift/Message.php | 280 + .../ContentEncoder/Base64ContentEncoder.php | 101 + .../lib/classes/Swift/Mime/MimePart.php | 208 + .../lib/classes/Swift/MimePart.php | 46 + .../lib/classes/Swift/SendmailTransport.php | 35 + .../lib/classes/Swift/SmtpTransport.php | 43 + .../lib/classes/Swift/SwiftException.php | 28 + .../Swift/Transport/Esmtp/AuthHandler.php | 268 + .../Swift/Transport/EsmtpTransport.php | 446 + .../classes/Swift/Transport/MailInvoker.php | 32 + .../classes/Swift/Transport/MailTransport.php | 262 + .../Swift/Transport/SimpleMailInvoker.php | 39 + .../classes/Swift/Transport/StreamBuffer.php | 328 + .../console/Descriptor/TextDescriptor.php | 342 + .../symfony/console/Helper/Helper.php | 138 + .../symfony/console/Helper/HelperSet.php | 108 + .../XPath/Extension/NodeExtension.php | 242 + .../symfony/debug/ExceptionHandler.php | 455 + .../overrides/symfony/finder/Finder.php | 751 + .../Iterator/DateRangeFilterIterator.php | 58 + .../Iterator/DepthRangeFilterIterator.php | 45 + .../ExcludeDirectoryFilterIterator.php | 84 + .../Iterator/FileTypeFilterIterator.php | 53 + .../Iterator/FilenameFilterIterator.php | 47 + .../finder/Iterator/FilterIterator.php | 60 + .../finder/Iterator/PathFilterIterator.php | 56 + .../Iterator/RecursiveDirectoryIterator.php | 156 + .../finder/Iterator/SortableIterator.php | 80 + .../symfony/http-foundation/AcceptHeader.php | 170 + .../symfony/http-foundation/Cookie.php | 292 + .../MimeType/FileBinaryMimeTypeGuesser.php | 99 + .../symfony/http-foundation/FileBag.php | 150 + .../symfony/http-foundation/HeaderBag.php | 331 + .../symfony/http-foundation/ParameterBag.php | 234 + .../symfony/http-foundation/Request.php | 2188 +++ .../symfony/http-foundation/Response.php | 1307 ++ .../http-foundation/ResponseHeaderBag.php | 341 + .../http-kernel/Exception/HttpException.php | 51 + .../symfony/http-kernel/HttpCache/Store.php | 503 + .../symfony/http-kernel/UriSigner.php | 106 + .../overrides/symfony/process/Process.php | 1751 ++ .../symfony/routing/CompiledRoute.php | 173 + .../overrides/symfony/routing/Route.php | 567 + .../symfony/var-dumper/Cloner/Data.php | 439 + .../symfony/var-dumper/Cloner/Stub.php | 67 + .../symfony/var-dumper/Dumper/HtmlDumper.php | 903 + .../overrides/tormjens/eventy/src/Action.php | 29 + .../overrides/tormjens/eventy/src/Event.php | 112 + .../overrides/tormjens/eventy/src/Filter.php | 32 + .../overrides/vlucas/phpdotenv/src/Loader.php | 427 + .../laravel-imap/src/IMAP/Attachment.php | 308 + .../webklex/laravel-imap/src/IMAP/Client.php | 576 + .../webklex/laravel-imap/src/IMAP/Message.php | 1315 ++ .../webklex/php-imap/src/Attachment.php | 388 + .../src/Connection/Protocols/ImapProtocol.php | 1162 ++ .../overrides/webklex/php-imap/src/Header.php | 815 + .../webklex/php-imap/src/Message.php | 1450 ++ .../webklex/php-imap/src/Structure.php | 174 + freescout-dist/package.json | 15 + freescout-dist/phpcs.xml | 162 + freescout-dist/phpunit.xml | 33 + freescout-dist/public/.htaccess | 21 + .../public/android-chrome-192x192.png | Bin 0 -> 5405 bytes .../public/android-chrome-256x256.png | Bin 0 -> 7380 bytes freescout-dist/public/apple-touch-icon.png | Bin 0 -> 11959 bytes freescout-dist/public/browserconfig.xml | 9 + freescout-dist/public/css/bootstrap-rtl.css | 1473 ++ freescout-dist/public/css/bootstrap.css | 6831 +++++++ freescout-dist/public/css/fonts.css | 64 + freescout-dist/public/css/magic-check.css | 1 + freescout-dist/public/css/select2/select2.css | 484 + .../public/css/select2/select2.min.css | 1 + freescout-dist/public/css/style-rtl.css | 500 + freescout-dist/public/css/style.css | 4524 +++++ freescout-dist/public/favicon.gif | Bin 0 -> 165 bytes freescout-dist/public/favicon.ico | Bin 0 -> 5430 bytes freescout-dist/public/favicon.png | Bin 0 -> 371 bytes .../glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../glyphicons-halflings-regular.svg | 288 + .../glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../LiberationSans-Bold-webfont.eot | Bin 0 -> 36962 bytes .../LiberationSans-Bold-webfont.svg | 352 + .../LiberationSans-Bold-webfont.ttf | Bin 0 -> 36756 bytes .../LiberationSans-Bold-webfont.woff | Bin 0 -> 21632 bytes .../LiberationSans-BoldItalic-webfont.eot | Bin 0 -> 37114 bytes .../LiberationSans-BoldItalic-webfont.svg | 348 + .../LiberationSans-BoldItalic-webfont.ttf | Bin 0 -> 36880 bytes .../LiberationSans-BoldItalic-webfont.woff | Bin 0 -> 22236 bytes .../LiberationSans-Italic-webfont.eot | Bin 0 -> 36818 bytes .../LiberationSans-Italic-webfont.svg | 349 + .../LiberationSans-Italic-webfont.ttf | Bin 0 -> 36604 bytes .../LiberationSans-Italic-webfont.woff | Bin 0 -> 21992 bytes .../LiberationSans-Regular-webfont.eot | Bin 0 -> 36342 bytes .../LiberationSans-Regular-webfont.svg | 356 + .../LiberationSans-Regular-webfont.ttf | Bin 0 -> 36140 bytes .../LiberationSans-Regular-webfont.woff | Bin 0 -> 21356 bytes freescout-dist/public/fonts/yekan/Yekan.eot | Bin 0 -> 17732 bytes freescout-dist/public/fonts/yekan/Yekan.otf | Bin 0 -> 37348 bytes freescout-dist/public/fonts/yekan/Yekan.ttf | Bin 0 -> 34964 bytes freescout-dist/public/fonts/yekan/Yekan.woff | Bin 0 -> 21336 bytes freescout-dist/public/fonts/yekan/Yekan.woff2 | Bin 0 -> 15908 bytes freescout-dist/public/img/banner.png | Bin 0 -> 2036 bytes freescout-dist/public/img/default-avatar.png | Bin 0 -> 6950 bytes freescout-dist/public/img/default-module.png | Bin 0 -> 4088 bytes freescout-dist/public/img/enable-push.png | Bin 0 -> 22529 bytes freescout-dist/public/img/loader-grey.gif | Bin 0 -> 2608 bytes freescout-dist/public/img/loader-main.gif | Bin 0 -> 1376 bytes freescout-dist/public/img/loader-tiny.gif | Bin 0 -> 1849 bytes freescout-dist/public/img/logo-300.png | Bin 0 -> 10089 bytes freescout-dist/public/img/logo-600.png | Bin 0 -> 19111 bytes freescout-dist/public/img/logo-brand.svg | 13 + freescout-dist/public/img/logo-icon-150.png | Bin 0 -> 3303 bytes .../public/img/logo-icon-white-300.png | Bin 0 -> 6197 bytes freescout-dist/public/index.php | 88 + freescout-dist/public/install.php | 231 + .../public/installer/css/fontawesome.css | 6 + freescout-dist/public/installer/css/style.css | 3541 ++++ .../public/installer/css/style.css.map | 7 + .../public/installer/css/style.min.css | 9 + .../public/installer/css/style.min.css.map | 7 + .../public/installer/fonts/FontAwesome.otf | Bin 0 -> 134808 bytes .../installer/fonts/fontawesome-webfont.eot | Bin 0 -> 165742 bytes .../installer/fonts/fontawesome-webfont.svg | 2671 +++ .../installer/fonts/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes .../installer/fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes .../installer/fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes .../public/installer/fonts/ionicons.eot | Bin 0 -> 120724 bytes .../public/installer/fonts/ionicons.svg | 2230 +++ .../public/installer/fonts/ionicons.ttf | Bin 0 -> 188508 bytes .../public/installer/fonts/ionicons.woff | Bin 0 -> 67904 bytes .../public/installer/img/pattern.png | Bin 0 -> 3354 bytes freescout-dist/public/js/bootstrap.js | 7 + .../css/bootstrap-editable.css | 663 + .../js/bootstrap3-editable/img/clear.png | Bin 0 -> 509 bytes .../js/bootstrap3-editable/img/loading.gif | Bin 0 -> 1849 bytes .../js/bootstrap-editable.js | 6807 +++++++ .../js/bootstrap-editable.min.js | 7 + .../css/dataTables.bootstrap.css | 187 + .../css/dataTables.bootstrap.min.css | 1 + .../css/dataTables.bootstrap4.css | 206 + .../css/dataTables.bootstrap4.min.css | 1 + .../css/dataTables.foundation.css | 118 + .../css/dataTables.foundation.min.css | 1 + .../css/dataTables.jqueryui.css | 481 + .../css/dataTables.jqueryui.min.css | 1 + .../css/dataTables.semanticui.css | 102 + .../css/dataTables.semanticui.min.css | 1 + .../css/jquery.dataTables.css | 448 + .../css/jquery.dataTables.min.css | 1 + .../DataTables-1.10.18/images/sort_asc.png | Bin 0 -> 160 bytes .../images/sort_asc_disabled.png | Bin 0 -> 148 bytes .../DataTables-1.10.18/images/sort_both.png | Bin 0 -> 201 bytes .../DataTables-1.10.18/images/sort_desc.png | Bin 0 -> 158 bytes .../images/sort_desc_disabled.png | Bin 0 -> 146 bytes .../js/dataTables.bootstrap.js | 182 + .../js/dataTables.bootstrap.min.js | 8 + .../js/dataTables.bootstrap4.js | 184 + .../js/dataTables.bootstrap4.min.js | 8 + .../js/dataTables.foundation.js | 174 + .../js/dataTables.foundation.min.js | 8 + .../js/dataTables.jqueryui.js | 164 + .../js/dataTables.jqueryui.min.js | 9 + .../js/dataTables.semanticui.js | 212 + .../js/dataTables.semanticui.min.js | 9 + .../js/jquery.dataTables.js | 15296 +++++++++++++++ .../js/jquery.dataTables.min.js | 166 + .../public/js/datatables/datatables.css | 462 + .../public/js/datatables/datatables.js | 15310 ++++++++++++++++ .../public/js/datatables/datatables.min.css | 15 + .../public/js/datatables/datatables.min.js | 180 + .../featherlight/featherlight.gallery.min.css | 8 + .../featherlight/featherlight.gallery.min.js | 7 + .../js/featherlight/featherlight.min.css | 8 + .../js/featherlight/featherlight.min.js | 8 + .../public/js/flatpickr/flatpickr.css | 785 + .../public/js/flatpickr/flatpickr.js | 2585 +++ .../public/js/flatpickr/flatpickr.min.css | 13 + .../public/js/flatpickr/flatpickr.min.js | 2 + freescout-dist/public/js/flatpickr/ie.css | 13 + freescout-dist/public/js/flatpickr/l10n/ar.js | 52 + freescout-dist/public/js/flatpickr/l10n/at.js | 69 + freescout-dist/public/js/flatpickr/l10n/az.js | 74 + freescout-dist/public/js/flatpickr/l10n/be.js | 75 + freescout-dist/public/js/flatpickr/l10n/bg.js | 66 + freescout-dist/public/js/flatpickr/l10n/bn.js | 64 + freescout-dist/public/js/flatpickr/l10n/bs.js | 66 + .../public/js/flatpickr/l10n/cat.js | 83 + freescout-dist/public/js/flatpickr/l10n/cs.js | 75 + freescout-dist/public/js/flatpickr/l10n/cy.js | 93 + freescout-dist/public/js/flatpickr/l10n/da.js | 71 + freescout-dist/public/js/flatpickr/l10n/de.js | 70 + .../public/js/flatpickr/l10n/default.js | 81 + freescout-dist/public/js/flatpickr/l10n/en.js | 0 freescout-dist/public/js/flatpickr/l10n/eo.js | 73 + freescout-dist/public/js/flatpickr/l10n/es.js | 70 + freescout-dist/public/js/flatpickr/l10n/et.js | 73 + freescout-dist/public/js/flatpickr/l10n/fa.js | 68 + freescout-dist/public/js/flatpickr/l10n/fi.js | 69 + freescout-dist/public/js/flatpickr/l10n/fo.js | 74 + freescout-dist/public/js/flatpickr/l10n/fr.js | 75 + freescout-dist/public/js/flatpickr/l10n/ga.js | 66 + freescout-dist/public/js/flatpickr/l10n/gr.js | 73 + freescout-dist/public/js/flatpickr/l10n/he.js | 58 + freescout-dist/public/js/flatpickr/l10n/hi.js | 64 + freescout-dist/public/js/flatpickr/l10n/hr.js | 66 + freescout-dist/public/js/flatpickr/l10n/hu.js | 73 + freescout-dist/public/js/flatpickr/l10n/id.js | 62 + freescout-dist/public/js/flatpickr/l10n/is.js | 72 + freescout-dist/public/js/flatpickr/l10n/it.js | 71 + freescout-dist/public/js/flatpickr/l10n/ja.js | 66 + freescout-dist/public/js/flatpickr/l10n/km.js | 74 + freescout-dist/public/js/flatpickr/l10n/ko.js | 68 + freescout-dist/public/js/flatpickr/l10n/kz.js | 74 + freescout-dist/public/js/flatpickr/l10n/lt.js | 72 + freescout-dist/public/js/flatpickr/l10n/lv.js | 67 + freescout-dist/public/js/flatpickr/l10n/mk.js | 68 + freescout-dist/public/js/flatpickr/l10n/mn.js | 59 + freescout-dist/public/js/flatpickr/l10n/ms.js | 67 + freescout-dist/public/js/flatpickr/l10n/my.js | 69 + freescout-dist/public/js/flatpickr/l10n/nl.js | 75 + freescout-dist/public/js/flatpickr/l10n/no.js | 73 + freescout-dist/public/js/flatpickr/l10n/pa.js | 65 + freescout-dist/public/js/flatpickr/l10n/pl.js | 73 + .../public/js/flatpickr/l10n/pt-br.js | 66 + .../public/js/flatpickr/l10n/pt-pt.js | 0 freescout-dist/public/js/flatpickr/l10n/ro.js | 69 + freescout-dist/public/js/flatpickr/l10n/ru.js | 75 + freescout-dist/public/js/flatpickr/l10n/si.js | 65 + freescout-dist/public/js/flatpickr/l10n/sk.js | 70 + freescout-dist/public/js/flatpickr/l10n/sl.js | 70 + freescout-dist/public/js/flatpickr/l10n/sq.js | 65 + .../public/js/flatpickr/l10n/sr-cyr.js | 67 + freescout-dist/public/js/flatpickr/l10n/sr.js | 68 + freescout-dist/public/js/flatpickr/l10n/sv.js | 70 + freescout-dist/public/js/flatpickr/l10n/th.js | 72 + freescout-dist/public/js/flatpickr/l10n/tr.js | 74 + freescout-dist/public/js/flatpickr/l10n/uk.js | 66 + freescout-dist/public/js/flatpickr/l10n/vn.js | 66 + .../public/js/flatpickr/l10n/zh-cn.js | 68 + .../public/js/flatpickr/l10n/zh-tw.js | 68 + .../js/flatpickr/plugins/confirmDate.css | 24 + .../js/flatpickr/plugins/confirmDate.js | 84 + .../js/flatpickr/plugins/labelPlugin.js | 31 + .../js/flatpickr/plugins/minMaxTimePlugin.js | 323 + .../js/flatpickr/plugins/rangePlugin.js | 146 + .../js/flatpickr/plugins/scrollPlugin.js | 58 + .../public/js/flatpickr/plugins/style.css | 69 + .../public/js/flatpickr/plugins/weekSelect.js | 86 + freescout-dist/public/js/html5sortable.js | 1214 ++ freescout-dist/public/js/jquery.js | 2 + freescout-dist/public/js/jquery.titlealert.js | 17 + freescout-dist/public/js/lang.js | 9 + freescout-dist/public/js/laroute.js | 259 + freescout-dist/public/js/main.js | 5645 ++++++ freescout-dist/public/js/parsley/i18n/al.js | 29 + freescout-dist/public/js/parsley/i18n/ar.js | 29 + freescout-dist/public/js/parsley/i18n/bg.js | 29 + freescout-dist/public/js/parsley/i18n/ca.js | 29 + .../public/js/parsley/i18n/cs.extra.js | 13 + freescout-dist/public/js/parsley/i18n/cs.js | 29 + freescout-dist/public/js/parsley/i18n/da.js | 29 + .../public/js/parsley/i18n/de.extra.js | 13 + freescout-dist/public/js/parsley/i18n/de.js | 29 + .../public/js/parsley/i18n/el.extra.js | 14 + freescout-dist/public/js/parsley/i18n/el.js | 29 + .../public/js/parsley/i18n/en.extra.js | 14 + freescout-dist/public/js/parsley/i18n/en.js | 30 + freescout-dist/public/js/parsley/i18n/es.js | 30 + freescout-dist/public/js/parsley/i18n/et.js | 29 + freescout-dist/public/js/parsley/i18n/eu.js | 29 + freescout-dist/public/js/parsley/i18n/fa.js | 29 + .../public/js/parsley/i18n/fi.extra.js | 6 + freescout-dist/public/js/parsley/i18n/fi.js | 29 + .../public/js/parsley/i18n/fr.extra.js | 14 + freescout-dist/public/js/parsley/i18n/fr.js | 29 + .../public/js/parsley/i18n/he.extra.js | 6 + freescout-dist/public/js/parsley/i18n/he.js | 29 + .../public/js/parsley/i18n/hr.extra.js | 14 + freescout-dist/public/js/parsley/i18n/hr.js | 29 + .../public/js/parsley/i18n/hu.extra.js | 14 + freescout-dist/public/js/parsley/i18n/hu.js | 30 + .../public/js/parsley/i18n/id.extra.js | 6 + freescout-dist/public/js/parsley/i18n/id.js | 29 + .../public/js/parsley/i18n/it.extra.js | 6 + freescout-dist/public/js/parsley/i18n/it.js | 29 + .../public/js/parsley/i18n/ja.extra.js | 14 + freescout-dist/public/js/parsley/i18n/ja.js | 29 + freescout-dist/public/js/parsley/i18n/ko.js | 29 + .../public/js/parsley/i18n/lt.extra.js | 14 + freescout-dist/public/js/parsley/i18n/lt.js | 29 + .../public/js/parsley/i18n/lv.extra.js | 14 + freescout-dist/public/js/parsley/i18n/lv.js | 29 + .../public/js/parsley/i18n/ms.extra.js | 13 + freescout-dist/public/js/parsley/i18n/ms.js | 29 + .../public/js/parsley/i18n/nl.extra.js | 11 + freescout-dist/public/js/parsley/i18n/nl.js | 26 + freescout-dist/public/js/parsley/i18n/no.js | 29 + freescout-dist/public/js/parsley/i18n/pl.js | 29 + .../public/js/parsley/i18n/pt-br.js | 29 + .../public/js/parsley/i18n/pt-pt.js | 29 + .../public/js/parsley/i18n/ro.extra.js | 14 + freescout-dist/public/js/parsley/i18n/ro.js | 29 + .../public/js/parsley/i18n/ru.extra.js | 14 + freescout-dist/public/js/parsley/i18n/ru.js | 29 + .../public/js/parsley/i18n/sk.extra.js | 13 + freescout-dist/public/js/parsley/i18n/sk.js | 29 + .../public/js/parsley/i18n/sl.extra.js | 14 + freescout-dist/public/js/parsley/i18n/sl.js | 30 + freescout-dist/public/js/parsley/i18n/sq.js | 29 + .../public/js/parsley/i18n/sr.extra.js | 14 + freescout-dist/public/js/parsley/i18n/sr.js | 29 + .../public/js/parsley/i18n/sv.extra.js | 6 + freescout-dist/public/js/parsley/i18n/sv.js | 29 + freescout-dist/public/js/parsley/i18n/th.js | 29 + freescout-dist/public/js/parsley/i18n/tk.js | 29 + freescout-dist/public/js/parsley/i18n/tr.js | 29 + .../public/js/parsley/i18n/ua.extra.js | 14 + freescout-dist/public/js/parsley/i18n/ua.js | 29 + .../public/js/parsley/i18n/uk.extra.js | 9 + freescout-dist/public/js/parsley/i18n/uk.js | 29 + .../public/js/parsley/i18n/zh-cn.extra.js | 6 + .../public/js/parsley/i18n/zh-cn.js | 29 + .../public/js/parsley/i18n/zh-tw.js | 29 + freescout-dist/public/js/parsley/parsley.js | 2494 +++ .../public/js/parsley/parsley.js.map | 1 + .../public/js/parsley/parsley.min.js | 18 + .../public/js/parsley/parsley.min.js.map | 1 + freescout-dist/public/js/polycast/polycast.js | 385 + .../public/js/polycast/polycast.min.js | 1 + freescout-dist/public/js/push/push.js | 1029 ++ freescout-dist/public/js/push/push.min.js | 37 + .../public/js/push/serviceWorker.min.js | 1 + freescout-dist/public/js/select2/i18n/af.js | 3 + freescout-dist/public/js/select2/i18n/ar.js | 3 + freescout-dist/public/js/select2/i18n/az.js | 3 + freescout-dist/public/js/select2/i18n/bg.js | 3 + freescout-dist/public/js/select2/i18n/bs.js | 3 + freescout-dist/public/js/select2/i18n/ca.js | 3 + freescout-dist/public/js/select2/i18n/cs.js | 3 + freescout-dist/public/js/select2/i18n/da.js | 3 + freescout-dist/public/js/select2/i18n/de.js | 3 + freescout-dist/public/js/select2/i18n/dsb.js | 3 + freescout-dist/public/js/select2/i18n/el.js | 3 + freescout-dist/public/js/select2/i18n/en.js | 3 + freescout-dist/public/js/select2/i18n/es.js | 3 + freescout-dist/public/js/select2/i18n/et.js | 3 + freescout-dist/public/js/select2/i18n/eu.js | 3 + freescout-dist/public/js/select2/i18n/fa.js | 3 + freescout-dist/public/js/select2/i18n/fi.js | 3 + freescout-dist/public/js/select2/i18n/fr.js | 3 + freescout-dist/public/js/select2/i18n/gl.js | 3 + freescout-dist/public/js/select2/i18n/he.js | 3 + freescout-dist/public/js/select2/i18n/hi.js | 3 + freescout-dist/public/js/select2/i18n/hr.js | 3 + freescout-dist/public/js/select2/i18n/hsb.js | 3 + freescout-dist/public/js/select2/i18n/hu.js | 3 + freescout-dist/public/js/select2/i18n/hy.js | 3 + freescout-dist/public/js/select2/i18n/id.js | 3 + freescout-dist/public/js/select2/i18n/is.js | 3 + freescout-dist/public/js/select2/i18n/it.js | 3 + freescout-dist/public/js/select2/i18n/ja.js | 3 + freescout-dist/public/js/select2/i18n/km.js | 3 + freescout-dist/public/js/select2/i18n/ko.js | 3 + freescout-dist/public/js/select2/i18n/lt.js | 3 + freescout-dist/public/js/select2/i18n/lv.js | 3 + freescout-dist/public/js/select2/i18n/mk.js | 3 + freescout-dist/public/js/select2/i18n/ms.js | 3 + freescout-dist/public/js/select2/i18n/nb.js | 3 + freescout-dist/public/js/select2/i18n/nl.js | 3 + freescout-dist/public/js/select2/i18n/pl.js | 3 + freescout-dist/public/js/select2/i18n/ps.js | 3 + .../public/js/select2/i18n/pt-BR.js | 3 + freescout-dist/public/js/select2/i18n/pt.js | 3 + freescout-dist/public/js/select2/i18n/ro.js | 3 + freescout-dist/public/js/select2/i18n/ru.js | 3 + freescout-dist/public/js/select2/i18n/sk.js | 3 + freescout-dist/public/js/select2/i18n/sl.js | 3 + .../public/js/select2/i18n/sr-Cyrl.js | 3 + freescout-dist/public/js/select2/i18n/sr.js | 3 + freescout-dist/public/js/select2/i18n/sv.js | 3 + freescout-dist/public/js/select2/i18n/th.js | 3 + freescout-dist/public/js/select2/i18n/tr.js | 3 + freescout-dist/public/js/select2/i18n/uk.js | 3 + freescout-dist/public/js/select2/i18n/vi.js | 3 + .../public/js/select2/i18n/zh-CN.js | 3 + .../public/js/select2/i18n/zh-TW.js | 3 + .../public/js/select2/select2.full.js | 6459 +++++++ .../public/js/select2/select2.full.min.js | 1 + freescout-dist/public/js/select2/select2.js | 5746 ++++++ .../public/js/select2/select2.min.js | 1 + .../public/js/summernote/font/summernote.eot | Bin 0 -> 16570 bytes .../public/js/summernote/font/summernote.ttf | Bin 0 -> 16384 bytes .../public/js/summernote/font/summernote.woff | Bin 0 -> 10184 bytes .../js/summernote/lang/summernote-ar-AR.js | 155 + .../js/summernote/lang/summernote-bg-BG.js | 155 + .../js/summernote/lang/summernote-ca-ES.js | 155 + .../js/summernote/lang/summernote-cs-CZ.js | 149 + .../js/summernote/lang/summernote-da-DK.js | 155 + .../js/summernote/lang/summernote-de-DE.js | 154 + .../js/summernote/lang/summernote-el-GR.js | 155 + .../js/summernote/lang/summernote-es-ES.js | 155 + .../js/summernote/lang/summernote-es-EU.js | 154 + .../js/summernote/lang/summernote-fa-IR.js | 155 + .../js/summernote/lang/summernote-fi-FI.js | 153 + .../js/summernote/lang/summernote-fr-FR.js | 155 + .../js/summernote/lang/summernote-gl-ES.js | 155 + .../js/summernote/lang/summernote-he-IL.js | 155 + .../js/summernote/lang/summernote-hr-HR.js | 155 + .../js/summernote/lang/summernote-hu-HU.js | 155 + .../js/summernote/lang/summernote-id-ID.js | 155 + .../js/summernote/lang/summernote-it-IT.js | 155 + .../js/summernote/lang/summernote-ja-JP.js | 155 + .../js/summernote/lang/summernote-ko-KR.js | 155 + .../js/summernote/lang/summernote-lt-LT.js | 155 + .../js/summernote/lang/summernote-lt-LV.js | 155 + .../js/summernote/lang/summernote-mn-MN.js | 157 + .../js/summernote/lang/summernote-nb-NO.js | 154 + .../js/summernote/lang/summernote-nl-NL.js | 155 + .../js/summernote/lang/summernote-pl-PL.js | 155 + .../js/summernote/lang/summernote-pt-BR.js | 155 + .../js/summernote/lang/summernote-pt-PT.js | 154 + .../js/summernote/lang/summernote-ro-RO.js | 155 + .../js/summernote/lang/summernote-ru-RU.js | 155 + .../js/summernote/lang/summernote-sk-SK.js | 153 + .../js/summernote/lang/summernote-sl-SI.js | 155 + .../summernote/lang/summernote-sr-RS-Latin.js | 155 + .../js/summernote/lang/summernote-sr-RS.js | 155 + .../js/summernote/lang/summernote-sv-SE.js | 155 + .../js/summernote/lang/summernote-ta-IN.js | 155 + .../js/summernote/lang/summernote-th-TH.js | 155 + .../js/summernote/lang/summernote-tr-TR.js | 155 + .../js/summernote/lang/summernote-uk-UA.js | 155 + .../js/summernote/lang/summernote-vi-VN.js | 155 + .../js/summernote/lang/summernote-zh-CN.js | 155 + .../js/summernote/lang/summernote-zh-TW.js | 155 + .../databasic/summernote-ext-databasic.css | 16 + .../databasic/summernote-ext-databasic.js | 292 + .../plugin/hello/summernote-ext-hello.js | 81 + .../summernote-ext-specialchars.js | 312 + .../public/js/summernote/summernote.css | 1 + .../public/js/summernote/summernote.js | 7330 ++++++++ .../public/js/summernote/summernote.min.js | 3 + freescout-dist/public/js/taphold.js | 117 + freescout-dist/public/mstile-150x150.png | Bin 0 -> 3938 bytes freescout-dist/public/robots.txt | 2 + freescout-dist/public/safari-pinned-tab.svg | 29 + freescout-dist/public/site.webmanifest | 19 + freescout-dist/public/tools.php | 215 + freescout-dist/resources/assets/js/app.js | 22 + .../resources/assets/js/bootstrap.js | 55 + .../assets/js/components/ExampleComponent.vue | 23 + freescout-dist/resources/assets/js/laroute.js | 202 + .../resources/assets/js/laroute_module.js | 10 + .../resources/assets/sass/_variables.scss | 38 + freescout-dist/resources/assets/sass/app.scss | 9 + freescout-dist/resources/lang/cs.json | 794 + freescout-dist/resources/lang/da.json | 433 + freescout-dist/resources/lang/de.json | 784 + freescout-dist/resources/lang/de/auth.php | 6 + .../resources/lang/de/installer_messages.php | 246 + .../resources/lang/de/passwords.php | 9 + .../resources/lang/de/validation.php | 83 + freescout-dist/resources/lang/en/auth.php | 6 + .../resources/lang/en/installer_messages.php | 246 + .../resources/lang/en/passwords.php | 9 + .../resources/lang/en/validation.php | 83 + freescout-dist/resources/lang/es.json | 760 + freescout-dist/resources/lang/fa.json | 757 + freescout-dist/resources/lang/fa/auth.php | 6 + .../resources/lang/fa/installer_messages.php | 247 + .../resources/lang/fa/passwords.php | 9 + .../resources/lang/fa/validation.php | 84 + freescout-dist/resources/lang/fi.json | 792 + freescout-dist/resources/lang/fr.json | 792 + freescout-dist/resources/lang/fr/auth.php | 17 + .../resources/lang/fr/pagination.php | 17 + .../resources/lang/fr/passwords.php | 20 + .../resources/lang/fr/validation.php | 175 + freescout-dist/resources/lang/hr.json | 14 + freescout-dist/resources/lang/it.json | 791 + freescout-dist/resources/lang/ja.json | 758 + freescout-dist/resources/lang/ko.json | 758 + freescout-dist/resources/lang/ko/auth.php | 6 + .../resources/lang/ko/installer_messages.php | 246 + .../resources/lang/ko/passwords.php | 9 + .../resources/lang/ko/validation.php | 83 + freescout-dist/resources/lang/nl.json | 718 + freescout-dist/resources/lang/nl/auth.php | 6 + .../resources/lang/nl/installer_messages.php | 246 + .../resources/lang/nl/passwords.php | 9 + .../resources/lang/nl/validation.php | 83 + freescout-dist/resources/lang/no.json | 793 + freescout-dist/resources/lang/pl.json | 806 + freescout-dist/resources/lang/pt-BR.json | 792 + freescout-dist/resources/lang/pt-BR/auth.php | 17 + .../resources/lang/pt-BR/pagination.php | 17 + .../resources/lang/pt-BR/passwords.php | 20 + .../resources/lang/pt-BR/validation.php | 180 + freescout-dist/resources/lang/pt-PT.json | 806 + freescout-dist/resources/lang/pt-PT/auth.php | 17 + .../resources/lang/pt-PT/pagination.php | 17 + .../resources/lang/pt-PT/passwords.php | 20 + .../resources/lang/pt-PT/validation.php | 180 + freescout-dist/resources/lang/ru.json | 785 + freescout-dist/resources/lang/sk.json | 293 + freescout-dist/resources/lang/sv.json | 69 + freescout-dist/resources/lang/zh-CN.json | 794 + freescout-dist/resources/lang/zh-CN/auth.php | 17 + .../resources/lang/zh-CN/pagination.php | 17 + .../resources/lang/zh-CN/passwords.php | 20 + .../lang/zh-CN/validation-inline.php | 138 + .../resources/lang/zh-CN/validation.php | 179 + .../resources/views/auth/banner.blade.php | 3 + .../resources/views/auth/login.blade.php | 78 + .../views/auth/passwords/email.blade.php | 50 + .../views/auth/passwords/reset.blade.php | 73 + .../resources/views/auth/register.blade.php | 94 + .../ajax_html/change_customer.blade.php | 39 + .../ajax_html/default_redirect.blade.php | 19 + .../ajax_html/merge_conv.blade.php | 56 + .../ajax_html/move_conv.blade.php | 16 + .../ajax_html/send_log.blade.php | 61 + .../ajax_html/show_original.blade.php | 24 + .../views/conversations/chats.blade.php | 18 + .../conversations_pagination.blade.php | 15 + .../conversations_table.blade.php | 223 + .../views/conversations/create.blade.php | 248 + .../editor_bottom_toolbar.blade.php | 91 + .../conversations/partials/badges.blade.php | 1 + .../partials/bulk_actions.blade.php | 50 + .../partials/customer_sidebar.blade.php | 36 + .../partials/edit_thread.blade.php | 11 + .../partials/merge_search_result.blade.php | 1 + .../partials/prev_convs_short.blade.php | 28 + .../partials/settings_modal.blade.php | 27 + .../conversations/partials/thread.blade.php | 326 + .../partials/thread_attachments.blade.php | 16 + .../conversations/partials/threads.blade.php | 6 + .../views/conversations/search.blade.php | 175 + .../views/conversations/thread_by.blade.php | 5 + .../views/conversations/view.blade.php | 309 + .../views/customers/conversations.blade.php | 23 + .../partials/customers_table.blade.php | 23 + .../customers/partials/edit_form.blade.php | 277 + .../views/customers/profile_menu.blade.php | 11 + .../views/customers/profile_snippet.blade.php | 68 + .../views/customers/profile_tabs.blade.php | 4 + .../views/customers/update.blade.php | 23 + .../emails/customer/auto_reply.blade.php | 18 + .../emails/customer/auto_reply_text.blade.php | 6 + .../emails/customer/reply_fancy.blade.php | 78 + .../customer/reply_fancy_text.blade.php | 16 + .../views/emails/user/alert.blade.php | 21 + .../emails/user/email_reply_error.blade.php | 8 + .../emails/user/layouts/system.blade.php | 83 + .../views/emails/user/notification.blade.php | 273 + .../emails/user/notification_text.blade.php | 33 + .../emails/user/password_changed.blade.php | 9 + .../user/password_changed_text.blade.php | 3 + .../views/emails/user/test.blade.php | 8 + .../views/emails/user/test_system.blade.php | 8 + .../views/emails/user/thread_by.blade.php | 1 + .../views/emails/user/user_invite.blade.php | 14 + .../emails/user/user_invite_text.blade.php | 10 + .../resources/views/errors/403.blade.php | 14 + .../resources/views/errors/404.blade.php | 8 + .../resources/views/errors/500.blade.php | 5 + .../resources/views/js/vars.blade.php | 94 + .../resources/views/layouts/app.blade.php | 306 + .../views/mailboxes/auto_reply.blade.php | 85 + .../views/mailboxes/connection.blade.php | 154 + .../mailboxes/connection_incoming.blade.php | 210 + .../views/mailboxes/connection_menu.blade.php | 4 + .../views/mailboxes/create.blade.php | 114 + .../views/mailboxes/mailboxes.blade.php | 29 + .../mailboxes/partials/chat_list.blade.php | 36 + .../mailboxes/partials/folders.blade.php | 33 + .../mailboxes/partials/mute_icon.blade.php | 1 + .../views/mailboxes/permissions.blade.php | 107 + .../views/mailboxes/settings_menu.blade.php | 23 + .../views/mailboxes/sidebar_menu.blade.php | 21 + .../mailboxes/sidebar_menu_view.blade.php | 35 + .../views/mailboxes/update.blade.php | 235 + .../resources/views/mailboxes/view.blade.php | 40 + .../resources/views/modules/modules.blade.php | 119 + .../partials/invalid_symlinks.blade.php | 8 + .../modules/partials/module_card.blade.php | 67 + .../views/modules/sidebar_menu.blade.php | 10 + .../resources/views/open/user_setup.blade.php | 150 + .../views/partials/calendar.blade.php | 2 + .../resources/views/partials/editor.blade.php | 9 + .../resources/views/partials/empty.blade.php | 9 + .../views/partials/field_error.blade.php | 9 + .../views/partials/flash_messages.blade.php | 42 + .../floating_flash_messages.blade.php | 44 + .../partials/include_datepicker.blade.php | 23 + .../views/partials/locale_options.blade.php | 19 + .../views/partials/person_photo.blade.php | 5 + .../partials/sidebar_menu_toggle.blade.php | 6 + .../views/partials/timezone_options.blade.php | 58 + .../views/secure/dashboard.blade.php | 70 + .../resources/views/secure/logs.blade.php | 82 + .../resources/views/settings/alerts.blade.php | 106 + .../resources/views/settings/emails.blade.php | 154 + .../views/settings/general.blade.php | 178 + .../resources/views/settings/view.blade.php | 32 + .../views/system/sidebar_menu.blade.php | 7 + .../resources/views/system/status.blade.php | 401 + .../resources/views/system/tools.blade.php | 57 + .../resources/views/users/create.blade.php | 156 + .../views/users/is_subscribed.blade.php | 1 + .../views/users/notifications.blade.php | 41 + .../partials/web_notifications.blade.php | 37 + .../resources/views/users/password.blade.php | 69 + .../views/users/permissions.blade.php | 87 + .../resources/views/users/profile.blade.php | 278 + .../views/users/sidebar_menu.blade.php | 23 + .../views/users/subscriptions_table.blade.php | 137 + .../resources/views/users/users.blade.php | 56 + .../installer/environment-classic.blade.php | 38 + .../installer/environment-wizard.blade.php | 285 + .../vendor/installer/environment.blade.php | 26 + .../views/vendor/installer/finished.blade.php | 70 + .../installer/layouts/master-update.blade.php | 43 + .../vendor/installer/layouts/master.blade.php | 109 + .../vendor/installer/permissions.blade.php | 41 + .../vendor/installer/requirements.blade.php | 51 + .../installer/update/finished.blade.php | 9 + .../installer/update/overview.blade.php | 9 + .../vendor/installer/update/welcome.blade.php | 11 + .../views/vendor/installer/welcome.blade.php | 21 + .../vendor/laravel-log-viewer/log.blade.php | 206 + .../views/vendor/mail/html/button.blade.php | 19 + .../views/vendor/mail/html/footer.blade.php | 11 + .../views/vendor/mail/html/header.blade.php | 7 + .../views/vendor/mail/html/layout.blade.php | 55 + .../views/vendor/mail/html/message.blade.php | 27 + .../views/vendor/mail/html/panel.blade.php | 13 + .../vendor/mail/html/promotion.blade.php | 7 + .../mail/html/promotion/button.blade.php | 13 + .../views/vendor/mail/html/subcopy.blade.php | 7 + .../views/vendor/mail/html/table.blade.php | 3 + .../views/vendor/mail/html/themes/default.css | 292 + .../vendor/mail/markdown/button.blade.php | 1 + .../vendor/mail/markdown/footer.blade.php | 1 + .../vendor/mail/markdown/header.blade.php | 1 + .../vendor/mail/markdown/layout.blade.php | 9 + .../vendor/mail/markdown/message.blade.php | 27 + .../vendor/mail/markdown/panel.blade.php | 1 + .../vendor/mail/markdown/promotion.blade.php | 1 + .../mail/markdown/promotion/button.blade.php | 1 + .../vendor/mail/markdown/subcopy.blade.php | 1 + .../vendor/mail/markdown/table.blade.php | 1 + .../vendor/notifications/email.blade.php | 58 + .../views/vendor/translation-manager/.gitkeep | 0 .../vendor/translation-manager/content.php | 271 + .../translation-manager/index.blade.php | 135 + .../vendor/translation-manager/index.php | 1 + freescout-dist/routes/channels.php | 16 + freescout-dist/routes/console.php | 18 + freescout-dist/routes/web.php | 126 + freescout-dist/server.php | 19 + freescout-dist/storage/app/.gitignore | 3 + freescout-dist/storage/app/public/.gitignore | 2 + freescout-dist/storage/debugbar/.gitignore | 2 + freescout-dist/storage/framework/.gitignore | 8 + .../storage/framework/cache/.gitignore | 2 + .../storage/framework/sessions/.gitignore | 2 + .../storage/framework/testing/.gitignore | 2 + .../storage/framework/views/.gitignore | 2 + freescout-dist/storage/logs/.gitignore | 2 + freescout-dist/tests/CreatesApplication.php | 25 + freescout-dist/tests/Feature/.gitkeep | 0 freescout-dist/tests/TestCase.php | 10 + freescout-dist/tests/Unit/.keyfile | 1 + freescout-dist/tests/Unit/ConfigTest.php | 83 + freescout-dist/tests/Unit/ExampleTest.php | 18 + .../anahkiasen/underscore-php/.travis.yml | 29 + .../underscore-php/config/config.php | 16 + .../anahkiasen/underscore-php/helpers.php | 27 + .../underscore-php/src/Dispatch.php | 96 + .../anahkiasen/underscore-php/src/Method.php | 178 + .../src/Methods/ArraysMethods.php | 558 + .../src/Methods/CollectionMethods.php | 375 + .../src/Methods/FunctionsMethods.php | 243 + .../src/Methods/NumberMethods.php | 32 + .../src/Methods/ObjectMethods.php | 29 + .../src/Methods/StringsMethods.php | 543 + .../anahkiasen/underscore-php/src/Parse.php | 257 + .../underscore-php/src/Traits/Repository.php | 275 + .../underscore-php/src/Types/Arrays.php | 30 + .../underscore-php/src/Types/Functions.php | 14 + .../underscore-php/src/Types/Number.php | 30 + .../underscore-php/src/Types/Object.php | 31 + .../underscore-php/src/Types/Strings.php | 21 + .../underscore-php/src/Underscore.php | 57 + freescout-dist/vendor/autoload.php | 25 + .../laravel-laroute/src/ServiceProvider.php | 28 + .../config/translation-manager.php | 69 + .../database/migrations/.gitkeep | 0 ...04_02_193005_create_translations_table.php | 37 + .../resources/views/.gitkeep | 0 .../resources/views/index.php | 276 + .../src/Console/CleanCommand.php | 41 + .../src/Console/ExportCommand.php | 87 + .../src/Console/FindCommand.php | 41 + .../src/Console/ImportCommand.php | 55 + .../src/Console/ResetCommand.php | 41 + .../src/Events/TranslationsExportedEvent.php | 24 + .../src/ManagerServiceProvider.php | 111 + .../src/Models/Translation.php | 55 + .../src/TranslationServiceProvider.php | 41 + .../src/Translator.php | 47 + freescout-dist/vendor/bin/doctrine-dbal | 117 + freescout-dist/vendor/bin/php-parse | 120 + freescout-dist/vendor/bin/psysh | 117 + .../vendor/chumper/zipper/.travis.yml | 32 + .../src/Chumper/Zipper/Facades/Zipper.php | 13 + .../Repositories/RepositoryInterface.php | 106 + .../zipper/src/Chumper/Zipper/Zipper.php | 622 + .../Chumper/Zipper/ZipperServiceProvider.php | 50 + .../codedge/laravel-selfupdater/.styleci.yml | 16 + .../codedge/laravel-selfupdater/.travis.yml | 18 + .../config/self-update.php | 116 + .../views/mails/update-available.blade.php | 5 + .../resources/views/self-update.blade.php | 0 .../src/AbstractRepositoryType.php | 162 + .../src/Commands/CheckForUpdate.php | 56 + .../SourceRepositoryTypeContract.php | 55 + .../src/Contracts/UpdaterContract.php | 15 + .../src/Events/HasWrongPermissions.php | 24 + .../src/Events/UpdateAvailable.php | 55 + .../src/Events/UpdateFailed.php | 44 + .../src/Events/UpdateSucceeded.php | 55 + .../SendUpdateAvailableNotification.php | 80 + .../SendUpdateSucceededNotification.php | 80 + .../src/SourceRepository.php | 127 + .../laravel-selfupdater/src/UpdaterFacade.php | 24 + .../src/UpdaterManager.php | 186 + .../src/UpdaterServiceProvider.php | 98 + .../vendor/composer/ClassLoader.php | 581 + .../vendor/composer/InstalledVersions.php | 352 + .../vendor/composer/autoload_classmap.php | 3273 ++++ .../vendor/composer/autoload_files.php | 31 + .../vendor/composer/autoload_namespaces.php | 16 + .../vendor/composer/autoload_psr4.php | 134 + .../vendor/composer/autoload_real.php | 48 + .../vendor/composer/autoload_static.php | 4073 ++++ freescout-dist/vendor/composer/installed.json | 5185 ++++++ freescout-dist/vendor/composer/installed.php | 891 + .../vendor/devfactory/minify/.travis.yml | 16 + .../Minify/Providers/JavaScriptSpec.php | 101 + .../Minify/Providers/StyleSheetSpec.php | 99 + .../minify/src/Contracts/MinifyInterface.php | 16 + .../Exceptions/CannotRemoveFileException.php | 3 + .../Exceptions/CannotSaveFileException.php | 3 + .../src/Exceptions/DirNotExistException.php | 3 + .../Exceptions/DirNotWritableException.php | 3 + .../src/Exceptions/FileNotExistException.php | 3 + .../Exceptions/InvalidArgumentException.php | 3 + .../minify/src/Facades/MinifyFacade.php | 16 + .../vendor/devfactory/minify/src/Minify.php | 259 + .../minify/src/MinifyServiceProvider.php | 89 + .../minify/src/Providers/JavaScript.php | 34 + .../minify/src/Providers/StyleSheet.php | 34 + .../devfactory/minify/src/config/config.php | 84 + .../dnoegel/php-xdg-base-dir/src/Xdg.php | 132 + .../vendor/doctrine/cache/.coveralls.yml | 4 + .../vendor/doctrine/cache/.travis.yml | 42 + .../vendor/doctrine/cache/build.properties | 3 + .../lib/Doctrine/Common/Cache/ApcCache.php | 118 + .../lib/Doctrine/Common/Cache/ApcuCache.php | 106 + .../lib/Doctrine/Common/Cache/ArrayCache.php | 142 + .../cache/lib/Doctrine/Common/Cache/Cache.php | 116 + .../Doctrine/Common/Cache/CacheProvider.php | 312 + .../lib/Doctrine/Common/Cache/ChainCache.php | 147 + .../Doctrine/Common/Cache/ClearableCache.php | 40 + .../Doctrine/Common/Cache/CouchbaseCache.php | 121 + .../lib/Doctrine/Common/Cache/FileCache.php | 286 + .../Doctrine/Common/Cache/FilesystemCache.php | 111 + .../Doctrine/Common/Cache/FlushableCache.php | 37 + .../Doctrine/Common/Cache/MemcacheCache.php | 126 + .../Doctrine/Common/Cache/MemcachedCache.php | 147 + .../Doctrine/Common/Cache/MongoDBCache.php | 197 + .../Doctrine/Common/Cache/MultiGetCache.php | 39 + .../Doctrine/Common/Cache/MultiPutCache.php | 41 + .../Doctrine/Common/Cache/PhpFileCache.php | 120 + .../lib/Doctrine/Common/Cache/PredisCache.php | 136 + .../lib/Doctrine/Common/Cache/RedisCache.php | 180 + .../lib/Doctrine/Common/Cache/RiakCache.php | 250 + .../Doctrine/Common/Cache/SQLite3Cache.php | 220 + .../lib/Doctrine/Common/Cache/Version.php | 25 + .../lib/Doctrine/Common/Cache/VoidCache.php | 78 + .../Doctrine/Common/Cache/WinCacheCache.php | 109 + .../lib/Doctrine/Common/Cache/XcacheCache.php | 112 + .../Doctrine/Common/Cache/ZendDataCache.php | 83 + .../vendor/doctrine/dbal/bin/doctrine-dbal | 4 + .../doctrine/dbal/bin/doctrine-dbal.php | 61 + .../lib/Doctrine/DBAL/Abstraction/Result.php | 45 + .../Doctrine/DBAL/Cache/ArrayStatement.php | 242 + .../Doctrine/DBAL/Cache/CacheException.php | 27 + .../Doctrine/DBAL/Cache/QueryCacheProfile.php | 123 + .../DBAL/Cache/ResultCacheStatement.php | 344 + .../dbal/lib/Doctrine/DBAL/ColumnCase.php | 34 + .../dbal/lib/Doctrine/DBAL/Configuration.php | 164 + .../dbal/lib/Doctrine/DBAL/Connection.php | 2167 +++ .../lib/Doctrine/DBAL/ConnectionException.php | 41 + .../Connections/MasterSlaveConnection.php | 101 + .../PrimaryReadReplicaConnection.php | 435 + .../dbal/lib/Doctrine/DBAL/DBALException.php | 314 + .../dbal/lib/Doctrine/DBAL/Driver.php | 61 + .../DBAL/Driver/AbstractDB2Driver.php | 54 + .../DBAL/Driver/AbstractDriverException.php | 12 + .../DBAL/Driver/AbstractException.php | 60 + .../DBAL/Driver/AbstractMySQLDriver.php | 256 + .../DBAL/Driver/AbstractOracleDriver.php | 111 + .../EasyConnectString.php | 121 + .../DBAL/Driver/AbstractPostgreSQLDriver.php | 171 + .../DBAL/Driver/AbstractSQLAnywhereDriver.php | 168 + .../DBAL/Driver/AbstractSQLServerDriver.php | 107 + .../Exception/PortWithoutHost.php | 20 + .../DBAL/Driver/AbstractSQLiteDriver.php | 116 + .../lib/Doctrine/DBAL/Driver/Connection.php | 97 + .../Doctrine/DBAL/Driver/DriverException.php | 12 + .../Driver/DrizzlePDOMySql/Connection.php | 24 + .../DBAL/Driver/DrizzlePDOMySql/Driver.php | 65 + .../lib/Doctrine/DBAL/Driver/Exception.php | 34 + .../DBAL/Driver/ExceptionConverterDriver.php | 29 + .../lib/Doctrine/DBAL/Driver/FetchUtils.php | 75 + .../DBAL/Driver/IBMDB2/Connection.php | 7 + .../DBAL/Driver/IBMDB2/DB2Connection.php | 214 + .../Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php | 40 + .../DBAL/Driver/IBMDB2/DB2Exception.php | 14 + .../DBAL/Driver/IBMDB2/DB2Statement.php | 560 + .../DBAL/Driver/IBMDB2/DataSourceName.php | 77 + .../Doctrine/DBAL/Driver/IBMDB2/Driver.php | 9 + .../Exception/CannotCopyStreamToStream.php | 29 + .../Exception/CannotCreateTemporaryFile.php | 29 + .../Exception/CannotWriteToTemporaryFile.php | 29 + .../IBMDB2/Exception/ConnectionError.php | 26 + .../IBMDB2/Exception/ConnectionFailed.php | 23 + .../Driver/IBMDB2/Exception/PrepareFailed.php | 27 + .../IBMDB2/Exception/StatementError.php | 26 + .../Doctrine/DBAL/Driver/IBMDB2/Statement.php | 7 + .../DBAL/Driver/Mysqli/Connection.php | 7 + .../Doctrine/DBAL/Driver/Mysqli/Driver.php | 29 + .../Mysqli/Exception/ConnectionError.php | 21 + .../Mysqli/Exception/ConnectionFailed.php | 21 + .../Exception/FailedReadingStreamOffset.php | 22 + .../Driver/Mysqli/Exception/InvalidOption.php | 28 + .../Mysqli/Exception/StatementError.php | 21 + .../Driver/Mysqli/Exception/UnknownType.php | 25 + .../DBAL/Driver/Mysqli/MysqliConnection.php | 320 + .../DBAL/Driver/Mysqli/MysqliException.php | 14 + .../DBAL/Driver/Mysqli/MysqliStatement.php | 570 + .../Doctrine/DBAL/Driver/Mysqli/Statement.php | 7 + .../Doctrine/DBAL/Driver/OCI8/Connection.php | 7 + .../lib/Doctrine/DBAL/Driver/OCI8/Driver.php | 55 + .../Exception/NonTerminatedStringLiteral.php | 27 + .../OCI8/Exception/SequenceDoesNotExist.php | 20 + .../OCI8/Exception/UnknownParameterIndex.php | 24 + .../DBAL/Driver/OCI8/OCI8Connection.php | 259 + .../DBAL/Driver/OCI8/OCI8Exception.php | 27 + .../DBAL/Driver/OCI8/OCI8Statement.php | 654 + .../Doctrine/DBAL/Driver/OCI8/Statement.php | 7 + .../Doctrine/DBAL/Driver/PDO/Connection.php | 9 + .../Doctrine/DBAL/Driver/PDO/Exception.php | 20 + .../Doctrine/DBAL/Driver/PDO/MySQL/Driver.php | 9 + .../Doctrine/DBAL/Driver/PDO/OCI/Driver.php | 9 + .../Doctrine/DBAL/Driver/PDO/PgSQL/Driver.php | 9 + .../DBAL/Driver/PDO/SQLSrv/Connection.php | 9 + .../DBAL/Driver/PDO/SQLSrv/Driver.php | 9 + .../DBAL/Driver/PDO/SQLSrv/Statement.php | 9 + .../DBAL/Driver/PDO/SQLite/Driver.php | 9 + .../Doctrine/DBAL/Driver/PDO/Statement.php | 9 + .../lib/Doctrine/DBAL/Driver/PDOException.php | 56 + .../Doctrine/DBAL/Driver/PDOIbm/Driver.php | 63 + .../Doctrine/DBAL/Driver/PDOMySql/Driver.php | 78 + .../Doctrine/DBAL/Driver/PDOOracle/Driver.php | 59 + .../Doctrine/DBAL/Driver/PDOPgSql/Driver.php | 121 + .../Doctrine/DBAL/Driver/PDOSqlite/Driver.php | 86 + .../DBAL/Driver/PDOSqlsrv/Connection.php | 44 + .../Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php | 100 + .../DBAL/Driver/PDOSqlsrv/Statement.php | 46 + .../DBAL/Driver/PingableConnection.php | 19 + .../dbal/lib/Doctrine/DBAL/Driver/Result.php | 89 + .../Doctrine/DBAL/Driver/ResultStatement.php | 110 + .../DBAL/Driver/SQLAnywhere/Driver.php | 95 + .../SQLAnywhere/SQLAnywhereConnection.php | 235 + .../SQLAnywhere/SQLAnywhereException.php | 74 + .../SQLAnywhere/SQLAnywhereStatement.php | 465 + .../DBAL/Driver/SQLSrv/Connection.php | 7 + .../Doctrine/DBAL/Driver/SQLSrv/Driver.php | 62 + .../DBAL/Driver/SQLSrv/Exception/Error.php | 47 + .../DBAL/Driver/SQLSrv/LastInsertId.php | 32 + .../DBAL/Driver/SQLSrv/SQLSrvConnection.php | 219 + .../DBAL/Driver/SQLSrv/SQLSrvException.php | 24 + .../DBAL/Driver/SQLSrv/SQLSrvStatement.php | 537 + .../Doctrine/DBAL/Driver/SQLSrv/Statement.php | 7 + .../DBAL/Driver/ServerInfoAwareConnection.php | 25 + .../lib/Doctrine/DBAL/Driver/Statement.php | 109 + .../DBAL/Driver/StatementIterator.php | 29 + .../dbal/lib/Doctrine/DBAL/DriverManager.php | 455 + .../DBAL/Event/ConnectionEventArgs.php | 61 + .../DBAL/Event/Listeners/MysqlSessionInit.php | 58 + .../Event/Listeners/OracleSessionInit.php | 74 + .../DBAL/Event/Listeners/SQLSessionInit.php | 41 + .../SchemaAlterTableAddColumnEventArgs.php | 82 + .../SchemaAlterTableChangeColumnEventArgs.php | 82 + .../DBAL/Event/SchemaAlterTableEventArgs.php | 69 + .../SchemaAlterTableRemoveColumnEventArgs.php | 82 + .../SchemaAlterTableRenameColumnEventArgs.php | 97 + .../Event/SchemaColumnDefinitionEventArgs.php | 108 + .../SchemaCreateTableColumnEventArgs.php | 82 + .../DBAL/Event/SchemaCreateTableEventArgs.php | 97 + .../DBAL/Event/SchemaDropTableEventArgs.php | 69 + .../Doctrine/DBAL/Event/SchemaEventArgs.php | 32 + .../Event/SchemaIndexDefinitionEventArgs.php | 92 + .../dbal/lib/Doctrine/DBAL/Events.php | 33 + .../dbal/lib/Doctrine/DBAL/Exception.php | 10 + .../DBAL/Exception/ConnectionException.php | 12 + .../DBAL/Exception/ConnectionLost.php | 10 + .../ConstraintViolationException.php | 12 + .../DatabaseObjectExistsException.php | 16 + .../DatabaseObjectNotFoundException.php | 16 + .../DBAL/Exception/DeadlockException.php | 12 + .../DBAL/Exception/DriverException.php | 56 + ...ForeignKeyConstraintViolationException.php | 12 + .../Exception/InvalidArgumentException.php | 21 + .../Exception/InvalidFieldNameException.php | 12 + .../Exception/LockWaitTimeoutException.php | 12 + .../Doctrine/DBAL/Exception/NoKeyValue.php | 25 + .../Exception/NonUniqueFieldNameException.php | 12 + .../NotNullConstraintViolationException.php | 12 + .../DBAL/Exception/ReadOnlyException.php | 12 + .../DBAL/Exception/RetryableException.php | 14 + .../DBAL/Exception/ServerException.php | 12 + .../DBAL/Exception/SyntaxErrorException.php | 12 + .../DBAL/Exception/TableExistsException.php | 12 + .../DBAL/Exception/TableNotFoundException.php | 12 + .../UniqueConstraintViolationException.php | 12 + .../dbal/lib/Doctrine/DBAL/FetchMode.php | 75 + .../lib/Doctrine/DBAL/Id/TableGenerator.php | 160 + .../DBAL/Id/TableGeneratorSchemaVisitor.php | 71 + .../dbal/lib/Doctrine/DBAL/LockMode.php | 23 + .../lib/Doctrine/DBAL/Logging/DebugStack.php | 62 + .../Doctrine/DBAL/Logging/EchoSQLLogger.php | 40 + .../lib/Doctrine/DBAL/Logging/LoggerChain.php | 52 + .../lib/Doctrine/DBAL/Logging/SQLLogger.php | 29 + .../dbal/lib/Doctrine/DBAL/ParameterType.php | 65 + .../DBAL/Platforms/AbstractPlatform.php | 3692 ++++ .../Doctrine/DBAL/Platforms/DB2Platform.php | 925 + .../DBAL/Platforms/DateIntervalUnit.php | 31 + .../DBAL/Platforms/DrizzlePlatform.php | 645 + .../DBAL/Platforms/Keywords/DB2Keywords.php | 420 + .../Platforms/Keywords/DrizzleKeywords.php | 326 + .../DBAL/Platforms/Keywords/KeywordList.php | 54 + .../Platforms/Keywords/MariaDb102Keywords.php | 271 + .../DBAL/Platforms/Keywords/MsSQLKeywords.php | 21 + .../Platforms/Keywords/MySQL57Keywords.php | 263 + .../Platforms/Keywords/MySQL80Keywords.php | 65 + .../DBAL/Platforms/Keywords/MySQLKeywords.php | 265 + .../Platforms/Keywords/OracleKeywords.php | 139 + .../Keywords/PostgreSQL100Keywords.php | 16 + .../Keywords/PostgreSQL91Keywords.php | 126 + .../Keywords/PostgreSQL92Keywords.php | 29 + .../Keywords/PostgreSQL94Keywords.php | 32 + .../Platforms/Keywords/PostgreSQLKeywords.php | 113 + .../Keywords/ReservedKeywordsValidator.php | 127 + .../Keywords/SQLAnywhere11Keywords.php | 39 + .../Keywords/SQLAnywhere12Keywords.php | 48 + .../Keywords/SQLAnywhere16Keywords.php | 39 + .../Keywords/SQLAnywhereKeywords.php | 262 + .../Keywords/SQLServer2005Keywords.php | 39 + .../Keywords/SQLServer2008Keywords.php | 31 + .../Keywords/SQLServer2012Keywords.php | 37 + .../Platforms/Keywords/SQLServerKeywords.php | 209 + .../Platforms/Keywords/SQLiteKeywords.php | 147 + .../DBAL/Platforms/MariaDb1027Platform.php | 35 + .../DBAL/Platforms/MySQL57Platform.php | 71 + .../DBAL/Platforms/MySQL80Platform.php | 17 + .../Doctrine/DBAL/Platforms/MySqlPlatform.php | 1201 ++ .../DBAL/Platforms/OraclePlatform.php | 1247 ++ .../DBAL/Platforms/PostgreSQL100Platform.php | 33 + .../DBAL/Platforms/PostgreSQL91Platform.php | 50 + .../DBAL/Platforms/PostgreSQL92Platform.php | 72 + .../DBAL/Platforms/PostgreSQL94Platform.php | 41 + .../DBAL/Platforms/SQLAnywhere11Platform.php | 28 + .../DBAL/Platforms/SQLAnywhere12Platform.php | 116 + .../DBAL/Platforms/SQLAnywhere16Platform.php | 39 + .../DBAL/Platforms/SQLAnywherePlatform.php | 1525 ++ .../DBAL/Platforms/SQLAzurePlatform.php | 35 + .../DBAL/Platforms/SQLServer2005Platform.php | 48 + .../DBAL/Platforms/SQLServer2008Platform.php | 120 + .../DBAL/Platforms/SQLServer2012Platform.php | 146 + .../DBAL/Platforms/SQLServerPlatform.php | 1718 ++ .../DBAL/Platforms/SqlitePlatform.php | 1283 ++ .../lib/Doctrine/DBAL/Platforms/TrimMode.php | 23 + .../Doctrine/DBAL/Portability/Connection.php | 128 + .../DBAL/Portability/OptimizeFlags.php | 44 + .../Doctrine/DBAL/Portability/Statement.php | 400 + .../Query/Expression/CompositeExpression.php | 165 + .../Query/Expression/ExpressionBuilder.php | 309 + .../lib/Doctrine/DBAL/Query/QueryBuilder.php | 1411 ++ .../Doctrine/DBAL/Query/QueryException.php | 39 + .../dbal/lib/Doctrine/DBAL/SQLParserUtils.php | 311 + .../Doctrine/DBAL/SQLParserUtilsException.php | 37 + .../Doctrine/DBAL/Schema/AbstractAsset.php | 213 + .../DBAL/Schema/AbstractSchemaManager.php | 1150 ++ .../dbal/lib/Doctrine/DBAL/Schema/Column.php | 454 + .../lib/Doctrine/DBAL/Schema/ColumnDiff.php | 59 + .../lib/Doctrine/DBAL/Schema/Comparator.php | 556 + .../lib/Doctrine/DBAL/Schema/Constraint.php | 43 + .../Doctrine/DBAL/Schema/DB2SchemaManager.php | 239 + .../DBAL/Schema/DrizzleSchemaManager.php | 104 + .../DBAL/Schema/ForeignKeyConstraint.php | 399 + .../lib/Doctrine/DBAL/Schema/Identifier.php | 27 + .../dbal/lib/Doctrine/DBAL/Schema/Index.php | 376 + .../DBAL/Schema/MySqlSchemaManager.php | 372 + .../DBAL/Schema/OracleSchemaManager.php | 419 + .../DBAL/Schema/SQLAnywhereSchemaManager.php | 238 + .../DBAL/Schema/SQLServerSchemaManager.php | 354 + .../dbal/lib/Doctrine/DBAL/Schema/Schema.php | 482 + .../lib/Doctrine/DBAL/Schema/SchemaConfig.php | 100 + .../lib/Doctrine/DBAL/Schema/SchemaDiff.php | 170 + .../Doctrine/DBAL/Schema/SchemaException.php | 187 + .../lib/Doctrine/DBAL/Schema/Sequence.php | 140 + .../DBAL/Schema/SqliteSchemaManager.php | 563 + .../AbstractSchemaSynchronizer.php | 49 + .../Synchronizer/SchemaSynchronizer.php | 74 + .../SingleDatabaseSynchronizer.php | 160 + .../dbal/lib/Doctrine/DBAL/Schema/Table.php | 909 + .../lib/Doctrine/DBAL/Schema/TableDiff.php | 152 + .../dbal/lib/Doctrine/DBAL/Schema/View.php | 30 + .../DBAL/Schema/Visitor/AbstractVisitor.php | 47 + .../Visitor/CreateSchemaSqlCollector.php | 99 + .../Schema/Visitor/DropSchemaSqlCollector.php | 101 + .../Doctrine/DBAL/Schema/Visitor/Graphviz.php | 168 + .../DBAL/Schema/Visitor/NamespaceVisitor.php | 18 + .../Schema/Visitor/RemoveNamespacedAssets.php | 79 + .../DBAL/Schema/Visitor/SchemaDiffVisitor.php | 50 + .../Doctrine/DBAL/Schema/Visitor/Visitor.php | 46 + .../DBAL/Sharding/PoolingShardConnection.php | 264 + .../DBAL/Sharding/PoolingShardManager.php | 103 + .../SQLAzureFederationsSynchronizer.php | 284 + .../SQLAzure/SQLAzureShardManager.php | 211 + .../SQLAzure/Schema/MultiTenantVisitor.php | 152 + .../ShardChoser/MultiTenantShardChoser.php | 22 + .../DBAL/Sharding/ShardChoser/ShardChoser.php | 23 + .../Doctrine/DBAL/Sharding/ShardManager.php | 76 + .../DBAL/Sharding/ShardingException.php | 63 + .../dbal/lib/Doctrine/DBAL/Statement.php | 589 + .../Tools/Console/Command/ImportCommand.php | 143 + .../Console/Command/ReservedWordsCommand.php | 243 + .../Tools/Console/Command/RunSqlCommand.php | 120 + .../DBAL/Tools/Console/ConnectionNotFound.php | 9 + .../DBAL/Tools/Console/ConnectionProvider.php | 15 + .../SingleConnectionProvider.php | 38 + .../DBAL/Tools/Console/ConsoleRunner.php | 118 + .../Tools/Console/Helper/ConnectionHelper.php | 47 + .../dbal/lib/Doctrine/DBAL/Tools/Dumper.php | 182 + .../DBAL/TransactionIsolationLevel.php | 35 + .../lib/Doctrine/DBAL/Types/ArrayType.php | 73 + .../Doctrine/DBAL/Types/AsciiStringType.php | 32 + .../lib/Doctrine/DBAL/Types/BigIntType.php | 44 + .../lib/Doctrine/DBAL/Types/BinaryType.php | 67 + .../dbal/lib/Doctrine/DBAL/Types/BlobType.php | 67 + .../lib/Doctrine/DBAL/Types/BooleanType.php | 52 + .../DBAL/Types/ConversionException.php | 123 + .../Doctrine/DBAL/Types/DateImmutableType.php | 70 + .../Doctrine/DBAL/Types/DateIntervalType.php | 88 + .../DBAL/Types/DateTimeImmutableType.php | 76 + .../lib/Doctrine/DBAL/Types/DateTimeType.php | 73 + .../DBAL/Types/DateTimeTzImmutableType.php | 70 + .../Doctrine/DBAL/Types/DateTimeTzType.php | 79 + .../dbal/lib/Doctrine/DBAL/Types/DateType.php | 66 + .../lib/Doctrine/DBAL/Types/DecimalType.php | 35 + .../lib/Doctrine/DBAL/Types/FloatType.php | 32 + .../dbal/lib/Doctrine/DBAL/Types/GuidType.php | 35 + .../lib/Doctrine/DBAL/Types/IntegerType.php | 44 + .../lib/Doctrine/DBAL/Types/JsonArrayType.php | 47 + .../dbal/lib/Doctrine/DBAL/Types/JsonType.php | 84 + .../lib/Doctrine/DBAL/Types/ObjectType.php | 72 + .../DBAL/Types/PhpDateTimeMappingType.php | 12 + .../DBAL/Types/PhpIntegerMappingType.php | 12 + .../Doctrine/DBAL/Types/SimpleArrayType.php | 68 + .../lib/Doctrine/DBAL/Types/SmallIntType.php | 44 + .../lib/Doctrine/DBAL/Types/StringType.php | 35 + .../dbal/lib/Doctrine/DBAL/Types/TextType.php | 38 + .../Doctrine/DBAL/Types/TimeImmutableType.php | 70 + .../dbal/lib/Doctrine/DBAL/Types/TimeType.php | 66 + .../dbal/lib/Doctrine/DBAL/Types/Type.php | 386 + .../lib/Doctrine/DBAL/Types/TypeRegistry.php | 127 + .../dbal/lib/Doctrine/DBAL/Types/Types.php | 47 + .../DBAL/Types/VarDateTimeImmutableType.php | 68 + .../Doctrine/DBAL/Types/VarDateTimeType.php | 35 + .../dbal/lib/Doctrine/DBAL/Version.php | 36 + .../DBAL/VersionAwarePlatformDriver.php | 28 + freescout-dist/vendor/doctrine/dbal/psalm.xml | 240 + .../lib/Doctrine/Deprecations/Deprecation.php | 312 + .../PHPUnit/VerifyDeprecations.php | 66 + .../vendor/doctrine/deprecations/phpstan.neon | 9 + .../vendor/doctrine/deprecations/psalm.xml | 30 + .../vendor/doctrine/event-manager/psalm.xml | 15 + .../doctrine/event-manager/src/EventArgs.php | 45 + .../event-manager/src/EventManager.php | 156 + .../event-manager/src/EventSubscriber.php | 21 + .../Doctrine/Common/Inflector/Inflector.php | 499 + .../Doctrine/Common/Lexer/AbstractLexer.php | 327 + .../EmailValidator/EmailLexer.php | 235 + .../EmailValidator/EmailParser.php | 104 + .../EmailValidator/EmailValidator.php | 67 + .../Exception/AtextAfterCFWS.php | 9 + .../EmailValidator/Exception/CRLFAtTheEnd.php | 9 + .../EmailValidator/Exception/CRLFX2.php | 9 + .../EmailValidator/Exception/CRNoLF.php | 9 + .../Exception/CharNotAllowed.php | 9 + .../Exception/CommaInDomain.php | 9 + .../Exception/ConsecutiveAt.php | 9 + .../Exception/ConsecutiveDot.php | 9 + .../Exception/DomainHyphened.php | 9 + .../EmailValidator/Exception/DotAtEnd.php | 9 + .../EmailValidator/Exception/DotAtStart.php | 9 + .../EmailValidator/Exception/ExpectingAT.php | 9 + .../Exception/ExpectingATEXT.php | 9 + .../Exception/ExpectingCTEXT.php | 9 + .../Exception/ExpectingDTEXT.php | 9 + .../Exception/ExpectingDomainLiteralClose.php | 9 + .../Exception/ExpectingQPair.php | 9 + .../EmailValidator/Exception/InvalidEmail.php | 14 + .../EmailValidator/Exception/NoDNSRecord.php | 9 + .../EmailValidator/Exception/NoDomainPart.php | 9 + .../EmailValidator/Exception/NoLocalPart.php | 9 + .../Exception/UnclosedComment.php | 9 + .../Exception/UnclosedQuotedString.php | 9 + .../Exception/UnopenedComment.php | 9 + .../EmailValidator/Parser/DomainPart.php | 386 + .../EmailValidator/Parser/LocalPart.php | 138 + .../EmailValidator/Parser/Parser.php | 215 + .../Validation/DNSCheckValidation.php | 72 + .../Validation/EmailValidation.php | 34 + .../Validation/Error/RFCWarnings.php | 11 + .../Validation/Error/SpoofEmail.php | 11 + .../Exception/EmptyValidationList.php | 13 + .../Validation/MultipleErrors.php | 26 + .../Validation/MultipleValidationWithAnd.php | 111 + .../Validation/NoRFCWarningsValidation.php | 41 + .../Validation/RFCValidation.php | 49 + .../Validation/SpoofCheckValidation.php | 45 + .../EmailValidator/Warning/AddressLiteral.php | 14 + .../EmailValidator/Warning/CFWSNearAt.php | 13 + .../EmailValidator/Warning/CFWSWithFWS.php | 13 + .../EmailValidator/Warning/Comment.php | 13 + .../Warning/DeprecatedComment.php | 13 + .../EmailValidator/Warning/DomainLiteral.php | 14 + .../EmailValidator/Warning/DomainTooLong.php | 14 + .../EmailValidator/Warning/EmailTooLong.php | 15 + .../EmailValidator/Warning/IPV6BadChar.php | 14 + .../EmailValidator/Warning/IPV6ColonEnd.php | 14 + .../EmailValidator/Warning/IPV6ColonStart.php | 14 + .../EmailValidator/Warning/IPV6Deprecated.php | 14 + .../Warning/IPV6DoubleColon.php | 14 + .../EmailValidator/Warning/IPV6GroupCount.php | 14 + .../EmailValidator/Warning/IPV6MaxGroups.php | 14 + .../EmailValidator/Warning/LabelTooLong.php | 14 + .../EmailValidator/Warning/LocalTooLong.php | 15 + .../EmailValidator/Warning/NoDNSMXRecord.php | 14 + .../EmailValidator/Warning/ObsoleteDTEXT.php | 14 + .../EmailValidator/Warning/QuotedPart.php | 13 + .../EmailValidator/Warning/QuotedString.php | 13 + .../EmailValidator/Warning/TLD.php | 13 + .../EmailValidator/Warning/Warning.php | 30 + .../src/ElementReference/Resolver.php | 169 + .../src/ElementReference/Subject.php | 153 + .../src/ElementReference/Usage.php | 49 + .../src/Exceptions/NestingException.php | 36 + .../enshrined/svg-sanitize/src/Helper.php | 53 + .../enshrined/svg-sanitize/src/Sanitizer.php | 669 + .../src/data/AllowedAttributes.php | 355 + .../svg-sanitize/src/data/AllowedTags.php | 196 + .../src/data/AttributeInterface.php | 18 + .../svg-sanitize/src/data/TagInterface.php | 19 + .../enshrined/svg-sanitize/src/data/XPath.php | 64 + .../svg-sanitize/src/svg-scanner.php | 192 + .../vendor/erusev/parsedown/Parsedown.php | 1693 ++ .../vendor/ezyang/htmlpurifier/CREDITS | 9 + .../vendor/ezyang/htmlpurifier/INSTALL | 341 + .../ezyang/htmlpurifier/INSTALL.fr.utf8 | 60 + .../vendor/ezyang/htmlpurifier/NEWS | 1224 ++ .../vendor/ezyang/htmlpurifier/TODO | 150 + .../vendor/ezyang/htmlpurifier/VERSION | 1 + .../vendor/ezyang/htmlpurifier/WHATSNEW | 2 + .../vendor/ezyang/htmlpurifier/WYSIWYG | 20 + .../extras/ConfigDoc/HTMLXSLTProcessor.php | 91 + .../ezyang/htmlpurifier/extras/FSTools.php | 164 + .../htmlpurifier/extras/FSTools/File.php | 141 + .../extras/HTMLPurifierExtras.auto.php | 11 + .../HTMLPurifierExtras.autoload-legacy.php | 15 + .../extras/HTMLPurifierExtras.autoload.php | 23 + .../extras/HTMLPurifierExtras.php | 31 + .../vendor/ezyang/htmlpurifier/extras/README | 32 + .../library/HTMLPurifier.auto.php | 11 + .../library/HTMLPurifier.autoload-legacy.php | 15 + .../library/HTMLPurifier.autoload.php | 24 + .../library/HTMLPurifier.composer.php | 4 + .../library/HTMLPurifier.func.php | 25 + .../library/HTMLPurifier.includes.php | 234 + .../library/HTMLPurifier.kses.php | 30 + .../library/HTMLPurifier.path.php | 11 + .../htmlpurifier/library/HTMLPurifier.php | 296 + .../library/HTMLPurifier.safe-includes.php | 228 + .../library/HTMLPurifier/Arborize.php | 71 + .../library/HTMLPurifier/AttrCollections.php | 148 + .../library/HTMLPurifier/AttrDef.php | 144 + .../library/HTMLPurifier/AttrDef/CSS.php | 136 + .../HTMLPurifier/AttrDef/CSS/AlphaValue.php | 34 + .../HTMLPurifier/AttrDef/CSS/Background.php | 111 + .../AttrDef/CSS/BackgroundPosition.php | 157 + .../HTMLPurifier/AttrDef/CSS/Border.php | 56 + .../HTMLPurifier/AttrDef/CSS/Color.php | 161 + .../HTMLPurifier/AttrDef/CSS/Composite.php | 48 + .../AttrDef/CSS/DenyElementDecorator.php | 44 + .../HTMLPurifier/AttrDef/CSS/Filter.php | 77 + .../library/HTMLPurifier/AttrDef/CSS/Font.php | 176 + .../HTMLPurifier/AttrDef/CSS/FontFamily.php | 219 + .../HTMLPurifier/AttrDef/CSS/Ident.php | 32 + .../AttrDef/CSS/ImportantDecorator.php | 56 + .../HTMLPurifier/AttrDef/CSS/Length.php | 77 + .../HTMLPurifier/AttrDef/CSS/ListStyle.php | 112 + .../HTMLPurifier/AttrDef/CSS/Multiple.php | 71 + .../HTMLPurifier/AttrDef/CSS/Number.php | 84 + .../HTMLPurifier/AttrDef/CSS/Percentage.php | 54 + .../AttrDef/CSS/TextDecoration.php | 46 + .../library/HTMLPurifier/AttrDef/CSS/URI.php | 77 + .../library/HTMLPurifier/AttrDef/Clone.php | 44 + .../library/HTMLPurifier/AttrDef/Enum.php | 73 + .../HTMLPurifier/AttrDef/HTML/Bool.php | 48 + .../HTMLPurifier/AttrDef/HTML/Class.php | 48 + .../HTMLPurifier/AttrDef/HTML/Color.php | 51 + .../HTMLPurifier/AttrDef/HTML/FrameTarget.php | 38 + .../library/HTMLPurifier/AttrDef/HTML/ID.php | 113 + .../HTMLPurifier/AttrDef/HTML/Length.php | 56 + .../HTMLPurifier/AttrDef/HTML/LinkTypes.php | 72 + .../HTMLPurifier/AttrDef/HTML/MultiLength.php | 60 + .../HTMLPurifier/AttrDef/HTML/Nmtokens.php | 70 + .../HTMLPurifier/AttrDef/HTML/Pixels.php | 76 + .../library/HTMLPurifier/AttrDef/Integer.php | 91 + .../library/HTMLPurifier/AttrDef/Lang.php | 86 + .../library/HTMLPurifier/AttrDef/Switch.php | 53 + .../library/HTMLPurifier/AttrDef/Text.php | 21 + .../library/HTMLPurifier/AttrDef/URI.php | 111 + .../HTMLPurifier/AttrDef/URI/Email.php | 20 + .../AttrDef/URI/Email/SimpleCheck.php | 29 + .../library/HTMLPurifier/AttrDef/URI/IPv4.php | 45 + .../library/HTMLPurifier/AttrDef/URI/IPv6.php | 89 + .../library/HTMLPurifier/AttrTransform.php | 60 + .../HTMLPurifier/AttrTransform/Background.php | 28 + .../HTMLPurifier/AttrTransform/BdoDir.php | 27 + .../HTMLPurifier/AttrTransform/BgColor.php | 28 + .../HTMLPurifier/AttrTransform/BoolToCSS.php | 47 + .../HTMLPurifier/AttrTransform/Border.php | 26 + .../HTMLPurifier/AttrTransform/EnumToCSS.php | 68 + .../AttrTransform/ImgRequired.php | 47 + .../HTMLPurifier/AttrTransform/ImgSpace.php | 61 + .../HTMLPurifier/AttrTransform/Input.php | 56 + .../HTMLPurifier/AttrTransform/Lang.php | 31 + .../HTMLPurifier/AttrTransform/Length.php | 45 + .../HTMLPurifier/AttrTransform/Name.php | 33 + .../HTMLPurifier/AttrTransform/Nofollow.php | 52 + .../HTMLPurifier/AttrTransform/SafeEmbed.php | 25 + .../HTMLPurifier/AttrTransform/SafeObject.php | 28 + .../HTMLPurifier/AttrTransform/SafeParam.php | 79 + .../AttrTransform/ScriptRequired.php | 23 + .../AttrTransform/TargetBlank.php | 45 + .../AttrTransform/TargetNoopener.php | 37 + .../AttrTransform/TargetNoreferrer.php | 37 + .../HTMLPurifier/AttrTransform/Textarea.php | 27 + .../library/HTMLPurifier/AttrTypes.php | 96 + .../library/HTMLPurifier/Bootstrap.php | 124 + .../library/HTMLPurifier/CSSDefinition.php | 533 + .../library/HTMLPurifier/ChildDef.php | 52 + .../HTMLPurifier/ChildDef/Chameleon.php | 67 + .../library/HTMLPurifier/ChildDef/Custom.php | 102 + .../library/HTMLPurifier/ChildDef/Empty.php | 38 + .../HTMLPurifier/ChildDef/Optional.php | 45 + .../HTMLPurifier/ChildDef/Required.php | 118 + .../ChildDef/StrictBlockquote.php | 110 + .../library/HTMLPurifier/ChildDef/Table.php | 224 + .../library/HTMLPurifier/Config.php | 920 + .../library/HTMLPurifier/ConfigSchema.php | 176 + .../ConfigSchema/Builder/ConfigSchema.php | 48 + .../HTMLPurifier/ConfigSchema/Builder/Xml.php | 144 + .../HTMLPurifier/ConfigSchema/Exception.php | 11 + .../HTMLPurifier/ConfigSchema/Interchange.php | 47 + .../ConfigSchema/Interchange/Directive.php | 89 + .../ConfigSchema/Interchange/Id.php | 58 + .../ConfigSchema/InterchangeBuilder.php | 226 + .../HTMLPurifier/ConfigSchema/Validator.php | 248 + .../ConfigSchema/ValidatorAtom.php | 130 + .../HTMLPurifier/ConfigSchema/schema.ser | 1 + .../HTMLPurifier/ConfigSchema/schema/info.ini | 3 + .../library/HTMLPurifier/ContentSets.php | 170 + .../library/HTMLPurifier/Context.php | 95 + .../library/HTMLPurifier/Definition.php | 55 + .../library/HTMLPurifier/DefinitionCache.php | 129 + .../DefinitionCache/Decorator.php | 112 + .../DefinitionCache/Decorator/Cleanup.php | 78 + .../DefinitionCache/Decorator/Memory.php | 85 + .../DefinitionCache/Decorator/Template.php.in | 82 + .../HTMLPurifier/DefinitionCache/Null.php | 76 + .../DefinitionCache/Serializer.php | 311 + .../DefinitionCache/Serializer/README | 3 + .../HTMLPurifier/DefinitionCacheFactory.php | 106 + .../library/HTMLPurifier/Doctype.php | 73 + .../library/HTMLPurifier/DoctypeRegistry.php | 142 + .../library/HTMLPurifier/ElementDef.php | 216 + .../library/HTMLPurifier/Encoder.php | 617 + .../library/HTMLPurifier/EntityLookup.php | 48 + .../HTMLPurifier/EntityLookup/entities.ser | 1 + .../library/HTMLPurifier/EntityParser.php | 285 + .../library/HTMLPurifier/ErrorCollector.php | 244 + .../library/HTMLPurifier/ErrorStruct.php | 74 + .../library/HTMLPurifier/Exception.php | 12 + .../library/HTMLPurifier/Filter.php | 56 + .../Filter/ExtractStyleBlocks.php | 341 + .../library/HTMLPurifier/Filter/YouTube.php | 65 + .../library/HTMLPurifier/Generator.php | 286 + .../library/HTMLPurifier/HTMLDefinition.php | 493 + .../library/HTMLPurifier/HTMLModule.php | 284 + .../library/HTMLPurifier/HTMLModule/Bdo.php | 44 + .../HTMLModule/CommonAttributes.php | 31 + .../library/HTMLPurifier/HTMLModule/Edit.php | 55 + .../library/HTMLPurifier/HTMLModule/Forms.php | 190 + .../HTMLPurifier/HTMLModule/Hypertext.php | 40 + .../HTMLPurifier/HTMLModule/Iframe.php | 51 + .../library/HTMLPurifier/HTMLModule/Image.php | 49 + .../HTMLPurifier/HTMLModule/Legacy.php | 186 + .../library/HTMLPurifier/HTMLModule/List.php | 51 + .../library/HTMLPurifier/HTMLModule/Name.php | 26 + .../HTMLPurifier/HTMLModule/Nofollow.php | 25 + .../HTMLModule/NonXMLCommonAttributes.php | 20 + .../HTMLPurifier/HTMLModule/Object.php | 62 + .../HTMLPurifier/HTMLModule/Presentation.php | 42 + .../HTMLPurifier/HTMLModule/Proprietary.php | 40 + .../library/HTMLPurifier/HTMLModule/Ruby.php | 36 + .../HTMLPurifier/HTMLModule/SafeEmbed.php | 40 + .../HTMLPurifier/HTMLModule/SafeObject.php | 62 + .../HTMLPurifier/HTMLModule/SafeScripting.php | 40 + .../HTMLPurifier/HTMLModule/Scripting.php | 73 + .../HTMLModule/StyleAttribute.php | 33 + .../HTMLPurifier/HTMLModule/Tables.php | 75 + .../HTMLPurifier/HTMLModule/Target.php | 28 + .../HTMLPurifier/HTMLModule/TargetBlank.php | 24 + .../HTMLModule/TargetNoopener.php | 21 + .../HTMLModule/TargetNoreferrer.php | 21 + .../library/HTMLPurifier/HTMLModule/Text.php | 87 + .../library/HTMLPurifier/HTMLModule/Tidy.php | 230 + .../HTMLPurifier/HTMLModule/Tidy/Name.php | 33 + .../HTMLModule/Tidy/Proprietary.php | 34 + .../HTMLPurifier/HTMLModule/Tidy/Strict.php | 43 + .../HTMLModule/Tidy/Transitional.php | 16 + .../HTMLPurifier/HTMLModule/Tidy/XHTML.php | 26 + .../HTMLModule/Tidy/XHTMLAndHTML4.php | 179 + .../HTMLModule/XMLCommonAttributes.php | 20 + .../HTMLPurifier/HTMLModuleManager.php | 467 + .../library/HTMLPurifier/IDAccumulator.php | 57 + .../library/HTMLPurifier/Injector.php | 283 + .../HTMLPurifier/Injector/AutoParagraph.php | 356 + .../HTMLPurifier/Injector/DisplayLinkURI.php | 40 + .../library/HTMLPurifier/Injector/Linkify.php | 64 + .../HTMLPurifier/Injector/PurifierLinkify.php | 71 + .../HTMLPurifier/Injector/RemoveEmpty.php | 112 + .../Injector/RemoveSpansWithoutAttributes.php | 84 + .../HTMLPurifier/Injector/SafeObject.php | 124 + .../library/HTMLPurifier/Language.php | 204 + .../Language/classes/en-x-test.php | 9 + .../Language/messages/en-x-test.php | 13 + .../Language/messages/en-x-testmini.php | 14 + .../HTMLPurifier/Language/messages/en.php | 55 + .../library/HTMLPurifier/LanguageFactory.php | 209 + .../library/HTMLPurifier/Length.php | 162 + .../library/HTMLPurifier/Lexer/DOMLex.php | 338 + .../library/HTMLPurifier/Lexer/DirectLex.php | 539 + .../library/HTMLPurifier/Lexer/PH5P.php | 4788 +++++ .../library/HTMLPurifier/Node.php | 49 + .../library/HTMLPurifier/Node/Comment.php | 36 + .../library/HTMLPurifier/Node/Element.php | 59 + .../library/HTMLPurifier/Node/Text.php | 54 + .../library/HTMLPurifier/PercentEncoder.php | 111 + .../library/HTMLPurifier/Printer.php | 218 + .../HTMLPurifier/Printer/CSSDefinition.php | 44 + .../HTMLPurifier/Printer/ConfigForm.css | 10 + .../HTMLPurifier/Printer/ConfigForm.js | 5 + .../HTMLPurifier/Printer/ConfigForm.php | 451 + .../HTMLPurifier/Printer/HTMLDefinition.php | 324 + .../library/HTMLPurifier/PropertyList.php | 122 + .../HTMLPurifier/PropertyListIterator.php | 42 + .../library/HTMLPurifier/Queue.php | 56 + .../library/HTMLPurifier/Strategy.php | 26 + .../HTMLPurifier/Strategy/Composite.php | 30 + .../library/HTMLPurifier/Strategy/Core.php | 17 + .../HTMLPurifier/Strategy/FixNesting.php | 181 + .../HTMLPurifier/Strategy/MakeWellFormed.php | 659 + .../Strategy/RemoveForeignElements.php | 207 + .../Strategy/ValidateAttributes.php | 45 + .../library/HTMLPurifier/StringHash.php | 47 + .../library/HTMLPurifier/StringHashParser.php | 136 + .../library/HTMLPurifier/TagTransform.php | 37 + .../HTMLPurifier/TagTransform/Font.php | 114 + .../HTMLPurifier/TagTransform/Simple.php | 44 + .../library/HTMLPurifier/Token.php | 100 + .../library/HTMLPurifier/Token/Comment.php | 38 + .../library/HTMLPurifier/Token/Empty.php | 15 + .../library/HTMLPurifier/Token/End.php | 24 + .../library/HTMLPurifier/Token/Start.php | 10 + .../library/HTMLPurifier/Token/Tag.php | 68 + .../library/HTMLPurifier/Token/Text.php | 53 + .../library/HTMLPurifier/TokenFactory.php | 118 + .../htmlpurifier/library/HTMLPurifier/URI.php | 316 + .../library/HTMLPurifier/URIDefinition.php | 112 + .../library/HTMLPurifier/URIFilter.php | 74 + .../URIFilter/DisableExternal.php | 54 + .../URIFilter/DisableExternalResources.php | 25 + .../URIFilter/DisableResources.php | 22 + .../HTMLPurifier/URIFilter/HostBlacklist.php | 46 + .../HTMLPurifier/URIFilter/MakeAbsolute.php | 158 + .../library/HTMLPurifier/URIFilter/Munge.php | 115 + .../HTMLPurifier/URIFilter/SafeIframe.php | 68 + .../library/HTMLPurifier/URIParser.php | 71 + .../library/HTMLPurifier/URIScheme.php | 102 + .../library/HTMLPurifier/URIScheme/data.php | 136 + .../library/HTMLPurifier/URIScheme/file.php | 44 + .../library/HTMLPurifier/URIScheme/ftp.php | 58 + .../library/HTMLPurifier/URIScheme/http.php | 36 + .../library/HTMLPurifier/URIScheme/https.php | 18 + .../library/HTMLPurifier/URIScheme/mailto.php | 40 + .../library/HTMLPurifier/URIScheme/news.php | 35 + .../library/HTMLPurifier/URIScheme/nntp.php | 32 + .../library/HTMLPurifier/URIScheme/tel.php | 46 + .../HTMLPurifier/URISchemeRegistry.php | 81 + .../library/HTMLPurifier/UnitConverter.php | 307 + .../library/HTMLPurifier/VarParser.php | 198 + .../HTMLPurifier/VarParser/Flexible.php | 130 + .../library/HTMLPurifier/VarParser/Native.php | 38 + .../HTMLPurifier/VarParserException.php | 11 + .../library/HTMLPurifier/Zipper.php | 157 + .../htmlpurifier/maintenance/PH5P.patch | 102 + .../ezyang/htmlpurifier/maintenance/PH5P.php | 3889 ++++ .../htmlpurifier/maintenance/add-vimline.php | 130 + .../htmlpurifier/maintenance/common.php | 25 + .../maintenance/compile-doxygen.sh | 11 + .../maintenance/config-scanner.php | 155 + .../maintenance/flush-definition-cache.php | 42 + .../ezyang/htmlpurifier/maintenance/flush.sh | 8 + .../maintenance/generate-entity-file.php | 75 + .../maintenance/generate-includes.php | 192 + .../maintenance/generate-ph5p-patch.php | 22 + .../maintenance/generate-schema-cache.php | 45 + .../maintenance/generate-standalone.php | 159 + .../maintenance/merge-library.php | 11 + .../maintenance/old-extract-schema.php | 71 + .../maintenance/old-remove-require-once.php | 32 + .../maintenance/old-remove-schema-def.php | 32 + .../maintenance/regenerate-docs.sh | 5 + .../remove-trailing-whitespace.php | 37 + .../maintenance/rename-config.php | 84 + .../vendor/ezyang/htmlpurifier/package.php | 61 + .../vendor/ezyang/htmlpurifier/phpdoc.ini | 102 + .../htmlpurifier/plugins/phorum/Changelog | 27 + .../htmlpurifier/plugins/phorum/INSTALL | 84 + .../ezyang/htmlpurifier/plugins/phorum/README | 45 + .../plugins/phorum/config.default.php | 58 + .../plugins/phorum/htmlpurifier.php | 316 + .../plugins/phorum/htmlpurifier/README | 3 + .../plugins/phorum/init-config.php | 30 + .../plugins/phorum/migrate.bbcode.php | 31 + .../htmlpurifier/plugins/phorum/settings.php | 64 + .../plugins/phorum/settings/form.php | 95 + .../phorum/settings/migrate-sigs-form.php | 22 + .../plugins/phorum/settings/migrate-sigs.php | 79 + .../plugins/phorum/settings/save.php | 29 + .../htmlpurifier/test-settings.sample.php | 74 + .../htmlpurifier/test-settings.travis.php | 72 + .../ezyang/htmlpurifier/update-for-release | 110 + .../fideloper/proxy/config/trustedproxy.php | 71 + .../fideloper/proxy/src/TrustProxies.php | 181 + .../proxy/src/TrustedProxyServiceProvider.php | 38 + .../vendor/guzzlehttp/guzzle/Dockerfile | 18 + .../guzzlehttp/guzzle/src/ClientInterface.php | 87 + .../guzzle/src/Cookie/CookieJarInterface.php | 84 + .../guzzle/src/Cookie/FileCookieJar.php | 91 + .../guzzle/src/Cookie/SessionCookieJar.php | 72 + .../guzzle/src/Cookie/SetCookie.php | 410 + .../src/Exception/BadResponseException.php | 27 + .../guzzle/src/Exception/ClientException.php | 9 + .../guzzle/src/Exception/ConnectException.php | 37 + .../guzzle/src/Exception/GuzzleException.php | 23 + .../Exception/InvalidArgumentException.php | 7 + .../guzzle/src/Exception/RequestException.php | 192 + .../guzzle/src/Exception/SeekException.php | 27 + .../guzzle/src/Exception/ServerException.php | 9 + .../Exception/TooManyRedirectsException.php | 6 + .../src/Exception/TransferException.php | 6 + .../guzzle/src/Handler/CurlFactory.php | 585 + .../src/Handler/CurlFactoryInterface.php | 27 + .../guzzle/src/Handler/CurlHandler.php | 45 + .../guzzle/src/Handler/CurlMultiHandler.php | 219 + .../guzzle/src/Handler/EasyHandle.php | 92 + .../guzzle/src/Handler/MockHandler.php | 195 + .../guzzlehttp/guzzle/src/Handler/Proxy.php | 55 + .../guzzle/src/Handler/StreamHandler.php | 545 + .../guzzlehttp/guzzle/src/HandlerStack.php | 277 + .../guzzle/src/MessageFormatter.php | 185 + .../guzzlehttp/guzzle/src/Middleware.php | 254 + .../vendor/guzzlehttp/guzzle/src/Pool.php | 134 + .../guzzle/src/PrepareBodyMiddleware.php | 111 + .../guzzle/src/RedirectMiddleware.php | 264 + .../guzzlehttp/guzzle/src/RequestOptions.php | 263 + .../guzzlehttp/guzzle/src/RetryMiddleware.php | 128 + .../guzzlehttp/guzzle/src/TransferStats.php | 126 + .../guzzlehttp/guzzle/src/UriTemplate.php | 237 + .../vendor/guzzlehttp/guzzle/src/Utils.php | 92 + .../guzzlehttp/guzzle/src/functions.php | 334 + .../guzzle/src/functions_include.php | 6 + .../promises/src/AggregateException.php | 17 + .../promises/src/CancellationException.php | 10 + .../guzzlehttp/promises/src/Coroutine.php | 169 + .../vendor/guzzlehttp/promises/src/Create.php | 84 + .../vendor/guzzlehttp/promises/src/Each.php | 90 + .../guzzlehttp/promises/src/EachPromise.php | 247 + .../promises/src/FulfilledPromise.php | 84 + .../vendor/guzzlehttp/promises/src/Is.php | 46 + .../guzzlehttp/promises/src/Promise.php | 278 + .../promises/src/PromiseInterface.php | 97 + .../promises/src/PromisorInterface.php | 16 + .../promises/src/RejectedPromise.php | 91 + .../promises/src/RejectionException.php | 48 + .../guzzlehttp/promises/src/TaskQueue.php | 67 + .../promises/src/TaskQueueInterface.php | 24 + .../vendor/guzzlehttp/promises/src/Utils.php | 276 + .../guzzlehttp/promises/src/functions.php | 363 + .../promises/src/functions_include.php | 6 + .../guzzlehttp/psr7/src/AppendStream.php | 246 + .../guzzlehttp/psr7/src/BufferStream.php | 142 + .../guzzlehttp/psr7/src/CachingStream.php | 147 + .../guzzlehttp/psr7/src/DroppingStream.php | 45 + .../vendor/guzzlehttp/psr7/src/FnStream.php | 163 + .../vendor/guzzlehttp/psr7/src/Header.php | 71 + .../guzzlehttp/psr7/src/InflateStream.php | 56 + .../guzzlehttp/psr7/src/LimitStream.php | 157 + .../vendor/guzzlehttp/psr7/src/Message.php | 252 + .../guzzlehttp/psr7/src/MessageTrait.php | 269 + .../vendor/guzzlehttp/psr7/src/MimeType.php | 140 + .../guzzlehttp/psr7/src/MultipartStream.php | 158 + .../guzzlehttp/psr7/src/NoSeekStream.php | 25 + .../vendor/guzzlehttp/psr7/src/PumpStream.php | 170 + .../vendor/guzzlehttp/psr7/src/Query.php | 113 + .../vendor/guzzlehttp/psr7/src/Request.php | 152 + .../vendor/guzzlehttp/psr7/src/Response.php | 155 + .../vendor/guzzlehttp/psr7/src/Rfc7230.php | 19 + .../guzzlehttp/psr7/src/ServerRequest.php | 379 + .../vendor/guzzlehttp/psr7/src/Stream.php | 270 + .../psr7/src/StreamDecoratorTrait.php | 152 + .../guzzlehttp/psr7/src/StreamWrapper.php | 165 + .../guzzlehttp/psr7/src/UploadedFile.php | 328 + .../vendor/guzzlehttp/psr7/src/Uri.php | 810 + .../guzzlehttp/psr7/src/UriComparator.php | 55 + .../guzzlehttp/psr7/src/UriNormalizer.php | 219 + .../guzzlehttp/psr7/src/UriResolver.php | 222 + .../vendor/guzzlehttp/psr7/src/Utils.php | 428 + .../vendor/guzzlehttp/psr7/src/functions.php | 422 + .../guzzlehttp/psr7/src/functions_include.php | 6 + .../vendor/html2text/html2text/.travis.yml | 16 + .../html2text/html2text/src/Html2Text.php | 629 + .../php-console-color/.travis.yml | 24 + .../php-console-color/example.php | 38 + .../php-console-color/src/ConsoleColor.php | 287 + .../src/InvalidStyleException.php | 10 + .../php-console-highlighter/.travis.yml | 22 + .../src/Highlighter.php | 263 + .../src/Facades/MacroableModels.php | 13 + .../src/MacroableModelsServiceProvider.php | 15 + .../Auth/Access/AuthorizationException.php | 10 + .../src/Illuminate/Auth/Access/Gate.php | 560 + .../Auth/Access/HandlesAuthorization.php | 30 + .../src/Illuminate/Auth/Access/Response.php | 44 + .../src/Illuminate/Auth/AuthManager.php | 294 + .../Illuminate/Auth/AuthServiceProvider.php | 90 + .../src/Illuminate/Auth/Authenticatable.php | 78 + .../Auth/AuthenticationException.php | 39 + .../Auth/Console/AuthMakeCommand.php | 119 + .../Auth/Console/ClearResetsCommand.php | 34 + .../make/controllers/HomeController.stub | 28 + .../Auth/Console/stubs/make/routes.stub | 4 + .../Console/stubs/make/views/auth/login.stub | 69 + .../make/views/auth/passwords/email.stub | 47 + .../make/views/auth/passwords/reset.stub | 70 + .../stubs/make/views/auth/register.stub | 77 + .../Auth/Console/stubs/make/views/home.stub | 23 + .../Console/stubs/make/views/layouts/app.stub | 80 + .../Illuminate/Auth/CreatesUserProviders.php | 94 + .../Illuminate/Auth/DatabaseUserProvider.php | 152 + .../Illuminate/Auth/EloquentUserProvider.php | 195 + .../src/Illuminate/Auth/Events/Attempting.php | 33 + .../Illuminate/Auth/Events/Authenticated.php | 28 + .../src/Illuminate/Auth/Events/Failed.php | 33 + .../src/Illuminate/Auth/Events/Lockout.php | 26 + .../src/Illuminate/Auth/Events/Login.php | 37 + .../src/Illuminate/Auth/Events/Logout.php | 28 + .../Illuminate/Auth/Events/PasswordReset.php | 28 + .../src/Illuminate/Auth/Events/Registered.php | 28 + .../src/Illuminate/Auth/GenericUser.php | 134 + .../src/Illuminate/Auth/GuardHelpers.php | 108 + .../Middleware/AuthenticateWithBasicAuth.php | 40 + .../Illuminate/Auth/Middleware/Authorize.php | 100 + .../Auth/Notifications/ResetPassword.php | 52 + .../Auth/Passwords/CanResetPassword.php | 29 + .../Passwords/DatabaseTokenRepository.php | 204 + .../Auth/Passwords/PasswordBroker.php | 242 + .../Auth/Passwords/PasswordBrokerManager.php | 147 + .../PasswordResetServiceProvider.php | 51 + .../Passwords/TokenRepositoryInterface.php | 40 + .../src/Illuminate/Auth/Recaller.php | 88 + .../src/Illuminate/Auth/RequestGuard.php | 87 + .../src/Illuminate/Auth/TokenGuard.php | 133 + .../Broadcasting/BroadcastController.php | 21 + .../Broadcasting/BroadcastEvent.php | 111 + .../Broadcasting/BroadcastException.php | 10 + .../Broadcasting/BroadcastManager.php | 312 + .../Broadcasting/BroadcastServiceProvider.php | 51 + .../Broadcasters/LogBroadcaster.php | 54 + .../Broadcasters/NullBroadcaster.php | 30 + .../Broadcasters/PusherBroadcaster.php | 121 + .../Broadcasters/RedisBroadcaster.php | 103 + .../src/Illuminate/Broadcasting/Channel.php | 34 + .../Broadcasting/InteractsWithSockets.php | 39 + .../Broadcasting/PendingBroadcast.php | 59 + .../Broadcasting/PresenceChannel.php | 17 + .../Broadcasting/PrivateChannel.php | 17 + .../src/Illuminate/Bus/BusServiceProvider.php | 54 + .../src/Illuminate/Bus/Dispatcher.php | 212 + .../src/Illuminate/Bus/Queueable.php | 150 + .../src/Illuminate/Cache/ApcStore.php | 132 + .../src/Illuminate/Cache/ApcWrapper.php | 92 + .../src/Illuminate/Cache/ArrayStore.php | 117 + .../src/Illuminate/Cache/CacheManager.php | 306 + .../Illuminate/Cache/CacheServiceProvider.php | 47 + .../Cache/Console/CacheTableCommand.php | 81 + .../Cache/Console/ForgetCommand.php | 57 + .../Illuminate/Cache/Console/stubs/cache.stub | 32 + .../src/Illuminate/Cache/DatabaseStore.php | 256 + .../Illuminate/Cache/Events/CacheEvent.php | 46 + .../src/Illuminate/Cache/Events/CacheHit.php | 28 + .../Illuminate/Cache/Events/CacheMissed.php | 8 + .../Illuminate/Cache/Events/KeyForgotten.php | 8 + .../Illuminate/Cache/Events/KeyWritten.php | 37 + .../src/Illuminate/Cache/FileStore.php | 262 + .../framework/src/Illuminate/Cache/Lock.php | 93 + .../Illuminate/Cache/MemcachedConnector.php | 87 + .../src/Illuminate/Cache/MemcachedLock.php | 52 + .../src/Illuminate/Cache/MemcachedStore.php | 250 + .../src/Illuminate/Cache/NullStore.php | 108 + .../src/Illuminate/Cache/RateLimiter.php | 134 + .../src/Illuminate/Cache/RedisLock.php | 56 + .../src/Illuminate/Cache/RedisStore.php | 289 + .../src/Illuminate/Cache/RedisTaggedCache.php | 166 + .../Cache/RetrievesMultipleKeys.php | 39 + .../framework/src/Illuminate/Cache/TagSet.php | 110 + .../src/Illuminate/Cache/TaggableStore.php | 17 + .../src/Illuminate/Cache/TaggedCache.php | 95 + .../src/Illuminate/Console/Application.php | 283 + .../src/Illuminate/Console/Command.php | 594 + .../Illuminate/Console/ConfirmableTrait.php | 54 + .../Console/DetectsApplicationNamespace.php | 18 + .../Console/Events/ArtisanStarting.php | 24 + .../Console/Events/CommandFinished.php | 54 + .../Console/Events/CommandStarting.php | 45 + .../Illuminate/Console/GeneratorCommand.php | 235 + .../src/Illuminate/Console/OutputStyle.php | 71 + .../src/Illuminate/Console/Parser.php | 146 + .../Console/Scheduling/CacheMutex.php | 61 + .../Console/Scheduling/CallbackEvent.php | 142 + .../Console/Scheduling/CommandBuilder.php | 71 + .../Illuminate/Console/Scheduling/Event.php | 677 + .../Console/Scheduling/ManagesFrequencies.php | 411 + .../Illuminate/Console/Scheduling/Mutex.php | 30 + .../Console/Scheduling/Schedule.php | 153 + .../Scheduling/ScheduleFinishCommand.php | 61 + .../Console/Scheduling/ScheduleRunCommand.php | 68 + .../Container/ContextualBindingBuilder.php | 68 + .../Container/EntryNotFoundException.php | 11 + .../Contracts/Auth/Access/Authorizable.php | 15 + .../Illuminate/Contracts/Auth/Access/Gate.php | 120 + .../Contracts/Auth/Authenticatable.php | 49 + .../Contracts/Auth/CanResetPassword.php | 21 + .../src/Illuminate/Contracts/Auth/Factory.php | 22 + .../src/Illuminate/Contracts/Auth/Guard.php | 50 + .../Contracts/Auth/PasswordBroker.php | 76 + .../Contracts/Auth/PasswordBrokerFactory.php | 14 + .../Contracts/Auth/StatefulGuard.php | 63 + .../Contracts/Auth/SupportsBasicAuth.php | 24 + .../Contracts/Auth/UserProvider.php | 49 + .../Contracts/Broadcasting/Broadcaster.php | 33 + .../Contracts/Broadcasting/Factory.php | 14 + .../Broadcasting/ShouldBroadcast.php | 15 + .../Broadcasting/ShouldBroadcastNow.php | 8 + .../Illuminate/Contracts/Bus/Dispatcher.php | 31 + .../Contracts/Bus/QueueingDispatcher.php | 14 + .../Illuminate/Contracts/Cache/Factory.php | 14 + .../src/Illuminate/Contracts/Cache/Lock.php | 30 + .../Contracts/Cache/LockProvider.php | 15 + .../Contracts/Cache/LockTimeoutException.php | 10 + .../Illuminate/Contracts/Cache/Repository.php | 125 + .../src/Illuminate/Contracts/Cache/Store.php | 92 + .../Contracts/Config/Repository.php | 57 + .../Contracts/Console/Application.php | 22 + .../Illuminate/Contracts/Console/Kernel.php | 47 + .../Container/BindingResolutionException.php | 11 + .../Contracts/Container/Container.php | 153 + .../Container/ContextualBindingBuilder.php | 22 + .../Illuminate/Contracts/Cookie/Factory.php | 43 + .../Contracts/Cookie/QueueingFactory.php | 28 + .../Contracts/Database/ModelIdentifier.php | 44 + .../Contracts/Debug/ExceptionHandler.php | 34 + .../Contracts/Encryption/DecryptException.php | 10 + .../Contracts/Encryption/EncryptException.php | 10 + .../Contracts/Encryption/Encrypter.php | 24 + .../Contracts/Events/Dispatcher.php | 82 + .../Illuminate/Contracts/Filesystem/Cloud.php | 14 + .../Contracts/Filesystem/Factory.php | 14 + .../Filesystem/FileNotFoundException.php | 10 + .../Contracts/Filesystem/Filesystem.php | 175 + .../Contracts/Foundation/Application.php | 106 + .../Illuminate/Contracts/Hashing/Hasher.php | 34 + .../src/Illuminate/Contracts/Http/Kernel.php | 37 + .../src/Illuminate/Contracts/Logging/Log.php | 98 + .../Illuminate/Contracts/Mail/MailQueue.php | 25 + .../Illuminate/Contracts/Mail/Mailable.php | 33 + .../src/Illuminate/Contracts/Mail/Mailer.php | 48 + .../Contracts/Notifications/Dispatcher.php | 24 + .../Contracts/Notifications/Factory.php | 32 + .../Pagination/LengthAwarePaginator.php | 29 + .../Contracts/Pagination/Paginator.php | 117 + .../src/Illuminate/Contracts/Pipeline/Hub.php | 15 + .../Contracts/Pipeline/Pipeline.php | 40 + .../Queue/EntityNotFoundException.php | 22 + .../Contracts/Queue/EntityResolver.php | 15 + .../Illuminate/Contracts/Queue/Factory.php | 14 + .../src/Illuminate/Contracts/Queue/Job.php | 115 + .../Illuminate/Contracts/Queue/Monitor.php | 30 + .../src/Illuminate/Contracts/Queue/Queue.php | 99 + .../Contracts/Queue/QueueableCollection.php | 27 + .../Contracts/Queue/QueueableEntity.php | 20 + .../Contracts/Queue/ShouldQueue.php | 8 + .../Illuminate/Contracts/Redis/Factory.php | 14 + .../Redis/LimiterTimeoutException.php | 10 + .../Contracts/Routing/BindingRegistrar.php | 23 + .../Contracts/Routing/Registrar.php | 105 + .../Contracts/Routing/ResponseFactory.php | 126 + .../Contracts/Routing/UrlGenerator.php | 71 + .../Contracts/Routing/UrlRoutable.php | 28 + .../Illuminate/Contracts/Session/Session.php | 165 + .../Contracts/Support/Arrayable.php | 13 + .../Illuminate/Contracts/Support/Htmlable.php | 13 + .../Illuminate/Contracts/Support/Jsonable.php | 14 + .../Contracts/Support/MessageBag.php | 107 + .../Contracts/Support/MessageProvider.php | 13 + .../Contracts/Support/Renderable.php | 13 + .../Contracts/Support/Responsable.php | 14 + .../Contracts/Translation/Loader.php | 40 + .../Contracts/Translation/Translator.php | 42 + .../Contracts/Validation/Factory.php | 46 + .../Contracts/Validation/ImplicitRule.php | 8 + .../Illuminate/Contracts/Validation/Rule.php | 22 + .../Validation/ValidatesWhenResolved.php | 13 + .../Contracts/Validation/Validator.php | 47 + .../src/Illuminate/Contracts/View/Engine.php | 15 + .../src/Illuminate/Contracts/View/Factory.php | 79 + .../src/Illuminate/Contracts/View/View.php | 24 + .../src/Illuminate/Cookie/CookieJar.php | 193 + .../Cookie/CookieServiceProvider.php | 24 + .../Middleware/AddQueuedCookiesToResponse.php | 45 + .../Illuminate/Database/Capsule/Manager.php | 201 + .../Database/Concerns/BuildsQueries.php | 161 + .../Database/Concerns/ManagesTransactions.php | 219 + .../src/Illuminate/Database/Connection.php | 1238 ++ .../Database/ConnectionInterface.php | 150 + .../Database/ConnectionResolver.php | 92 + .../Database/ConnectionResolverInterface.php | 29 + .../Database/Connectors/ConnectionFactory.php | 288 + .../Database/Connectors/Connector.php | 136 + .../Connectors/ConnectorInterface.php | 14 + .../Database/Connectors/MySqlConnector.php | 180 + .../Database/Connectors/PostgresConnector.php | 174 + .../Database/Connectors/SQLiteConnector.php | 39 + .../Connectors/SqlServerConnector.php | 183 + .../Console/Factories/FactoryMakeCommand.php | 84 + .../Console/Factories/stubs/factory.stub | 9 + .../Console/Migrations/BaseCommand.php | 39 + .../Console/Migrations/FreshCommand.php | 112 + .../Console/Migrations/InstallCommand.php | 70 + .../Console/Migrations/MigrateCommand.php | 102 + .../Console/Migrations/MigrateMakeCommand.php | 131 + .../Console/Migrations/RefreshCommand.php | 154 + .../Console/Migrations/ResetCommand.php | 96 + .../Console/Migrations/RollbackCommand.php | 94 + .../Console/Migrations/StatusCommand.php | 108 + .../Database/Console/Seeds/SeedCommand.php | 106 + .../Console/Seeds/SeederMakeCommand.php | 96 + .../Database/Console/Seeds/stubs/seeder.stub | 16 + .../Illuminate/Database/DatabaseManager.php | 329 + .../Database/DatabaseServiceProvider.php | 99 + .../Illuminate/Database/DetectsDeadlocks.php | 32 + .../Database/DetectsLostConnections.php | 37 + .../Illuminate/Database/Eloquent/Builder.php | 1330 ++ .../Database/Eloquent/Collection.php | 440 + .../Eloquent/Concerns/HasAttributes.php | 1141 ++ .../Database/Eloquent/Concerns/HasEvents.php | 340 + .../Eloquent/Concerns/HasGlobalScopes.php | 71 + .../Eloquent/Concerns/HasRelationships.php | 741 + .../Eloquent/Concerns/HasTimestamps.php | 125 + .../Eloquent/Concerns/HidesAttributes.php | 126 + .../Concerns/QueriesRelationships.php | 310 + .../Database/Eloquent/FactoryBuilder.php | 331 + .../Eloquent/JsonEncodingException.php | 35 + .../Eloquent/MassAssignmentException.php | 10 + .../Eloquent/ModelNotFoundException.php | 66 + .../Database/Eloquent/QueueEntityResolver.php | 29 + .../Eloquent/RelationNotFoundException.php | 41 + .../Database/Eloquent/Relations/BelongsTo.php | 362 + .../Eloquent/Relations/BelongsToMany.php | 994 + .../Concerns/InteractsWithPivotTable.php | 517 + .../Concerns/SupportsDefaultModels.php | 63 + .../Database/Eloquent/Relations/HasMany.php | 47 + .../Eloquent/Relations/HasManyThrough.php | 520 + .../Database/Eloquent/Relations/HasOne.php | 64 + .../Eloquent/Relations/HasOneOrMany.php | 423 + .../Database/Eloquent/Relations/MorphMany.php | 47 + .../Database/Eloquent/Relations/MorphOne.php | 64 + .../Eloquent/Relations/MorphOneOrMany.php | 140 + .../Eloquent/Relations/MorphPivot.php | 79 + .../Database/Eloquent/Relations/MorphTo.php | 279 + .../Eloquent/Relations/MorphToMany.php | 168 + .../Database/Eloquent/Relations/Pivot.php | 229 + .../Database/Eloquent/Relations/Relation.php | 385 + .../Illuminate/Database/Eloquent/Scope.php | 15 + .../Database/Eloquent/SoftDeletes.php | 169 + .../Database/Eloquent/SoftDeletingScope.php | 131 + .../Database/Events/ConnectionEvent.php | 32 + .../Database/Events/QueryExecuted.php | 59 + .../Database/Events/StatementPrepared.php | 33 + .../Database/Events/TransactionBeginning.php | 8 + .../Database/Events/TransactionCommitted.php | 8 + .../Database/Events/TransactionRolledBack.php | 8 + .../src/Illuminate/Database/Grammar.php | 205 + .../Database/MigrationServiceProvider.php | 87 + .../DatabaseMigrationRepository.php | 199 + .../Database/Migrations/Migration.php | 30 + .../Database/Migrations/MigrationCreator.php | 202 + .../MigrationRepositoryInterface.php | 74 + .../Database/Migrations/Migrator.php | 578 + .../Database/Migrations/stubs/blank.stub | 28 + .../Database/Migrations/stubs/create.stub | 31 + .../Database/Migrations/stubs/update.stub | 32 + .../Illuminate/Database/MySqlConnection.php | 84 + .../Database/PostgresConnection.php | 66 + .../src/Illuminate/Database/Query/Builder.php | 2485 +++ .../Illuminate/Database/Query/Expression.php | 44 + .../Database/Query/Grammars/Grammar.php | 863 + .../Database/Query/Grammars/MySqlGrammar.php | 316 + .../Query/Grammars/PostgresGrammar.php | 345 + .../Database/Query/Grammars/SQLiteGrammar.php | 202 + .../Query/Grammars/SqlServerGrammar.php | 442 + .../Illuminate/Database/Query/JoinClause.php | 110 + .../Database/Query/JsonExpression.php | 45 + .../Query/Processors/MySqlProcessor.php | 19 + .../Query/Processors/PostgresProcessor.php | 41 + .../Database/Query/Processors/Processor.php | 49 + .../Query/Processors/SQLiteProcessor.php | 19 + .../Query/Processors/SqlServerProcessor.php | 69 + .../Illuminate/Database/QueryException.php | 78 + .../Illuminate/Database/SQLiteConnection.php | 66 + .../Illuminate/Database/Schema/Blueprint.php | 1263 ++ .../Illuminate/Database/Schema/Builder.php | 304 + .../Database/Schema/Grammars/ChangeColumn.php | 205 + .../Database/Schema/Grammars/Grammar.php | 255 + .../Database/Schema/Grammars/MySqlGrammar.php | 967 + .../Schema/Grammars/PostgresGrammar.php | 802 + .../Database/Schema/Grammars/RenameColumn.php | 69 + .../Schema/Grammars/SQLiteGrammar.php | 799 + .../Schema/Grammars/SqlServerGrammar.php | 785 + .../Database/Schema/MySqlBuilder.php | 78 + .../Database/Schema/PostgresBuilder.php | 105 + .../Database/Schema/SQLiteBuilder.php | 34 + .../Database/Schema/SqlServerBuilder.php | 20 + .../src/Illuminate/Database/Seeder.php | 125 + .../Database/SqlServerConnection.php | 113 + .../src/Illuminate/Encryption/Encrypter.php | 251 + .../Encryption/EncryptionServiceProvider.php | 48 + .../Illuminate/Events/CallQueuedListener.php | 160 + .../src/Illuminate/Events/Dispatcher.php | 563 + .../Events/EventServiceProvider.php | 23 + .../src/Illuminate/Filesystem/Cache.php | 77 + .../Filesystem/FilesystemAdapter.php | 684 + .../Filesystem/FilesystemManager.php | 370 + .../Filesystem/FilesystemServiceProvider.php | 82 + .../src/Illuminate/Foundation/AliasLoader.php | 243 + .../src/Illuminate/Foundation/Application.php | 1196 ++ .../Foundation/Auth/Access/Authorizable.php | 44 + .../Auth/Access/AuthorizesRequests.php | 126 + .../Foundation/Auth/AuthenticatesUsers.php | 169 + .../Foundation/Auth/RedirectsUsers.php | 20 + .../Foundation/Auth/RegistersUsers.php | 62 + .../Foundation/Auth/ResetsPasswords.php | 161 + .../Auth/SendsPasswordResetEmails.php | 87 + .../Foundation/Auth/ThrottlesLogins.php | 120 + .../src/Illuminate/Foundation/Auth/User.php | 19 + .../Foundation/Bootstrap/BootProviders.php | 19 + .../Foundation/Bootstrap/HandleExceptions.php | 161 + .../Bootstrap/LoadConfiguration.php | 115 + .../Bootstrap/LoadEnvironmentVariables.php | 75 + .../Foundation/Bootstrap/RegisterFacades.php | 29 + .../Bootstrap/RegisterProviders.php | 19 + .../Bootstrap/SetRequestForConsole.php | 35 + .../Foundation/Bus/Dispatchable.php | 27 + .../Foundation/Bus/DispatchesJobs.php | 30 + .../Foundation/Bus/PendingChain.php | 45 + .../Foundation/Bus/PendingDispatch.php | 114 + .../Illuminate/Foundation/ComposerScripts.php | 65 + .../Foundation/Console/AppNameCommand.php | 296 + .../Console/ClearCompiledCommand.php | 40 + .../Foundation/Console/ClosureCommand.php | 71 + .../Foundation/Console/ConfigCacheCommand.php | 76 + .../Foundation/Console/ConfigClearCommand.php | 55 + .../Foundation/Console/ConsoleMakeCommand.php | 90 + .../Foundation/Console/DownCommand.php | 67 + .../Foundation/Console/EnvironmentCommand.php | 32 + .../Console/EventGenerateCommand.php | 78 + .../Foundation/Console/EventMakeCommand.php | 61 + .../Console/ExceptionMakeCommand.php | 84 + .../Foundation/Console/JobMakeCommand.php | 65 + .../Illuminate/Foundation/Console/Kernel.php | 366 + .../Foundation/Console/KeyGenerateCommand.php | 111 + .../Console/ListenerMakeCommand.php | 112 + .../Foundation/Console/MailMakeCommand.php | 116 + .../Foundation/Console/ModelMakeCommand.php | 156 + .../Console/NotificationMakeCommand.php | 116 + .../Foundation/Console/OptimizeCommand.php | 47 + .../Console/PackageDiscoverCommand.php | 40 + .../Foundation/Console/PolicyMakeCommand.php | 140 + .../Foundation/Console/PresetCommand.php | 92 + .../Foundation/Console/Presets/Bootstrap.php | 43 + .../Foundation/Console/Presets/None.php | 58 + .../Foundation/Console/Presets/Preset.php | 61 + .../Foundation/Console/Presets/React.php | 76 + .../Foundation/Console/Presets/Vue.php | 76 + .../Presets/bootstrap-stubs/_variables.scss | 38 + .../Console/Presets/bootstrap-stubs/app.scss | 9 + .../Console/Presets/none-stubs/app.js | 8 + .../Console/Presets/react-stubs/Example.js | 26 + .../Console/Presets/react-stubs/app.js | 16 + .../Presets/react-stubs/webpack.mix.js | 15 + .../Presets/vue-stubs/ExampleComponent.vue | 23 + .../Console/Presets/vue-stubs/app.js | 22 + .../Console/Presets/vue-stubs/webpack.mix.js | 15 + .../Console/ProviderMakeCommand.php | 50 + .../Foundation/Console/QueuedCommand.php | 42 + .../Foundation/Console/RequestMakeCommand.php | 50 + .../Console/ResourceMakeCommand.php | 91 + .../Foundation/Console/RouteCacheCommand.php | 109 + .../Foundation/Console/RouteClearCommand.php | 55 + .../Foundation/Console/RouteListCommand.php | 192 + .../Foundation/Console/RuleMakeCommand.php | 50 + .../Foundation/Console/ServeCommand.php | 90 + .../Foundation/Console/StorageLinkCommand.php | 40 + .../Foundation/Console/TestMakeCommand.php | 82 + .../Foundation/Console/UpCommand.php | 34 + .../Console/VendorPublishCommand.php | 275 + .../Foundation/Console/ViewClearCommand.php | 64 + .../Foundation/Console/stubs/console.stub | 42 + .../Console/stubs/event-handler-queued.stub | 33 + .../Console/stubs/event-handler.stub | 31 + .../Foundation/Console/stubs/event.stub | 36 + .../stubs/exception-render-report.stub | 29 + .../Console/stubs/exception-render.stub | 19 + .../Console/stubs/exception-report.stub | 18 + .../Foundation/Console/stubs/exception.stub | 10 + .../Foundation/Console/stubs/job-queued.stub | 34 + .../Foundation/Console/stubs/job.stub | 31 + .../Console/stubs/listener-duck.stub | 30 + .../Console/stubs/listener-queued-duck.stub | 32 + .../Console/stubs/listener-queued.stub | 33 + .../Foundation/Console/stubs/listener.stub | 31 + .../Foundation/Console/stubs/mail.stub | 33 + .../Console/stubs/markdown-mail.stub | 33 + .../Console/stubs/markdown-notification.stub | 58 + .../Foundation/Console/stubs/markdown.stub | 12 + .../Foundation/Console/stubs/model.stub | 10 + .../Console/stubs/notification.stub | 61 + .../Foundation/Console/stubs/pivot.model.stub | 10 + .../Console/stubs/policy.plain.stub | 21 + .../Foundation/Console/stubs/policy.stub | 59 + .../Foundation/Console/stubs/provider.stub | 28 + .../Foundation/Console/stubs/request.stub | 30 + .../Console/stubs/resource-collection.stub | 19 + .../Foundation/Console/stubs/resource.stub | 19 + .../Foundation/Console/stubs/routes.stub | 16 + .../Foundation/Console/stubs/rule.stub | 40 + .../Foundation/Console/stubs/test.stub | 20 + .../Foundation/Console/stubs/unit-test.stub | 20 + .../Foundation/EnvironmentDetector.php | 69 + .../Foundation/Events/Dispatchable.php | 26 + .../Foundation/Events/LocaleUpdated.php | 24 + .../Foundation/Exceptions/Handler.php | 493 + .../Foundation/Exceptions/views/404.blade.php | 5 + .../Foundation/Exceptions/views/419.blade.php | 9 + .../Foundation/Exceptions/views/429.blade.php | 5 + .../Foundation/Exceptions/views/500.blade.php | 5 + .../Foundation/Exceptions/views/503.blade.php | 5 + .../Exceptions/views/layout.blade.php | 57 + .../Foundation/Http/Events/RequestHandled.php | 33 + .../Exceptions/MaintenanceModeException.php | 54 + .../Foundation/Http/FormRequest.php | 227 + .../src/Illuminate/Foundation/Http/Kernel.php | 338 + .../Middleware/CheckForMaintenanceMode.php | 48 + .../Middleware/ConvertEmptyStringsToNull.php | 18 + .../Http/Middleware/TransformsRequest.php | 101 + .../Http/Middleware/TrimStrings.php | 31 + .../Http/Middleware/ValidatePostSize.php | 55 + .../src/Illuminate/Foundation/Inspiring.php | 34 + .../Providers/ArtisanServiceProvider.php | 967 + .../Providers/ComposerServiceProvider.php | 38 + .../ConsoleSupportServiceProvider.php | 27 + .../Providers/FormRequestServiceProvider.php | 69 + .../Providers/FoundationServiceProvider.php | 46 + .../Support/Providers/AuthServiceProvider.php | 36 + .../Providers/EventServiceProvider.php | 59 + .../Providers/RouteServiceProvider.php | 101 + .../Concerns/InteractsWithAuthentication.php | 147 + .../Testing/Concerns/InteractsWithConsole.php | 20 + .../Concerns/InteractsWithContainer.php | 32 + .../Concerns/InteractsWithDatabase.php | 91 + .../InteractsWithExceptionHandling.php | 136 + .../Testing/Concerns/InteractsWithRedis.php | 109 + .../Testing/Concerns/InteractsWithSession.php | 64 + .../Testing/Concerns/MakesHttpRequests.php | 460 + .../Concerns/MocksApplicationServices.php | 283 + .../Testing/Constraints/HasInDatabase.php | 103 + .../Constraints/SoftDeletedInDatabase.php | 103 + .../Foundation/Testing/DatabaseMigrations.php | 26 + .../Testing/DatabaseTransactions.php | 40 + .../Foundation/Testing/HttpException.php | 10 + .../Foundation/Testing/RefreshDatabase.php | 96 + .../Testing/RefreshDatabaseState.php | 13 + .../Foundation/Testing/TestResponse.php | 805 + .../Foundation/Testing/WithFaker.php | 47 + .../Foundation/Testing/WithoutEvents.php | 22 + .../Foundation/Testing/WithoutMiddleware.php | 22 + .../Validation/ValidatesRequests.php | 98 + .../src/Illuminate/Foundation/helpers.php | 979 + .../Illuminate/Foundation/stubs/facade.stub | 21 + .../src/Illuminate/Hashing/BcryptHasher.php | 93 + .../Hashing/HashServiceProvider.php | 37 + .../Concerns/InteractsWithContentTypes.php | 157 + .../Http/Concerns/InteractsWithFlashData.php | 64 + .../Http/Concerns/InteractsWithInput.php | 375 + .../Http/Exceptions/HttpResponseException.php | 37 + .../Http/Exceptions/PostTooLargeException.php | 23 + .../framework/src/Illuminate/Http/File.php | 10 + .../src/Illuminate/Http/FileHelpers.php | 62 + .../src/Illuminate/Http/JsonResponse.php | 114 + .../CheckResponseForModifications.php | 27 + .../Illuminate/Http/Middleware/FrameGuard.php | 24 + .../src/Illuminate/Http/RedirectResponse.php | 238 + .../Http/Resources/CollectsResources.php | 59 + .../ConditionallyLoadsAttributes.php | 178 + .../Http/Resources/DelegatesToResource.php | 130 + .../Json/AnonymousResourceCollection.php | 27 + .../Json/PaginatedResourceResponse.php | 80 + .../Http/Resources/Json/Resource.php | 206 + .../Resources/Json/ResourceCollection.php | 63 + .../Http/Resources/Json/ResourceResponse.php | 118 + .../Illuminate/Http/Resources/MergeValue.php | 26 + .../Http/Resources/MissingValue.php | 16 + .../Http/Resources/PotentiallyMissing.php | 13 + .../src/Illuminate/Http/Response.php | 81 + .../src/Illuminate/Http/ResponseTrait.php | 141 + .../src/Illuminate/Http/Testing/File.php | 115 + .../Illuminate/Http/Testing/FileFactory.php | 51 + .../src/Illuminate/Http/Testing/MimeType.php | 827 + .../src/Illuminate/Http/UploadedFile.php | 122 + .../Illuminate/Log/Events/MessageLogged.php | 42 + .../src/Illuminate/Log/LogServiceProvider.php | 159 + .../framework/src/Illuminate/Log/Writer.php | 377 + .../Illuminate/Mail/Events/MessageSending.php | 24 + .../Illuminate/Mail/Events/MessageSent.php | 24 + .../Illuminate/Mail/MailServiceProvider.php | 145 + .../src/Illuminate/Mail/Mailable.php | 695 + .../framework/src/Illuminate/Mail/Mailer.php | 554 + .../src/Illuminate/Mail/Markdown.php | 160 + .../framework/src/Illuminate/Mail/Message.php | 328 + .../src/Illuminate/Mail/PendingMail.php | 154 + .../Illuminate/Mail/SendQueuedMailable.php | 87 + .../Mail/Transport/ArrayTransport.php | 58 + .../Mail/Transport/LogTransport.php | 59 + .../Mail/Transport/MailgunTransport.php | 168 + .../Mail/Transport/MandrillTransport.php | 105 + .../Mail/Transport/SesTransport.php | 48 + .../Mail/Transport/SparkPostTransport.php | 159 + .../Illuminate/Mail/Transport/Transport.php | 108 + .../resources/views/html/button.blade.php | 19 + .../resources/views/html/footer.blade.php | 11 + .../resources/views/html/header.blade.php | 7 + .../resources/views/html/layout.blade.php | 54 + .../resources/views/html/message.blade.php | 27 + .../Mail/resources/views/html/panel.blade.php | 13 + .../resources/views/html/promotion.blade.php | 7 + .../views/html/promotion/button.blade.php | 13 + .../resources/views/html/subcopy.blade.php | 7 + .../Mail/resources/views/html/table.blade.php | 3 + .../resources/views/html/themes/default.css | 287 + .../resources/views/markdown/button.blade.php | 1 + .../resources/views/markdown/footer.blade.php | 1 + .../resources/views/markdown/header.blade.php | 1 + .../resources/views/markdown/layout.blade.php | 9 + .../views/markdown/message.blade.php | 27 + .../resources/views/markdown/panel.blade.php | 1 + .../views/markdown/promotion.blade.php | 1 + .../views/markdown/promotion/button.blade.php | 1 + .../views/markdown/subcopy.blade.php | 1 + .../resources/views/markdown/table.blade.php | 1 + .../src/Illuminate/Notifications/Action.php | 33 + .../Notifications/AnonymousNotifiable.php | 72 + .../Notifications/ChannelManager.php | 171 + .../Channels/BroadcastChannel.php | 77 + .../Channels/DatabaseChannel.php | 51 + .../Notifications/Channels/MailChannel.php | 201 + .../Channels/NexmoSmsChannel.php | 64 + .../Channels/SlackWebhookChannel.php | 120 + .../Console/NotificationTableCommand.php | 81 + .../Console/stubs/notifications.stub | 35 + .../Notifications/DatabaseNotification.php | 102 + .../DatabaseNotificationCollection.php | 32 + .../Events/BroadcastNotificationCreated.php | 94 + .../Events/NotificationFailed.php | 51 + .../Events/NotificationSending.php | 42 + .../Notifications/Events/NotificationSent.php | 51 + .../HasDatabaseNotifications.php | 33 + .../Messages/BroadcastMessage.php | 41 + .../Messages/DatabaseMessage.php | 24 + .../Notifications/Messages/MailMessage.php | 233 + .../Notifications/Messages/NexmoMessage.php | 76 + .../Notifications/Messages/SimpleMessage.php | 224 + .../Messages/SlackAttachment.php | 301 + .../Messages/SlackAttachmentField.php | 79 + .../Notifications/Messages/SlackMessage.php | 261 + .../Illuminate/Notifications/Notifiable.php | 8 + .../Illuminate/Notifications/Notification.php | 27 + .../Notifications/NotificationSender.php | 181 + .../NotificationServiceProvider.php | 46 + .../Notifications/RoutesNotifications.php | 54 + .../Notifications/SendQueuedNotifications.php | 93 + .../resources/views/email.blade.php | 58 + .../Pagination/PaginationServiceProvider.php | 50 + .../src/Illuminate/Pagination/UrlWindow.php | 218 + .../resources/views/bootstrap-4.blade.php | 36 + .../resources/views/default.blade.php | 36 + .../resources/views/semantic-ui.blade.php | 36 + .../views/simple-bootstrap-4.blade.php | 17 + .../resources/views/simple-default.blade.php | 17 + .../framework/src/Illuminate/Pipeline/Hub.php | 74 + .../src/Illuminate/Pipeline/Pipeline.php | 186 + .../Pipeline/PipelineServiceProvider.php | 40 + .../src/Illuminate/Queue/BeanstalkdQueue.php | 163 + .../Illuminate/Queue/CallQueuedHandler.php | 152 + .../src/Illuminate/Queue/Capsule/Manager.php | 187 + .../Queue/Connectors/BeanstalkdConnector.php | 40 + .../Queue/Connectors/ConnectorInterface.php | 14 + .../Queue/Connectors/DatabaseConnector.php | 43 + .../Queue/Connectors/NullConnector.php | 19 + .../Queue/Connectors/RedisConnector.php | 51 + .../Queue/Connectors/SqsConnector.php | 46 + .../Queue/Connectors/SyncConnector.php | 19 + .../Queue/Console/FailedTableCommand.php | 102 + .../Queue/Console/FlushFailedCommand.php | 34 + .../Queue/Console/ForgetFailedCommand.php | 36 + .../Queue/Console/ListFailedCommand.php | 118 + .../Queue/Console/ListenCommand.php | 114 + .../Queue/Console/RestartCommand.php | 37 + .../Illuminate/Queue/Console/RetryCommand.php | 93 + .../Illuminate/Queue/Console/TableCommand.php | 102 + .../Illuminate/Queue/Console/WorkCommand.php | 213 + .../Queue/Console/stubs/failed_jobs.stub | 35 + .../Illuminate/Queue/Console/stubs/jobs.stub | 36 + .../src/Illuminate/Queue/DatabaseQueue.php | 330 + .../Queue/Events/JobExceptionOccurred.php | 42 + .../src/Illuminate/Queue/Events/JobFailed.php | 42 + .../Illuminate/Queue/Events/JobProcessed.php | 33 + .../Illuminate/Queue/Events/JobProcessing.php | 33 + .../src/Illuminate/Queue/Events/Looping.php | 33 + .../Queue/Events/WorkerStopping.php | 8 + .../Failed/DatabaseFailedJobProvider.php | 117 + .../Failed/FailedJobProviderInterface.php | 47 + .../Queue/Failed/NullFailedJobProvider.php | 62 + .../src/Illuminate/Queue/FailingJob.php | 50 + .../Illuminate/Queue/InteractsWithQueue.php | 76 + .../Queue/InvalidPayloadException.php | 19 + .../Illuminate/Queue/Jobs/BeanstalkdJob.php | 135 + .../src/Illuminate/Queue/Jobs/DatabaseJob.php | 100 + .../Queue/Jobs/DatabaseJobRecord.php | 63 + .../src/Illuminate/Queue/Jobs/Job.php | 271 + .../src/Illuminate/Queue/Jobs/JobName.php | 35 + .../src/Illuminate/Queue/Jobs/RedisJob.php | 139 + .../src/Illuminate/Queue/Jobs/SqsJob.php | 124 + .../src/Illuminate/Queue/Jobs/SyncJob.php | 91 + .../src/Illuminate/Queue/ListenerOptions.php | 32 + .../src/Illuminate/Queue/LuaScripts.php | 103 + .../Queue/ManuallyFailedException.php | 10 + .../Queue/MaxAttemptsExceededException.php | 10 + .../src/Illuminate/Queue/NullQueue.php | 70 + .../framework/src/Illuminate/Queue/Queue.php | 212 + .../src/Illuminate/Queue/QueueManager.php | 270 + .../Illuminate/Queue/QueueServiceProvider.php | 230 + .../src/Illuminate/Queue/RedisQueue.php | 279 + .../SerializesAndRestoresModelIdentifiers.php | 85 + .../src/Illuminate/Queue/SerializesModels.php | 58 + .../src/Illuminate/Queue/SqsQueue.php | 155 + .../src/Illuminate/Queue/SyncQueue.php | 161 + .../framework/src/Illuminate/Queue/Worker.php | 639 + .../src/Illuminate/Queue/WorkerOptions.php | 69 + .../Redis/Connections/Connection.php | 110 + .../Connections/PhpRedisClusterConnection.php | 8 + .../Redis/Connections/PhpRedisConnection.php | 411 + .../Connections/PredisClusterConnection.php | 8 + .../Redis/Connections/PredisConnection.php | 45 + .../Redis/Connectors/PhpRedisConnector.php | 117 + .../Redis/Connectors/PredisConnector.php | 44 + .../Redis/Limiters/ConcurrencyLimiter.php | 130 + .../Limiters/ConcurrencyLimiterBuilder.php | 122 + .../Redis/Limiters/DurationLimiter.php | 147 + .../Redis/Limiters/DurationLimiterBuilder.php | 122 + .../src/Illuminate/Redis/RedisManager.php | 142 + .../Illuminate/Redis/RedisServiceProvider.php | 44 + .../Routing/Console/ControllerMakeCommand.php | 172 + .../Routing/Console/MiddlewareMakeCommand.php | 50 + .../Console/stubs/controller.model.stub | 86 + .../Console/stubs/controller.nested.stub | 94 + .../Console/stubs/controller.plain.stub | 11 + .../Routing/Console/stubs/controller.stub | 85 + .../Routing/Console/stubs/middleware.stub | 20 + .../Contracts/ControllerDispatcher.php | 27 + .../Routing/ControllerDispatcher.php | 81 + .../Routing/ControllerMiddlewareOptions.php | 50 + .../Routing/Events/RouteMatched.php | 33 + .../Exceptions/UrlGenerationException.php | 19 + .../Routing/ImplicitRouteBinding.php | 60 + .../Routing/Matching/HostValidator.php | 25 + .../Routing/Matching/MethodValidator.php | 21 + .../Routing/Matching/SchemeValidator.php | 27 + .../Routing/Matching/UriValidator.php | 23 + .../Routing/Matching/ValidatorInterface.php | 18 + .../Routing/Middleware/SubstituteBindings.php | 43 + .../Routing/Middleware/ThrottleRequests.php | 193 + .../Middleware/ThrottleRequestsWithRedis.php | 119 + .../Routing/MiddlewareNameResolver.php | 85 + .../Routing/PendingResourceRegistration.php | 154 + .../src/Illuminate/Routing/Pipeline.php | 91 + .../Illuminate/Routing/RedirectController.php | 21 + .../src/Illuminate/Routing/Redirector.php | 213 + .../Illuminate/Routing/ResourceRegistrar.php | 440 + .../Illuminate/Routing/ResponseFactory.php | 215 + .../src/Illuminate/Routing/Route.php | 898 + .../src/Illuminate/Routing/RouteAction.php | 89 + .../src/Illuminate/Routing/RouteBinding.php | 82 + .../src/Illuminate/Routing/RouteCompiler.php | 54 + .../src/Illuminate/Routing/RouteGroup.php | 95 + .../Routing/RouteParameterBinder.php | 120 + .../src/Illuminate/Routing/RouteRegistrar.php | 180 + .../Illuminate/Routing/RouteUrlGenerator.php | 307 + .../Routing/RoutingServiceProvider.php | 166 + .../Illuminate/Routing/SortedMiddleware.php | 84 + .../src/Illuminate/Routing/ViewController.php | 39 + .../Session/CacheBasedSessionHandler.php | 94 + .../Session/Console/SessionTableCommand.php | 81 + .../Session/Console/stubs/database.stub | 35 + .../Session/CookieSessionHandler.php | 121 + .../Session/DatabaseSessionHandler.php | 294 + .../src/Illuminate/Session/EncryptedStore.php | 69 + .../Session/ExistenceAwareInterface.php | 14 + .../Middleware/AuthenticateSession.php | 96 + .../Session/Middleware/StartSession.php | 242 + .../Illuminate/Session/NullSessionHandler.php | 56 + .../src/Illuminate/Session/SessionManager.php | 215 + .../Session/SessionServiceProvider.php | 50 + .../src/Illuminate/Session/Store.php | 656 + .../Session/TokenMismatchException.php | 10 + .../Support/AggregateServiceProvider.php | 52 + .../framework/src/Illuminate/Support/Arr.php | 610 + .../src/Illuminate/Support/Composer.php | 99 + .../src/Illuminate/Support/Debug/Dumper.php | 26 + .../Illuminate/Support/Debug/HtmlDumper.php | 29 + .../src/Illuminate/Support/Facades/App.php | 31 + .../Illuminate/Support/Facades/Artisan.php | 27 + .../src/Illuminate/Support/Facades/Auth.php | 48 + .../src/Illuminate/Support/Facades/Blade.php | 19 + .../Illuminate/Support/Facades/Broadcast.php | 21 + .../src/Illuminate/Support/Facades/Bus.php | 32 + .../src/Illuminate/Support/Facades/Cache.php | 20 + .../src/Illuminate/Support/Facades/Config.php | 19 + .../src/Illuminate/Support/Facades/Cookie.php | 42 + .../src/Illuminate/Support/Facades/Crypt.php | 19 + .../src/Illuminate/Support/Facades/DB.php | 20 + .../src/Illuminate/Support/Facades/Event.php | 35 + .../src/Illuminate/Support/Facades/Facade.php | 223 + .../src/Illuminate/Support/Facades/File.php | 19 + .../src/Illuminate/Support/Facades/Gate.php | 21 + .../src/Illuminate/Support/Facades/Hash.php | 19 + .../src/Illuminate/Support/Facades/Input.php | 33 + .../src/Illuminate/Support/Facades/Lang.php | 19 + .../src/Illuminate/Support/Facades/Log.php | 21 + .../src/Illuminate/Support/Facades/Mail.php | 31 + .../Support/Facades/Notification.php | 47 + .../Illuminate/Support/Facades/Password.php | 54 + .../src/Illuminate/Support/Facades/Queue.php | 32 + .../Illuminate/Support/Facades/Redirect.php | 19 + .../src/Illuminate/Support/Facades/Redis.php | 20 + .../Illuminate/Support/Facades/Request.php | 19 + .../Illuminate/Support/Facades/Response.php | 21 + .../src/Illuminate/Support/Facades/Route.php | 42 + .../src/Illuminate/Support/Facades/Schema.php | 35 + .../Illuminate/Support/Facades/Session.php | 20 + .../Illuminate/Support/Facades/Storage.php | 54 + .../src/Illuminate/Support/Facades/URL.php | 19 + .../Illuminate/Support/Facades/Validator.php | 19 + .../src/Illuminate/Support/Facades/View.php | 19 + .../Support/HigherOrderCollectionProxy.php | 63 + .../Support/HigherOrderTapProxy.php | 38 + .../src/Illuminate/Support/HtmlString.php | 46 + .../Illuminate/Support/InteractsWithTime.php | 64 + .../src/Illuminate/Support/Manager.php | 140 + .../Support/NamespacedItemResolver.php | 102 + .../src/Illuminate/Support/Pluralizer.php | 119 + .../src/Illuminate/Support/ProcessUtils.php | 69 + .../Illuminate/Support/ServiceProvider.php | 300 + .../Support/Testing/Fakes/BusFake.php | 132 + .../Support/Testing/Fakes/EventFake.php | 260 + .../Support/Testing/Fakes/MailFake.php | 316 + .../Testing/Fakes/NotificationFake.php | 199 + .../Support/Testing/Fakes/PendingMailFake.php | 53 + .../Support/Testing/Fakes/QueueFake.php | 267 + .../Support/Traits/CapsuleManagerTrait.php | 69 + .../Illuminate/Support/Traits/Macroable.php | 107 + .../src/Illuminate/Support/helpers.php | 1176 ++ .../Illuminate/Translation/ArrayLoader.php | 85 + .../src/Illuminate/Translation/FileLoader.php | 187 + .../Translation/MessageSelector.php | 412 + .../TranslationServiceProvider.php | 62 + .../src/Illuminate/Translation/Translator.php | 479 + .../Validation/ClosureValidationRule.php | 70 + .../Concerns/ReplacesAttributes.php | 380 + .../Validation/DatabasePresenceVerifier.php | 138 + .../src/Illuminate/Validation/Factory.php | 283 + .../Validation/PresenceVerifierInterface.php | 30 + .../src/Illuminate/Validation/Rule.php | 67 + .../Validation/Rules/DatabaseRule.php | 172 + .../Validation/Rules/Dimensions.php | 131 + .../Illuminate/Validation/Rules/Exists.php | 22 + .../src/Illuminate/Validation/Rules/In.php | 45 + .../src/Illuminate/Validation/Rules/NotIn.php | 43 + .../Illuminate/Validation/Rules/Unique.php | 53 + .../Validation/UnauthorizedException.php | 10 + .../Validation/ValidatesWhenResolvedTrait.php | 86 + .../Illuminate/Validation/ValidationData.php | 106 + .../Validation/ValidationException.php | 138 + .../Validation/ValidationRuleParser.php | 275 + .../Validation/ValidationServiceProvider.php | 72 + .../src/Illuminate/Validation/Validator.php | 1142 ++ .../View/Compilers/BladeCompiler.php | 468 + .../View/Compilers/CompilerInterface.php | 30 + .../Concerns/CompilesAuthorizations.php | 70 + .../Compilers/Concerns/CompilesComments.php | 19 + .../Compilers/Concerns/CompilesComponents.php | 48 + .../Concerns/CompilesConditionals.php | 204 + .../View/Compilers/Concerns/CompilesEchos.php | 105 + .../Compilers/Concerns/CompilesIncludes.php | 69 + .../Compilers/Concerns/CompilesInjections.php | 23 + .../View/Compilers/Concerns/CompilesJson.php | 30 + .../View/Compilers/Concerns/CompilesLoops.php | 180 + .../Compilers/Concerns/CompilesRawPhp.php | 32 + .../Compilers/Concerns/CompilesStacks.php | 59 + .../Concerns/CompilesTranslations.php | 44 + .../View/Concerns/ManagesComponents.php | 128 + .../View/Concerns/ManagesEvents.php | 192 + .../Illuminate/View/Concerns/ManagesLoops.php | 90 + .../View/Concerns/ManagesStacks.php | 177 + .../View/Concerns/ManagesTranslations.php | 38 + .../View/Engines/CompilerEngine.php | 102 + .../src/Illuminate/View/Engines/Engine.php | 23 + .../View/Engines/EngineResolver.php | 59 + .../Illuminate/View/Engines/FileEngine.php | 20 + .../src/Illuminate/View/Engines/PhpEngine.php | 70 + .../framework/src/Illuminate/View/Factory.php | 563 + .../src/Illuminate/View/FileViewFinder.php | 298 + .../Middleware/ShareErrorsFromSession.php | 51 + .../Illuminate/View/ViewFinderInterface.php | 71 + .../src/Illuminate/View/ViewName.php | 25 + .../Illuminate/View/ViewServiceProvider.php | 149 + .../vendor/laravel/tinker/config/tinker.php | 18 + .../tinker/src/ClassAliasAutoloader.php | 116 + .../tinker/src/Console/TinkerCommand.php | 123 + .../laravel/tinker/src/TinkerCaster.php | 95 + .../tinker/src/TinkerServiceProvider.php | 60 + .../flysystem/src/Adapter/AbstractAdapter.php | 72 + .../src/Adapter/AbstractFtpAdapter.php | 705 + .../src/Adapter/CanOverwriteFiles.php | 12 + .../league/flysystem/src/Adapter/Ftp.php | 579 + .../league/flysystem/src/Adapter/Ftpd.php | 48 + .../league/flysystem/src/Adapter/Local.php | 533 + .../flysystem/src/Adapter/NullAdapter.php | 144 + .../Polyfill/NotSupportingVisibilityTrait.php | 33 + .../Adapter/Polyfill/StreamedCopyTrait.php | 51 + .../Adapter/Polyfill/StreamedReadingTrait.php | 44 + .../src/Adapter/Polyfill/StreamedTrait.php | 9 + .../Adapter/Polyfill/StreamedWritingTrait.php | 60 + .../flysystem/src/Adapter/SynologyFtp.php | 8 + .../league/flysystem/src/AdapterInterface.php | 118 + .../vendor/league/flysystem/src/Config.php | 107 + .../league/flysystem/src/ConfigAwareTrait.php | 49 + .../src/ConnectionErrorException.php | 9 + .../src/ConnectionRuntimeException.php | 9 + .../flysystem/src/CorruptedPathDetected.php | 17 + .../vendor/league/flysystem/src/Directory.php | 31 + .../vendor/league/flysystem/src/Exception.php | 8 + .../vendor/league/flysystem/src/File.php | 205 + .../flysystem/src/FileExistsException.php | 37 + .../flysystem/src/FileNotFoundException.php | 37 + .../league/flysystem/src/Filesystem.php | 409 + .../flysystem/src/FilesystemException.php | 7 + .../flysystem/src/FilesystemInterface.php | 284 + .../src/FilesystemNotFoundException.php | 12 + .../vendor/league/flysystem/src/Handler.php | 137 + .../flysystem/src/InvalidRootException.php | 9 + .../league/flysystem/src/MountManager.php | 648 + .../flysystem/src/NotSupportedException.php | 37 + .../flysystem/src/Plugin/AbstractPlugin.php | 24 + .../league/flysystem/src/Plugin/EmptyDir.php | 34 + .../flysystem/src/Plugin/ForcedCopy.php | 44 + .../flysystem/src/Plugin/ForcedRename.php | 44 + .../flysystem/src/Plugin/GetWithMetadata.php | 51 + .../league/flysystem/src/Plugin/ListFiles.php | 35 + .../league/flysystem/src/Plugin/ListPaths.php | 36 + .../league/flysystem/src/Plugin/ListWith.php | 60 + .../flysystem/src/Plugin/PluggableTrait.php | 97 + .../src/Plugin/PluginNotFoundException.php | 10 + .../league/flysystem/src/PluginInterface.php | 20 + .../league/flysystem/src/ReadInterface.php | 88 + .../flysystem/src/RootViolationException.php | 10 + .../league/flysystem/src/SafeStorage.php | 39 + .../flysystem/src/UnreadableFileException.php | 18 + .../vendor/league/flysystem/src/Util.php | 354 + .../src/Util/ContentListingFormatter.php | 122 + .../league/flysystem/src/Util/MimeType.php | 80 + .../flysystem/src/Util/StreamHasher.php | 36 + .../src/EmptyExtensionToMimeTypeMap.php | 13 + .../src/ExtensionLookup.php | 14 + .../src/ExtensionMimeTypeDetector.php | 56 + .../src/ExtensionToMimeTypeMap.php | 10 + .../src/FinfoMimeTypeDetector.php | 106 + .../src/GeneratedExtensionToMimeTypeMap.php | 2291 +++ .../src/MimeTypeDetector.php | 19 + .../src/OverridingExtensionToMimeTypeMap.php | 30 + .../vendor/lord/laroute/.travis.yml | 14 + .../vendor/lord/laroute/config/laroute.php | 58 + .../vendor/lord/laroute/gruntfile.js | 17 + freescout-dist/vendor/lord/laroute/karma.js | 19 + .../vendor/lord/laroute/public/.gitkeep | 0 .../vendor/lord/laroute/public/js/.gitkeep | 0 .../vendor/lord/laroute/public/js/laroute.js | 1 + .../src/Compilers/CompilerInterface.php | 16 + .../src/Compilers/TemplateCompiler.php | 28 + .../Commands/LarouteGeneratorCommand.php | 174 + .../src/Generators/GeneratorInterface.php | 29 + .../src/Generators/TemplateGenerator.php | 66 + .../laroute/src/LarouteServiceProvider.php | 95 + .../Routes/Exceptions/ZeroRoutesException.php | 5 + .../lord/laroute/src/templates/laroute.js | 195 + .../lord/laroute/src/templates/laroute.min.js | 1 + .../vendor/mews/purifier/.scrutinizer.yml | 23 + .../vendor/mews/purifier/config/purifier.php | 105 + .../mews/purifier/src/Facades/Purifier.php | 18 + .../vendor/mews/purifier/src/Purifier.php | 275 + .../purifier/src/PurifierServiceProvider.php | 66 + .../vendor/mews/purifier/src/helpers.php | 8 + .../monolog/src/Monolog/ErrorHandler.php | 239 + .../Monolog/Formatter/ChromePHPFormatter.php | 78 + .../Monolog/Formatter/ElasticaFormatter.php | 89 + .../Monolog/Formatter/FlowdockFormatter.php | 116 + .../Monolog/Formatter/FluentdFormatter.php | 88 + .../Monolog/Formatter/FormatterInterface.php | 36 + .../Formatter/GelfMessageFormatter.php | 138 + .../src/Monolog/Formatter/HtmlFormatter.php | 142 + .../src/Monolog/Formatter/JsonFormatter.php | 214 + .../src/Monolog/Formatter/LineFormatter.php | 181 + .../src/Monolog/Formatter/LogglyFormatter.php | 47 + .../Monolog/Formatter/LogstashFormatter.php | 166 + .../Monolog/Formatter/MongoDBFormatter.php | 107 + .../Monolog/Formatter/NormalizerFormatter.php | 199 + .../src/Monolog/Formatter/ScalarFormatter.php | 48 + .../Monolog/Formatter/WildfireFormatter.php | 113 + .../src/Monolog/Handler/AbstractHandler.php | 196 + .../Handler/AbstractProcessingHandler.php | 68 + .../Monolog/Handler/AbstractSyslogHandler.php | 101 + .../src/Monolog/Handler/AmqpHandler.php | 148 + .../Monolog/Handler/BrowserConsoleHandler.php | 241 + .../src/Monolog/Handler/BufferHandler.php | 148 + .../src/Monolog/Handler/ChromePHPHandler.php | 212 + .../src/Monolog/Handler/CouchDBHandler.php | 72 + .../src/Monolog/Handler/CubeHandler.php | 152 + .../monolog/src/Monolog/Handler/Curl/Util.php | 57 + .../Monolog/Handler/DeduplicationHandler.php | 169 + .../Handler/DoctrineCouchDBHandler.php | 45 + .../src/Monolog/Handler/DynamoDbHandler.php | 108 + .../Monolog/Handler/ElasticSearchHandler.php | 128 + .../src/Monolog/Handler/ErrorLogHandler.php | 82 + .../src/Monolog/Handler/FilterHandler.php | 172 + .../ActivationStrategyInterface.php | 28 + .../ChannelLevelActivationStrategy.php | 59 + .../ErrorLevelActivationStrategy.php | 34 + .../Monolog/Handler/FingersCrossedHandler.php | 207 + .../src/Monolog/Handler/FirePHPHandler.php | 195 + .../src/Monolog/Handler/FleepHookHandler.php | 126 + .../src/Monolog/Handler/FlowdockHandler.php | 128 + .../Handler/FormattableHandlerInterface.php | 39 + .../Handler/FormattableHandlerTrait.php | 63 + .../src/Monolog/Handler/GelfHandler.php | 65 + .../src/Monolog/Handler/GroupHandler.php | 117 + .../src/Monolog/Handler/HandlerInterface.php | 90 + .../src/Monolog/Handler/HandlerWrapper.php | 116 + .../src/Monolog/Handler/HipChatHandler.php | 367 + .../src/Monolog/Handler/IFTTTHandler.php | 70 + .../src/Monolog/Handler/InsightOpsHandler.php | 62 + .../src/Monolog/Handler/LogEntriesHandler.php | 55 + .../src/Monolog/Handler/LogglyHandler.php | 102 + .../src/Monolog/Handler/MailHandler.php | 67 + .../src/Monolog/Handler/MandrillHandler.php | 72 + .../Handler/MissingExtensionException.php | 21 + .../src/Monolog/Handler/MongoDBHandler.php | 59 + .../Monolog/Handler/NativeMailerHandler.php | 185 + .../src/Monolog/Handler/NewRelicHandler.php | 205 + .../src/Monolog/Handler/NullHandler.php | 45 + .../src/Monolog/Handler/PHPConsoleHandler.php | 243 + .../Handler/ProcessableHandlerInterface.php | 40 + .../Handler/ProcessableHandlerTrait.php | 73 + .../src/Monolog/Handler/PsrHandler.php | 56 + .../src/Monolog/Handler/PushoverHandler.php | 185 + .../src/Monolog/Handler/RavenHandler.php | 234 + .../src/Monolog/Handler/RedisHandler.php | 98 + .../src/Monolog/Handler/RollbarHandler.php | 144 + .../Monolog/Handler/RotatingFileHandler.php | 191 + .../src/Monolog/Handler/SamplingHandler.php | 113 + .../src/Monolog/Handler/Slack/SlackRecord.php | 299 + .../src/Monolog/Handler/SlackHandler.php | 221 + .../Monolog/Handler/SlackWebhookHandler.php | 121 + .../src/Monolog/Handler/SlackbotHandler.php | 84 + .../src/Monolog/Handler/SocketHandler.php | 385 + .../src/Monolog/Handler/StreamHandler.php | 194 + .../Monolog/Handler/SwiftMailerHandler.php | 111 + .../src/Monolog/Handler/SyslogHandler.php | 67 + .../Monolog/Handler/SyslogUdp/UdpSocket.php | 56 + .../src/Monolog/Handler/SyslogUdpHandler.php | 124 + .../src/Monolog/Handler/TestHandler.php | 177 + .../Handler/WhatFailureGroupHandler.php | 72 + .../Monolog/Handler/ZendMonitorHandler.php | 101 + .../monolog/monolog/src/Monolog/Logger.php | 796 + .../src/Monolog/Processor/GitProcessor.php | 64 + .../Processor/IntrospectionProcessor.php | 112 + .../Processor/MemoryPeakUsageProcessor.php | 35 + .../src/Monolog/Processor/MemoryProcessor.php | 63 + .../Processor/MemoryUsageProcessor.php | 35 + .../Monolog/Processor/MercurialProcessor.php | 63 + .../Monolog/Processor/ProcessIdProcessor.php | 31 + .../Monolog/Processor/ProcessorInterface.php | 25 + .../Processor/PsrLogMessageProcessor.php | 81 + .../src/Monolog/Processor/TagProcessor.php | 44 + .../src/Monolog/Processor/UidProcessor.php | 59 + .../src/Monolog/Processor/WebProcessor.php | 113 + .../monolog/monolog/src/Monolog/Registry.php | 134 + .../src/Monolog/ResettableInterface.php | 31 + .../monolog/src/Monolog/SignalHandler.php | 115 + .../monolog/monolog/src/Monolog/Utils.php | 189 + .../src/Cron/AbstractField.php | 148 + .../src/Cron/CronExpression.php | 389 + .../src/Cron/DayOfMonthField.php | 173 + .../src/Cron/DayOfWeekField.php | 141 + .../cron-expression/src/Cron/FieldFactory.php | 57 + .../src/Cron/FieldInterface.php | 40 + .../cron-expression/src/Cron/HoursField.php | 71 + .../cron-expression/src/Cron/MinutesField.php | 62 + .../cron-expression/src/Cron/MonthField.php | 44 + .../cron-expression/src/Cron/YearField.php | 37 + freescout-dist/vendor/natxet/cssmin/README | 3 + .../carbon/src/Carbon/CarbonInterval.php | 1143 ++ .../nesbot/carbon/src/Carbon/CarbonPeriod.php | 1445 ++ .../Exceptions/InvalidDateException.php | 67 + .../nesbot/carbon/src/Carbon/Lang/af.php | 31 + .../nesbot/carbon/src/Carbon/Lang/ar.php | 31 + .../carbon/src/Carbon/Lang/ar_Shakl.php | 31 + .../nesbot/carbon/src/Carbon/Lang/az.php | 40 + .../nesbot/carbon/src/Carbon/Lang/bg.php | 31 + .../nesbot/carbon/src/Carbon/Lang/bn.php | 38 + .../nesbot/carbon/src/Carbon/Lang/bs_BA.php | 31 + .../nesbot/carbon/src/Carbon/Lang/ca.php | 40 + .../nesbot/carbon/src/Carbon/Lang/cs.php | 31 + .../nesbot/carbon/src/Carbon/Lang/cy.php | 29 + .../nesbot/carbon/src/Carbon/Lang/da.php | 31 + .../nesbot/carbon/src/Carbon/Lang/de.php | 46 + .../nesbot/carbon/src/Carbon/Lang/dv_MV.php | 31 + .../nesbot/carbon/src/Carbon/Lang/el.php | 31 + .../nesbot/carbon/src/Carbon/Lang/en.php | 40 + .../nesbot/carbon/src/Carbon/Lang/eo.php | 31 + .../nesbot/carbon/src/Carbon/Lang/es.php | 36 + .../nesbot/carbon/src/Carbon/Lang/et.php | 38 + .../nesbot/carbon/src/Carbon/Lang/eu.php | 31 + .../nesbot/carbon/src/Carbon/Lang/fa.php | 31 + .../nesbot/carbon/src/Carbon/Lang/fi.php | 31 + .../nesbot/carbon/src/Carbon/Lang/fo.php | 31 + .../nesbot/carbon/src/Carbon/Lang/fr.php | 40 + .../nesbot/carbon/src/Carbon/Lang/gl.php | 24 + .../nesbot/carbon/src/Carbon/Lang/gu.php | 31 + .../nesbot/carbon/src/Carbon/Lang/he.php | 31 + .../nesbot/carbon/src/Carbon/Lang/hi.php | 31 + .../nesbot/carbon/src/Carbon/Lang/hr.php | 31 + .../nesbot/carbon/src/Carbon/Lang/hu.php | 52 + .../nesbot/carbon/src/Carbon/Lang/hy.php | 31 + .../nesbot/carbon/src/Carbon/Lang/id.php | 31 + .../nesbot/carbon/src/Carbon/Lang/is.php | 31 + .../nesbot/carbon/src/Carbon/Lang/it.php | 36 + .../nesbot/carbon/src/Carbon/Lang/ja.php | 31 + .../nesbot/carbon/src/Carbon/Lang/ka.php | 31 + .../nesbot/carbon/src/Carbon/Lang/kk.php | 29 + .../nesbot/carbon/src/Carbon/Lang/km.php | 31 + .../nesbot/carbon/src/Carbon/Lang/ko.php | 31 + .../nesbot/carbon/src/Carbon/Lang/lt.php | 38 + .../nesbot/carbon/src/Carbon/Lang/lv.php | 47 + .../nesbot/carbon/src/Carbon/Lang/mk.php | 24 + .../nesbot/carbon/src/Carbon/Lang/mn.php | 62 + .../nesbot/carbon/src/Carbon/Lang/ms.php | 31 + .../nesbot/carbon/src/Carbon/Lang/my.php | 37 + .../nesbot/carbon/src/Carbon/Lang/ne.php | 31 + .../nesbot/carbon/src/Carbon/Lang/nl.php | 36 + .../nesbot/carbon/src/Carbon/Lang/no.php | 36 + .../nesbot/carbon/src/Carbon/Lang/oc.php | 44 + .../nesbot/carbon/src/Carbon/Lang/pl.php | 36 + .../nesbot/carbon/src/Carbon/Lang/ps.php | 31 + .../nesbot/carbon/src/Carbon/Lang/pt.php | 31 + .../nesbot/carbon/src/Carbon/Lang/pt_BR.php | 40 + .../nesbot/carbon/src/Carbon/Lang/pt_PT.php | 31 + .../nesbot/carbon/src/Carbon/Lang/ro.php | 31 + .../nesbot/carbon/src/Carbon/Lang/ru.php | 31 + .../nesbot/carbon/src/Carbon/Lang/sh.php | 35 + .../nesbot/carbon/src/Carbon/Lang/sk.php | 38 + .../nesbot/carbon/src/Carbon/Lang/sl.php | 43 + .../nesbot/carbon/src/Carbon/Lang/sq.php | 31 + .../nesbot/carbon/src/Carbon/Lang/sr.php | 37 + .../nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php | 43 + .../carbon/src/Carbon/Lang/sr_Cyrl_ME.php | 43 + .../carbon/src/Carbon/Lang/sr_Latn_ME.php | 43 + .../nesbot/carbon/src/Carbon/Lang/sr_ME.php | 12 + .../nesbot/carbon/src/Carbon/Lang/sv.php | 31 + .../nesbot/carbon/src/Carbon/Lang/sw.php | 31 + .../nesbot/carbon/src/Carbon/Lang/th.php | 31 + .../nesbot/carbon/src/Carbon/Lang/tr.php | 31 + .../nesbot/carbon/src/Carbon/Lang/uk.php | 40 + .../nesbot/carbon/src/Carbon/Lang/ur.php | 24 + .../nesbot/carbon/src/Carbon/Lang/uz.php | 31 + .../nesbot/carbon/src/Carbon/Lang/vi.php | 31 + .../nesbot/carbon/src/Carbon/Lang/zh.php | 31 + .../nesbot/carbon/src/Carbon/Lang/zh_TW.php | 31 + .../src/Carbon/Laravel/ServiceProvider.php | 37 + .../nesbot/carbon/src/Carbon/Translator.php | 143 + .../nesbot/carbon/src/JsonSerializable.php | 18 + .../vendor/nikic/php-parser/bin/php-parse | 205 + .../nikic/php-parser/grammar/parser.template | 106 + .../vendor/nikic/php-parser/grammar/php5.y | 1046 ++ .../vendor/nikic/php-parser/grammar/php7.y | 1245 ++ .../nikic/php-parser/grammar/phpyLang.php | 184 + .../php-parser/grammar/rebuildParsers.php | 81 + .../nikic/php-parser/grammar/tokens.template | 17 + .../vendor/nikic/php-parser/grammar/tokens.y | 115 + .../php-parser/lib/PhpParser/Builder.php | 13 + .../lib/PhpParser/Builder/ClassConst.php | 148 + .../lib/PhpParser/Builder/Class_.php | 146 + .../lib/PhpParser/Builder/Declaration.php | 43 + .../lib/PhpParser/Builder/EnumCase.php | 85 + .../lib/PhpParser/Builder/Enum_.php | 117 + .../lib/PhpParser/Builder/FunctionLike.php | 73 + .../lib/PhpParser/Builder/Function_.php | 67 + .../lib/PhpParser/Builder/Interface_.php | 93 + .../lib/PhpParser/Builder/Method.php | 146 + .../lib/PhpParser/Builder/Namespace_.php | 45 + .../lib/PhpParser/Builder/Param.php | 168 + .../lib/PhpParser/Builder/Property.php | 161 + .../lib/PhpParser/Builder/TraitUse.php | 64 + .../PhpParser/Builder/TraitUseAdaptation.php | 148 + .../lib/PhpParser/Builder/Trait_.php | 78 + .../php-parser/lib/PhpParser/Builder/Use_.php | 49 + .../lib/PhpParser/BuilderFactory.php | 399 + .../lib/PhpParser/BuilderHelpers.php | 335 + .../php-parser/lib/PhpParser/Comment.php | 239 + .../php-parser/lib/PhpParser/Comment/Doc.php | 7 + .../ConstExprEvaluationException.php | 6 + .../lib/PhpParser/ConstExprEvaluator.php | 229 + .../nikic/php-parser/lib/PhpParser/Error.php | 180 + .../php-parser/lib/PhpParser/ErrorHandler.php | 13 + .../lib/PhpParser/ErrorHandler/Collecting.php | 46 + .../lib/PhpParser/ErrorHandler/Throwing.php | 18 + .../lib/PhpParser/Internal/DiffElem.php | 27 + .../lib/PhpParser/Internal/Differ.php | 164 + .../Internal/PrintableNewAnonClassNode.php | 64 + .../lib/PhpParser/Internal/TokenStream.php | 286 + .../php-parser/lib/PhpParser/JsonDecoder.php | 103 + .../nikic/php-parser/lib/PhpParser/Lexer.php | 560 + .../lib/PhpParser/Lexer/Emulative.php | 251 + .../Lexer/TokenEmulator/AttributeEmulator.php | 56 + .../CoaleseEqualTokenEmulator.php | 47 + .../Lexer/TokenEmulator/EnumTokenEmulator.php | 31 + .../TokenEmulator/ExplicitOctalEmulator.php | 44 + .../FlexibleDocStringEmulator.php | 76 + .../Lexer/TokenEmulator/FnTokenEmulator.php | 23 + .../Lexer/TokenEmulator/KeywordEmulator.php | 62 + .../TokenEmulator/MatchTokenEmulator.php | 23 + .../TokenEmulator/NullsafeTokenEmulator.php | 67 + .../NumericLiteralSeparatorEmulator.php | 105 + .../ReadonlyFunctionTokenEmulator.php | 31 + .../TokenEmulator/ReadonlyTokenEmulator.php | 36 + .../Lexer/TokenEmulator/ReverseEmulator.php | 36 + .../Lexer/TokenEmulator/TokenEmulator.php | 25 + .../php-parser/lib/PhpParser/NameContext.php | 285 + .../nikic/php-parser/lib/PhpParser/Node.php | 151 + .../php-parser/lib/PhpParser/Node/Arg.php | 46 + .../lib/PhpParser/Node/Attribute.php | 34 + .../lib/PhpParser/Node/AttributeGroup.php | 29 + .../lib/PhpParser/Node/ComplexType.php | 14 + .../php-parser/lib/PhpParser/Node/Const_.php | 37 + .../php-parser/lib/PhpParser/Node/Expr.php | 9 + .../lib/PhpParser/Node/Expr/ArrayDimFetch.php | 34 + .../lib/PhpParser/Node/Expr/ArrayItem.php | 41 + .../lib/PhpParser/Node/Expr/Array_.php | 34 + .../lib/PhpParser/Node/Expr/ArrowFunction.php | 79 + .../lib/PhpParser/Node/Expr/Assign.php | 34 + .../lib/PhpParser/Node/Expr/AssignOp.php | 30 + .../Node/Expr/AssignOp/BitwiseAnd.php | 12 + .../Node/Expr/AssignOp/BitwiseOr.php | 12 + .../Node/Expr/AssignOp/BitwiseXor.php | 12 + .../PhpParser/Node/Expr/AssignOp/Coalesce.php | 12 + .../PhpParser/Node/Expr/AssignOp/Concat.php | 12 + .../lib/PhpParser/Node/Expr/AssignOp/Div.php | 12 + .../PhpParser/Node/Expr/AssignOp/Minus.php | 12 + .../lib/PhpParser/Node/Expr/AssignOp/Mod.php | 12 + .../lib/PhpParser/Node/Expr/AssignOp/Mul.php | 12 + .../lib/PhpParser/Node/Expr/AssignOp/Plus.php | 12 + .../lib/PhpParser/Node/Expr/AssignOp/Pow.php | 12 + .../Node/Expr/AssignOp/ShiftLeft.php | 12 + .../Node/Expr/AssignOp/ShiftRight.php | 12 + .../lib/PhpParser/Node/Expr/AssignRef.php | 34 + .../lib/PhpParser/Node/Expr/BinaryOp.php | 40 + .../Node/Expr/BinaryOp/BitwiseAnd.php | 16 + .../Node/Expr/BinaryOp/BitwiseOr.php | 16 + .../Node/Expr/BinaryOp/BitwiseXor.php | 16 + .../Node/Expr/BinaryOp/BooleanAnd.php | 16 + .../Node/Expr/BinaryOp/BooleanOr.php | 16 + .../PhpParser/Node/Expr/BinaryOp/Coalesce.php | 16 + .../PhpParser/Node/Expr/BinaryOp/Concat.php | 16 + .../lib/PhpParser/Node/Expr/BinaryOp/Div.php | 16 + .../PhpParser/Node/Expr/BinaryOp/Equal.php | 16 + .../PhpParser/Node/Expr/BinaryOp/Greater.php | 16 + .../Node/Expr/BinaryOp/GreaterOrEqual.php | 16 + .../Node/Expr/BinaryOp/Identical.php | 16 + .../Node/Expr/BinaryOp/LogicalAnd.php | 16 + .../Node/Expr/BinaryOp/LogicalOr.php | 16 + .../Node/Expr/BinaryOp/LogicalXor.php | 16 + .../PhpParser/Node/Expr/BinaryOp/Minus.php | 16 + .../lib/PhpParser/Node/Expr/BinaryOp/Mod.php | 16 + .../lib/PhpParser/Node/Expr/BinaryOp/Mul.php | 16 + .../PhpParser/Node/Expr/BinaryOp/NotEqual.php | 16 + .../Node/Expr/BinaryOp/NotIdentical.php | 16 + .../lib/PhpParser/Node/Expr/BinaryOp/Plus.php | 16 + .../lib/PhpParser/Node/Expr/BinaryOp/Pow.php | 16 + .../Node/Expr/BinaryOp/ShiftLeft.php | 16 + .../Node/Expr/BinaryOp/ShiftRight.php | 16 + .../PhpParser/Node/Expr/BinaryOp/Smaller.php | 16 + .../Node/Expr/BinaryOp/SmallerOrEqual.php | 16 + .../Node/Expr/BinaryOp/Spaceship.php | 16 + .../lib/PhpParser/Node/Expr/BitwiseNot.php | 30 + .../lib/PhpParser/Node/Expr/BooleanNot.php | 30 + .../lib/PhpParser/Node/Expr/CallLike.php | 39 + .../lib/PhpParser/Node/Expr/Cast.php | 26 + .../lib/PhpParser/Node/Expr/Cast/Array_.php | 12 + .../lib/PhpParser/Node/Expr/Cast/Bool_.php | 12 + .../lib/PhpParser/Node/Expr/Cast/Double.php | 17 + .../lib/PhpParser/Node/Expr/Cast/Int_.php | 12 + .../lib/PhpParser/Node/Expr/Cast/Object_.php | 12 + .../lib/PhpParser/Node/Expr/Cast/String_.php | 12 + .../lib/PhpParser/Node/Expr/Cast/Unset_.php | 12 + .../PhpParser/Node/Expr/ClassConstFetch.php | 36 + .../lib/PhpParser/Node/Expr/Clone_.php | 30 + .../lib/PhpParser/Node/Expr/Closure.php | 79 + .../lib/PhpParser/Node/Expr/ClosureUse.php | 34 + .../lib/PhpParser/Node/Expr/ConstFetch.php | 31 + .../lib/PhpParser/Node/Expr/Empty_.php | 30 + .../lib/PhpParser/Node/Expr/Error.php | 31 + .../lib/PhpParser/Node/Expr/ErrorSuppress.php | 30 + .../lib/PhpParser/Node/Expr/Eval_.php | 30 + .../lib/PhpParser/Node/Expr/Exit_.php | 34 + .../lib/PhpParser/Node/Expr/FuncCall.php | 39 + .../lib/PhpParser/Node/Expr/Include_.php | 39 + .../lib/PhpParser/Node/Expr/Instanceof_.php | 35 + .../lib/PhpParser/Node/Expr/Isset_.php | 30 + .../lib/PhpParser/Node/Expr/List_.php | 30 + .../lib/PhpParser/Node/Expr/Match_.php | 31 + .../lib/PhpParser/Node/Expr/MethodCall.php | 45 + .../lib/PhpParser/Node/Expr/New_.php | 41 + .../Node/Expr/NullsafeMethodCall.php | 45 + .../Node/Expr/NullsafePropertyFetch.php | 35 + .../lib/PhpParser/Node/Expr/PostDec.php | 30 + .../lib/PhpParser/Node/Expr/PostInc.php | 30 + .../lib/PhpParser/Node/Expr/PreDec.php | 30 + .../lib/PhpParser/Node/Expr/PreInc.php | 30 + .../lib/PhpParser/Node/Expr/Print_.php | 30 + .../lib/PhpParser/Node/Expr/PropertyFetch.php | 35 + .../lib/PhpParser/Node/Expr/ShellExec.php | 30 + .../lib/PhpParser/Node/Expr/StaticCall.php | 46 + .../Node/Expr/StaticPropertyFetch.php | 36 + .../lib/PhpParser/Node/Expr/Ternary.php | 38 + .../lib/PhpParser/Node/Expr/Throw_.php | 30 + .../lib/PhpParser/Node/Expr/UnaryMinus.php | 30 + .../lib/PhpParser/Node/Expr/UnaryPlus.php | 30 + .../lib/PhpParser/Node/Expr/Variable.php | 30 + .../lib/PhpParser/Node/Expr/YieldFrom.php | 30 + .../lib/PhpParser/Node/Expr/Yield_.php | 34 + .../lib/PhpParser/Node/FunctionLike.php | 43 + .../lib/PhpParser/Node/Identifier.php | 75 + .../lib/PhpParser/Node/IntersectionType.php | 30 + .../lib/PhpParser/Node/MatchArm.php | 31 + .../php-parser/lib/PhpParser/Node/Name.php | 254 + .../PhpParser/Node/Name/FullyQualified.php | 50 + .../lib/PhpParser/Node/Name/Relative.php | 50 + .../lib/PhpParser/Node/NullableType.php | 28 + .../php-parser/lib/PhpParser/Node/Param.php | 60 + .../php-parser/lib/PhpParser/Node/Scalar.php | 7 + .../lib/PhpParser/Node/Scalar/DNumber.php | 77 + .../lib/PhpParser/Node/Scalar/Encapsed.php | 31 + .../Node/Scalar/EncapsedStringPart.php | 30 + .../lib/PhpParser/Node/Scalar/LNumber.php | 80 + .../lib/PhpParser/Node/Scalar/MagicConst.php | 28 + .../Node/Scalar/MagicConst/Class_.php | 16 + .../PhpParser/Node/Scalar/MagicConst/Dir.php | 16 + .../PhpParser/Node/Scalar/MagicConst/File.php | 16 + .../Node/Scalar/MagicConst/Function_.php | 16 + .../PhpParser/Node/Scalar/MagicConst/Line.php | 16 + .../Node/Scalar/MagicConst/Method.php | 16 + .../Node/Scalar/MagicConst/Namespace_.php | 16 + .../Node/Scalar/MagicConst/Trait_.php | 16 + .../lib/PhpParser/Node/Scalar/String_.php | 157 + .../php-parser/lib/PhpParser/Node/Stmt.php | 9 + .../lib/PhpParser/Node/Stmt/Break_.php | 30 + .../lib/PhpParser/Node/Stmt/Case_.php | 34 + .../lib/PhpParser/Node/Stmt/Catch_.php | 41 + .../lib/PhpParser/Node/Stmt/ClassConst.php | 85 + .../lib/PhpParser/Node/Stmt/ClassLike.php | 109 + .../lib/PhpParser/Node/Stmt/ClassMethod.php | 161 + .../lib/PhpParser/Node/Stmt/Class_.php | 137 + .../lib/PhpParser/Node/Stmt/Const_.php | 30 + .../lib/PhpParser/Node/Stmt/Continue_.php | 30 + .../PhpParser/Node/Stmt/DeclareDeclare.php | 34 + .../lib/PhpParser/Node/Stmt/Declare_.php | 34 + .../lib/PhpParser/Node/Stmt/Do_.php | 34 + .../lib/PhpParser/Node/Stmt/Echo_.php | 30 + .../lib/PhpParser/Node/Stmt/ElseIf_.php | 34 + .../lib/PhpParser/Node/Stmt/Else_.php | 30 + .../lib/PhpParser/Node/Stmt/EnumCase.php | 37 + .../lib/PhpParser/Node/Stmt/Enum_.php | 40 + .../lib/PhpParser/Node/Stmt/Expression.php | 33 + .../lib/PhpParser/Node/Stmt/Finally_.php | 30 + .../lib/PhpParser/Node/Stmt/For_.php | 43 + .../lib/PhpParser/Node/Stmt/Foreach_.php | 47 + .../lib/PhpParser/Node/Stmt/Function_.php | 77 + .../lib/PhpParser/Node/Stmt/Global_.php | 30 + .../lib/PhpParser/Node/Stmt/Goto_.php | 31 + .../lib/PhpParser/Node/Stmt/GroupUse.php | 39 + .../lib/PhpParser/Node/Stmt/HaltCompiler.php | 30 + .../lib/PhpParser/Node/Stmt/If_.php | 43 + .../lib/PhpParser/Node/Stmt/InlineHTML.php | 30 + .../lib/PhpParser/Node/Stmt/Interface_.php | 37 + .../lib/PhpParser/Node/Stmt/Label.php | 31 + .../lib/PhpParser/Node/Stmt/Namespace_.php | 38 + .../lib/PhpParser/Node/Stmt/Nop.php | 17 + .../lib/PhpParser/Node/Stmt/Property.php | 91 + .../PhpParser/Node/Stmt/PropertyProperty.php | 34 + .../lib/PhpParser/Node/Stmt/Return_.php | 30 + .../lib/PhpParser/Node/Stmt/StaticVar.php | 37 + .../lib/PhpParser/Node/Stmt/Static_.php | 30 + .../lib/PhpParser/Node/Stmt/Switch_.php | 34 + .../lib/PhpParser/Node/Stmt/Throw_.php | 30 + .../lib/PhpParser/Node/Stmt/TraitUse.php | 34 + .../Node/Stmt/TraitUseAdaptation.php | 13 + .../Node/Stmt/TraitUseAdaptation/Alias.php | 38 + .../Stmt/TraitUseAdaptation/Precedence.php | 34 + .../lib/PhpParser/Node/Stmt/Trait_.php | 32 + .../lib/PhpParser/Node/Stmt/TryCatch.php | 38 + .../lib/PhpParser/Node/Stmt/Unset_.php | 30 + .../lib/PhpParser/Node/Stmt/UseUse.php | 52 + .../lib/PhpParser/Node/Stmt/Use_.php | 47 + .../lib/PhpParser/Node/Stmt/While_.php | 34 + .../lib/PhpParser/Node/UnionType.php | 28 + .../lib/PhpParser/Node/VarLikeIdentifier.php | 17 + .../PhpParser/Node/VariadicPlaceholder.php | 27 + .../php-parser/lib/PhpParser/NodeAbstract.php | 178 + .../php-parser/lib/PhpParser/NodeDumper.php | 206 + .../php-parser/lib/PhpParser/NodeFinder.php | 81 + .../lib/PhpParser/NodeTraverser.php | 291 + .../lib/PhpParser/NodeTraverserInterface.php | 29 + .../php-parser/lib/PhpParser/NodeVisitor.php | 72 + .../PhpParser/NodeVisitor/CloningVisitor.php | 20 + .../PhpParser/NodeVisitor/FindingVisitor.php | 48 + .../NodeVisitor/FirstFindingVisitor.php | 50 + .../PhpParser/NodeVisitor/NameResolver.php | 257 + .../NodeVisitor/NodeConnectingVisitor.php | 52 + .../NodeVisitor/ParentConnectingVisitor.php | 41 + .../lib/PhpParser/NodeVisitorAbstract.php | 25 + .../nikic/php-parser/lib/PhpParser/Parser.php | 18 + .../lib/PhpParser/Parser/Multiple.php | 55 + .../php-parser/lib/PhpParser/Parser/Php5.php | 2682 +++ .../php-parser/lib/PhpParser/Parser/Php7.php | 2898 +++ .../lib/PhpParser/Parser/Tokens.php | 148 + .../lib/PhpParser/ParserAbstract.php | 1060 ++ .../lib/PhpParser/ParserFactory.php | 44 + .../lib/PhpParser/PrettyPrinter/Standard.php | 1126 ++ .../lib/PhpParser/PrettyPrinterAbstract.php | 1576 ++ .../nwidart/laravel-modules/config/config.php | 183 + .../laravel-modules/src/Collection.php | 38 + .../src/Commands/CommandMakeCommand.php | 108 + .../src/Commands/ControllerMakeCommand.php | 125 + .../src/Commands/DisableCommand.php | 51 + .../src/Commands/DumpCommand.php | 62 + .../src/Commands/EnableCommand.php | 51 + .../src/Commands/EventMakeCommand.php | 74 + .../src/Commands/FactoryMakeCommand.php | 76 + .../src/Commands/GeneratorCommand.php | 99 + .../src/Commands/InstallCommand.php | 146 + .../src/Commands/JobMakeCommand.php | 109 + .../src/Commands/ListCommand.php | 86 + .../src/Commands/ListenerMakeCommand.php | 122 + .../src/Commands/MailMakeCommand.php | 84 + .../src/Commands/MiddlewareMakeCommand.php | 86 + .../src/Commands/MigrateCommand.php | 109 + .../src/Commands/MigrateRefreshCommand.php | 90 + .../src/Commands/MigrateResetCommand.php | 114 + .../src/Commands/MigrateRollbackCommand.php | 114 + .../src/Commands/MigrateStatusCommand.php | 94 + .../src/Commands/MigrationMakeCommand.php | 163 + .../src/Commands/ModelMakeCommand.php | 167 + .../src/Commands/ModuleMakeCommand.php | 64 + .../src/Commands/NotificationMakeCommand.php | 84 + .../src/Commands/PolicyMakeCommand.php | 86 + .../src/Commands/ProviderMakeCommand.php | 111 + .../src/Commands/PublishCommand.php | 82 + .../Commands/PublishConfigurationCommand.php | 86 + .../src/Commands/PublishMigrationCommand.php | 68 + .../Commands/PublishTranslationCommand.php | 82 + .../src/Commands/RequestMakeCommand.php | 86 + .../src/Commands/ResourceMakeCommand.php | 100 + .../src/Commands/RouteProviderMakeCommand.php | 94 + .../src/Commands/RuleMakeCommand.php | 86 + .../src/Commands/SeedCommand.php | 206 + .../src/Commands/SeedMakeCommand.php | 103 + .../src/Commands/SetupCommand.php | 76 + .../src/Commands/TestMakeCommand.php | 69 + .../src/Commands/UnUseCommand.php | 32 + .../src/Commands/UpdateCommand.php | 48 + .../src/Commands/UseCommand.php | 54 + .../src/Commands/stubs/command.stub | 68 + .../src/Commands/stubs/composer.stub | 25 + .../src/Commands/stubs/controller-plain.stub | 9 + .../src/Commands/stubs/controller.stub | 72 + .../src/Commands/stubs/event.stub | 30 + .../src/Commands/stubs/factory.stub | 9 + .../src/Commands/stubs/job-queued.stub | 34 + .../src/Commands/stubs/job.stub | 31 + .../src/Commands/stubs/json.stub | 16 + .../src/Commands/stubs/listener-duck.stub | 30 + .../Commands/stubs/listener-queued-duck.stub | 32 + .../src/Commands/stubs/listener-queued.stub | 33 + .../src/Commands/stubs/listener.stub | 31 + .../src/Commands/stubs/mail.stub | 33 + .../src/Commands/stubs/middleware.stub | 21 + .../src/Commands/stubs/migration/add.stub | 32 + .../src/Commands/stubs/migration/create.stub | 32 + .../src/Commands/stubs/migration/delete.stub | 32 + .../src/Commands/stubs/migration/drop.stub | 32 + .../src/Commands/stubs/migration/plain.stub | 28 + .../src/Commands/stubs/model.stub | 10 + .../src/Commands/stubs/notification.stub | 61 + .../src/Commands/stubs/policy.plain.stub | 20 + .../src/Commands/stubs/provider.stub | 35 + .../src/Commands/stubs/request.stub | 30 + .../Commands/stubs/resource-collection.stub | 19 + .../src/Commands/stubs/resource.stub | 19 + .../src/Commands/stubs/route-provider.stub | 40 + .../src/Commands/stubs/routes.stub | 6 + .../src/Commands/stubs/rule.stub | 40 + .../src/Commands/stubs/scaffold/config.stub | 5 + .../src/Commands/stubs/scaffold/provider.stub | 112 + .../src/Commands/stubs/seeder.stub | 21 + .../src/Commands/stubs/start.stub | 17 + .../src/Commands/stubs/unit-test.stub | 19 + .../src/Commands/stubs/views/index.stub | 9 + .../src/Commands/stubs/views/master.stub | 12 + .../src/Contracts/PublisherInterface.php | 13 + .../src/Contracts/RepositoryInterface.php | 96 + .../src/Contracts/RunableInterface.php | 13 + .../Exceptions/FileAlreadyExistException.php | 7 + .../src/Exceptions/InvalidAssetPath.php | 11 + .../src/Exceptions/InvalidJsonException.php | 7 + .../Exceptions/ModuleNotFoundException.php | 7 + .../laravel-modules/src/Facades/Module.php | 18 + .../src/Generators/FileGenerator.php | 128 + .../src/Generators/Generator.php | 7 + .../src/Generators/ModuleGenerator.php | 499 + .../laravel-modules/src/Laravel/Module.php | 40 + .../src/Laravel/Repository.php | 17 + .../src/LaravelModulesServiceProvider.php | 53 + .../laravel-modules/src/Lumen/Module.php | 34 + .../laravel-modules/src/Lumen/Repository.php | 17 + .../src/LumenModulesServiceProvider.php | 51 + .../src/Migrations/Migrator.php | 314 + .../src/ModulesServiceProvider.php | 77 + .../laravel-modules/src/Process/Installer.php | 300 + .../laravel-modules/src/Process/Runner.php | 36 + .../laravel-modules/src/Process/Updater.php | 78 + .../Providers/BootstrapServiceProvider.php | 24 + .../src/Providers/ConsoleServiceProvider.php | 114 + .../Providers/ContractsServiceProvider.php | 18 + .../src/Publishing/AssetPublisher.php | 37 + .../src/Publishing/LangPublisher.php | 39 + .../src/Publishing/MigrationPublisher.php | 43 + .../src/Publishing/Publisher.php | 197 + .../src/Routing/Controller.php | 13 + .../Support/Config/GenerateConfigReader.php | 11 + .../src/Support/Config/GeneratorPath.php | 31 + .../src/Support/Migrations/NameParser.php | 200 + .../src/Support/Migrations/SchemaParser.php | 272 + .../laravel-modules/src/Support/Stub.php | 171 + .../src/Traits/CanClearModulesCache.php | 16 + .../src/Traits/MigrationLoaderTrait.php | 32 + .../src/Traits/ModuleCommandTrait.php | 20 + .../nwidart/laravel-modules/src/helpers.php | 36 + .../paragonie/random_compat/build-phar.sh | 5 + .../dist/random_compat.phar.pubkey | 5 + .../dist/random_compat.phar.pubkey.asc | 11 + .../paragonie/random_compat/lib/random.php | 32 + .../random_compat/other/build_phar.php | 57 + .../random_compat/psalm-autoload.php | 9 + .../vendor/paragonie/random_compat/psalm.xml | 19 + .../vendor/patchwork/utf8/.travis.yml | 42 + .../vendor/patchwork/utf8/appveyor.yml | 38 + .../vendor/patchwork/utf8/src/Normalizer.php | 18 + .../utf8/src/Patchwork/PHP/Shim/Iconv.php | 729 + .../utf8/src/Patchwork/PHP/Shim/Intl.php | 232 + .../utf8/src/Patchwork/PHP/Shim/Mbstring.php | 602 + .../src/Patchwork/PHP/Shim/Normalizer.php | 301 + .../utf8/src/Patchwork/PHP/Shim/Xml.php | 61 + .../Patchwork/PHP/Shim/charset/from.big5.ser | 1 + .../Patchwork/PHP/Shim/charset/from.cp037.ser | Bin 0 -> 4192 bytes .../PHP/Shim/charset/from.cp1006.ser | Bin 0 -> 4273 bytes .../PHP/Shim/charset/from.cp1026.ser | Bin 0 -> 4192 bytes .../Patchwork/PHP/Shim/charset/from.cp424.ser | Bin 0 -> 3547 bytes .../Patchwork/PHP/Shim/charset/from.cp437.ser | Bin 0 -> 4254 bytes .../Patchwork/PHP/Shim/charset/from.cp500.ser | Bin 0 -> 4192 bytes .../Patchwork/PHP/Shim/charset/from.cp737.ser | Bin 0 -> 4247 bytes .../Patchwork/PHP/Shim/charset/from.cp775.ser | Bin 0 -> 4228 bytes .../Patchwork/PHP/Shim/charset/from.cp850.ser | Bin 0 -> 4222 bytes .../Patchwork/PHP/Shim/charset/from.cp852.ser | Bin 0 -> 4221 bytes .../Patchwork/PHP/Shim/charset/from.cp855.ser | Bin 0 -> 4222 bytes .../Patchwork/PHP/Shim/charset/from.cp856.ser | Bin 0 -> 3525 bytes .../Patchwork/PHP/Shim/charset/from.cp857.ser | Bin 0 -> 4170 bytes .../Patchwork/PHP/Shim/charset/from.cp860.ser | Bin 0 -> 4253 bytes .../Patchwork/PHP/Shim/charset/from.cp861.ser | Bin 0 -> 4254 bytes .../Patchwork/PHP/Shim/charset/from.cp862.ser | Bin 0 -> 4254 bytes .../Patchwork/PHP/Shim/charset/from.cp863.ser | Bin 0 -> 4254 bytes .../Patchwork/PHP/Shim/charset/from.cp864.ser | Bin 0 -> 4180 bytes .../Patchwork/PHP/Shim/charset/from.cp865.ser | Bin 0 -> 4254 bytes .../Patchwork/PHP/Shim/charset/from.cp866.ser | Bin 0 -> 4244 bytes .../Patchwork/PHP/Shim/charset/from.cp869.ser | Bin 0 -> 4071 bytes .../Patchwork/PHP/Shim/charset/from.cp874.ser | Bin 0 -> 3761 bytes .../Patchwork/PHP/Shim/charset/from.cp875.ser | Bin 0 -> 4189 bytes .../Patchwork/PHP/Shim/charset/from.cp932.ser | Bin 0 -> 149785 bytes .../Patchwork/PHP/Shim/charset/from.cp936.ser | Bin 0 -> 415908 bytes .../Patchwork/PHP/Shim/charset/from.cp949.ser | Bin 0 -> 325759 bytes .../Patchwork/PHP/Shim/charset/from.cp950.ser | Bin 0 -> 258514 bytes .../PHP/Shim/charset/from.gsm0338.ser | Bin 0 -> 2228 bytes .../PHP/Shim/charset/from.iso-8859-1.ser | Bin 0 -> 4192 bytes .../PHP/Shim/charset/from.iso-8859-10.ser | Bin 0 -> 4193 bytes .../PHP/Shim/charset/from.iso-8859-11.ser | Bin 0 -> 4143 bytes .../PHP/Shim/charset/from.iso-8859-13.ser | Bin 0 -> 4196 bytes .../PHP/Shim/charset/from.iso-8859-14.ser | Bin 0 -> 4214 bytes .../PHP/Shim/charset/from.iso-8859-15.ser | Bin 0 -> 4193 bytes .../PHP/Shim/charset/from.iso-8859-16.ser | Bin 0 -> 4195 bytes .../PHP/Shim/charset/from.iso-8859-2.ser | Bin 0 -> 4192 bytes .../PHP/Shim/charset/from.iso-8859-3.ser | Bin 0 -> 4073 bytes .../PHP/Shim/charset/from.iso-8859-4.ser | Bin 0 -> 4192 bytes .../PHP/Shim/charset/from.iso-8859-5.ser | Bin 0 -> 4193 bytes .../PHP/Shim/charset/from.iso-8859-6.ser | Bin 0 -> 3427 bytes .../PHP/Shim/charset/from.iso-8859-7.ser | Bin 0 -> 4093 bytes .../PHP/Shim/charset/from.iso-8859-8.ser | Bin 0 -> 3583 bytes .../PHP/Shim/charset/from.iso-8859-9.ser | Bin 0 -> 4192 bytes .../PHP/Shim/charset/from.koi8-r.ser | Bin 0 -> 4248 bytes .../PHP/Shim/charset/from.koi8-u.ser | Bin 0 -> 4240 bytes .../PHP/Shim/charset/from.mazovia.ser | Bin 0 -> 4254 bytes .../PHP/Shim/charset/from.nextstep.ser | Bin 0 -> 4211 bytes .../PHP/Shim/charset/from.stdenc.ser | 1 + .../PHP/Shim/charset/from.symbol.ser | 1 + .../PHP/Shim/charset/from.turkish.ser | Bin 0 -> 4185 bytes .../PHP/Shim/charset/from.us-ascii-quotes.ser | Bin 0 -> 2020 bytes .../PHP/Shim/charset/from.us-ascii.ser | Bin 0 -> 2016 bytes .../PHP/Shim/charset/from.windows-1250.ser | Bin 0 -> 4124 bytes .../PHP/Shim/charset/from.windows-1251.ser | Bin 0 -> 4193 bytes .../PHP/Shim/charset/from.windows-1252.ser | Bin 0 -> 4124 bytes .../PHP/Shim/charset/from.windows-1253.ser | Bin 0 -> 3921 bytes .../PHP/Shim/charset/from.windows-1254.ser | Bin 0 -> 4090 bytes .../PHP/Shim/charset/from.windows-1255.ser | Bin 0 -> 3821 bytes .../PHP/Shim/charset/from.windows-1256.ser | Bin 0 -> 4213 bytes .../PHP/Shim/charset/from.windows-1257.ser | Bin 0 -> 4005 bytes .../PHP/Shim/charset/from.windows-1258.ser | Bin 0 -> 4057 bytes .../PHP/Shim/charset/from.x-mac-ce.ser | Bin 0 -> 4214 bytes .../PHP/Shim/charset/from.x-mac-cyrillic.ser | Bin 0 -> 4212 bytes .../PHP/Shim/charset/from.x-mac-greek.ser | Bin 0 -> 4190 bytes .../PHP/Shim/charset/from.x-mac-icelandic.ser | Bin 0 -> 4201 bytes .../PHP/Shim/charset/from.x-mac-roman.ser | Bin 0 -> 4207 bytes .../PHP/Shim/charset/from.zdingbat.ser | 1 + .../Patchwork/PHP/Shim/charset/to.gsm0338.ser | Bin 0 -> 2459 bytes .../Patchwork/PHP/Shim/charset/to.mazovia.ser | Bin 0 -> 4232 bytes .../Patchwork/PHP/Shim/charset/to.stdenc.ser | 1 + .../Patchwork/PHP/Shim/charset/to.symbol.ser | 1 + .../PHP/Shim/charset/to.zdingbat.ser | 1 + .../Patchwork/PHP/Shim/charset/translit.ser | 1 + .../PHP/Shim/unidata/canonicalComposition.ser | 1 + .../Shim/unidata/canonicalDecomposition.ser | 1 + .../PHP/Shim/unidata/combiningClass.ser | 1 + .../unidata/compatibilityDecomposition.ser | 1 + .../Patchwork/PHP/Shim/unidata/lowerCase.ser | 1 + .../Patchwork/PHP/Shim/unidata/upperCase.ser | 1 + .../utf8/src/Patchwork/TurkishUtf8.php | 155 + .../patchwork/utf8/src/Patchwork/Utf8.php | 775 + .../utf8/src/Patchwork/Utf8/BestFit.php | 79 + .../utf8/src/Patchwork/Utf8/Bootup.php | 230 + .../utf8/src/Patchwork/Utf8/Bootup/iconv.php | 51 + .../utf8/src/Patchwork/Utf8/Bootup/intl.php | 35 + .../src/Patchwork/Utf8/Bootup/mbstring.php | 54 + .../src/Patchwork/Utf8/Bootup/utf8_encode.php | 17 + .../Patchwork/Utf8/WindowsStreamWrapper.php | 405 + .../Patchwork/Utf8/data/caseFolding_full.ser | 1 + .../Patchwork/Utf8/data/to.bestfit1250.ser | Bin 0 -> 11885 bytes .../Patchwork/Utf8/data/to.bestfit1251.ser | Bin 0 -> 10912 bytes .../Patchwork/Utf8/data/to.bestfit1252.ser | Bin 0 -> 11968 bytes .../Patchwork/Utf8/data/to.bestfit1253.ser | Bin 0 -> 10587 bytes .../Patchwork/Utf8/data/to.bestfit1254.ser | Bin 0 -> 11902 bytes .../Patchwork/Utf8/data/to.bestfit1255.ser | Bin 0 -> 5948 bytes .../Patchwork/Utf8/data/to.bestfit1256.ser | Bin 0 -> 9202 bytes .../Patchwork/Utf8/data/to.bestfit1257.ser | Bin 0 -> 5903 bytes .../Patchwork/Utf8/data/to.bestfit1258.ser | Bin 0 -> 5902 bytes .../src/Patchwork/Utf8/data/to.bestfit874.ser | Bin 0 -> 6760 bytes .../src/Patchwork/Utf8/data/to.bestfit932.ser | Bin 0 -> 179471 bytes .../src/Patchwork/Utf8/data/to.bestfit936.ser | Bin 0 -> 464524 bytes .../src/Patchwork/Utf8/data/to.bestfit949.ser | Bin 0 -> 336734 bytes .../src/Patchwork/Utf8/data/to.bestfit950.ser | Bin 0 -> 385467 bytes .../Patchwork/Utf8/data/translit_extra.ser | 1 + .../src/ContainerExceptionInterface.php | 13 + .../psr/container/src/ContainerInterface.php | 37 + .../src/NotFoundExceptionInterface.php | 13 + .../psr/http-message/src/MessageInterface.php | 187 + .../psr/http-message/src/RequestInterface.php | 129 + .../http-message/src/ResponseInterface.php | 68 + .../src/ServerRequestInterface.php | 261 + .../psr/http-message/src/StreamInterface.php | 158 + .../src/UploadedFileInterface.php | 123 + .../psr/http-message/src/UriInterface.php | 323 + .../vendor/psr/log/Psr/Log/AbstractLogger.php | 128 + .../log/Psr/Log/InvalidArgumentException.php | 7 + .../vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + .../psr/log/Psr/Log/LoggerAwareTrait.php | 26 + .../psr/log/Psr/Log/LoggerInterface.php | 123 + .../vendor/psr/log/Psr/Log/LoggerTrait.php | 140 + .../vendor/psr/log/Psr/Log/NullLogger.php | 28 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 140 + .../psr/simple-cache/src/CacheException.php | 10 + .../psr/simple-cache/src/CacheInterface.php | 114 + .../src/InvalidArgumentException.php | 13 + .../vendor/psy/psysh/.phan/config.php | 46 + freescout-dist/vendor/psy/psysh/.styleci.yml | 29 + freescout-dist/vendor/psy/psysh/.travis.yml | 47 + .../vendor/psy/psysh/bin/build-stub | 22 + freescout-dist/vendor/psy/psysh/bin/psysh | 138 + .../vendor/psy/psysh/src/CodeCleaner.php | 349 + .../src/CodeCleaner/AbstractClassPass.php | 71 + .../CodeCleaner/AssignThisVariablePass.php | 39 + .../CallTimePassByReferencePass.php | 50 + .../psysh/src/CodeCleaner/CalledClassPass.php | 83 + .../psysh/src/CodeCleaner/CodeCleanerPass.php | 22 + .../psy/psysh/src/CodeCleaner/ExitPass.php | 32 + .../psysh/src/CodeCleaner/FinalClassPass.php | 70 + .../src/CodeCleaner/FunctionContextPass.php | 61 + .../FunctionReturnInWriteContextPass.php | 81 + .../src/CodeCleaner/ImplicitReturnPass.php | 128 + .../psysh/src/CodeCleaner/InstanceOfPass.php | 47 + .../src/CodeCleaner/LeavePsyshAlonePass.php | 36 + .../psysh/src/CodeCleaner/LegacyEmptyPass.php | 73 + .../psy/psysh/src/CodeCleaner/ListPass.php | 112 + .../psysh/src/CodeCleaner/LoopContextPass.php | 103 + .../src/CodeCleaner/MagicConstantsPass.php | 42 + .../src/CodeCleaner/NamespaceAwarePass.php | 71 + .../psysh/src/CodeCleaner/NamespacePass.php | 88 + .../psysh/src/CodeCleaner/NoReturnValue.php | 35 + .../CodeCleaner/PassableByReferencePass.php | 109 + .../psy/psysh/src/CodeCleaner/RequirePass.php | 101 + .../psysh/src/CodeCleaner/StrictTypesPass.php | 87 + .../src/CodeCleaner/UseStatementPass.php | 126 + .../src/CodeCleaner/ValidClassNamePass.php | 411 + .../src/CodeCleaner/ValidConstantPass.php | 90 + .../src/CodeCleaner/ValidConstructorPass.php | 112 + .../src/CodeCleaner/ValidFunctionNamePass.php | 97 + .../psy/psysh/src/Command/BufferCommand.php | 79 + .../psy/psysh/src/Command/ClearCommand.php | 51 + .../vendor/psy/psysh/src/Command/Command.php | 282 + .../psy/psysh/src/Command/DocCommand.php | 133 + .../psy/psysh/src/Command/DumpCommand.php | 96 + .../psy/psysh/src/Command/EditCommand.php | 189 + .../psy/psysh/src/Command/ExitCommand.php | 52 + .../psy/psysh/src/Command/HelpCommand.php | 100 + .../psy/psysh/src/Command/HistoryCommand.php | 248 + .../psy/psysh/src/Command/ListCommand.php | 278 + .../ListCommand/ClassConstantEnumerator.php | 127 + .../Command/ListCommand/ClassEnumerator.php | 126 + .../ListCommand/ConstantEnumerator.php | 122 + .../src/Command/ListCommand/Enumerator.php | 106 + .../ListCommand/FunctionEnumerator.php | 112 + .../ListCommand/GlobalVariableEnumerator.php | 92 + .../ListCommand/InterfaceEnumerator.php | 89 + .../Command/ListCommand/MethodEnumerator.php | 145 + .../ListCommand/PropertyEnumerator.php | 180 + .../Command/ListCommand/TraitEnumerator.php | 89 + .../ListCommand/VariableEnumerator.php | 137 + .../psy/psysh/src/Command/ParseCommand.php | 182 + .../psysh/src/Command/PsyVersionCommand.php | 43 + .../psysh/src/Command/ReflectingCommand.php | 303 + .../psy/psysh/src/Command/ShowCommand.php | 292 + .../psy/psysh/src/Command/SudoCommand.php | 145 + .../psy/psysh/src/Command/ThrowUpCommand.php | 174 + .../psy/psysh/src/Command/TimeitCommand.php | 197 + .../Command/TimeitCommand/TimeitVisitor.php | 139 + .../psy/psysh/src/Command/TraceCommand.php | 169 + .../psy/psysh/src/Command/WhereamiCommand.php | 149 + .../psy/psysh/src/Command/WtfCommand.php | 127 + .../vendor/psy/psysh/src/ConfigPaths.php | 237 + .../vendor/psy/psysh/src/Configuration.php | 1307 ++ .../psy/psysh/src/ConsoleColorFactory.php | 82 + .../vendor/psy/psysh/src/Context.php | 320 + .../vendor/psy/psysh/src/ContextAware.php | 28 + .../psysh/src/Exception/BreakException.php | 51 + .../src/Exception/DeprecatedException.php | 20 + .../psysh/src/Exception/ErrorException.php | 114 + .../psy/psysh/src/Exception/Exception.php | 27 + .../src/Exception/FatalErrorException.php | 52 + .../src/Exception/ParseErrorException.php | 42 + .../psysh/src/Exception/RuntimeException.php | 43 + .../psysh/src/Exception/ThrowUpException.php | 57 + .../src/Exception/TypeErrorException.php | 55 + .../vendor/psy/psysh/src/ExecutionClosure.php | 119 + .../vendor/psy/psysh/src/ExecutionLoop.php | 67 + .../src/ExecutionLoop/AbstractListener.php | 62 + .../psy/psysh/src/ExecutionLoop/Listener.php | 83 + .../psysh/src/ExecutionLoop/ProcessForker.php | 219 + .../src/ExecutionLoop/RunkitReloader.php | 135 + .../psy/psysh/src/ExecutionLoopClosure.php | 104 + .../psy/psysh/src/Formatter/CodeFormatter.php | 71 + .../psysh/src/Formatter/DocblockFormatter.php | 168 + .../psy/psysh/src/Formatter/Formatter.php | 25 + .../src/Formatter/SignatureFormatter.php | 308 + .../psy/psysh/src/Input/CodeArgument.php | 50 + .../psy/psysh/src/Input/FilterOptions.php | 145 + .../vendor/psy/psysh/src/Input/ShellInput.php | 336 + .../psy/psysh/src/Input/SilentInput.php | 44 + .../psy/psysh/src/Output/OutputPager.php | 26 + .../psy/psysh/src/Output/PassthruPager.php | 39 + .../psy/psysh/src/Output/ProcOutputPager.php | 103 + .../psy/psysh/src/Output/ShellOutput.php | 204 + .../vendor/psy/psysh/src/ParserFactory.php | 91 + .../psy/psysh/src/Readline/GNUReadline.php | 170 + .../psy/psysh/src/Readline/HoaConsole.php | 107 + .../vendor/psy/psysh/src/Readline/Libedit.php | 83 + .../psy/psysh/src/Readline/Readline.php | 76 + .../psy/psysh/src/Readline/Transient.php | 147 + .../Reflection/ReflectionClassConstant.php | 228 + .../src/Reflection/ReflectionConstant.php | 30 + .../src/Reflection/ReflectionConstant_.php | 182 + .../ReflectionLanguageConstruct.php | 164 + .../ReflectionLanguageConstructParameter.php | 103 + freescout-dist/vendor/psy/psysh/src/Shell.php | 1339 ++ freescout-dist/vendor/psy/psysh/src/Sudo.php | 150 + .../vendor/psy/psysh/src/Sudo/SudoVisitor.php | 124 + .../psysh/src/TabCompletion/AutoCompleter.php | 110 + .../Matcher/AbstractContextAwareMatcher.php | 65 + .../AbstractDefaultParametersMatcher.php | 76 + .../TabCompletion/Matcher/AbstractMatcher.php | 195 + .../Matcher/ClassAttributesMatcher.php | 87 + .../ClassMethodDefaultParametersMatcher.php | 64 + .../Matcher/ClassMethodsMatcher.php | 84 + .../Matcher/ClassNamesMatcher.php | 77 + .../TabCompletion/Matcher/CommandsMatcher.php | 114 + .../Matcher/ConstantsMatcher.php | 54 + .../FunctionDefaultParametersMatcher.php | 53 + .../Matcher/FunctionsMatcher.php | 56 + .../TabCompletion/Matcher/KeywordsMatcher.php | 85 + .../Matcher/MongoClientMatcher.php | 71 + .../Matcher/MongoDatabaseMatcher.php | 67 + .../Matcher/ObjectAttributesMatcher.php | 78 + .../ObjectMethodDefaultParametersMatcher.php | 71 + .../Matcher/ObjectMethodsMatcher.php | 80 + .../Matcher/VariablesMatcher.php | 51 + .../vendor/psy/psysh/src/Util/Docblock.php | 241 + .../vendor/psy/psysh/src/Util/Json.php | 33 + .../vendor/psy/psysh/src/Util/Mirror.php | 99 + .../vendor/psy/psysh/src/Util/Str.php | 114 + .../vendor/psy/psysh/src/VarDumper/Cloner.php | 42 + .../vendor/psy/psysh/src/VarDumper/Dumper.php | 109 + .../psy/psysh/src/VarDumper/Presenter.php | 137 + .../psysh/src/VarDumper/PresenterAware.php | 26 + .../psy/psysh/src/VersionUpdater/Checker.php | 31 + .../src/VersionUpdater/GitHubChecker.php | 89 + .../src/VersionUpdater/IntervalChecker.php | 67 + .../psysh/src/VersionUpdater/NoopChecker.php | 36 + .../vendor/psy/psysh/src/functions.php | 358 + .../src/Config/installer.php | 146 + .../src/Controllers/DatabaseController.php | 35 + .../src/Controllers/PermissionsController.php | 38 + .../Controllers/RequirementsController.php | 39 + .../src/Controllers/UpdateController.php | 62 + .../src/Controllers/WelcomeController.php | 22 + .../src/Events/LaravelInstallerFinished.php | 22 + .../src/Helpers/MigrationsHelper.php | 29 + .../src/Helpers/functions.php | 22 + .../src/Lang/ar/installer_messages.php | 68 + .../src/Lang/de/installer_messages.php | 69 + .../src/Lang/en/installer_messages.php | 246 + .../src/Lang/es/installer_messages.php | 69 + .../src/Lang/et/installer_messages.php | 68 + .../src/Lang/fa/installer_messages.php | 68 + .../src/Lang/fr/installer_messages.php | 241 + .../src/Lang/gr/installer_messages.php | 68 + .../src/Lang/id/installer_messages.php | 246 + .../src/Lang/it/installer_messages.php | 57 + .../src/Lang/nl/installer_messages.php | 68 + .../src/Lang/pl/installer_messages.php | 68 + .../src/Lang/pt-br/installer_messages.php | 69 + .../src/Lang/pt/installer_messages.php | 69 + .../src/Lang/ro/installer_messages.php | 68 + .../src/Lang/ru/installer_messages.php | 68 + .../src/Lang/tr/installer_messages.php | 245 + .../src/Lang/zh-CN/installer_messages.php | 69 + .../src/Lang/zh-TW/installer_messages.php | 69 + .../src/Middleware/canUpdate.php | 66 + .../laravel-installer/src/Routes/web.php | 81 + .../src/Views/environment-classic.blade.php | 38 + .../src/Views/environment-wizard.blade.php | 523 + .../src/Views/environment.blade.php | 26 + .../src/Views/finished.blade.php | 32 + .../src/Views/layouts/master-update.blade.php | 46 + .../src/Views/layouts/master.blade.php | 114 + .../src/Views/permissions.blade.php | 35 + .../src/Views/requirements.blade.php | 50 + .../src/Views/update/finished.blade.php | 9 + .../src/Views/update/overview.blade.php | 9 + .../src/Views/update/welcome.blade.php | 11 + .../src/Views/welcome.blade.php | 21 + .../src/assets/css/sass/_variables.sass | 46 + .../src/assets/css/sass/style.sass | 3213 ++++ .../src/assets/css/scss/_variables.scss | 58 + .../css/scss/font-awesome/_animated.scss | 34 + .../scss/font-awesome/_bordered-pulled.scss | 25 + .../assets/css/scss/font-awesome/_core.scss | 12 + .../css/scss/font-awesome/_fixed-width.scss | 6 + .../assets/css/scss/font-awesome/_icons.scss | 789 + .../assets/css/scss/font-awesome/_larger.scss | 13 + .../assets/css/scss/font-awesome/_list.scss | 19 + .../assets/css/scss/font-awesome/_mixins.scss | 60 + .../assets/css/scss/font-awesome/_path.scss | 15 + .../scss/font-awesome/_rotated-flipped.scss | 20 + .../css/scss/font-awesome/_screen-reader.scss | 5 + .../css/scss/font-awesome/_stacked.scss | 20 + .../css/scss/font-awesome/_variables.scss | 800 + .../css/scss/font-awesome/font-awesome.scss | 18 + .../src/assets/css/scss/style.scss | 1348 ++ .../src/assets/css/style.css | 3539 ++++ .../src/assets/css/style.css.map | 7 + .../src/assets/css/style.min.css | 6 + .../src/assets/css/style.min.css.map | 7 + .../src/assets/fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes .../assets/fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes .../src/assets/fonts/ionicons.woff | Bin 0 -> 67904 bytes .../getallheaders/src/getallheaders.php | 46 + .../vendor/ramsey/uuid/src/BinaryUtils.php | 41 + .../uuid/src/Builder/DefaultUuidBuilder.php | 54 + .../uuid/src/Builder/DegradedUuidBuilder.php | 53 + .../uuid/src/Builder/UuidBuilderInterface.php | 34 + .../ramsey/uuid/src/Codec/CodecInterface.php | 60 + .../ramsey/uuid/src/Codec/GuidStringCodec.php | 103 + .../uuid/src/Codec/OrderedTimeCodec.php | 68 + .../ramsey/uuid/src/Codec/StringCodec.php | 170 + .../src/Codec/TimestampFirstCombCodec.php | 108 + .../uuid/src/Codec/TimestampLastCombCodec.php | 22 + .../Converter/Number/BigNumberConverter.php | 54 + .../Number/DegradedNumberConverter.php | 58 + .../Converter/NumberConverterInterface.php | 48 + .../Converter/Time/BigNumberTimeConverter.php | 59 + .../Converter/Time/DegradedTimeConverter.php | 42 + .../src/Converter/Time/PhpTimeConverter.php | 47 + .../src/Converter/TimeConverterInterface.php | 37 + .../vendor/ramsey/uuid/src/DegradedUuid.php | 116 + .../Exception/InvalidUuidStringException.php | 24 + .../UnsatisfiedDependencyException.php | 25 + .../UnsupportedOperationException.php | 24 + .../vendor/ramsey/uuid/src/FeatureSet.php | 335 + .../uuid/src/Generator/CombGenerator.php | 91 + .../src/Generator/DefaultTimeGenerator.php | 141 + .../uuid/src/Generator/MtRandGenerator.php | 45 + .../uuid/src/Generator/OpenSslGenerator.php | 43 + .../src/Generator/PeclUuidRandomGenerator.php | 37 + .../src/Generator/PeclUuidTimeGenerator.php | 38 + .../src/Generator/RandomBytesGenerator.php | 39 + .../src/Generator/RandomGeneratorFactory.php | 31 + .../Generator/RandomGeneratorInterface.php | 37 + .../uuid/src/Generator/RandomLibAdapter.php | 62 + .../src/Generator/SodiumRandomGenerator.php | 41 + .../src/Generator/TimeGeneratorFactory.php | 72 + .../src/Generator/TimeGeneratorInterface.php | 43 + .../Provider/Node/FallbackNodeProvider.php | 59 + .../src/Provider/Node/RandomNodeProvider.php | 57 + .../src/Provider/Node/SystemNodeProvider.php | 128 + .../src/Provider/NodeProviderInterface.php | 32 + .../src/Provider/Time/FixedTimeProvider.php | 77 + .../src/Provider/Time/SystemTimeProvider.php | 33 + .../src/Provider/TimeProviderInterface.php | 29 + .../vendor/ramsey/uuid/src/UuidFactory.php | 315 + .../ramsey/uuid/src/UuidFactoryInterface.php | 108 + .../vendor/ramsey/uuid/src/UuidInterface.php | 274 + .../vendor/ramsey/uuid/src/functions.php | 78 + .../laravel-log-viewer/.travis.yml | 18 + .../LaravelLogViewerServiceProvider.php | 71 + .../Rap2hpoutre/LaravelLogViewer/Level.php | 68 + .../Rap2hpoutre/LaravelLogViewer/Pattern.php | 47 + .../src/config/logviewer.php | 15 + .../src/controllers/LogViewerController.php | 155 + .../src/views/log.blade.php | 334 + .../spatie/laravel-activitylog/.styleci.yml | 1 + .../config/activitylog.php | 44 + .../create_activity_log_table.php.stub | 35 + .../src/ActivityLogger.php | 205 + .../src/ActivitylogServiceProvider.php | 61 + .../src/CleanActivitylogCommand.php | 48 + .../src/Exceptions/CouldNotLogActivity.php | 13 + .../src/Exceptions/CouldNotLogChanges.php | 13 + .../src/Exceptions/InvalidConfiguration.php | 14 + .../src/Models/Activity.php | 102 + .../src/Traits/CausesActivity.php | 20 + .../src/Traits/DetectsChanges.php | 120 + .../src/Traits/HasActivity.php | 16 + .../src/Traits/LogsActivity.php | 126 + .../laravel-activitylog/src/helpers.php | 12 + .../ErrorCreatingStringException.php | 9 + .../Exceptions/UnknownFunctionException.php | 9 + .../src/Exceptions/UnsetOffsetException.php | 9 + .../string/src/Integrations/Underscore.php | 102 + .../spatie/string/src/string_functions.php | 13 + .../swiftmailer/swiftmailer/.travis.yml | 25 + .../vendor/swiftmailer/swiftmailer/CHANGES | 332 + .../vendor/swiftmailer/swiftmailer/README | 15 + .../swiftmailer/swiftmailer/doc/headers.rst | 621 + .../swiftmailer/swiftmailer/doc/index.rst | 12 + .../swiftmailer/doc/introduction.rst | 61 + .../swiftmailer/swiftmailer/doc/japanese.rst | 19 + .../swiftmailer/swiftmailer/doc/messages.rst | 950 + .../swiftmailer/swiftmailer/doc/plugins.rst | 337 + .../swiftmailer/swiftmailer/doc/sending.rst | 453 + .../swiftmailer/lib/classes/Swift.php | 78 + .../lib/classes/Swift/AddressEncoder.php | 25 + .../AddressEncoder/IdnAddressEncoder.php | 69 + .../AddressEncoder/Utf8AddressEncoder.php | 36 + .../classes/Swift/AddressEncoderException.php | 32 + .../lib/classes/Swift/Attachment.php | 54 + .../AbstractFilterableInputStream.php | 176 + .../Swift/ByteStream/ArrayByteStream.php | 178 + .../Swift/ByteStream/FileByteStream.php | 216 + .../ByteStream/TemporaryFileByteStream.php | 42 + .../lib/classes/Swift/CharacterReader.php | 67 + .../GenericFixedWidthReader.php | 97 + .../Swift/CharacterReader/UsAsciiReader.php | 84 + .../Swift/CharacterReader/Utf8Reader.php | 176 + .../classes/Swift/CharacterReaderFactory.php | 26 + .../SimpleCharacterReaderFactory.php | 124 + .../lib/classes/Swift/CharacterStream.php | 89 + .../CharacterStream/ArrayCharacterStream.php | 291 + .../CharacterStream/NgCharacterStream.php | 262 + .../lib/classes/Swift/ConfigurableSpool.php | 63 + .../lib/classes/Swift/DependencyContainer.php | 391 + .../lib/classes/Swift/DependencyException.php | 27 + .../swiftmailer/lib/classes/Swift/Encoder.php | 28 + .../classes/Swift/Encoder/Base64Encoder.php | 58 + .../lib/classes/Swift/Encoder/QpEncoder.php | 300 + .../classes/Swift/Encoder/Rfc2231Encoder.php | 90 + .../lib/classes/Swift/Events/CommandEvent.php | 64 + .../classes/Swift/Events/CommandListener.php | 24 + .../lib/classes/Swift/Events/Event.php | 38 + .../classes/Swift/Events/EventDispatcher.php | 83 + .../classes/Swift/Events/EventListener.php | 18 + .../lib/classes/Swift/Events/EventObject.php | 61 + .../classes/Swift/Events/ResponseEvent.php | 64 + .../classes/Swift/Events/ResponseListener.php | 24 + .../lib/classes/Swift/Events/SendEvent.php | 126 + .../lib/classes/Swift/Events/SendListener.php | 31 + .../Swift/Events/SimpleEventDispatcher.php | 143 + .../Swift/Events/TransportChangeEvent.php | 27 + .../Swift/Events/TransportChangeListener.php | 45 + .../Swift/Events/TransportExceptionEvent.php | 43 + .../Events/TransportExceptionListener.php | 24 + .../lib/classes/Swift/FailoverTransport.php | 33 + .../lib/classes/Swift/FileSpool.php | 208 + .../lib/classes/Swift/FileStream.php | 24 + .../lib/classes/Swift/Filterable.php | 32 + .../lib/classes/Swift/IdGenerator.php | 22 + .../swiftmailer/lib/classes/Swift/Image.php | 43 + .../lib/classes/Swift/InputByteStream.php | 75 + .../lib/classes/Swift/IoException.php | 28 + .../lib/classes/Swift/KeyCache.php | 105 + .../classes/Swift/KeyCache/ArrayKeyCache.php | 203 + .../classes/Swift/KeyCache/DiskKeyCache.php | 295 + .../Swift/KeyCache/KeyCacheInputStream.php | 51 + .../classes/Swift/KeyCache/NullKeyCache.php | 113 + .../KeyCache/SimpleKeyCacheInputStream.php | 123 + .../classes/Swift/LoadBalancedTransport.php | 33 + .../swiftmailer/lib/classes/Swift/Mailer.php | 98 + .../Swift/Mailer/ArrayRecipientIterator.php | 53 + .../Swift/Mailer/RecipientIterator.php | 32 + .../lib/classes/Swift/MemorySpool.php | 110 + .../swiftmailer/lib/classes/Swift/Message.php | 279 + .../lib/classes/Swift/Mime/Attachment.php | 144 + .../classes/Swift/Mime/CharsetObserver.php | 24 + .../lib/classes/Swift/Mime/ContentEncoder.php | 34 + .../ContentEncoder/Base64ContentEncoder.php | 101 + .../ContentEncoder/NativeQpContentEncoder.php | 123 + .../ContentEncoder/NullContentEncoder.php | 79 + .../ContentEncoder/PlainContentEncoder.php | 164 + .../Mime/ContentEncoder/QpContentEncoder.php | 134 + .../ContentEncoder/QpContentEncoderProxy.php | 96 + .../Mime/ContentEncoder/RawContentEncoder.php | 65 + .../lib/classes/Swift/Mime/EmbeddedFile.php | 41 + .../classes/Swift/Mime/EncodingObserver.php | 24 + .../lib/classes/Swift/Mime/Header.php | 93 + .../lib/classes/Swift/Mime/HeaderEncoder.php | 24 + .../HeaderEncoder/Base64HeaderEncoder.php | 55 + .../Mime/HeaderEncoder/QpHeaderEncoder.php | 65 + .../Swift/Mime/Headers/AbstractHeader.php | 476 + .../classes/Swift/Mime/Headers/DateHeader.php | 113 + .../Mime/Headers/IdentificationHeader.php | 186 + .../Swift/Mime/Headers/MailboxHeader.php | 360 + .../Swift/Mime/Headers/OpenDKIMHeader.php | 135 + .../Mime/Headers/ParameterizedHeader.php | 255 + .../classes/Swift/Mime/Headers/PathHeader.php | 155 + .../Swift/Mime/Headers/UnstructuredHeader.php | 109 + .../lib/classes/Swift/Mime/IdGenerator.php | 54 + .../lib/classes/Swift/Mime/MimePart.php | 208 + .../Swift/Mime/SimpleHeaderFactory.php | 195 + .../classes/Swift/Mime/SimpleHeaderSet.php | 399 + .../lib/classes/Swift/Mime/SimpleMessage.php | 642 + .../classes/Swift/Mime/SimpleMimeEntity.php | 820 + .../lib/classes/Swift/MimePart.php | 45 + .../lib/classes/Swift/NullTransport.php | 26 + .../lib/classes/Swift/OutputByteStream.php | 46 + .../classes/Swift/Plugins/AntiFloodPlugin.php | 137 + .../Swift/Plugins/BandwidthMonitorPlugin.php | 154 + .../Swift/Plugins/Decorator/Replacements.php | 31 + .../classes/Swift/Plugins/DecoratorPlugin.php | 200 + .../Swift/Plugins/ImpersonatePlugin.php | 65 + .../lib/classes/Swift/Plugins/Logger.php | 36 + .../classes/Swift/Plugins/LoggerPlugin.php | 126 + .../Swift/Plugins/Loggers/ArrayLogger.php | 72 + .../Swift/Plugins/Loggers/EchoLogger.php | 58 + .../classes/Swift/Plugins/MessageLogger.php | 70 + .../Swift/Plugins/Pop/Pop3Connection.php | 31 + .../Swift/Plugins/Pop/Pop3Exception.php | 27 + .../Swift/Plugins/PopBeforeSmtpPlugin.php | 254 + .../Swift/Plugins/RedirectingPlugin.php | 201 + .../lib/classes/Swift/Plugins/Reporter.php | 32 + .../classes/Swift/Plugins/ReporterPlugin.php | 57 + .../Swift/Plugins/Reporters/HitReporter.php | 58 + .../Swift/Plugins/Reporters/HtmlReporter.php | 38 + .../lib/classes/Swift/Plugins/Sleeper.php | 24 + .../classes/Swift/Plugins/ThrottlerPlugin.php | 196 + .../lib/classes/Swift/Plugins/Timer.php | 24 + .../lib/classes/Swift/Preferences.php | 100 + .../Swift/ReplacementFilterFactory.php | 27 + .../classes/Swift/RfcComplianceException.php | 27 + .../lib/classes/Swift/SendmailTransport.php | 33 + .../swiftmailer/lib/classes/Swift/Signer.php | 19 + .../lib/classes/Swift/Signers/BodySigner.php | 33 + .../lib/classes/Swift/Signers/DKIMSigner.php | 682 + .../classes/Swift/Signers/DomainKeySigner.php | 504 + .../classes/Swift/Signers/HeaderSigner.php | 65 + .../classes/Swift/Signers/OpenDKIMSigner.php | 183 + .../lib/classes/Swift/Signers/SMimeSigner.php | 542 + .../lib/classes/Swift/SmtpTransport.php | 42 + .../swiftmailer/lib/classes/Swift/Spool.php | 53 + .../lib/classes/Swift/SpoolTransport.php | 33 + .../lib/classes/Swift/StreamFilter.php | 35 + .../ByteArrayReplacementFilter.php | 166 + .../StreamFilters/StringReplacementFilter.php | 70 + .../StringReplacementFilterFactory.php | 45 + .../lib/classes/Swift/SwiftException.php | 28 + .../lib/classes/Swift/Transport.php | 79 + .../Swift/Transport/AbstractSmtpTransport.php | 543 + .../Esmtp/Auth/CramMd5Authenticator.php | 75 + .../Esmtp/Auth/LoginAuthenticator.php | 45 + .../Esmtp/Auth/NTLMAuthenticator.php | 681 + .../Esmtp/Auth/PlainAuthenticator.php | 44 + .../Esmtp/Auth/XOAuth2Authenticator.php | 64 + .../Swift/Transport/Esmtp/AuthHandler.php | 268 + .../Swift/Transport/Esmtp/Authenticator.php | 37 + .../Transport/Esmtp/EightBitMimeHandler.php | 113 + .../Swift/Transport/Esmtp/SmtpUtf8Handler.php | 107 + .../classes/Swift/Transport/EsmtpHandler.php | 86 + .../Swift/Transport/EsmtpTransport.php | 446 + .../Swift/Transport/FailoverTransport.php | 105 + .../lib/classes/Swift/Transport/IoBuffer.php | 67 + .../Swift/Transport/LoadBalancedTransport.php | 194 + .../classes/Swift/Transport/NullTransport.php | 98 + .../Swift/Transport/SendmailTransport.php | 158 + .../lib/classes/Swift/Transport/SmtpAgent.php | 36 + .../Swift/Transport/SpoolTransport.php | 120 + .../lib/classes/Swift/TransportException.php | 28 + .../lib/dependency_maps/cache_deps.php | 23 + .../lib/dependency_maps/message_deps.php | 9 + .../lib/dependency_maps/mime_deps.php | 134 + .../lib/dependency_maps/transport_deps.php | 97 + .../swiftmailer/lib/mime_types.php | 1007 + .../swiftmailer/lib/preferences.php | 19 + .../swiftmailer/lib/swift_required.php | 22 + .../lib/swiftmailer_generate_mimes_config.php | 182 + .../vendor/symfony/console/Application.php | 1276 ++ .../symfony/console/Command/Command.php | 666 + .../symfony/console/Command/HelpCommand.php | 81 + .../symfony/console/Command/ListCommand.php | 90 + .../symfony/console/Command/LockableTrait.php | 72 + .../CommandLoader/CommandLoaderInterface.php | 46 + .../CommandLoader/ContainerCommandLoader.php | 64 + .../CommandLoader/FactoryCommandLoader.php | 62 + .../vendor/symfony/console/ConsoleEvents.php | 60 + .../AddConsoleCommandPass.php | 106 + .../Descriptor/ApplicationDescription.php | 157 + .../symfony/console/Descriptor/Descriptor.php | 107 + .../Descriptor/DescriptorInterface.php | 29 + .../console/Descriptor/JsonDescriptor.php | 168 + .../console/Descriptor/MarkdownDescriptor.php | 182 + .../console/Descriptor/XmlDescriptor.php | 248 + .../console/Event/ConsoleCommandEvent.php | 60 + .../console/Event/ConsoleErrorEvent.php | 83 + .../symfony/console/Event/ConsoleEvent.php | 67 + .../console/Event/ConsoleExceptionEvent.php | 71 + .../console/Event/ConsoleTerminateEvent.php | 58 + .../console/EventListener/ErrorListener.php | 95 + .../Exception/CommandNotFoundException.php | 43 + .../console/Exception/ExceptionInterface.php | 21 + .../Exception/InvalidArgumentException.php | 19 + .../Exception/InvalidOptionException.php | 21 + .../console/Exception/LogicException.php | 19 + .../console/Exception/RuntimeException.php | 19 + .../console/Formatter/OutputFormatter.php | 243 + .../Formatter/OutputFormatterInterface.php | 71 + .../Formatter/OutputFormatterStyle.php | 183 + .../OutputFormatterStyleInterface.php | 62 + .../Formatter/OutputFormatterStyleStack.php | 109 + .../console/Helper/DebugFormatterHelper.php | 127 + .../console/Helper/DescriptorHelper.php | 91 + .../console/Helper/FormatterHelper.php | 102 + .../console/Helper/HelperInterface.php | 39 + .../console/Helper/InputAwareHelper.php | 33 + .../symfony/console/Helper/ProcessHelper.php | 145 + .../symfony/console/Helper/ProgressBar.php | 614 + .../console/Helper/ProgressIndicator.php | 270 + .../symfony/console/Helper/QuestionHelper.php | 518 + .../console/Helper/SymfonyQuestionHelper.php | 124 + .../vendor/symfony/console/Helper/Table.php | 698 + .../symfony/console/Helper/TableCell.php | 75 + .../symfony/console/Helper/TableSeparator.php | 25 + .../symfony/console/Helper/TableStyle.php | 258 + .../symfony/console/Input/ArgvInput.php | 370 + .../symfony/console/Input/ArrayInput.php | 208 + .../vendor/symfony/console/Input/Input.php | 203 + .../symfony/console/Input/InputArgument.php | 129 + .../console/Input/InputAwareInterface.php | 26 + .../symfony/console/Input/InputDefinition.php | 404 + .../symfony/console/Input/InputInterface.php | 163 + .../symfony/console/Input/InputOption.php | 208 + .../Input/StreamableInputInterface.php | 37 + .../symfony/console/Input/StringInput.php | 72 + .../symfony/console/Logger/ConsoleLogger.php | 128 + .../symfony/console/Output/BufferedOutput.php | 45 + .../symfony/console/Output/ConsoleOutput.php | 152 + .../console/Output/ConsoleOutputInterface.php | 30 + .../symfony/console/Output/NullOutput.php | 123 + .../vendor/symfony/console/Output/Output.php | 175 + .../console/Output/OutputInterface.php | 114 + .../symfony/console/Output/StreamOutput.php | 120 + .../console/Question/ChoiceQuestion.php | 187 + .../console/Question/ConfirmationQuestion.php | 59 + .../symfony/console/Question/Question.php | 245 + .../console/Resources/bin/hiddeninput.exe | Bin 0 -> 9216 bytes .../symfony/console/Style/OutputStyle.php | 155 + .../symfony/console/Style/StyleInterface.php | 153 + .../symfony/console/Style/SymfonyStyle.php | 431 + .../vendor/symfony/console/Terminal.php | 181 + .../console/Tester/ApplicationTester.php | 176 + .../symfony/console/Tester/CommandTester.php | 162 + .../symfony/console/Tests/ApplicationTest.php | 1786 ++ .../console/Tests/Command/CommandTest.php | 430 + .../console/Tests/Command/HelpCommandTest.php | 71 + .../console/Tests/Command/ListCommandTest.php | 113 + .../Tests/Command/LockableTraitTest.php | 67 + .../ContainerCommandLoaderTest.php | 59 + .../FactoryCommandLoaderTest.php | 58 + .../AddConsoleCommandPassTest.php | 256 + .../Descriptor/AbstractDescriptorTest.php | 107 + .../Descriptor/ApplicationDescriptionTest.php | 53 + .../Tests/Descriptor/JsonDescriptorTest.php | 35 + .../Descriptor/MarkdownDescriptorTest.php | 45 + .../Tests/Descriptor/ObjectsProvider.php | 82 + .../Tests/Descriptor/TextDescriptorTest.php | 53 + .../Tests/Descriptor/XmlDescriptorTest.php | 27 + .../Tests/EventListener/ErrorListenerTest.php | 156 + .../console/Tests/Fixtures/BarBucCommand.php | 11 + .../Tests/Fixtures/DescriptorApplication1.php | 18 + .../Tests/Fixtures/DescriptorApplication2.php | 26 + .../DescriptorApplicationMbString.php | 24 + .../Tests/Fixtures/DescriptorCommand1.php | 27 + .../Tests/Fixtures/DescriptorCommand2.php | 32 + .../Tests/Fixtures/DescriptorCommand3.php | 27 + .../Tests/Fixtures/DescriptorCommand4.php | 25 + .../Fixtures/DescriptorCommandMbString.php | 32 + .../console/Tests/Fixtures/DummyOutput.php | 36 + .../console/Tests/Fixtures/Foo1Command.php | 26 + .../console/Tests/Fixtures/Foo2Command.php | 21 + .../console/Tests/Fixtures/Foo3Command.php | 29 + .../console/Tests/Fixtures/Foo4Command.php | 11 + .../console/Tests/Fixtures/Foo5Command.php | 10 + .../console/Tests/Fixtures/Foo6Command.php | 11 + .../console/Tests/Fixtures/FooCommand.php | 33 + .../Tests/Fixtures/FooHiddenCommand.php | 21 + .../Tests/Fixtures/FooLock2Command.php | 28 + .../console/Tests/Fixtures/FooLockCommand.php | 27 + .../console/Tests/Fixtures/FooOptCommand.php | 36 + .../Fixtures/FooSameCaseLowercaseCommand.php | 11 + .../Fixtures/FooSameCaseUppercaseCommand.php | 11 + .../Fixtures/FooSubnamespaced1Command.php | 26 + .../Fixtures/FooSubnamespaced2Command.php | 26 + .../console/Tests/Fixtures/FoobarCommand.php | 25 + .../Style/SymfonyStyle/command/command_0.php | 11 + .../Style/SymfonyStyle/command/command_1.php | 13 + .../Style/SymfonyStyle/command/command_10.php | 17 + .../Style/SymfonyStyle/command/command_11.php | 12 + .../Style/SymfonyStyle/command/command_12.php | 13 + .../Style/SymfonyStyle/command/command_13.php | 14 + .../Style/SymfonyStyle/command/command_14.php | 17 + .../Style/SymfonyStyle/command/command_15.php | 14 + .../Style/SymfonyStyle/command/command_16.php | 15 + .../Style/SymfonyStyle/command/command_17.php | 13 + .../Style/SymfonyStyle/command/command_2.php | 16 + .../Style/SymfonyStyle/command/command_3.php | 12 + .../Style/SymfonyStyle/command/command_4.php | 34 + .../Style/SymfonyStyle/command/command_5.php | 37 + .../Style/SymfonyStyle/command/command_6.php | 16 + .../Style/SymfonyStyle/command/command_7.php | 15 + .../Style/SymfonyStyle/command/command_8.php | 26 + .../Style/SymfonyStyle/command/command_9.php | 11 + .../command/interactive_command_1.php | 19 + .../TestAmbiguousCommandRegistering.php | 22 + .../TestAmbiguousCommandRegistering2.php | 21 + .../console/Tests/Fixtures/TestCommand.php | 28 + .../console/Tests/Fixtures/application_1.json | 156 + .../console/Tests/Fixtures/application_1.xml | 104 + .../console/Tests/Fixtures/application_2.json | 509 + .../console/Tests/Fixtures/application_2.xml | 254 + .../console/Tests/Fixtures/command_1.json | 15 + .../console/Tests/Fixtures/command_1.xml | 12 + .../console/Tests/Fixtures/command_2.json | 33 + .../console/Tests/Fixtures/command_2.xml | 21 + .../Tests/Fixtures/input_argument_1.json | 7 + .../Tests/Fixtures/input_argument_1.xml | 5 + .../Tests/Fixtures/input_argument_2.json | 7 + .../Tests/Fixtures/input_argument_2.xml | 5 + .../Tests/Fixtures/input_argument_3.json | 7 + .../Tests/Fixtures/input_argument_3.xml | 7 + .../Tests/Fixtures/input_argument_4.json | 7 + .../Tests/Fixtures/input_argument_4.xml | 6 + ...input_argument_with_default_inf_value.json | 7 + .../input_argument_with_default_inf_value.xml | 7 + .../Fixtures/input_argument_with_style.json | 7 + .../Fixtures/input_argument_with_style.xml | 7 + .../Tests/Fixtures/input_definition_1.json | 4 + .../Tests/Fixtures/input_definition_1.xml | 5 + .../Tests/Fixtures/input_definition_2.json | 12 + .../Tests/Fixtures/input_definition_2.xml | 10 + .../Tests/Fixtures/input_definition_3.json | 14 + .../Tests/Fixtures/input_definition_3.xml | 9 + .../Tests/Fixtures/input_definition_4.json | 22 + .../Tests/Fixtures/input_definition_4.xml | 14 + .../Tests/Fixtures/input_option_1.json | 9 + .../console/Tests/Fixtures/input_option_1.xml | 4 + .../Tests/Fixtures/input_option_2.json | 9 + .../console/Tests/Fixtures/input_option_2.xml | 7 + .../Tests/Fixtures/input_option_3.json | 9 + .../console/Tests/Fixtures/input_option_3.xml | 5 + .../Tests/Fixtures/input_option_4.json | 9 + .../console/Tests/Fixtures/input_option_4.xml | 5 + .../Tests/Fixtures/input_option_5.json | 9 + .../console/Tests/Fixtures/input_option_5.xml | 6 + .../Tests/Fixtures/input_option_6.json | 9 + .../console/Tests/Fixtures/input_option_6.xml | 5 + .../input_option_with_default_inf_value.json | 9 + .../input_option_with_default_inf_value.xml | 7 + .../Fixtures/input_option_with_style.json | 9 + .../Fixtures/input_option_with_style.xml | 7 + .../input_option_with_style_array.json | 12 + .../input_option_with_style_array.xml | 8 + .../OutputFormatterStyleStackTest.php | 69 + .../Formatter/OutputFormatterStyleTest.php | 100 + .../Tests/Formatter/OutputFormatterTest.php | 344 + .../Helper/AbstractQuestionHelperTest.php | 34 + .../Tests/Helper/FormatterHelperTest.php | 129 + .../console/Tests/Helper/HelperSetTest.php | 127 + .../console/Tests/Helper/HelperTest.php | 55 + .../Tests/Helper/ProcessHelperTest.php | 118 + .../console/Tests/Helper/ProgressBarTest.php | 805 + .../Tests/Helper/ProgressIndicatorTest.php | 175 + .../Tests/Helper/QuestionHelperTest.php | 1115 ++ .../Helper/SymfonyQuestionHelperTest.php | 214 + .../console/Tests/Helper/TableStyleTest.php | 26 + .../console/Tests/Helper/TableTest.php | 868 + .../console/Tests/Input/ArgvInputTest.php | 462 + .../console/Tests/Input/ArrayInputTest.php | 173 + .../console/Tests/Input/InputArgumentTest.php | 109 + .../Tests/Input/InputDefinitionTest.php | 389 + .../console/Tests/Input/InputOptionTest.php | 194 + .../symfony/console/Tests/Input/InputTest.php | 137 + .../console/Tests/Input/StringInputTest.php | 87 + .../Tests/Logger/ConsoleLoggerTest.php | 213 + .../Tests/Output/ConsoleOutputTest.php | 42 + .../console/Tests/Output/NullOutputTest.php | 88 + .../console/Tests/Output/OutputTest.php | 176 + .../console/Tests/Output/StreamOutputTest.php | 67 + .../Tests/Question/ChoiceQuestionTest.php | 64 + .../Question/ConfirmationQuestionTest.php | 62 + .../console/Tests/Style/SymfonyStyleTest.php | 118 + .../symfony/console/Tests/TerminalTest.php | 97 + .../Tests/Tester/ApplicationTesterTest.php | 70 + .../Tests/Tester/CommandTesterTest.php | 212 + .../css-selector/CssSelectorConverter.php | 65 + .../Exception/ExceptionInterface.php | 24 + .../Exception/ExpressionErrorException.php | 24 + .../Exception/InternalErrorException.php | 24 + .../css-selector/Exception/ParseException.php | 24 + .../Exception/SyntaxErrorException.php | 73 + .../css-selector/Node/AbstractNode.php | 42 + .../css-selector/Node/AttributeNode.php | 107 + .../symfony/css-selector/Node/ClassNode.php | 70 + .../Node/CombinedSelectorNode.php | 83 + .../symfony/css-selector/Node/ElementNode.php | 72 + .../css-selector/Node/FunctionNode.php | 87 + .../symfony/css-selector/Node/HashNode.php | 70 + .../css-selector/Node/NegationNode.php | 66 + .../css-selector/Node/NodeInterface.php | 46 + .../symfony/css-selector/Node/PseudoNode.php | 70 + .../css-selector/Node/SelectorNode.php | 70 + .../symfony/css-selector/Node/Specificity.php | 88 + .../Parser/Handler/CommentHandler.php | 48 + .../Parser/Handler/HandlerInterface.php | 33 + .../Parser/Handler/HashHandler.php | 58 + .../Parser/Handler/IdentifierHandler.php | 58 + .../Parser/Handler/NumberHandler.php | 54 + .../Parser/Handler/StringHandler.php | 77 + .../Parser/Handler/WhitespaceHandler.php | 46 + .../symfony/css-selector/Parser/Parser.php | 384 + .../css-selector/Parser/ParserInterface.php | 36 + .../symfony/css-selector/Parser/Reader.php | 114 + .../Parser/Shortcut/ClassParser.php | 51 + .../Parser/Shortcut/ElementParser.php | 47 + .../Parser/Shortcut/EmptyStringParser.php | 46 + .../Parser/Shortcut/HashParser.php | 51 + .../symfony/css-selector/Parser/Token.php | 149 + .../css-selector/Parser/TokenStream.php | 175 + .../Parser/Tokenizer/Tokenizer.php | 75 + .../Parser/Tokenizer/TokenizerEscaping.php | 78 + .../Parser/Tokenizer/TokenizerPatterns.php | 112 + .../Tests/CssSelectorConverterTest.php | 76 + .../Tests/Node/AbstractNodeTest.php | 34 + .../Tests/Node/AttributeNodeTest.php | 37 + .../css-selector/Tests/Node/ClassNodeTest.php | 33 + .../Tests/Node/CombinedSelectorNodeTest.php | 35 + .../Tests/Node/ElementNodeTest.php | 35 + .../Tests/Node/FunctionNodeTest.php | 47 + .../css-selector/Tests/Node/HashNodeTest.php | 33 + .../Tests/Node/NegationNodeTest.php | 33 + .../Tests/Node/PseudoNodeTest.php | 32 + .../Tests/Node/SelectorNodeTest.php | 34 + .../Tests/Node/SpecificityTest.php | 63 + .../Parser/Handler/AbstractHandlerTest.php | 70 + .../Parser/Handler/CommentHandlerTest.php | 55 + .../Tests/Parser/Handler/HashHandlerTest.php | 49 + .../Parser/Handler/IdentifierHandlerTest.php | 49 + .../Parser/Handler/NumberHandlerTest.php | 50 + .../Parser/Handler/StringHandlerTest.php | 50 + .../Parser/Handler/WhitespaceHandlerTest.php | 44 + .../css-selector/Tests/Parser/ParserTest.php | 250 + .../css-selector/Tests/Parser/ReaderTest.php | 102 + .../Tests/Parser/Shortcut/ClassParserTest.php | 45 + .../Parser/Shortcut/ElementParserTest.php | 44 + .../Parser/Shortcut/EmptyStringParserTest.php | 36 + .../Tests/Parser/Shortcut/HashParserTest.php | 45 + .../Tests/Parser/TokenStreamTest.php | 96 + .../Tests/XPath/Fixtures/ids.html | 48 + .../Tests/XPath/Fixtures/lang.xml | 11 + .../Tests/XPath/Fixtures/shakespear.html | 308 + .../Tests/XPath/TranslatorTest.php | 327 + .../XPath/Extension/AbstractExtension.php | 65 + .../Extension/AttributeMatchingExtension.php | 175 + .../XPath/Extension/CombinationExtension.php | 83 + .../XPath/Extension/ExtensionInterface.php | 69 + .../XPath/Extension/FunctionExtension.php | 190 + .../XPath/Extension/HtmlExtension.php | 213 + .../XPath/Extension/PseudoClassExtension.php | 148 + .../symfony/css-selector/XPath/Translator.php | 267 + .../XPath/TranslatorInterface.php | 47 + .../symfony/css-selector/XPath/XPathExpr.php | 129 + .../vendor/symfony/debug/BufferingLogger.php | 37 + freescout-dist/vendor/symfony/debug/Debug.php | 60 + .../vendor/symfony/debug/DebugClassLoader.php | 434 + .../vendor/symfony/debug/ErrorHandler.php | 772 + .../Exception/ClassNotFoundException.php | 36 + .../debug/Exception/ContextErrorException.php | 40 + .../debug/Exception/FatalErrorException.php | 82 + .../debug/Exception/FatalThrowableError.php | 45 + .../debug/Exception/FlattenException.php | 263 + .../debug/Exception/OutOfMemoryException.php | 21 + .../debug/Exception/SilencedErrorContext.php | 67 + .../Exception/UndefinedFunctionException.php | 36 + .../Exception/UndefinedMethodException.php | 36 + .../ClassNotFoundFatalErrorHandler.php | 206 + .../FatalErrorHandlerInterface.php | 32 + .../UndefinedFunctionFatalErrorHandler.php | 84 + .../UndefinedMethodFatalErrorHandler.php | 66 + .../symfony/debug/Resources/ext/config.m4 | 63 + .../symfony/debug/Resources/ext/config.w32 | 13 + .../debug/Resources/ext/php_symfony_debug.h | 60 + .../debug/Resources/ext/symfony_debug.c | 283 + .../debug/Tests/DebugClassLoaderTest.php | 438 + .../symfony/debug/Tests/ErrorHandlerTest.php | 586 + .../Tests/Exception/FlattenExceptionTest.php | 302 + .../debug/Tests/ExceptionHandlerTest.php | 133 + .../ClassNotFoundFatalErrorHandlerTest.php | 176 + ...UndefinedFunctionFatalErrorHandlerTest.php | 81 + .../UndefinedMethodFatalErrorHandlerTest.php | 76 + .../debug/Tests/Fixtures/AnnotatedClass.php | 13 + .../debug/Tests/Fixtures/ClassAlias.php | 3 + .../debug/Tests/Fixtures/DeprecatedClass.php | 12 + .../Tests/Fixtures/DeprecatedInterface.php | 12 + .../Tests/Fixtures/ExtendedFinalMethod.php | 19 + .../debug/Tests/Fixtures/FinalClass.php | 10 + .../debug/Tests/Fixtures/FinalMethod.php | 24 + .../Tests/Fixtures/FinalMethod2Trait.php | 10 + .../debug/Tests/Fixtures/InternalClass.php | 15 + .../Tests/Fixtures/InternalInterface.php | 10 + .../debug/Tests/Fixtures/InternalTrait.php | 10 + .../debug/Tests/Fixtures/InternalTrait2.php | 23 + .../Tests/Fixtures/NonDeprecatedInterface.php | 7 + .../debug/Tests/Fixtures/PEARClass.php | 5 + .../symfony/debug/Tests/Fixtures/Throwing.php | 3 + .../debug/Tests/Fixtures/ToStringThrower.php | 24 + .../Fixtures/TraitWithInternalMethod.php | 13 + .../debug/Tests/Fixtures/casemismatch.php | 7 + .../debug/Tests/Fixtures/notPsr0Bis.php | 7 + .../Tests/Fixtures/psr4/Psr4CaseMismatch.php | 7 + .../debug/Tests/Fixtures/reallyNotPsr0.php | 7 + .../debug/Tests/Fixtures2/RequiredTwice.php | 7 + .../vendor/symfony/debug/Tests/HeaderMock.php | 38 + .../debug/Tests/MockExceptionHandler.php | 24 + .../debug/Tests/phpt/debug_class_loader.phpt | 27 + .../Tests/phpt/decorate_exception_hander.phpt | 47 + .../debug/Tests/phpt/exception_rethrown.phpt | 35 + .../phpt/fatal_with_nested_handlers.phpt | 42 + .../ContainerAwareEventDispatcher.php | 197 + .../Debug/TraceableEventDispatcher.php | 322 + .../TraceableEventDispatcherInterface.php | 36 + .../Debug/WrappedListener.php | 114 + .../RegisterListenersPass.php | 137 + .../vendor/symfony/event-dispatcher/Event.php | 58 + .../event-dispatcher/EventDispatcher.php | 236 + .../EventDispatcherInterface.php | 93 + .../EventSubscriberInterface.php | 46 + .../symfony/event-dispatcher/GenericEvent.php | 175 + .../ImmutableEventDispatcher.php | 91 + .../Tests/AbstractEventDispatcherTest.php | 442 + .../ContainerAwareEventDispatcherTest.php | 210 + .../Debug/TraceableEventDispatcherTest.php | 257 + .../RegisterListenersPassTest.php | 154 + .../Tests/EventDispatcherTest.php | 22 + .../event-dispatcher/Tests/EventTest.php | 55 + .../Tests/GenericEventTest.php | 136 + .../Tests/ImmutableEventDispatcherTest.php | 106 + .../symfony/finder/Comparator/Comparator.php | 98 + .../finder/Comparator/DateComparator.php | 51 + .../finder/Comparator/NumberComparator.php | 79 + .../Exception/AccessDeniedException.php | 19 + .../finder/Exception/ExceptionInterface.php | 25 + freescout-dist/vendor/symfony/finder/Glob.php | 116 + .../finder/Iterator/CustomFilterIterator.php | 61 + .../Iterator/FilecontentFilterIterator.php | 58 + .../Iterator/MultiplePcreFilterIterator.php | 112 + .../Iterator/SizeRangeFilterIterator.php | 57 + .../vendor/symfony/finder/SplFileInfo.php | 78 + .../Tests/Comparator/ComparatorTest.php | 65 + .../Tests/Comparator/DateComparatorTest.php | 64 + .../Tests/Comparator/NumberComparatorTest.php | 108 + .../symfony/finder/Tests/FinderTest.php | 737 + .../symfony/finder/Tests/Fixtures/.dot/a | 0 .../finder/Tests/Fixtures/.dot/b/c.neon | 0 .../finder/Tests/Fixtures/.dot/b/d.neon | 0 .../finder/Tests/Fixtures/A/B/C/abc.dat | 0 .../symfony/finder/Tests/Fixtures/A/B/ab.dat | 0 .../symfony/finder/Tests/Fixtures/A/a.dat | 0 .../Tests/Fixtures/copy/A/B/C/abc.dat.copy | 0 .../Tests/Fixtures/copy/A/B/ab.dat.copy | 0 .../finder/Tests/Fixtures/copy/A/a.dat.copy | 0 .../symfony/finder/Tests/Fixtures/one/.dot | 1 + .../symfony/finder/Tests/Fixtures/one/a | 0 .../finder/Tests/Fixtures/one/b/c.neon | 0 .../finder/Tests/Fixtures/one/b/d.neon | 0 .../Fixtures/r+e.gex[c]a(r)s/dir/bar.dat | 0 .../vendor/symfony/finder/Tests/GlobTest.php | 95 + .../Iterator/CustomFilterIteratorTest.php | 46 + .../Iterator/DateRangeFilterIteratorTest.php | 74 + .../Iterator/DepthRangeFilterIteratorTest.php | 83 + .../ExcludeDirectoryFilterIteratorTest.php | 80 + .../Iterator/FileTypeFilterIteratorTest.php | 73 + .../FilecontentFilterIteratorTest.php | 86 + .../Iterator/FilenameFilterIteratorTest.php | 54 + .../Tests/Iterator/FilterIteratorTest.php | 53 + .../finder/Tests/Iterator/Iterator.php | 55 + .../Tests/Iterator/IteratorTestCase.php | 100 + .../Tests/Iterator/MockFileListIterator.php | 21 + .../finder/Tests/Iterator/MockSplFileInfo.php | 132 + .../MultiplePcreFilterIteratorTest.php | 71 + .../Tests/Iterator/PathFilterIteratorTest.php | 82 + .../Tests/Iterator/RealIteratorTestCase.php | 119 + .../RecursiveDirectoryIteratorTest.php | 59 + .../Iterator/SizeRangeFilterIteratorTest.php | 69 + .../Tests/Iterator/SortableIteratorTest.php | 183 + .../http-foundation/AcceptHeaderItem.php | 209 + .../symfony/http-foundation/ApacheRequest.php | 43 + .../http-foundation/BinaryFileResponse.php | 359 + .../Exception/ConflictingHeadersException.php | 21 + .../Exception/RequestExceptionInterface.php | 21 + .../SuspiciousOperationException.php | 20 + .../ExpressionRequestMatcher.php | 47 + .../File/Exception/AccessDeniedException.php | 28 + .../File/Exception/FileException.php | 21 + .../File/Exception/FileNotFoundException.php | 28 + .../Exception/UnexpectedTypeException.php | 20 + .../File/Exception/UploadException.php | 21 + .../symfony/http-foundation/File/File.php | 138 + .../File/MimeType/ExtensionGuesser.php | 94 + .../MimeType/ExtensionGuesserInterface.php | 27 + .../File/MimeType/FileinfoMimeTypeGuesser.php | 69 + .../MimeType/MimeTypeExtensionGuesser.php | 808 + .../File/MimeType/MimeTypeGuesser.php | 133 + .../MimeType/MimeTypeGuesserInterface.php | 35 + .../symfony/http-foundation/File/Stream.php | 28 + .../http-foundation/File/UploadedFile.php | 268 + .../symfony/http-foundation/IpUtils.php | 156 + .../symfony/http-foundation/JsonResponse.php | 220 + .../http-foundation/RedirectResponse.php | 109 + .../http-foundation/RequestMatcher.php | 178 + .../RequestMatcherInterface.php | 27 + .../symfony/http-foundation/RequestStack.php | 103 + .../symfony/http-foundation/ServerBag.php | 102 + .../Session/Attribute/AttributeBag.php | 148 + .../Attribute/AttributeBagInterface.php | 72 + .../Attribute/NamespacedAttributeBag.php | 159 + .../Session/Flash/AutoExpireFlashBag.php | 161 + .../Session/Flash/FlashBag.php | 152 + .../Session/Flash/FlashBagInterface.php | 93 + .../http-foundation/Session/Session.php | 280 + .../Session/SessionBagInterface.php | 46 + .../Session/SessionBagProxy.php | 89 + .../Session/SessionInterface.php | 180 + .../Handler/AbstractSessionHandler.php | 168 + .../Handler/MemcacheSessionHandler.php | 118 + .../Handler/MemcachedSessionHandler.php | 122 + .../Storage/Handler/MongoDbSessionHandler.php | 255 + .../Handler/NativeFileSessionHandler.php | 55 + .../Storage/Handler/NativeSessionHandler.php | 24 + .../Storage/Handler/NullSessionHandler.php | 76 + .../Storage/Handler/PdoSessionHandler.php | 918 + .../Storage/Handler/StrictSessionHandler.php | 103 + .../Handler/WriteCheckSessionHandler.php | 92 + .../Session/Storage/MetadataBag.php | 168 + .../Storage/MockArraySessionStorage.php | 256 + .../Storage/MockFileSessionStorage.php | 152 + .../Session/Storage/NativeSessionStorage.php | 436 + .../Storage/PhpBridgeSessionStorage.php | 59 + .../Session/Storage/Proxy/AbstractProxy.php | 122 + .../Session/Storage/Proxy/NativeProxy.php | 40 + .../Storage/Proxy/SessionHandlerProxy.php | 101 + .../Storage/SessionStorageInterface.php | 137 + .../http-foundation/StreamedResponse.php | 146 + .../Tests/AcceptHeaderItemTest.php | 113 + .../Tests/AcceptHeaderTest.php | 103 + .../Tests/ApacheRequestTest.php | 93 + .../Tests/BinaryFileResponseTest.php | 365 + .../http-foundation/Tests/CookieTest.php | 235 + .../Tests/ExpressionRequestMatcherTest.php | 69 + .../http-foundation/Tests/File/FakeFile.php | 45 + .../http-foundation/Tests/File/FileTest.php | 180 + .../Tests/File/Fixtures/.unknownextension | 1 + .../Tests/File/Fixtures/directory/.empty | 0 .../Tests/File/Fixtures/other-file.example | 0 .../Tests/File/MimeType/MimeTypeTest.php | 90 + .../Tests/File/UploadedFileTest.php | 273 + .../http-foundation/Tests/FileBagTest.php | 175 + .../Fixtures/response-functional/common.inc | 43 + .../cookie_max_age.expected | 11 + .../response-functional/cookie_max_age.php | 10 + .../cookie_raw_urlencode.expected | 10 + .../cookie_raw_urlencode.php | 12 + .../cookie_samesite_lax.expected | 9 + .../cookie_samesite_lax.php | 8 + .../cookie_samesite_strict.expected | 9 + .../cookie_samesite_strict.php | 8 + .../cookie_urlencode.expected | 10 + .../response-functional/cookie_urlencode.php | 12 + .../invalid_cookie_name.expected | 6 + .../invalid_cookie_name.php | 11 + .../http-foundation/Tests/HeaderBagTest.php | 205 + .../http-foundation/Tests/IpUtilsTest.php | 104 + .../Tests/JsonResponseTest.php | 266 + .../Tests/ParameterBagTest.php | 194 + .../Tests/RedirectResponseTest.php | 97 + .../Tests/RequestMatcherTest.php | 151 + .../Tests/RequestStackTest.php | 70 + .../http-foundation/Tests/RequestTest.php | 2345 +++ .../Tests/ResponseFunctionalTest.php | 58 + .../Tests/ResponseHeaderBagTest.php | 363 + .../http-foundation/Tests/ResponseTest.php | 1013 + .../Tests/ResponseTestCase.php | 89 + .../http-foundation/Tests/ServerBagTest.php | 170 + .../Session/Attribute/AttributeBagTest.php | 186 + .../Attribute/NamespacedAttributeBagTest.php | 204 + .../Session/Flash/AutoExpireFlashBagTest.php | 161 + .../Tests/Session/Flash/FlashBagTest.php | 157 + .../Tests/Session/SessionTest.php | 263 + .../Handler/AbstractSessionHandlerTest.php | 61 + .../Storage/Handler/Fixtures/common.inc | 151 + .../Handler/Fixtures/empty_destroys.expected | 17 + .../Handler/Fixtures/empty_destroys.php | 8 + .../Handler/Fixtures/read_only.expected | 14 + .../Storage/Handler/Fixtures/read_only.php | 8 + .../Handler/Fixtures/regenerate.expected | 24 + .../Storage/Handler/Fixtures/regenerate.php | 10 + .../Storage/Handler/Fixtures/storage.expected | 20 + .../Storage/Handler/Fixtures/storage.php | 24 + .../Handler/Fixtures/with_cookie.expected | 15 + .../Storage/Handler/Fixtures/with_cookie.php | 8 + .../Fixtures/with_cookie_and_session.expected | 24 + .../Fixtures/with_cookie_and_session.php | 13 + .../Handler/MemcacheSessionHandlerTest.php | 135 + .../Handler/MemcachedSessionHandlerTest.php | 139 + .../Handler/MongoDbSessionHandlerTest.php | 333 + .../Handler/NativeFileSessionHandlerTest.php | 77 + .../Handler/NativeSessionHandlerTest.php | 38 + .../Handler/NullSessionHandlerTest.php | 59 + .../Storage/Handler/PdoSessionHandlerTest.php | 411 + .../Handler/StrictSessionHandlerTest.php | 189 + .../Handler/WriteCheckSessionHandlerTest.php | 97 + .../Tests/Session/Storage/MetadataBagTest.php | 139 + .../Storage/MockArraySessionStorageTest.php | 131 + .../Storage/MockFileSessionStorageTest.php | 127 + .../Storage/NativeSessionStorageTest.php | 294 + .../Storage/PhpBridgeSessionStorageTest.php | 96 + .../Storage/Proxy/AbstractProxyTest.php | 113 + .../Session/Storage/Proxy/NativeProxyTest.php | 38 + .../Storage/Proxy/SessionHandlerProxyTest.php | 157 + .../Tests/StreamedResponseTest.php | 144 + .../Tests/schema/http-status-codes.rng | 31 + .../Tests/schema/iana-registry.rng | 198 + .../symfony/http-kernel/Bundle/Bundle.php | 216 + .../http-kernel/Bundle/BundleInterface.php | 84 + .../CacheClearer/CacheClearerInterface.php | 27 + .../CacheClearer/ChainCacheClearer.php | 56 + .../CacheClearer/Psr6CacheClearer.php | 58 + .../http-kernel/CacheWarmer/CacheWarmer.php | 32 + .../CacheWarmer/CacheWarmerAggregate.php | 90 + .../CacheWarmer/CacheWarmerInterface.php | 32 + .../CacheWarmer/WarmableInterface.php | 27 + .../vendor/symfony/http-kernel/Client.php | 206 + .../Config/EnvParametersResource.php | 99 + .../http-kernel/Config/FileLocator.php | 54 + .../Controller/ArgumentResolver.php | 94 + .../ArgumentResolver/DefaultValueResolver.php | 40 + .../RequestAttributeValueResolver.php | 40 + .../ArgumentResolver/RequestValueResolver.php | 40 + .../ArgumentResolver/ServiceValueResolver.php | 77 + .../ArgumentResolver/SessionValueResolver.php | 46 + .../VariadicValueResolver.php | 48 + .../Controller/ArgumentResolverInterface.php | 35 + .../ArgumentValueResolverInterface.php | 43 + .../ContainerControllerResolver.php | 121 + .../Controller/ControllerReference.php | 44 + .../Controller/ControllerResolver.php | 258 + .../ControllerResolverInterface.php | 57 + .../Controller/TraceableArgumentResolver.php | 44 + .../TraceableControllerResolver.php | 69 + .../ControllerMetadata/ArgumentMetadata.php | 115 + .../ArgumentMetadataFactory.php | 141 + .../ArgumentMetadataFactoryInterface.php | 27 + .../DataCollector/AjaxDataCollector.php | 38 + .../DataCollector/ConfigDataCollector.php | 332 + .../DataCollector/DataCollector.php | 128 + .../DataCollector/DataCollectorInterface.php | 37 + .../DataCollector/DumpDataCollector.php | 315 + .../DataCollector/EventDataCollector.php | 124 + .../DataCollector/ExceptionDataCollector.php | 112 + .../LateDataCollectorInterface.php | 25 + .../DataCollector/LoggerDataCollector.php | 278 + .../DataCollector/MemoryDataCollector.php | 120 + .../DataCollector/RequestDataCollector.php | 404 + .../DataCollector/RouterDataCollector.php | 108 + .../DataCollector/TimeDataCollector.php | 149 + .../DataCollector/Util/ValueExporter.php | 99 + .../http-kernel/Debug/FileLinkFormatter.php | 109 + .../Debug/TraceableEventDispatcher.php | 82 + .../AddAnnotatedClassesToCachePass.php | 153 + .../AddClassesToCachePass.php | 25 + .../ConfigurableExtension.php | 42 + .../ControllerArgumentValueResolverPass.php | 48 + .../DependencyInjection/Extension.php | 77 + .../FragmentRendererPass.php | 67 + .../LazyLoadingFragmentHandler.php | 79 + .../DependencyInjection/LoggerPass.php | 41 + .../MergeExtensionConfigurationPass.php | 41 + ...RegisterControllerArgumentLocatorsPass.php | 179 + ...oveEmptyControllerArgumentLocatorsPass.php | 76 + .../ResettableServicePass.php | 66 + .../DependencyInjection/ServicesResetter.php | 39 + .../Event/FilterControllerArgumentsEvent.php | 52 + .../Event/FilterControllerEvent.php | 53 + .../http-kernel/Event/FilterResponseEvent.php | 55 + .../http-kernel/Event/FinishRequestEvent.php | 21 + .../http-kernel/Event/GetResponseEvent.php | 58 + .../GetResponseForControllerResultEvent.php | 61 + .../Event/GetResponseForExceptionEvent.php | 90 + .../symfony/http-kernel/Event/KernelEvent.php | 82 + .../http-kernel/Event/PostResponseEvent.php | 46 + .../EventListener/AbstractSessionListener.php | 91 + .../AbstractTestSessionListener.php | 100 + .../AddRequestFormatsListener.php | 50 + .../EventListener/DebugHandlersListener.php | 156 + .../EventListener/DumpListener.php | 55 + .../EventListener/ExceptionListener.php | 129 + .../EventListener/FragmentListener.php | 99 + .../EventListener/LocaleListener.php | 83 + .../EventListener/ProfilerListener.php | 128 + .../EventListener/ResponseListener.php | 56 + .../EventListener/RouterListener.php | 178 + .../EventListener/SaveSessionListener.php | 66 + .../EventListener/SessionListener.php | 40 + .../StreamedResponseListener.php | 49 + .../EventListener/SurrogateListener.php | 65 + .../EventListener/TestSessionListener.php | 40 + .../EventListener/TranslatorListener.php | 69 + .../EventListener/ValidateRequestListener.php | 53 + .../Exception/AccessDeniedHttpException.php | 29 + .../Exception/BadRequestHttpException.php | 28 + .../Exception/ConflictHttpException.php | 28 + .../Exception/GoneHttpException.php | 28 + .../Exception/HttpExceptionInterface.php | 34 + .../Exception/LengthRequiredHttpException.php | 28 + .../MethodNotAllowedHttpException.php | 31 + .../Exception/NotAcceptableHttpException.php | 28 + .../Exception/NotFoundHttpException.php | 28 + .../PreconditionFailedHttpException.php | 28 + .../PreconditionRequiredHttpException.php | 30 + .../ServiceUnavailableHttpException.php | 34 + .../TooManyRequestsHttpException.php | 36 + .../Exception/UnauthorizedHttpException.php | 31 + .../UnprocessableEntityHttpException.php | 28 + .../UnsupportedMediaTypeHttpException.php | 28 + .../AbstractSurrogateFragmentRenderer.php | 110 + .../Fragment/EsiFragmentRenderer.php | 28 + .../http-kernel/Fragment/FragmentHandler.php | 112 + .../Fragment/FragmentRendererInterface.php | 42 + .../Fragment/HIncludeFragmentRenderer.php | 165 + .../Fragment/InlineFragmentRenderer.php | 142 + .../Fragment/RoutableFragmentRenderer.php | 90 + .../Fragment/SsiFragmentRenderer.php | 28 + .../HttpCache/AbstractSurrogate.php | 134 + .../symfony/http-kernel/HttpCache/Esi.php | 115 + .../http-kernel/HttpCache/HttpCache.php | 699 + .../HttpCache/ResponseCacheStrategy.php | 96 + .../ResponseCacheStrategyInterface.php | 37 + .../symfony/http-kernel/HttpCache/Ssi.php | 98 + .../http-kernel/HttpCache/StoreInterface.php | 83 + .../HttpCache/SubRequestHandler.php | 108 + .../HttpCache/SurrogateInterface.php | 92 + .../vendor/symfony/http-kernel/HttpKernel.php | 299 + .../http-kernel/HttpKernelInterface.php | 43 + .../vendor/symfony/http-kernel/Kernel.php | 969 + .../symfony/http-kernel/KernelEvents.php | 103 + .../symfony/http-kernel/KernelInterface.php | 165 + .../http-kernel/Log/DebugLoggerInterface.php | 40 + .../vendor/symfony/http-kernel/Log/Logger.php | 111 + .../Profiler/FileProfilerStorage.php | 292 + .../symfony/http-kernel/Profiler/Profile.php | 287 + .../symfony/http-kernel/Profiler/Profiler.php | 265 + .../Profiler/ProfilerStorageInterface.php | 57 + .../http-kernel/RebootableInterface.php | 30 + .../http-kernel/Resources/welcome.html.php | 84 + .../http-kernel/TerminableInterface.php | 32 + .../http-kernel/Tests/Bundle/BundleTest.php | 104 + .../CacheClearer/ChainCacheClearerTest.php | 61 + .../CacheClearer/Psr6CacheClearerTest.php | 69 + .../CacheWarmer/CacheWarmerAggregateTest.php | 107 + .../Tests/CacheWarmer/CacheWarmerTest.php | 68 + .../symfony/http-kernel/Tests/ClientTest.php | 179 + .../Config/EnvParametersResourceTest.php | 110 + .../Tests/Config/FileLocatorTest.php | 48 + .../ServiceValueResolverTest.php | 130 + .../Tests/Controller/ArgumentResolverTest.php | 349 + .../ContainerControllerResolverTest.php | 308 + .../Controller/ControllerResolverTest.php | 331 + .../ArgumentMetadataFactoryTest.php | 148 + .../ArgumentMetadataTest.php | 46 + .../DataCollector/ConfigDataCollectorTest.php | 66 + .../Tests/DataCollector/DataCollectorTest.php | 38 + .../DataCollector/DumpDataCollectorTest.php | 138 + .../ExceptionDataCollectorTest.php | 59 + .../DataCollector/LoggerDataCollectorTest.php | 144 + .../DataCollector/MemoryDataCollectorTest.php | 59 + .../RequestDataCollectorTest.php | 334 + .../DataCollector/TimeDataCollectorTest.php | 55 + .../DataCollector/Util/ValueExporterTest.php | 51 + .../Tests/Debug/FileLinkFormatterTest.php | 66 + .../Debug/TraceableEventDispatcherTest.php | 121 + .../AddAnnotatedClassesToCachePassTest.php | 99 + ...ontrollerArgumentValueResolverPassTest.php | 67 + .../FragmentRendererPassTest.php | 71 + .../LazyLoadingFragmentHandlerTest.php | 66 + .../DependencyInjection/LoggerPassTest.php | 56 + .../MergeExtensionConfigurationPassTest.php | 50 + ...sterControllerArgumentLocatorsPassTest.php | 413 + ...mptyControllerArgumentLocatorsPassTest.php | 148 + .../ResettableServicePassTest.php | 78 + .../ServicesResetterTest.php | 42 + .../FilterControllerArgumentsEventTest.php | 17 + .../GetResponseForExceptionEventTest.php | 27 + .../AddRequestFormatsListenerTest.php | 84 + .../DebugHandlersListenerTest.php | 155 + .../Tests/EventListener/DumpListenerTest.php | 81 + .../EventListener/ExceptionListenerTest.php | 178 + .../EventListener/FragmentListenerTest.php | 122 + .../EventListener/LocaleListenerTest.php | 102 + .../EventListener/ProfilerListenerTest.php | 71 + .../EventListener/ResponseListenerTest.php | 95 + .../EventListener/RouterListenerTest.php | 226 + .../EventListener/SaveSessionListenerTest.php | 49 + .../EventListener/SessionListenerTest.php | 113 + .../EventListener/SurrogateListenerTest.php | 67 + .../EventListener/TestSessionListenerTest.php | 221 + .../EventListener/TranslatorListenerTest.php | 118 + .../ValidateRequestListenerTest.php | 48 + .../AccessDeniedHttpExceptionTest.php | 13 + .../Exception/BadRequestHttpExceptionTest.php | 13 + .../Exception/ConflictHttpExceptionTest.php | 13 + .../Tests/Exception/GoneHttpExceptionTest.php | 13 + .../Tests/Exception/HttpExceptionTest.php | 53 + .../LengthRequiredHttpExceptionTest.php | 13 + .../MethodNotAllowedHttpExceptionTest.php | 24 + .../NotAcceptableHttpExceptionTest.php | 13 + .../Exception/NotFoundHttpExceptionTest.php | 13 + .../PreconditionFailedHttpExceptionTest.php | 13 + .../PreconditionRequiredHttpExceptionTest.php | 13 + .../ServiceUnavailableHttpExceptionTest.php | 29 + .../TooManyRequestsHttpExceptionTest.php | 29 + .../UnauthorizedHttpExceptionTest.php | 24 + .../UnprocessableEntityHttpExceptionTest.php | 13 + .../UnsupportedMediaTypeHttpExceptionTest.php | 13 + .../Tests/Fixtures/123/Kernel123.php | 37 + .../Tests/Fixtures/ClearableService.php | 13 + .../Controller/BasicTypesController.php | 19 + .../Fixtures/Controller/ExtendingRequest.php | 18 + .../Fixtures/Controller/ExtendingSession.php | 18 + .../Controller/NullableController.php | 19 + .../Controller/VariadicController.php | 19 + .../DataCollector/CloneVarDataCollector.php | 46 + .../ExtensionAbsentBundle.php | 18 + .../ExtensionLoadedExtension.php | 22 + .../ExtensionLoadedBundle.php | 18 + .../ExtensionNotValidExtension.php | 20 + .../ExtensionNotValidBundle.php | 18 + .../Command/BarCommand.php | 17 + .../Command/FooCommand.php | 22 + .../ExtensionPresentExtension.php | 22 + .../ExtensionPresentBundle.php | 18 + .../Tests/Fixtures/KernelForOverrideName.php | 28 + .../Tests/Fixtures/KernelForTest.php | 37 + .../Tests/Fixtures/KernelWithoutBundles.php | 33 + .../Tests/Fixtures/ResettableService.php | 13 + .../http-kernel/Tests/Fixtures/TestClient.php | 31 + .../Tests/Fixtures/TestEventDispatcher.php | 32 + .../Fragment/EsiFragmentRendererTest.php | 118 + .../Tests/Fragment/FragmentHandlerTest.php | 99 + .../Fragment/HIncludeFragmentRendererTest.php | 102 + .../Fragment/InlineFragmentRendererTest.php | 320 + .../Fragment/RoutableFragmentRendererTest.php | 94 + .../Fragment/SsiFragmentRendererTest.php | 97 + .../http-kernel/Tests/HttpCache/EsiTest.php | 248 + .../Tests/HttpCache/HttpCacheTest.php | 1525 ++ .../Tests/HttpCache/HttpCacheTestCase.php | 185 + .../HttpCache/ResponseCacheStrategyTest.php | 240 + .../http-kernel/Tests/HttpCache/SsiTest.php | 215 + .../http-kernel/Tests/HttpCache/StoreTest.php | 301 + .../Tests/HttpCache/SubRequestHandlerTest.php | 153 + .../Tests/HttpCache/TestHttpKernel.php | 102 + .../HttpCache/TestMultipleHttpKernel.php | 81 + .../http-kernel/Tests/HttpKernelTest.php | 405 + .../symfony/http-kernel/Tests/KernelTest.php | 1075 ++ .../http-kernel/Tests/Log/LoggerTest.php | 212 + .../symfony/http-kernel/Tests/Logger.php | 88 + .../Profiler/FileProfilerStorageTest.php | 350 + .../Tests/Profiler/ProfilerTest.php | 105 + .../http-kernel/Tests/TestHttpKernel.php | 42 + .../http-kernel/Tests/UriSignerTest.php | 64 + .../vendor/symfony/polyfill-ctype/Ctype.php | 227 + .../symfony/polyfill-ctype/bootstrap.php | 26 + .../vendor/symfony/polyfill-intl-idn/Idn.php | 923 + .../vendor/symfony/polyfill-intl-idn/Info.php | 23 + .../Resources/unidata/DisallowedRanges.php | 384 + .../Resources/unidata/Regex.php | 33 + .../Resources/unidata/deviation.php | 8 + .../Resources/unidata/disallowed.php | 2638 +++ .../unidata/disallowed_STD3_mapped.php | 308 + .../unidata/disallowed_STD3_valid.php | 71 + .../Resources/unidata/ignored.php | 273 + .../Resources/unidata/mapped.php | 5778 ++++++ .../Resources/unidata/virama.php | 65 + .../symfony/polyfill-intl-idn/bootstrap.php | 145 + .../symfony/polyfill-intl-idn/bootstrap80.php | 125 + .../polyfill-intl-normalizer/Normalizer.php | 310 + .../Resources/stubs/Normalizer.php | 17 + .../unidata/canonicalComposition.php | 945 + .../unidata/canonicalDecomposition.php | 2065 +++ .../Resources/unidata/combiningClass.php | 876 + .../unidata/compatibilityDecomposition.php | 3695 ++++ .../polyfill-intl-normalizer/bootstrap.php | 23 + .../polyfill-intl-normalizer/bootstrap80.php | 19 + .../symfony/polyfill-mbstring/Mbstring.php | 873 + .../Resources/unidata/lowerCase.php | 1397 ++ .../Resources/unidata/titleCaseRegexp.php | 5 + .../Resources/unidata/upperCase.php | 1489 ++ .../symfony/polyfill-mbstring/bootstrap.php | 147 + .../symfony/polyfill-mbstring/bootstrap80.php | 143 + .../vendor/symfony/polyfill-php70/Php70.php | 74 + .../Resources/stubs/ArithmeticError.php | 5 + .../Resources/stubs/AssertionError.php | 5 + .../Resources/stubs/DivisionByZeroError.php | 5 + .../polyfill-php70/Resources/stubs/Error.php | 5 + .../Resources/stubs/ParseError.php | 5 + ...SessionUpdateTimestampHandlerInterface.php | 23 + .../Resources/stubs/TypeError.php | 5 + .../symfony/polyfill-php70/bootstrap.php | 27 + .../vendor/symfony/polyfill-php72/Php72.php | 217 + .../symfony/polyfill-php72/bootstrap.php | 57 + .../process/Exception/ExceptionInterface.php | 21 + .../Exception/InvalidArgumentException.php | 21 + .../process/Exception/LogicException.php | 21 + .../Exception/ProcessFailedException.php | 54 + .../Exception/ProcessTimedOutException.php | 69 + .../process/Exception/RuntimeException.php | 21 + .../symfony/process/ExecutableFinder.php | 88 + .../vendor/symfony/process/InputStream.php | 92 + .../symfony/process/PhpExecutableFinder.php | 94 + .../vendor/symfony/process/PhpProcess.php | 76 + .../symfony/process/Pipes/AbstractPipes.php | 178 + .../symfony/process/Pipes/PipesInterface.php | 67 + .../symfony/process/Pipes/UnixPipes.php | 153 + .../symfony/process/Pipes/WindowsPipes.php | 191 + .../vendor/symfony/process/ProcessBuilder.php | 280 + .../vendor/symfony/process/ProcessUtils.php | 123 + .../process/Tests/ExecutableFinderTest.php | 163 + .../process/Tests/NonStopableProcess.php | 47 + .../process/Tests/PhpExecutableFinderTest.php | 72 + .../symfony/process/Tests/PhpProcessTest.php | 48 + .../PipeStdinInStdoutStdErrStreamSelect.php | 72 + .../process/Tests/ProcessBuilderTest.php | 226 + .../Tests/ProcessFailedExceptionTest.php | 137 + .../symfony/process/Tests/ProcessTest.php | 1618 ++ .../process/Tests/ProcessUtilsTest.php | 53 + .../symfony/process/Tests/SignalListener.php | 21 + .../symfony/routing/Annotation/Route.php | 144 + .../RoutingResolverPass.php | 49 + .../routing/Exception/ExceptionInterface.php | 21 + .../Exception/InvalidParameterException.php | 21 + .../Exception/MethodNotAllowedException.php | 41 + .../MissingMandatoryParametersException.php | 22 + .../Exception/NoConfigurationException.php | 21 + .../Exception/ResourceNotFoundException.php | 23 + .../Exception/RouteNotFoundException.php | 21 + .../ConfigurableRequirementsInterface.php | 55 + .../Generator/Dumper/GeneratorDumper.php | 37 + .../Dumper/GeneratorDumperInterface.php | 39 + .../Generator/Dumper/PhpGeneratorDumper.php | 118 + .../routing/Generator/UrlGenerator.php | 321 + .../Generator/UrlGeneratorInterface.php | 86 + .../routing/Loader/AnnotationClassLoader.php | 274 + .../Loader/AnnotationDirectoryLoader.php | 93 + .../routing/Loader/AnnotationFileLoader.php | 142 + .../symfony/routing/Loader/ClosureLoader.php | 46 + .../Configurator/CollectionConfigurator.php | 81 + .../Configurator/ImportConfigurator.php | 49 + .../Loader/Configurator/RouteConfigurator.php | 34 + .../Configurator/RoutingConfigurator.php | 62 + .../Loader/Configurator/Traits/AddTrait.php | 55 + .../Loader/Configurator/Traits/RouteTrait.php | 131 + .../ServiceRouterLoader.php | 40 + .../routing/Loader/DirectoryLoader.php | 58 + .../symfony/routing/Loader/GlobFileLoader.php | 47 + .../routing/Loader/ObjectRouteLoader.php | 95 + .../symfony/routing/Loader/PhpFileLoader.php | 75 + .../symfony/routing/Loader/XmlFileLoader.php | 359 + .../symfony/routing/Loader/YamlFileLoader.php | 221 + .../Loader/schema/routing/routing-1.0.xsd | 148 + .../Matcher/Dumper/DumperCollection.php | 159 + .../routing/Matcher/Dumper/DumperRoute.php | 57 + .../routing/Matcher/Dumper/MatcherDumper.php | 37 + .../Matcher/Dumper/MatcherDumperInterface.php | 39 + .../Matcher/Dumper/PhpMatcherDumper.php | 429 + .../Matcher/Dumper/StaticPrefixCollection.php | 238 + .../Matcher/RedirectableUrlMatcher.php | 65 + .../RedirectableUrlMatcherInterface.php | 31 + .../Matcher/RequestMatcherInterface.php | 39 + .../routing/Matcher/TraceableUrlMatcher.php | 141 + .../symfony/routing/Matcher/UrlMatcher.php | 252 + .../routing/Matcher/UrlMatcherInterface.php | 41 + .../vendor/symfony/routing/RequestContext.php | 336 + .../routing/RequestContextAwareInterface.php | 27 + .../symfony/routing/RouteCollection.php | 280 + .../routing/RouteCollectionBuilder.php | 378 + .../vendor/symfony/routing/RouteCompiler.php | 316 + .../routing/RouteCompilerInterface.php | 30 + .../vendor/symfony/routing/Router.php | 388 + .../symfony/routing/RouterInterface.php | 32 + .../routing/Tests/Annotation/RouteTest.php | 50 + .../routing/Tests/CompiledRouteTest.php | 27 + .../RoutingResolverPassTest.php | 36 + .../AnnotatedClasses/AbstractClass.php | 16 + .../Fixtures/AnnotatedClasses/BarClass.php | 19 + .../Fixtures/AnnotatedClasses/BazClass.php | 19 + .../Fixtures/AnnotatedClasses/FooClass.php | 16 + .../Fixtures/AnnotatedClasses/FooTrait.php | 13 + .../Tests/Fixtures/CustomCompiledRoute.php | 18 + .../Tests/Fixtures/CustomRouteCompiler.php | 26 + .../Tests/Fixtures/CustomXmlFileLoader.php | 26 + .../AnonymousClassInTrait.php | 24 + .../OtherAnnotatedClasses/NoStartTagClass.php | 3 + .../OtherAnnotatedClasses/VariadicClass.php | 19 + .../Tests/Fixtures/RedirectableUrlMatcher.php | 30 + .../routing/Tests/Fixtures/annotated.php | 0 .../routing/Tests/Fixtures/bad_format.yml | 3 + .../symfony/routing/Tests/Fixtures/bar.xml | 0 .../controller/import__controller.xml | 10 + .../controller/import__controller.yml | 4 + .../Fixtures/controller/import_controller.xml | 8 + .../Fixtures/controller/import_controller.yml | 3 + .../controller/import_override_defaults.xml | 10 + .../controller/import_override_defaults.yml | 5 + .../Fixtures/controller/override_defaults.xml | 10 + .../Fixtures/controller/override_defaults.yml | 5 + .../Tests/Fixtures/controller/routing.xml | 14 + .../Tests/Fixtures/controller/routing.yml | 11 + .../Fixtures/directory/recurse/routes1.yml | 2 + .../Fixtures/directory/recurse/routes2.yml | 2 + .../Tests/Fixtures/directory/routes3.yml | 2 + .../Fixtures/directory_import/import.yml | 3 + .../Tests/Fixtures/dumper/url_matcher0.php | 37 + .../Tests/Fixtures/dumper/url_matcher1.php | 318 + .../Tests/Fixtures/dumper/url_matcher2.php | 380 + .../Tests/Fixtures/dumper/url_matcher3.php | 55 + .../Tests/Fixtures/dumper/url_matcher4.php | 112 + .../Tests/Fixtures/dumper/url_matcher5.php | 209 + .../Tests/Fixtures/dumper/url_matcher6.php | 213 + .../Tests/Fixtures/dumper/url_matcher7.php | 249 + .../symfony/routing/Tests/Fixtures/empty.yml | 0 .../routing/Tests/Fixtures/file_resource.yml | 0 .../symfony/routing/Tests/Fixtures/foo.xml | 0 .../symfony/routing/Tests/Fixtures/foo1.xml | 0 .../routing/Tests/Fixtures/glob/bar.xml | 8 + .../routing/Tests/Fixtures/glob/bar.yml | 4 + .../routing/Tests/Fixtures/glob/baz.xml | 8 + .../routing/Tests/Fixtures/glob/baz.yml | 4 + .../Tests/Fixtures/glob/import_multiple.xml | 8 + .../Tests/Fixtures/glob/import_multiple.yml | 2 + .../Tests/Fixtures/glob/import_single.xml | 8 + .../Tests/Fixtures/glob/import_single.yml | 2 + .../routing/Tests/Fixtures/glob/php_dsl.php | 7 + .../Tests/Fixtures/glob/php_dsl_bar.php | 12 + .../Tests/Fixtures/glob/php_dsl_baz.php | 12 + .../routing/Tests/Fixtures/incomplete.yml | 2 + .../routing/Tests/Fixtures/list_defaults.xml | 20 + .../Tests/Fixtures/list_in_list_defaults.xml | 22 + .../Tests/Fixtures/list_in_map_defaults.xml | 22 + .../Tests/Fixtures/list_null_values.xml | 22 + .../routing/Tests/Fixtures/map_defaults.xml | 20 + .../Tests/Fixtures/map_in_list_defaults.xml | 22 + .../Tests/Fixtures/map_in_map_defaults.xml | 22 + .../Tests/Fixtures/map_null_values.xml | 22 + .../routing/Tests/Fixtures/missing_id.xml | 8 + .../routing/Tests/Fixtures/missing_path.xml | 8 + .../Tests/Fixtures/namespaceprefix.xml | 16 + .../Fixtures/nonesense_resource_plus_path.yml | 3 + .../nonesense_type_without_resource.yml | 3 + .../routing/Tests/Fixtures/nonvalid.xml | 10 + .../routing/Tests/Fixtures/nonvalid.yml | 1 + .../routing/Tests/Fixtures/nonvalid2.yml | 1 + .../routing/Tests/Fixtures/nonvalidkeys.yml | 3 + .../routing/Tests/Fixtures/nonvalidnode.xml | 8 + .../routing/Tests/Fixtures/nonvalidroute.xml | 12 + .../routing/Tests/Fixtures/null_values.xml | 12 + .../routing/Tests/Fixtures/php_dsl.php | 22 + .../routing/Tests/Fixtures/php_dsl_sub.php | 14 + .../Tests/Fixtures/scalar_defaults.xml | 33 + .../Tests/Fixtures/special_route_name.yml | 2 + .../routing/Tests/Fixtures/validpattern.php | 18 + .../routing/Tests/Fixtures/validpattern.xml | 15 + .../routing/Tests/Fixtures/validpattern.yml | 13 + .../routing/Tests/Fixtures/validresource.php | 18 + .../routing/Tests/Fixtures/validresource.xml | 13 + .../routing/Tests/Fixtures/validresource.yml | 8 + .../Fixtures/with_define_path_variable.php | 5 + .../routing/Tests/Fixtures/withdoctype.xml | 3 + .../Dumper/PhpGeneratorDumperTest.php | 181 + .../Tests/Generator/UrlGeneratorTest.php | 724 + .../Loader/AbstractAnnotationLoaderTest.php | 33 + .../Loader/AnnotationClassLoaderTest.php | 296 + .../Loader/AnnotationDirectoryLoaderTest.php | 109 + .../Tests/Loader/AnnotationFileLoaderTest.php | 91 + .../Tests/Loader/ClosureLoaderTest.php | 49 + .../Tests/Loader/DirectoryLoaderTest.php | 74 + .../Tests/Loader/GlobFileLoaderTest.php | 45 + .../Tests/Loader/ObjectRouteLoaderTest.php | 123 + .../Tests/Loader/PhpFileLoaderTest.php | 133 + .../Tests/Loader/XmlFileLoaderTest.php | 385 + .../Tests/Loader/YamlFileLoaderTest.php | 206 + .../DumpedRedirectableUrlMatcherTest.php | 43 + .../Tests/Matcher/DumpedUrlMatcherTest.php | 48 + .../Matcher/Dumper/DumperCollectionTest.php | 34 + .../Matcher/Dumper/PhpMatcherDumperTest.php | 459 + .../Dumper/StaticPrefixCollectionTest.php | 175 + .../Matcher/RedirectableUrlMatcherTest.php | 124 + .../Tests/Matcher/TraceableUrlMatcherTest.php | 122 + .../routing/Tests/Matcher/UrlMatcherTest.php | 509 + .../routing/Tests/RequestContextTest.php | 160 + .../Tests/RouteCollectionBuilderTest.php | 364 + .../routing/Tests/RouteCollectionTest.php | 305 + .../routing/Tests/RouteCompilerTest.php | 389 + .../symfony/routing/Tests/RouteTest.php | 258 + .../symfony/routing/Tests/RouterTest.php | 163 + .../Catalogue/AbstractOperation.php | 157 + .../translation/Catalogue/MergeOperation.php | 55 + .../Catalogue/OperationInterface.php | 77 + .../translation/Catalogue/TargetOperation.php | 69 + .../translation/Command/XliffLintCommand.php | 246 + .../TranslationDataCollector.php | 167 + .../translation/DataCollectorTranslator.php | 165 + .../TranslationDumperPass.php | 44 + .../TranslationExtractorPass.php | 49 + .../DependencyInjection/TranslatorPass.php | 95 + .../translation/Dumper/CsvFileDumper.php | 63 + .../translation/Dumper/DumperInterface.php | 31 + .../symfony/translation/Dumper/FileDumper.php | 126 + .../translation/Dumper/IcuResFileDumper.php | 106 + .../translation/Dumper/IniFileDumper.php | 45 + .../translation/Dumper/JsonFileDumper.php | 44 + .../translation/Dumper/MoFileDumper.php | 82 + .../translation/Dumper/PhpFileDumper.php | 38 + .../translation/Dumper/PoFileDumper.php | 61 + .../translation/Dumper/QtFileDumper.php | 50 + .../translation/Dumper/XliffFileDumper.php | 200 + .../translation/Dumper/YamlFileDumper.php | 62 + .../Exception/ExceptionInterface.php | 21 + .../Exception/InvalidArgumentException.php | 21 + .../Exception/InvalidResourceException.php | 21 + .../translation/Exception/LogicException.php | 21 + .../Exception/NotFoundResourceException.php | 21 + .../Exception/RuntimeException.php | 21 + .../Extractor/AbstractFileExtractor.php | 85 + .../translation/Extractor/ChainExtractor.php | 60 + .../Extractor/ExtractorInterface.php | 38 + .../translation/Extractor/PhpExtractor.php | 258 + .../Extractor/PhpStringTokenParser.php | 142 + .../ChoiceMessageFormatterInterface.php | 30 + .../Formatter/MessageFormatter.php | 48 + .../Formatter/MessageFormatterInterface.php | 30 + .../translation/IdentityTranslator.php | 63 + .../vendor/symfony/translation/Interval.php | 109 + .../translation/Loader/ArrayLoader.php | 66 + .../translation/Loader/CsvFileLoader.php | 65 + .../symfony/translation/Loader/FileLoader.php | 65 + .../translation/Loader/IcuDatFileLoader.php | 62 + .../translation/Loader/IcuResFileLoader.php | 92 + .../translation/Loader/IniFileLoader.php | 28 + .../translation/Loader/JsonFileLoader.php | 64 + .../translation/Loader/LoaderInterface.php | 38 + .../translation/Loader/MoFileLoader.php | 148 + .../translation/Loader/PhpFileLoader.php | 28 + .../translation/Loader/PoFileLoader.php | 148 + .../translation/Loader/QtFileLoader.php | 77 + .../translation/Loader/XliffFileLoader.php | 347 + .../translation/Loader/YamlFileLoader.php | 57 + .../dic/xliff-core/xliff-core-1.2-strict.xsd | 2223 +++ .../schema/dic/xliff-core/xliff-core-2.0.xsd | 411 + .../Loader/schema/dic/xliff-core/xml.xsd | 309 + .../symfony/translation/LoggingTranslator.php | 136 + .../symfony/translation/MessageCatalogue.php | 271 + .../translation/MessageCatalogueInterface.php | 136 + .../symfony/translation/MessageSelector.php | 94 + .../translation/MetadataAwareInterface.php | 54 + .../translation/PluralizationRules.php | 210 + .../translation/Reader/TranslationReader.php | 63 + .../Reader/TranslationReaderInterface.php | 30 + .../schemas/xliff-core-1.2-strict.xsd | 2223 +++ .../Tests/Catalogue/AbstractOperationTest.php | 74 + .../Tests/Catalogue/MergeOperationTest.php | 83 + .../Tests/Catalogue/TargetOperationTest.php | 82 + .../TranslationDataCollectorTest.php | 150 + .../Tests/DataCollectorTranslatorTest.php | 92 + .../TranslationDumperPassTest.php | 48 + .../TranslationExtractorPassTest.php | 66 + .../TranslationPassTest.php | 103 + .../Tests/Dumper/CsvFileDumperTest.php | 30 + .../Tests/Dumper/FileDumperTest.php | 89 + .../Tests/Dumper/IcuResFileDumperTest.php | 29 + .../Tests/Dumper/IniFileDumperTest.php | 29 + .../Tests/Dumper/JsonFileDumperTest.php | 39 + .../Tests/Dumper/MoFileDumperTest.php | 29 + .../Tests/Dumper/PhpFileDumperTest.php | 29 + .../Tests/Dumper/PoFileDumperTest.php | 29 + .../Tests/Dumper/QtFileDumperTest.php | 29 + .../Tests/Dumper/XliffFileDumperTest.php | 115 + .../Tests/Dumper/YamlFileDumperTest.php | 47 + .../Tests/Extractor/PhpExtractorTest.php | 95 + .../Tests/Formatter/MessageFormatterTest.php | 82 + .../Tests/IdentityTranslatorTest.php | 96 + .../translation/Tests/IntervalTest.php | 49 + .../Tests/Loader/CsvFileLoaderTest.php | 61 + .../Tests/Loader/IcuDatFileLoaderTest.php | 64 + .../Tests/Loader/IcuResFileLoaderTest.php | 51 + .../Tests/Loader/IniFileLoaderTest.php | 51 + .../Tests/Loader/JsonFileLoaderTest.php | 62 + .../Tests/Loader/LocalizedTestCase.php | 24 + .../Tests/Loader/MoFileLoaderTest.php | 72 + .../Tests/Loader/PhpFileLoaderTest.php | 50 + .../Tests/Loader/PoFileLoaderTest.php | 109 + .../Tests/Loader/QtFileLoaderTest.php | 75 + .../Tests/Loader/XliffFileLoaderTest.php | 260 + .../Tests/Loader/YamlFileLoaderTest.php | 71 + .../Tests/LoggingTranslatorTest.php | 50 + .../Tests/MessageCatalogueTest.php | 222 + .../translation/Tests/MessageSelectorTest.php | 137 + .../Tests/PluralizationRulesTest.php | 122 + .../translation/Tests/TranslatorCacheTest.php | 311 + .../translation/Tests/TranslatorTest.php | 549 + .../Tests/Util/ArrayConverterTest.php | 74 + .../Tests/Writer/TranslationWriterTest.php | 82 + .../Tests/fixtures/empty-translation.mo | Bin 0 -> 49 bytes .../Tests/fixtures/empty-translation.po | 3 + .../translation/Tests/fixtures/empty.csv | 0 .../translation/Tests/fixtures/empty.ini | 0 .../translation/Tests/fixtures/empty.json | 0 .../translation/Tests/fixtures/empty.mo | 0 .../translation/Tests/fixtures/empty.po | 0 .../translation/Tests/fixtures/empty.xlf | 0 .../translation/Tests/fixtures/empty.yml | 0 .../translation/Tests/fixtures/encoding.xlf | 16 + .../Tests/fixtures/escaped-id-plurals.po | 10 + .../translation/Tests/fixtures/escaped-id.po | 8 + .../fixtures/extractor/resource.format.engine | 0 .../this.is.a.template.format.engine | 0 .../fixtures/extractor/translation.html.php | 49 + .../Tests/fixtures/fuzzy-translations.po | 10 + .../Tests/fixtures/invalid-xml-resources.xlf | 23 + .../translation/Tests/fixtures/malformed.json | 3 + .../translation/Tests/fixtures/messages.yml | 3 + .../Tests/fixtures/messages_linear.yml | 2 + .../translation/Tests/fixtures/non-valid.xlf | 11 + .../translation/Tests/fixtures/non-valid.yml | 1 + .../translation/Tests/fixtures/plurals.mo | Bin 0 -> 74 bytes .../translation/Tests/fixtures/plurals.po | 5 + .../translation/Tests/fixtures/resname.xlf | 19 + .../resourcebundle/corrupted/resources.dat | 1 + .../Tests/fixtures/resourcebundle/dat/en.res | Bin 0 -> 120 bytes .../Tests/fixtures/resourcebundle/dat/fr.res | Bin 0 -> 124 bytes .../fixtures/resourcebundle/dat/resources.dat | Bin 0 -> 352 bytes .../Tests/fixtures/resourcebundle/res/en.res | Bin 0 -> 84 bytes .../Tests/fixtures/resources-2.0-clean.xlf | 23 + .../resources-2.0-multi-segment-unit.xlf | 17 + .../Tests/fixtures/resources-2.0.xlf | 25 + .../Tests/fixtures/resources-clean.xlf | 25 + .../Tests/fixtures/resources-notes-meta.xlf | 26 + .../fixtures/resources-target-attributes.xlf | 14 + .../Tests/fixtures/resources-tool-info.xlf | 14 + .../translation/Tests/fixtures/resources.csv | 4 + .../Tests/fixtures/resources.dump.json | 1 + .../translation/Tests/fixtures/resources.ini | 1 + .../translation/Tests/fixtures/resources.json | 3 + .../translation/Tests/fixtures/resources.mo | Bin 0 -> 52 bytes .../translation/Tests/fixtures/resources.php | 5 + .../translation/Tests/fixtures/resources.po | 8 + .../translation/Tests/fixtures/resources.ts | 10 + .../translation/Tests/fixtures/resources.xlf | 23 + .../translation/Tests/fixtures/resources.yml | 1 + .../translation/Tests/fixtures/valid.csv | 4 + .../Tests/fixtures/with-attributes.xlf | 21 + .../Tests/fixtures/withdoctype.xlf | 12 + .../translation/Tests/fixtures/withnote.xlf | 22 + .../vendor/symfony/translation/Translator.php | 450 + .../translation/TranslatorBagInterface.php | 33 + .../translation/TranslatorInterface.php | 67 + .../translation/Util/ArrayConverter.php | 99 + .../translation/Writer/TranslationWriter.php | 103 + .../Writer/TranslationWriterInterface.php | 34 + .../symfony/var-dumper/Caster/AmqpCaster.php | 210 + .../symfony/var-dumper/Caster/ArgsStub.php | 80 + .../symfony/var-dumper/Caster/Caster.php | 162 + .../symfony/var-dumper/Caster/ClassStub.php | 87 + .../symfony/var-dumper/Caster/ConstStub.php | 33 + .../var-dumper/Caster/CutArrayStub.php | 30 + .../symfony/var-dumper/Caster/CutStub.php | 59 + .../symfony/var-dumper/Caster/DOMCaster.php | 302 + .../symfony/var-dumper/Caster/DateCaster.php | 129 + .../var-dumper/Caster/DoctrineCaster.php | 60 + .../symfony/var-dumper/Caster/EnumStub.php | 30 + .../var-dumper/Caster/ExceptionCaster.php | 349 + .../symfony/var-dumper/Caster/FrameStub.php | 30 + .../symfony/var-dumper/Caster/LinkStub.php | 108 + .../symfony/var-dumper/Caster/MongoCaster.php | 38 + .../symfony/var-dumper/Caster/PdoCaster.php | 120 + .../symfony/var-dumper/Caster/PgSqlCaster.php | 154 + .../symfony/var-dumper/Caster/RedisCaster.php | 77 + .../var-dumper/Caster/ReflectionCaster.php | 336 + .../var-dumper/Caster/ResourceCaster.php | 72 + .../symfony/var-dumper/Caster/SplCaster.php | 213 + .../symfony/var-dumper/Caster/StubCaster.php | 82 + .../var-dumper/Caster/SymfonyCaster.php | 43 + .../symfony/var-dumper/Caster/TraceStub.php | 36 + .../var-dumper/Caster/XmlReaderCaster.php | 77 + .../var-dumper/Caster/XmlResourceCaster.php | 61 + .../var-dumper/Cloner/AbstractCloner.php | 332 + .../var-dumper/Cloner/ClonerInterface.php | 27 + .../symfony/var-dumper/Cloner/Cursor.php | 43 + .../var-dumper/Cloner/DumperInterface.php | 60 + .../symfony/var-dumper/Cloner/VarCloner.php | 327 + .../var-dumper/Dumper/AbstractDumper.php | 211 + .../symfony/var-dumper/Dumper/CliDumper.php | 597 + .../var-dumper/Dumper/DataDumperInterface.php | 24 + .../Exception/ThrowingCasterException.php | 26 + .../var-dumper/Resources/functions/dump.php | 30 + .../var-dumper/Test/VarDumperTestTrait.php | 60 + .../var-dumper/Tests/Caster/CasterTest.php | 181 + .../Tests/Caster/DateCasterTest.php | 426 + .../Tests/Caster/ExceptionCasterTest.php | 230 + .../var-dumper/Tests/Caster/PdoCasterTest.php | 64 + .../Tests/Caster/RedisCasterTest.php | 84 + .../Tests/Caster/ReflectionCasterTest.php | 242 + .../var-dumper/Tests/Caster/SplCasterTest.php | 213 + .../Tests/Caster/StubCasterTest.php | 192 + .../Tests/Caster/XmlReaderCasterTest.php | 248 + .../var-dumper/Tests/Cloner/DataTest.php | 115 + .../var-dumper/Tests/Cloner/VarClonerTest.php | 437 + .../var-dumper/Tests/Dumper/CliDumperTest.php | 591 + .../var-dumper/Tests/Dumper/FunctionsTest.php | 57 + .../Tests/Dumper/HtmlDumperTest.php | 168 + .../Tests/Fixtures/FooInterface.php | 11 + .../Tests/Fixtures/GeneratorDemo.php | 21 + .../Tests/Fixtures/NotLoadableClass.php | 7 + .../var-dumper/Tests/Fixtures/Twig.php | 38 + .../var-dumper/Tests/Fixtures/dumb-var.php | 40 + .../var-dumper/Tests/Fixtures/xml_reader.xml | 10 + .../Tests/Test/VarDumperTestTraitTest.php | 41 + .../vendor/symfony/var-dumper/VarDumper.php | 48 + .../tedivm/jshrink/src/JShrink/Minifier.php | 613 + .../src/Css/Processor.php | 71 + .../src/Css/Property/Processor.php | 127 + .../src/Css/Property/Property.php | 90 + .../src/Css/Rule/Processor.php | 163 + .../src/Css/Rule/Rule.php | 85 + .../src/CssToInlineStyles.php | 240 + .../vendor/tormjens/eventy/.travis.yml | 15 + .../eventy/src/EventBladeServiceProvider.php | 31 + .../eventy/src/EventServiceProvider.php | 20 + .../vendor/tormjens/eventy/src/Events.php | 165 + .../tormjens/eventy/src/Facades/Events.php | 18 + .../vendor/vlucas/phpdotenv/src/Dotenv.php | 130 + .../src/Exception/ExceptionInterface.php | 11 + .../Exception/InvalidCallbackException.php | 13 + .../src/Exception/InvalidFileException.php | 13 + .../src/Exception/InvalidPathException.php | 13 + .../src/Exception/ValidationException.php | 13 + .../vendor/vlucas/phpdotenv/src/Validator.php | 149 + .../watson/rememberable/src/Query/Builder.php | 266 + .../watson/rememberable/src/Rememberable.php | 40 + .../vendor/webklex/laravel-imap/.travis.yml | 21 + .../vendor/webklex/laravel-imap/_config.yml | 1 + .../laravel-imap/src/IMAP/ClientManager.php | 126 + .../laravel-imap/src/IMAP/EncodingAliases.php | 481 + .../Exceptions/ConnectionFailedException.php | 24 + .../Exceptions/GetMessagesFailedException.php | 24 + .../InvalidWhereQueryCriteriaException.php | 24 + .../MessageSearchValidationException.php | 24 + .../Exceptions/MethodNotFoundException.php | 24 + .../laravel-imap/src/IMAP/Facades/Client.php | 33 + .../webklex/laravel-imap/src/IMAP/Folder.php | 433 + .../IMAP/Providers/LaravelServiceProvider.php | 145 + .../laravel-imap/src/IMAP/Query/Query.php | 418 + .../src/IMAP/Query/WhereQuery.php | 373 + .../src/IMAP/Support/AttachmentCollection.php | 22 + .../src/IMAP/Support/FlagCollection.php | 22 + .../src/IMAP/Support/FolderCollection.php | 22 + .../src/IMAP/Support/MessageCollection.php | 22 + .../src/IMAP/Support/PaginatedCollection.php | 60 + .../webklex/laravel-imap/src/config/imap.php | 115 + .../vendor/webklex/php-imap/.travis.yml | 34 + .../vendor/webklex/php-imap/_config.yml | 1 + .../vendor/webklex/php-imap/src/Address.php | 90 + .../vendor/webklex/php-imap/src/Attribute.php | 262 + .../vendor/webklex/php-imap/src/Client.php | 746 + .../webklex/php-imap/src/ClientManager.php | 276 + .../Connection/Protocols/LegacyProtocol.php | 643 + .../src/Connection/Protocols/Protocol.php | 285 + .../Protocols/ProtocolInterface.php | 408 + .../webklex/php-imap/src/EncodingAliases.php | 482 + .../webklex/php-imap/src/Events/Event.php | 28 + .../php-imap/src/Events/FlagDeletedEvent.php | 22 + .../php-imap/src/Events/FlagNewEvent.php | 39 + .../src/Events/FolderDeletedEvent.php | 22 + .../php-imap/src/Events/FolderMovedEvent.php | 38 + .../php-imap/src/Events/FolderNewEvent.php | 35 + .../src/Events/MessageCopiedEvent.php | 22 + .../src/Events/MessageDeletedEvent.php | 22 + .../php-imap/src/Events/MessageMovedEvent.php | 38 + .../php-imap/src/Events/MessageNewEvent.php | 35 + .../src/Events/MessageRestoredEvent.php | 22 + .../src/Exceptions/AuthFailedException.php | 24 + .../Exceptions/ConnectionFailedException.php | 24 + .../src/Exceptions/EventNotFoundException.php | 24 + .../Exceptions/FolderFetchingException.php | 24 + .../Exceptions/GetMessagesFailedException.php | 24 + .../InvalidMessageDateException.php | 24 + .../InvalidWhereQueryCriteriaException.php | 24 + .../src/Exceptions/MaskNotFoundException.php | 24 + .../MessageContentFetchingException.php | 24 + .../src/Exceptions/MessageFlagException.php | 24 + .../MessageHeaderFetchingException.php | 24 + .../Exceptions/MessageNotFoundException.php | 24 + .../MessageSearchValidationException.php | 24 + .../Exceptions/MethodNotFoundException.php | 24 + .../MethodNotSupportedException.php | 24 + .../NotSupportedCapabilityException.php | 24 + .../ProtocolNotSupportedException.php | 24 + .../src/Exceptions/RuntimeException.php | 24 + .../vendor/webklex/php-imap/src/Folder.php | 475 + .../vendor/webklex/php-imap/src/IMAP.php | 375 + .../vendor/webklex/php-imap/src/Part.php | 312 + .../webklex/php-imap/src/Query/Query.php | 979 + .../webklex/php-imap/src/Query/WhereQuery.php | 546 + .../src/Support/AttachmentCollection.php | 22 + .../php-imap/src/Support/FlagCollection.php | 22 + .../php-imap/src/Support/FolderCollection.php | 22 + .../src/Support/Masks/AttachmentMask.php | 44 + .../php-imap/src/Support/Masks/Mask.php | 137 + .../src/Support/Masks/MessageMask.php | 86 + .../src/Support/MessageCollection.php | 22 + .../src/Support/PaginatedCollection.php | 82 + .../webklex/php-imap/src/Traits/HasEvents.php | 77 + .../webklex/php-imap/src/config/imap.php | 226 + freescout-dist/webpack.mix.js | 15 + 5423 files changed, 775725 insertions(+) create mode 100644 freescout-dist/.editorconfig create mode 100644 freescout-dist/.env.example create mode 100644 freescout-dist/.env.travis create mode 100644 freescout-dist/.gitattributes create mode 100644 freescout-dist/.gitcommit create mode 100644 freescout-dist/.github/ISSUE_TEMPLATE/general_help_request.md create mode 100644 freescout-dist/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 freescout-dist/.github/workflows/lint-php.yml create mode 100644 freescout-dist/.github/workflows/test-pgsql.yml create mode 100644 freescout-dist/.github/workflows/test.yml create mode 100644 freescout-dist/.gitignore create mode 100644 freescout-dist/.htaccess create mode 100644 freescout-dist/.travis.yml create mode 100644 freescout-dist/LICENSE create mode 100644 freescout-dist/README.md create mode 100644 freescout-dist/SECURITY.md create mode 100644 freescout-dist/app/ActivityLog.php create mode 100644 freescout-dist/app/Attachment.php create mode 100644 freescout-dist/app/Broadcasting/Broadcasters/PolycastBroadcaster.php create mode 100644 freescout-dist/app/Channels/RealtimeBroadcastChannel.php create mode 100644 freescout-dist/app/Console/Commands/AfterAppUpdate.php create mode 100644 freescout-dist/app/Console/Commands/Build.php create mode 100644 freescout-dist/app/Console/Commands/CheckConvViewers.php create mode 100644 freescout-dist/app/Console/Commands/CheckRequirements.php create mode 100644 freescout-dist/app/Console/Commands/CleanNotificationsTable.php create mode 100644 freescout-dist/app/Console/Commands/CleanSendLog.php create mode 100644 freescout-dist/app/Console/Commands/CleanTmp.php create mode 100644 freescout-dist/app/Console/Commands/ClearCache.php create mode 100644 freescout-dist/app/Console/Commands/CreateUser.php create mode 100644 freescout-dist/app/Console/Commands/FetchEmails.php create mode 100644 freescout-dist/app/Console/Commands/FetchMonitor.php create mode 100644 freescout-dist/app/Console/Commands/GenerateVars.php create mode 100644 freescout-dist/app/Console/Commands/LogoutUsers.php create mode 100644 freescout-dist/app/Console/Commands/LogsMonitor.php create mode 100644 freescout-dist/app/Console/Commands/ModuleBuild.php create mode 100644 freescout-dist/app/Console/Commands/ModuleCheckLicenses.php create mode 100644 freescout-dist/app/Console/Commands/ModuleInstall.php create mode 100644 freescout-dist/app/Console/Commands/ModuleLaroute.php create mode 100644 freescout-dist/app/Console/Commands/ModuleUpdate.php create mode 100644 freescout-dist/app/Console/Commands/SendMonitor.php create mode 100644 freescout-dist/app/Console/Commands/Update.php create mode 100644 freescout-dist/app/Console/Commands/UpdateFolderCounters.php create mode 100644 freescout-dist/app/Console/Kernel.php create mode 100644 freescout-dist/app/Conversation.php create mode 100644 freescout-dist/app/ConversationFolder.php create mode 100644 freescout-dist/app/Customer.php create mode 100644 freescout-dist/app/CustomerChannel.php create mode 100644 freescout-dist/app/Email.php create mode 100644 freescout-dist/app/Events/ConversationCustomerChanged.php create mode 100644 freescout-dist/app/Events/ConversationStatusChanged.php create mode 100644 freescout-dist/app/Events/ConversationUserChanged.php create mode 100644 freescout-dist/app/Events/CustomerCreatedConversation.php create mode 100644 freescout-dist/app/Events/CustomerReplied.php create mode 100644 freescout-dist/app/Events/RealtimeBroadcastNotificationCreated.php create mode 100644 freescout-dist/app/Events/RealtimeChat.php create mode 100644 freescout-dist/app/Events/RealtimeConvNewThread.php create mode 100644 freescout-dist/app/Events/RealtimeConvView.php create mode 100644 freescout-dist/app/Events/RealtimeConvViewFinish.php create mode 100644 freescout-dist/app/Events/RealtimeMailboxNewThread.php create mode 100644 freescout-dist/app/Events/UserAddedNote.php create mode 100644 freescout-dist/app/Events/UserCreatedConversation.php create mode 100644 freescout-dist/app/Events/UserCreatedConversationDraft.php create mode 100644 freescout-dist/app/Events/UserCreatedThreadDraft.php create mode 100644 freescout-dist/app/Events/UserDeleted.php create mode 100644 freescout-dist/app/Events/UserReplied.php create mode 100644 freescout-dist/app/Exceptions/Handler.php create mode 100644 freescout-dist/app/FailedJob.php create mode 100644 freescout-dist/app/Folder.php create mode 100644 freescout-dist/app/Follower.php create mode 100644 freescout-dist/app/Http/Controllers/Auth/ForgotPasswordController.php create mode 100644 freescout-dist/app/Http/Controllers/Auth/LoginController.php create mode 100644 freescout-dist/app/Http/Controllers/Auth/RegisterController.php create mode 100644 freescout-dist/app/Http/Controllers/Auth/ResetPasswordController.php create mode 100644 freescout-dist/app/Http/Controllers/Controller.php create mode 100755 freescout-dist/app/Http/Controllers/ConversationsController.php create mode 100644 freescout-dist/app/Http/Controllers/CustomersController.php create mode 100755 freescout-dist/app/Http/Controllers/MailboxesController.php create mode 100644 freescout-dist/app/Http/Controllers/ModulesController.php create mode 100644 freescout-dist/app/Http/Controllers/OpenController.php create mode 100644 freescout-dist/app/Http/Controllers/SecureController.php create mode 100644 freescout-dist/app/Http/Controllers/SettingsController.php create mode 100644 freescout-dist/app/Http/Controllers/SystemController.php create mode 100644 freescout-dist/app/Http/Controllers/TranslateController.php create mode 100644 freescout-dist/app/Http/Controllers/UsersController.php create mode 100644 freescout-dist/app/Http/Kernel.php create mode 100644 freescout-dist/app/Http/Middleware/CheckRole.php create mode 100644 freescout-dist/app/Http/Middleware/CustomHandle.php create mode 100644 freescout-dist/app/Http/Middleware/EncryptCookies.php create mode 100644 freescout-dist/app/Http/Middleware/FrameGuard.php create mode 100644 freescout-dist/app/Http/Middleware/HttpsRedirect.php create mode 100644 freescout-dist/app/Http/Middleware/Localize.php create mode 100644 freescout-dist/app/Http/Middleware/LogoutIfDeleted.php create mode 100644 freescout-dist/app/Http/Middleware/RedirectIfAuthenticated.php create mode 100644 freescout-dist/app/Http/Middleware/ResponseHeaders.php create mode 100644 freescout-dist/app/Http/Middleware/TerminateHandler.php create mode 100644 freescout-dist/app/Http/Middleware/TokenAuth.php create mode 100644 freescout-dist/app/Http/Middleware/TrimStrings.php create mode 100644 freescout-dist/app/Http/Middleware/TrustProxies.php create mode 100644 freescout-dist/app/Http/Middleware/VerifyCsrfToken.php create mode 100644 freescout-dist/app/Job.php create mode 100644 freescout-dist/app/Jobs/RestartQueueWorker.php create mode 100644 freescout-dist/app/Jobs/SendAlert.php create mode 100644 freescout-dist/app/Jobs/SendAutoReply.php create mode 100644 freescout-dist/app/Jobs/SendEmailReplyError.php create mode 100644 freescout-dist/app/Jobs/SendNotificationToUsers.php create mode 100644 freescout-dist/app/Jobs/SendReplyToCustomer.php create mode 100644 freescout-dist/app/Jobs/TriggerAction.php create mode 100644 freescout-dist/app/Jobs/UpdateFolderCounters.php create mode 100644 freescout-dist/app/Listeners/ActivateUser.php create mode 100644 freescout-dist/app/Listeners/LogFailedLogin.php create mode 100644 freescout-dist/app/Listeners/LogLockout.php create mode 100644 freescout-dist/app/Listeners/LogPasswordReset.php create mode 100644 freescout-dist/app/Listeners/LogRegisteredUser.php create mode 100644 freescout-dist/app/Listeners/LogSuccessfulLogin.php create mode 100644 freescout-dist/app/Listeners/LogSuccessfulLogout.php create mode 100644 freescout-dist/app/Listeners/LogUserDeletion.php create mode 100644 freescout-dist/app/Listeners/ProcessSwiftMessage.php create mode 100644 freescout-dist/app/Listeners/RefreshConversations.php create mode 100644 freescout-dist/app/Listeners/RememberUserLocale.php create mode 100644 freescout-dist/app/Listeners/RestartSwiftMailer.php create mode 100644 freescout-dist/app/Listeners/SendAutoReply.php create mode 100644 freescout-dist/app/Listeners/SendNotificationToUsers.php create mode 100644 freescout-dist/app/Listeners/SendPasswordChanged.php create mode 100644 freescout-dist/app/Listeners/SendReplyToCustomer.php create mode 100644 freescout-dist/app/Listeners/UpdateMailboxCounters.php create mode 100644 freescout-dist/app/Mail/Alert.php create mode 100644 freescout-dist/app/Mail/AutoReply.php create mode 100644 freescout-dist/app/Mail/PasswordChanged.php create mode 100644 freescout-dist/app/Mail/ReplyToCustomer.php create mode 100644 freescout-dist/app/Mail/Test.php create mode 100644 freescout-dist/app/Mail/UserEmailReplyError.php create mode 100644 freescout-dist/app/Mail/UserInvite.php create mode 100644 freescout-dist/app/Mail/UserNotification.php create mode 100644 freescout-dist/app/Mailbox.php create mode 100644 freescout-dist/app/MailboxUser.php create mode 100644 freescout-dist/app/Misc/Helper.php create mode 100644 freescout-dist/app/Misc/Mail.php create mode 100644 freescout-dist/app/Misc/SwiftGetSmtpQueueId.php create mode 100644 freescout-dist/app/Misc/WpApi.php create mode 100644 freescout-dist/app/Module.php create mode 100644 freescout-dist/app/Notifications/BroadcastNotification.php create mode 100644 freescout-dist/app/Notifications/WebsiteNotification.php create mode 100644 freescout-dist/app/Observers/AttachmentObserver.php create mode 100644 freescout-dist/app/Observers/ConversationObserver.php create mode 100644 freescout-dist/app/Observers/CustomerObserver.php create mode 100644 freescout-dist/app/Observers/DatabaseNotificationObserver.php create mode 100644 freescout-dist/app/Observers/EmailObserver.php create mode 100644 freescout-dist/app/Observers/FollowerObserver.php create mode 100644 freescout-dist/app/Observers/MailboxObserver.php create mode 100644 freescout-dist/app/Observers/SendLogObserver.php create mode 100644 freescout-dist/app/Observers/ThreadObserver.php create mode 100644 freescout-dist/app/Observers/UserObserver.php create mode 100644 freescout-dist/app/Option.php create mode 100644 freescout-dist/app/Policies/ConversationPolicy.php create mode 100644 freescout-dist/app/Policies/FolderPolicy.php create mode 100644 freescout-dist/app/Policies/MailboxPolicy.php create mode 100644 freescout-dist/app/Policies/ThreadPolicy.php create mode 100644 freescout-dist/app/Policies/UserPolicy.php create mode 100644 freescout-dist/app/Providers/AppServiceProvider.php create mode 100644 freescout-dist/app/Providers/AuthServiceProvider.php create mode 100644 freescout-dist/app/Providers/BroadcastServiceProvider.php create mode 100644 freescout-dist/app/Providers/EventServiceProvider.php create mode 100644 freescout-dist/app/Providers/PolycastServiceProvider.php create mode 100644 freescout-dist/app/Providers/RouteServiceProvider.php create mode 100644 freescout-dist/app/SendLog.php create mode 100644 freescout-dist/app/Sendmail.php create mode 100644 freescout-dist/app/Subscription.php create mode 100644 freescout-dist/app/Thread.php create mode 100644 freescout-dist/app/User.php create mode 100644 freescout-dist/artisan create mode 100644 freescout-dist/bootstrap/app.php create mode 100755 freescout-dist/bootstrap/cache/.gitignore create mode 100644 freescout-dist/composer.json create mode 100644 freescout-dist/composer.lock create mode 100644 freescout-dist/config/activitylog.php create mode 100644 freescout-dist/config/app.php create mode 100644 freescout-dist/config/auth.php create mode 100644 freescout-dist/config/broadcasting.php create mode 100644 freescout-dist/config/cache.php create mode 100644 freescout-dist/config/database.php create mode 100644 freescout-dist/config/filesystems.php create mode 100644 freescout-dist/config/installer.php create mode 100644 freescout-dist/config/laroute.php create mode 100644 freescout-dist/config/mail.php create mode 100644 freescout-dist/config/minify.config.php create mode 100644 freescout-dist/config/modules.php create mode 100644 freescout-dist/config/purifier.php create mode 100644 freescout-dist/config/queue.php create mode 100644 freescout-dist/config/self-update.php create mode 100644 freescout-dist/config/services.php create mode 100644 freescout-dist/config/session.php create mode 100644 freescout-dist/config/subscriptions.php create mode 100644 freescout-dist/config/translation-manager.php create mode 100644 freescout-dist/config/trustedproxy.php create mode 100644 freescout-dist/config/view.php create mode 100644 freescout-dist/database/.gitignore create mode 100644 freescout-dist/database/factories/ConversationFactory.php create mode 100644 freescout-dist/database/factories/CustomerFactory.php create mode 100644 freescout-dist/database/factories/EmailFactory.php create mode 100644 freescout-dist/database/factories/FolderFactory.php create mode 100644 freescout-dist/database/factories/MailboxFactory.php create mode 100644 freescout-dist/database/factories/ThreadFactory.php create mode 100644 freescout-dist/database/factories/UserFactory.php create mode 100644 freescout-dist/database/migrations/2014_04_02_193005_create_translations_table.php create mode 100644 freescout-dist/database/migrations/2018_06_10_000000_create_users_table.php create mode 100644 freescout-dist/database/migrations/2018_06_10_100000_create_password_resets_table.php create mode 100644 freescout-dist/database/migrations/2018_06_25_065719_create_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2018_06_29_041002_create_mailbox_user_table.php create mode 100644 freescout-dist/database/migrations/2018_07_07_071443_create_activity_logs_table.php create mode 100644 freescout-dist/database/migrations/2018_07_09_052314_create_emails_table.php create mode 100644 freescout-dist/database/migrations/2018_07_09_053559_create_customers_table.php create mode 100644 freescout-dist/database/migrations/2018_07_11_010333_create_conversations_table.php create mode 100644 freescout-dist/database/migrations/2018_07_11_074558_create_folders_table.php create mode 100644 freescout-dist/database/migrations/2018_07_11_081928_create_conversation_folder_table.php create mode 100644 freescout-dist/database/migrations/2018_07_12_003318_create_threads_table.php create mode 100644 freescout-dist/database/migrations/2018_07_30_153206_create_jobs_table.php create mode 100644 freescout-dist/database/migrations/2018_07_30_165237_create_failed_jobs_table.php create mode 100644 freescout-dist/database/migrations/2018_08_04_063414_create_attachments_table.php create mode 100644 freescout-dist/database/migrations/2018_08_05_045458_create_options_table.php create mode 100644 freescout-dist/database/migrations/2018_08_05_153518_create_subscriptions_table.php create mode 100644 freescout-dist/database/migrations/2018_08_06_114901_create_send_logs_table.php create mode 100644 freescout-dist/database/migrations/2018_09_05_024109_create_notifications_table.php create mode 100644 freescout-dist/database/migrations/2018_09_05_033609_create_polycast_events_table.php create mode 100644 freescout-dist/database/migrations/2018_11_04_113009_create_modules_table.php create mode 100644 freescout-dist/database/migrations/2018_11_13_143000_encrypt_mailbox_password.php create mode 100644 freescout-dist/database/migrations/2018_11_26_122617_add_locale_column_to_users_table.php create mode 100644 freescout-dist/database/migrations/2018_12_11_130728_add_status_column_to_users_table.php create mode 100644 freescout-dist/database/migrations/2018_12_15_151003_add_send_status_data_column_to_threads_table.php create mode 100644 freescout-dist/database/migrations/2019_06_16_124000_add_in_validate_cert_column_to_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2019_06_21_130200_add_meta_subtype_columns_to_threads_table.php create mode 100644 freescout-dist/database/migrations/2019_06_25_105200_change_status_message_column_in_send_logs_table.php create mode 100644 freescout-dist/database/migrations/2019_07_05_370100_add_in_imap_folders_column_to_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2019_10_06_123000_add_auto_bcc_column_to_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2019_12_10_0856000_add_before_reply_column_to_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2019_12_19_183015_add_meta_column_to_folders_table.php create mode 100644 freescout-dist/database/migrations/2019_12_22_111025_change_passwords_types_in_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2019_12_24_155120_create_followers_table.php create mode 100644 freescout-dist/database/migrations/2020_02_06_103815_add_hide_column_to_mailbox_user_table.php create mode 100644 freescout-dist/database/migrations/2020_02_16_121001_add_mute_column_to_mailbox_user_table.php create mode 100644 freescout-dist/database/migrations/2020_03_06_100100_add_public_column_to_attachments_table.php create mode 100644 freescout-dist/database/migrations/2020_03_29_095201_update_in_imap_folders_in_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2020_04_16_122803_add_imap_sent_folder_column_to_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2020_05_28_095100_drop_slug_column_in_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2020_06_26_080258_add_email_history_column_to_conversations_table.php create mode 100644 freescout-dist/database/migrations/2020_09_18_123314_add_access_column_to_mailbox_user_table.php create mode 100644 freescout-dist/database/migrations/2020_09_20_010000_drop_email_history_column_in_conversations_table.php create mode 100644 freescout-dist/database/migrations/2020_11_04_140000_change_foreign_keys_types.php create mode 100644 freescout-dist/database/migrations/2020_11_19_070000_update_customers_table.php create mode 100644 freescout-dist/database/migrations/2020_12_22_070000_move_user_permissions_to_env.php create mode 100644 freescout-dist/database/migrations/2020_12_22_080000_add_permissions_column_to_users_table.php create mode 100644 freescout-dist/database/migrations/2020_12_30_010000_add_imported_column_to_threads_table.php create mode 100644 freescout-dist/database/migrations/2021_02_06_010101_add_meta_column_to_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2021_02_09_010101_add_hash_column_to_ltm_translations_table.php create mode 100644 freescout-dist/database/migrations/2021_02_17_010101_change_string_columns_in_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2021_03_01_010101_add_channel_column_to_conversations_table.php create mode 100644 freescout-dist/database/migrations/2021_03_01_010101_add_channel_columns_to_customers_table.php create mode 100644 freescout-dist/database/migrations/2021_04_15_010101_add_meta_column_to_customers_table.php create mode 100644 freescout-dist/database/migrations/2021_05_21_090000_encrypt_mailbox_out_password.php create mode 100644 freescout-dist/database/migrations/2021_05_21_105200_encrypt_mail_password.php create mode 100644 freescout-dist/database/migrations/2021_09_21_010101_add_indexes_to_conversations_table.php create mode 100644 freescout-dist/database/migrations/2021_11_30_010101_remove_unique_index_in_folders_table.php create mode 100644 freescout-dist/database/migrations/2021_12_25_010101_change_emails_column_in_users_table.php create mode 100644 freescout-dist/database/migrations/2022_12_17_010101_add_meta_column_to_conversations_table.php create mode 100644 freescout-dist/database/migrations/2022_12_18_010101_set_user_type_field.php create mode 100644 freescout-dist/database/migrations/2022_12_25_010101_set_numeric_phones_in_customers_table.php create mode 100644 freescout-dist/database/migrations/2023_01_14_010101_change_deleted_folder_index.php create mode 100644 freescout-dist/database/migrations/2023_05_09_010101_add_aliases_reply_column_to_mailboxes_table.php create mode 100644 freescout-dist/database/migrations/2023_08_19_010101_create_customer_channel_table.php create mode 100644 freescout-dist/database/migrations/2023_08_19_020202_populate_customer_channel_table.php create mode 100644 freescout-dist/database/migrations/2023_08_29_010101_add_id_column_to_customer_channel_table.php create mode 100644 freescout-dist/database/migrations/2023_09_05_010101_add_smtp_queue_id_column_to_send_logs_table.php create mode 100644 freescout-dist/database/migrations/2023_11_14_010101_change_aliases_column_in_mailboxes_table.php create mode 100644 freescout-dist/database/seeds/CustomersTableSeeder.php create mode 100644 freescout-dist/database/seeds/DatabaseSeeder.php create mode 100644 freescout-dist/database/seeds/MailboxesTableSeeder.php create mode 100644 freescout-dist/database/seeds/UsersTableSeeder.php create mode 100644 freescout-dist/overrides/axn/laravel-laroute/src/Routes/Collection.php create mode 100644 freescout-dist/overrides/barryvdh/laravel-debugbar/src/DataFormatter/QueryFormatter.php create mode 100644 freescout-dist/overrides/barryvdh/laravel-debugbar/src/JavascriptRenderer.php create mode 100644 freescout-dist/overrides/barryvdh/laravel-translation-manager/src/Controller.php create mode 100644 freescout-dist/overrides/barryvdh/laravel-translation-manager/src/Manager.php create mode 100644 freescout-dist/overrides/chumper/zipper/src/Chumper/Zipper/Repositories/ZipRepository.php create mode 100644 freescout-dist/overrides/codedge/laravel-selfupdater/src/AbstractRepositoryType.php create mode 100644 freescout-dist/overrides/codedge/laravel-selfupdater/src/SourceRepositoryTypes/GithubRepositoryType.php create mode 100644 freescout-dist/overrides/devfactory/minify/src/Providers/BaseProvider.php create mode 100644 freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php create mode 100644 freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOQueryImplementation.php create mode 100644 freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php create mode 100644 freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatementImplementations.php create mode 100644 freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php create mode 100644 freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php create mode 100644 freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php create mode 100644 freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php create mode 100644 freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php create mode 100644 freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php create mode 100644 freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php create mode 100644 freescout-dist/overrides/fzaninotto/faker/src/Faker/Provider/Base.php create mode 100644 freescout-dist/overrides/guzzlehttp/guzzle/src/Client.php create mode 100644 freescout-dist/overrides/guzzlehttp/guzzle/src/Cookie/CookieJar.php create mode 100644 freescout-dist/overrides/guzzlehttp/psr7/src/LazyOpenStream.php create mode 100644 freescout-dist/overrides/javoscript/laravel-macroable-models/src/MacroableModels.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/Events/Validated.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/SessionGuard.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Cache/Repository.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Config/Repository.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Container/BoundMethod.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Container/Container.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Cookie/CookieValuePrefix.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Factory.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Model.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Filesystem/Filesystem.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/PackageManifest.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Http/Request.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Mail/TransportManager.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/Paginator.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Queue/Listener.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/Controller.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteCollection.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteSignatureParameters.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/Router.php create mode 100755 freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/UrlGenerator.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Session/FileSessionHandler.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Carbon.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Collection.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Fluent.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Support/MessageBag.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Optional.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Str.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Support/ViewErrorBag.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Validation/Concerns/FormatsMessages.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/View/Compilers/Compiler.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesLayouts.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/View/Concerns/ManagesLayouts.php create mode 100644 freescout-dist/overrides/laravel/framework/src/Illuminate/View/View.php create mode 100644 freescout-dist/overrides/lord/laroute/src/Routes/Collection.php create mode 100644 freescout-dist/overrides/maximebf/debugbar/src/DebugBar/DataFormatter/DataFormatter.php create mode 100644 freescout-dist/overrides/maximebf/debugbar/src/DebugBar/DebugBar.php create mode 100644 freescout-dist/overrides/maximebf/debugbar/src/DebugBar/JavascriptRenderer.php create mode 100644 freescout-dist/overrides/natxet/cssmin/src/CssMin.php create mode 100644 freescout-dist/overrides/nesbot/carbon/src/Carbon/Carbon.php create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/command.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/composer.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/controller-plain.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/controller.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/event.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/factory.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/job-queued.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/job.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/json.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/listener-duck.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/listener-queued-duck.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/listener-queued.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/listener.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/mail.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/middleware.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/add.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/create.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/delete.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/drop.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/plain.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/model.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/notification.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/policy.plain.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/provider.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/request.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/resource-collection.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/resource.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/route-provider.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/routes.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/rule.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/scaffold/config.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/scaffold/provider.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/seeder.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/start.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/unit-test.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/views/index.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/views/master.stub create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Json.php create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Module.php create mode 100644 freescout-dist/overrides/nwidart/laravel-modules/src/Repository.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Controllers/EnvironmentController.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Controllers/FinalController.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Events/EnvironmentSaved.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/DatabaseManager.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/EnvironmentManager.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/FinalInstallManager.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/InstalledFileManager.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/PermissionsChecker.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/RequirementsChecker.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Middleware/canInstall.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Providers/LaravelInstallerServiceProvider.php create mode 100644 freescout-dist/overrides/rachidlaasri/laravel-installer/src/Routes/web.php create mode 100644 freescout-dist/overrides/ramsey/uuid/src/Uuid.php create mode 100644 freescout-dist/overrides/rap2hpoutre/laravel-log-viewer/src/Rap2hpoutre/LaravelLogViewer/LaravelLogViewer.php create mode 100644 freescout-dist/overrides/spatie/string/src/Str.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/EmbeddedFile.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Message.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SendmailTransport.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailTransport.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php create mode 100644 freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php create mode 100644 freescout-dist/overrides/symfony/console/Descriptor/TextDescriptor.php create mode 100644 freescout-dist/overrides/symfony/console/Helper/Helper.php create mode 100644 freescout-dist/overrides/symfony/console/Helper/HelperSet.php create mode 100644 freescout-dist/overrides/symfony/css-selector/XPath/Extension/NodeExtension.php create mode 100644 freescout-dist/overrides/symfony/debug/ExceptionHandler.php create mode 100644 freescout-dist/overrides/symfony/finder/Finder.php create mode 100644 freescout-dist/overrides/symfony/finder/Iterator/DateRangeFilterIterator.php create mode 100644 freescout-dist/overrides/symfony/finder/Iterator/DepthRangeFilterIterator.php create mode 100644 freescout-dist/overrides/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php create mode 100644 freescout-dist/overrides/symfony/finder/Iterator/FileTypeFilterIterator.php create mode 100644 freescout-dist/overrides/symfony/finder/Iterator/FilenameFilterIterator.php create mode 100644 freescout-dist/overrides/symfony/finder/Iterator/FilterIterator.php create mode 100644 freescout-dist/overrides/symfony/finder/Iterator/PathFilterIterator.php create mode 100644 freescout-dist/overrides/symfony/finder/Iterator/RecursiveDirectoryIterator.php create mode 100644 freescout-dist/overrides/symfony/finder/Iterator/SortableIterator.php create mode 100644 freescout-dist/overrides/symfony/http-foundation/AcceptHeader.php create mode 100644 freescout-dist/overrides/symfony/http-foundation/Cookie.php create mode 100644 freescout-dist/overrides/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php create mode 100644 freescout-dist/overrides/symfony/http-foundation/FileBag.php create mode 100644 freescout-dist/overrides/symfony/http-foundation/HeaderBag.php create mode 100644 freescout-dist/overrides/symfony/http-foundation/ParameterBag.php create mode 100644 freescout-dist/overrides/symfony/http-foundation/Request.php create mode 100644 freescout-dist/overrides/symfony/http-foundation/Response.php create mode 100644 freescout-dist/overrides/symfony/http-foundation/ResponseHeaderBag.php create mode 100644 freescout-dist/overrides/symfony/http-kernel/Exception/HttpException.php create mode 100644 freescout-dist/overrides/symfony/http-kernel/HttpCache/Store.php create mode 100644 freescout-dist/overrides/symfony/http-kernel/UriSigner.php create mode 100644 freescout-dist/overrides/symfony/process/Process.php create mode 100644 freescout-dist/overrides/symfony/routing/CompiledRoute.php create mode 100644 freescout-dist/overrides/symfony/routing/Route.php create mode 100644 freescout-dist/overrides/symfony/var-dumper/Cloner/Data.php create mode 100644 freescout-dist/overrides/symfony/var-dumper/Cloner/Stub.php create mode 100644 freescout-dist/overrides/symfony/var-dumper/Dumper/HtmlDumper.php create mode 100644 freescout-dist/overrides/tormjens/eventy/src/Action.php create mode 100644 freescout-dist/overrides/tormjens/eventy/src/Event.php create mode 100644 freescout-dist/overrides/tormjens/eventy/src/Filter.php create mode 100644 freescout-dist/overrides/vlucas/phpdotenv/src/Loader.php create mode 100644 freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Attachment.php create mode 100644 freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Client.php create mode 100644 freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Message.php create mode 100644 freescout-dist/overrides/webklex/php-imap/src/Attachment.php create mode 100644 freescout-dist/overrides/webklex/php-imap/src/Connection/Protocols/ImapProtocol.php create mode 100644 freescout-dist/overrides/webklex/php-imap/src/Header.php create mode 100644 freescout-dist/overrides/webklex/php-imap/src/Message.php create mode 100644 freescout-dist/overrides/webklex/php-imap/src/Structure.php create mode 100644 freescout-dist/package.json create mode 100644 freescout-dist/phpcs.xml create mode 100644 freescout-dist/phpunit.xml create mode 100644 freescout-dist/public/.htaccess create mode 100644 freescout-dist/public/android-chrome-192x192.png create mode 100644 freescout-dist/public/android-chrome-256x256.png create mode 100644 freescout-dist/public/apple-touch-icon.png create mode 100644 freescout-dist/public/browserconfig.xml create mode 100644 freescout-dist/public/css/bootstrap-rtl.css create mode 100644 freescout-dist/public/css/bootstrap.css create mode 100644 freescout-dist/public/css/fonts.css create mode 100644 freescout-dist/public/css/magic-check.css create mode 100644 freescout-dist/public/css/select2/select2.css create mode 100644 freescout-dist/public/css/select2/select2.min.css create mode 100644 freescout-dist/public/css/style-rtl.css create mode 100644 freescout-dist/public/css/style.css create mode 100644 freescout-dist/public/favicon.gif create mode 100644 freescout-dist/public/favicon.ico create mode 100644 freescout-dist/public/favicon.png create mode 100644 freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.eot create mode 100644 freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.svg create mode 100644 freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.ttf create mode 100644 freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.woff create mode 100644 freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.woff2 create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.eot create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.svg create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.ttf create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.woff create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-BoldItalic-webfont.eot create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-BoldItalic-webfont.svg create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-BoldItalic-webfont.ttf create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-BoldItalic-webfont.woff create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Italic-webfont.eot create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Italic-webfont.svg create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Italic-webfont.ttf create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Italic-webfont.woff create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Regular-webfont.eot create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Regular-webfont.svg create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Regular-webfont.ttf create mode 100644 freescout-dist/public/fonts/liberation-sans/LiberationSans-Regular-webfont.woff create mode 100644 freescout-dist/public/fonts/yekan/Yekan.eot create mode 100644 freescout-dist/public/fonts/yekan/Yekan.otf create mode 100644 freescout-dist/public/fonts/yekan/Yekan.ttf create mode 100644 freescout-dist/public/fonts/yekan/Yekan.woff create mode 100644 freescout-dist/public/fonts/yekan/Yekan.woff2 create mode 100644 freescout-dist/public/img/banner.png create mode 100644 freescout-dist/public/img/default-avatar.png create mode 100644 freescout-dist/public/img/default-module.png create mode 100644 freescout-dist/public/img/enable-push.png create mode 100644 freescout-dist/public/img/loader-grey.gif create mode 100644 freescout-dist/public/img/loader-main.gif create mode 100644 freescout-dist/public/img/loader-tiny.gif create mode 100644 freescout-dist/public/img/logo-300.png create mode 100644 freescout-dist/public/img/logo-600.png create mode 100644 freescout-dist/public/img/logo-brand.svg create mode 100644 freescout-dist/public/img/logo-icon-150.png create mode 100644 freescout-dist/public/img/logo-icon-white-300.png create mode 100644 freescout-dist/public/index.php create mode 100644 freescout-dist/public/install.php create mode 100644 freescout-dist/public/installer/css/fontawesome.css create mode 100644 freescout-dist/public/installer/css/style.css create mode 100644 freescout-dist/public/installer/css/style.css.map create mode 100644 freescout-dist/public/installer/css/style.min.css create mode 100644 freescout-dist/public/installer/css/style.min.css.map create mode 100644 freescout-dist/public/installer/fonts/FontAwesome.otf create mode 100644 freescout-dist/public/installer/fonts/fontawesome-webfont.eot create mode 100644 freescout-dist/public/installer/fonts/fontawesome-webfont.svg create mode 100644 freescout-dist/public/installer/fonts/fontawesome-webfont.ttf create mode 100644 freescout-dist/public/installer/fonts/fontawesome-webfont.woff create mode 100644 freescout-dist/public/installer/fonts/fontawesome-webfont.woff2 create mode 100644 freescout-dist/public/installer/fonts/ionicons.eot create mode 100644 freescout-dist/public/installer/fonts/ionicons.svg create mode 100644 freescout-dist/public/installer/fonts/ionicons.ttf create mode 100644 freescout-dist/public/installer/fonts/ionicons.woff create mode 100644 freescout-dist/public/installer/img/pattern.png create mode 100644 freescout-dist/public/js/bootstrap.js create mode 100644 freescout-dist/public/js/bootstrap3-editable/css/bootstrap-editable.css create mode 100644 freescout-dist/public/js/bootstrap3-editable/img/clear.png create mode 100644 freescout-dist/public/js/bootstrap3-editable/img/loading.gif create mode 100644 freescout-dist/public/js/bootstrap3-editable/js/bootstrap-editable.js create mode 100644 freescout-dist/public/js/bootstrap3-editable/js/bootstrap-editable.min.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/dataTables.bootstrap.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/dataTables.bootstrap.min.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/dataTables.bootstrap4.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/dataTables.bootstrap4.min.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/dataTables.foundation.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/dataTables.foundation.min.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/dataTables.jqueryui.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/dataTables.jqueryui.min.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/dataTables.semanticui.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/dataTables.semanticui.min.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/jquery.dataTables.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/css/jquery.dataTables.min.css create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/images/sort_asc.png create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/images/sort_asc_disabled.png create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/images/sort_both.png create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/images/sort_desc.png create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/images/sort_desc_disabled.png create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/dataTables.bootstrap.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/dataTables.bootstrap.min.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/dataTables.bootstrap4.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/dataTables.bootstrap4.min.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/dataTables.foundation.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/dataTables.foundation.min.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/dataTables.jqueryui.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/dataTables.jqueryui.min.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/dataTables.semanticui.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/dataTables.semanticui.min.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/jquery.dataTables.js create mode 100644 freescout-dist/public/js/datatables/DataTables-1.10.18/js/jquery.dataTables.min.js create mode 100644 freescout-dist/public/js/datatables/datatables.css create mode 100644 freescout-dist/public/js/datatables/datatables.js create mode 100644 freescout-dist/public/js/datatables/datatables.min.css create mode 100644 freescout-dist/public/js/datatables/datatables.min.js create mode 100644 freescout-dist/public/js/featherlight/featherlight.gallery.min.css create mode 100644 freescout-dist/public/js/featherlight/featherlight.gallery.min.js create mode 100644 freescout-dist/public/js/featherlight/featherlight.min.css create mode 100644 freescout-dist/public/js/featherlight/featherlight.min.js create mode 100644 freescout-dist/public/js/flatpickr/flatpickr.css create mode 100644 freescout-dist/public/js/flatpickr/flatpickr.js create mode 100644 freescout-dist/public/js/flatpickr/flatpickr.min.css create mode 100644 freescout-dist/public/js/flatpickr/flatpickr.min.js create mode 100644 freescout-dist/public/js/flatpickr/ie.css create mode 100644 freescout-dist/public/js/flatpickr/l10n/ar.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/at.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/az.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/be.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/bg.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/bn.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/bs.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/cat.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/cs.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/cy.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/da.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/de.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/default.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/en.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/eo.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/es.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/et.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/fa.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/fi.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/fo.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/fr.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/ga.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/gr.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/he.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/hi.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/hr.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/hu.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/id.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/is.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/it.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/ja.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/km.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/ko.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/kz.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/lt.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/lv.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/mk.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/mn.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/ms.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/my.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/nl.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/no.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/pa.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/pl.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/pt-br.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/pt-pt.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/ro.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/ru.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/si.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/sk.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/sl.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/sq.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/sr-cyr.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/sr.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/sv.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/th.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/tr.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/uk.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/vn.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/zh-cn.js create mode 100644 freescout-dist/public/js/flatpickr/l10n/zh-tw.js create mode 100644 freescout-dist/public/js/flatpickr/plugins/confirmDate.css create mode 100644 freescout-dist/public/js/flatpickr/plugins/confirmDate.js create mode 100644 freescout-dist/public/js/flatpickr/plugins/labelPlugin.js create mode 100644 freescout-dist/public/js/flatpickr/plugins/minMaxTimePlugin.js create mode 100644 freescout-dist/public/js/flatpickr/plugins/rangePlugin.js create mode 100644 freescout-dist/public/js/flatpickr/plugins/scrollPlugin.js create mode 100644 freescout-dist/public/js/flatpickr/plugins/style.css create mode 100644 freescout-dist/public/js/flatpickr/plugins/weekSelect.js create mode 100644 freescout-dist/public/js/html5sortable.js create mode 100644 freescout-dist/public/js/jquery.js create mode 100644 freescout-dist/public/js/jquery.titlealert.js create mode 100644 freescout-dist/public/js/lang.js create mode 100644 freescout-dist/public/js/laroute.js create mode 100755 freescout-dist/public/js/main.js create mode 100644 freescout-dist/public/js/parsley/i18n/al.js create mode 100644 freescout-dist/public/js/parsley/i18n/ar.js create mode 100644 freescout-dist/public/js/parsley/i18n/bg.js create mode 100644 freescout-dist/public/js/parsley/i18n/ca.js create mode 100644 freescout-dist/public/js/parsley/i18n/cs.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/cs.js create mode 100644 freescout-dist/public/js/parsley/i18n/da.js create mode 100644 freescout-dist/public/js/parsley/i18n/de.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/de.js create mode 100644 freescout-dist/public/js/parsley/i18n/el.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/el.js create mode 100644 freescout-dist/public/js/parsley/i18n/en.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/en.js create mode 100644 freescout-dist/public/js/parsley/i18n/es.js create mode 100644 freescout-dist/public/js/parsley/i18n/et.js create mode 100644 freescout-dist/public/js/parsley/i18n/eu.js create mode 100644 freescout-dist/public/js/parsley/i18n/fa.js create mode 100644 freescout-dist/public/js/parsley/i18n/fi.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/fi.js create mode 100644 freescout-dist/public/js/parsley/i18n/fr.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/fr.js create mode 100644 freescout-dist/public/js/parsley/i18n/he.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/he.js create mode 100644 freescout-dist/public/js/parsley/i18n/hr.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/hr.js create mode 100644 freescout-dist/public/js/parsley/i18n/hu.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/hu.js create mode 100644 freescout-dist/public/js/parsley/i18n/id.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/id.js create mode 100644 freescout-dist/public/js/parsley/i18n/it.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/it.js create mode 100644 freescout-dist/public/js/parsley/i18n/ja.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/ja.js create mode 100644 freescout-dist/public/js/parsley/i18n/ko.js create mode 100644 freescout-dist/public/js/parsley/i18n/lt.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/lt.js create mode 100644 freescout-dist/public/js/parsley/i18n/lv.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/lv.js create mode 100644 freescout-dist/public/js/parsley/i18n/ms.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/ms.js create mode 100644 freescout-dist/public/js/parsley/i18n/nl.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/nl.js create mode 100644 freescout-dist/public/js/parsley/i18n/no.js create mode 100644 freescout-dist/public/js/parsley/i18n/pl.js create mode 100644 freescout-dist/public/js/parsley/i18n/pt-br.js create mode 100644 freescout-dist/public/js/parsley/i18n/pt-pt.js create mode 100644 freescout-dist/public/js/parsley/i18n/ro.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/ro.js create mode 100644 freescout-dist/public/js/parsley/i18n/ru.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/ru.js create mode 100644 freescout-dist/public/js/parsley/i18n/sk.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/sk.js create mode 100644 freescout-dist/public/js/parsley/i18n/sl.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/sl.js create mode 100644 freescout-dist/public/js/parsley/i18n/sq.js create mode 100644 freescout-dist/public/js/parsley/i18n/sr.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/sr.js create mode 100644 freescout-dist/public/js/parsley/i18n/sv.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/sv.js create mode 100644 freescout-dist/public/js/parsley/i18n/th.js create mode 100644 freescout-dist/public/js/parsley/i18n/tk.js create mode 100644 freescout-dist/public/js/parsley/i18n/tr.js create mode 100644 freescout-dist/public/js/parsley/i18n/ua.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/ua.js create mode 100644 freescout-dist/public/js/parsley/i18n/uk.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/uk.js create mode 100644 freescout-dist/public/js/parsley/i18n/zh-cn.extra.js create mode 100644 freescout-dist/public/js/parsley/i18n/zh-cn.js create mode 100644 freescout-dist/public/js/parsley/i18n/zh-tw.js create mode 100644 freescout-dist/public/js/parsley/parsley.js create mode 100644 freescout-dist/public/js/parsley/parsley.js.map create mode 100644 freescout-dist/public/js/parsley/parsley.min.js create mode 100644 freescout-dist/public/js/parsley/parsley.min.js.map create mode 100644 freescout-dist/public/js/polycast/polycast.js create mode 100644 freescout-dist/public/js/polycast/polycast.min.js create mode 100644 freescout-dist/public/js/push/push.js create mode 100644 freescout-dist/public/js/push/push.min.js create mode 100644 freescout-dist/public/js/push/serviceWorker.min.js create mode 100644 freescout-dist/public/js/select2/i18n/af.js create mode 100644 freescout-dist/public/js/select2/i18n/ar.js create mode 100644 freescout-dist/public/js/select2/i18n/az.js create mode 100644 freescout-dist/public/js/select2/i18n/bg.js create mode 100644 freescout-dist/public/js/select2/i18n/bs.js create mode 100644 freescout-dist/public/js/select2/i18n/ca.js create mode 100644 freescout-dist/public/js/select2/i18n/cs.js create mode 100644 freescout-dist/public/js/select2/i18n/da.js create mode 100644 freescout-dist/public/js/select2/i18n/de.js create mode 100644 freescout-dist/public/js/select2/i18n/dsb.js create mode 100644 freescout-dist/public/js/select2/i18n/el.js create mode 100644 freescout-dist/public/js/select2/i18n/en.js create mode 100644 freescout-dist/public/js/select2/i18n/es.js create mode 100644 freescout-dist/public/js/select2/i18n/et.js create mode 100644 freescout-dist/public/js/select2/i18n/eu.js create mode 100644 freescout-dist/public/js/select2/i18n/fa.js create mode 100644 freescout-dist/public/js/select2/i18n/fi.js create mode 100644 freescout-dist/public/js/select2/i18n/fr.js create mode 100644 freescout-dist/public/js/select2/i18n/gl.js create mode 100644 freescout-dist/public/js/select2/i18n/he.js create mode 100644 freescout-dist/public/js/select2/i18n/hi.js create mode 100644 freescout-dist/public/js/select2/i18n/hr.js create mode 100644 freescout-dist/public/js/select2/i18n/hsb.js create mode 100644 freescout-dist/public/js/select2/i18n/hu.js create mode 100644 freescout-dist/public/js/select2/i18n/hy.js create mode 100644 freescout-dist/public/js/select2/i18n/id.js create mode 100644 freescout-dist/public/js/select2/i18n/is.js create mode 100644 freescout-dist/public/js/select2/i18n/it.js create mode 100644 freescout-dist/public/js/select2/i18n/ja.js create mode 100644 freescout-dist/public/js/select2/i18n/km.js create mode 100644 freescout-dist/public/js/select2/i18n/ko.js create mode 100644 freescout-dist/public/js/select2/i18n/lt.js create mode 100644 freescout-dist/public/js/select2/i18n/lv.js create mode 100644 freescout-dist/public/js/select2/i18n/mk.js create mode 100644 freescout-dist/public/js/select2/i18n/ms.js create mode 100644 freescout-dist/public/js/select2/i18n/nb.js create mode 100644 freescout-dist/public/js/select2/i18n/nl.js create mode 100644 freescout-dist/public/js/select2/i18n/pl.js create mode 100644 freescout-dist/public/js/select2/i18n/ps.js create mode 100644 freescout-dist/public/js/select2/i18n/pt-BR.js create mode 100644 freescout-dist/public/js/select2/i18n/pt.js create mode 100644 freescout-dist/public/js/select2/i18n/ro.js create mode 100644 freescout-dist/public/js/select2/i18n/ru.js create mode 100644 freescout-dist/public/js/select2/i18n/sk.js create mode 100644 freescout-dist/public/js/select2/i18n/sl.js create mode 100644 freescout-dist/public/js/select2/i18n/sr-Cyrl.js create mode 100644 freescout-dist/public/js/select2/i18n/sr.js create mode 100644 freescout-dist/public/js/select2/i18n/sv.js create mode 100644 freescout-dist/public/js/select2/i18n/th.js create mode 100644 freescout-dist/public/js/select2/i18n/tr.js create mode 100644 freescout-dist/public/js/select2/i18n/uk.js create mode 100644 freescout-dist/public/js/select2/i18n/vi.js create mode 100644 freescout-dist/public/js/select2/i18n/zh-CN.js create mode 100644 freescout-dist/public/js/select2/i18n/zh-TW.js create mode 100644 freescout-dist/public/js/select2/select2.full.js create mode 100644 freescout-dist/public/js/select2/select2.full.min.js create mode 100644 freescout-dist/public/js/select2/select2.js create mode 100644 freescout-dist/public/js/select2/select2.min.js create mode 100644 freescout-dist/public/js/summernote/font/summernote.eot create mode 100644 freescout-dist/public/js/summernote/font/summernote.ttf create mode 100644 freescout-dist/public/js/summernote/font/summernote.woff create mode 100644 freescout-dist/public/js/summernote/lang/summernote-ar-AR.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-bg-BG.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-ca-ES.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-cs-CZ.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-da-DK.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-de-DE.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-el-GR.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-es-ES.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-es-EU.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-fa-IR.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-fi-FI.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-fr-FR.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-gl-ES.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-he-IL.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-hr-HR.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-hu-HU.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-id-ID.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-it-IT.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-ja-JP.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-ko-KR.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-lt-LT.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-lt-LV.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-mn-MN.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-nb-NO.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-nl-NL.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-pl-PL.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-pt-BR.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-pt-PT.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-ro-RO.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-ru-RU.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-sk-SK.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-sl-SI.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-sr-RS-Latin.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-sr-RS.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-sv-SE.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-ta-IN.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-th-TH.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-tr-TR.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-uk-UA.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-vi-VN.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-zh-CN.js create mode 100644 freescout-dist/public/js/summernote/lang/summernote-zh-TW.js create mode 100644 freescout-dist/public/js/summernote/plugin/databasic/summernote-ext-databasic.css create mode 100644 freescout-dist/public/js/summernote/plugin/databasic/summernote-ext-databasic.js create mode 100644 freescout-dist/public/js/summernote/plugin/hello/summernote-ext-hello.js create mode 100644 freescout-dist/public/js/summernote/plugin/specialchars/summernote-ext-specialchars.js create mode 100644 freescout-dist/public/js/summernote/summernote.css create mode 100644 freescout-dist/public/js/summernote/summernote.js create mode 100644 freescout-dist/public/js/summernote/summernote.min.js create mode 100644 freescout-dist/public/js/taphold.js create mode 100644 freescout-dist/public/mstile-150x150.png create mode 100644 freescout-dist/public/robots.txt create mode 100644 freescout-dist/public/safari-pinned-tab.svg create mode 100644 freescout-dist/public/site.webmanifest create mode 100644 freescout-dist/public/tools.php create mode 100644 freescout-dist/resources/assets/js/app.js create mode 100644 freescout-dist/resources/assets/js/bootstrap.js create mode 100644 freescout-dist/resources/assets/js/components/ExampleComponent.vue create mode 100644 freescout-dist/resources/assets/js/laroute.js create mode 100644 freescout-dist/resources/assets/js/laroute_module.js create mode 100644 freescout-dist/resources/assets/sass/_variables.scss create mode 100644 freescout-dist/resources/assets/sass/app.scss create mode 100644 freescout-dist/resources/lang/cs.json create mode 100644 freescout-dist/resources/lang/da.json create mode 100644 freescout-dist/resources/lang/de.json create mode 100644 freescout-dist/resources/lang/de/auth.php create mode 100644 freescout-dist/resources/lang/de/installer_messages.php create mode 100644 freescout-dist/resources/lang/de/passwords.php create mode 100644 freescout-dist/resources/lang/de/validation.php create mode 100644 freescout-dist/resources/lang/en/auth.php create mode 100644 freescout-dist/resources/lang/en/installer_messages.php create mode 100644 freescout-dist/resources/lang/en/passwords.php create mode 100644 freescout-dist/resources/lang/en/validation.php create mode 100644 freescout-dist/resources/lang/es.json create mode 100644 freescout-dist/resources/lang/fa.json create mode 100644 freescout-dist/resources/lang/fa/auth.php create mode 100644 freescout-dist/resources/lang/fa/installer_messages.php create mode 100644 freescout-dist/resources/lang/fa/passwords.php create mode 100644 freescout-dist/resources/lang/fa/validation.php create mode 100644 freescout-dist/resources/lang/fi.json create mode 100644 freescout-dist/resources/lang/fr.json create mode 100644 freescout-dist/resources/lang/fr/auth.php create mode 100644 freescout-dist/resources/lang/fr/pagination.php create mode 100644 freescout-dist/resources/lang/fr/passwords.php create mode 100644 freescout-dist/resources/lang/fr/validation.php create mode 100644 freescout-dist/resources/lang/hr.json create mode 100644 freescout-dist/resources/lang/it.json create mode 100644 freescout-dist/resources/lang/ja.json create mode 100644 freescout-dist/resources/lang/ko.json create mode 100644 freescout-dist/resources/lang/ko/auth.php create mode 100644 freescout-dist/resources/lang/ko/installer_messages.php create mode 100644 freescout-dist/resources/lang/ko/passwords.php create mode 100644 freescout-dist/resources/lang/ko/validation.php create mode 100644 freescout-dist/resources/lang/nl.json create mode 100644 freescout-dist/resources/lang/nl/auth.php create mode 100644 freescout-dist/resources/lang/nl/installer_messages.php create mode 100644 freescout-dist/resources/lang/nl/passwords.php create mode 100644 freescout-dist/resources/lang/nl/validation.php create mode 100644 freescout-dist/resources/lang/no.json create mode 100644 freescout-dist/resources/lang/pl.json create mode 100644 freescout-dist/resources/lang/pt-BR.json create mode 100644 freescout-dist/resources/lang/pt-BR/auth.php create mode 100644 freescout-dist/resources/lang/pt-BR/pagination.php create mode 100644 freescout-dist/resources/lang/pt-BR/passwords.php create mode 100644 freescout-dist/resources/lang/pt-BR/validation.php create mode 100644 freescout-dist/resources/lang/pt-PT.json create mode 100644 freescout-dist/resources/lang/pt-PT/auth.php create mode 100644 freescout-dist/resources/lang/pt-PT/pagination.php create mode 100644 freescout-dist/resources/lang/pt-PT/passwords.php create mode 100644 freescout-dist/resources/lang/pt-PT/validation.php create mode 100644 freescout-dist/resources/lang/ru.json create mode 100644 freescout-dist/resources/lang/sk.json create mode 100644 freescout-dist/resources/lang/sv.json create mode 100644 freescout-dist/resources/lang/zh-CN.json create mode 100644 freescout-dist/resources/lang/zh-CN/auth.php create mode 100644 freescout-dist/resources/lang/zh-CN/pagination.php create mode 100644 freescout-dist/resources/lang/zh-CN/passwords.php create mode 100644 freescout-dist/resources/lang/zh-CN/validation-inline.php create mode 100644 freescout-dist/resources/lang/zh-CN/validation.php create mode 100644 freescout-dist/resources/views/auth/banner.blade.php create mode 100644 freescout-dist/resources/views/auth/login.blade.php create mode 100644 freescout-dist/resources/views/auth/passwords/email.blade.php create mode 100644 freescout-dist/resources/views/auth/passwords/reset.blade.php create mode 100644 freescout-dist/resources/views/auth/register.blade.php create mode 100644 freescout-dist/resources/views/conversations/ajax_html/change_customer.blade.php create mode 100644 freescout-dist/resources/views/conversations/ajax_html/default_redirect.blade.php create mode 100644 freescout-dist/resources/views/conversations/ajax_html/merge_conv.blade.php create mode 100644 freescout-dist/resources/views/conversations/ajax_html/move_conv.blade.php create mode 100644 freescout-dist/resources/views/conversations/ajax_html/send_log.blade.php create mode 100644 freescout-dist/resources/views/conversations/ajax_html/show_original.blade.php create mode 100644 freescout-dist/resources/views/conversations/chats.blade.php create mode 100644 freescout-dist/resources/views/conversations/conversations_pagination.blade.php create mode 100755 freescout-dist/resources/views/conversations/conversations_table.blade.php create mode 100644 freescout-dist/resources/views/conversations/create.blade.php create mode 100644 freescout-dist/resources/views/conversations/editor_bottom_toolbar.blade.php create mode 100644 freescout-dist/resources/views/conversations/partials/badges.blade.php create mode 100644 freescout-dist/resources/views/conversations/partials/bulk_actions.blade.php create mode 100644 freescout-dist/resources/views/conversations/partials/customer_sidebar.blade.php create mode 100644 freescout-dist/resources/views/conversations/partials/edit_thread.blade.php create mode 100644 freescout-dist/resources/views/conversations/partials/merge_search_result.blade.php create mode 100644 freescout-dist/resources/views/conversations/partials/prev_convs_short.blade.php create mode 100644 freescout-dist/resources/views/conversations/partials/settings_modal.blade.php create mode 100644 freescout-dist/resources/views/conversations/partials/thread.blade.php create mode 100644 freescout-dist/resources/views/conversations/partials/thread_attachments.blade.php create mode 100644 freescout-dist/resources/views/conversations/partials/threads.blade.php create mode 100644 freescout-dist/resources/views/conversations/search.blade.php create mode 100644 freescout-dist/resources/views/conversations/thread_by.blade.php create mode 100644 freescout-dist/resources/views/conversations/view.blade.php create mode 100644 freescout-dist/resources/views/customers/conversations.blade.php create mode 100644 freescout-dist/resources/views/customers/partials/customers_table.blade.php create mode 100644 freescout-dist/resources/views/customers/partials/edit_form.blade.php create mode 100644 freescout-dist/resources/views/customers/profile_menu.blade.php create mode 100644 freescout-dist/resources/views/customers/profile_snippet.blade.php create mode 100644 freescout-dist/resources/views/customers/profile_tabs.blade.php create mode 100644 freescout-dist/resources/views/customers/update.blade.php create mode 100644 freescout-dist/resources/views/emails/customer/auto_reply.blade.php create mode 100644 freescout-dist/resources/views/emails/customer/auto_reply_text.blade.php create mode 100644 freescout-dist/resources/views/emails/customer/reply_fancy.blade.php create mode 100644 freescout-dist/resources/views/emails/customer/reply_fancy_text.blade.php create mode 100644 freescout-dist/resources/views/emails/user/alert.blade.php create mode 100644 freescout-dist/resources/views/emails/user/email_reply_error.blade.php create mode 100644 freescout-dist/resources/views/emails/user/layouts/system.blade.php create mode 100644 freescout-dist/resources/views/emails/user/notification.blade.php create mode 100644 freescout-dist/resources/views/emails/user/notification_text.blade.php create mode 100644 freescout-dist/resources/views/emails/user/password_changed.blade.php create mode 100644 freescout-dist/resources/views/emails/user/password_changed_text.blade.php create mode 100644 freescout-dist/resources/views/emails/user/test.blade.php create mode 100644 freescout-dist/resources/views/emails/user/test_system.blade.php create mode 100644 freescout-dist/resources/views/emails/user/thread_by.blade.php create mode 100644 freescout-dist/resources/views/emails/user/user_invite.blade.php create mode 100644 freescout-dist/resources/views/emails/user/user_invite_text.blade.php create mode 100644 freescout-dist/resources/views/errors/403.blade.php create mode 100644 freescout-dist/resources/views/errors/404.blade.php create mode 100644 freescout-dist/resources/views/errors/500.blade.php create mode 100644 freescout-dist/resources/views/js/vars.blade.php create mode 100644 freescout-dist/resources/views/layouts/app.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/auto_reply.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/connection.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/connection_incoming.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/connection_menu.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/create.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/mailboxes.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/partials/chat_list.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/partials/folders.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/partials/mute_icon.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/permissions.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/settings_menu.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/sidebar_menu.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/sidebar_menu_view.blade.php create mode 100644 freescout-dist/resources/views/mailboxes/update.blade.php create mode 100755 freescout-dist/resources/views/mailboxes/view.blade.php create mode 100644 freescout-dist/resources/views/modules/modules.blade.php create mode 100644 freescout-dist/resources/views/modules/partials/invalid_symlinks.blade.php create mode 100644 freescout-dist/resources/views/modules/partials/module_card.blade.php create mode 100644 freescout-dist/resources/views/modules/sidebar_menu.blade.php create mode 100644 freescout-dist/resources/views/open/user_setup.blade.php create mode 100644 freescout-dist/resources/views/partials/calendar.blade.php create mode 100644 freescout-dist/resources/views/partials/editor.blade.php create mode 100644 freescout-dist/resources/views/partials/empty.blade.php create mode 100644 freescout-dist/resources/views/partials/field_error.blade.php create mode 100644 freescout-dist/resources/views/partials/flash_messages.blade.php create mode 100644 freescout-dist/resources/views/partials/floating_flash_messages.blade.php create mode 100644 freescout-dist/resources/views/partials/include_datepicker.blade.php create mode 100644 freescout-dist/resources/views/partials/locale_options.blade.php create mode 100644 freescout-dist/resources/views/partials/person_photo.blade.php create mode 100644 freescout-dist/resources/views/partials/sidebar_menu_toggle.blade.php create mode 100644 freescout-dist/resources/views/partials/timezone_options.blade.php create mode 100644 freescout-dist/resources/views/secure/dashboard.blade.php create mode 100644 freescout-dist/resources/views/secure/logs.blade.php create mode 100644 freescout-dist/resources/views/settings/alerts.blade.php create mode 100644 freescout-dist/resources/views/settings/emails.blade.php create mode 100644 freescout-dist/resources/views/settings/general.blade.php create mode 100644 freescout-dist/resources/views/settings/view.blade.php create mode 100644 freescout-dist/resources/views/system/sidebar_menu.blade.php create mode 100644 freescout-dist/resources/views/system/status.blade.php create mode 100644 freescout-dist/resources/views/system/tools.blade.php create mode 100644 freescout-dist/resources/views/users/create.blade.php create mode 100644 freescout-dist/resources/views/users/is_subscribed.blade.php create mode 100644 freescout-dist/resources/views/users/notifications.blade.php create mode 100644 freescout-dist/resources/views/users/partials/web_notifications.blade.php create mode 100644 freescout-dist/resources/views/users/password.blade.php create mode 100644 freescout-dist/resources/views/users/permissions.blade.php create mode 100644 freescout-dist/resources/views/users/profile.blade.php create mode 100644 freescout-dist/resources/views/users/sidebar_menu.blade.php create mode 100644 freescout-dist/resources/views/users/subscriptions_table.blade.php create mode 100644 freescout-dist/resources/views/users/users.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/environment-classic.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/environment-wizard.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/environment.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/finished.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/layouts/master-update.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/layouts/master.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/permissions.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/requirements.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/update/finished.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/update/overview.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/update/welcome.blade.php create mode 100644 freescout-dist/resources/views/vendor/installer/welcome.blade.php create mode 100644 freescout-dist/resources/views/vendor/laravel-log-viewer/log.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/button.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/footer.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/header.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/layout.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/message.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/panel.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/promotion.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/promotion/button.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/subcopy.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/table.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/html/themes/default.css create mode 100644 freescout-dist/resources/views/vendor/mail/markdown/button.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/markdown/footer.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/markdown/header.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/markdown/layout.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/markdown/message.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/markdown/panel.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/markdown/promotion.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/markdown/promotion/button.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/markdown/subcopy.blade.php create mode 100644 freescout-dist/resources/views/vendor/mail/markdown/table.blade.php create mode 100644 freescout-dist/resources/views/vendor/notifications/email.blade.php create mode 100644 freescout-dist/resources/views/vendor/translation-manager/.gitkeep create mode 100644 freescout-dist/resources/views/vendor/translation-manager/content.php create mode 100644 freescout-dist/resources/views/vendor/translation-manager/index.blade.php create mode 100644 freescout-dist/resources/views/vendor/translation-manager/index.php create mode 100644 freescout-dist/routes/channels.php create mode 100644 freescout-dist/routes/console.php create mode 100644 freescout-dist/routes/web.php create mode 100644 freescout-dist/server.php create mode 100755 freescout-dist/storage/app/.gitignore create mode 100755 freescout-dist/storage/app/public/.gitignore create mode 100644 freescout-dist/storage/debugbar/.gitignore create mode 100755 freescout-dist/storage/framework/.gitignore create mode 100755 freescout-dist/storage/framework/cache/.gitignore create mode 100755 freescout-dist/storage/framework/sessions/.gitignore create mode 100755 freescout-dist/storage/framework/testing/.gitignore create mode 100755 freescout-dist/storage/framework/views/.gitignore create mode 100755 freescout-dist/storage/logs/.gitignore create mode 100644 freescout-dist/tests/CreatesApplication.php create mode 100644 freescout-dist/tests/Feature/.gitkeep create mode 100644 freescout-dist/tests/TestCase.php create mode 100644 freescout-dist/tests/Unit/.keyfile create mode 100644 freescout-dist/tests/Unit/ConfigTest.php create mode 100644 freescout-dist/tests/Unit/ExampleTest.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/.travis.yml create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/config/config.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/helpers.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Dispatch.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Method.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Methods/ArraysMethods.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Methods/CollectionMethods.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Methods/FunctionsMethods.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Methods/NumberMethods.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Methods/ObjectMethods.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Methods/StringsMethods.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Parse.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Traits/Repository.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Types/Arrays.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Types/Functions.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Types/Number.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Types/Object.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Types/Strings.php create mode 100644 freescout-dist/vendor/anahkiasen/underscore-php/src/Underscore.php create mode 100644 freescout-dist/vendor/autoload.php create mode 100644 freescout-dist/vendor/axn/laravel-laroute/src/ServiceProvider.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/config/translation-manager.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/database/migrations/.gitkeep create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/database/migrations/2014_04_02_193005_create_translations_table.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/resources/views/.gitkeep create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/resources/views/index.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/src/Console/CleanCommand.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/src/Console/ExportCommand.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/src/Console/FindCommand.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/src/Console/ImportCommand.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/src/Console/ResetCommand.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/src/Events/TranslationsExportedEvent.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/src/ManagerServiceProvider.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/src/Models/Translation.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/src/TranslationServiceProvider.php create mode 100644 freescout-dist/vendor/barryvdh/laravel-translation-manager/src/Translator.php create mode 100755 freescout-dist/vendor/bin/doctrine-dbal create mode 100755 freescout-dist/vendor/bin/php-parse create mode 100755 freescout-dist/vendor/bin/psysh create mode 100644 freescout-dist/vendor/chumper/zipper/.travis.yml create mode 100644 freescout-dist/vendor/chumper/zipper/src/Chumper/Zipper/Facades/Zipper.php create mode 100644 freescout-dist/vendor/chumper/zipper/src/Chumper/Zipper/Repositories/RepositoryInterface.php create mode 100644 freescout-dist/vendor/chumper/zipper/src/Chumper/Zipper/Zipper.php create mode 100644 freescout-dist/vendor/chumper/zipper/src/Chumper/Zipper/ZipperServiceProvider.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/.styleci.yml create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/.travis.yml create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/config/self-update.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/resources/views/mails/update-available.blade.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/resources/views/self-update.blade.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/AbstractRepositoryType.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/Commands/CheckForUpdate.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/Contracts/SourceRepositoryTypeContract.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/Contracts/UpdaterContract.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/Events/HasWrongPermissions.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/Events/UpdateAvailable.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/Events/UpdateFailed.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/Events/UpdateSucceeded.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/Listeners/SendUpdateAvailableNotification.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/Listeners/SendUpdateSucceededNotification.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/SourceRepository.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/UpdaterFacade.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/UpdaterManager.php create mode 100644 freescout-dist/vendor/codedge/laravel-selfupdater/src/UpdaterServiceProvider.php create mode 100644 freescout-dist/vendor/composer/ClassLoader.php create mode 100644 freescout-dist/vendor/composer/InstalledVersions.php create mode 100644 freescout-dist/vendor/composer/autoload_classmap.php create mode 100644 freescout-dist/vendor/composer/autoload_files.php create mode 100644 freescout-dist/vendor/composer/autoload_namespaces.php create mode 100644 freescout-dist/vendor/composer/autoload_psr4.php create mode 100644 freescout-dist/vendor/composer/autoload_real.php create mode 100644 freescout-dist/vendor/composer/autoload_static.php create mode 100644 freescout-dist/vendor/composer/installed.json create mode 100644 freescout-dist/vendor/composer/installed.php create mode 100644 freescout-dist/vendor/devfactory/minify/.travis.yml create mode 100755 freescout-dist/vendor/devfactory/minify/spec/Devfactory/Minify/Providers/JavaScriptSpec.php create mode 100755 freescout-dist/vendor/devfactory/minify/spec/Devfactory/Minify/Providers/StyleSheetSpec.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Contracts/MinifyInterface.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Exceptions/CannotRemoveFileException.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Exceptions/CannotSaveFileException.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Exceptions/DirNotExistException.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Exceptions/DirNotWritableException.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Exceptions/FileNotExistException.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Exceptions/InvalidArgumentException.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Facades/MinifyFacade.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Minify.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/MinifyServiceProvider.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Providers/JavaScript.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/Providers/StyleSheet.php create mode 100644 freescout-dist/vendor/devfactory/minify/src/config/config.php create mode 100644 freescout-dist/vendor/dnoegel/php-xdg-base-dir/src/Xdg.php create mode 100644 freescout-dist/vendor/doctrine/cache/.coveralls.yml create mode 100644 freescout-dist/vendor/doctrine/cache/.travis.yml create mode 100644 freescout-dist/vendor/doctrine/cache/build.properties create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiPutCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php create mode 100644 freescout-dist/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php create mode 100755 freescout-dist/vendor/doctrine/dbal/bin/doctrine-dbal create mode 100644 freescout-dist/vendor/doctrine/dbal/bin/doctrine-dbal.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Abstraction/Result.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ArrayStatement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/CacheException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/QueryCacheProfile.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/ColumnCase.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Configuration.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/ConnectionException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connections/MasterSlaveConnection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connections/PrimaryReadReplicaConnection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractDB2Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractDriverException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractOracleDriver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractOracleDriver/EasyConnectString.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractPostgreSQLDriver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLAnywhereDriver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLServerDriver/Exception/PortWithoutHost.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractSQLiteDriver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DriverException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Exception.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ExceptionConverterDriver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/FetchUtils.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DataSourceName.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/CannotCopyStreamToStream.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/CannotCreateTemporaryFile.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/CannotWriteToTemporaryFile.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/ConnectionError.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/ConnectionFailed.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/PrepareFailed.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/StatementError.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/ConnectionError.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/ConnectionFailed.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/FailedReadingStreamOffset.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/InvalidOption.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/StatementError.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Exception/UnknownType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Exception/NonTerminatedStringLiteral.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Exception/SequenceDoesNotExist.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Exception/UnknownParameterIndex.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/MySQL/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/OCI/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/PgSQL/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/SQLSrv/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/SQLSrv/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/SQLSrv/Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/SQLite/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PingableConnection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Result.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ResultStatement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereConnection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Exception/Error.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/LastInsertId.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvConnection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ServerInfoAwareConnection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/StatementIterator.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/DriverManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/ConnectionEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/MysqlSessionInit.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/OracleSessionInit.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/SQLSessionInit.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableAddColumnEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableChangeColumnEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRemoveColumnEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRenameColumnEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaColumnDefinitionEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableColumnEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaDropTableEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaIndexDefinitionEventArgs.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Events.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ConnectionException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ConnectionLost.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ConstraintViolationException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DatabaseObjectExistsException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DatabaseObjectNotFoundException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DeadlockException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/DriverException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ForeignKeyConstraintViolationException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/InvalidArgumentException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/InvalidFieldNameException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/LockWaitTimeoutException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/NoKeyValue.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/NonUniqueFieldNameException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/NotNullConstraintViolationException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ReadOnlyException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/RetryableException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/ServerException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/SyntaxErrorException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/TableExistsException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/TableNotFoundException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Exception/UniqueConstraintViolationException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/FetchMode.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGenerator.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/LockMode.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/DebugStack.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/EchoSQLLogger.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/LoggerChain.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/SQLLogger.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/ParameterType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DB2Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DateIntervalUnit.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DB2Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DrizzleKeywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/KeywordList.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MariaDb102Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MsSQLKeywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQL57Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQL80Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQLKeywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/OracleKeywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL100Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL91Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL92Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQL94Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQLKeywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/ReservedKeywordsValidator.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere11Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere12Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere16Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhereKeywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2005Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2008Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2012Keywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServerKeywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLiteKeywords.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MariaDb1027Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySQL57Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySQL80Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/OraclePlatform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL100Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL91Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSQL94Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere11Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere12Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywhere16Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAzurePlatform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2005Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2008Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2012Platform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/TrimMode.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Connection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/OptimizeFlags.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/CompositeExpression.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/ExpressionBuilder.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtils.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtilsException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractAsset.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Column.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ColumnDiff.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Comparator.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Constraint.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ForeignKeyConstraint.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Identifier.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Index.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLAnywhereSchemaManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Schema.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaConfig.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaDiff.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Sequence.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/AbstractSchemaSynchronizer.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SchemaSynchronizer.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SingleDatabaseSynchronizer.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Table.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/TableDiff.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/View.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/AbstractVisitor.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/CreateSchemaSqlCollector.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/DropSchemaSqlCollector.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Graphviz.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/NamespaceVisitor.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/RemoveNamespacedAssets.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/SchemaDiffVisitor.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Visitor.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureFederationsSynchronizer.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/MultiTenantShardChoser.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/ShardChoser.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardManager.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardingException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/ConnectionNotFound.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/ConnectionProvider.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/ConnectionProvider/SingleConnectionProvider.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/ConsoleRunner.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Helper/ConnectionHelper.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Dumper.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/TransactionIsolationLevel.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ArrayType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/AsciiStringType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BigIntType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BinaryType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BlobType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BooleanType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ConversionException.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateImmutableType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateIntervalType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeImmutableType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeTzImmutableType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeTzType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DecimalType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/FloatType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/GuidType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/IntegerType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/JsonArrayType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/JsonType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/PhpDateTimeMappingType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/PhpIntegerMappingType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SimpleArrayType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SmallIntType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/StringType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TextType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TimeImmutableType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TimeType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Type.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TypeRegistry.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Types.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeImmutableType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeType.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/Version.php create mode 100644 freescout-dist/vendor/doctrine/dbal/lib/Doctrine/DBAL/VersionAwarePlatformDriver.php create mode 100644 freescout-dist/vendor/doctrine/dbal/psalm.xml create mode 100644 freescout-dist/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/Deprecation.php create mode 100644 freescout-dist/vendor/doctrine/deprecations/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php create mode 100644 freescout-dist/vendor/doctrine/deprecations/phpstan.neon create mode 100644 freescout-dist/vendor/doctrine/deprecations/psalm.xml create mode 100644 freescout-dist/vendor/doctrine/event-manager/psalm.xml create mode 100644 freescout-dist/vendor/doctrine/event-manager/src/EventArgs.php create mode 100644 freescout-dist/vendor/doctrine/event-manager/src/EventManager.php create mode 100644 freescout-dist/vendor/doctrine/event-manager/src/EventSubscriber.php create mode 100644 freescout-dist/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php create mode 100644 freescout-dist/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/EmailLexer.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/EmailParser.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/EmailValidator.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/AtextAfterCFWS.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/CRLFAtTheEnd.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/CRLFX2.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/CRNoLF.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/CharNotAllowed.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/CommaInDomain.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/ConsecutiveAt.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/ConsecutiveDot.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/DomainHyphened.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/DotAtEnd.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/DotAtStart.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/ExpectingAT.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/ExpectingATEXT.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/ExpectingCTEXT.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/ExpectingDTEXT.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/ExpectingDomainLiteralClose.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/ExpectingQPair.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/InvalidEmail.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/NoDNSRecord.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/NoDomainPart.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/NoLocalPart.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/UnclosedComment.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/UnclosedQuotedString.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Exception/UnopenedComment.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Parser/DomainPart.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Parser/LocalPart.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Parser/Parser.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Validation/DNSCheckValidation.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Validation/EmailValidation.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Validation/Error/RFCWarnings.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Validation/Error/SpoofEmail.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Validation/Exception/EmptyValidationList.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Validation/MultipleErrors.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Validation/MultipleValidationWithAnd.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Validation/NoRFCWarningsValidation.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Validation/RFCValidation.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Validation/SpoofCheckValidation.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/AddressLiteral.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/CFWSNearAt.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/CFWSWithFWS.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/Comment.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/DeprecatedComment.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/DomainLiteral.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/DomainTooLong.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/EmailTooLong.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/IPV6BadChar.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/IPV6ColonEnd.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/IPV6ColonStart.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/IPV6Deprecated.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/IPV6DoubleColon.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/IPV6GroupCount.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/IPV6MaxGroups.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/LabelTooLong.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/LocalTooLong.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/NoDNSMXRecord.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/ObsoleteDTEXT.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/QuotedPart.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/QuotedString.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/TLD.php create mode 100644 freescout-dist/vendor/egulias/email-validator/EmailValidator/Warning/Warning.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/ElementReference/Resolver.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/ElementReference/Subject.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/ElementReference/Usage.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/Exceptions/NestingException.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/Helper.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/Sanitizer.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/data/AllowedAttributes.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/data/AllowedTags.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/data/AttributeInterface.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/data/TagInterface.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/data/XPath.php create mode 100644 freescout-dist/vendor/enshrined/svg-sanitize/src/svg-scanner.php create mode 100644 freescout-dist/vendor/erusev/parsedown/Parsedown.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/CREDITS create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/INSTALL create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/INSTALL.fr.utf8 create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/NEWS create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/TODO create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/VERSION create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/WHATSNEW create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/WYSIWYG create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/extras/FSTools.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/extras/FSTools/File.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload-legacy.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/extras/README create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload-legacy.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier.composer.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier.func.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier.kses.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoreferrer.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php create mode 100755 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoopener.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoreferrer.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Length.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/PH5P.patch create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/PH5P.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/add-vimline.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/common.php create mode 100755 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/compile-doxygen.sh create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/config-scanner.php create mode 100755 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/flush-definition-cache.php create mode 100755 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/flush.sh create mode 100755 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/generate-entity-file.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/generate-includes.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/generate-ph5p-patch.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/generate-schema-cache.php create mode 100755 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/generate-standalone.php create mode 100755 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/merge-library.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/old-extract-schema.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/old-remove-require-once.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/old-remove-schema-def.php create mode 100755 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/regenerate-docs.sh create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/remove-trailing-whitespace.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/maintenance/rename-config.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/package.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/phpdoc.ini create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/Changelog create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/INSTALL create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/README create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/config.default.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier/README create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/init-config.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/settings.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/settings/form.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/plugins/phorum/settings/save.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/test-settings.sample.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/test-settings.travis.php create mode 100644 freescout-dist/vendor/ezyang/htmlpurifier/update-for-release create mode 100644 freescout-dist/vendor/fideloper/proxy/config/trustedproxy.php create mode 100644 freescout-dist/vendor/fideloper/proxy/src/TrustProxies.php create mode 100644 freescout-dist/vendor/fideloper/proxy/src/TrustedProxyServiceProvider.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/Dockerfile create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/ClientInterface.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Exception/ClientException.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Exception/ConnectException.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Exception/TransferException.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/HandlerStack.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/MessageFormatter.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Middleware.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Pool.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/RequestOptions.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/RetryMiddleware.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/TransferStats.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/UriTemplate.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/Utils.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/functions.php create mode 100644 freescout-dist/vendor/guzzlehttp/guzzle/src/functions_include.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/AggregateException.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/CancellationException.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/Coroutine.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/Create.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/Each.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/EachPromise.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/FulfilledPromise.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/Is.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/Promise.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/PromiseInterface.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/PromisorInterface.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/RejectedPromise.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/RejectionException.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/TaskQueue.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/TaskQueueInterface.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/Utils.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/functions.php create mode 100644 freescout-dist/vendor/guzzlehttp/promises/src/functions_include.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/AppendStream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/BufferStream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/CachingStream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/DroppingStream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/FnStream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/Header.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/InflateStream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/LimitStream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/Message.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/MessageTrait.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/MimeType.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/MultipartStream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/NoSeekStream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/PumpStream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/Query.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/Request.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/Response.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/Rfc7230.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/ServerRequest.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/Stream.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/StreamWrapper.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/UploadedFile.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/Uri.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/UriComparator.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/UriNormalizer.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/UriResolver.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/Utils.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/functions.php create mode 100644 freescout-dist/vendor/guzzlehttp/psr7/src/functions_include.php create mode 100644 freescout-dist/vendor/html2text/html2text/.travis.yml create mode 100644 freescout-dist/vendor/html2text/html2text/src/Html2Text.php create mode 100644 freescout-dist/vendor/jakub-onderka/php-console-color/.travis.yml create mode 100644 freescout-dist/vendor/jakub-onderka/php-console-color/example.php create mode 100644 freescout-dist/vendor/jakub-onderka/php-console-color/src/ConsoleColor.php create mode 100644 freescout-dist/vendor/jakub-onderka/php-console-color/src/InvalidStyleException.php create mode 100644 freescout-dist/vendor/jakub-onderka/php-console-highlighter/.travis.yml create mode 100644 freescout-dist/vendor/jakub-onderka/php-console-highlighter/src/Highlighter.php create mode 100644 freescout-dist/vendor/javoscript/laravel-macroable-models/src/Facades/MacroableModels.php create mode 100644 freescout-dist/vendor/javoscript/laravel-macroable-models/src/MacroableModelsServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Access/AuthorizationException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Access/HandlesAuthorization.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Access/Response.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Authenticatable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/AuthenticationException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Console/AuthMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Console/ClearResetsCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/controllers/HomeController.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/routes.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/passwords/email.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/passwords/reset.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/register.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/home.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/layouts/app.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/CreatesUserProviders.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Events/Attempting.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Events/Authenticated.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Events/Failed.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Events/Lockout.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Events/Login.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Events/Logout.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Events/PasswordReset.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Events/Registered.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/GenericUser.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/GuardHelpers.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Middleware/AuthenticateWithBasicAuth.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authorize.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Notifications/ResetPassword.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Passwords/CanResetPassword.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBroker.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordResetServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Passwords/TokenRepositoryInterface.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/Recaller.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/RequestGuard.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Auth/TokenGuard.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastController.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastEvent.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/LogBroadcaster.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/NullBroadcaster.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/RedisBroadcaster.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/Channel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/InteractsWithSockets.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/PresenceChannel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Broadcasting/PrivateChannel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Bus/BusServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Bus/Queueable.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/ApcStore.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/ApcWrapper.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/ArrayStore.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/Console/CacheTableCommand.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/Console/ForgetCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/Console/stubs/cache.stub create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/DatabaseStore.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/Events/CacheEvent.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/Events/CacheHit.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/Events/CacheMissed.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/Events/KeyForgotten.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/Events/KeyWritten.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/FileStore.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/Lock.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/MemcachedConnector.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/MemcachedLock.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/MemcachedStore.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/NullStore.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/RateLimiter.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/RedisLock.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/RedisStore.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/RedisTaggedCache.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/RetrievesMultipleKeys.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/TagSet.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/TaggableStore.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cache/TaggedCache.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Application.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Command.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/ConfirmableTrait.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/DetectsApplicationNamespace.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Events/ArtisanStarting.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Events/CommandFinished.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Events/CommandStarting.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/GeneratorCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/OutputStyle.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Parser.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Scheduling/CacheMutex.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Scheduling/CallbackEvent.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Scheduling/CommandBuilder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Scheduling/Event.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Scheduling/ManagesFrequencies.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Scheduling/Mutex.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Scheduling/Schedule.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Scheduling/ScheduleFinishCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Container/ContextualBindingBuilder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Container/EntryNotFoundException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/Access/Authorizable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/Access/Gate.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/Authenticatable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/CanResetPassword.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/Factory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/Guard.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/PasswordBroker.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/PasswordBrokerFactory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/StatefulGuard.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/SupportsBasicAuth.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Auth/UserProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Broadcasting/Broadcaster.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Broadcasting/Factory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Broadcasting/ShouldBroadcast.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Broadcasting/ShouldBroadcastNow.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Bus/Dispatcher.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Bus/QueueingDispatcher.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Cache/Factory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Cache/Lock.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Cache/LockProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Cache/LockTimeoutException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Cache/Repository.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Cache/Store.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Config/Repository.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Console/Application.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Console/Kernel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Container/BindingResolutionException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Container/Container.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Container/ContextualBindingBuilder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Cookie/Factory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Cookie/QueueingFactory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Database/ModelIdentifier.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Debug/ExceptionHandler.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Encryption/DecryptException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Encryption/EncryptException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Encryption/Encrypter.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Events/Dispatcher.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Filesystem/Cloud.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Filesystem/Factory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Filesystem/FileNotFoundException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Filesystem/Filesystem.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Hashing/Hasher.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Http/Kernel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Logging/Log.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Mail/MailQueue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Mail/Mailable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Mail/Mailer.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Notifications/Dispatcher.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Notifications/Factory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Pagination/LengthAwarePaginator.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Pagination/Paginator.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Hub.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Pipeline/Pipeline.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Queue/EntityNotFoundException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Queue/EntityResolver.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Factory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Job.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Monitor.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Queue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Queue/QueueableCollection.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Queue/QueueableEntity.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Queue/ShouldQueue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Redis/Factory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Redis/LimiterTimeoutException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Routing/BindingRegistrar.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Routing/Registrar.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Routing/ResponseFactory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Routing/UrlGenerator.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Routing/UrlRoutable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Session/Session.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Support/Arrayable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Support/Htmlable.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Support/Jsonable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Support/MessageBag.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Support/MessageProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Support/Renderable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Support/Responsable.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Translation/Loader.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Translation/Translator.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Validation/Factory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Validation/ImplicitRule.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Validation/Rule.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Validation/ValidatesWhenResolved.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/Validation/Validator.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/View/Engine.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/View/Factory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Contracts/View/View.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cookie/CookieJar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Cookie/CookieServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Capsule/Manager.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Concerns/BuildsQueries.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Connection.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/ConnectionInterface.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/ConnectionResolver.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/ConnectionResolverInterface.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectionFactory.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectorInterface.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Connectors/MySqlConnector.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Connectors/PostgresConnector.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Connectors/SQLiteConnector.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Connectors/SqlServerConnector.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Factories/FactoryMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Factories/stubs/factory.stub create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/BaseCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/FreshCommand.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/InstallCommand.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RefreshCommand.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/ResetCommand.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RollbackCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/StatusCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeedCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/SeederMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Console/Seeds/stubs/seeder.stub create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/DetectsDeadlocks.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/DetectsLostConnections.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Collection.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasTimestamps.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HidesAttributes.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/JsonEncodingException.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/MassAssignmentException.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/ModelNotFoundException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/QueueEntityResolver.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/RelationNotFoundException.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Concerns/SupportsDefaultModels.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOne.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOne.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphTo.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Pivot.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Scope.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletes.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletingScope.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Events/ConnectionEvent.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Events/QueryExecuted.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Events/StatementPrepared.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Events/TransactionBeginning.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Events/TransactionCommitted.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Events/TransactionRolledBack.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Grammar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/MigrationServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migration.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationCreator.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationRepositoryInterface.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/blank.stub create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/create.stub create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/update.stub create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/MySqlConnection.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/PostgresConnection.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Expression.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/JoinClause.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/JsonExpression.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/MySqlProcessor.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/PostgresProcessor.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/Processor.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/QueryException.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/SQLiteConnection.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/ChangeColumn.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/Grammar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/RenameColumn.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/MySqlBuilder.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/PostgresBuilder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/SQLiteBuilder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Schema/SqlServerBuilder.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/Seeder.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Database/SqlServerConnection.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Events/CallQueuedListener.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Filesystem/Cache.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemManager.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Application.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Auth/Access/Authorizable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Auth/Access/AuthorizesRequests.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Auth/RedirectsUsers.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Auth/RegistersUsers.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Auth/SendsPasswordResetEmails.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Auth/ThrottlesLogins.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Auth/User.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/BootProviders.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/SetRequestForConsole.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bus/Dispatchable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bus/DispatchesJobs.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bus/PendingChain.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Bus/PendingDispatch.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/ComposerScripts.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/AppNameCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ClearCompiledCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ClosureCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigCacheCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigClearCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConsoleMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/DownCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/EnvironmentCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/EventGenerateCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/EventMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ExceptionMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/JobMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/KeyGenerateCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ListenerMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/MailMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ModelMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/NotificationMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/OptimizeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/PackageDiscoverCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/PolicyMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/PresetCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/Bootstrap.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/None.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/Preset.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/React.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/Vue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/bootstrap-stubs/_variables.scss create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/bootstrap-stubs/app.scss create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/none-stubs/app.js create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/Example.js create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/app.js create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/webpack.mix.js create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/ExampleComponent.vue create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/app.js create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/webpack.mix.js create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ProviderMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/QueuedCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/RequestMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ResourceMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteCacheCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteClearCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteListCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/RuleMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/StorageLinkCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/TestMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/UpCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/VendorPublishCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/ViewClearCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/console.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/event-handler-queued.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/event-handler.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/event.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/exception-render-report.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/exception-render.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/exception-report.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/exception.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/job-queued.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/job.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/listener-duck.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/listener-queued-duck.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/listener-queued.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/listener.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/mail.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/markdown-mail.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/markdown-notification.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/markdown.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/model.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/notification.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/pivot.model.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/policy.plain.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/policy.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/provider.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/request.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/resource-collection.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/resource.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/routes.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/rule.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/test.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/unit-test.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/EnvironmentDetector.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Events/Dispatchable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Events/LocaleUpdated.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/419.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/429.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/500.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/503.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/layout.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Http/Events/RequestHandled.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Http/Exceptions/MaintenanceModeException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Inspiring.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ComposerServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ConsoleSupportServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Providers/FormRequestServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/AuthServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithAuthentication.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithConsole.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithContainer.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithDatabase.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithRedis.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithSession.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MocksApplicationServices.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Constraints/HasInDatabase.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Constraints/SoftDeletedInDatabase.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/DatabaseMigrations.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/DatabaseTransactions.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/HttpException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/RefreshDatabase.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/RefreshDatabaseState.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/WithFaker.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/WithoutEvents.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Testing/WithoutMiddleware.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/Validation/ValidatesRequests.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Foundation/stubs/facade.stub create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Concerns/InteractsWithContentTypes.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Concerns/InteractsWithFlashData.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Concerns/InteractsWithInput.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Exceptions/HttpResponseException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Exceptions/PostTooLargeException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/File.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/FileHelpers.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/JsonResponse.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Middleware/CheckResponseForModifications.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Middleware/FrameGuard.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/RedirectResponse.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/CollectsResources.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/DelegatesToResource.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/Json/AnonymousResourceCollection.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/Json/Resource.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/Json/ResourceCollection.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/Json/ResourceResponse.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/MergeValue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/MissingValue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Resources/PotentiallyMissing.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Response.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/ResponseTrait.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Testing/File.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Testing/FileFactory.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/Testing/MimeType.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Http/UploadedFile.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Log/Events/MessageLogged.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Log/LogServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Log/Writer.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Events/MessageSending.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Events/MessageSent.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/MailServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Mailable.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Markdown.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Message.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/PendingMail.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/SendQueuedMailable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Transport/ArrayTransport.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Transport/LogTransport.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Transport/MailgunTransport.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Transport/MandrillTransport.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Transport/SesTransport.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Transport/SparkPostTransport.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/Transport/Transport.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/button.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/footer.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/header.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/layout.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/message.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/panel.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/promotion.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/promotion/button.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/subcopy.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/table.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/themes/default.css create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/markdown/button.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/markdown/footer.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/markdown/header.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/markdown/layout.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/markdown/message.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/markdown/panel.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/markdown/promotion.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/markdown/promotion/button.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/markdown/subcopy.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Mail/resources/views/markdown/table.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Action.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/AnonymousNotifiable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/ChannelManager.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Channels/BroadcastChannel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Channels/DatabaseChannel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Channels/MailChannel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Channels/NexmoSmsChannel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Channels/SlackWebhookChannel.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Console/NotificationTableCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Console/stubs/notifications.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/DatabaseNotification.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/DatabaseNotificationCollection.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Events/BroadcastNotificationCreated.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Events/NotificationFailed.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Events/NotificationSending.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Events/NotificationSent.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/HasDatabaseNotifications.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Messages/BroadcastMessage.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Messages/DatabaseMessage.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Messages/MailMessage.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Messages/NexmoMessage.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Messages/SimpleMessage.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Messages/SlackAttachment.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Messages/SlackAttachmentField.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Messages/SlackMessage.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Notifiable.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/Notification.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/NotificationSender.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/NotificationServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/RoutesNotifications.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/SendQueuedNotifications.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Notifications/resources/views/email.blade.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Pagination/UrlWindow.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Pagination/resources/views/bootstrap-4.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Pagination/resources/views/default.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Pagination/resources/views/semantic-ui.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Pagination/resources/views/simple-bootstrap-4.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Pagination/resources/views/simple-default.blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Pipeline/Hub.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Pipeline/PipelineServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/BeanstalkdQueue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Capsule/Manager.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Connectors/BeanstalkdConnector.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Connectors/ConnectorInterface.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Connectors/DatabaseConnector.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Connectors/NullConnector.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Connectors/RedisConnector.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SqsConnector.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SyncConnector.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/FailedTableCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/FlushFailedCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/ForgetFailedCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/ListFailedCommand.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/ListenCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/RestartCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/RetryCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/TableCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/stubs/failed_jobs.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Console/stubs/jobs.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/DatabaseQueue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Events/JobExceptionOccurred.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Events/JobFailed.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Events/JobProcessed.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Events/JobProcessing.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Events/Looping.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Events/WorkerStopping.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Failed/DatabaseFailedJobProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Failed/FailedJobProviderInterface.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Failed/NullFailedJobProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/FailingJob.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/InteractsWithQueue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/InvalidPayloadException.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Jobs/BeanstalkdJob.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Jobs/DatabaseJob.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Jobs/DatabaseJobRecord.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Jobs/JobName.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Jobs/RedisJob.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SqsJob.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SyncJob.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/ListenerOptions.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/LuaScripts.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/ManuallyFailedException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/MaxAttemptsExceededException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/NullQueue.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Queue.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/QueueManager.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/QueueServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/SerializesModels.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/SqsQueue.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/Worker.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Queue/WorkerOptions.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Connections/Connection.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Connections/PhpRedisClusterConnection.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Connections/PhpRedisConnection.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Connections/PredisClusterConnection.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Connections/PredisConnection.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Connectors/PhpRedisConnector.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Connectors/PredisConnector.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Limiters/ConcurrencyLimiter.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Limiters/ConcurrencyLimiterBuilder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Limiters/DurationLimiter.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/Limiters/DurationLimiterBuilder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/RedisManager.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Redis/RedisServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Console/ControllerMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Console/MiddlewareMakeCommand.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Console/stubs/controller.model.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Console/stubs/controller.nested.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Console/stubs/controller.plain.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Console/stubs/controller.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Console/stubs/middleware.stub create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Contracts/ControllerDispatcher.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/ControllerMiddlewareOptions.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Events/RouteMatched.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Exceptions/UrlGenerationException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/ImplicitRouteBinding.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Matching/HostValidator.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Matching/MethodValidator.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Matching/SchemeValidator.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Matching/UriValidator.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Matching/ValidatorInterface.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequestsWithRedis.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/MiddlewareNameResolver.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/PendingResourceRegistration.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/RedirectController.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Redirector.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/ResourceRegistrar.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/ResponseFactory.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/Route.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/RouteAction.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/RouteBinding.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/RouteCompiler.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/RouteGroup.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/RouteParameterBinder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/RouteRegistrar.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/RouteUrlGenerator.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/SortedMiddleware.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Routing/ViewController.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/CacheBasedSessionHandler.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/Console/SessionTableCommand.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/Console/stubs/database.stub create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/CookieSessionHandler.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/DatabaseSessionHandler.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/EncryptedStore.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/ExistenceAwareInterface.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/Middleware/AuthenticateSession.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/NullSessionHandler.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/SessionManager.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/SessionServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/Store.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Session/TokenMismatchException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/AggregateServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Arr.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Composer.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Debug/Dumper.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Debug/HtmlDumper.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/App.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Artisan.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Auth.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Blade.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Broadcast.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Bus.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Config.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Cookie.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Crypt.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/DB.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Event.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/File.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Gate.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Hash.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Input.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Lang.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Log.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Mail.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Notification.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Password.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Queue.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Redirect.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Redis.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Request.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Response.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Schema.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Session.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Storage.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/URL.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/Validator.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Facades/View.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/HigherOrderCollectionProxy.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/HigherOrderTapProxy.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/HtmlString.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/InteractsWithTime.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Manager.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/NamespacedItemResolver.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Pluralizer.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/ProcessUtils.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/ServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/BusFake.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/EventFake.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/MailFake.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/NotificationFake.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/PendingMailFake.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Testing/Fakes/QueueFake.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Traits/CapsuleManagerTrait.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Support/helpers.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Translation/ArrayLoader.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Translation/MessageSelector.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Translation/TranslationServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Translation/Translator.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/ClosureValidationRule.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/Concerns/ReplacesAttributes.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/DatabasePresenceVerifier.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/Factory.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/PresenceVerifierInterface.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/Rule.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/Rules/DatabaseRule.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/Rules/Dimensions.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/Rules/Exists.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/Rules/In.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/Rules/NotIn.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/Rules/Unique.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/UnauthorizedException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/ValidatesWhenResolvedTrait.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/ValidationData.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/ValidationException.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/ValidationRuleParser.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/ValidationServiceProvider.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/Validation/Validator.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/CompilerInterface.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesAuthorizations.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesComments.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesConditionals.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesEchos.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesIncludes.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesInjections.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesJson.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesLoops.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesRawPhp.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesStacks.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesTranslations.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Concerns/ManagesComponents.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Concerns/ManagesEvents.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Concerns/ManagesLoops.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Concerns/ManagesStacks.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Concerns/ManagesTranslations.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Engines/CompilerEngine.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Engines/Engine.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Engines/EngineResolver.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Engines/FileEngine.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Factory.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/View/FileViewFinder.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/View/ViewFinderInterface.php create mode 100644 freescout-dist/vendor/laravel/framework/src/Illuminate/View/ViewName.php create mode 100755 freescout-dist/vendor/laravel/framework/src/Illuminate/View/ViewServiceProvider.php create mode 100644 freescout-dist/vendor/laravel/tinker/config/tinker.php create mode 100644 freescout-dist/vendor/laravel/tinker/src/ClassAliasAutoloader.php create mode 100644 freescout-dist/vendor/laravel/tinker/src/Console/TinkerCommand.php create mode 100644 freescout-dist/vendor/laravel/tinker/src/TinkerCaster.php create mode 100644 freescout-dist/vendor/laravel/tinker/src/TinkerServiceProvider.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/AbstractAdapter.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/Ftp.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/Ftpd.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/Local.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/NullAdapter.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/Polyfill/StreamedCopyTrait.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/Polyfill/StreamedWritingTrait.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Adapter/SynologyFtp.php create mode 100644 freescout-dist/vendor/league/flysystem/src/AdapterInterface.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Config.php create mode 100644 freescout-dist/vendor/league/flysystem/src/ConfigAwareTrait.php create mode 100644 freescout-dist/vendor/league/flysystem/src/ConnectionErrorException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/ConnectionRuntimeException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/CorruptedPathDetected.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Directory.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Exception.php create mode 100644 freescout-dist/vendor/league/flysystem/src/File.php create mode 100644 freescout-dist/vendor/league/flysystem/src/FileExistsException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/FileNotFoundException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Filesystem.php create mode 100644 freescout-dist/vendor/league/flysystem/src/FilesystemException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/FilesystemInterface.php create mode 100644 freescout-dist/vendor/league/flysystem/src/FilesystemNotFoundException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Handler.php create mode 100644 freescout-dist/vendor/league/flysystem/src/InvalidRootException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/MountManager.php create mode 100644 freescout-dist/vendor/league/flysystem/src/NotSupportedException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Plugin/AbstractPlugin.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Plugin/EmptyDir.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Plugin/ForcedCopy.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Plugin/ForcedRename.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Plugin/GetWithMetadata.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Plugin/ListFiles.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Plugin/ListPaths.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Plugin/ListWith.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Plugin/PluggableTrait.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/PluginInterface.php create mode 100644 freescout-dist/vendor/league/flysystem/src/ReadInterface.php create mode 100644 freescout-dist/vendor/league/flysystem/src/RootViolationException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/SafeStorage.php create mode 100644 freescout-dist/vendor/league/flysystem/src/UnreadableFileException.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Util.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Util/ContentListingFormatter.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Util/MimeType.php create mode 100644 freescout-dist/vendor/league/flysystem/src/Util/StreamHasher.php create mode 100644 freescout-dist/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php create mode 100644 freescout-dist/vendor/league/mime-type-detection/src/ExtensionLookup.php create mode 100644 freescout-dist/vendor/league/mime-type-detection/src/ExtensionMimeTypeDetector.php create mode 100644 freescout-dist/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php create mode 100644 freescout-dist/vendor/league/mime-type-detection/src/FinfoMimeTypeDetector.php create mode 100644 freescout-dist/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php create mode 100644 freescout-dist/vendor/league/mime-type-detection/src/MimeTypeDetector.php create mode 100644 freescout-dist/vendor/league/mime-type-detection/src/OverridingExtensionToMimeTypeMap.php create mode 100644 freescout-dist/vendor/lord/laroute/.travis.yml create mode 100644 freescout-dist/vendor/lord/laroute/config/laroute.php create mode 100644 freescout-dist/vendor/lord/laroute/gruntfile.js create mode 100644 freescout-dist/vendor/lord/laroute/karma.js create mode 100644 freescout-dist/vendor/lord/laroute/public/.gitkeep create mode 100644 freescout-dist/vendor/lord/laroute/public/js/.gitkeep create mode 100644 freescout-dist/vendor/lord/laroute/public/js/laroute.js create mode 100644 freescout-dist/vendor/lord/laroute/src/Compilers/CompilerInterface.php create mode 100644 freescout-dist/vendor/lord/laroute/src/Compilers/TemplateCompiler.php create mode 100644 freescout-dist/vendor/lord/laroute/src/Console/Commands/LarouteGeneratorCommand.php create mode 100644 freescout-dist/vendor/lord/laroute/src/Generators/GeneratorInterface.php create mode 100644 freescout-dist/vendor/lord/laroute/src/Generators/TemplateGenerator.php create mode 100644 freescout-dist/vendor/lord/laroute/src/LarouteServiceProvider.php create mode 100644 freescout-dist/vendor/lord/laroute/src/Routes/Exceptions/ZeroRoutesException.php create mode 100644 freescout-dist/vendor/lord/laroute/src/templates/laroute.js create mode 100644 freescout-dist/vendor/lord/laroute/src/templates/laroute.min.js create mode 100644 freescout-dist/vendor/mews/purifier/.scrutinizer.yml create mode 100644 freescout-dist/vendor/mews/purifier/config/purifier.php create mode 100644 freescout-dist/vendor/mews/purifier/src/Facades/Purifier.php create mode 100644 freescout-dist/vendor/mews/purifier/src/Purifier.php create mode 100644 freescout-dist/vendor/mews/purifier/src/PurifierServiceProvider.php create mode 100644 freescout-dist/vendor/mews/purifier/src/helpers.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/ErrorHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Logger.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Registry.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/ResettableInterface.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/SignalHandler.php create mode 100644 freescout-dist/vendor/monolog/monolog/src/Monolog/Utils.php create mode 100644 freescout-dist/vendor/mtdowling/cron-expression/src/Cron/AbstractField.php create mode 100644 freescout-dist/vendor/mtdowling/cron-expression/src/Cron/CronExpression.php create mode 100644 freescout-dist/vendor/mtdowling/cron-expression/src/Cron/DayOfMonthField.php create mode 100644 freescout-dist/vendor/mtdowling/cron-expression/src/Cron/DayOfWeekField.php create mode 100644 freescout-dist/vendor/mtdowling/cron-expression/src/Cron/FieldFactory.php create mode 100644 freescout-dist/vendor/mtdowling/cron-expression/src/Cron/FieldInterface.php create mode 100644 freescout-dist/vendor/mtdowling/cron-expression/src/Cron/HoursField.php create mode 100644 freescout-dist/vendor/mtdowling/cron-expression/src/Cron/MinutesField.php create mode 100644 freescout-dist/vendor/mtdowling/cron-expression/src/Cron/MonthField.php create mode 100644 freescout-dist/vendor/mtdowling/cron-expression/src/Cron/YearField.php create mode 100644 freescout-dist/vendor/natxet/cssmin/README create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidDateException.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/af.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ar.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/az.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/bg.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/bn.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/bs_BA.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ca.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/cs.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/cy.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/da.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/de.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/dv_MV.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/el.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/en.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/eo.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/es.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/et.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/eu.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/fa.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/fi.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/fo.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/fr.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/gl.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/gu.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/he.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/hi.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/hr.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/hu.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/hy.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/id.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/is.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/it.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ja.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ka.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/kk.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/km.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ko.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/lt.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/lv.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/mk.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/mn.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ms.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/my.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ne.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/nl.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/no.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/oc.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/pl.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ps.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/pt.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/pt_BR.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ro.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ru.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sh.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sk.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sl.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sq.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sr.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sr_ME.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sv.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/sw.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/th.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/tr.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/uk.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/ur.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/uz.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/vi.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/zh.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Lang/zh_TW.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/Carbon/Translator.php create mode 100644 freescout-dist/vendor/nesbot/carbon/src/JsonSerializable.php create mode 100755 freescout-dist/vendor/nikic/php-parser/bin/php-parse create mode 100644 freescout-dist/vendor/nikic/php-parser/grammar/parser.template create mode 100644 freescout-dist/vendor/nikic/php-parser/grammar/php5.y create mode 100644 freescout-dist/vendor/nikic/php-parser/grammar/php7.y create mode 100644 freescout-dist/vendor/nikic/php-parser/grammar/phpyLang.php create mode 100644 freescout-dist/vendor/nikic/php-parser/grammar/rebuildParsers.php create mode 100644 freescout-dist/vendor/nikic/php-parser/grammar/tokens.template create mode 100644 freescout-dist/vendor/nikic/php-parser/grammar/tokens.y create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/ClassConst.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Enum_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Method.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Comment.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluationException.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Error.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Collecting.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Internal/DiffElem.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyFunctionTokenEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NameContext.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Arg.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/ComplexType.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Const_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Coalesce.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Identifier.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Name.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Param.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/EnumCase.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Enum_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Node/VariadicPlaceholder.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Parser.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Parser/Multiple.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/ParserFactory.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php create mode 100644 freescout-dist/vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/config/config.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Collection.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/CommandMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/ControllerMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/DisableCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/DumpCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/EnableCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/EventMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/FactoryMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/GeneratorCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/InstallCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/JobMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/ListCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/ListenerMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/MailMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/MiddlewareMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/MigrateCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/MigrateRefreshCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/MigrateResetCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/MigrateRollbackCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/MigrateStatusCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/MigrationMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/ModelMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/ModuleMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/NotificationMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/PolicyMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/ProviderMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/PublishCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/PublishConfigurationCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/PublishMigrationCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/PublishTranslationCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/RequestMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/ResourceMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/RouteProviderMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/RuleMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/SeedCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/SeedMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/SetupCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/TestMakeCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/UnUseCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/UpdateCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/UseCommand.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/command.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/composer.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/controller-plain.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/controller.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/event.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/factory.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/job-queued.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/job.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/json.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/listener-duck.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/listener-queued-duck.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/listener-queued.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/listener.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/mail.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/middleware.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/migration/add.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/migration/create.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/migration/delete.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/migration/drop.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/migration/plain.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/model.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/notification.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/policy.plain.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/provider.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/request.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/resource-collection.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/resource.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/route-provider.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/routes.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/rule.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/scaffold/config.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/scaffold/provider.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/seeder.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/start.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/unit-test.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/views/index.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Commands/stubs/views/master.stub create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Contracts/PublisherInterface.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Contracts/RepositoryInterface.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Contracts/RunableInterface.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Exceptions/FileAlreadyExistException.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Exceptions/InvalidAssetPath.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Exceptions/InvalidJsonException.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Exceptions/ModuleNotFoundException.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Facades/Module.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Generators/FileGenerator.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Generators/Generator.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Generators/ModuleGenerator.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Laravel/Module.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Laravel/Repository.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/LaravelModulesServiceProvider.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Lumen/Module.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Lumen/Repository.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/LumenModulesServiceProvider.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Migrations/Migrator.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/ModulesServiceProvider.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Process/Installer.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Process/Runner.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Process/Updater.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Providers/BootstrapServiceProvider.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Providers/ConsoleServiceProvider.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Providers/ContractsServiceProvider.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Publishing/AssetPublisher.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Publishing/LangPublisher.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Publishing/MigrationPublisher.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Publishing/Publisher.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Routing/Controller.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Support/Config/GenerateConfigReader.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Support/Config/GeneratorPath.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Support/Migrations/NameParser.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Support/Migrations/SchemaParser.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Support/Stub.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Traits/CanClearModulesCache.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Traits/MigrationLoaderTrait.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/Traits/ModuleCommandTrait.php create mode 100644 freescout-dist/vendor/nwidart/laravel-modules/src/helpers.php create mode 100755 freescout-dist/vendor/paragonie/random_compat/build-phar.sh create mode 100644 freescout-dist/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey create mode 100644 freescout-dist/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc create mode 100644 freescout-dist/vendor/paragonie/random_compat/lib/random.php create mode 100644 freescout-dist/vendor/paragonie/random_compat/other/build_phar.php create mode 100644 freescout-dist/vendor/paragonie/random_compat/psalm-autoload.php create mode 100644 freescout-dist/vendor/paragonie/random_compat/psalm.xml create mode 100644 freescout-dist/vendor/patchwork/utf8/.travis.yml create mode 100644 freescout-dist/vendor/patchwork/utf8/appveyor.yml create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Normalizer.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Iconv.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Intl.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Mbstring.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Normalizer.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Xml.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.big5.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp037.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp1006.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp1026.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp424.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp437.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp500.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp737.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp775.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp850.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp852.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp855.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp856.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp857.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp860.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp861.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp862.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp863.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp864.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp865.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp866.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp869.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp874.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp875.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp932.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp936.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp949.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp950.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.gsm0338.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-1.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-10.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-11.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-13.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-14.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-15.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-16.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-2.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-3.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-4.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-5.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-6.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-7.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-8.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-9.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.koi8-r.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.koi8-u.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.mazovia.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.nextstep.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.stdenc.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.symbol.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.turkish.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.us-ascii-quotes.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.us-ascii.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1250.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1251.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1252.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1253.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1254.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1255.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1256.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1257.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1258.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-ce.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-cyrillic.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-greek.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-icelandic.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-roman.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.zdingbat.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.gsm0338.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.mazovia.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.stdenc.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.symbol.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.zdingbat.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/translit.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/canonicalComposition.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/canonicalDecomposition.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/combiningClass.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/compatibilityDecomposition.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/lowerCase.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/upperCase.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/TurkishUtf8.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/BestFit.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup/iconv.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup/intl.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup/mbstring.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup/utf8_encode.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/WindowsStreamWrapper.php create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/caseFolding_full.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1250.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1251.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1252.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1253.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1254.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1255.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1256.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1257.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1258.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit874.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit932.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit936.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit949.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit950.ser create mode 100644 freescout-dist/vendor/patchwork/utf8/src/Patchwork/Utf8/data/translit_extra.ser create mode 100644 freescout-dist/vendor/psr/container/src/ContainerExceptionInterface.php create mode 100644 freescout-dist/vendor/psr/container/src/ContainerInterface.php create mode 100644 freescout-dist/vendor/psr/container/src/NotFoundExceptionInterface.php create mode 100644 freescout-dist/vendor/psr/http-message/src/MessageInterface.php create mode 100644 freescout-dist/vendor/psr/http-message/src/RequestInterface.php create mode 100644 freescout-dist/vendor/psr/http-message/src/ResponseInterface.php create mode 100644 freescout-dist/vendor/psr/http-message/src/ServerRequestInterface.php create mode 100644 freescout-dist/vendor/psr/http-message/src/StreamInterface.php create mode 100644 freescout-dist/vendor/psr/http-message/src/UploadedFileInterface.php create mode 100644 freescout-dist/vendor/psr/http-message/src/UriInterface.php create mode 100644 freescout-dist/vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 freescout-dist/vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 freescout-dist/vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 freescout-dist/vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 freescout-dist/vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 freescout-dist/vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 freescout-dist/vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 freescout-dist/vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 freescout-dist/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 freescout-dist/vendor/psr/simple-cache/src/CacheException.php create mode 100644 freescout-dist/vendor/psr/simple-cache/src/CacheInterface.php create mode 100644 freescout-dist/vendor/psr/simple-cache/src/InvalidArgumentException.php create mode 100644 freescout-dist/vendor/psy/psysh/.phan/config.php create mode 100644 freescout-dist/vendor/psy/psysh/.styleci.yml create mode 100644 freescout-dist/vendor/psy/psysh/.travis.yml create mode 100755 freescout-dist/vendor/psy/psysh/bin/build-stub create mode 100755 freescout-dist/vendor/psy/psysh/bin/psysh create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/AbstractClassPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/AssignThisVariablePass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/CallTimePassByReferencePass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/CalledClassPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/CodeCleanerPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/ExitPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/FinalClassPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/FunctionContextPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/FunctionReturnInWriteContextPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/ImplicitReturnPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/InstanceOfPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/LeavePsyshAlonePass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/LegacyEmptyPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/ListPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/LoopContextPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/MagicConstantsPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/NamespaceAwarePass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/NamespacePass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/NoReturnValue.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/PassableByReferencePass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/RequirePass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/StrictTypesPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/UseStatementPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/ValidClassNamePass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/ValidConstantPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/ValidConstructorPass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/CodeCleaner/ValidFunctionNamePass.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/BufferCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ClearCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/Command.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/DocCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/DumpCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/EditCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ExitCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/HelpCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/HistoryCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/ClassConstantEnumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/ClassEnumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/ConstantEnumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/Enumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/FunctionEnumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/GlobalVariableEnumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/InterfaceEnumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/MethodEnumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/PropertyEnumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/TraitEnumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ListCommand/VariableEnumerator.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ParseCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/PsyVersionCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ReflectingCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ShowCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/SudoCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/ThrowUpCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/TimeitCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/TimeitCommand/TimeitVisitor.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/TraceCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/WhereamiCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Command/WtfCommand.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ConfigPaths.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Configuration.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ConsoleColorFactory.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Context.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ContextAware.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Exception/BreakException.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Exception/DeprecatedException.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Exception/ErrorException.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Exception/Exception.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Exception/FatalErrorException.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Exception/ParseErrorException.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Exception/RuntimeException.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Exception/ThrowUpException.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Exception/TypeErrorException.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ExecutionClosure.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ExecutionLoop.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ExecutionLoop/AbstractListener.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ExecutionLoop/Listener.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ExecutionLoop/ProcessForker.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ExecutionLoop/RunkitReloader.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ExecutionLoopClosure.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Formatter/CodeFormatter.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Formatter/DocblockFormatter.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Formatter/Formatter.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Formatter/SignatureFormatter.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Input/CodeArgument.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Input/FilterOptions.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Input/ShellInput.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Input/SilentInput.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Output/OutputPager.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Output/PassthruPager.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Output/ProcOutputPager.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Output/ShellOutput.php create mode 100644 freescout-dist/vendor/psy/psysh/src/ParserFactory.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Readline/GNUReadline.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Readline/HoaConsole.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Readline/Libedit.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Readline/Readline.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Readline/Transient.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Reflection/ReflectionClassConstant.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Reflection/ReflectionConstant.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Reflection/ReflectionConstant_.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Reflection/ReflectionLanguageConstruct.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Reflection/ReflectionLanguageConstructParameter.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Shell.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Sudo.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Sudo/SudoVisitor.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/AutoCompleter.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractContextAwareMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractDefaultParametersMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/AbstractMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/ClassAttributesMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodDefaultParametersMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/ClassMethodsMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/ClassNamesMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/CommandsMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/ConstantsMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionDefaultParametersMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionsMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/KeywordsMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/MongoClientMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/MongoDatabaseMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectAttributesMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodDefaultParametersMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/ObjectMethodsMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/TabCompletion/Matcher/VariablesMatcher.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Util/Docblock.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Util/Json.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Util/Mirror.php create mode 100644 freescout-dist/vendor/psy/psysh/src/Util/Str.php create mode 100644 freescout-dist/vendor/psy/psysh/src/VarDumper/Cloner.php create mode 100644 freescout-dist/vendor/psy/psysh/src/VarDumper/Dumper.php create mode 100644 freescout-dist/vendor/psy/psysh/src/VarDumper/Presenter.php create mode 100644 freescout-dist/vendor/psy/psysh/src/VarDumper/PresenterAware.php create mode 100644 freescout-dist/vendor/psy/psysh/src/VersionUpdater/Checker.php create mode 100644 freescout-dist/vendor/psy/psysh/src/VersionUpdater/GitHubChecker.php create mode 100644 freescout-dist/vendor/psy/psysh/src/VersionUpdater/IntervalChecker.php create mode 100644 freescout-dist/vendor/psy/psysh/src/VersionUpdater/NoopChecker.php create mode 100644 freescout-dist/vendor/psy/psysh/src/functions.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Config/installer.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Controllers/DatabaseController.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Controllers/PermissionsController.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Controllers/RequirementsController.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Controllers/UpdateController.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Controllers/WelcomeController.php create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Events/LaravelInstallerFinished.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Helpers/MigrationsHelper.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Helpers/functions.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/ar/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/de/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/en/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/es/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/et/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/fa/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/fr/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/gr/installer_messages.php create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/id/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/it/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/nl/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/pl/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/pt-br/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/pt/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/ro/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/ru/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/tr/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/zh-CN/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Lang/zh-TW/installer_messages.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Middleware/canUpdate.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Routes/web.php create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/environment-classic.blade.php create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/environment-wizard.blade.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/environment.blade.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/finished.blade.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/layouts/master-update.blade.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/layouts/master.blade.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/permissions.blade.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/requirements.blade.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/update/finished.blade.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/update/overview.blade.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/update/welcome.blade.php create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/Views/welcome.blade.php create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/sass/_variables.sass create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/sass/style.sass create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/_variables.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_animated.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_bordered-pulled.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_core.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_fixed-width.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_icons.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_larger.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_list.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_mixins.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_path.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_rotated-flipped.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_screen-reader.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_stacked.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/_variables.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/font-awesome/font-awesome.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/scss/style.scss create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/style.css create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/style.css.map create mode 100755 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/style.min.css create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/css/style.min.css.map create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/fonts/fontawesome-webfont.woff create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/fonts/fontawesome-webfont.woff2 create mode 100644 freescout-dist/vendor/rachidlaasri/laravel-installer/src/assets/fonts/ionicons.woff create mode 100644 freescout-dist/vendor/ralouphie/getallheaders/src/getallheaders.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/BinaryUtils.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Builder/DefaultUuidBuilder.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Builder/DegradedUuidBuilder.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Builder/UuidBuilderInterface.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Codec/CodecInterface.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Codec/GuidStringCodec.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Codec/OrderedTimeCodec.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Codec/StringCodec.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Codec/TimestampLastCombCodec.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Converter/Number/BigNumberConverter.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Converter/NumberConverterInterface.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Converter/TimeConverterInterface.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/DegradedUuid.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Exception/InvalidUuidStringException.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Exception/UnsupportedOperationException.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/FeatureSet.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/CombGenerator.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/DefaultTimeGenerator.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/MtRandGenerator.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/OpenSslGenerator.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/RandomBytesGenerator.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/RandomGeneratorFactory.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/RandomGeneratorInterface.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/RandomLibAdapter.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/SodiumRandomGenerator.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/TimeGeneratorFactory.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Generator/TimeGeneratorInterface.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Provider/NodeProviderInterface.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/Provider/TimeProviderInterface.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/UuidFactory.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/UuidFactoryInterface.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/UuidInterface.php create mode 100644 freescout-dist/vendor/ramsey/uuid/src/functions.php create mode 100644 freescout-dist/vendor/rap2hpoutre/laravel-log-viewer/.travis.yml create mode 100644 freescout-dist/vendor/rap2hpoutre/laravel-log-viewer/src/Rap2hpoutre/LaravelLogViewer/LaravelLogViewerServiceProvider.php create mode 100644 freescout-dist/vendor/rap2hpoutre/laravel-log-viewer/src/Rap2hpoutre/LaravelLogViewer/Level.php create mode 100644 freescout-dist/vendor/rap2hpoutre/laravel-log-viewer/src/Rap2hpoutre/LaravelLogViewer/Pattern.php create mode 100644 freescout-dist/vendor/rap2hpoutre/laravel-log-viewer/src/config/logviewer.php create mode 100644 freescout-dist/vendor/rap2hpoutre/laravel-log-viewer/src/controllers/LogViewerController.php create mode 100644 freescout-dist/vendor/rap2hpoutre/laravel-log-viewer/src/views/log.blade.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/.styleci.yml create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/config/activitylog.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/migrations/create_activity_log_table.php.stub create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/ActivityLogger.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/ActivitylogServiceProvider.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/CleanActivitylogCommand.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/Exceptions/CouldNotLogActivity.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/Exceptions/CouldNotLogChanges.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/Exceptions/InvalidConfiguration.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/Models/Activity.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/Traits/CausesActivity.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/Traits/DetectsChanges.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/Traits/HasActivity.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/Traits/LogsActivity.php create mode 100644 freescout-dist/vendor/spatie/laravel-activitylog/src/helpers.php create mode 100644 freescout-dist/vendor/spatie/string/src/Exceptions/ErrorCreatingStringException.php create mode 100644 freescout-dist/vendor/spatie/string/src/Exceptions/UnknownFunctionException.php create mode 100644 freescout-dist/vendor/spatie/string/src/Exceptions/UnsetOffsetException.php create mode 100644 freescout-dist/vendor/spatie/string/src/Integrations/Underscore.php create mode 100644 freescout-dist/vendor/spatie/string/src/string_functions.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/.travis.yml create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/CHANGES create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/README create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/doc/headers.rst create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/doc/index.rst create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/doc/introduction.rst create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/doc/japanese.rst create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/doc/messages.rst create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/doc/plugins.rst create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/doc/sending.rst create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/AddressEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/AddressEncoder/IdnAddressEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/AddressEncoder/Utf8AddressEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/AddressEncoderException.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory/SimpleCharacterReaderFactory.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/ArrayCharacterStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Base64Encoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/Event.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventDispatcher.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventListener.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventObject.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendEvent.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SimpleEventDispatcher.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionEvent.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FailoverTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Filterable.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/IdGenerator.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Image.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/IoException.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/ArrayKeyCache.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/NullKeyCache.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/SimpleKeyCacheInputStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NullContentEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/Base64HeaderEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/AbstractHeader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/IdGenerator.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderFactory.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/AntiFloodPlugin.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/DecoratorPlugin.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/LoggerPlugin.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Exception.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/PopBeforeSmtpPlugin.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ReporterPlugin.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ThrottlerPlugin.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Preferences.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/RfcComplianceException.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SendmailTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/ByteArrayReplacementFilter.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/EightBitMimeHandler.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/SmtpUtf8Handler.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpHandler.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/LoadBalancedTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SmtpAgent.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SpoolTransport.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/mime_types.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/preferences.php create mode 100644 freescout-dist/vendor/swiftmailer/swiftmailer/lib/swift_required.php create mode 100755 freescout-dist/vendor/swiftmailer/swiftmailer/lib/swiftmailer_generate_mimes_config.php create mode 100644 freescout-dist/vendor/symfony/console/Application.php create mode 100644 freescout-dist/vendor/symfony/console/Command/Command.php create mode 100644 freescout-dist/vendor/symfony/console/Command/HelpCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Command/ListCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Command/LockableTrait.php create mode 100644 freescout-dist/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php create mode 100644 freescout-dist/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php create mode 100644 freescout-dist/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php create mode 100644 freescout-dist/vendor/symfony/console/ConsoleEvents.php create mode 100644 freescout-dist/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php create mode 100644 freescout-dist/vendor/symfony/console/Descriptor/ApplicationDescription.php create mode 100644 freescout-dist/vendor/symfony/console/Descriptor/Descriptor.php create mode 100644 freescout-dist/vendor/symfony/console/Descriptor/DescriptorInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Descriptor/JsonDescriptor.php create mode 100644 freescout-dist/vendor/symfony/console/Descriptor/MarkdownDescriptor.php create mode 100644 freescout-dist/vendor/symfony/console/Descriptor/XmlDescriptor.php create mode 100644 freescout-dist/vendor/symfony/console/Event/ConsoleCommandEvent.php create mode 100644 freescout-dist/vendor/symfony/console/Event/ConsoleErrorEvent.php create mode 100644 freescout-dist/vendor/symfony/console/Event/ConsoleEvent.php create mode 100644 freescout-dist/vendor/symfony/console/Event/ConsoleExceptionEvent.php create mode 100644 freescout-dist/vendor/symfony/console/Event/ConsoleTerminateEvent.php create mode 100644 freescout-dist/vendor/symfony/console/EventListener/ErrorListener.php create mode 100644 freescout-dist/vendor/symfony/console/Exception/CommandNotFoundException.php create mode 100644 freescout-dist/vendor/symfony/console/Exception/ExceptionInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Exception/InvalidArgumentException.php create mode 100644 freescout-dist/vendor/symfony/console/Exception/InvalidOptionException.php create mode 100644 freescout-dist/vendor/symfony/console/Exception/LogicException.php create mode 100644 freescout-dist/vendor/symfony/console/Exception/RuntimeException.php create mode 100644 freescout-dist/vendor/symfony/console/Formatter/OutputFormatter.php create mode 100644 freescout-dist/vendor/symfony/console/Formatter/OutputFormatterInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Formatter/OutputFormatterStyle.php create mode 100644 freescout-dist/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/DebugFormatterHelper.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/DescriptorHelper.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/FormatterHelper.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/HelperInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/InputAwareHelper.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/ProcessHelper.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/ProgressBar.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/ProgressIndicator.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/QuestionHelper.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/SymfonyQuestionHelper.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/Table.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/TableCell.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/TableSeparator.php create mode 100644 freescout-dist/vendor/symfony/console/Helper/TableStyle.php create mode 100644 freescout-dist/vendor/symfony/console/Input/ArgvInput.php create mode 100644 freescout-dist/vendor/symfony/console/Input/ArrayInput.php create mode 100644 freescout-dist/vendor/symfony/console/Input/Input.php create mode 100644 freescout-dist/vendor/symfony/console/Input/InputArgument.php create mode 100644 freescout-dist/vendor/symfony/console/Input/InputAwareInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Input/InputDefinition.php create mode 100644 freescout-dist/vendor/symfony/console/Input/InputInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Input/InputOption.php create mode 100644 freescout-dist/vendor/symfony/console/Input/StreamableInputInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Input/StringInput.php create mode 100644 freescout-dist/vendor/symfony/console/Logger/ConsoleLogger.php create mode 100644 freescout-dist/vendor/symfony/console/Output/BufferedOutput.php create mode 100644 freescout-dist/vendor/symfony/console/Output/ConsoleOutput.php create mode 100644 freescout-dist/vendor/symfony/console/Output/ConsoleOutputInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Output/NullOutput.php create mode 100644 freescout-dist/vendor/symfony/console/Output/Output.php create mode 100644 freescout-dist/vendor/symfony/console/Output/OutputInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Output/StreamOutput.php create mode 100644 freescout-dist/vendor/symfony/console/Question/ChoiceQuestion.php create mode 100644 freescout-dist/vendor/symfony/console/Question/ConfirmationQuestion.php create mode 100644 freescout-dist/vendor/symfony/console/Question/Question.php create mode 100644 freescout-dist/vendor/symfony/console/Resources/bin/hiddeninput.exe create mode 100644 freescout-dist/vendor/symfony/console/Style/OutputStyle.php create mode 100644 freescout-dist/vendor/symfony/console/Style/StyleInterface.php create mode 100644 freescout-dist/vendor/symfony/console/Style/SymfonyStyle.php create mode 100644 freescout-dist/vendor/symfony/console/Terminal.php create mode 100644 freescout-dist/vendor/symfony/console/Tester/ApplicationTester.php create mode 100644 freescout-dist/vendor/symfony/console/Tester/CommandTester.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/ApplicationTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Command/CommandTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Command/HelpCommandTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Command/ListCommandTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Command/LockableTraitTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/CommandLoader/ContainerCommandLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/CommandLoader/FactoryCommandLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/DependencyInjection/AddConsoleCommandPassTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Descriptor/AbstractDescriptorTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Descriptor/ApplicationDescriptionTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Descriptor/JsonDescriptorTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Descriptor/MarkdownDescriptorTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Descriptor/ObjectsProvider.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Descriptor/TextDescriptorTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Descriptor/XmlDescriptorTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/EventListener/ErrorListenerTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/BarBucCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/DescriptorApplication1.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/DescriptorApplication2.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/DescriptorApplicationMbString.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/DescriptorCommand1.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/DescriptorCommand2.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/DescriptorCommand3.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/DescriptorCommand4.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/DescriptorCommandMbString.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/DummyOutput.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Foo1Command.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Foo2Command.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Foo3Command.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Foo4Command.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Foo5Command.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Foo6Command.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/FooCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/FooHiddenCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/FooLock2Command.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/FooLockCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/FooOptCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/FooSameCaseLowercaseCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/FooSameCaseUppercaseCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced1Command.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced2Command.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/FoobarCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_0.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_1.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_10.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_11.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_12.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_13.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_14.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_15.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_16.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_17.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_2.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_3.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_5.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_6.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_7.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_8.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_9.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/interactive_command_1.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/TestAmbiguousCommandRegistering.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/TestAmbiguousCommandRegistering2.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/TestCommand.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/application_1.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/application_1.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/application_2.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/application_2.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/command_1.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/command_1.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/command_2.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/command_2.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_1.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_1.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_2.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_2.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_3.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_3.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_4.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_4.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_with_default_inf_value.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_argument_with_style.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_definition_1.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_definition_1.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_definition_2.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_definition_2.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_definition_3.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_definition_3.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_definition_4.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_definition_4.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_1.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_1.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_2.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_2.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_3.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_3.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_4.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_4.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_5.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_5.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_6.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_6.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_with_default_inf_value.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_with_style.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_with_style.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.json create mode 100644 freescout-dist/vendor/symfony/console/Tests/Fixtures/input_option_with_style_array.xml create mode 100644 freescout-dist/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleStackTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Formatter/OutputFormatterStyleTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Formatter/OutputFormatterTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/AbstractQuestionHelperTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/FormatterHelperTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/HelperSetTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/HelperTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/ProcessHelperTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/ProgressBarTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/ProgressIndicatorTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/QuestionHelperTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/SymfonyQuestionHelperTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/TableStyleTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Helper/TableTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Input/ArgvInputTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Input/ArrayInputTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Input/InputArgumentTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Input/InputDefinitionTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Input/InputOptionTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Input/InputTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Input/StringInputTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Output/ConsoleOutputTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Output/NullOutputTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Output/OutputTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Output/StreamOutputTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Question/ChoiceQuestionTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Question/ConfirmationQuestionTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Style/SymfonyStyleTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/TerminalTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Tester/ApplicationTesterTest.php create mode 100644 freescout-dist/vendor/symfony/console/Tests/Tester/CommandTesterTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/CssSelectorConverter.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Exception/ExceptionInterface.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Exception/ExpressionErrorException.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Exception/InternalErrorException.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Exception/ParseException.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Exception/SyntaxErrorException.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/AbstractNode.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/AttributeNode.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/ClassNode.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/CombinedSelectorNode.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/ElementNode.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/FunctionNode.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/HashNode.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/NegationNode.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/NodeInterface.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/PseudoNode.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/SelectorNode.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Node/Specificity.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Handler/CommentHandler.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Handler/HandlerInterface.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Handler/HashHandler.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Handler/IdentifierHandler.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Handler/NumberHandler.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Handler/StringHandler.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Handler/WhitespaceHandler.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Parser.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/ParserInterface.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Reader.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Shortcut/ClassParser.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Shortcut/ElementParser.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Shortcut/HashParser.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Token.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/TokenStream.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Tokenizer/Tokenizer.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Parser/Tokenizer/TokenizerPatterns.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/CssSelectorConverterTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/AbstractNodeTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/AttributeNodeTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/ClassNodeTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/CombinedSelectorNodeTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/ElementNodeTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/FunctionNodeTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/HashNodeTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/NegationNodeTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/PseudoNodeTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/SelectorNodeTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Node/SpecificityTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Handler/AbstractHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Handler/CommentHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Handler/HashHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Handler/IdentifierHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Handler/NumberHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Handler/StringHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Handler/WhitespaceHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/ParserTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/ReaderTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Shortcut/ClassParserTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Shortcut/ElementParserTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Shortcut/EmptyStringParserTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/Shortcut/HashParserTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/Parser/TokenStreamTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/XPath/Fixtures/ids.html create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/XPath/Fixtures/lang.xml create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/XPath/Fixtures/shakespear.html create mode 100644 freescout-dist/vendor/symfony/css-selector/Tests/XPath/TranslatorTest.php create mode 100644 freescout-dist/vendor/symfony/css-selector/XPath/Extension/AbstractExtension.php create mode 100644 freescout-dist/vendor/symfony/css-selector/XPath/Extension/AttributeMatchingExtension.php create mode 100644 freescout-dist/vendor/symfony/css-selector/XPath/Extension/CombinationExtension.php create mode 100644 freescout-dist/vendor/symfony/css-selector/XPath/Extension/ExtensionInterface.php create mode 100644 freescout-dist/vendor/symfony/css-selector/XPath/Extension/FunctionExtension.php create mode 100644 freescout-dist/vendor/symfony/css-selector/XPath/Extension/HtmlExtension.php create mode 100644 freescout-dist/vendor/symfony/css-selector/XPath/Extension/PseudoClassExtension.php create mode 100644 freescout-dist/vendor/symfony/css-selector/XPath/Translator.php create mode 100644 freescout-dist/vendor/symfony/css-selector/XPath/TranslatorInterface.php create mode 100644 freescout-dist/vendor/symfony/css-selector/XPath/XPathExpr.php create mode 100644 freescout-dist/vendor/symfony/debug/BufferingLogger.php create mode 100644 freescout-dist/vendor/symfony/debug/Debug.php create mode 100644 freescout-dist/vendor/symfony/debug/DebugClassLoader.php create mode 100644 freescout-dist/vendor/symfony/debug/ErrorHandler.php create mode 100644 freescout-dist/vendor/symfony/debug/Exception/ClassNotFoundException.php create mode 100644 freescout-dist/vendor/symfony/debug/Exception/ContextErrorException.php create mode 100644 freescout-dist/vendor/symfony/debug/Exception/FatalErrorException.php create mode 100644 freescout-dist/vendor/symfony/debug/Exception/FatalThrowableError.php create mode 100644 freescout-dist/vendor/symfony/debug/Exception/FlattenException.php create mode 100644 freescout-dist/vendor/symfony/debug/Exception/OutOfMemoryException.php create mode 100644 freescout-dist/vendor/symfony/debug/Exception/SilencedErrorContext.php create mode 100644 freescout-dist/vendor/symfony/debug/Exception/UndefinedFunctionException.php create mode 100644 freescout-dist/vendor/symfony/debug/Exception/UndefinedMethodException.php create mode 100644 freescout-dist/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php create mode 100644 freescout-dist/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php create mode 100644 freescout-dist/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php create mode 100644 freescout-dist/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php create mode 100644 freescout-dist/vendor/symfony/debug/Resources/ext/config.m4 create mode 100644 freescout-dist/vendor/symfony/debug/Resources/ext/config.w32 create mode 100644 freescout-dist/vendor/symfony/debug/Resources/ext/php_symfony_debug.h create mode 100644 freescout-dist/vendor/symfony/debug/Resources/ext/symfony_debug.c create mode 100644 freescout-dist/vendor/symfony/debug/Tests/DebugClassLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/ErrorHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/ExceptionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/AnnotatedClass.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/ClassAlias.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/DeprecatedClass.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/DeprecatedInterface.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/ExtendedFinalMethod.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/FinalClass.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/FinalMethod.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/FinalMethod2Trait.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/InternalClass.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/InternalInterface.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/InternalTrait.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/InternalTrait2.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/NonDeprecatedInterface.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/PEARClass.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/Throwing.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/ToStringThrower.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/TraitWithInternalMethod.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/casemismatch.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/notPsr0Bis.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/psr4/Psr4CaseMismatch.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures/reallyNotPsr0.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/Fixtures2/RequiredTwice.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/HeaderMock.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/MockExceptionHandler.php create mode 100644 freescout-dist/vendor/symfony/debug/Tests/phpt/debug_class_loader.phpt create mode 100644 freescout-dist/vendor/symfony/debug/Tests/phpt/decorate_exception_hander.phpt create mode 100644 freescout-dist/vendor/symfony/debug/Tests/phpt/exception_rethrown.phpt create mode 100644 freescout-dist/vendor/symfony/debug/Tests/phpt/fatal_with_nested_handlers.phpt create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Debug/WrappedListener.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Event.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/EventDispatcher.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/EventDispatcherInterface.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/EventSubscriberInterface.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/GenericEvent.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Tests/EventTest.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php create mode 100644 freescout-dist/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Comparator/Comparator.php create mode 100644 freescout-dist/vendor/symfony/finder/Comparator/DateComparator.php create mode 100644 freescout-dist/vendor/symfony/finder/Comparator/NumberComparator.php create mode 100644 freescout-dist/vendor/symfony/finder/Exception/AccessDeniedException.php create mode 100644 freescout-dist/vendor/symfony/finder/Exception/ExceptionInterface.php create mode 100644 freescout-dist/vendor/symfony/finder/Glob.php create mode 100644 freescout-dist/vendor/symfony/finder/Iterator/CustomFilterIterator.php create mode 100644 freescout-dist/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php create mode 100644 freescout-dist/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php create mode 100644 freescout-dist/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php create mode 100644 freescout-dist/vendor/symfony/finder/SplFileInfo.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Comparator/ComparatorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Comparator/DateComparatorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Comparator/NumberComparatorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/FinderTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/.dot/a create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/.dot/b/c.neon create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/.dot/b/d.neon create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/A/B/C/abc.dat create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/A/B/ab.dat create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/A/a.dat create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/copy/A/B/C/abc.dat.copy create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/copy/A/B/ab.dat.copy create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/copy/A/a.dat.copy create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/one/.dot create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/one/a create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/one/b/c.neon create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/one/b/d.neon create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Fixtures/r+e.gex[c]a(r)s/dir/bar.dat create mode 100644 freescout-dist/vendor/symfony/finder/Tests/GlobTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/CustomFilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/DateRangeFilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/DepthRangeFilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/FileTypeFilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/FilecontentFilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/FilenameFilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/FilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/Iterator.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/IteratorTestCase.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/MockFileListIterator.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/MockSplFileInfo.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/PathFilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/RealIteratorTestCase.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/SizeRangeFilterIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/finder/Tests/Iterator/SortableIteratorTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/AcceptHeaderItem.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/ApacheRequest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/BinaryFileResponse.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/ExpressionRequestMatcher.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/Exception/FileException.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/Exception/UploadException.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/File.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/Stream.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/File/UploadedFile.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/IpUtils.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/JsonResponse.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/RedirectResponse.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/RequestMatcher.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/RequestMatcherInterface.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/RequestStack.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/ServerBag.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Flash/FlashBag.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Session.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/SessionBagInterface.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/SessionBagProxy.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/SessionInterface.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/StreamedResponse.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/CookieTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/File/FakeFile.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/File/FileTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/FileBagTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/common.inc create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/HeaderBagTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/IpUtilsTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/JsonResponseTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/ParameterBagTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/RequestStackTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/RequestTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/ResponseFunctionalTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/ResponseTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/ResponseTestCase.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/ServerBagTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/SessionTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/read_only.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/storage.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng create mode 100644 freescout-dist/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng create mode 100644 freescout-dist/vendor/symfony/http-kernel/Bundle/Bundle.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Bundle/BundleInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Client.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Config/EnvParametersResource.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Config/FileLocator.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ArgumentResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ControllerReference.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ControllerResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/DataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Debug/FileLinkFormatter.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/Extension.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/LoggerPass.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/ResettableServicePass.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/DependencyInjection/ServicesResetter.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Event/FilterControllerEvent.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Event/FilterResponseEvent.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Event/FinishRequestEvent.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Event/GetResponseEvent.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Event/KernelEvent.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Event/PostResponseEvent.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/DumpListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/ExceptionListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/FragmentListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/LocaleListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/ProfilerListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/ResponseListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/RouterListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/SessionListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/SurrogateListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/TestSessionListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/TranslatorListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/ConflictHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/GoneHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Fragment/FragmentHandler.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpCache/Esi.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpCache/HttpCache.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpCache/Ssi.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpCache/StoreInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpCache/SubRequestHandler.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpKernel.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/HttpKernelInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Kernel.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/KernelEvents.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/KernelInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Log/Logger.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Profiler/Profile.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Profiler/Profiler.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/RebootableInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Resources/welcome.html.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/TerminableInterface.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/ClientTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DependencyInjection/LoggerPassTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DependencyInjection/ResettableServicePassTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/DependencyInjection/ServicesResetterTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Event/FilterControllerArgumentsEventTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/SaveSessionListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/SessionListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/BadRequestHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/ConflictHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/GoneHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/HttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/MethodNotAllowedHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/NotFoundHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/PreconditionFailedHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/PreconditionRequiredHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/ServiceUnavailableHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ClearableService.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/FooCommand.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/ResettableService.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/HttpCache/SubRequestHandlerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/HttpKernelTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/KernelTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Log/LoggerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Logger.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/TestHttpKernel.php create mode 100644 freescout-dist/vendor/symfony/http-kernel/Tests/UriSignerTest.php create mode 100644 freescout-dist/vendor/symfony/polyfill-ctype/Ctype.php create mode 100644 freescout-dist/vendor/symfony/polyfill-ctype/bootstrap.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Idn.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Info.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Resources/unidata/Regex.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Resources/unidata/deviation.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Resources/unidata/ignored.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Resources/unidata/mapped.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/Resources/unidata/virama.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/bootstrap.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-idn/bootstrap80.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-normalizer/Normalizer.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-normalizer/bootstrap.php create mode 100644 freescout-dist/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php create mode 100644 freescout-dist/vendor/symfony/polyfill-mbstring/Mbstring.php create mode 100644 freescout-dist/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php create mode 100644 freescout-dist/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php create mode 100644 freescout-dist/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php create mode 100644 freescout-dist/vendor/symfony/polyfill-mbstring/bootstrap.php create mode 100644 freescout-dist/vendor/symfony/polyfill-mbstring/bootstrap80.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php70/Php70.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php70/Resources/stubs/AssertionError.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php70/Resources/stubs/Error.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php70/Resources/stubs/ParseError.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php70/Resources/stubs/TypeError.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php70/bootstrap.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php72/Php72.php create mode 100644 freescout-dist/vendor/symfony/polyfill-php72/bootstrap.php create mode 100644 freescout-dist/vendor/symfony/process/Exception/ExceptionInterface.php create mode 100644 freescout-dist/vendor/symfony/process/Exception/InvalidArgumentException.php create mode 100644 freescout-dist/vendor/symfony/process/Exception/LogicException.php create mode 100644 freescout-dist/vendor/symfony/process/Exception/ProcessFailedException.php create mode 100644 freescout-dist/vendor/symfony/process/Exception/ProcessTimedOutException.php create mode 100644 freescout-dist/vendor/symfony/process/Exception/RuntimeException.php create mode 100644 freescout-dist/vendor/symfony/process/ExecutableFinder.php create mode 100644 freescout-dist/vendor/symfony/process/InputStream.php create mode 100644 freescout-dist/vendor/symfony/process/PhpExecutableFinder.php create mode 100644 freescout-dist/vendor/symfony/process/PhpProcess.php create mode 100644 freescout-dist/vendor/symfony/process/Pipes/AbstractPipes.php create mode 100644 freescout-dist/vendor/symfony/process/Pipes/PipesInterface.php create mode 100644 freescout-dist/vendor/symfony/process/Pipes/UnixPipes.php create mode 100644 freescout-dist/vendor/symfony/process/Pipes/WindowsPipes.php create mode 100644 freescout-dist/vendor/symfony/process/ProcessBuilder.php create mode 100644 freescout-dist/vendor/symfony/process/ProcessUtils.php create mode 100644 freescout-dist/vendor/symfony/process/Tests/ExecutableFinderTest.php create mode 100644 freescout-dist/vendor/symfony/process/Tests/NonStopableProcess.php create mode 100644 freescout-dist/vendor/symfony/process/Tests/PhpExecutableFinderTest.php create mode 100644 freescout-dist/vendor/symfony/process/Tests/PhpProcessTest.php create mode 100644 freescout-dist/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php create mode 100644 freescout-dist/vendor/symfony/process/Tests/ProcessBuilderTest.php create mode 100644 freescout-dist/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php create mode 100644 freescout-dist/vendor/symfony/process/Tests/ProcessTest.php create mode 100644 freescout-dist/vendor/symfony/process/Tests/ProcessUtilsTest.php create mode 100644 freescout-dist/vendor/symfony/process/Tests/SignalListener.php create mode 100644 freescout-dist/vendor/symfony/routing/Annotation/Route.php create mode 100644 freescout-dist/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php create mode 100644 freescout-dist/vendor/symfony/routing/Exception/ExceptionInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/Exception/InvalidParameterException.php create mode 100644 freescout-dist/vendor/symfony/routing/Exception/MethodNotAllowedException.php create mode 100644 freescout-dist/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php create mode 100644 freescout-dist/vendor/symfony/routing/Exception/NoConfigurationException.php create mode 100644 freescout-dist/vendor/symfony/routing/Exception/ResourceNotFoundException.php create mode 100644 freescout-dist/vendor/symfony/routing/Exception/RouteNotFoundException.php create mode 100644 freescout-dist/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php create mode 100644 freescout-dist/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php create mode 100644 freescout-dist/vendor/symfony/routing/Generator/UrlGenerator.php create mode 100644 freescout-dist/vendor/symfony/routing/Generator/UrlGeneratorInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/AnnotationClassLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/AnnotationFileLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/ClosureLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/Configurator/CollectionConfigurator.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/Configurator/RouteConfigurator.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/Configurator/RoutingConfigurator.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/Configurator/Traits/AddTrait.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/Configurator/Traits/RouteTrait.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/DirectoryLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/GlobFileLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/ObjectRouteLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/PhpFileLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/XmlFileLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/YamlFileLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/RequestMatcherInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/UrlMatcher.php create mode 100644 freescout-dist/vendor/symfony/routing/Matcher/UrlMatcherInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/RequestContext.php create mode 100644 freescout-dist/vendor/symfony/routing/RequestContextAwareInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/RouteCollection.php create mode 100644 freescout-dist/vendor/symfony/routing/RouteCollectionBuilder.php create mode 100644 freescout-dist/vendor/symfony/routing/RouteCompiler.php create mode 100644 freescout-dist/vendor/symfony/routing/RouteCompilerInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/Router.php create mode 100644 freescout-dist/vendor/symfony/routing/RouterInterface.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Annotation/RouteTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/CompiledRouteTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/CustomCompiledRoute.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/annotated.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/bad_format.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/bar.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/controller/import__controller.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/controller/import_controller.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/controller/import_override_defaults.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/controller/override_defaults.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/controller/routing.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/controller/routing.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher0.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/empty.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/file_resource.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/foo.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/foo1.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/bar.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/bar.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/baz.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/baz.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/import_multiple.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/import_single.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/import_single.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_bar.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_baz.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/incomplete.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/missing_id.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/missing_path.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/null_values.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/php_dsl.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/validpattern.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/validpattern.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/validpattern.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/validresource.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/validresource.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/validresource.yml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Fixtures/withdoctype.xml create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/GlobFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Matcher/DumpedUrlMatcherTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/RequestContextTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/RouteCollectionTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/RouteCompilerTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/RouteTest.php create mode 100644 freescout-dist/vendor/symfony/routing/Tests/RouterTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Catalogue/AbstractOperation.php create mode 100644 freescout-dist/vendor/symfony/translation/Catalogue/MergeOperation.php create mode 100644 freescout-dist/vendor/symfony/translation/Catalogue/OperationInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/Catalogue/TargetOperation.php create mode 100644 freescout-dist/vendor/symfony/translation/Command/XliffLintCommand.php create mode 100644 freescout-dist/vendor/symfony/translation/DataCollector/TranslationDataCollector.php create mode 100644 freescout-dist/vendor/symfony/translation/DataCollectorTranslator.php create mode 100644 freescout-dist/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php create mode 100644 freescout-dist/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php create mode 100644 freescout-dist/vendor/symfony/translation/DependencyInjection/TranslatorPass.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/CsvFileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/DumperInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/FileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/IcuResFileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/IniFileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/JsonFileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/MoFileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/PhpFileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/PoFileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/QtFileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/XliffFileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Dumper/YamlFileDumper.php create mode 100644 freescout-dist/vendor/symfony/translation/Exception/ExceptionInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/Exception/InvalidArgumentException.php create mode 100644 freescout-dist/vendor/symfony/translation/Exception/InvalidResourceException.php create mode 100644 freescout-dist/vendor/symfony/translation/Exception/LogicException.php create mode 100644 freescout-dist/vendor/symfony/translation/Exception/NotFoundResourceException.php create mode 100644 freescout-dist/vendor/symfony/translation/Exception/RuntimeException.php create mode 100644 freescout-dist/vendor/symfony/translation/Extractor/AbstractFileExtractor.php create mode 100644 freescout-dist/vendor/symfony/translation/Extractor/ChainExtractor.php create mode 100644 freescout-dist/vendor/symfony/translation/Extractor/ExtractorInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/Extractor/PhpExtractor.php create mode 100644 freescout-dist/vendor/symfony/translation/Extractor/PhpStringTokenParser.php create mode 100644 freescout-dist/vendor/symfony/translation/Formatter/ChoiceMessageFormatterInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/Formatter/MessageFormatter.php create mode 100644 freescout-dist/vendor/symfony/translation/Formatter/MessageFormatterInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/IdentityTranslator.php create mode 100644 freescout-dist/vendor/symfony/translation/Interval.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/ArrayLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/CsvFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/FileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/IcuDatFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/IcuResFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/IniFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/JsonFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/LoaderInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/MoFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/PhpFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/PoFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/QtFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/XliffFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/YamlFileLoader.php create mode 100644 freescout-dist/vendor/symfony/translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd create mode 100644 freescout-dist/vendor/symfony/translation/Loader/schema/dic/xliff-core/xliff-core-2.0.xsd create mode 100644 freescout-dist/vendor/symfony/translation/Loader/schema/dic/xliff-core/xml.xsd create mode 100644 freescout-dist/vendor/symfony/translation/LoggingTranslator.php create mode 100644 freescout-dist/vendor/symfony/translation/MessageCatalogue.php create mode 100644 freescout-dist/vendor/symfony/translation/MessageCatalogueInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/MessageSelector.php create mode 100644 freescout-dist/vendor/symfony/translation/MetadataAwareInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/PluralizationRules.php create mode 100644 freescout-dist/vendor/symfony/translation/Reader/TranslationReader.php create mode 100644 freescout-dist/vendor/symfony/translation/Reader/TranslationReaderInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Catalogue/MergeOperationTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Catalogue/TargetOperationTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/DataCollectorTranslatorTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/DependencyInjection/TranslationDumperPassTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/DependencyInjection/TranslationExtractorPassTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/DependencyInjection/TranslationPassTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/CsvFileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/FileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/IcuResFileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/IniFileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/JsonFileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/MoFileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/PhpFileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/PoFileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/QtFileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/XliffFileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Dumper/YamlFileDumperTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Extractor/PhpExtractorTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Formatter/MessageFormatterTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/IdentityTranslatorTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/IntervalTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/CsvFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/IcuDatFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/IcuResFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/IniFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/JsonFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/LocalizedTestCase.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/MoFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/PhpFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/PoFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/QtFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/XliffFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Loader/YamlFileLoaderTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/LoggingTranslatorTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/MessageCatalogueTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/MessageSelectorTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/PluralizationRulesTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/TranslatorCacheTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/TranslatorTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Util/ArrayConverterTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/Writer/TranslationWriterTest.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/empty-translation.mo create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/empty-translation.po create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/empty.csv create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/empty.ini create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/empty.json create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/empty.mo create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/empty.po create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/empty.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/empty.yml create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/encoding.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/escaped-id-plurals.po create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/escaped-id.po create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/extractor/resource.format.engine create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/extractor/this.is.a.template.format.engine create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/extractor/translation.html.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/fuzzy-translations.po create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/invalid-xml-resources.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/malformed.json create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/messages.yml create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/messages_linear.yml create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/non-valid.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/non-valid.yml create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/plurals.mo create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/plurals.po create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resname.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resourcebundle/corrupted/resources.dat create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/en.res create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/fr.res create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resourcebundle/dat/resources.dat create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resourcebundle/res/en.res create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources-2.0-clean.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources-2.0-multi-segment-unit.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources-2.0.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources-clean.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources-notes-meta.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources-target-attributes.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources-tool-info.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources.csv create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources.dump.json create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources.ini create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources.json create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources.mo create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources.php create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources.po create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources.ts create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/resources.yml create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/valid.csv create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/with-attributes.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/withdoctype.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Tests/fixtures/withnote.xlf create mode 100644 freescout-dist/vendor/symfony/translation/Translator.php create mode 100644 freescout-dist/vendor/symfony/translation/TranslatorBagInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/TranslatorInterface.php create mode 100644 freescout-dist/vendor/symfony/translation/Util/ArrayConverter.php create mode 100644 freescout-dist/vendor/symfony/translation/Writer/TranslationWriter.php create mode 100644 freescout-dist/vendor/symfony/translation/Writer/TranslationWriterInterface.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/AmqpCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/ArgsStub.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/Caster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/ClassStub.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/ConstStub.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/CutArrayStub.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/CutStub.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/DOMCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/DateCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/DoctrineCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/EnumStub.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/ExceptionCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/FrameStub.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/LinkStub.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/MongoCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/PdoCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/PgSqlCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/RedisCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/ReflectionCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/ResourceCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/SplCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/StubCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/SymfonyCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/TraceStub.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Cloner/AbstractCloner.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Cloner/ClonerInterface.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Cloner/Cursor.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Cloner/DumperInterface.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Cloner/VarCloner.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Dumper/AbstractDumper.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Dumper/CliDumper.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Resources/functions/dump.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Caster/CasterTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Caster/DateCasterTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Caster/ExceptionCasterTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Caster/PdoCasterTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Caster/RedisCasterTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Caster/ReflectionCasterTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Caster/SplCasterTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Caster/StubCasterTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Caster/XmlReaderCasterTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Cloner/DataTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Dumper/FunctionsTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Fixtures/FooInterface.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Fixtures/GeneratorDemo.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Fixtures/NotLoadableClass.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Fixtures/Twig.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Fixtures/dumb-var.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml create mode 100644 freescout-dist/vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php create mode 100644 freescout-dist/vendor/symfony/var-dumper/VarDumper.php create mode 100644 freescout-dist/vendor/tedivm/jshrink/src/JShrink/Minifier.php create mode 100644 freescout-dist/vendor/tijsverkoyen/css-to-inline-styles/src/Css/Processor.php create mode 100644 freescout-dist/vendor/tijsverkoyen/css-to-inline-styles/src/Css/Property/Processor.php create mode 100644 freescout-dist/vendor/tijsverkoyen/css-to-inline-styles/src/Css/Property/Property.php create mode 100644 freescout-dist/vendor/tijsverkoyen/css-to-inline-styles/src/Css/Rule/Processor.php create mode 100644 freescout-dist/vendor/tijsverkoyen/css-to-inline-styles/src/Css/Rule/Rule.php create mode 100644 freescout-dist/vendor/tijsverkoyen/css-to-inline-styles/src/CssToInlineStyles.php create mode 100644 freescout-dist/vendor/tormjens/eventy/.travis.yml create mode 100644 freescout-dist/vendor/tormjens/eventy/src/EventBladeServiceProvider.php create mode 100644 freescout-dist/vendor/tormjens/eventy/src/EventServiceProvider.php create mode 100644 freescout-dist/vendor/tormjens/eventy/src/Events.php create mode 100644 freescout-dist/vendor/tormjens/eventy/src/Facades/Events.php create mode 100644 freescout-dist/vendor/vlucas/phpdotenv/src/Dotenv.php create mode 100644 freescout-dist/vendor/vlucas/phpdotenv/src/Exception/ExceptionInterface.php create mode 100644 freescout-dist/vendor/vlucas/phpdotenv/src/Exception/InvalidCallbackException.php create mode 100644 freescout-dist/vendor/vlucas/phpdotenv/src/Exception/InvalidFileException.php create mode 100644 freescout-dist/vendor/vlucas/phpdotenv/src/Exception/InvalidPathException.php create mode 100644 freescout-dist/vendor/vlucas/phpdotenv/src/Exception/ValidationException.php create mode 100644 freescout-dist/vendor/vlucas/phpdotenv/src/Validator.php create mode 100644 freescout-dist/vendor/watson/rememberable/src/Query/Builder.php create mode 100644 freescout-dist/vendor/watson/rememberable/src/Rememberable.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/.travis.yml create mode 100644 freescout-dist/vendor/webklex/laravel-imap/_config.yml create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/ClientManager.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/EncodingAliases.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Exceptions/ConnectionFailedException.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Exceptions/GetMessagesFailedException.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Exceptions/InvalidWhereQueryCriteriaException.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Exceptions/MessageSearchValidationException.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Exceptions/MethodNotFoundException.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Facades/Client.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Folder.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Providers/LaravelServiceProvider.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Query/Query.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Query/WhereQuery.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Support/AttachmentCollection.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Support/FlagCollection.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Support/FolderCollection.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Support/MessageCollection.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/IMAP/Support/PaginatedCollection.php create mode 100644 freescout-dist/vendor/webklex/laravel-imap/src/config/imap.php create mode 100644 freescout-dist/vendor/webklex/php-imap/.travis.yml create mode 100644 freescout-dist/vendor/webklex/php-imap/_config.yml create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Address.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Attribute.php create mode 100755 freescout-dist/vendor/webklex/php-imap/src/Client.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/ClientManager.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Connection/Protocols/LegacyProtocol.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Connection/Protocols/Protocol.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Connection/Protocols/ProtocolInterface.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/EncodingAliases.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/Event.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/FlagDeletedEvent.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/FlagNewEvent.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/FolderDeletedEvent.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/FolderMovedEvent.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/FolderNewEvent.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/MessageCopiedEvent.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/MessageDeletedEvent.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/MessageMovedEvent.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/MessageNewEvent.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Events/MessageRestoredEvent.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/AuthFailedException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/ConnectionFailedException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/EventNotFoundException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/FolderFetchingException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/GetMessagesFailedException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/InvalidMessageDateException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/InvalidWhereQueryCriteriaException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/MaskNotFoundException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/MessageContentFetchingException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/MessageFlagException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/MessageHeaderFetchingException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/MessageNotFoundException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/MessageSearchValidationException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/MethodNotFoundException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/MethodNotSupportedException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/NotSupportedCapabilityException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/ProtocolNotSupportedException.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Exceptions/RuntimeException.php create mode 100755 freescout-dist/vendor/webklex/php-imap/src/Folder.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/IMAP.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Part.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Query/Query.php create mode 100755 freescout-dist/vendor/webklex/php-imap/src/Query/WhereQuery.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Support/AttachmentCollection.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Support/FlagCollection.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Support/FolderCollection.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Support/Masks/AttachmentMask.php create mode 100755 freescout-dist/vendor/webklex/php-imap/src/Support/Masks/Mask.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Support/Masks/MessageMask.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Support/MessageCollection.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Support/PaginatedCollection.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/Traits/HasEvents.php create mode 100644 freescout-dist/vendor/webklex/php-imap/src/config/imap.php create mode 100644 freescout-dist/webpack.mix.js diff --git a/freescout-dist/.editorconfig b/freescout-dist/.editorconfig new file mode 100644 index 0000000..6f2164b --- /dev/null +++ b/freescout-dist/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.json] +insert_final_newline = false diff --git a/freescout-dist/.env.example b/freescout-dist/.env.example new file mode 100644 index 0000000..883337e --- /dev/null +++ b/freescout-dist/.env.example @@ -0,0 +1,47 @@ +#################################################################################################### +## If you want to use web installer **DO NOT** create `.env` file manually. +## If `.env` file exists in the root of your app, web installer won't run. +## +## Every time you are making changes in .env file, in order changes to take an effect you need to run: +## php artisan freescout:clear-cache +##################################################################################################### + +# Application URL +APP_URL=https://example.com + +# If you are using HTTPS, feel free to uncomment this line to improve security +#SESSION_SECURE_COOKIE=true + +# Enter your proxy address here if freescout.net is not available from your server +# (access to freescout.net is required to obtain official modules) +#APP_PROXY= + +# Custom headers to add to all outgoing emails. +#APP_CUSTOM_MAIL_HEADERS="IsTransactional:True;X-Custom-Header:value" + +# Uncomment if you have many folders and you are experiencing performance issues +#APP_UPDATE_FOLDER_COUNTERS_IN_BACKGROUND=true + +# Timezones: https://github.com/freescout-helpdesk/freescout/wiki/PHP-Timezones +# Comment it to use default timezone from php.ini +#APP_TIMEZONE=Europe/London + +# Comma separated list of trusted proxies for proper IP detection in FreeScout. +# To trust all proxies that connect to your server use single asterisk: * +# To trust ALL proxies, including those that are in a chain of forwarding use double asterisk: ** +#APP_TRUSTED_PROXIES=192.168.1.1,192.168.1.2,192.168.1.3 + +DB_CONNECTION=mysql +DB_HOST=localhost +DB_PORT=3306 +DB_DATABASE= +DB_USERNAME= +# Maximum password length is 50 characters +DB_PASSWORD= + +# Run the following console command to generate the key: php artisan key:generate +# Otherwise application will show the following error: "Whoops, looks like something went wrong" +APP_KEY= + +# Uncomment to see errors in your browser, don't forget to comment it back when debugging finished +#APP_DEBUG=true diff --git a/freescout-dist/.env.travis b/freescout-dist/.env.travis new file mode 100644 index 0000000..3045790 --- /dev/null +++ b/freescout-dist/.env.travis @@ -0,0 +1,10 @@ +APP_ENV=testing +APP_KEY=SomeRandomString7 + +DB_CONNECTION=testing +DB_TEST_USERNAME=root +DB_TEST_PASSWORD= + +CACHE_DRIVER=array +SESSION_DRIVER=array +QUEUE_DRIVER=sync \ No newline at end of file diff --git a/freescout-dist/.gitattributes b/freescout-dist/.gitattributes new file mode 100644 index 0000000..967315d --- /dev/null +++ b/freescout-dist/.gitattributes @@ -0,0 +1,5 @@ +* text=auto +*.css linguist-vendored +*.scss linguist-vendored +*.js linguist-vendored +CHANGELOG.md export-ignore diff --git a/freescout-dist/.gitcommit b/freescout-dist/.gitcommit new file mode 100644 index 0000000..b62fed1 --- /dev/null +++ b/freescout-dist/.gitcommit @@ -0,0 +1 @@ +28e2d659db742540723b7d6cea7f0261cfe34bf1 diff --git a/freescout-dist/.github/ISSUE_TEMPLATE/general_help_request.md b/freescout-dist/.github/ISSUE_TEMPLATE/general_help_request.md new file mode 100644 index 0000000..2db1823 --- /dev/null +++ b/freescout-dist/.github/ISSUE_TEMPLATE/general_help_request.md @@ -0,0 +1,23 @@ +--- +name: General Help Request +about: Create a general help request + +--- + + +PHP version: +FreeScout version: +Database: MySQL / PostgreSQL +Are you using CloudFlare: Yes / No \ No newline at end of file diff --git a/freescout-dist/.github/PULL_REQUEST_TEMPLATE.md b/freescout-dist/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..49a1813 --- /dev/null +++ b/freescout-dist/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,3 @@ +Keep in mind that pull requests should be sent to the `master` branch! See https://github.com/freescout-helpdesk/freescout/wiki/Development-Guide#github-workflow + +Now you can delete this text and type the description of your pull request... \ No newline at end of file diff --git a/freescout-dist/.github/workflows/lint-php.yml b/freescout-dist/.github/workflows/lint-php.yml new file mode 100644 index 0000000..de2c81f --- /dev/null +++ b/freescout-dist/.github/workflows/lint-php.yml @@ -0,0 +1,20 @@ +name: PHP Code Sniffer + +on: + workflow_dispatch: + +jobs: + build: + name: Lint PHP + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + tools: phpcs + + - name: Run check + run: phpcs diff --git a/freescout-dist/.github/workflows/test-pgsql.yml b/freescout-dist/.github/workflows/test-pgsql.yml new file mode 100644 index 0000000..4cf6462 --- /dev/null +++ b/freescout-dist/.github/workflows/test-pgsql.yml @@ -0,0 +1,59 @@ +name: Test App (PostgreSQL) + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + test: + name: Test App (PostgreSQL) + runs-on: ubuntu-latest + + env: + DB_CONNECTION: testing_pgsql + + services: + postgres: + image: postgres:latest + env: + POSTGRES_USER: freescout-test + POSTGRES_PASSWORD: freescout-test + POSTGRES_DB: freescout-test + ports: + - 5432:5432 + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + strategy: + matrix: + php: ['7.3', '7.4', '8.0', '8.1', '8.2'] + + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: pgsql, mbstring, xml, imap, zip, gd, curl, intl, json + + - name: Install composer dependencies + run: composer install --ignore-platform-reqs --no-interaction + + - name: Migrate and seed the database + run: | + php${{ matrix.php }} artisan migrate --force -n --database=testing_pgsql + php${{ matrix.php }} artisan db:seed --force -n --database=testing_pgsql + env: + DB_PORT: ${{ job.services.postgres.ports[5432] }} + + - name: Run PHP tests + run: php${{ matrix.php }} ./vendor/bin/phpunit + env: + DB_PORT: ${{ job.services.postgres.ports[5432] }} \ No newline at end of file diff --git a/freescout-dist/.github/workflows/test.yml b/freescout-dist/.github/workflows/test.yml new file mode 100644 index 0000000..6e327e7 --- /dev/null +++ b/freescout-dist/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: Test App (MySQL) + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + test: + name: Test App (MySQL) + runs-on: ubuntu-latest + strategy: + matrix: + php: ['7.3', '7.4', '8.0', '8.1', '8.2'] + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: mysql, mbstring, xml, imap, zip, gd, curl, intl, json + + - name: Start MySQL + run: | + sudo systemctl start mysql + + - name: Setup database + run: | + mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `freescout-test`;' + mysql -uroot -proot -e "CREATE USER 'freescout-test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'freescout-test';" + mysql -uroot -proot -e "GRANT ALL ON \`freescout-test\`.* TO 'freescout-test'@'localhost';" + mysql -uroot -proot -e 'FLUSH PRIVILEGES;' + + - name: Install composer dependencies + run: composer install --ignore-platform-reqs --no-interaction + + - name: Migrate and seed the database + run: | + php${{ matrix.php }} artisan migrate --force -n --database=testing + php${{ matrix.php }} artisan db:seed --force -n --database=testing + + - name: Run PHP tests + run: php${{ matrix.php }} ./vendor/bin/phpunit \ No newline at end of file diff --git a/freescout-dist/.gitignore b/freescout-dist/.gitignore new file mode 100644 index 0000000..29e5382 --- /dev/null +++ b/freescout-dist/.gitignore @@ -0,0 +1,36 @@ +/node_modules +/public/hot +/public/storage +/storage/*.key +# We are committing /vendor directory to make installation process super easy, even on a shared hosting: +# - https://www.codeenigma.com/build/blog/do-you-really-need-composer-production +# - https://getcomposer.org/doc/faqs/should-i-commit-the-dependencies-in-my-vendor-directory.md +/vendor/**/.git +#/vendor +/.idea +/.vagrant +Homestead.json +Homestead.yaml +npm-debug.log +yarn-error.log +.env +app/Console/Commands/Test* +/bootstrap/compiled.php +composer.phar +#composer.lock +.DS_Store +Thumbs.db +#/.htaccess +/public/css/builds/ +/public/js/builds/ +/public/.well-known +/Modules +/Modules/**/.git +/public/modules +/public/docs +/storage/.ignore_locales +/storage/.installed +/tools +.well-known +/resources/lang/module.* +.phpunit.result.cache \ No newline at end of file diff --git a/freescout-dist/.htaccess b/freescout-dist/.htaccess new file mode 100644 index 0000000..d92a72c --- /dev/null +++ b/freescout-dist/.htaccess @@ -0,0 +1,10 @@ +# On some hostings it is impossible to change web root directory +# so we rewrite all web requests into /public folder + + RewriteEngine on + # On some hostings it does not work as "public/$1", but works as "/public/$1". + # But we can't write it as "/public/$1" because in this case it does not work + # when application is installed in subdirectory. + RewriteCond %{REQUEST_URI} !/\.well\-known/?.* + RewriteRule (.*) public/$1 [L] + \ No newline at end of file diff --git a/freescout-dist/.travis.yml b/freescout-dist/.travis.yml new file mode 100644 index 0000000..e83a632 --- /dev/null +++ b/freescout-dist/.travis.yml @@ -0,0 +1,15 @@ +language: php + +php: + - 7.0 + - 7.2 + - 7.4 + - 8.0 + +before_script: + - cp .env.travis .env + - mysql -e 'create database homestead_test;' + +# phpunit testing is disabled as dist vendor is cleared by removing unneeded files +#script: +# - vendor/bin/phpunit \ No newline at end of file diff --git a/freescout-dist/LICENSE b/freescout-dist/LICENSE new file mode 100644 index 0000000..da7618d --- /dev/null +++ b/freescout-dist/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defences to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/freescout-dist/README.md b/freescout-dist/README.md new file mode 100644 index 0000000..df97bae --- /dev/null +++ b/freescout-dist/README.md @@ -0,0 +1,150 @@ +# Free Self-Hosted Zendesk & Help Scout Alternative + +
+ + +

+ +[![PHP version](https://freescout-helpdesk.github.io/img/badges/PHP-7.1%2B-blue.svg)](https://github.com/freescout-helpdesk/freescout#requirements) [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Ffreescout-helpdesk%2Ffreescout&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) + +
+ +**FreeScout** is the super lightweight and powerful free open source help desk and shared inbox built with PHP (Laravel framework). Now you can enjoy free Zendesk & Help Scout without giving up privacy or locking yourself into a service you don't control. FreeScout has been developed from scratch and is not using any copyrighted Help Scout or Zendesk materials. + +If you want to support the project feel free to **star this repository**. It helps to increase the visibility of the project and let people know that it is valuable. Thanks for your support! + +![FreeScout](https://freescout-helpdesk.github.io/img/screenshots/screenshot.png) + +## Table of Contents + * [Demo](#demo) + * [Features](#features) + * [Mobile Apps](#mobile-apps) + * [Requirements](#requirements) + * [Installation](#installation) + * [Cloud Hosted](#cloud-hosted) + * [Modules](#modules) + * [Tools & Integrations](#tools--integrations) + * [News & Updates](#news--updates) + * [Contributing](#contributing) + * [Screenshots](#screenshots) + +## Demo + +**[Live Demo](https://demo.freescout.net)** + +## Features + + * No limitations on the number of users, tickets, mailboxes, etc. + * 100% Mobile-friendly. + * Multilingual: English, Chinese, Croatian, Czech, Danish, Dutch, Finnish, French, German, Italian, Japanese, Korean, Norwegian, Persian, Polish, Portuguese, Russian, Spanish, Slovak, Swedish. + * Seamless email integration. + * Supports modern Microsoft Exchange authentication. + * Web installer & updater. + * Starred conversations. + * Forwarding conversations. + * Merging conversations. + * Moving conversations between mailboxes. + * Phone conversations. + * Sending new conversations to multiple recipients at once. + * Collision detection – notice is shown when two agents open the same conversation. + * Push notifications. + * Following a conversation. + * Auto reply. + * Internal notes. + * Automatic refreshing of the conversations list without the need to reload the page. + * Pasting screenshots from the clipboard into the reply area. + * Configuring notifications on a per user basis. + * Open tracking. + * Editing threads. + * Search. + * And more… + +Need anything else? Suggest features [here](https://freescout.net/request-feature/). + +## Mobile Apps + +Mobile apps support the same functionality and modules as the web version of your FreeScout installation. Both support agents and administrators can use mobile apps. + +Android App iOS App + +## Requirements + +FreeScout is a pure PHP/MySQL application, so it can be easily deployed even on a shared hosting. + + * Nginx / Apache / IIS + * PHP 7.1 - 8.2 + * MySQL 5.0+ / MariaDB 5.0+ / PostgreSQL + +There is no minimum system requirements (CPU / RAM) – FreeScout will run on any system. + +## Installation + +[Installation Guide](https://github.com/freescout-helpdesk/freescout/wiki/Installation-Guide) + +Images & one-click installs: + +* [Docker Image](http://freescout.net/docker/) +* [Softaculous](http://www.softaculous.com/apps/customersupport/FreeScout) (cPanel, Plesk, ISPmanager, H-Sphere, DirectAdmin, InterWorx) +* [Fantastico](http://ff3.netenberg.com/visitors/scripts/freescout/view) (cPanel, DirectAdmin, ISP Manager, ISP Config) +* [Cloudron](https://cloudron.io/store/net.freescout.cloudronapp.html) +* [Ubuntu](https://github.com/freescout-helpdesk/freescout/wiki/Installation-Guide#interactive-installation-bash-script-ubuntu) (bash script) + +## Cloud Hosted + +[Cloud Hosted FreeScout](https://freescout.net/cloud-hosted/) + +## Modules + +* [Official Modules](https://freescout.net/modules/) +* [Community Modules](https://freescout.net/community-modules/) + +## Tools & Integrations + + * [API](https://api-docs.freescout.net/) + * [Migrate to FreeScout](http://freescout.net/migrate/) (from any help desk) + * [Zapier](https://freescout.net/zapier/) + * [Make](https://freescout.net/make-integration/) (Integromat) + +## News & Updates + +Don't miss news, updates and new modules! + +[Email Newsletter](https://freescout.net/subscribe/) | [Facebook](https://freescout.net/facebook/) | [Twitter](https://freescout.net/twitter/) | [YouTube](https://freescout.net/youtube/) | [Telegram](https://freescout.net/telegram/) | [RSS](https://freescout.net/feed/) + +## Contributing + +* [Support the project by leaving a feedback](https://github.com/freescout-helpdesk/freescout/issues/288) +* [Development Guide](https://github.com/freescout-helpdesk/freescout/wiki/Development-Guide) +* [Todo list](https://github.com/freescout-helpdesk/freescout/labels/help%20wanted) +* [Translate](https://github.com/freescout-helpdesk/freescout/wiki/Translate) + +## Screenshots + +Dashboard: + +![Dashboard](https://freescout-helpdesk.github.io/img/screenshots/dashboard.png) + +Conversation: + +![Conversation](https://freescout-helpdesk.github.io/img/screenshots/conversation.png) + + +Mailbox connection settings page: + +![Mailbox connection settings page](https://freescout-helpdesk.github.io/img/screenshots/mailbox-connection.png) + +Notifications: + +![Notifications](https://freescout-helpdesk.github.io/img/screenshots/notifications.png) + +Push notification: + +![Push notification](https://freescout-helpdesk.github.io/img/screenshots/push.png) + +Web installer: + +![Web installer](https://freescout-helpdesk.github.io/img/screenshots/installer.png) + +Login page: + +![Login page](https://freescout-helpdesk.github.io/img/screenshots/freescout-login.png) \ No newline at end of file diff --git a/freescout-dist/SECURITY.md b/freescout-dist/SECURITY.md new file mode 100644 index 0000000..b445b39 --- /dev/null +++ b/freescout-dist/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Email at support@freescout.net \ No newline at end of file diff --git a/freescout-dist/app/ActivityLog.php b/freescout-dist/app/ActivityLog.php new file mode 100644 index 0000000..944d014 --- /dev/null +++ b/freescout-dist/app/ActivityLog.php @@ -0,0 +1,135 @@ +description) { + case self::DESCRIPTION_USER_LOGIN: + return __('Logged in'); + case self::DESCRIPTION_USER_LOGOUT: + return __('Logged out'); + case self::DESCRIPTION_USER_REGISTER: + return __('Registered'); + case self::DESCRIPTION_USER_LOCKED: + return __('Locked out'); + case self::DESCRIPTION_USER_LOGIN_FAILED: + return __('Failed login'); + case self::DESCRIPTION_USER_PASSWORD_RESET: + return __('Reset password'); + case self::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER: + return __('Error sending email to customer'); + case self::DESCRIPTION_EMAILS_SENDING_ERROR_TO_USER: + return __('Error sending email to user'); + case self::DESCRIPTION_EMAILS_SENDING_ERROR_INVITE: + return __('Error sending invitation email to user'); + case self::DESCRIPTION_EMAILS_SENDING_ERROR_PASSWORD_CHANGED: + return __('Error sending password changed notification to user'); + case self::DESCRIPTION_EMAILS_SENDING_ERROR_ALERT: + return __('Error sending alert'); + case self::DESCRIPTION_EMAILS_SENDING_WRONG_EMAIL: + return __('Error sending email to the user who replied to notification from wrong email'); + case self::DESCRIPTION_EMAILS_FETCHING_ERROR: + return __('Error fetching email'); + case self::DESCRIPTION_SYSTEM_ERROR: + return __('System error'); + case self::DESCRIPTION_USER_DELETED: + return __('Deleted user'); + default: + return $this->description; + break; + } + } + + /** + * Get title for the log record. + */ + public static function getLogTitle($log_name) + { + switch ($log_name) { + case self::NAME_USER: + return __('Users'); + case self::NAME_OUT_EMAILS: + return __('Outgoing Emails'); + case self::NAME_EMAILS_SENDING: + return __('Send Errors'); + case self::NAME_EMAILS_FETCHING: + return __('Fetch Errors'); + case self::NAME_SYSTEM: + return __('System'); + case self::NAME_APP_LOGS: + return __('App Logs'); + default: + return ucwords(str_replace('_', ' ', $log_name)); + } + } + + public static function formatColTitle($col) + { + $col = str_replace('_', ' ', $col); + $col = ucfirst($col); + + return $col; + } + + /** + * Get log names. + * + * @return [type] [description] + */ + public static function getLogNames() + { + return self::select('log_name')->distinct()->pluck('log_name')->toArray(); + } + + /** + * Get available log names. + * + * @return [type] [description] + */ + public static function getAvailableLogs($check_existing = true) + { + $available_logs = self::$available_logs; + if ($check_existing) { + $available_logs = array_merge($available_logs, self::getLogNames()); + } + + return array_unique(\Eventy::filter('activity_log.available_logs', self::$available_logs)); + } +} diff --git a/freescout-dist/app/Attachment.php b/freescout-dist/app/Attachment.php new file mode 100644 index 0000000..61773f5 --- /dev/null +++ b/freescout-dist/app/Attachment.php @@ -0,0 +1,450 @@ + self::TYPE_MESSAGE, + 'application' => self::TYPE_APPLICATION, + 'audio' => self::TYPE_AUDIO, + 'image' => self::TYPE_IMAGE, + 'video' => self::TYPE_VIDEO, + 'model' => self::TYPE_MODEL, + 'text' => self::TYPE_TEXT, + 'multipart' => self::TYPE_MULTIPART, + 'other' => self::TYPE_OTHER, + ]; + + public static $type_extensions = [ + self::TYPE_VIDEO => ['flv', 'mp4', 'm3u8', 'ts', '3gp', 'mov', 'avi', 'wmv'] + ]; + + public $timestamps = false; + + /** + * Get thread. + */ + public function thread() + { + return $this->belongsTo('App\Thread'); + } + + /** + * Save attachment to file and database. + */ + public static function create($file_name, $mime_type, $type, $content, $uploaded_file, $embedded = false, $thread_id = null, $user_id = null) + { + if (!$content && !$uploaded_file) { + return false; + } + + // Sanitize mime type. + // https://github.com/freescout-helpdesk/freescout/issues/3048 + $mime_duplicate = strpos($mime_type, "application/vnd.openxmlformats", 1); + if ($mime_duplicate) { + $mime_type = substr($mime_type, $mime_duplicate); + } + $mime_type = substr($mime_type, 0, self::MIME_TYPE_MAX_LENGTH); + + $orig_extension = pathinfo($file_name, PATHINFO_EXTENSION); + + // Add underscore to the extension if file has restricted extension. + $file_name = \Helper::sanitizeUploadedFileName($file_name, $uploaded_file, $content); + + // Replace some symbols in file name. + // Gmail can not load image if it contains spaces. + $file_name = preg_replace('/[ #\/]/', '_', $file_name); + // Replace soft hyphens. + $file_name = str_replace(html_entity_decode('­'), '_', $file_name); + + if (!$file_name) { + if (!$orig_extension) { + preg_match("/.*\/([^\/]+)$/", $mime_type, $m); + if (!empty($m[1])) { + $orig_extension = $m[1]; + } + } + $file_name = uniqid(); + if ($orig_extension) { + $file_name .= '.'.$orig_extension; + } + } + + // https://github.com/freescout-helpdesk/freescout/issues/2385 + // Fix for webklex/php-imap. + if ($file_name == 'undefined' && $mime_type == 'message/rfc822') { + $file_name = 'RFC822.eml'; + } + + // https://github.com/freescout-helpdesk/freescout/issues/1412#issuecomment-1658881493 + if ($file_name == 'undefined' && $mime_type == 'text/calendar') { + $file_name = 'calendar.ics'; + } + + if (strlen($file_name) > 255) { + $without_ext = pathinfo($file_name, PATHINFO_FILENAME); + $extension = pathinfo($file_name, PATHINFO_EXTENSION); + // 125 because file name may have unicode symbols. + $file_name = \Helper::substrUnicode($without_ext, 0, 125-strlen($extension)-1); + $file_name .= '.'.$extension; + } + + if (!$type) { + $type = self::detectType($mime_type, $orig_extension); + } + + $attachment = new self(); + $attachment->thread_id = $thread_id; + $attachment->user_id = $user_id; + $attachment->file_name = $file_name; + $attachment->mime_type = $mime_type; + $attachment->type = $type; + $attachment->embedded = $embedded; + $attachment->save(); + + $file_info = self::saveFileToDisk($attachment, $file_name, $content, $uploaded_file); + + $attachment->file_dir = $file_info['file_dir']; + $attachment->size = Storage::disk(self::DISK)->size($file_info['file_path']); + $attachment->save(); + + return $attachment; + } + + /** + * Save file to the disk and return file_dir. + */ + public static function saveFileToDisk($attachment, $file_name, $content, $uploaded_file) + { + // Save file from content or copy file. + // We have to keep file name as is, so if file exists we create extra folder. + // Examples: 1/2/3 + $file_dir = self::generatePath($attachment->id); + + $i = 0; + do { + $i++; + $file_path = self::DIRECTORY.DIRECTORY_SEPARATOR.$file_dir.$i.DIRECTORY_SEPARATOR.$file_name; + } while (Storage::disk(self::DISK)->exists($file_path)); + + $file_dir .= $i.DIRECTORY_SEPARATOR; + + if ($uploaded_file) { + $uploaded_file->storeAs(self::DIRECTORY.DIRECTORY_SEPARATOR.$file_dir, $file_name, ['disk' => self::DISK]); + } else { + Storage::disk(self::DISK)->put($file_path, $content); + } + + \Helper::sanitizeUploadedFileData($file_path, \Helper::getPrivateStorage(), $content); + + return [ + 'file_dir' => $file_dir, + 'file_path' => $file_path, + ]; + } + + /** + * Get file path. + * Examples: 1/2, 1/3. + * + * @param int $id + * + * @return string + */ + public static function generatePath($id) + { + $hash = md5($id); + + $first = -1; + $second = 0; + + for ($i = 0; $i < strlen($hash); $i++) { + if (is_numeric($hash[$i])) { + if ($first == -1) { + $first = $hash[$i]; + } else { + $second = $hash[$i]; + break; + } + } + } + if ($first == -1) { + $first = 0; + } + + return $first.DIRECTORY_SEPARATOR.$second.DIRECTORY_SEPARATOR; + } + + /** + * Detect attachment type by it's mime type. + * + * @param string $mime_type + * + * @return int + */ + public static function detectType($mime_type, $extension = '') + { + if (preg_match("/^text\//", $mime_type)) { + return self::TYPE_TEXT; + } elseif (preg_match("/^message\//", $mime_type)) { + return self::TYPE_MESSAGE; + } elseif (preg_match("/^application\//", $mime_type)) { + // This is tricky mime type. + // For .mp4 mime type can be application/octet-stream + if (!empty($extension) && in_array(strtolower($extension), self::$type_extensions[self::TYPE_VIDEO])) { + return self::TYPE_VIDEO; + } + return self::TYPE_APPLICATION; + } elseif (preg_match("/^audio\//", $mime_type)) { + return self::TYPE_AUDIO; + } elseif (preg_match("/^image\//", $mime_type)) { + return self::TYPE_IMAGE; + } elseif (preg_match("/^video\//", $mime_type)) { + return self::TYPE_VIDEO; + } elseif (preg_match("/^model\//", $mime_type)) { + return self::TYPE_MODEL; + } else { + return self::TYPE_OTHER; + } + } + + /** + * Convert type name to integer. + */ + public static function typeNameToInt($type_name) + { + if (!empty(self::$types[$type_name])) { + return self::$types[$type_name]; + } else { + return self::TYPE_OTHER; + } + } + + /** + * Get attachment full public URL. + * + * @return string + */ + public function url() + { + $file_url = Storage::url($this->getStorageFilePath()); + + // Fix percents. + // https://github.com/freescout-helpdesk/freescout/issues/3530 + $file_url = str_replace('%', '%25', $file_url); + + return $file_url.'?id='.$this->id.'&token='.$this->getToken(); + } + + /** + * Get hashed security token for the attachment. + */ + public function getToken() + { + // \Hash::make() may contain . and / symbols which may cause problems. + return md5(config('app.key').$this->id.$this->size); + } + + /** + * Outputs the current Attachment as download + */ + public function download($view = false) + { + $headers = []; + // #533 + //return $this->getDisk()->download($this->getStorageFilePath(), \Str::ascii($this->file_name)); + if ($view) { + $headers['Content-Disposition'] = ''; + } + $file_name = $this->file_name; + + if ($file_name == "RFC822"){ + $file_name = $file_name.'.eml'; + } + + return $this->getDisk()->download($this->getStorageFilePath(), $file_name, $headers); + } + + private function getDisk() { + return Storage::disk(self::DISK); + } + + /** + * Convert size into human readable format. + * + * @return string + */ + public function getSizeName() + { + return self::formatBytes($this->size); + } + + /** + * attachment/... + */ + public function getStorageFilePath() + { + return self::DIRECTORY.DIRECTORY_SEPARATOR.$this->file_dir.$this->file_name; + } + + /** + * /var/html/storage/app/attachment/... + */ + public function getLocalFilePath($full = true) + { + if ($full) { + return $this->getDisk()->path(self::DIRECTORY.DIRECTORY_SEPARATOR.$this->file_dir.$this->file_name); + } else { + return DIRECTORY_SEPARATOR.'storage'.DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.self::DIRECTORY.DIRECTORY_SEPARATOR.$this->file_dir.$this->file_name; + } + } + + /** + * Check if the attachment file actually exists on the disk. + */ + public function fileExists() + { + return $this->getDisk()->exists(self::DIRECTORY.DIRECTORY_SEPARATOR.$this->file_dir.$this->file_name); + } + + public static function formatBytes($size, $precision = 0) + { + $size = (int) $size; + if ($size > 0) { + $base = log($size) / log(1024); + $suffixes = [' b', ' KB', ' MB', ' GB', ' TB']; + + return round(pow(1024, $base - floor($base)), $precision).$suffixes[floor($base)]; + } else { + return $size; + } + } + + /** + * Delete attachments from disk and DB. + * Embeds are not taken into account. + * + * @param array $attachments + */ + public static function deleteByIds($attachment_ids) + { + if (!count($attachment_ids)) { + return; + } + $attachments = self::whereIn('id', $attachment_ids)->get(); + + // Delete from disk + self::deleteForever($attachments); + } + + /** + * Delete attachments by thread IDs. + */ + public static function deleteByThreadIds($thread_ids) + { + if (!count($thread_ids)) { + return; + } + $attachments = self::whereIn('thread_id', $thread_ids)->get(); + + // Delete from disk + self::deleteForever($attachments); + } + + public static function deleteForever($attachments) + { + // Delete from disk + foreach ($attachments as $attachment) { + $attachment->getDisk()->delete($attachment->getStorageFilePath()); + } + + // Delete from DB + self::whereIn('id', $attachments->pluck('id')->toArray())->delete(); + } + + /** + * Delete attachments and update Thread & Conversation. + */ + public static function deleteAttachments($attachments) + { + if (!$attachments instanceof \Illuminate\Support\Collection) { + $attachments = collect($attachments); + } + + foreach ($attachments as $attachment) { + if ($attachment->thread_id && $attachment->thread + && count($attachment->thread->attachments) <= 1 + ) { + $attachment->thread->has_attachments = false; + $attachment->thread->save(); + // Update conversation. + $conversation = $attachment->thread->conversation; + foreach ($conversation->threads as $thread) { + if ($thread->has_attachments) { + break 2; + } + } + $conversation->has_attachments = false; + $conversation->save(); + } + } + Attachment::deleteForever($attachments); + } + + /** + * Create a copy of the attachment and it's file. + */ + public function duplicate($thread_id = null) + { + $new_attachment = $this->replicate(); + if ($thread_id) { + $new_attachment->thread_id = $thread_id; + } + + $new_attachment->save(); + + try { + $attachment_file = new \Illuminate\Http\UploadedFile( + $this->getLocalFilePath(), $this->file_name, + null, null, true + ); + + $file_info = Attachment::saveFileToDisk($new_attachment, $new_attachment->file_name, '', $attachment_file); + + if (!empty($file_info['file_dir'])) { + $new_attachment->file_dir = $file_info['file_dir']; + $new_attachment->save(); + } + } catch (\Exception $e) { + \Helper::logException($e); + } + + return $new_attachment; + } + + public function getFileContents() + { + return $this->getDisk()->get($this->getStorageFilePath()); + } +} diff --git a/freescout-dist/app/Broadcasting/Broadcasters/PolycastBroadcaster.php b/freescout-dist/app/Broadcasting/Broadcasters/PolycastBroadcaster.php new file mode 100644 index 0000000..51e5b51 --- /dev/null +++ b/freescout-dist/app/Broadcasting/Broadcasters/PolycastBroadcaster.php @@ -0,0 +1,127 @@ +db = $app['db']; + if (\Config::get('broadcasting.connections.polycast.delete_old')) { + $this->delete_old = \Config::get('broadcasting.connections.polycast.delete_old'); + } + } + + /** + * Broadcast is called when the queued job is processed. + */ + public function broadcast(array $channels, $event, array $payload = []) + { + // delete events older than two minutes + \DB::table('polycast_events')->where('created_at', '<', Carbon::now()->subMinutes($this->delete_old)->toDateTimeString())->delete(); + + // insert the new event + \DB::table('polycast_events')->insert([ + 'channels' => json_encode($channels), + 'event' => $event, + 'payload' => json_encode($payload), + 'created_at' => Carbon::now()->toDateTimeString(), + ]); + } + + /** + * Authenticate the incoming request for a given channel. + * + * @param \Illuminate\Http\Request $request + * + * @return mixed + */ + public function auth($request) + { + // For connect request + if (empty($request->channels)) { + return true; + } + + // Check all channels + foreach ($request->channels as $channel_name => $channel_info) { + // Copied from Illuminate\Broadcasting\Broadcasters\PusherBroadcaster + if (Str::startsWith($channel_name, ['private-', 'presence-']) && + !$request->user()) { + + throw new AccessDeniedHttpException(); + } + + if (Str::startsWith($channel_name, ['private-', 'presence-'])) { + $channelName = Str::startsWith($channel_name, 'private-') + ? Str::replaceFirst('private-', '', $channel_name) + : Str::replaceFirst('presence-', '', $channel_name); + // This throws an exception if needed. + parent::verifyUserCanAccessChannel( + $request, $channelName + ); + } + } + + return true; + } + + /** + * Return the valid authentication response. + * + * @param \Illuminate\Http\Request $request + * @param mixed $result + * + * @return mixed + */ + public function validAuthenticationResponse($request, $result) + { + // By some reason this is never called + return false; + + // Copied from Illuminate\Broadcasting\Broadcasters\RedisBroadcaster + // if (is_bool($result)) { + // return json_encode($result); + // } + + // return json_encode(['channel_data' => [ + // 'user_id' => $request->user()->getAuthIdentifier(), + // 'user_info' => $result, + // ]]); + } + + public function isDeferred() + { + return false; + } + + /* + * Created as there was an error: + * "Call to undefined method App\Broadcasting\Broadcasters\PolycastBroadcaster::channel()" + * + * It is called from routes/channels.php + */ + // public function channel($channel, callable $callback) + // { + // return true; + // //return (int) $user->id === (int) $id; + // } +} diff --git a/freescout-dist/app/Channels/RealtimeBroadcastChannel.php b/freescout-dist/app/Channels/RealtimeBroadcastChannel.php new file mode 100644 index 0000000..d96c013 --- /dev/null +++ b/freescout-dist/app/Channels/RealtimeBroadcastChannel.php @@ -0,0 +1,29 @@ +getData($notifiable, $notification); + + $event = new RealtimeBroadcastNotificationCreated( + $notifiable, $notification, is_array($message) ? $message : $message->data + ); + + return $this->events->dispatch($event); + } +} diff --git a/freescout-dist/app/Console/Commands/AfterAppUpdate.php b/freescout-dist/app/Console/Commands/AfterAppUpdate.php new file mode 100644 index 0000000..48dcc69 --- /dev/null +++ b/freescout-dist/app/Console/Commands/AfterAppUpdate.php @@ -0,0 +1,44 @@ +call('freescout:clear-cache'); + $this->call('migrate', ['--force' => true]); + $this->call('queue:restart'); + } +} diff --git a/freescout-dist/app/Console/Commands/Build.php b/freescout-dist/app/Console/Commands/Build.php new file mode 100644 index 0000000..f7f97e9 --- /dev/null +++ b/freescout-dist/app/Console/Commands/Build.php @@ -0,0 +1,43 @@ +call('freescout:generate-vars'); + $this->call('laroute:generate'); + } +} diff --git a/freescout-dist/app/Console/Commands/CheckConvViewers.php b/freescout-dist/app/Console/Commands/CheckConvViewers.php new file mode 100644 index 0000000..b41f388 --- /dev/null +++ b/freescout-dist/app/Console/Commands/CheckConvViewers.php @@ -0,0 +1,117 @@ + $conv_data) { + if (empty($conv_data) || !is_array($conv_data)) { + continue; + } + foreach ($conv_data as $user_id => $data) { + + if (!isset($data['t']) || !isset($data['r'])) { + continue; + } + + $view_date = Carbon::createFromFormat('Y-m-d H:i:s', $data['t']); + + if ($view_date && $now->diffInSeconds($view_date) > 25) { + // Remove user from viewers. + unset($cache_data[$conversation_id][$user_id]); + if (empty($cache_data[$conversation_id])) { + unset($cache_data[$conversation_id]); + } + $need_update = true; + + \Cache::forget('conv_view_'.$user_id.'_'.$conversation_id); + + // Create event to let other users know that user finished viewing conversation. + $notification_data = [ + 'conversation_id' => $conversation_id, + 'user_id' => $user_id, + ]; + event(new \App\Events\RealtimeConvViewFinish($notification_data)); + + \Eventy::action('conversation.view.finish', $conversation_id, $user_id, $now->diffInSeconds($view_date)); + } + } + } + + if ($need_update) { + // Update conversation cache data. + \Cache::put($cache_key, $cache_data, 20 /*minutes*/); + } + /*$cache_key = 'conv_view_'.$this->user_id.'_'.$this->conversation_id; + $cache_data = \Cache::get($cache_key); + + if (!isset($cache_data['t']) || !isset($cache_data['r'])) { + return; + } + + $view_date = Carbon::createFromFormat('Y-m-d H:i:s', $cache_data['t']); + $now = Carbon::now(); + + if ($view_date && $now->diffInSeconds($view_date) > 30) { + $cache_key = 'conv_view'; + if (!empty($cache_data[$this->conversation_id]) && !empty($cache_data[$this->conversation_id][$this->user_id])) { + // Remove user from viewers. + unset($cache_data[$this->conversation_id][$this->user_id]); + + // Update conversation cache data. + \Cache::put($cache_key, $cache_data, 1); + } + + // Create event to let other users know that user finished viewing conversation. + $notification_data = [ + 'conversation_id' => $conversation->id, + 'user_id' => $user->id, + ]; + event(new \App\Events\RealtimeConvViewFinish($notification_data)); + }*/ + } +} diff --git a/freescout-dist/app/Console/Commands/CheckRequirements.php b/freescout-dist/app/Console/Commands/CheckRequirements.php new file mode 100644 index 0000000..84fbf6f --- /dev/null +++ b/freescout-dist/app/Console/Commands/CheckRequirements.php @@ -0,0 +1,63 @@ +comment("PHP Version"); + $this->line(' '.str_pad(phpversion(), 30, '.'). ' '.(version_compare(phpversion(), config('installer.core.minPhpVersion'), '>=') ? 'OK' : 'NOT FOUND'), false); + + $this->comment("PHP Extensions"); + $this->output($php_extensions); + + // Functions. + $functions = \Helper::checkRequiredFunctions(); + + $this->comment("Functions"); + $this->output($functions); + $this->line(''); + } + + public function output($items) + { + foreach ($items as $item => $status) { + $this->line(' '.str_pad($item, 30, '.'). ' '.($status ? 'OK' : 'NOT FOUND'), false); + } + } +} diff --git a/freescout-dist/app/Console/Commands/CleanNotificationsTable.php b/freescout-dist/app/Console/Commands/CleanNotificationsTable.php new file mode 100644 index 0000000..35ebed8 --- /dev/null +++ b/freescout-dist/app/Console/Commands/CleanNotificationsTable.php @@ -0,0 +1,48 @@ +where('created_at', '<', \Carbon\Carbon::now()->modify(self::PERIOD)) + ->whereNotNull('read_at') + ->delete(); + + $this->info('['.date('Y-m-d H:i:s').'] Deleted old read notifications for: '.self::PERIOD); + } +} diff --git a/freescout-dist/app/Console/Commands/CleanSendLog.php b/freescout-dist/app/Console/Commands/CleanSendLog.php new file mode 100644 index 0000000..6deaa1f --- /dev/null +++ b/freescout-dist/app/Console/Commands/CleanSendLog.php @@ -0,0 +1,46 @@ +modify(self::PERIOD))->delete(); + + $this->info('['.date('Y-m-d H:i:s').'] Deleted send logs: '.self::PERIOD); + } +} diff --git a/freescout-dist/app/Console/Commands/CleanTmp.php b/freescout-dist/app/Console/Commands/CleanTmp.php new file mode 100644 index 0000000..9999f5d --- /dev/null +++ b/freescout-dist/app/Console/Commands/CleanTmp.php @@ -0,0 +1,46 @@ +comment("Done"); + } +} diff --git a/freescout-dist/app/Console/Commands/ClearCache.php b/freescout-dist/app/Console/Commands/ClearCache.php new file mode 100644 index 0000000..2c6adc2 --- /dev/null +++ b/freescout-dist/app/Console/Commands/ClearCache.php @@ -0,0 +1,62 @@ +call('clear-compiled'); + $this->call('cache:clear'); + $this->call('view:clear'); + if ($this->option('doNotCacheConfig')) { + $this->call('config:clear'); + } else { + $this->call('config:cache'); + // Laravel users `require` function to include config.php + // If opcache is being used for few seconds config.php is being cached. + if (function_exists('opcache_invalidate')) { + opcache_invalidate(app()->getCachedConfigPath()); + } + } + // Regenerate vars to get new data from .env + if (!$this->option('doNotGenerateVars')) { + $this->call('freescout:generate-vars'); + } + // This should not be done during installation. + if (\Helper::isInstalled()) { + \Helper::queueWorkerRestart(); + } + } +} diff --git a/freescout-dist/app/Console/Commands/CreateUser.php b/freescout-dist/app/Console/Commands/CreateUser.php new file mode 100644 index 0000000..b7de6be --- /dev/null +++ b/freescout-dist/app/Console/Commands/CreateUser.php @@ -0,0 +1,102 @@ +role = $this->option('role'); + if ($user->role ) { + if (!in_array($user->role , User::$roles)) { + $this->error('Invalid role'); + return false; + } + } else { + $user->role = $this->ask('User role (admin/user)', 'admin'); + while (!in_array($user->role, User::$roles)) { + $this->error('Invalid role'); + $user->role = $this->ask('Please enter valid role'); + } + } + $user->role = array_flip(User::$roles)[$user->role]; + + $user->first_name = $this->option('firstName') ? $this->option('firstName') : $this->ask('User first name'); + $user->last_name = $this->option('lastName') ? $this->option('lastName') : $this->ask('User last name'); + + $user->email = $this->option('email'); + if ($user->email) { + if (!filter_var($user->email, FILTER_VALIDATE_EMAIL)) { + $this->error('Invalid email address'); + return false; + } + } else { + $user->email = $this->ask('User email address'); + while (!filter_var($user->email, FILTER_VALIDATE_EMAIL)) { + $this->error('Incorrect email address'); + $user->email = $this->ask('Please enter valid email address'); + } + } + + $user->password = \Hash::make($this->option('password') ? $this->option('password') : $this->secret('User password')); + + if ($this->confirm('Do you want to create the user?', true)) { + if ($user->isAdmin()) { + $user->invite_state = User::INVITE_STATE_ACTIVATED; + } + + try { + $user->save(); + } catch (\Exception $e) { + $this->line($e->getMessage()); + $this->error('User already exists.'); + return false; + } + } + + $this->info('User created with id: '.$user->id); + + return true; + } +} diff --git a/freescout-dist/app/Console/Commands/FetchEmails.php b/freescout-dist/app/Console/Commands/FetchEmails.php new file mode 100644 index 0000000..3b68f30 --- /dev/null +++ b/freescout-dist/app/Console/Commands/FetchEmails.php @@ -0,0 +1,1503 @@ +line('['.date('Y-m-d H:i:s').'] Fetching '.($this->option('unseen') ? 'UNREAD' : 'ALL').' emails for the last '.$this->option('days').' days.'); + + $this->extra_import = []; + + if (Mailbox::getInProtocols() === Mailbox::$in_protocols) { + $this->mailboxes = Mailbox::get(); + } else { + // Get active mailboxes with the default in_protocols + $this->mailboxes = Mailbox::whereIn('in_protocol', array_keys(Mailbox::$in_protocols))->get(); + } + + // https://github.com/freescout-helpdesk/freescout/issues/2563 + // Add small delay between connections to avoid blocking by mail servers, + // especially when there many mailboxes. + // Microseconds: 1 second = 1 000 000 microseconds. + $sleep = 20000; + + foreach ($this->mailboxes as $mailbox) { + if (!$mailbox->isInActive()) { + continue; + } + + $sleep += 20000; + if ($sleep > 500000) { + $sleep = 500000; + } + + $this->info('['.date('Y-m-d H:i:s').'] Mailbox: '.$mailbox->name); + + $this->mailbox = $mailbox; + + try { + $this->fetch($mailbox); + } catch (\Exception $e) { + $successfully = false; + $this->logError('Error: '.$e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')').')'; + } + + usleep($sleep); + } + + // Import emails sent to several mailboxes at once. + if (count($this->extra_import)) { + $this->line('['.date('Y-m-d H:i:s').'] Importing emails sent to several mailboxes at once: '.count($this->extra_import)); + foreach ($this->extra_import as $i => $extra_import) { + $this->line('['.date('Y-m-d H:i:s').'] '.($i+1).') '.$extra_import['message']->getSubject()); + $this->processMessage($extra_import['message'], $extra_import['message_id'], $extra_import['mailbox'], [], true); + } + } + + if ($successfully && count($this->mailboxes)) { + Option::set('fetch_emails_last_successful_run', $now); + } + + // Middleware Terminate handler is not launched for commands, + // so we need to run processing subscription events manually + Subscription::processEvents(); + + $this->info('['.date('Y-m-d H:i:s').'] Fetching finished'); + + $this->extra_import = []; + $this->mailbox = null; + $this->mailboxes = []; + } + + public function fetch($mailbox) + { + $no_charset = false; + + $client = \MailHelper::getMailboxClient($mailbox); + + // Connect to the Server + $client->connect(); + + $folders = []; + + // Fetch emails from custom IMAP folders. + //if ($mailbox->in_protocol == Mailbox::IN_PROTOCOL_IMAP) { + $imap_folders = $mailbox->getInImapFolders(); + + foreach ($imap_folders as $folder_name) { + $folder = null; + try { + $folder = \MailHelper::getImapFolder($client, $folder_name); + } catch (\Exception $e) { + // Just log error and continue. + $this->error('['.date('Y-m-d H:i:s').'] Could not get mailbox IMAP folder: '.$folder_name); + } + + if ($folder) { + $folders[] = $folder; + } + } + // try { + // //$folders = $client->getFolders(); + // } catch (\Exception $e) { + // // Do nothing + // } + + $unseen = \Eventy::filter('fetch_emails.unseen', $this->option('unseen'), $mailbox); + if ($unseen != $this->option('unseen')) { + $this->line('['.date('Y-m-d H:i:s').'] Fetching: '.($unseen ? 'UNREAD' : 'ALL')); + } + + foreach ($folders as $folder) { + $this->line('['.date('Y-m-d H:i:s').'] Folder: '.$folder->name); + + // Requesting emails by bunches allows to fetch large amounts of emails + // without problems with memory. + $page = 0; + do { + // Get messages. + $last_error = ''; + $messages = collect([]); + + try { + $messages_query = $folder->query()->since(now()->subDays($this->option('days')))->leaveUnread(); + if ($unseen) { + $messages_query->unseen(); + } + if ($no_charset) { + $messages_query->setCharset(null); + } + $messages_query->limit(self::PAGE_SIZE, $page); + + $messages = $messages_query->get(); + + if (method_exists($client, 'getLastError')) { + $last_error = $client->getLastError(); + } + } catch (\Exception $e) { + $last_error = $e->getMessage(); + } + + if ($last_error && stristr($last_error, 'The specified charset is not supported')) { + $errors_count = count($client->getErrors()); + // Solution for MS mailboxes. + // https://github.com/freescout-helpdesk/freescout/issues/176 + $messages_query = $folder->query()->since(now()->subDays($this->option('days')))->leaveUnread()->setCharset(null); + if ($unseen) { + $messages_query->unseen(); + } + $messages = $messages_query->get(); + + $no_charset = true; + if (count($client->getErrors()) > $errors_count) { + $last_error = $client->getLastError(); + } else { + $last_error = null; + } + } + + if ($last_error && !\Str::startsWith($last_error, 'Mailbox is empty')) { + // Throw exception for INBOX only + if ($folder->name == 'INBOX' && !$messages) { + throw new \Exception($last_error, 1); + } else { + $this->error('['.date('Y-m-d H:i:s').'] '.$last_error); + $this->logError('Folder: '.$folder->name.'; Error: '.$last_error); + } + } + + $this->line('['.date('Y-m-d H:i:s').'] Fetched: '.count($messages)); + + $message_index = 1; + + // We have to sort messages manually, as they can be in non-chronological order + $messages = $this->sortMessage($messages); + foreach ($messages as $message_id => $message) { + $this->line('['.date('Y-m-d H:i:s').'] '.$message_index.') '.$message->getSubject()); + $message_index++; + + $dest_mailbox = \Eventy::filter('fetch_emails.mailbox_to_save_message', $mailbox, $folder); + $this->processMessage($message, $message_id, $dest_mailbox, $this->mailboxes); + } + $page++; + } while (count($messages) == self::PAGE_SIZE); + } + + $client->disconnect(); + } + + public function processMessage($message, $message_id, $mailbox, $mailboxes, $extra = false) + { + try { + + // From - $from is the plain text email. + $from = $message->getReplyTo(); + + if (!$from + // https://github.com/freescout-helpdesk/freescout/issues/3101 + || !($reply_to = $this->formatEmailList($from)) + || empty($reply_to[0]) + || preg_match('/^.+@unknown$/', $reply_to[0]) + ) { + $from = $message->getFrom(); + } + // https://github.com/freescout-helpdesk/freescout/issues/2833 + /*else { + // If this is an auto-responder do not use Reply-To as sender email. + // https://github.com/freescout-helpdesk/freescout/issues/2826 + $headers = $this->headerToStr($message->getHeader()); + if (\MailHelper::isAutoResponder($headers)) { + $from = $message->getFrom(); + } + }*/ + + if ($from) { + $from = $this->formatEmailList($from); + } + + if (!$from) { + $this->logError('From is empty'); + $this->setSeen($message, $mailbox); + return; + } else { + $from = $from[0]; + } + + // Message-ID can be empty. + // https://stackoverflow.com/questions/8513165/php-imap-do-emails-have-to-have-a-messageid + if (!$message_id) { + // Generate artificial Message-ID. + $message_id = \MailHelper::generateMessageId($from, $message->getRawBody()); + $this->line('['.date('Y-m-d H:i:s').'] Message-ID is empty, generated artificial Message-ID: '.$message_id); + } + + $duplicate_message_id = false; + + // Special hack to allow threading into conversations Jira messages. + // https://github.com/freescout-helpdesk/freescout/issues/2927 + // + // Jira does not properly populate Reference / In-Reply-To headers. + // When Jira sends a reply the In-Reply-To header is set to: + // JIRA.$\{issue-id}.$\{issue-created-date-millis}@$\{host} + // + // If we see the first message of a ticket we change the Message-ID, + // so all follow-ups in the ticket are nicely threaded. + $jira_message_id = preg_replace('/^(JIRA\.\d+\.\d+)\..*(@Atlassian.JIRA)/', '\1\2', $message_id); + if ($jira_message_id != $message_id) { + if (!Thread::where('message_id', $jira_message_id)->exists()) { + $message_id = $jira_message_id; + } + } + + if (!$extra) { + $duplicate_message_id = Thread::where('message_id', $message_id)->first(); + } + + // Mailbox has been mentioned in Bcc. + if (!$extra && $duplicate_message_id) { + + $recipients = array_merge( + $this->formatEmailList($message->getTo()), + $this->formatEmailList($message->getCc()) + ); + + if (!in_array(Email::sanitizeEmail($mailbox->email), $recipients) + // Make sure that previous email has been imported into other mailbox. + && $duplicate_message_id->conversation + && $duplicate_message_id->conversation->mailbox_id != $mailbox->id + ) { + $extra = true; + $duplicate_message_id = null; + } + } + + // Gnerate artificial Message-ID if importing same email into several mailboxes. + if ($extra) { + // Generate artificial Message-ID. + $message_id = \MailHelper::generateMessageId(strstr($message_id, '@') ? $message_id : $from, $mailbox->id.$message_id); + $this->line('['.date('Y-m-d H:i:s').'] Generated artificial Message-ID: '.$message_id); + } + + // Check if message already fetched. + if ($duplicate_message_id) { + $this->line('['.date('Y-m-d H:i:s').'] Message with such Message-ID has been fetched before: '.$message_id); + $this->setSeen($message, $mailbox); + return; + } + + // Detect prev thread + $is_reply = false; + $prev_thread = null; + $user_id = null; + $user = null; // for user reply only + $message_from_customer = true; + $in_reply_to = $message->getInReplyTo(); + $references = $message->getReferences(); + $attachments = $message->getAttachments(); + $html_body = ''; + + // Is it a bounce message + $is_bounce = false; + + // Determine previous Message-ID + $prev_message_id = ''; + if ($in_reply_to) { + $prev_message_id = trim($in_reply_to, '<>'); + } elseif ($references) { + if (!is_array($references)) { + $references = array_filter(preg_split('/[, <>]/', $references)); + } + // Find first non-empty reference + if (is_array($references)) { + foreach ($references as $reference) { + if (!empty(trim($reference))) { + $prev_message_id = trim($reference); + break; + } + } + } + } + + // Some mail service providers change Message-ID of the outgoing email, + // so we are passing Message-ID in marker in body. + $reply_prefixes = [ + \MailHelper::MESSAGE_ID_PREFIX_NOTIFICATION, + \MailHelper::MESSAGE_ID_PREFIX_REPLY_TO_CUSTOMER, + \MailHelper::MESSAGE_ID_PREFIX_AUTO_REPLY, + ]; + + // Try to get previous message ID from marker in body. + if (!$prev_message_id || !preg_match('/^('.implode('|', $reply_prefixes).')\-(\d+)\-/', $prev_message_id)) { + $html_body = $message->getHTMLBody(false); + $marker_message_id = \MailHelper::fetchMessageMarkerValue($html_body); + + if ($marker_message_id) { + $prev_message_id = $marker_message_id; + } + } + + // Bounce detection. + $bounced_message_id = null; + if ($message->hasAttachments()) { + // Detect bounce by attachment. + // Check all attachments. + foreach ($attachments as $attachment) { + if (!empty(Attachment::$types[$attachment->getType()]) && Attachment::$types[$attachment->getType()] == Attachment::TYPE_MESSAGE + ) { + if ( + // Checking the name will lead to mistakes if someone attaches a file with such name. + // Dashes are converted to space. + //in_array(strtoupper($attachment->getName()), ['RFC822', 'DELIVERY STATUS', 'DELIVERY STATUS NOTIFICATION', 'UNDELIVERED MESSAGE']) + preg_match('/delivery-status/', strtolower($attachment->content_type)) + // 7.3.1 The Message/rfc822 (primary) subtype. A Content-Type of "message/rfc822" indicates that the body contains an encapsulated message, with the syntax of an RFC 822 message + //|| $attachment->content_type == 'message/rfc822' + ) { + $is_bounce = true; + + $this->line('['.date('Y-m-d H:i:s').'] Bounce detected by attachment content-type: '.$attachment->content_type); + + // Try to get Message-ID of the original email. + if (!$bounced_message_id) { + //print_r(\MailHelper::parseHeaders($attachment->getContent())); + $bounced_message_id = \MailHelper::getHeader($attachment->getContent(), 'message_id'); + } + } + } + } + } + $message_header = $this->headerToStr($message->getHeader()); + + // Check Content-Type header. + if (!$is_bounce && $message_header) { + if (\MailHelper::detectBounceByHeaders($message_header)) { + $is_bounce = true; + } + } + // Check message's From field. + if (!$is_bounce) { + if ($message->getFrom()) { + $original_from = $this->formatEmailList($message->getFrom()); + $original_from = $original_from[0]; + $is_bounce = preg_match('/^mailer\-daemon@/i', $original_from); + if ($is_bounce) { + $this->line('['.date('Y-m-d H:i:s').'] Bounce detected by From header: '.$original_from); + } + } + } + // Check Return-Path header + if (!$is_bounce && preg_match("/^Return\-Path: <>/i", $message_header)) { + $this->line('['.date('Y-m-d H:i:s').'] Bounce detected by Return-Path header.'); + $is_bounce = true; + } + + if ($is_bounce && !$bounced_message_id) { + foreach ($attachments as $attachment_msg) { + // 7.3.1 The Message/rfc822 (primary) subtype. A Content-Type of "message/rfc822" indicates that the body contains an encapsulated message, with the syntax of an RFC 822 message + if ($attachment_msg->content_type == 'message/rfc822') { + $bounced_message_id = \MailHelper::getHeader($attachment_msg->getContent(), 'message_id'); + if ($bounced_message_id) { + break; + } + } + } + } + + // Is it a message from Customer or User replied to the notification + preg_match('/^'.\MailHelper::MESSAGE_ID_PREFIX_NOTIFICATION."\-(\d+)\-(\d+)\-/", $prev_message_id, $m); + + if (!$is_bounce && !empty($m[1]) && !empty($m[2])) { + // Reply from User to the notification + $prev_thread = Thread::find($m[1]); + $user_id = $m[2]; + $user = User::find($user_id); + $message_from_customer = false; + $is_reply = true; + + if (!$user) { + $this->logError('User not found: '.$user_id); + $this->setSeen($message, $mailbox); + return; + } + $this->line('['.date('Y-m-d H:i:s').'] Message from: User'); + } else { + // Message from Customer or User replied to his reply to notification + $this->line('['.date('Y-m-d H:i:s').'] Message from: Customer'); + + if (!$is_bounce) { + if ($prev_message_id) { + $prev_thread_id = ''; + + // Customer replied to the email from user + preg_match('/^'.\MailHelper::MESSAGE_ID_PREFIX_REPLY_TO_CUSTOMER."\-(\d+)\-([a-z0-9]+)@/", $prev_message_id, $m); + // Simply checking thread_id from message_id was causing an issue when + // customer was sending a message from FreeScout - the message was + // connected to the wrong conversation. + if (!empty($m[1]) && !empty($m[2])) { + $message_id_hash = $m[2]; + if (strlen($message_id_hash) == 16) { + if ($message_id_hash == \MailHelper::getMessageIdHash($m[1])) { + $prev_thread_id = $m[1]; + } + } else { + // Backward compatibility. + $prev_thread_id = $m[1]; + } + } + + // Customer replied to the auto reply + if (!$prev_thread_id) { + preg_match('/^'.\MailHelper::MESSAGE_ID_PREFIX_AUTO_REPLY."\-(\d+)\-([a-z0-9]+)@/", $prev_message_id, $m); + if (!empty($m[1]) && !empty($m[2])) { + $message_id_hash = $m[2]; + if (strlen($message_id_hash) == 16) { + if ($message_id_hash == \MailHelper::getMessageIdHash($m[1])) { + $prev_thread_id = $m[1]; + } + } else { + // Backward compatibility. + $prev_thread_id = $m[1]; + } + } + } + + if ($prev_thread_id) { + $prev_thread = Thread::find($prev_thread_id); + } else { + // Customer replied to his own message + $prev_thread = Thread::where('message_id', $prev_message_id)->first(); + } + + // Reply from user to his reply to the notification + if (!$prev_thread + && ($prev_thread = Thread::where('message_id', $prev_message_id)->first()) + && $prev_thread->created_by_user_id + && $prev_thread->created_by_user->hasEmail($from) + ) { + $user_id = $user->id; + $message_from_customer = false; + $is_reply = true; + } + } + if (!empty($prev_thread)) { + $is_reply = true; + } + } + } + + // Make sure that prev_thread belongs to the current mailbox. + // Problems may arise when forwarding conversation for example. + // + // For replies to email notifications it's allowed to have prev_thread in + // another mailbox as conversation can be moved. + // https://github.com/freescout-helpdesk/freescout/issues/3455 + if ($prev_thread && $message_from_customer) { + if ($prev_thread->conversation->mailbox_id != $mailbox->id) { + // https://github.com/freescout-helpdesk/freescout/issues/2807 + // Behaviour of email sent to multiple mailboxes: + // If a user from either mailbox replies, then a new conversation is created + // in the other mailbox with another new conversation ID. + // + // Try to get thread by generated message ID. + if ($in_reply_to) { + $prev_thread = Thread::where('message_id', \MailHelper::generateMessageId($in_reply_to, $mailbox->id.$in_reply_to))->first(); + + if (!$prev_thread) { + $prev_thread = null; + $is_reply = false; + } + } else { + $prev_thread = null; + $is_reply = false; + } + } + } + + // Get body + if (!$html_body) { + // Get body and do not replace :cid with images base64 + $html_body = $message->getHTMLBody(false); + } + + $is_html = true; + + if ($html_body) { + $body = $html_body; + } else { + $is_html = false; + $body = $message->getTextBody() ?? ''; + $body = htmlspecialchars($body); + } + $body = $this->separateReply($body, $is_html, $is_reply, !$message_from_customer); + + // We have to fetch absolutely all emails, even with empty body. + // if (!$body) { + // $this->logError('Message body is empty'); + // $this->setSeen($message, $mailbox); + // continue; + // } + + // Webklex/php-imap returns object instead of a string. + $subject = $message->getSubject().""; + + // Convert subject encoding + if (preg_match('/=\?[a-z\d-]+\?[BQ]\?.*\?=/i', $subject)) { + $subject = iconv_mime_decode($subject, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8'); + } + + $to = $this->formatEmailList($message->getTo()); + + $cc = $this->formatEmailList($message->getCc()); + + // It will always return an empty value as it's Bcc. + $bcc = $this->formatEmailList($message->getBcc()); + + // If existing user forwarded customer's email to the mailbox + // we are creating a new conversation as if it was sent by the customer. + if ($in_reply_to + // We should use body here, as entire HTML may contain + // email looking things. + //&& ($fwd_body = $html_body ?: $message->getTextBody()) + && $body + //&& preg_match("/^(".implode('|', \MailHelper::$fwd_prefixes)."):(.*)/i", $subject, $m) + // F:, FW:, FWD:, WG:, De: + && preg_match("/^[[:alpha:]]{1,3}:(.*)/i", $subject, $m) + // It can be just "Fwd:" + //&& !empty($m[1]) + && !$user_id && !$is_reply && !$prev_thread + // Only if the email has been sent to one mailbox. + && count($to) == 1 && count($cc) == 0 + && preg_match("/^[\s]*".self::FWD_AS_CUSTOMER_COMMAND."/su", trim(strip_tags($body))) + ) { + // Try to get "From:" from body. + $original_sender = $this->getOriginalSenderFromFwd($body); + + if ($original_sender) { + // Check if sender is the existing user. + $sender_is_user = User::nonDeleted()->where('email', $from)->exists(); + + if ($sender_is_user) { + // Substitute sender. + $from = $original_sender; + $subject = trim($m[1] ?? $subject); + $message_from_customer = true; + + // Remove @fwd from body. + $body = trim(preg_replace("/".self::FWD_AS_CUSTOMER_COMMAND."([\s<]+)/su", '$1', $body)); + } + } + } + + // Create customers + $emails = array_merge( + $this->attrToArray($message->getFrom()), + $this->attrToArray($message->getReplyTo()), + $this->attrToArray($message->getTo()), + $this->attrToArray($message->getCc()), + // It will always return an empty value as it's Bcc. + $this->attrToArray($message->getBcc()) + ); + $this->createCustomers($emails, $mailbox->getEmails()); + + $date = $this->attrToDate($message->getDate()); + + if ($date) { + $app_timezone = config('app.timezone'); + if ($app_timezone) { + $date->setTimezone($app_timezone); + } + } + $now = now(); + if (!$date || $date->greaterThan($now)) { + $date = $now; + } + + $data = \Eventy::filter('fetch_emails.data_to_save', [ + 'mailbox' => $mailbox, + 'message_id' => $message_id, + 'prev_thread' => $prev_thread, + 'from' => $from, + 'to' => $to, + 'cc' => $cc, + 'bcc' => $bcc, + 'subject' => $subject, + 'body' => $body, + 'attachments' => $attachments, + 'message' => $message, + 'is_bounce' => $is_bounce, + 'message_from_customer' => $message_from_customer, + 'user' => $user, + 'date' => $date, + ]); + + $new_thread = null; + if ($message_from_customer) { + + // We should import the message into other mailboxes even if previous thread is set. + // https://github.com/freescout-helpdesk/freescout/issues/3473 + //if (!$data['prev_thread']) { + + // Maybe this email need to be imported also into other mailbox. + + $recipient_emails = array_unique($this->formatEmailList(array_merge( + $this->attrToArray($message->getTo()), + $this->attrToArray($message->getCc()), + // It will always return an empty value as it's Bcc. + $this->attrToArray($message->getBcc()) + ))); + + if (count($mailboxes) && count($recipient_emails) > 1) { + foreach ($mailboxes as $check_mailbox) { + if ($check_mailbox->id == $mailbox->id) { + continue; + } + if (!$check_mailbox->isInActive()) { + continue; + } + foreach ($recipient_emails as $recipient_email) { + // No need to check mailbox aliases. + if (\App\Email::sanitizeEmail($check_mailbox->email) == $recipient_email) { + $this->extra_import[] = [ + 'mailbox' => $check_mailbox, + 'message' => $message, + 'message_id' => $message_id, + ]; + break; + } + } + } + } + //} + + if (\Eventy::filter('fetch_emails.should_save_thread', true, $data) !== false) { + // SendAutoReply listener will check bounce flag and will not send an auto reply if this is an auto responder. + $new_thread = $this->saveCustomerThread($mailbox, $data['message_id'], $data['prev_thread'], $data['from'], $data['to'], $data['cc'], $data['bcc'], $data['subject'], $data['body'], $data['attachments'], $data['message']->getHeader(), $data['date']); + } else { + $this->line('['.date('Y-m-d H:i:s').'] Hook fetch_emails.should_save_thread returned false. Skipping message.'); + $this->setSeen($message, $mailbox); + return; + } + } else { + // Check if From is the same as user's email. + // If not we send an email with information to the sender. + if (!$user->hasEmail($from)) { + $this->logError("Sender address {$from} does not match ".$user->getFullName()." user email: ".$user->email.". Add ".$user->email." to user's Alternate Emails in the users's profile to allow the user reply from this address."); + $this->setSeen($message, $mailbox); + + // Send "Unable to process your update email" to user + \App\Jobs\SendEmailReplyError::dispatch($from, $user, $mailbox)->onQueue('emails'); + + return; + } + + // Save user thread only if there prev_thread is set. + // https://github.com/freescout-helpdesk/freescout/issues/3455 + if (!$prev_thread) { + $this->logError("Support agent's reply to the email notification could not be processed as previous thread could not be determined."); + $this->setSeen($message, $mailbox); + + return; + } + + if (\Eventy::filter('fetch_emails.should_save_thread', true, $data) !== false) { + $new_thread = $this->saveUserThread($data['mailbox'], $data['message_id'], $data['prev_thread'], $data['user'], $data['from'], $data['to'], $data['cc'], $data['bcc'], $data['body'], $data['attachments'], $data['message']->getHeader(), $data['date']); + } else { + $this->line('['.date('Y-m-d H:i:s').'] Hook fetch_emails.should_save_thread returned false. Skipping message.'); + $this->setSeen($message, $mailbox); + return; + } + } + + if ($new_thread) { + $this->setSeen($message, $mailbox); + $this->line('['.date('Y-m-d H:i:s').'] Thread successfully created: '.$new_thread->id); + + // If it was a bounce message, save bounce data. + if ($message_from_customer && $is_bounce) { + $this->saveBounceData($new_thread, $bounced_message_id, $from); + } + } else { + $this->logError('Error occurred processing message'); + } + } catch (\Exception $e) { + $this->setSeen($message, $mailbox); + $this->logError(\Helper::formatException($e)); + } + } + + // Try to get "From:" from body. + public function getOriginalSenderFromFwd($body) + { + // https://github.com/freescout-helpdesk/freescout/issues/2672 + $body = preg_replace("/[\"']cid:/", '!', $body); + // Cut out the command, otherwise it will be recognized as an email. + $body = preg_replace("/".self::FWD_AS_CUSTOMER_COMMAND."([\s<]+)/su", '$1', $body); + + // Looks like email texts may appear in attributes: + // https://github.com/freescout-helpdesk/freescout/issues/276 + // - :test@example.org + // - + // - <test@example.org> + + preg_match("/[\"'<:;]([^\"'<:;!@\s]+@[^\"'>:&@\s]+)[\"'>:&]/", $body, $b); + + $email = $b[1] ?? ''; + // https://github.com/freescout-helpdesk/freescout/issues/2517 + $email = preg_replace("#.*<(.*)>.*#", "$1", $email); + return Email::sanitizeEmail($email); + } + + public function saveBounceData($new_thread, $bounced_message_id, $from) + { + // Try to find bounced thread by Message-ID. + $bounced_thread = null; + if ($bounced_message_id) { + $prefixes = [ + \MailHelper::MESSAGE_ID_PREFIX_REPLY_TO_CUSTOMER, + \MailHelper::MESSAGE_ID_PREFIX_AUTO_REPLY, + ]; + preg_match('/^('.implode('|', $prefixes).')\-(\d+)\-/', $bounced_message_id, $matches); + if (!empty($matches[2])) { + $bounced_thread = Thread::find($matches[2]); + } + } + + $status_data = [ + 'is_bounce' => true, + ]; + if ($bounced_thread) { + $status_data['bounce_for_thread'] = $bounced_thread->id; + $status_data['bounce_for_conversation'] = $bounced_thread->conversation_id; + } + + $new_thread->updateSendStatusData($status_data); + $new_thread->save(); + + // Update status of the original message and create log record. + if ($bounced_thread) { + $bounced_thread->send_status = SendLog::STATUS_DELIVERY_ERROR; + + $status_data = [ + 'bounced_by_thread' => $new_thread->id, + 'bounced_by_conversation' => $new_thread->conversation_id, + // todo. + // 'bounce_info' => [ + // ] + ]; + + $bounced_thread->updateSendStatusData($status_data); + $bounced_thread->save(); + + // Bounces can be soft and hard, for now log both as STATUS_DELIVERY_ERROR. + SendLog::log($bounced_thread->id, null, $from, SendLog::MAIL_TYPE_EMAIL_TO_CUSTOMER, SendLog::STATUS_DELIVERY_ERROR, $bounced_thread->created_by_customer_id, null, 'Message bounced'); + } + } + + public function logError($message) + { + $this->error('['.date('Y-m-d H:i:s').'] '.$message); + + $mailbox_name = ''; + if ($this->mailbox) { + $mailbox_name = $this->mailbox->name; + } + + try { + activity() + ->withProperties([ + 'error' => $message, + 'mailbox' => $mailbox_name, + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_FETCHING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_FETCHING_ERROR); + } catch (\Exception $e) { + // Do nothing + } + } + + /** + * Save email from customer as thread. + */ + public function saveCustomerThread($mailbox, $message_id, $prev_thread, $from, $to, $cc, $bcc, $subject, $body, $attachments, $headers, $date) + { + // Fetch date & time setting. + $use_mail_date_on_fetching = config('app.use_mail_date_on_fetching'); + + // Find conversation. + $new = false; + $conversation = null; + $prev_customer_id = null; + if ($use_mail_date_on_fetching) { + $now = $date; + }else{ + $now = date('Y-m-d H:i:s'); + } + $conv_cc = $cc; + $prev_conv_cc = $conv_cc; + + // Customers are created before with email and name + $customer = Customer::create($from); + if ($prev_thread) { + $conversation = $prev_thread->conversation; + + // If reply came from another customer: change customer, add original as CC. + // If FreeScout will not change the customer, the reply will be shown + // as coming from the original customer (not the real sender) and cause confusion. + // Below after events are fired we roll customer back. + if ($conversation->customer_id != $customer->id) { + $prev_customer_id = $conversation->customer_id; + $prev_customer_email = $conversation->customer_email; + + // Do not add to CC emails from the original's BCC + if (!in_array($conversation->customer_email, $conversation->getBccArray())) { + $conv_cc[] = $conversation->customer_email; + } + $conversation->customer_id = $customer->id; + } + } else { + // Create conversation + $new = true; + + $conversation = new Conversation(); + $conversation->type = Conversation::TYPE_EMAIL; + $conversation->state = Conversation::STATE_PUBLISHED; + $conversation->subject = $subject; + $conversation->setPreview($body); + $conversation->mailbox_id = $mailbox->id; + $conversation->customer_id = $customer->id; + $conversation->created_by_customer_id = $customer->id; + $conversation->source_via = Conversation::PERSON_CUSTOMER; + $conversation->source_type = Conversation::SOURCE_TYPE_EMAIL; + $conversation->created_at = $now; + } + + $prev_has_attachments = $conversation->has_attachments; + // Update has_attachments only if email has attachments AND conversation hasn't has_attachments already set + // Prevent to set has_attachments value back to 0 if the new reply doesn't have any attachment + if (!$conversation->has_attachments && count($attachments)) { + // Later we will check which attachments are embedded. + $conversation->has_attachments = true; + } + + // Save extra recipients to CC, but do not add the mailbox itself as a CC. + $conversation->setCc(array_merge($conv_cc, array_diff($to, $mailbox->getEmails()))); + // BCC should keep BCC of the first email, + // so we change BCC only if it contains emails. + if ($bcc) { + $conversation->setBcc($bcc); + } + $conversation->customer_email = $from; + // Reply from customer makes conversation active + if ($conversation->status != Conversation::STATUS_ACTIVE) { + $conversation->status = \Eventy::filter('conversation.status_changing', Conversation::STATUS_ACTIVE, $conversation); + } + $conversation->last_reply_at = $now; + $conversation->last_reply_from = Conversation::PERSON_CUSTOMER; + // Reply from customer to deleted conversation should undelete it. + if ($conversation->state == Conversation::STATE_DELETED) { + $conversation->state = Conversation::STATE_PUBLISHED; + } + // Set folder id + $conversation->updateFolder(); + $conversation->save(); + + // Thread + $thread = new Thread(); + $thread->conversation_id = $conversation->id; + $thread->user_id = $conversation->user_id; + $thread->type = Thread::TYPE_CUSTOMER; + $thread->status = $conversation->status; + $thread->state = Thread::STATE_PUBLISHED; + $thread->message_id = $message_id; + $thread->headers = $this->headerToStr($headers); + $thread->body = $body; + $thread->from = $from; + $thread->setTo($to); + $thread->setCc($cc); + $thread->setBcc($bcc); + $thread->source_via = Thread::PERSON_CUSTOMER; + $thread->source_type = Thread::SOURCE_TYPE_EMAIL; + $thread->customer_id = $customer->id; + $thread->created_by_customer_id = $customer->id; + $thread->created_at = $now; + $thread->updated_at = $now; + if ($new) { + $thread->first = true; + } + try { + $thread->save(); + } catch (\Exception $e) { + // Could not save thread. + // https://github.com/freescout-helpdesk/freescout/issues/3186 + if ($new) { + $conversation->deleteForever(); + } + throw $e; + } + + $body_changed = false; + $saved_attachments = $this->saveAttachments($attachments, $thread->id); + if ($saved_attachments) { + $thread->has_attachments = true; + + // After attachments saved to the disk we can replace cids in body (for PLAIN and HTML body) + $thread->body = $this->replaceCidsWithAttachmentUrls($thread->body, $saved_attachments, $conversation, $prev_has_attachments); + $body_changed = true; + } + + $new_body = Thread::replaceBase64ImagesWithAttachments($thread->body); + if ($new_body != $thread->body) { + $thread->body = $new_body; + $body_changed = true; + } + + if ($body_changed) { + $thread->save(); + } + + // Update conversation here if needed. + if ($new) { + $conversation = \Eventy::filter('conversation.created_by_customer', $conversation, $thread, $customer); + } else { + $conversation = \Eventy::filter('conversation.customer_replied', $conversation, $thread, $customer); + } + // save() will check if something in the model has changed. If it hasn't it won't run a db query. + $conversation->save(); + + // Update folders counters + $conversation->mailbox->updateFoldersCounters(); + + if ($new) { + event(new CustomerCreatedConversation($conversation, $thread)); + \Eventy::action('conversation.created_by_customer', $conversation, $thread, $customer); + } else { + event(new CustomerReplied($conversation, $thread)); + \Eventy::action('conversation.customer_replied', $conversation, $thread, $customer); + } + + // Conversation customer changed + // if ($prev_customer_id) { + // event(new ConversationCustomerChanged($conversation, $prev_customer_id, $prev_customer_email, null, $customer)); + // } + + // Return original customer back. + if ($prev_customer_id) { + $conversation->customer_id = $prev_customer_id; + $conversation->customer_email = $prev_customer_email; + $conversation->setCc(array_merge($prev_conv_cc, array_diff($to, $mailbox->getEmails()))); + $conversation->save(); + } + + return $thread; + } + + /** + * Save email reply from user as thread. + */ + public function saveUserThread($mailbox, $message_id, $prev_thread, $user, $from, $to, $cc, $bcc, $body, $attachments, $headers, $date) + { + // fetch time setting. + $use_mail_date_on_fetching = config('app.use_mail_date_on_fetching'); + + $conversation = null; + if ($use_mail_date_on_fetching) { + $now = $date; + }else{ + $now = date('Y-m-d H:i:s'); + } + $user_id = $user->id; + + $conversation = $prev_thread->conversation; + // Determine assignee. + switch ($mailbox->ticket_assignee) { + case Mailbox::TICKET_ASSIGNEE_ANYONE: + $conversation->user_id = Conversation::USER_UNASSIGNED; + break; + case Mailbox::TICKET_ASSIGNEE_REPLYING_UNASSIGNED: + if (!$conversation->user_id) { + $conversation->user_id = $user_id; + } + break; + case Mailbox::TICKET_ASSIGNEE_REPLYING: + $conversation->user_id = $user_id; + break; + case Mailbox::TICKET_ASSIGNEE_KEEP_CURRENT: + // Do nothing. + break; + } + + $prev_has_attachments = $conversation->has_attachments; + if (!$conversation->has_attachments && count($attachments)) { + // Later we will check which attachments are embedded. + $conversation->has_attachments = true; + } + + // Save extra recipients to CC + $conv_cc = $conversation->getCcArray(); + $conversation->setCc(array_merge($cc, $to)); + $conversation->setBcc($bcc); + + // Respect mailbox settings for "Status After Replying + $prev_status = $conversation->status; + $conversation->status = ($mailbox->ticket_status == Mailbox::TICKET_STATUS_KEEP_CURRENT ? $conversation->status : $mailbox->ticket_status); + if ($conversation->status != $mailbox->ticket_status) { + \Eventy::action('conversation.status_changed', $conversation, $user, true, $prev_status); + } + $conversation->last_reply_at = $now; + $conversation->last_reply_from = Conversation::PERSON_USER; + $conversation->user_updated_at = $now; + // Set folder id + $conversation->updateFolder(); + $conversation->save(); + + // Update folders counters + $conversation->mailbox->updateFoldersCounters(); + + // Set CC for the thread to send user reply to CCed emails also. + if ($conv_cc) { + $cc = array_merge($cc, $conv_cc); + } + + // Thread + $thread = new Thread(); + $thread->conversation_id = $conversation->id; + $thread->user_id = $conversation->user_id; + $thread->type = Thread::TYPE_MESSAGE; + $thread->status = $conversation->status; + $thread->state = Thread::STATE_PUBLISHED; + $thread->message_id = $message_id; + $thread->headers = $this->headerToStr($headers); + $thread->body = $body; + $thread->from = $from; + // To must be customer's email + $thread->setTo([$conversation->customer_email]); + $thread->setCc($cc); + $thread->setBcc($bcc); + $thread->source_via = Thread::PERSON_USER; + $thread->source_type = Thread::SOURCE_TYPE_EMAIL; + $thread->customer_id = $conversation->customer_id; + $thread->created_by_user_id = $user_id; + $thread->created_at = $now; + $thread->updated_at = $now; + $thread->save(); + + $body_changed = false; + $saved_attachments = $this->saveAttachments($attachments, $thread->id); + if ($saved_attachments) { + $thread->has_attachments = true; + + // After attachments saved to the disk we can replace cids in body (for PLAIN and HTML body) + $thread->body = $this->replaceCidsWithAttachmentUrls($thread->body, $saved_attachments, $conversation, $prev_has_attachments); + $body_changed = true; + } + + $new_body = Thread::replaceBase64ImagesWithAttachments($thread->body); + if ($new_body != $thread->body) { + $thread->body = $new_body; + $body_changed = true; + } + + if ($body_changed) { + $thread->save(); + } + + event(new UserReplied($conversation, $thread)); + \Eventy::action('conversation.user_replied', $conversation, $thread); + + return $thread; + } + + /** + * Save attachments from email. + * + * @param array $attachments + * @param int $thread_id + * + * @return bool + */ + public function saveAttachments($email_attachments, $thread_id) + { + $created_attachments = []; + foreach ($email_attachments as $email_attachment) { + $created_attachment = Attachment::create( + $this->processAttachmentName($email_attachment->getName()), + $email_attachment->getMimeType(), + Attachment::typeNameToInt($email_attachment->getType()), + $email_attachment->getContent(), + $uploaded_file = '', + $embedded = false, + $thread_id + ); + if ($created_attachment) { + $created_attachments[] = [ + 'imap_attachment' => $email_attachment, + 'attachment' => $created_attachment, + ]; + } + } + + return $created_attachments; + } + + public function processAttachmentName($name) + { + // Fix for Webklex/laravel-imap. + // https://github.com/freescout-helpdesk/freescout/issues/2782 + if (\Str::startsWith($name, '=?')) { + $name_decoded = \imap_utf8($name); + + if ($name_decoded) { + return $name_decoded; + } + } + + return $name; + } + + /** + * Separate reply in the body. + * + * @param string $body + * + * @return string + */ + public function separateReply($body, $is_html, $is_reply, $user_reply_to_notification = false) + { + $cmp_reply_length_desc = function ($a, $b) { + if (mb_strlen($a) == mb_strlen($b)) { + return 0; + } + + return (mb_strlen($a) < mb_strlen($b)) ? -1 : 1; + }; + + $result = ''; + + if ($is_html) { + // Extract body content from HTML + // Split by + $htmls = []; + preg_match_all("/]*>(.*?)<\/html>/is", $body, $htmls); + + if (empty($htmls[0])) { + $htmls[0] = [$body]; + } + foreach ($htmls[0] as $html) { + // One body. + $dom = new \DOMDocument(); + libxml_use_internal_errors(true); + //$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + //$dom->loadHTML(\Helper::mbConvertEncodingHtmlEntities($html)); + $dom->loadHTML(\Symfony\Polyfill\Mbstring\Mbstring::mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + libxml_use_internal_errors(false); + $bodies = $dom->getElementsByTagName('body'); + if ($bodies->length == 1) { + $body_el = $bodies->item(0); + $html = $dom->saveHTML($body_el); + } + preg_match("/]*>(.*?)<\/body>/is", $html, $matches); + if (count($matches)) { + $result .= $matches[1]; + } + } + if (!$result) { + $result = $body; + } + } else { + $result = nl2br($body ?? ''); + } + + // This is reply, we need to separate reply text from old text + if ($is_reply) { + // Check all separators and choose the shortest reply + $reply_bodies = []; + + $reply_separators = Mail::$alternative_reply_separators; + + if (!empty($this->mailbox->before_reply)) { + $reply_separators[] = $this->mailbox->before_reply; + } + + // If user replied to the email notification use only predefined reply separator. + // https://github.com/freescout-helpdesk/freescout/issues/3580 + if ($user_reply_to_notification && strstr($result, \MailHelper::REPLY_SEPARATOR_NOTIFICATION)) { + $reply_separators = [\MailHelper::REPLY_SEPARATOR_NOTIFICATION]; + } + + foreach ($reply_separators as $reply_separator) { + if (\Str::startsWith($reply_separator, 'regex:')) { + $regex = preg_replace("/^regex:/", '', $reply_separator); + $parts = preg_split($regex, $result); + } else { + $parts = explode($reply_separator, $result); + } + if (count($parts) > 1) { + // Check if part contains any real text. + $text = \Helper::htmlToText($parts[0]); + $text = trim($text); + $text = preg_replace('/^\s+/mu', '', $text); + + if ($text) { + $reply_bodies[] = $parts[0]; + } + } + } + if (count($reply_bodies)) { + usort($reply_bodies, $cmp_reply_length_desc); + + return $reply_bodies[0]; + } + } + + return $result; + } + + public function replaceCidsWithAttachmentUrls($body, $attachments, $conversation, $prev_has_attachments) + { + $only_embedded_attachments = true; + + foreach ($attachments as $attachment) { + // webklex: + // [type] => image + // [content_type] => image/png + // [id] => ii_l0krlfiu0 + // [name] => 2.png + // [disposition] => inline + // [img_src] => ... + // + // php-imap: + // [content] => ... + // [type] => text + // [part_number] => 3 + // [content_type] => image/png + // [id] => ii_l0krolw00 + // [name] => 2.png + // [disposition] => Webklex\PHPIMAP\Attribute Object + // ( + // [name:protected] => content_disposition + // [values:protected] => Array + // ( + // [0] => inline + // ) + + // ) + // [img_src] => + // [size] => 2326 + if ($attachment['imap_attachment']->id && (isset($attachment['imap_attachment']->img_src) || strlen($attachment['imap_attachment']->content ?? ''))) { + $cid = 'cid:'.$attachment['imap_attachment']->id; + if (strstr($body, $cid)) { + $body = str_replace($cid, $attachment['attachment']->url(), $body); + // Set embedded flag for the attachment. + $attachment['attachment']->embedded = true; + $attachment['attachment']->save(); + } else { + $only_embedded_attachments = false; + } + } else { + $only_embedded_attachments = false; + } + } + + if ($only_embedded_attachments + && $conversation + && $conversation->has_attachments + && !$prev_has_attachments + ) { + $conversation->has_attachments = false; + $conversation->save(); + } + + return $body; + } + + /** + * Convert email object to plain emails. + * + * @param array $obj_list + * + * @return array + */ + public function formatEmailList($obj_list) + { + $plain_list = []; + + if (!$obj_list) { + return $plain_list; + } + + $obj_list = $this->attrToArray($obj_list); + + foreach ($obj_list as $item) { + $item->mail = Email::sanitizeEmail($item->mail); + if ($item->mail) { + $plain_list[] = $item->mail; + } + } + + return $plain_list; + } + + public function attrToArray($attr) + { + if (!$attr) { + return []; + } + + if (is_object($attr) && get_class($attr) == 'Webklex\PHPIMAP\Attribute') { + $attr = $attr->get(); + } + + return $attr; + } + + public function attrToDate($attr) + { + if (!$attr) { + return null; + } + + if (is_object($attr) && get_class($attr) == 'Webklex\PHPIMAP\Attribute') { + $attr = $attr->toDate(); + } + + return $attr; + } + + public function headerToStr($header) + { + if (!is_string($header)) { + $header = $header->raw; + } + return $header; + } + + /** + * We have to sort messages manually, as they can be in non-chronological order. + * + * @param Collection $messages + * + * @return Collection + */ + public function sortMessage($messages) + { + $messages = $messages->sortBy(function ($message, $key) { + $date = $message->getDate(); + if ($date) { + if (isset($message->getDate()->timestamp)) { + return $message->getDate()->timestamp; + } else { + return (string)$message->getDate(); + } + } else { + return 0; + } + }); + + return $messages; + } + + /** + * Create customers from emails. + * + * @param array $emails_data + */ + public function createCustomers($emails, $exclude_emails) + { + foreach ($emails as $item) { + // Email belongs to mailbox + // if (in_array(Email::sanitizeEmail($item->mail), $exclude_emails)) { + // continue; + // } + $data = []; + if (!empty($item->personal)) { + $name_parts = explode(' ', $item->personal, 2); + $data['first_name'] = $name_parts[0]; + if (!empty($name_parts[1])) { + $data['last_name'] = $name_parts[1]; + } + } + Customer::create($item->mail, $data); + } + } + + public function setSeen($message, $mailbox) + { + $message->setFlag(['Seen']); + \Eventy::action('fetch_emails.after_set_seen', $message, $mailbox, $this); + } +} diff --git a/freescout-dist/app/Console/Commands/FetchMonitor.php b/freescout-dist/app/Console/Commands/FetchMonitor.php new file mode 100644 index 0000000..eec2d6f --- /dev/null +++ b/freescout-dist/app/Console/Commands/FetchMonitor.php @@ -0,0 +1,73 @@ +'.$mins_ago.' minutes ago. Please check fetching logs and make sure that the following cron task is running: php artisan schedule:run'; + + if (\Option::get('alert_fetch') && !\Option::get('alert_fetch_sent')) { + // We send alert only once + \Option::set('alert_fetch_sent', true); + \MailHelper::sendAlertMail($text, 'Fetching Problems'); + } + + $this->error('['.date('Y-m-d H:i:s').'] '.$text); + } elseif (!$last_successful_run) { + $this->line('['.date('Y-m-d H:i:s').'] Fetching has not been configured yet'); + } else { + if (\Option::get('alert_fetch_sent')) { + $text = 'Previously there were some problems fetching emails. Fetching recovered and functioning now!'; + + \MailHelper::sendAlertMail($text, 'Fetching Recovered'); + } + \Option::set('alert_fetch_sent', false); + + $this->info('['.date('Y-m-d H:i:s').'] Fetching is working'); + } + } +} diff --git a/freescout-dist/app/Console/Commands/GenerateVars.php b/freescout-dist/app/Console/Commands/GenerateVars.php new file mode 100644 index 0000000..e62b226 --- /dev/null +++ b/freescout-dist/app/Console/Commands/GenerateVars.php @@ -0,0 +1,77 @@ + \Helper::getAllLocales(), + ]; + + //$filesystem = new Filesystem(); + + //$file_path = public_path('js/vars.js'); + $file_path = storage_path('app/public/js/vars.js'); + + $content = view('js/vars', $params)->render(); + + //$filesystem->put($file_path, $content); + // Save vars only if content changed + try { + if (\Storage::exists('js/vars.js')) { + $old_content = \Storage::get('js/vars.js'); + if ($content != $old_content) { + \Storage::put('js/vars.js', $content); + } + } else { + \Storage::put('js/vars.js', $content); + } + $this->info("Created: ".substr($file_path, strlen(base_path())+1)); + } catch (\Exception $e) { + $msg = "Error occurred saving /storage/app/public/js/vars.js. ".\Helper::formatException($e); + \Log::error($msg); + $this->error($msg); + } + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + } +} diff --git a/freescout-dist/app/Console/Commands/LogoutUsers.php b/freescout-dist/app/Console/Commands/LogoutUsers.php new file mode 100644 index 0000000..01cb77c --- /dev/null +++ b/freescout-dist/app/Console/Commands/LogoutUsers.php @@ -0,0 +1,61 @@ +getPathname()); + if ($deleted) { + $count++; + } + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + } + $this->line('Deleted sessions: '.$count); + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + } +} diff --git a/freescout-dist/app/Console/Commands/LogsMonitor.php b/freescout-dist/app/Console/Commands/LogsMonitor.php new file mode 100644 index 0000000..6750428 --- /dev/null +++ b/freescout-dist/app/Console/Commands/LogsMonitor.php @@ -0,0 +1,96 @@ + config('app.alert_logs_period'), + ]); + + if (!$options['alert_logs_names']) { + $this->error('['.date('Y-m-d H:i:s').'] No logs to monitor selected'); + + return; + } + if (!$options['alert_logs_period']) { + $this->error('['.date('Y-m-d H:i:s').'] No logs monitoring period set'); + + return; + } + + $logs = \App\ActivityLog::whereIn('log_name', $options['alert_logs_names']) + ->where('created_at', '>=', \Carbon\Carbon::now()->modify('-1 '.$options['alert_logs_period'])->toDateTimeString()) + ->where('created_at', '<', $now->toDateTimeString()) + ->get(); + + if (!count($logs)) { + $this->line('['.date('Y-m-d H:i:s').'] No new log records found for the last '.$options['alert_logs_period']); + + return; + } + + $names = $logs->pluck('log_name')->unique()->toArray(); + $text = 'Logs having new records for the last '.$options['alert_logs_period'].':
    '; + foreach ($names as $name) { + $text .= '
  • '; + $text .= ''.\App\ActivityLog::getLogTitle($name).''; + $text .= '
  • '; + } + $text .= '
'; + + foreach ($names as $name) { + $text .= '
'.\App\ActivityLog::getLogTitle($name).'
'; + foreach ($logs as $log) { + if ($log->log_name != $name) { + continue; + } + $text .= '● ['.$log->created_at.'] '.$log->getEventDescription().' '.$log->properties.'
'; + } + } + // Send alert. + \MailHelper::sendAlertMail($text, 'Logs Monitoring'); + + $this->line($text); + + $this->info('['.date('Y-m-d H:i:s').'] Monitoring finished'); + } +} diff --git a/freescout-dist/app/Console/Commands/ModuleBuild.php b/freescout-dist/app/Console/Commands/ModuleBuild.php new file mode 100644 index 0000000..1abd139 --- /dev/null +++ b/freescout-dist/app/Console/Commands/ModuleBuild.php @@ -0,0 +1,118 @@ +argument('module_alias'); + if (!$module_alias) { + $modules = \Module::all(); + + $modules_aliases = []; + foreach ($modules as $module) { + $modules_aliases[] = $module->name; + } + if (!$modules_aliases) { + $this->error('No modules found'); + + return; + } + $all = true; + // $all = $this->confirm('You have not specified a module alias, would you like to build all available modules ('.implode(', ', $modules_aliases).')?'); + // if (!$all) { + // return; + // } + } + + if ($all) { + foreach ($modules as $module) { + $this->buildModule($module); + $this->call('freescout:module-laroute', ['module_alias' => $module->getAlias()]); + } + } else { + $module = \Module::findByAlias($module_alias); + if (!$module) { + $this->error('Module with the specified alias not found: '.$module_alias); + + return; + } + $this->buildModule($module); + $this->call('freescout:module-laroute'); + } + } + + public function buildModule($module) + { + $this->line('Module: '.$module->getName()); + + $public_symlink = public_path('modules').DIRECTORY_SEPARATOR.$module->alias; + if (!file_exists($public_symlink)) { + $this->error('Public symlink ['.$public_symlink.'] not found. Run module installation command first: php artisan freescout:module-install'); + + return; + } + + $this->buildVars($module); + } + + public function buildVars($module) + { + try { + $params = [ + 'locales' => config('app.locales'), + ]; + + $filesystem = new Filesystem(); + + $file_path = public_path('modules/'.$module->alias.'/js/vars.js'); + + $compiled = view($module->alias.'::js/vars', $params)->render(); + + if ($compiled) { + $filesystem->put($file_path, $compiled); + } + + $this->info("Created: {$file_path}"); + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + } +} diff --git a/freescout-dist/app/Console/Commands/ModuleCheckLicenses.php b/freescout-dist/app/Console/Commands/ModuleCheckLicenses.php new file mode 100644 index 0000000..13480c0 --- /dev/null +++ b/freescout-dist/app/Console/Commands/ModuleCheckLicenses.php @@ -0,0 +1,102 @@ +info('Active modules found: '.count($modules)); + + $params = [ + 'url' => \App\Module::getAppUrl(), + 'data' => [], + ]; + + foreach ($modules as $module) { + $license = $module->getLicense(); + + if (!$module->isOfficial() || !$license) { + continue; + } + + $data['license'] = $license; + $data['module_alias'] = $module->getAlias(); + + $params['data'][] = $data; + } + + $result = WpApi::checkLicenses($params); + + if (!empty($result['statuses'])) { + foreach ($modules as $module) { + $module_alias = $module->getAlias(); + + foreach ($result['statuses'] as $result_module_alias => $status) { + if ($result_module_alias != $module_alias) { + continue; + } + if (!empty($status) && $status != 'valid') { + $msg = 'Module '.$module->getName().' has been deactivated due to invalid license: '.$status; + + $this->error($module->getName().': '.$msg); + + // Deactive module + \App\Module::deactiveModule($module->getAlias(), true); + + // Inform admin + \Log::error($msg); + activity() + ->withProperties([ + 'error' => $msg, + ]) + ->useLog(\App\ActivityLog::NAME_SYSTEM) + ->log(\App\ActivityLog::DESCRIPTION_SYSTEM_ERROR); + } else { + $this->info($module->getName().': OK'); + } + continue 2; + } + + $this->info($module->getName().': Unknown status'); + } + } + + $this->info('Checking licenses finished'); + } +} diff --git a/freescout-dist/app/Console/Commands/ModuleInstall.php b/freescout-dist/app/Console/Commands/ModuleInstall.php new file mode 100644 index 0000000..e95c2e0 --- /dev/null +++ b/freescout-dist/app/Console/Commands/ModuleInstall.php @@ -0,0 +1,140 @@ +call('cache:clear'); + + // Create a symlink for the module (or all modules) + $module_alias = $this->argument('module_alias'); + if (!$module_alias) { + $modules = \Module::all(); + + $modules_aliases = []; + foreach ($modules as $module) { + $modules_aliases[] = $module->name; + } + if (!$modules_aliases) { + $this->error('No modules found'); + + return; + } + $install_all = $this->confirm('You have not specified a module alias, would you like to install all available modules ('.implode(', ', $modules_aliases).')?'); + if (!$install_all) { + return; + } + } + + if ($install_all) { + foreach ($modules as $module) { + $this->line('Module: '.$module->getName()); + $this->call('module:migrate', ['module' => $module->getName()]); + $this->createModulePublicSymlink($module); + } + } else { + $module = \Module::findByAlias($module_alias); + if (!$module) { + $this->error('Module with the specified alias not found: '.$module_alias); + + return; + } + $this->call('module:migrate', ['module' => $module->getName(), '--force' => true]); + $this->createModulePublicSymlink($module); + } + $this->line('Clearing cache...'); + $this->call('freescout:clear-cache'); + } + + // There is similar function in \App\Module. + public function createModulePublicSymlink($module) + { + $from = public_path('modules').DIRECTORY_SEPARATOR.$module->alias; + $to = $module->getExtraPath('Public'); + + // file_exists() may throw "open_basedir restriction in effect". + try { + // If module's Public is symlink. + if (is_link($to)) { + @unlink($to); + } + + // Symlimk may exist but lead to the module folder in a wrong case. + // So we need first try to remove it. + if (!file_exists($from) || !is_link($from)) { + if (is_dir($from)) { + @rename($from, $from.'_'.date('YmdHis')); + } else { + @unlink($from); + } + } + + if (file_exists($from)) { + return $this->info('Public symlink already exists'); + } + + // Check target. + if (!file_exists($to)) { + // Try to create Public folder. + try { + \File::makeDirectory($to, \Helper::DIR_PERMISSIONS); + } catch (\Exception $e) { + // If it's a broken symlink. + if (is_link($to)) { + @unlink($to); + } + } + } + + try { + symlink($to, $from); + } catch (\Exception $e) { + $this->error('Error occurred creating ['.$from.' » '.$to.'] symlink: '.$e->getMessage()); + } + } catch (\Exception $e) { + $this->error('Error occurred creating ['.$from.' » '.$to.'] symlink: '.$e->getMessage()); + } + + $this->info('The ['.$from.'] symlink has been created'); + } +} diff --git a/freescout-dist/app/Console/Commands/ModuleLaroute.php b/freescout-dist/app/Console/Commands/ModuleLaroute.php new file mode 100644 index 0000000..12657e5 --- /dev/null +++ b/freescout-dist/app/Console/Commands/ModuleLaroute.php @@ -0,0 +1,171 @@ +config = $app['config']; + $this->generator = $app->make('Lord\Laroute\Generators\GeneratorInterface'); + + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $all = false; + $modules = []; + + // Create a symlink for the module (or all modules) + $module_alias = $this->argument('module_alias'); + if (!$module_alias) { + $modules = \Module::all(); + + $modules_aliases = []; + foreach ($modules as $module) { + $modules_aliases[] = $module->name; + } + if (!$modules_aliases) { + $this->error('No modules found'); + + return; + } + $all = true; + // $all = $this->confirm('You have not specified a module alias, would you like to generate routes for all available modules ('.implode(', ', $modules_aliases).')?'); + // if (!$all) { + // return; + // } + } + + if ($all) { + foreach ($modules as $module) { + $this->generateModuleRoutes($module); + } + } else { + $module = \Module::findByAlias($module_alias); + if (!$module) { + $this->error('Module with the specified alias not found: '.$module_alias); + + return; + } + $this->generateModuleRoutes($module); + } + } + + public function generateModuleRoutes($module) + { + $this->line('Module: '.$module->getName()); + + $public_symlink = public_path('modules').DIRECTORY_SEPARATOR.$module->getAlias(); + if (!file_exists($public_symlink)) { + $this->error('Public symlink ['.$public_symlink.'] not found. Run module installation command first: php artisan freescout:module-install'); + + return; + } + + $this->routes = new Routes(app()['router']->getRoutes(), $this->config->get('laroute.filter', 'all'), $this->config->get('laroute.action_namespace', ''), $module->getAlias()); + + try { + $filePath = $this->generator->compile( + $this->getTemplatePath(), + $this->getTemplateData(), + $this->getFileGenerationPath($module->getAlias()) + ); + + $this->info("Created: {$filePath}"); + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + } + + /** + * Get path to the template file. + * + * @return string + */ + protected function getTemplatePath() + { + return 'resources/assets/js/laroute_module.js'; + } + + /** + * Get the data for the template. + * + * @return array + */ + protected function getTemplateData() + { + $namespace = $this->getOptionOrConfig('namespace'); + $routes = $this->routes->toJSON(); + $absolute = $this->config->get('laroute.absolute', false); + $rootUrl = $this->config->get('app.url', ''); + $prefix = $this->config->get('laroute.prefix', ''); + + return compact('namespace', 'routes', 'absolute', 'rootUrl', 'prefix'); + } + + /** + * Get the path where the file will be generated. + * + * @return string + */ + protected function getFileGenerationPath($module_alias) + { + $path = 'public/modules/'.$module_alias.'/js'; + $filename = 'laroute'; //$this->getOptionOrConfig('filename'); + + return "{$path}/{$filename}.js"; + } + + /** + * Get an option value either from console input, or the config files. + * + * @param $key + * + * @return array|mixed|string + */ + protected function getOptionOrConfig($key) + { + // if ($option = $this->option($key)) { + // return $option; + // } + + return $this->config->get("laroute.{$key}"); + } +} diff --git a/freescout-dist/app/Console/Commands/ModuleUpdate.php b/freescout-dist/app/Console/Commands/ModuleUpdate.php new file mode 100644 index 0000000..1bf3957 --- /dev/null +++ b/freescout-dist/app/Console/Commands/ModuleUpdate.php @@ -0,0 +1,105 @@ +argument('module_alias'); + + $modules_directory = \WpApi::getModules(); + if (\WpApi::$lastError) { + $this->error(__('Error occurred').': '.$lastError['message'].' ('.$lastError['code'].')'); + return; + } + + $installed_modules = \Module::all(); + + $counter = 0; + $found = false; + foreach ($modules_directory as $dir_module) { + // Update single module. + if ($module_alias && $dir_module['alias'] != $module_alias) { + continue; + } + + $found = true; + + // Detect if new version is available. + foreach ($installed_modules as $module) { + if ($module->getAlias() != $dir_module['alias'] || !$module->active()) { + continue; + } + if (!empty($dir_module['version']) && version_compare($dir_module['version'], $module->get('version'), '>')) { + $update_result = \App\Module::updateModule($dir_module['alias']); + + $this->info('['.$update_result['module_name'].' Module'.']'); + if ($update_result['status'] == 'success') { + $this->line($update_result['msg_success']); + } else { + $msg = $update_result['msg']; + if ($update_result['download_msg']) { + $msg .= ' ('.$update_result['download_msg'].')'; + } + $this->error('ERROR: '.$msg); + } + if (trim($update_result['output'])) { + $this->line(preg_replace("#\n#", "\n> ", '> '.trim($update_result['output']))); + } + + $counter++; + } + } + } + + if ($module_alias && !$found) { + $this->error('Module with the following alias not found: '.$module_alias); + } elseif (!$counter) { + $this->line('All modules are up-to-date'); + } + + \Artisan::call('freescout:clear-cache'); + } +} diff --git a/freescout-dist/app/Console/Commands/SendMonitor.php b/freescout-dist/app/Console/Commands/SendMonitor.php new file mode 100644 index 0000000..df88ad3 --- /dev/null +++ b/freescout-dist/app/Console/Commands/SendMonitor.php @@ -0,0 +1,66 @@ +where('payload', 'like', '{"displayName":"App\\\\\\\\Jobs\\\\\\\\SendReplyToCustomer"%') + ->where('available_at', '<', time() - self::CHECK_PERIOD) + ->exists(); + + // Check failed_jobs. + // No need - it can be done via Manage > Alerts > Logs Monitoring + // if (!$pending_jobs) { + // $pending_jobs = \App\FailedJob::where('queue', 'emails') + // ->where('payload', 'like', '{"displayName":"App\\\\\\\\Jobs\\\\\\\\SendReplyToCustomer"%') + // ->where('created_at', '<', time() - self::CHECK_PERIOD) + // ->exists(); + // } + + if ($pending_jobs) { + \Option::set('send_emails_problem', '1'); + $this->error('['.date('Y-m-d H:i:s').'] There are problems with emails queue processing'); + } else { + \Option::remove('send_emails_problem'); + $this->info('['.date('Y-m-d H:i:s').'] Emails queue processing is working'); + } + } +} diff --git a/freescout-dist/app/Console/Commands/Update.php b/freescout-dist/app/Console/Commands/Update.php new file mode 100644 index 0000000..cc904c4 --- /dev/null +++ b/freescout-dist/app/Console/Commands/Update.php @@ -0,0 +1,64 @@ +confirmToProceed()) { + return; + } + + @ini_set('memory_limit', '128M'); + + if (\Updater::isNewVersionAvailable(config('app.version'))) { + $this->info('Updating... This may take several minutes'); + + try { + // Script may fail here and stop with the error: + // PHP Fatal error: Allowed memory size of 94371840 bytes exhausted + \Updater::update(); + $this->call('freescout:after-app-update'); + } catch (\Exception $e) { + $this->error('Error occurred: '.$e->getMessage()); + } + } else { + $this->info('You have the latest version installed: '.config('app.version')); + } + } +} diff --git a/freescout-dist/app/Console/Commands/UpdateFolderCounters.php b/freescout-dist/app/Console/Commands/UpdateFolderCounters.php new file mode 100644 index 0000000..58b35bf --- /dev/null +++ b/freescout-dist/app/Console/Commands/UpdateFolderCounters.php @@ -0,0 +1,46 @@ +updateCounters(); + $this->line('Updated counters for folder: '.$folder->id); + } + $this->info('Updating finished'); + } +} diff --git a/freescout-dist/app/Console/Kernel.php b/freescout-dist/app/Console/Kernel.php new file mode 100644 index 0000000..fdbe628 --- /dev/null +++ b/freescout-dist/app/Console/Kernel.php @@ -0,0 +1,273 @@ +command('queue:flush') + ->weekly(); + + // Restart processing queued jobs (just in case) + $schedule->command('queue:restart') + ->hourly(); + + $schedule->command('freescout:fetch-monitor') + ->everyMinute() + ->withoutOverlapping(); + + $schedule->command('freescout:send-monitor') + // Every 10 minutes. + ->cron('*/10 * * * *') + ->withoutOverlapping(); + + $schedule->command('freescout:update-folder-counters') + ->hourly(); + + $app_key = config('app.key'); + if ($app_key) { + $crc = crc32($app_key); + $schedule->command('freescout:module-check-licenses') + ->cron((int)($crc % 59).' '.(int)($crc % 23).' * * *'); + } + + // Check if user finished viewing conversation. + $schedule->command('freescout:check-conv-viewers') + ->everyMinute() + ->withoutOverlapping(); + + $schedule->command('freescout:clean-send-log') + ->monthly(); + + $schedule->command('freescout:clean-notifications-table') + ->weekly(); + + $schedule->command('freescout:clean-tmp') + ->daily(); + + // Logs monitoring. + $alert_logs_period = config('app.alert_logs_period'); + if (config('app.alert_logs') && $alert_logs_period) { + $logs_cron = ''; + switch ($alert_logs_period) { + case 'hour': + $logs_cron = '0 * * * *'; + break; + case 'day': + $logs_cron = '0 0 * * *'; + break; + case 'week': + $logs_cron = '0 0 * * 0'; + break; + case 'month': + $logs_cron = '0 0 1 * *'; + break; + } + if ($logs_cron) { + $schedule->command('freescout:logs-monitor') + ->cron($logs_cron) + ->withoutOverlapping(); + } + } + + $fetch_command_identifier = \Helper::getWorkerIdentifier('freescout:fetch-emails'); + $fetch_command_name = 'freescout:fetch-emails --identifier='.$fetch_command_identifier; + + // Kill fetch commands running for too long. + // In shedule:run this code is executed every time $schedule->command() in this function is executed. + if ($this->isScheduleRun() && function_exists('shell_exec')) { + $fetch_command_pids = \Helper::getRunningProcesses($fetch_command_identifier); + + // The name of the command here must be exactly the same as below! + // Otherwise long fetching will be killed and won't run longer than 1 mintue. + $mutex_name = $schedule->command($fetch_command_name) + ->skip(function () { + return true; + }) + ->mutexName(); + + // If there is no cache mutext but there are running fetch commands + // it means the mutex had expired after self::FETCH_MAX_EXECUTION_TIME + // and the existing command(s) is running longer than self::FETCH_MAX_EXECUTION_TIME. + if (count($fetch_command_pids) > 0 && !\Cache::get($mutex_name)) { + // Kill freescout:fetch-emails commands running for too long + shell_exec('kill '.implode(' | kill ', $fetch_command_pids)); + } elseif (count($fetch_command_pids) == 0) { + // Make sure 'ps' command actually works. + $ps_works = \Helper::getRunningProcesses('schedule:run'); + + if (count($ps_works)) { + // Previous freescout:fetch-emails may have been killed or errored and did not remove the mutex. + // So here we are forcefully removing the mutex. Otherwise mutex will live for 24 hours. + if (\Cache::has($mutex_name)) { + \Cache::forget($mutex_name); + } + } + } + } + + // Fetch emails from mailboxes + $fetch_command = $schedule->command($fetch_command_name) + // withoutOverlapping() option creates a mutex in the cache + // which by default expires in 24 hours. + // So we are passing an 'expiresAt' parameter to withoutOverlapping() to + // prevent fetching from not being executed when fetching command by some reason + // does not remove the mutex from the cache. + ->withoutOverlapping($expiresAt = self::FETCH_MAX_EXECUTION_TIME /* minutes */) + ->sendOutputTo(storage_path().'/logs/fetch-emails.log'); + + switch (config('app.fetch_schedule')) { + case Mail::FETCH_SCHEDULE_EVERY_FIVE_MINUTES: + $fetch_command->everyFiveMinutes(); + break; + case Mail::FETCH_SCHEDULE_EVERY_TEN_MINUTES: + $fetch_command->everyTenMinutes(); + break; + case Mail::FETCH_SCHEDULE_EVERY_FIFTEEN_MINUTES: + $fetch_command->everyFifteenMinutes(); + break; + case Mail::FETCH_SCHEDULE_EVERY_THIRTY_MINUTES: + $fetch_command->everyThirtyMinutes(); + break; + case Mail::FETCH_SCHEDULE_HOURLY: + $fetch_command->Hourly(); + break; + default: + $fetch_command->everyMinute(); + break; + } + + $schedule = \Eventy::filter('schedule', $schedule); + + // If --no-daemonize flag is passed - do not run 'queue:work' daemon. + foreach ($_SERVER['argv'] ?? [] as $arg) { + if ($arg == '--no-interaction') { + return; + } + } + + // Command runs as subprocess and sets cache mutex. If schedule:run command is killed + // subprocess does not clear the mutex and it stays in the cache until cache:clear is executed. + // By default, the lock will expire after 24 hours. + + $queue_work_params = Config('app.queue_work_params'); + // Add identifier to avoid conflicts with other FreeScout instances on the same server. + $queue_work_params['--queue'] .= ','.\Helper::getWorkerIdentifier(); + + // $schedule->command('queue:work') command below has withoutOverlapping() option, + // which works via special mutex stored in the cache preventing several 'queue:work' to work at the same time. + // So when the cache is cleared the mutex indicating that the 'queue:work' is running is removed, + // and the second 'queue:work' command is launched by cron. When `artisan schedule:run` is executed it sees + // that there are two 'queue:work' processes running and kills them. + // After one minute 'queue:work' is executed by cron via `artisan schedule:run` and works in the background. + if ($this->isScheduleRun() && function_exists('shell_exec')) { + $running_commands = \Helper::getRunningProcesses(); + + if (count($running_commands) > 1) { + // Stop all queue:work processes. + // queue:work command is stopped by settings a cache key + \Helper::queueWorkerRestart(); + // Sometimes processes stuck and just continue running, so we need to kill them. + // Sleep to let processes stop. + sleep(1); + // Check processes again. + $worker_pids = \Helper::getRunningProcesses(); + + if (count($worker_pids) > 1) { + // Current process also has to be killed, as otherwise it "stucks" + // $current_pid = getmypid(); + // foreach ($worker_pids as $i => $pid) { + // if ($pid == $current_pid) { + // unset($worker_pids[$i]); + // break; + // } + // } + shell_exec('kill '.implode(' | kill ', $worker_pids)); + } + } elseif (count($running_commands) == 0) { + // Make sure 'ps' command actually works. + $ps_works = \Helper::getRunningProcesses('schedule:run'); + + if (count($ps_works)) { + // Previous queue:work may have been killed or errored and did not remove the mutex. + // So here we are forcefully removing the mutex. + $mutex_name = $schedule->command('queue:work', $queue_work_params) + ->skip(function () { + return true; + }) + ->mutexName(); + if (\Cache::get($mutex_name)) { + \Cache::forget($mutex_name); + } + } + } + } + + $schedule->command('queue:work', $queue_work_params) + ->everyMinute() + ->withoutOverlapping() + ->sendOutputTo(storage_path().'/logs/queue-jobs.log'); + } + + /** + * This function is needed because every time $schedule->command() is executed + * the schedule() is executed also. + */ + public function isScheduleRun() + { + if (!\Helper::isConsole()) { + return true; + } else { + return !empty($_SERVER['argv']) && in_array('schedule:run', $_SERVER['argv']); + } + } + + /** + * Register the commands for the application. + * + * @return void + */ + protected function commands() + { + $this->load(__DIR__.'/Commands'); + + // Swiftmailer uses $_SERVER['SERVER_NAME'] in transport_deps.php + // to set the host for EHLO command, if it is empty it uses [127.0.0.1]. + // G Suite sometimes rejects emails with EHLO [127.0.0.1]. + if (empty($_SERVER['SERVER_NAME'])) { + $_SERVER['SERVER_NAME'] = parse_url(config('app.url'), PHP_URL_HOST); + } + + require base_path('routes/console.php'); + } +} diff --git a/freescout-dist/app/Conversation.php b/freescout-dist/app/Conversation.php new file mode 100644 index 0000000..2bca11c --- /dev/null +++ b/freescout-dist/app/Conversation.php @@ -0,0 +1,2424 @@ + 'customer', + self::PERSON_USER => 'user', + ]; + + /** + * Conversation types. + */ + const TYPE_EMAIL = 1; + const TYPE_PHONE = 2; + const TYPE_CHAT = 3; + + public static $types = [ + self::TYPE_EMAIL => 'email', + self::TYPE_PHONE => 'phone', + self::TYPE_CHAT => 'chat', + ]; + + /** + * Conversation statuses (code must be equal to thread statuses). + */ + const STATUS_ACTIVE = 1; + const STATUS_PENDING = 2; + const STATUS_CLOSED = 3; + const STATUS_SPAM = 4; + // Not used + //const STATUS_OPEN = 5; + + public static $statuses = [ + self::STATUS_ACTIVE => 'active', + self::STATUS_PENDING => 'pending', + self::STATUS_CLOSED => 'closed', + self::STATUS_SPAM => 'spam', + //self::STATUS_OPEN => 'open', + ]; + + /** + * https://glyphicons.bootstrapcheatsheets.com/. + */ + public static $status_icons = [ + self::STATUS_ACTIVE => 'flag', + self::STATUS_PENDING => 'ok', + self::STATUS_CLOSED => 'lock', + self::STATUS_SPAM => 'ban-circle', + //self::STATUS_OPEN => 'folder-open', + ]; + + public static $status_classes = [ + self::STATUS_ACTIVE => 'success', + self::STATUS_PENDING => 'lightgrey', + self::STATUS_CLOSED => 'grey', + self::STATUS_SPAM => 'danger', + //self::STATUS_OPEN => 'folder-open', + ]; + + public static $status_colors = [ + self::STATUS_ACTIVE => '#6ac27b', + self::STATUS_PENDING => '#8b98a6', + self::STATUS_CLOSED => '#6b6b6b', + self::STATUS_SPAM => '#de6864', + ]; + + /** + * Conversation states. + */ + const STATE_DRAFT = 1; + const STATE_PUBLISHED = 2; + const STATE_DELETED = 3; + + public static $states = [ + self::STATE_DRAFT => 'draft', + self::STATE_PUBLISHED => 'published', + self::STATE_DELETED => 'deleted', + ]; + + /** + * Source types (equal to thread source types). + */ + const SOURCE_TYPE_EMAIL = 1; + const SOURCE_TYPE_WEB = 2; + const SOURCE_TYPE_API = 3; + + public static $source_types = [ + self::SOURCE_TYPE_EMAIL => 'email', + self::SOURCE_TYPE_WEB => 'web', + self::SOURCE_TYPE_API => 'api', + ]; + + /** + * Email history options. + */ + // const EMAIL_HISTORY_GLOBAL = 0; + // const EMAIL_HISTORY_NONE = 1; + // const EMAIL_HISTORY_LAST = 2; + // const EMAIL_HISTORY_FULL = 3; + + public static $email_history_codes = [ + 'global', + 'none', + 'last', + 'full', + ]; + + /** + * Assignee. + */ + const USER_UNASSIGNED = -1; + + /** + * Search filters. + */ + public static $search_filters = [ + 'assigned', + 'customer', + 'mailbox', + 'status', + 'state', + 'subject', + 'attachments', + 'type', + 'body', + 'number', + 'following', + 'id', + 'after', + 'before', + //'between', + //'on', + ]; + + /** + * Search mode. + */ + const SEARCH_MODE_CONV = 'conversations'; + const SEARCH_MODE_CUSTOMERS = 'customers'; + + /** + * Default size of the conversations table. + */ + const DEFAULT_LIST_SIZE = 50; + + /** + * Default size of the chats list. + */ + const CHATS_LIST_SIZE = 50; + + /** + * Cache of the conversations starred by user. + * + * @var array + */ + public static $starred_conversation_ids = []; + + /** + * Cache of the app.custom_number option. + */ + public static $custom_number_cache = null; + + /** + * Automatically converted into Carbon dates. + */ + protected $dates = ['created_at', 'updated_at', 'last_reply_at', 'closed_at', 'user_updated_at']; + + /** + * Attributes which are not fillable using fill() method. + */ + protected $guarded = ['id', 'folder_id']; + + /** + * Convert to array. + */ + protected $casts = [ + 'meta' => 'array', + ]; + + /** + * Default values. + */ + protected $attributes = [ + 'preview' => '', + ]; + + protected static function boot() + { + parent::boot(); + + self::creating(function (Conversation $model) { + $next_ticket = (int) Option::get('next_ticket'); + $current_number = Conversation::max('number'); + + if ($next_ticket) { + Option::remove('next_ticket'); + } + + if ($next_ticket && $next_ticket >= ($current_number + 1) && !Conversation::where('number', $next_ticket)->exists()) { + $model->number = $next_ticket; + } else { + $model->number = $current_number + 1; + } + }); + } + + /** + * Who the conversation is assigned to (assignee). + */ + public function user() + { + return $this->belongsTo('App\User'); + } + + /** + * Get the folder to which conversation belongs via folder field. + */ + public function folder() + { + return $this->belongsTo('App\Folder'); + } + + /** + * Get the folder to which conversation belongs via conversation_folder table. + */ + public function folders() + { + return $this->belongsToMany('App\Folder'); + } + + /** + * Get the mailbox to which conversation belongs. + */ + public function mailbox() + { + return $this->belongsTo('App\Mailbox'); + } + + /** + * Cached mailbox. + * @return [type] [description] + */ + public function mailbox_cached() + { + return $this->mailbox()->rememberForever(); + } + + /** + * Get the customer associated with this conversation (primaryCustomer). + */ + public function customer() + { + return $this->belongsTo('App\Customer'); + } + + /** + * Cached customer. + */ + public function customer_cached() + { + return $this->customer()->rememberForever(); + } + + /** + * Get conversation threads. + */ + public function threads() + { + return $this->hasMany('App\Thread'); + } + + /** + * Folders containing starred conversations. + */ + public function extraFolders() + { + return $this->belongsTo('App\Customer'); + } + + /** + * Get user who created the conversations. + */ + public function created_by_user() + { + return $this->belongsTo('App\User'); + } + + /** + * Get customer who created the conversations. + */ + public function created_by_customer() + { + return $this->belongsTo('App\Customer'); + } + + /** + * Get user who closed the conversations. + */ + public function closed_by_user() + { + return $this->belongsTo('App\User'); + } + + /** + * Get conversations followers. + */ + public function followers() + { + return $this->hasMany('App\Follower'); + } + + /** + * Check if user is following this conversation. + */ + public function isUserFollowing($user_id) + { + // We intentionally select all records from followers table, + // as it is more efficient than querying a particular user record. + foreach ($this->followers as $follower) { + if ($follower->user_id == $user_id) { + return true; + } + } + + return false; + } + + /** + * Get only reply threads from conversations. + * + * @return Collection + */ + public function getReplies() + { + return $this->threads() + ->whereIn('type', [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE]) + ->where('state', Thread::STATE_PUBLISHED) + ->orderBy('created_at', 'desc') + ->get(); + } + + /** + * Get all published conversation threads in desc order. + * + * @return Collection + */ + public function getThreads($skip = null, $take = null, $types = []) + { + $query = $this->threads() + ->where('state', Thread::STATE_PUBLISHED) + ->orderBy('created_at', 'desc'); + + if (!is_null($skip)) { + $query->skip($skip); + } + if (!is_null($take)) { + $query->take($take); + } + if ($types) { + $query->whereIn('type', $types); + } + + return $query->get(); + } + + /** + * Get first thread of the conversation. + */ + public function getFirstThread() + { + return $this->threads() + ->orderBy('created_at', 'asc') + ->first(); + } + + /** + * Get last reply by customer or support agent. + * + * @param bool $last [description] + * + * @return [type] [description] + */ + public function getLastReply($include_phone_replies = false) + { + $types = [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE]; + if ($include_phone_replies && $this->isPhone()) { + $types[] = Thread::TYPE_NOTE; + } + return $this->threads() + ->whereIn('type', $types) + ->where('state', Thread::STATE_PUBLISHED) + ->orderBy('created_at', 'desc') + ->first(); + } + + /** + * Get last thread by type. + */ + public function getLastThread($types = []) + { + $query = $this->threads() + ->where('state', Thread::STATE_PUBLISHED) + ->orderBy('created_at', 'desc'); + if ($types) { + if (count($types) == 1 && $types[0]) { + $query->where('type', $types[0]); + } else { + $query->whereIn('type', $types); + } + } + return $query->first(); + } + + /** + * Set preview text. + * + * @param string $text + */ + public function setPreview($text = '') + { + $this->preview = ''; + + if (!$text) { + $first_thread = $this->threads()->first(); + if ($first_thread) { + $text = $first_thread->body; + } + } + + $this->preview = \Helper::textPreview($text, self::PREVIEW_MAXLENGTH); + + return $this->preview; + } + + /** + * Get conversation timestamp title. + * + * @return string + */ + public function getDateTitle() + { + if ($this->threads_count == 1) { + $title = __('Created by :person', ['person' => __(ucfirst(self::$persons[$this->source_via]))]); + $title .= '
'.User::dateFormat($this->created_at, 'M j, Y H:i'); + } else { + $person = ''; + if (!empty(self::$persons[$this->last_reply_from])) { + $person = __(ucfirst(self::$persons[$this->last_reply_from])); + } + $title = __('Last reply by :person', ['person' => $person]); + $last_reply_at = $this->created_at; + if ($this->last_reply_at) { + $last_reply_at = $this->last_reply_at; + } + $title .= '
'.User::dateFormat($last_reply_at, 'M j, Y H:i'); + } + + return $title; + } + + public function isActive() + { + return $this->status == self::STATUS_ACTIVE; + } + + public function isPending() + { + return $this->status == self::STATUS_PENDING; + } + + public function isSpam() + { + return $this->status == self::STATUS_SPAM; + } + + public function isClosed() + { + return $this->status == self::STATUS_CLOSED; + } + + public function isPublished() + { + return $this->state == self::STATE_PUBLISHED; + } + + public function isDraft() + { + return $this->state == self::STATE_DRAFT; + } + + /** + * Get status name. + * + * @return string + */ + public function getStatusName() + { + return self::statusCodeToName($this->status); + } + + /** + * Convert status code to name. + * + * @param int $status + * + * @return string + */ + public static function statusCodeToName($status) + { + switch ($status) { + case self::STATUS_ACTIVE: + return __('Active'); + break; + + case self::STATUS_PENDING: + return __('Pending'); + break; + + case self::STATUS_CLOSED: + return __('Closed'); + break; + + case self::STATUS_SPAM: + return __('Spam'); + break; + + // case self::STATUS_OPEN: + // return __('Open'); + // break; + + default: + return ''; + break; + } + } + + /** + * Convert state code to name. + * + * @param int $status + * + * @return string + */ + public static function stateCodeToName($status) + { + switch ($status) { + case self::STATE_DRAFT: + return __('Draft'); + break; + + case self::STATE_PUBLISHED: + return __('Published'); + break; + + case self::STATE_DELETED: + return __('Deleted'); + break; + + default: + return ''; + break; + } + } + + public function getStatus() + { + if (array_key_exists($this->status, self::$statuses)) { + return $this->status; + } else { + return self::STATUS_ACTIVE; + } + } + + /** + * Set conversation status and all related fields. + * + * @param int $status + */ + public function setStatus($status, $user = null) + { + $now = date('Y-m-d H:i:s'); + + $this->status = $status; + $this->updateFolder(); + $this->user_updated_at = $now; + + if ($user && $status == self::STATUS_CLOSED) { + $this->closed_by_user_id = $user->id; + $this->closed_at = $now; + } + } + + /** + * Set conversation user and all related fields. + * + * @param int $user_id + */ + public function setUser($user_id) + { + $now = date('Y-m-d H:i:s'); + + if ($user_id == -1) { + $user_id = null; + } + + $this->user_id = $user_id; + $this->updateFolder(); + $this->user_updated_at = $now; + + // If user was previously following the conversation then unfollow + if (!is_null($user_id)) { + $follower = Follower::where('conversation_id', $this->id) + ->where('user_id', $user_id) + ->first(); + if ($follower) { + $follower->delete(); + } + } + } + + /** + * Get next active conversation. + * + * @param string $mode next|prev|closest + * + * @return Conversation + */ + public function getNearby($mode = 'closest', $folder_id = null, $status = null, $prev_if_no_next = false) + { + $conversation = null; + + if ($folder_id) { + $folder = Folder::find($folder_id); + } else { + $folder = $this->folder; + } + //$query = self::where('folder_id', $folder->id)->where('id', '<>', $this->id); + $query = self::getQueryByFolder($folder, \Auth::id()) + ->where('id', '<>', $this->id); + + $query = \Eventy::filter('conversation.get_nearby_query', $query, $this, $mode, $folder); + + if ($status) { + $query->where('status', $status); + } + + $order_bys = $folder->getOrderByArray(); + + // Next. + if ($mode != 'prev') { + // Try to get next conversation + $query_next = clone $query; + foreach ($order_bys as $order_by) { + foreach ($order_by as $field => $sort_order) { + if (!$this->$field) { + continue; + } + $field_value = $this->$field; + if ($field == 'status' && $status !== null) { + $field_value = $status; + } + if ($sort_order == 'asc') { + $query_next->where($field, '>=', $field_value); + } else { + $query_next->where($field, '<=', $field_value); + } + $query_next->orderBy($field, $sort_order); + } + } + $conversation = $query_next->first(); + } + + // https://github.com/freescout-helpdesk/freescout/issues/3486 + if ($conversation || ($mode == 'next' && !$prev_if_no_next)) { + return $conversation; + } + + // Prev. + $query_prev = $query; + foreach ($order_bys as $order_by) { + foreach ($order_by as $field => $sort_order) { + if (!$this->$field) { + continue; + } + $field_value = $this->$field; + if ($field == 'status' && $status !== null) { + $field_value = $status; + } + if ($sort_order == 'asc') { + $query_prev->where($field, '<=', $field_value); + } else { + $query_prev->where($field, '>=', $field_value); + } + $query_prev->orderBy($field, $sort_order == 'asc' ? 'desc' : 'asc'); + } + } + + return $query_prev->first(); + } + + /** + * Get URL of the next conversation. + */ + public function urlNext($folder_id = null, $status = null, $prev_if_no_next = false) + { + $next_conversation = $this->getNearby('next', $folder_id, $status, $prev_if_no_next); + if ($next_conversation) { + $url = $next_conversation->url(); + } else { + // Show folder + $url = route('mailboxes.view.folder', ['id' => $this->mailbox_id, 'folder_id' => $this->getCurrentFolder($this->folder_id)]); + } + + return $url; + } + + /** + * Get URL of the previous conversation. + */ + public function urlPrev($folder_id = null) + { + $prev_conversation = $this->getNearby('prev', $folder_id); + if ($prev_conversation) { + $url = $prev_conversation->url(); + } else { + // Show folder + $url = route('mailboxes.view.folder', ['id' => $this->mailbox_id, 'folder_id' => $this->getCurrentFolder($this->folder_id)]); + } + + return $url; + } + + /** + * Set folder according to the status, state and user of the conversation. + */ + public function updateFolder($mailbox = null) + { + if ($this->state == self::STATE_DRAFT) { + $folder_type = Folder::TYPE_DRAFTS; + } elseif ($this->state == self::STATE_DELETED) { + $folder_type = Folder::TYPE_DELETED; + } elseif ($this->status == self::STATUS_SPAM) { + $folder_type = Folder::TYPE_SPAM; + } elseif ($this->status == self::STATUS_CLOSED) { + $folder_type = Folder::TYPE_CLOSED; + } elseif ($this->user_id) { + $folder_type = Folder::TYPE_ASSIGNED; + } else { + $folder_type = Folder::TYPE_UNASSIGNED; + } + + if (!$mailbox) { + $mailbox = $this->mailbox; + } + + // Find folder + $folder = $mailbox->folders() + ->where('type', $folder_type) + ->first(); + + if ($folder) { + $this->folder_id = $folder->id; + } + } + + /** + * Set CC as JSON. + */ + public function setCc($emails) + { + $emails_array = self::sanitizeEmails($emails); + if ($emails_array) { + $emails_array = array_unique($emails_array); + $this->cc = \Helper::jsonEncodeUtf8($emails_array); + } else { + $this->cc = null; + } + } + + /** + * Set BCC as JSON. + */ + public function setBcc($emails) + { + $emails_array = self::sanitizeEmails($emails); + if ($emails_array) { + $emails_array = array_unique($emails_array); + $this->bcc = \Helper::jsonEncodeUtf8($emails_array); + } else { + $this->bcc = null; + } + } + + /** + * Get CC recipients. + * + * @return array + */ + public function getCcArray($exclude_array = []) + { + return \App\Misc\Helper::jsonToArray($this->cc, $exclude_array); + } + + /** + * Get BCC recipients. + * + * @return array + */ + public function getBccArray($exclude_array = []) + { + return \App\Misc\Helper::jsonToArray($this->bcc, $exclude_array); + } + + /** + * Convert list of email to array. + * + * @return + */ + public static function sanitizeEmails($emails) + { + // Create customers if needed: Test + if (is_array($emails)) { + foreach ($emails as $i => $email) { + preg_match("/^(.+)\s+([^\s]+)$/", $email ?? '', $m); + if (count($m) == 3) { + $customer_name = trim($m[1]); + $email_address = trim($m[2]); + + if ($customer_name) { + preg_match("/^([^\s]+)\s+([^\s]+)$/", $customer_name, $m_customer); + $customer_data = []; + + if (count($m_customer) == 3) { + $customer_data['first_name'] = $m_customer[1]; + $customer_data['last_name'] = $m_customer[2]; + } else { + $customer_data['first_name'] = $customer_name; + } + + Customer::create($email_address, $customer_data); + } + + $emails[$i] = $email_address; + } + } + } + return \MailHelper::sanitizeEmails($emails); + } + + /** + * Get conversation URL. + * + * @return string + */ + public function url($folder_id = null, $thread_id = null, $params = []) + { + if (!$folder_id) { + $folder_id = $this->getCurrentFolder(); + } + return self::conversationUrl($this->id, $folder_id, $thread_id, $params); + } + + /** + * Static function for retrieving URL. + * + * @param [type] $id [description] + * @param [type] $folder_id [description] + * @param [type] $thread_id [description] + * @param array $params [description] + * @return [type] [description] + */ + public static function conversationUrl($id, $folder_id = null, $thread_id = null, $params = []) + { + $params = array_merge($params, ['id' => $id]); + + $params['folder_id'] = $folder_id; + + $url = route('conversations.view', $params); + + if ($thread_id) { + $url .= '#thread-'.$thread_id; + } + + return $url; + } + + /** + * Get CSS color of the status. + * + * @return string + */ + public function getStatusColor() + { + return self::$status_colors[$this->status]; + } + + /** + * Get folder ID from request or use the default one. + */ + public function getCurrentFolder($default_folder_id = null) + { + $folder_id = self::getFolderParam(); + if ($folder_id) { + return $folder_id; + } + if ($this->folder_id) { + return $this->folder_id; + } else { + return $default_folder_id; + } + } + + public static function getFolderParam() + { + if (!empty(request()->folder_id)) { + return request()->folder_id; + } elseif (!empty(Input::get('folder_id'))) { + return Input::get('folder_id'); + } + + return ''; + } + + /** + * Check if conversation can be in the folder. + */ + public function isInFolderAllowed($folder) + { + if (in_array($folder->type, Folder::$public_types)) { + return $folder->id == $this->folder_id; + } elseif ($folder->type == Folder::TYPE_MINE) { + $user = auth()->user(); + if ($user && $user->id == $folder->user_id && $this->user_id == $user->id) { + return true; + } else { + return false; + } + } else { + // todo: check ConversationFolder here + return \Eventy::filter('conversation.is_in_folder_allowed', false, $folder, $this); + } + + return false; + } + + /** + * Check if conversation is starred. + * For each user starred conversations are cached. + */ + public function isStarredByUser($user_id = null) + { + if (!$user_id) { + $user = auth()->user(); + if ($user) { + $user_id = $user->id; + } else { + return false; + } + } + $mailbox_id = $this->mailbox_id; + + // Get ids of all the conversations starred by user and cache them + if (!isset(self::$starred_conversation_ids[$mailbox_id])) { + + self::$starred_conversation_ids[$mailbox_id] = self::getUserStarredConversationIds($mailbox_id, $user_id); + } + + if (self::$starred_conversation_ids[$mailbox_id]) { + return in_array($this->id, self::$starred_conversation_ids[$mailbox_id]); + } else { + return false; + } + } + + public static function clearStarredByUserCache($user_id, $mailbox_id) + { + if (!$user_id) { + $user = auth()->user(); + if ($user) { + $user_id = $user->id; + } else { + return false; + } + } + \Cache::forget('user_starred_conversations_'.$user_id.'_'.$mailbox_id); + } + + /** + * Get IDs of the conversations starred by user. + */ + public static function getUserStarredConversationIds($mailbox_id, $user_id = null) + { + return \Cache::rememberForever('user_starred_conversations_'.$user_id.'_'.$mailbox_id, function () use ($mailbox_id, $user_id) { + // Get user's folder + $folder = Folder::select('id') + ->where('mailbox_id', $mailbox_id) + ->where('user_id', $user_id) + ->where('type', Folder::TYPE_STARRED) + ->first(); + + if ($folder) { + return ConversationFolder::where('folder_id', $folder->id) + ->pluck('conversation_id') + ->toArray(); + } else { + activity() + ->withProperties([ + 'error' => "Folder not found (mailbox_id: $mailbox_id, user_id: $user_id)", + ]) + ->useLog(\App\ActivityLog::NAME_SYSTEM) + ->log(\App\ActivityLog::DESCRIPTION_SYSTEM_ERROR); + + return []; + } + }); + } + + /** + * Get text for the assignee. + * + * @return string + */ + public function getAssigneeName($ucfirst = false, $user = null) + { + if (!$this->user_id) { + if ($ucfirst) { + return __('Anyone'); + } else { + return __('anyone'); + } + } elseif (($user && $this->user_id == $user->id) || (!$user && auth()->user() && $this->user_id == auth()->user()->id)) { + if ($ucfirst) { + return __('Me'); + } else { + return __('me'); + } + } else { + return $this->user->getFullName(); + } + } + + /** + * Get query to fetch conversations by folder. + */ + public static function getQueryByFolder($folder, $user_id) + { + // Get conversations from personal folder + if ($folder->type == Folder::TYPE_MINE) { + $query_conversations = self::where('user_id', $user_id) + ->where('mailbox_id', $folder->mailbox_id) + ->whereIn('status', [self::STATUS_ACTIVE, self::STATUS_PENDING]) + ->where('state', self::STATE_PUBLISHED); + + // Assigned - do not show my conversations. + } elseif ($folder->type == Folder::TYPE_ASSIGNED) { + $query_conversations = $folder->conversations() + // This condition also removes from result records with user_id = null + ->where('user_id', '<>', $user_id) + ->where('state', self::STATE_PUBLISHED); + + // Starred by user conversations. + } elseif ($folder->type == Folder::TYPE_STARRED) { + $starred_conversation_ids = self::getUserStarredConversationIds($folder->mailbox_id, $user_id); + $query_conversations = self::whereIn('id', $starred_conversation_ids); + + // Conversations are connected to folder via conversation_folder table. + } elseif ($folder->isIndirect()) { + $query_conversations = self::select('conversations.*') + //->where('conversations.mailbox_id', $folder->mailbox_id) + ->join('conversation_folder', 'conversations.id', '=', 'conversation_folder.conversation_id') + ->where('conversation_folder.folder_id', $folder->id); + if ($folder->type != Folder::TYPE_DRAFTS) { + $query_conversations->where('state', self::STATE_PUBLISHED); + } + + // Deleted. + } elseif ($folder->type == Folder::TYPE_DELETED) { + $query_conversations = $folder->conversations()->where('state', self::STATE_DELETED); + + // Everything else. + } else { + $query_conversations = $folder->conversations()->where('state', self::STATE_PUBLISHED); + } + + // If show only assigned to the current user conversations. + if (!\Helper::isConsole() + && $user_id + && $user = auth()->user() + ) { + if ($user->id == $user_id + && $user->hasManageMailboxPermission($folder->mailbox_id, Mailbox::ACCESS_PERM_ASSIGNED) + ) { + $query_conversations->where('user_id', '=', $user_id); + } + } + + return \Eventy::filter('folder.conversations_query', $query_conversations, $folder, $user_id); + } + + /** + * Replace vars in signature. + * `data` contains extra info which can be used to build signature. + */ + public function getSignatureProcessed($data = [], $escape = false) + { + $replaced_text = $this->replaceTextVars( $this->mailbox->signature, $data, $escape ); + + return \Eventy::filter( 'conversation.signature_processed', $replaced_text, $this, $data, $escape ); + } + + /** + * Replace vars in the text. + */ + public function replaceTextVars($text, $data = [], $escape = false) + { + if (!\MailHelper::hasVars($text)) { + return $text; + } + + if (empty($data['user'])) { + // `user` should contain a user who replies to the conversation. + $user = auth()->user(); + if (!$user && !empty($data['thread'])) { + $user = $data['thread']->created_by_user; + } + } else { + $user = $data['user']; + } + + $data = [ + 'mailbox' => $this->mailbox, + 'conversation' => $this, + 'customer' => $this->customer_cached, + 'user' => $user, + ]; + + // Set variables + return \MailHelper::replaceMailVars($text, $data, $escape); + } + + /** + * Change conversation customer. + * Customer is changed using customer email, as each conversation has customer email. + * Method also creates line item thread if customer changed by user. + * Both by_user and by_customer can be null. + */ + public function changeCustomer($customer_email, $customer = null, $by_user = null, $by_customer = null) + { + if (!$customer) { + $email = Email::where('email', $customer_email)->first(); + if ($email) { + $customer = $email->customer; + } else { + return false; + } + } + + if (!$customer_email) { + $customer_email = $customer->getMainEmail(); + } + + $prev_customer_id = $this->customer_id; + $prev_customer_email = $this->customer_email; + + $this->customer_email = $customer_email; + $this->customer_id = $customer->id; + $this->save(); + + // Create line item thread + if ($by_user) { + $thread = new Thread(); + $thread->conversation_id = $this->id; + $thread->user_id = $this->user_id; + $thread->type = Thread::TYPE_LINEITEM; + $thread->state = Thread::STATE_PUBLISHED; + $thread->status = Thread::STATUS_NOCHANGE; + $thread->action_type = Thread::ACTION_TYPE_CUSTOMER_CHANGED; + $thread->action_data = $this->customer_email; + $thread->source_via = Thread::PERSON_USER; + $thread->source_type = Thread::SOURCE_TYPE_WEB; + $thread->customer_id = $this->customer_id; + $thread->created_by_user_id = $by_user->id; + $thread->save(); + } + + event(new ConversationCustomerChanged($this, $prev_customer_id, $prev_customer_email, $by_user, $by_customer)); + + return true; + } + + /** + * Move conversation to another mailbox. + */ + public function moveToMailbox($mailbox, $user) + { + $prev_mailbox = $this->mailbox; + + foreach ($this->folders as $folder) { + // Process indirect folders. + if (!in_array($folder->type, Folder::$indirect_types)) { + continue; + } + // Remove conversation from the folder. + $this->removeFromFolder($folder->type, $folder->user_id); + if ($folder->type == Folder::TYPE_STARRED) { + self::clearStarredByUserCache($folder->user_id, $this->mailbox_id); + } + } + + // We don't know how to replace $this->mailbox object. + $this->mailbox_id = $mailbox->id; + // Check assignee. + if ($this->user_id && !in_array($this->user_id, $mailbox->userIdsHavingAccess())) { + // Assign conversation to the user who moved it. + $this->user_id = $user->id; + } + $this->updateFolder($mailbox); + $this->save(); + + foreach ($this->folders as $folder) { + // Process indirect folders. + if (!in_array($folder->type, Folder::$indirect_types)) { + continue; + } + // If user has access to the new mailbox, + // move conversation to the same folder in the new mailbox. + if ($folder->user_id) { + if ($folder->user->hasAccessToMailbox($mailbox->id)) { + foreach ($mailbox->folders as $mailbox_folder) { + if ($mailbox_folder->type == $folder->type) { + $this->addToFolder($folder->type, $folder->user_id); + if ($folder->type == Folder::TYPE_STARRED) { + self::clearStarredByUserCache($folder->user_id, $mailbox->id); + } + break; + } + } + } + } else { + foreach ($mailbox->folders as $mailbox_folder) { + if ($mailbox_folder->type == $folder->type) { + $this->addToFolder($folder->type, $folder->user_id); + break; + } + } + } + } + + // Add record to the conversation history. + Thread::create($this, Thread::TYPE_LINEITEM, '', [ + 'created_by_user_id' => $user->id, + 'user_id' => $this->user_id, + 'state' => Thread::STATE_PUBLISHED, + 'action_type' => Thread::ACTION_TYPE_MOVED_FROM_MAILBOX, + 'source_via' => Thread::PERSON_USER, + 'source_type' => Thread::SOURCE_TYPE_WEB, + 'customer_id' => $this->customer_id, + ]); + + // Update counters. + $prev_mailbox->updateFoldersCounters(); + $mailbox->updateFoldersCounters(); + + \Eventy::action('conversation.moved', $this, $user, $prev_mailbox); + + return true; + } + /** + * Merge conversations + */ + public function mergeConversations($second_conversation, $user) + { + // Move all threads from old to new conversation. + foreach ($second_conversation->threads as $thread) { + $thread->conversation_id = $this->id; + $thread->setMeta(Thread::META_PREV_CONVERSATION, $second_conversation->id); + $thread->save(); + } + + // Add record to the new conversation. + Thread::create($this, Thread::TYPE_LINEITEM, '', [ + 'created_by_user_id' => $user->id, + 'user_id' => $this->user_id, + 'state' => Thread::STATE_PUBLISHED, + 'action_type' => Thread::ACTION_TYPE_MERGED, + 'source_via' => Thread::PERSON_USER, + 'source_type' => Thread::SOURCE_TYPE_WEB, + 'customer_id' => $this->customer_id, + 'meta' => [Thread::META_MERGED_WITH_CONV => $second_conversation->id], + ]); + + // Add record to the old conversation. + Thread::create($second_conversation, Thread::TYPE_LINEITEM, '', [ + 'created_by_user_id' => $user->id, + 'user_id' => $second_conversation->user_id, + 'state' => Thread::STATE_PUBLISHED, + 'action_type' => Thread::ACTION_TYPE_MERGED, + 'source_via' => Thread::PERSON_USER, + 'source_type' => Thread::SOURCE_TYPE_WEB, + 'customer_id' => $second_conversation->customer_id, + 'meta' => [Thread::META_MERGED_INTO_CONV => $this->id], + ]); + + if ($second_conversation->has_attachments && !$this->has_attachments) { + $this->has_attachments = true; + $this->save(); + } + + // Move star mark. + $mailbox_star_folders = Folder::where('mailbox_id', $second_conversation->mailbox_id) + ->where('type', Folder::TYPE_STARRED) + ->get(); + + $conv_star_folder_ids = ConversationFolder::select('folder_id') + ->whereIn('folder_id', $mailbox_star_folders->pluck('id')) + ->where('conversation_id', $second_conversation->id) + ->pluck('folder_id'); + + foreach ($conv_star_folder_ids as $conv_star_folder_id) { + $folder = $mailbox_star_folders->find($conv_star_folder_id); + if ($folder->user) { + $this->star($folder->user); + $second_conversation->unstar($folder->user); + } + } + + // Delete old conversation. + $second_conversation->deleteToFolder($user); + + // Update counters. + $this->mailbox->updateFoldersCounters(); + if ($this->mailbox_id != $second_conversation->mailbox_id) { + $second_conversation->mailbox->updateFoldersCounters(); + } + + \Eventy::action('conversation.merged', $this, $second_conversation, $user); + + return true; + } + + public function star($user) + { + $this->addToFolder(Folder::TYPE_STARRED, $user->id); + self::clearStarredByUserCache($user->id, $this->mailbox_id); + $this->mailbox->updateFoldersCounters(Folder::TYPE_STARRED); + } + + public function unstar($user) + { + $this->removeFromFolder(Folder::TYPE_STARRED, $user->id); + self::clearStarredByUserCache($user->id, $this->mailbox_id); + $this->mailbox->updateFoldersCounters(Folder::TYPE_STARRED); + } + + /** + * Get all users for conversations in one query. + */ + public static function loadUsers($conversations) + { + $user_ids = $conversations->pluck('user_id')->unique()->toArray(); + if (!$user_ids || (count($user_ids) == 1 && empty($user_ids[0]))) { + return; + } + + $users = User::whereIn('id', $user_ids)->get(); + if (!$users) { + return; + } + + foreach ($conversations as $conversation) { + if (empty($conversation->user_id)) { + continue; + } + foreach ($users as $user) { + if ($user->id == $conversation->user_id) { + $conversation->user = $user; + + continue 2; + } + } + } + } + + /** + * Get all customers for conversations in one query. + */ + public static function loadCustomers($conversations) + { + $ids = $conversations->pluck('customer_id')->unique()->toArray(); + if (!$ids) { + return; + } + + $customers = Customer::whereIn('id', $ids)->get(); + if (!$customers) { + return; + } + + foreach ($conversations as $conversation) { + if (empty($conversation->customer_id)) { + continue; + } + foreach ($customers as $customer) { + if ($customer->id == $conversation->customer_id) { + $conversation->customer = $customer; + + continue 2; + } + } + } + } + + /** + * Load mailboxes. + */ + public static function loadMailboxes($conversations) + { + $ids = $conversations->pluck('mailbox_id')->unique()->toArray(); + if (!$ids) { + return; + } + + $mailboxes = Mailbox::whereIn('id', $ids)->get(); + if (!$mailboxes) { + return; + } + + foreach ($conversations as $conversation) { + if (empty($conversation->mailbox_id)) { + continue; + } + foreach ($mailboxes as $mailbox) { + if ($mailbox->id == $conversation->mailbox_id) { + $conversation->mailbox = $mailbox; + + continue 2; + } + } + } + } + + public function getSubject() + { + if ($this->subject) { + return $this->subject; + } else { + return __('(no subject)'); + } + } + + /** + * Add conversation to folder via conversation_folder table. + */ + public function addToFolder($folder_type, $user_id = null) + { + // Find folder. + $folder_query = Folder::where('mailbox_id', $this->mailbox_id) + ->where('type', $folder_type); + if ($user_id) { + $folder_query->where('user_id', $user_id); + } + $folder = $folder_query->first(); + + if (!$folder) { + return false; + } + + $values = [ + 'folder_id' => $folder->id, + 'conversation_id' => $this->id, + ]; + $folder_exists = ConversationFolder::select('id')->where($values)->first(); + if (!$folder_exists) { + // This throws an exception if record exists + $this->folders()->attach($folder->id); + } + $folder->updateCounters(); + + // updateOrCreate does not create properly with ManyToMany + // $values = [ + // 'folder_id' => $folder->id, + // 'conversation_id' => $this->id, + // ]; + // ConversationFolder::updateOrCreate($values, $values); + + return true; + } + + /** + * When removing from Starred folder, don't forget to clear cache using clearStarredByUserCache() + */ + public function removeFromFolder($folder_type, $user_id = null) + { + // Find folder + $folder_query = Folder::where('mailbox_id', $this->mailbox_id) + ->where('type', $folder_type); + + if ($user_id) { + $folder_query->where('user_id', $user_id); + } + $folder = $folder_query->first(); + + if (!$folder) { + return false; + } + + $this->folders()->detach($folder->id); + $folder->updateCounters(); + + return true; + } + + /** + * Remove conversation from drafts folder if there are no draft threads in conversation. + */ + public function maybeRemoveFromDrafts() + { + $has_drafts = Thread::where('conversation_id', $this->id) + ->where('state', Thread::STATE_DRAFT) + ->select('id') + ->first(); + if (!$has_drafts) { + $this->removeFromFolder(Folder::TYPE_DRAFTS); + + return true; + } + + return false; + } + + /** + * Delete threads and everything connected to threads. + */ + public function deleteThreads() + { + $this->threads->each(function ($thread, $i) { + $thread->deleteThread(); + }); + } + + /** + * Get waiting since time for the conversation. + * + * @param [type] $folder [description] + * + * @return [type] [description] + */ + public function getWaitingSince($folder = null) + { + if (!$folder) { + $folder = $this->folder; + } + $waiting_since_field = $folder->getWaitingSinceField(); + if ($waiting_since_field) { + // For phone conversations. + if (empty($this->$waiting_since_field)) { + $waiting_since_field = 'updated_at'; + } + return \App\User::dateDiffForHumans($this->$waiting_since_field); + } else { + return ''; + } + } + + /** + * Get type name. + */ + public function getTypeName() + { + return self::typeToName($this->type); + } + + /** + * Get type name . + */ + public static function typeToName($type) + { + $name = ''; + + switch ($type) { + case self::TYPE_EMAIL: + $name = __('Email'); + break; + + case self::TYPE_PHONE: + $name = __('Phone'); + break; + + case self::TYPE_CHAT: + $name = __('Chat'); + break; + + default: + $name = \Eventy::filter('conversation.type_name', $type); + break; + } + + return $name; + } + + /** + * Get emails which should be excluded from CC and BCC. + */ + public function getExcludeArray($mailbox = null) + { + if (!$mailbox) { + $mailbox = $this->mailbox; + } + $customer_emails = [$this->customer_email]; + if (strstr($this->customer_email ?? '', ',')) { + // customer_email contains mutiple addresses (when new conversation for multiple recipients created) + $customer_emails = explode(',', $this->customer_email); + } + return array_merge($mailbox->getEmails(), $customer_emails); + } + + /** + * Is it an email conversation. + */ + public function isEmail() + { + return ($this->type == self::TYPE_EMAIL); + } + + /** + * Is it as phone conversation. + */ + public function isPhone() + { + return ($this->type == self::TYPE_PHONE); + } + + /** + * Is it as chat conversation. + */ + public function isChat() + { + return ($this->type == self::TYPE_CHAT); + } + + /** + * Get information on viewers for conversation table. + */ + public static function getViewersInfo($conversations, $fields = ['id', 'first_name', 'last_name'], $exclude_user_ids = []) + { + $viewers_cache = \Cache::get('conv_view'); + $viewers = []; + $first_user_id = null; + $user_ids = []; + foreach ($conversations as $conversation) { + if (!empty($viewers_cache[$conversation->id])) { + // Get replying viewers + foreach ($viewers_cache[$conversation->id] as $user_id => $viewer) { + if (!$first_user_id) { + $first_user_id = $user_id; + } + if (!empty($viewer['r']) && !in_array($user_id, $exclude_user_ids)) { + $viewers[$conversation->id] = [ + 'user' => null, + 'user_id' => $user_id, + 'replying' => true + ]; + $user_ids[] = $user_id; + break; + } + } + // Get first non-replying viewer + if (empty($viewers[$conversation->id]) && !in_array($user_id, $exclude_user_ids)) { + $viewers[$conversation->id] = [ + 'user' => null, + 'user_id' => $first_user_id, + 'replying' => false + ]; + $user_ids[] = $first_user_id; + } + } + } + // Get all viewing users in one query + if ($user_ids) { + $user_ids = array_unique($user_ids); + $users = User::select($fields)->whereIn('id', $user_ids)->get(); + + foreach ($viewers as $i => $viewer) { + foreach ($users as $user) { + if ($user->id == $viewer['user_id']) { + $viewers[$i]['user'] = $user; + } + } + } + } + return $viewers; + } + + public function changeState($new_state, $user = null) + { + if (!array_key_exists($new_state, self::$states)) { + return; + } + + $prev_state = $this->state; + + $this->state = $new_state; + $this->save(); + + \Eventy::action('conversation.state_changed', $this, $user, $prev_state); + } + + public function changeStatus($new_status, $user, $create_thread = true) + { + if (!array_key_exists($new_status, self::$statuses)) { + return; + } + + $prev_status = $this->status; + + $this->setStatus($new_status, $user); + $this->save(); + + // Create lineitem thread + if ($create_thread) { + $thread = new Thread(); + $thread->conversation_id = $this->id; + $thread->user_id = $this->user_id; + $thread->type = Thread::TYPE_LINEITEM; + $thread->state = Thread::STATE_PUBLISHED; + $thread->status = $this->status; + $thread->action_type = Thread::ACTION_TYPE_STATUS_CHANGED; + $thread->source_via = Thread::PERSON_USER; + // todo: this need to be changed for API + $thread->source_type = Thread::SOURCE_TYPE_WEB; + $thread->customer_id = $this->customer_id; + $thread->created_by_user_id = $user->id; + $thread->save(); + } + + event(new ConversationStatusChanged($this)); + \Eventy::action('conversation.status_changed', $this, $user, $changed_on_reply = false, $prev_status); + } + + public function changeUser($new_user_id, $user, $create_thread = true) + { + $prev_user_id = $this->user_id; + + $this->setUser($new_user_id); + $this->save(); + + if ($create_thread) { + // Create lineitem thread + $thread = new Thread(); + $thread->conversation_id = $this->id; + $thread->user_id = $this->user_id; + $thread->type = Thread::TYPE_LINEITEM; + $thread->state = Thread::STATE_PUBLISHED; + $thread->status = Thread::STATUS_NOCHANGE; + $thread->action_type = Thread::ACTION_TYPE_USER_CHANGED; + $thread->source_via = Thread::PERSON_USER; + // todo: this need to be changed for API + $thread->source_type = Thread::SOURCE_TYPE_WEB; + $thread->customer_id = $this->customer_id; + $thread->created_by_user_id = $user->id; + $thread->save(); + } + + event(new ConversationUserChanged($this, $user)); + \Eventy::action('conversation.user_changed', $this, $user, $prev_user_id); + } + + public function deleteToFolder($user) + { + $folder_id = $this->getCurrentFolder(); + + $prev_state = $this->state; + $this->state = Conversation::STATE_DELETED; + $this->user_updated_at = date('Y-m-d H:i:s'); + $this->updateFolder(); + $this->save(); + + // Create lineitem thread + $thread = new Thread(); + $thread->conversation_id = $this->id; + $thread->user_id = $this->user_id; + $thread->type = Thread::TYPE_LINEITEM; + $thread->state = Thread::STATE_PUBLISHED; + $thread->status = Thread::STATUS_NOCHANGE; + $thread->action_type = Thread::ACTION_TYPE_DELETED_TICKET; + $thread->source_via = Thread::PERSON_USER; + // todo: this need to be changed for API + $thread->source_type = Thread::SOURCE_TYPE_WEB; + $thread->customer_id = $this->customer_id; + $thread->created_by_user_id = $user->id; + $thread->save(); + + // Remove conversation from drafts folder. + $this->removeFromFolder(Folder::TYPE_DRAFTS); + + // Recalculate only old and new folders + $this->mailbox->updateFoldersCounters(); + + \Eventy::action('conversation.deleted', $this, $user); + \Eventy::action('conversation.state_changed', $this, $user, $prev_state); + } + + public function deleteForever() + { + self::deleteConversationsForever([$this->id]); + } + + public static function deleteConversationsForever($conversation_ids) + { + \Eventy::action('conversations.before_delete_forever', $conversation_ids); + + //$conversation_ids = $conversations->pluck('id')->toArray(); + for ($i=0; $i < ceil(count($conversation_ids) / \Helper::IN_LIMIT); $i++) { + + $ids = array_slice($conversation_ids, $i*\Helper::IN_LIMIT, \Helper::IN_LIMIT); + + // Delete attachments. + $thread_ids = Thread::whereIn('conversation_id', $ids)->pluck('id')->toArray(); + Attachment::deleteByThreadIds($thread_ids); + + // Observers do not react on this kind of deleting. + + // Delete threads. + Thread::whereIn('conversation_id', $ids)->delete(); + + // Delete followers. + Follower::whereIn('conversation_id', $ids)->delete(); + + // Delete conversations. + Conversation::whereIn('id', $ids)->delete(); + ConversationFolder::whereIn('conversation_id', $ids)->delete(); + } + } + + /** + * Create note or reply. + */ + public function createUserThread($user, $body, $data = []) + { + // Create thread + $thread = Thread::create($this, $data['type'] ?? Thread::TYPE_MESSAGE, $body, $data, false); + $thread->source_via = Thread::PERSON_USER; + $thread->source_type = Thread::SOURCE_TYPE_WEB; + $thread->user_id = $this->user_id; + $thread->status = $this->status; + $thread->state = Thread::STATE_PUBLISHED; + $thread->customer_id = $this->customer_id; + $thread->created_by_user_id = $user->id; + $thread->edited_by_user_id = null; + $thread->edited_at = null; + $thread->body = $body; + $thread->setTo($this->customer_email); + $thread->save(); + + // Update folders counters + $this->mailbox->updateFoldersCounters(); + + if ($thread->type == Thread::TYPE_NOTE) { + event(new UserAddedNote($this, $thread)); + \Eventy::action('conversation.note_added', $this, $thread); + } else { + event(new UserReplied($this, $thread)); + \Eventy::action('conversation.user_replied', $this, $thread); + } + } + + public function forward($user, $body, $to = '', $data = [], $include_attachments = false) + { + // Create thread + $thread = Thread::create($this, $data['type'] ?? Thread::TYPE_NOTE, $body, $data, false); + $thread->source_via = Thread::PERSON_USER; + $thread->source_type = Thread::SOURCE_TYPE_WEB; + $thread->user_id = $this->user_id; + $thread->status = $this->status; + $thread->state = Thread::STATE_PUBLISHED; + $thread->customer_id = $this->customer_id; + $thread->created_by_user_id = $user->id; + $thread->edited_by_user_id = null; + $thread->edited_at = null; + $thread->body = $body; + $thread->setTo($to); + + // Create forwarded conversation. + $now = date('Y-m-d H:i:s'); + $forwarded_conversation = $this->replicate(); + $forwarded_conversation->type = Conversation::TYPE_EMAIL; + $forwarded_conversation->setPreview($thread->body); + $forwarded_conversation->created_by_user_id = $user->id; + $forwarded_conversation->source_via = Conversation::PERSON_USER; + $forwarded_conversation->source_type = Conversation::SOURCE_TYPE_WEB; + $forwarded_conversation->threads_count = 0; // Counter will be incremented in ThreadObserver. + $forwarded_customer = Customer::create($to); + $forwarded_conversation->customer_id = $forwarded_customer->id; + $forwarded_conversation->customer_email = $to; + $forwarded_conversation->subject = 'Fwd: '.$forwarded_conversation->subject; + $forwarded_conversation->setCc(array_merge(Conversation::sanitizeEmails($data['cc'] ?? []), [$to])); + $forwarded_conversation->setBcc($data['bcc'] ?? []); + $forwarded_conversation->last_reply_at = $now; + $forwarded_conversation->last_reply_from = Conversation::PERSON_USER; + $forwarded_conversation->user_updated_at = $now; + $forwarded_conversation->updateFolder(); + $forwarded_conversation->save(); + + $forwarded_thread = $thread->replicate(); + + // Set forwarding meta data. + $thread->subtype = Thread::SUBTYPE_FORWARD; + $thread->setMeta(Thread::META_FORWARD_CHILD_CONVERSATION_NUMBER, $forwarded_conversation->number); + $thread->setMeta(Thread::META_FORWARD_CHILD_CONVERSATION_ID, $forwarded_conversation->id); + + $thread->save(); + + // Save forwarded thread. + $forwarded_thread->conversation_id = $forwarded_conversation->id; + $forwarded_thread->type = Thread::TYPE_MESSAGE; + $forwarded_thread->subtype = null; + $forwarded_thread->setTo($to); + // if ($attachments_info['has_attachments']) { + // $forwarded_thread->has_attachments = true; + // } + $forwarded_thread->setMeta(Thread::META_FORWARD_PARENT_CONVERSATION_NUMBER, $this->number); + $forwarded_thread->setMeta(Thread::META_FORWARD_PARENT_CONVERSATION_ID, $this->id); + $forwarded_thread->setMeta(Thread::META_FORWARD_PARENT_THREAD_ID, $thread->id); + $forwarded_thread->save(); + + // Add attachments if needed. + if ($include_attachments) { + + $replies = $this->getReplies(); + + $has_attachments = false; + foreach ($replies as $reply_thread) { + + $thread_has_attachments = false; + foreach ($reply_thread->attachments as $attachment) { + $new_attachment = $attachment->replicate(); + $new_attachment->thread_id = $forwarded_thread->id; + // We need to copy attachment file, because conversations + // can be deleted along with attachments. + $new_attachment->save(); + + try { + $attachment_file = new \Illuminate\Http\UploadedFile( + $attachment->getLocalFilePath(), $attachment->file_name, + null, null, true + ); + + $file_info = Attachment::saveFileToDisk($new_attachment, $new_attachment->file_name, '', $attachment_file); + + if (!empty($file_info['file_dir'])) { + $new_attachment->file_dir = $file_info['file_dir']; + $new_attachment->save(); + + $has_attachments = true; + $thread_has_attachments = true; + } + } catch (\Exception $e) { + \Helper::logException($e); + } + } + if ($thread_has_attachments) { + $forwarded_thread->has_attachments = true; + $forwarded_thread->save(); + } + } + if ($has_attachments) { + $forwarded_conversation->has_attachments = true; + $forwarded_conversation->save(); + } + } + + // Update folders counters + $this->mailbox->updateFoldersCounters(); + + // Notifications to users not sent. + event(new UserAddedNote($this, $thread)); + // To send email with forwarded conversation. + event(new UserReplied($forwarded_conversation, $forwarded_thread)); + \Eventy::action('conversation.user_forwarded', $this, $thread, $forwarded_conversation, $forwarded_thread); + } + + // public function getEmailHistoryCode() + // { + // return self::$email_history_codes[(int)$this->email_history] ?? 'global'; + // } + + public static function getEmailHistoryName($code) { + $label = ''; + + switch ($code) { + case 'global': + $label = __('Default'); + $label .= ' ('.self::getEmailHistoryName(config('app.email_conv_history')).')'; + break; + case 'none': + $label = __('Do not include previous messages'); + break; + case 'last': + $label = __('Include the last message'); + break; + case 'full': + $label = __('Send full conversation history'); + break; + } + + return $label; + } + + /** + * Create conversation. + * + * $threads should go from old to new. + */ + public static function create($data, $threads, $customer) + { + // Detect source_via. + $source_via = $data['source_via'] ?? 0; + if (!$source_via && !empty($threads[0])) { + if (!empty($threads[0]['type']) && $threads[0]['type'] == Thread::TYPE_CUSTOMER) { + $source_via = self::PERSON_CUSTOMER; + } else { + $source_via = self::PERSON_USER; + } + } + + $conversation = new Conversation(); + $conversation->type = $data['type']; + $conversation->subject = $data['subject']; + $conversation->mailbox_id = $data['mailbox_id']; + $conversation->source_via = $source_via; + $conversation->source_type = $data['source_type']; + $conversation->customer_id = $customer->id; + $conversation->customer_email = $customer->getMainEmail().''; + $conversation->state = $data['state'] ?? Conversation::STATE_PUBLISHED; + $conversation->imported = (int)($data['imported'] ?? false); + $conversation->closed_at = $data['closed_at'] ?? null; + $conversation->channel = $data['channel'] ?? null; + $conversation->preview = ''; + + // Phone conversation is always pending. + if ($conversation->isPhone()) { + $conversation->status = Conversation::STATUS_PENDING; + } + + // Set assignee + $conversation->user_id = null; + if (!empty($data['user_id'])) { + $user_assignee = User::find($data['user_id']); + if ($user_assignee) { + $conversation->user_id = $user_assignee->id; + } + } + + $conversation->updateFolder(); + $conversation->save(); + + // Create threads. + $threads = array_reverse($threads); + $thread_created = false; + $last_customer_id = null; + $thread_result = null; + foreach ($threads as $thread) { + + $thread['conversation_id'] = $conversation->id; + + if ($conversation->imported) { + $thread['imported'] = true; + } + if (!empty($data['status'])) { + $thread['status'] = $data['status']; + } + + $thread_result = Thread::createExtended($thread, $conversation, $customer, false); + if ($thread_result) { + $thread_created = true; + } + } + + // If no threads created, delete conversation + if (!$thread_created) { + $conversation->delete(); + return false; + } + + // Restore customer if needed. + // if ($last_customer_id && $last_customer_id != $customer->id) { + // // Otherwise it does not save. + // $conversation = $conversation->fresh(); + // $conversation->customer_id = $customer->id; + // $conversation->customer_email = $customer->getMainEmail(); + // $conversation->save(); + // } + + // Update folders counters + $conversation->mailbox->updateFoldersCounters(); + + return [ + 'conversation' => $conversation, + 'thread' => $thread_result + ]; + } + + public function getChannelName() + { + return self::channelCodeToName($this->channel); + } + + public static function channelCodeToName($channel) + { + return \Eventy::filter('channel.name', '', $channel); + } + + public static function subjectFromText($text) + { + return \Helper::textPreview($text, self::SUBJECT_LENGTH); + } + + public static function refreshConversations($conversation, $thread) + { + \App\Events\RealtimeConvNewThread::dispatchSelf($thread); + \App\Events\RealtimeMailboxNewThread::dispatchSelf($conversation->mailbox_id); + \App\Events\RealtimeChat::dispatchSelf($conversation->mailbox_id); + } + + public static function getConvTableSorting($request = null) + { + if (!$request) { + $request = request(); + } + + $result = [ + 'sort_by' => 'date', + 'order' => 'desc', + ]; + + if ( + !empty($request->sorting['sort_by']) && !empty($request->sorting['order']) && + in_array($request->sorting['sort_by'], ['subject', 'number', 'date']) && + in_array($request->sorting['order'], ['asc', 'desc']) + ) { + $result['sort_by'] = $request->sorting['sort_by']; + $result['order'] = $request->sorting['order']; + } + + return $result; + } + + public static function search($q, $filters, $user = null, $query_conversations = null) + { + $mailbox_ids = []; + + // Like is case insensitive. + $like = '%'.mb_strtolower($q).'%'; + + if (!$query_conversations) { + $query_conversations = Conversation::select('conversations.*'); + } + + // https://github.com/laravel/framework/issues/21242 + // https://github.com/laravel/framework/pull/27675 + $query_conversations->groupby('conversations.id'); + + if (!empty($filters['mailbox'])) { + // Check if the user has access to the mailbox. + if ($user->hasAccessToMailbox($filters['mailbox'])) { + $mailbox_ids[] = $filters['mailbox']; + } else { + unset($filters['mailbox']); + $mailbox_ids = $user->mailboxesIdsCanView(); + } + } else { + // Get IDs of mailboxes to which user has access + $mailbox_ids = $user->mailboxesIdsCanView(); + } + + $query_conversations->whereIn('conversations.mailbox_id', $mailbox_ids); + + $like_op = 'like'; + if (\Helper::isPgSql()) { + $like_op = 'ilike'; + } + + if ($q) { + $query_conversations->where(function ($query) use ($like, $filters, $q, $like_op) { + $query->where('conversations.subject', $like_op, $like) + ->orWhere('conversations.customer_email', $like_op, $like) + ->orWhere('conversations.'.self::numberFieldName(), (int)$q) + ->orWhere('conversations.id', (int)$q) + ->orWhere('customers.first_name', $like_op, $like) + ->orWhere('customers.last_name', $like_op, $like) + ->orWhere('threads.body', $like_op, $like) + ->orWhere('threads.from', $like_op, $like) + ->orWhere('threads.to', $like_op, $like) + ->orWhere('threads.cc', $like_op, $like) + ->orWhere('threads.bcc', $like_op, $like); + + $query = \Eventy::filter('search.conversations.or_where', $query, $filters, $q); + }); + } + + // Apply search filters. + if (!empty($filters['assigned'])) { + if ($filters['assigned'] == self::USER_UNASSIGNED) { + $filters['assigned'] = null; + } + $query_conversations->where('conversations.user_id', $filters['assigned']); + } + if (!empty($filters['customer'])) { + $customer_id = $filters['customer']; + $query_conversations->where(function ($query) use ($customer_id) { + $query->where('conversations.customer_id', '=', $customer_id) + ->orWhere('threads.created_by_customer_id', '=', $customer_id); + }); + } + if (!empty($filters['status'])) { + if (count($filters['status']) == 1) { + // = is faster than IN. + $query_conversations->where('conversations.status', '=', $filters['status'][0]); + } else { + $query_conversations->whereIn('conversations.status', $filters['status']); + } + } + if (!empty($filters['state'])) { + if (count($filters['state']) == 1) { + // = is faster than IN. + $query_conversations->where('conversations.state', '=', $filters['state'][0]); + } else { + $query_conversations->whereIn('conversations.state', $filters['state']); + } + } + if (!empty($filters['subject'])) { + $query_conversations->where('conversations.subject', $like_op, '%'.mb_strtolower($filters['subject']).'%'); + } + if (!empty($filters['attachments'])) { + $has_attachments = ($filters['attachments'] == 'yes' ? true : false); + $query_conversations->where('conversations.has_attachments', '=', $has_attachments); + } + if (!empty($filters['type'])) { + $query_conversations->where('conversations.type', '=', $filters['type']); + } + if (!empty($filters['body'])) { + $query_conversations->where('threads.body', $like_op, '%'.mb_strtolower($filters['body']).'%'); + } + if (!empty($filters['number'])) { + $query_conversations->where('conversations.'.self::numberFieldName(), '=', $filters['number']); + } + if (!empty($filters['following'])) { + if ($filters['following'] == 'yes') { + $query_conversations->join('followers', function ($join) { + $join->on('followers.conversation_id', '=', 'conversations.id'); + $join->where('followers.user_id', auth()->user()->id); + }); + } + } + if (!empty($filters['id'])) { + $query_conversations->where('conversations.id', '=', $filters['id']); + } + if (!empty($filters['after'])) { + $query_conversations->where('conversations.created_at', '>=', date('Y-m-d 00:00:00', strtotime($filters['after']))); + } + if (!empty($filters['before'])) { + $query_conversations->where('conversations.created_at', '<=', date('Y-m-d 23:59:59', strtotime($filters['before']))); + } + + // Join tables if needed + $query_sql = $query_conversations->toSql(); + if (!strstr($query_sql, '`threads`.`conversation_id`')) { + $query_conversations->join('threads', function ($join) { + $join->on('conversations.id', '=', 'threads.conversation_id'); + }); + } + + if (!strstr($query_sql, '`customers`.`id`')) { + $query_conversations->leftJoin('customers', 'conversations.customer_id', '=' ,'customers.id'); + } + + $query_conversations = \Eventy::filter('search.conversations.apply_filters', $query_conversations, $filters, $q); + + $sorting = Conversation::getConvTableSorting(); + if ($sorting['sort_by'] == 'date') { + $sorting['sort_by'] = 'last_reply_at'; + } + $query_conversations->orderBy($sorting['sort_by'], $sorting['order']); + + return $query_conversations; + } + + public function getNumberAttribute($value) + { + if (self::$custom_number_cache === null) { + self::$custom_number_cache = config('app.custom_number'); + } + if (self::$custom_number_cache) { + return $value; + } else { + return $this->id; + } + } + + public static function numberFieldName() + { + if (self::$custom_number_cache === null) { + self::$custom_number_cache = config('app.custom_number'); + } + if (self::$custom_number_cache) { + return 'number'; + } else { + return 'id'; + } + } + + /** + * Get meta value. + */ + public function getMeta($key, $default = null) + { + if (isset($this->meta[$key])) { + return $this->meta[$key]; + } else { + return $default; + } + } + + /** + * Set meta value. + */ + public function setMeta($key, $value, $save = false) + { + $meta = $this->meta; + $meta[$key] = $value; + $this->meta = $meta; + + if ($save) { + $this->save(); + } + } + + public static function updatePreview($conversation_id) + { + // Get last suitable thread. + $thread = Thread::where('conversation_id', $conversation_id) + ->whereIn('type', [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE, Thread::TYPE_NOTE]) + ->where('state', Thread::STATE_PUBLISHED) + ->where(function ($query) { + $query->where('subtype', null) + ->orWhere('subtype', '!=', Thread::SUBTYPE_FORWARD); + }) + ->orderBy('created_at', 'desc') + ->first(); + + if ($thread) { + $thread->conversation->setPreview($thread->body); + $thread->conversation->save(); + } + } + + public function isInChatMode() + { + return $this->isChat() && \Helper::isChatMode() && \Route::is('conversations.view'); + } + + public static function getChats($mailbox_id, $offset = 0, $limit = self::CHATS_LIST_SIZE+1) + { + $chats = Conversation::where('type', self::TYPE_CHAT) + ->where('mailbox_id', $mailbox_id) + ->where('state', self::STATE_PUBLISHED) + ->whereIn('status', [self::STATUS_ACTIVE, self::STATUS_PENDING]) + ->orderBy('last_reply_at', 'desc') + ->offset($offset) + ->limit($limit) + ->get(); + + // Preload customers. + if (count($chats)) { + self::loadCustomers($chats); + } + + return $chats; + } +} diff --git a/freescout-dist/app/ConversationFolder.php b/freescout-dist/app/ConversationFolder.php new file mode 100644 index 0000000..b1da108 --- /dev/null +++ b/freescout-dist/app/ConversationFolder.php @@ -0,0 +1,17 @@ + 'unknown', + self::GENDER_MALE => 'male', + self::GENDER_FEMALE => 'female', + ]; + + /** + * Phone types. + */ + const PHONE_TYPE_WORK = 1; + const PHONE_TYPE_HOME = 2; + const PHONE_TYPE_OTHER = 3; + const PHONE_TYPE_MOBILE = 4; + const PHONE_TYPE_FAX = 5; + const PHONE_TYPE_PAGER = 6; + + /** + * For API. + */ + public static $phone_types = [ + self::PHONE_TYPE_WORK => 'work', + self::PHONE_TYPE_HOME => 'home', + self::PHONE_TYPE_MOBILE => 'mobile', + self::PHONE_TYPE_FAX => 'fax', + self::PHONE_TYPE_PAGER => 'pager', + self::PHONE_TYPE_OTHER => 'other', + ]; + + /** + * Photo types. + */ + const PHOTO_TYPE_UKNOWN = 1; + const PHOTO_TYPE_GRAVATAR = 2; + const PHOTO_TYPE_TWITTER = 3; + const PHOTO_TYPE_FACEBOOK = 4; + const PHOTO_TYPE_GOOGLEPROFILE = 5; + const PHOTO_TYPE_GOOGLEPLUS = 6; + const PHOTO_TYPE_LINKEDIN = 7; + const PHOTO_TYPE_VK = 8; // Extra + + /** + * For API. + */ + public static $photo_types = [ + self::PHOTO_TYPE_UKNOWN => 'unknown', + self::PHOTO_TYPE_GRAVATAR => 'gravatar', + self::PHOTO_TYPE_TWITTER => 'twitter', + self::PHOTO_TYPE_FACEBOOK => 'facebook', + self::PHOTO_TYPE_GOOGLEPROFILE => 'googleprofile', + self::PHOTO_TYPE_GOOGLEPLUS => 'googleplus', + self::PHOTO_TYPE_LINKEDIN => 'linkedin', + self::PHOTO_TYPE_VK => 'vk', // Extra + ]; + + /** + * Chat types. + */ + // const CHAT_TYPE_AIM = 1; + // const CHAT_TYPE_GTALK = 2; + // const CHAT_TYPE_ICQ = 3; + // const CHAT_TYPE_XMPP = 4; + // const CHAT_TYPE_MSN = 5; + // const CHAT_TYPE_SKYPE = 6; + // const CHAT_TYPE_YAHOO = 7; + // const CHAT_TYPE_QQ = 8; + // const CHAT_TYPE_WECHAT = 10; + // const CHAT_TYPE_OTHER = 9; + + /** + * For API. + */ + // public static $chat_types = [ + // self::CHAT_TYPE_AIM => 'aim', + // self::CHAT_TYPE_GTALK => 'gtalk', + // self::CHAT_TYPE_ICQ => 'icq', + // self::CHAT_TYPE_XMPP => 'xmpp', + // self::CHAT_TYPE_MSN => 'msn', + // self::CHAT_TYPE_SKYPE => 'skype', + // self::CHAT_TYPE_YAHOO => 'yahoo', + // self::CHAT_TYPE_QQ => 'qq', + // self::CHAT_TYPE_WECHAT => 'wechat', // Extra + // self::CHAT_TYPE_OTHER => 'other', + // ]; + + // public static $chat_type_names = [ + // self::CHAT_TYPE_AIM => 'AIM', + // self::CHAT_TYPE_GTALK => 'Google+', + // self::CHAT_TYPE_ICQ => 'ICQ', + // self::CHAT_TYPE_XMPP => 'XMPP', + // self::CHAT_TYPE_MSN => 'MSN', + // self::CHAT_TYPE_SKYPE => 'Skype', + // self::CHAT_TYPE_YAHOO => 'Yahoo', + // self::CHAT_TYPE_QQ => 'QQ', + // self::CHAT_TYPE_WECHAT => 'WeChat', // Extra + // self::CHAT_TYPE_OTHER => 'Other', + // ]; + + /** + * Social types. + */ + const SOCIAL_TYPE_TWITTER = 1; + const SOCIAL_TYPE_FACEBOOK = 2; + const SOCIAL_TYPE_TELEGRAM = 14; + const SOCIAL_TYPE_LINKEDIN = 3; + const SOCIAL_TYPE_ABOUTME = 4; + const SOCIAL_TYPE_GOOGLE = 5; + const SOCIAL_TYPE_GOOGLEPLUS = 6; + const SOCIAL_TYPE_TUNGLEME = 7; + const SOCIAL_TYPE_QUORA = 8; + const SOCIAL_TYPE_FOURSQUARE = 9; + const SOCIAL_TYPE_YOUTUBE = 10; + const SOCIAL_TYPE_FLICKR = 11; + const SOCIAL_TYPE_VK = 13; // Extra + const SOCIAL_TYPE_OTHER = 12; + + public static $social_types = [ + self::SOCIAL_TYPE_TWITTER => 'twitter', + self::SOCIAL_TYPE_FACEBOOK => 'facebook', + self::SOCIAL_TYPE_TELEGRAM => 'telegram', + self::SOCIAL_TYPE_LINKEDIN => 'linkedin', + self::SOCIAL_TYPE_ABOUTME => 'aboutme', + self::SOCIAL_TYPE_GOOGLE => 'google', + self::SOCIAL_TYPE_GOOGLEPLUS => 'googleplus', + self::SOCIAL_TYPE_TUNGLEME => 'tungleme', + self::SOCIAL_TYPE_QUORA => 'quora', + self::SOCIAL_TYPE_FOURSQUARE => 'foursquare', + self::SOCIAL_TYPE_YOUTUBE => 'youtube', + self::SOCIAL_TYPE_FLICKR => 'flickr', + self::SOCIAL_TYPE_VK => 'vk', // Extra + self::SOCIAL_TYPE_OTHER => 'other', + ]; + + public static $social_type_names = [ + self::SOCIAL_TYPE_TWITTER => 'Twitter', + self::SOCIAL_TYPE_FACEBOOK => 'Facebook', + self::SOCIAL_TYPE_TELEGRAM => 'Telegram', + self::SOCIAL_TYPE_LINKEDIN => 'Linkedin', + self::SOCIAL_TYPE_ABOUTME => 'About.me', + self::SOCIAL_TYPE_GOOGLE => 'Google', + self::SOCIAL_TYPE_GOOGLEPLUS => 'Google+', + self::SOCIAL_TYPE_TUNGLEME => 'Tungle.me', + self::SOCIAL_TYPE_QUORA => 'Quora', + self::SOCIAL_TYPE_FOURSQUARE => 'Foursquare', + self::SOCIAL_TYPE_YOUTUBE => 'YouTube', + self::SOCIAL_TYPE_FLICKR => 'Flickr', + self::SOCIAL_TYPE_VK => 'VK', + self::SOCIAL_TYPE_OTHER => 'Other', + ]; + + /** + * Search filters. + */ + public static $search_filters = [ + 'mailbox', + ]; + + /** + * Countries list. + */ + public static $countries = [ + 'US' => 'United States', + 'AU' => 'Australia', + 'CA' => 'Canada', + 'DK' => 'Denmark', + 'FR' => 'France', + 'DE' => 'Germany', + 'IT' => 'Italy', + 'JP' => 'Japan', + 'MX' => 'Mexico', + 'ES' => 'Spain', + 'SE' => 'Sweden', + 'GB' => 'United Kingdom', + 'AF' => 'Afghanistan', + 'AX' => 'Åland Islands', + 'AL' => 'Albania', + 'DZ' => 'Algeria', + 'AS' => 'American Samoa', + 'AD' => 'Andorra', + 'AO' => 'Angola', + 'AI' => 'Anguilla', + 'AQ' => 'Antarctica', + 'AG' => 'Antigua and Barbuda', + 'AR' => 'Argentina', + 'AM' => 'Armenia', + 'AW' => 'Aruba', + 'AT' => 'Austria', + 'AZ' => 'Azerbaijan', + 'BS' => 'Bahamas', + 'BH' => 'Bahrain', + 'BD' => 'Bangladesh', + 'BB' => 'Barbados', + 'BY' => 'Belarus', + 'BE' => 'Belgium', + 'BZ' => 'Belize', + 'BJ' => 'Benin', + 'BM' => 'Bermuda', + 'BT' => 'Bhutan', + 'BO' => 'Bolivia', + 'BQ' => 'Bonaire', + 'BA' => 'Bosnia and Herzegowina', + 'BW' => 'Botswana', + 'BV' => 'Bouvet Island', + 'BR' => 'Brazil', + 'IO' => 'British Indian Ocean Territory', + 'BN' => 'Brunei Darussalam', + 'BG' => 'Bulgaria', + 'BF' => 'Burkina Faso', + 'BI' => 'Burundi', + 'KH' => 'Cambodia', + 'CM' => 'Cameroon', + 'CV' => 'Cape Verde', + 'KY' => 'Cayman Islands', + 'CF' => 'Central African Republic', + 'TD' => 'Chad', + 'CL' => 'Chile', + 'CN' => 'China', + 'CX' => 'Christmas Island', + 'CC' => 'Cocos (Keeling) Islands', + 'CO' => 'Colombia', + 'KM' => 'Comoros', + 'CG' => 'Congo', + 'CD' => 'Congo, DR', + 'CK' => 'Cook Islands', + 'CR' => 'Costa Rica', + 'CI' => "Cote D'Ivoire", + 'HR' => 'Croatia', + 'CU' => 'Cuba', + 'CW' => 'Curacao', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DJ' => 'Djibouti', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'EC' => 'Ecuador', + 'EG' => 'Egypt', + 'SV' => 'El Salvador', + 'GQ' => 'Equatorial Guinea', + 'ER' => 'Eritrea', + 'EE' => 'Estonia', + 'ET' => 'Ethiopia', + 'FK' => 'Falkland Islands (Malvinas)', + 'FO' => 'Faroe Islands', + 'FJ' => 'Fiji', + 'FI' => 'Finland', + 'GF' => 'French Guiana', + 'PF' => 'French Polynesia', + 'TF' => 'French Southern Territories', + 'GA' => 'Gabon', + 'GM' => 'Gambia', + 'GE' => 'Georgia', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GR' => 'Greece', + 'GL' => 'Greenland', + 'GD' => 'Grenada', + 'GP' => 'Guadeloupe', + 'GU' => 'Guam', + 'GT' => 'Guatemala', + 'GG' => 'Guernsey', + 'GN' => 'Guinea', + 'GW' => 'Guinea-bissau', + 'GY' => 'Guyana', + 'HT' => 'Haiti', + 'HM' => 'Heard and Mc Donald Islands', + 'HN' => 'Honduras', + 'HK' => 'Hong Kong', + 'HU' => 'Hungary', + 'IS' => 'Iceland', + 'IN' => 'India', + 'ID' => 'Indonesia', + 'IR' => 'Iran (Islamic Republic of)', + 'IQ' => 'Iraq', + 'IE' => 'Ireland', + 'IM' => 'Isle of Man', + 'IL' => 'Israel', + 'JM' => 'Jamaica', + 'JE' => 'Jersey', + 'JO' => 'Jordan', + 'KZ' => 'Kazakhstan', + 'KE' => 'Kenya', + 'KI' => 'Kiribati', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KG' => 'Kyrgyzstan', + 'LA' => "Lao People's Democratic Republic", + 'LV' => 'Latvia', + 'LB' => 'Lebanon', + 'LS' => 'Lesotho', + 'LR' => 'Liberia', + 'LY' => 'Libya', + 'LI' => 'Liechtenstein', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'MO' => 'Macao', + 'MK' => 'Macedonia, The Former Yugoslav Republic of', + 'MG' => 'Madagascar', + 'MW' => 'Malawi', + 'MY' => 'Malaysia', + 'MV' => 'Maldives', + 'ML' => 'Mali', + 'MT' => 'Malta', + 'MH' => 'Marshall Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MU' => 'Mauritius', + 'YT' => 'Mayotte', + 'FM' => 'Micronesia, Federated States of', + 'MD' => 'Moldova, Republic of', + 'MC' => 'Monaco', + 'MN' => 'Mongolia', + 'ME' => 'Montenegro', + 'MS' => 'Montserrat', + 'MA' => 'Morocco', + 'MZ' => 'Mozambique', + 'MM' => 'Myanmar', + 'NA' => 'Namibia', + 'NR' => 'Nauru', + 'NP' => 'Nepal', + 'NL' => 'Netherlands', + 'NC' => 'New Caledonia', + 'NZ' => 'New Zealand', + 'NI' => 'Nicaragua', + 'NE' => 'Niger', + 'NG' => 'Nigeria', + 'NU' => 'Niue', + '00' => 'None Available', + 'NF' => 'Norfolk Island', + 'MP' => 'Northern Mariana Islands', + 'NO' => 'Norway', + 'OM' => 'Oman', + 'PK' => 'Pakistan', + 'PW' => 'Palau', + 'PS' => 'Palestine, State of', + 'PA' => 'Panama', + 'PG' => 'Papua New Guinea', + 'PY' => 'Paraguay', + 'PE' => 'Peru', + 'PH' => 'Philippines', + 'PN' => 'Pitcairn', + 'PL' => 'Poland', + 'PT' => 'Portugal', + 'PR' => 'Puerto Rico', + 'QA' => 'Qatar', + 'RE' => 'Reunion', + 'RO' => 'Romania', + 'RU' => 'Russia', + 'RW' => 'Rwanda', + 'BL' => 'Saint Barthelemy', + 'KN' => 'Saint Kitts and Nevis', + 'LC' => 'Saint Lucia', + 'MF' => 'Saint Martin (French part)', + 'VC' => 'Saint Vincent and the Grenadines', + 'WS' => 'Samoa', + 'SM' => 'San Marino', + 'ST' => 'Sao Tome and Principe', + 'SA' => 'Saudi Arabia', + 'SN' => 'Senegal', + 'RS' => 'Serbia', + 'SC' => 'Seychelles', + 'SL' => 'Sierra Leone', + 'SG' => 'Singapore', + 'SX' => 'Sint Maarten (Dutch part)', + 'SK' => 'Slovakia', + 'SI' => 'Slovenia', + 'SB' => 'Solomon Islands', + 'SO' => 'Somalia', + 'ZA' => 'South Africa', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'SS' => 'South Sudan', + 'LK' => 'Sri Lanka', + 'SH' => 'St. Helena', + 'PM' => 'St. Pierre and Miquelon', + 'SD' => 'Sudan', + 'SR' => 'Suriname', + 'SJ' => 'Svalbard and Jan Mayen', + 'SZ' => 'Swaziland', + 'CH' => 'Switzerland', + 'SY' => 'Syrian Arab Republic', + 'TW' => 'Taiwan, Province of China', + 'TJ' => 'Tajikistan', + 'TZ' => 'Tanzania, United Republic of', + 'TH' => 'Thailand', + 'TL' => 'Timor-Leste', + 'TG' => 'Togo', + 'TK' => 'Tokelau', + 'TO' => 'Tonga', + 'TT' => 'Trinidad and Tobago', + 'TN' => 'Tunisia', + 'TR' => 'Turkey', + 'TM' => 'Turkmenistan', + 'TC' => 'Turks and Caicos Islands', + 'TV' => 'Tuvalu', + 'UG' => 'Uganda', + 'UA' => 'Ukraine', + 'AE' => 'United Arab Emirates', + 'UM' => 'United States Minor Outlying Islands', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VU' => 'Vanuatu', + 'VA' => 'Vatican City State (Holy See)', + 'VE' => 'Venezuela', + 'VN' => 'Vietnam', + 'VG' => 'Virgin Islands (British)', + 'VI' => 'Virgin Islands (U.S.)', + 'WF' => 'Wallis and Futuna Islands', + 'EH' => 'Western Sahara', + 'YE' => 'Yemen', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', + ]; + + protected $casts = [ + 'meta' => 'array', + ]; + + /** + * Attributes which are not fillable using fill() method. + */ + protected $guarded = ['id']; + + /** + * Attributes fillable using fill() method. + * + * @var [type] + */ + protected $fillable = ['first_name', 'last_name', 'company', 'job_title', 'address', 'city', 'state', 'zip', 'country', 'photo_url', 'age', 'gender', 'notes', 'channel', 'channel_id', 'social_profiles']; + + /** + * Fields stored as JSON. + */ + protected $json_fields = ['phones', 'websites', 'social_profiles']; + + /** + * Get customer emails. + */ + public function emails() + { + return $this->hasMany('App\Email'); + } + + /** + * Get customer emails. + */ + public function emails_cached() + { + return $this->hasMany('App\Email')->rememberForever(); + } + + /** + * Get customer conversations. + */ + public function conversations() + { + return $this->hasMany('App\Conversation'); + } + + /** + * Get main email. + */ + public function getMainEmail() + { + return optional($this->emails_cached()->first())->email.''; + } + + /** + * Get main email. + */ + public static function getMainEmailStatic($customer_id) + { + return Email::select('email')->where('customer_id', $customer_id)->pluck('email'); + } + + /** + * Get customer full name. + * + * @return string + */ + public function getFullName($email_if_empty = false, $first_part_from_email = false) + { + if ($this->first_name && $this->last_name) { + return $this->first_name.' '.$this->last_name; + } elseif (!$this->last_name && $this->first_name) { + return $this->first_name; + } elseif (!$this->first_name && $this->last_name) { + return $this->last_name; + } elseif ($email_if_empty) { + $email = $this->getMainEmail(); + if ($first_part_from_email) { + return $this->getNameFromEmail($email); + } else { + return $email; + } + } + + return ''; + } + + /** + * Get customer first name. + * + * @return string + */ + public function getFirstName($email_if_empty = false) + { + if ($this->first_name) { + return $this->first_name; + } elseif ($email_if_empty) { + return $this->getNameFromEmail(); + } + + return ''; + } + + /** + * Get first part of the email. + * + * @return string + */ + public function getNameFromEmail($email = '') + { + if (!$email) { + $email = optional($this->emails_cached()->first())->email; + } + if ($email) { + return explode('@', $email)[0]; + } else { + return ''; + } + } + + /** + * Set customer emails. + * + * @param array $emails + */ + public function syncEmails($emails) + { + if (is_array($emails)) { + $deleted_emails = []; + foreach ($this->emails as $email) { + foreach ($emails as $email_address) { + if (Email::sanitizeEmail($email->email) == Email::sanitizeEmail($email_address)) { + continue 2; + } + } + $deleted_emails[] = $email; + } + foreach ($emails as $email_address) { + $email_address = Email::sanitizeEmail($email_address); + if (!$email_address) { + continue; + } + $email = Email::where('email', $email_address)->first(); + $new_emails = []; + if ($email) { + // Assign email to current customer + if ($email->customer_id != $this->id) { + $email->customer()->associate($this); + $email->save(); + } + } else { + $new_emails[] = new Email(['email' => $email_address]); + } + if ($new_emails) { + $this->emails()->saveMany($new_emails); + } + } + + foreach ($deleted_emails as $email) { + if (Conversation::where('customer_email', $email->email)->exists()) { + // Create customers for deleted emails + // if there is a conversation with 'customer_email'. + $customer = new self(); + $customer->save(); + $email->customer()->associate($customer); + $email->save(); + } else { + // Simply delete an email. + $email->delete(); + } + } + } + } + + /** + * Add new email to customer. + */ + public function addEmail($email_address, $check_if_exists = false) + { + // Check if email already exists and belongs to another customer. + if ($check_if_exists) { + $email = Email::where('email', $email_address)->first(); + if ($email && !empty($email->customer_id)) { + return false; + } + } + $new_email = new Email(['email' => $email_address]); + $this->emails()->save($new_email); + } + + /** + * Get customers phones as array. + * + * @return array + */ + public function getPhones($dummy_if_empty = false) + { + $phones = json_decode($this->phones ?? '', true); + + if (is_array($phones) && count($phones)) { + return $phones; + } elseif ($dummy_if_empty) { + return [[ + 'value' => '', + 'type' => self::PHONE_TYPE_WORK, + ]]; + } else { + return []; + } + } + + public function getMainPhoneValue() + { + return $this->getMainPhoneNumber(); + } + + public function getMainPhoneNumber() + { + $phones = $this->getPhones(); + return $phones[0]['value'] ?? ''; + } + + /** + * Set phones as JSON. + * + * @param array $phones_array + */ + public function setPhones(array $phones_array) + { + $phones_array = self::formatPhones($phones_array); + + // Remove dubplicates. + $list = []; + foreach ($phones_array as $i => $data) { + if (in_array($data['value'], $list)) { + unset($phones_array[$i]); + } else { + $list[] = $data['value']; + } + } + + $this->phones = \Helper::jsonEncodeUtf8($phones_array); + } + + /** + * Sanitize phones array. + * + * @param array $phones_array [description] + * + * @return array [description] + */ + public static function formatPhones(array $phones_array) + { + $phones = []; + + foreach ($phones_array as $phone) { + if (is_array($phone)) { + if (!empty($phone['value'])) { + if (empty($phone['type']) || !in_array($phone['type'], array_keys(self::$phone_types))) { + $phone['type'] = self::PHONE_TYPE_WORK; + } + $phones[] = [ + 'value' => (string) $phone['value'], + 'type' => (int) $phone['type'], + 'n' => (string)\Helper::phoneToNumeric($phone['value']), + ]; + } + } else { + $phones[] = [ + 'value' => (string) $phone, + 'type' => self::PHONE_TYPE_WORK, + 'n' => (string)\Helper::phoneToNumeric($phone), + ]; + } + } + + return $phones; + } + + /** + * Add website. + */ + public function addPhone($phone, $type = self::PHONE_TYPE_WORK) + { + if (is_string($phone)) { + $this->setPhones(array_merge( + $this->getPhones(), + [['value' => $phone, 'type' => $type]] + )); + } else { + $this->setPhones(array_merge( + $this->getPhones(), + [$phone] + )); + } + } + + /** + * Find customer by phone number. + */ + public static function findByPhone($phone) + { + return Customer::byPhone($phone)->first(); + } + + /** + * Get query. + */ + public static function byPhone($phone) + { + $phone_numeric = \Helper::phoneToNumeric($phone); + return Customer::where('phones', 'LIKE', '%"'.$phone_numeric.'"%'); + } + + /** + * Get customers social profiles as array. + * + * @return array + */ + public function getSocialProfiles($dummy_if_empty = false) + { + $social_profiles = json_decode($this->social_profiles ?? '', true); + + if (is_array($social_profiles) && count($social_profiles)) { + return json_decode($this->social_profiles, true); + } elseif ($dummy_if_empty) { + return [[ + 'type' => '', + 'value' => '', + ]]; + } else { + return []; + } + } + + /** + * Get customers social profiles as array. + * + * @return array + */ + public function getWebsites($dummy_if_empty = false) + { + $websites = json_decode($this->websites ?? '', true); + if (is_array($websites) && count($websites)) { + return $websites; + } elseif ($dummy_if_empty) { + return ['']; + } else { + return []; + } + } + + public function getMainWebsite() + { + $websites = $this->getWebsites(); + return $websites[0] ?? ''; + } + + /** + * Set websites as JSON. + * + * @param array $websites_array + */ + public function setWebsites(array $websites_array) + { + $websites = []; + foreach ($websites_array as $key => $value) { + // FILTER_SANITIZE_URL cuts some symbols. + //$value = filter_var((string) $value, FILTER_SANITIZE_URL); + if (isset($value['value'])) { + $value = $value['value']; + } + if (!$value || preg_match("/^http(s)?:?\/?\/?$/i", $value)) { + continue; + } + if (!preg_match("/http(s)?:\/\//i", $value)) { + $value = 'http://'.$value; + } + $websites[] = (string) $value; + } + $this->websites = \Helper::jsonEncodeUtf8(array_unique($websites)); + } + + /** + * Add website. + */ + public function addWebsite($website) + { + $websites = $this->getWebsites(); + if (isset($website['value'])) { + $website = $website['value']; + } + array_push($websites, $website); + $this->setWebsites($websites); + } + + /** + * Sanitize social profiles. + * + * @param array $list [description] + * + * @return array [description] + */ + public static function formatSocialProfiles(array $list) + { + $social_profiles = []; + foreach ($list as $social_profile) { + if (is_array($social_profile)) { + if (!empty($social_profile['value']) && !empty($social_profile['type'])) { + + $type = null; + + if (is_numeric($social_profile['type']) && in_array($social_profile['type'], array_keys(self::$social_types))) { + $type = (int)$social_profile['type']; + } else { + // Find type. + foreach (self::$social_types as $type_id => $type_name) { + if ($type_name == strtolower($social_profile['type'])) { + $type = $type_id; + } + } + } + + if (!$type) { + continue; + } + + $social_profiles[] = [ + // Order of elements in array is important as we rely on it + // when searching customers by social profiles using "like". + 'value' => (string) $social_profile['value'], + 'type' => $type, + ]; + } + } else { + $social_profiles[] = [ + // Order of elements in array is important as we rely on it + // when searching customers by social profiles using "like". + 'value' => (string) $social_profile, + 'type' => self::SOCIAL_TYPE_OTHER, + ]; + } + } + + return $social_profiles; + } + + /** + * Set social profiles as JSON. + * + * @param array $websites_array + */ + public function setSocialProfiles(array $sp_array) + { + $sp_array = self::formatSocialProfiles($sp_array); + + // Remove dubplicates. + $list = []; + foreach ($sp_array as $i => $data) { + if (in_array($data['value'], $list)) { + unset($sp_array[$i]); + } else { + $list[] = $data['value']; + } + } + + $this->social_profiles = \Helper::jsonEncodeUtf8($sp_array); + } + + /** + * Create customer or get existing and fill empty fields. + * + * @param string $email + * @param array $data [description] + * + * @return [type] [description] + */ + public static function create($email, $data = []) + { + $new = false; + + $email = Email::sanitizeEmail($email); + if (!$email) { + return null; + } + $email_obj = Email::where('email', $email)->first(); + if ($email_obj) { + $customer = $email_obj->customer; + + // In case somehow the email has no customer. + if (!$customer) { + // Customer will be saved and connected to the email later. + $customer = new self(); + } + + // Update name if empty. + /*if (empty($customer->first_name) && !empty($data['first_name'])) { + $customer->first_name = $data['first_name']; + if (empty($customer->last_name) && !empty($data['last_name'])) { + $customer->last_name = $data['last_name']; + } + $customer->save(); + }*/ + } else { + $customer = new self(); + $email_obj = new Email(); + $email_obj->email = $email; + + $new = true; + } + + // Set empty fields + if ($customer->setData($data, false) || !$customer->id) { + $customer->save(); + } + + if (empty($email_obj->id) || !$email_obj->customer_id || $email_obj->customer_id != $customer->id) { + // Email may have been set in setData(). + $save_email = true; + if (!empty($data['emails']) && is_array($data['emails'])) { + foreach ($data['emails'] as $data_email) { + if (is_string($data_email) && $data_email == $email) { + $save_email = false; + break; + } + if (is_array($data_email) && !empty($data_email['value']) && $data_email['value'] == $email) { + $save_email = false; + break; + } + } + } + if ($save_email) { + $email_obj->customer()->associate($customer); + $email_obj->save(); + } + } + + // Todo: check phone uniqueness. + + if ($new) { + \Eventy::action('customer.created', $customer); + } + + return $customer; + } + + /** + * Set empty fields. + */ + public function setData($data, $replace_data = true, $save = false) + { + $result = false; + + // todo: photoUrl. + if (isset($data['photo_url'])) { + unset($data['photo_url']); + } + + if (!empty($data['background']) && empty($data['notes'])) { + $data['notes'] = $data['background']; + } + + if ($replace_data) { + // Replace data. + $data_prepared = $data; + foreach ($data_prepared as $i => $value) { + if (is_array($value)) { + unset($data_prepared[$i]); + } + } + $this->fill($data_prepared); + $result = true; + } else { + // Update empty fields. + foreach ($data as $key => $value) { + if (in_array($key, $this->fillable) && empty($this->$key)) { + $this->$key = $value; + $result = true; + } + } + } + + // Set JSON values. + if (!empty($data['phone'])) { + $this->addPhone($data['phone']); + } + foreach ($data as $key => $value) { + if (!in_array($key, $this->json_fields) && $key != 'emails') { + continue; + } + if ($key == 'phones') { + if (isset($value['value'])) { + $this->addPhone($value); + } else { + $this->setPhones($value); + // foreach ($value as $phone_value) { + // $this->addPhone($phone_value); + // } + } + $result = true; + } + if ($key == 'websites') { + if (is_array($value)) { + $this->setWebsites($value); + // foreach ($value as $website) { + // $this->addWebsite($website); + // } + } else { + $this->addWebsite($value); + } + $result = true; + } + if ($key == 'social_profiles') { + $this->setSocialProfiles($value); + $result = true; + } + if ($key == 'country') { + if (array_search($this->country, Customer::$countries)) { + $this->country = array_search($this->country, Customer::$countries); + } + $this->country = strtoupper(mb_substr($this->country, 0, 2)); + $result = true; + } + } + + // Emails must be processed the last as they need to save object. + foreach ($data as $key => $value) { + if ($key == 'emails') { + foreach ($value as $email_data) { + if (is_string($email_data)) { + if (!$this->id) { + $this->save(); + } + $email_created = Email::create($email_data, $this->id, Email::TYPE_WORK); + + if ($email_created) { + $result = true; + } + } elseif (!empty($email_data['value'])) { + if (!$this->id) { + $this->save(); + } + $email_created = Email::create($email_data['value'], $this->id, $email_data['type']); + + if ($email_created) { + $result = true; + } + } + } + break; + } + } + // Maybe Todo: check phone uniqueness. + // Same phone can be written in many ways, so it's almost useless to chek uniqueness. + + \Eventy::action('customer.set_data', $this, $data, $replace_data); + + if ($save) { + $this->save(); + } + + return $result; + } + + /** + * Create a customer, email is not required. + * For phone conversations. + */ + public static function createWithoutEmail($data = []) + { + $customer = new self(); + $customer->setData($data); + + $customer->save(); + + \Eventy::action('customer.created', $customer); + + return $customer; + } + + /** + * Get customer URL. + * + * @return string + */ + public function url() + { + return route('customers.update', ['id'=>$this->id]); + } + + /** + * Get view customer URL. + * + * @return string + */ + public function urlView() + { + return route('customers.conversations', ['id'=>$this->id]); + } + + /** + * Format date according to customer's timezone. + * + * @param Carbon $date + * @param string $format + * + * @return string + */ + public static function dateFormat($date, $format = 'M j, Y H:i') + { + return $date->format($format); + } + + /** + * Get full representation of customer. + */ + public function getEmailAndName() + { + // Email can be fetched using query. + $text = $this->email; + if (!$text) { + $text = $this->getMainEmail(); + } + if ($this->getFullName()) { + if ($text) { + $text .= ' ('.$this->getFullName().')'; + } else { + $text .= $this->getFullName(); + } + } + return $text; + } + + public function getNameAndEmail() + { + // Email can be fetched using query. + $text = $this->getFullName(); + $email = $this->email; + if (!$email) { + $email = $this->getMainEmail(); + } + if ($email) { + if ($text) { + $text .= ' <'.$email.'>'; + } else { + $text .= $email; + } + } + return $text; + } + + /** + * Get customers info for the list of emails. + */ + public static function emailsToCustomers($list) + { + $result = []; + + $data = Customer::select(['emails.email', 'customers.first_name', 'customers.last_name']) + ->join('emails', function ($join) { + $join->on('emails.customer_id', '=', 'customers.id'); + }) + ->whereIn('emails.email', $list) + //->groupby('customers.id') + ->get() + ->toArray(); + + foreach ($list as $email) { + // Dummy customer. + $customer = new Customer(); + $customer->email = $email; + + foreach ($data as $values) { + if (strtolower($values['email']) == strtolower($email)) { + $customer->first_name = $values['first_name']; + $customer->last_name = $values['last_name']; + break; + } + } + $result[$email] = $customer->getNameAndEmail(); + } + + return $result; + } + + /** + * Get customer by email. + */ + public static function getByEmail($email) + { + return Customer::select('customers.*') + ->where('emails.email', $email) + ->join('emails', function ($join) { + $join->on('emails.customer_id', '=', 'customers.id'); + })->first(); + } + + /** + * Get email or phone if email is empty. + */ + public function getEmailOrPhone() + { + if (!empty($this->email)) { + // Email can be selected with query. + return $this->email; + } elseif ($main_email = $this->getMainEmail()) { + return $main_email; + } elseif ($phones = $this->getPhones() && !empty($phones[0]['value'])) { + return $phones[0]['value']; + } + + return ''; + } + + public function getPhotoUrl($default_if_empty = true) + { + if (!empty($this->photo_url) || !$default_if_empty) { + if (!empty($this->photo_url)) { + return self::getPhotoUrlByFileName($this->photo_url); + } else { + return ''; + } + } else { + return \Eventy::filter('customer.default_avatar', asset('/img/default-avatar.png'), $this); + } + } + + public static function getPhotoUrlByFileName($file_name) + { + return Storage::url(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$file_name); + } + + /** + * Resize and save photo. + */ + public function savePhoto($real_path, $mime_type) + { + $photo_size = config('app.customer_photo_size'); + $resized_image = \App\Misc\Helper::resizeImage($real_path, $mime_type, $photo_size, $photo_size); + + if (!$resized_image) { + return false; + } + + $file_name = md5(Hash::make($this->id)).'.jpg'; + $dest_path = Storage::path(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$file_name); + + $dest_dir = pathinfo($dest_path, PATHINFO_DIRNAME); + if (!file_exists($dest_dir)) { + \File::makeDirectory($dest_dir, \Helper::DIR_PERMISSIONS); + } + + // Remove current photo + if ($this->photo_url) { + Storage::delete(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$this->photo_url); + } + + imagejpeg($resized_image, $dest_path, self::PHOTO_QUALITY); + + return $file_name; + } + + /** + * Remove user photo. + */ + public function removePhoto() + { + if ($this->photo_url) { + Storage::delete(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$this->photo_url); + } + $this->photo_url = ''; + } + + public function getCountryName() + { + if ($this->country && !empty(self::$countries[$this->country])) { + return self::$countries[$this->country]; + } else { + return ''; + } + } + + /** + * Get first and last name. + */ + public static function parseName($name) + { + $data = []; + + if (!$name) { + return $data; + } + + $name_parts = explode(' ', $name, 2); + $data['first_name'] = $name_parts[0]; + if (!empty($name_parts[1])) { + $data['last_name'] = $name_parts[1]; + } + + return $data; + } + + public static function formatSocialProfile($sp) + { + if (empty($sp['type']) || !isset(self::$social_type_names[$sp['type']])) { + $sp['type'] = self::SOCIAL_TYPE_OTHER; + } + + $sp['type_name'] = self::$social_type_names[$sp['type']]; + + $sp['value_url'] = $sp['value']; + + if (!preg_match("/^https?:\/\//i", $sp['value_url'])) { + switch ($sp['type']) { + case self::SOCIAL_TYPE_TELEGRAM: + $sp['value_url'] = 'https://t.me/'.$sp['value']; + break; + + default: + $sp['value_url'] = 'http://'.$sp['value_url']; + break; + } + } + if (empty($sp['value_url'])) { + $sp['value_url'] = ''; + } + + return $sp; + } + + public function setPhotoFromRemoteFile($url) + { + $headers = get_headers($url); + + if (!preg_match("/200/", $headers[0])) { + return false; + } + + $image_data = \Helper::getRemoteFileContents($url); + + if (!$image_data) { + return false; + } + + $temp_file = \Helper::getTempFileName(); + + \File::put($temp_file, $image_data); + + $photo_url = $this->savePhoto($temp_file, \File::mimeType($temp_file)); + + if ($photo_url) { + $this->photo_url = $photo_url; + return true; + } else { + return false; + } + } + + public function getChannelName() + { + return \Eventy::filter('channel.name', '', $this->channel); + } + + /** + * Get thread meta value. + */ + public function getMeta($key, $default = null) + { + if (isset($this->meta[$key])) { + return $this->meta[$key]; + } else { + return $default; + } + } + + /** + * Set thread meta value. + */ + public function setMeta($key, $value) + { + $meta = $this->meta; + $meta[$key] = $value; + $this->meta = $meta; + } + + public static function getPhoneTypeName($code) + { + $phone_types = [ + self::PHONE_TYPE_WORK => __('Work'), + self::PHONE_TYPE_HOME => __('Home'), + self::PHONE_TYPE_OTHER => __('Other'), + self::PHONE_TYPE_MOBILE => __('Mobile'), + self::PHONE_TYPE_FAX => __('Fax'), + self::PHONE_TYPE_PAGER => __('Pager'), + ]; + + return $phone_types[$code] ?? ''; + } + + public static function isDefaultPhoneType($code) + { + return (self::PHONE_TYPE_WORK == $code); + } + + // Method does not check if the customer + // has conversations. + public function deleteCustomer() + { + // Delete emails. + Email::where('customer_id', $this->id)->delete(); + $this->delete(); + } + + public function getChannels() + { + if (!$this->channel || !$this->channel_id) { + return collect([]); + } + return CustomerChannel::where('customer_id', $this->id)->get(); + } + + public function addChannel($channel, $channel_id) + { + // We are doing this to let existing modules not to throw error + // and as a flag that this customer has record(s) in cucstomer_channel table. + if (!$this->channel || !$this->channel_id) { + $this->channel = $channel; + $this->channel_id = $channel_id; + $this->save(); + } + + return CustomerChannel::create($this->id, $channel, $channel_id); + } + + public static function getCustomerByChannel($channel, $channel_id) + { + $customer_channel = CustomerChannel::where('channel', $channel) + ->where('channel_id', $channel_id) + ->first(); + + if ($customer_channel) { + return $customer_channel->customer; + } else { + return null; + } + } + + public function getChannelId($channel) + { + return CustomerChannel::where('customer_id', $this->id) + ->where('channel', $channel) + ->value('channel_id'); + } + + public static function findCustomersBySocialProfile($type, $value, $exclude_channel = null) + { + $value = mb_strtolower($value); + + $like = '%'.$value.'","type":'.$type.'}]'; + $customers = Customer::where('social_profiles', \Helper::sqlLikeOperator(), $like)->get(); + + // Now more prcise filtering. + foreach ($customers as $i => $customer) { + $ok = false; + foreach ($customer->getSocialProfiles() as $social_profile) { + if ($social_profile['type'] == $type + // Try to check username written in different ways: + // - username + // - @username + // - https://example.org/username + && preg_match("#(^|/|@)".preg_quote($value)."$#", trim(mb_strtolower($social_profile['value']))) + ) { + $ok = true; + break; + } + } + if (!$ok) { + $customers->forget($i); + } + } + + if ($exclude_channel && count($customers)) { + $exclude_customer_ids = CustomerChannel::whereIn('customer_id', $customers->pluck('id')) + ->where('channel', $exclude_channel) + ->pluck('customer_id'); + return $customers->whereNotIn('customer_id', $exclude_customer_ids); + } else { + return $customers; + } + } +} + diff --git a/freescout-dist/app/CustomerChannel.php b/freescout-dist/app/CustomerChannel.php new file mode 100644 index 0000000..ce1fbfc --- /dev/null +++ b/freescout-dist/app/CustomerChannel.php @@ -0,0 +1,58 @@ +belongsTo('App\Customer'); + } + + public static function create($customer_id, $channel, $channel_id) + { + try { + $customer_channel = new self(); + $customer_channel->customer_id = $customer_id; + $customer_channel->channel = $channel; + $customer_channel->channel_id = $channel_id; + $customer_channel->save(); + + return $customer_channel; + } catch (\Exception $e) { + // Already exists. + return null; + } + } + + public function getChannelName() + { + return \Eventy::filter('channel.name', '', $this->channel); + } + + public static function getChannels() + { + if (self::$channels !== null) { + return self::$channels; + } else { + self::$channels = \Eventy::filter('channels.list', []); + return self::$channels; + } + } +} diff --git a/freescout-dist/app/Email.php b/freescout-dist/app/Email.php new file mode 100644 index 0000000..4e2ed60 --- /dev/null +++ b/freescout-dist/app/Email.php @@ -0,0 +1,87 @@ + 'work', + self::TYPE_HOME => 'home', + self::TYPE_OTHER => 'other', + ]; + + public $timestamps = false; + + /** + * Attributes which are not fillable using fill() method. + */ + protected $guarded = ['id', 'customer_id']; + + /** + * Get email's customer. + */ + public function customer() + { + return $this->belongsTo('App\Customer'); + } + + /** + * Sanitize email address. + * + * @param string $email + * + * @return string + */ + public static function sanitizeEmail($email) + { + // FILTER_VALIDATE_EMAIL does not work with long emails for example + // Email validation is not recommended: + // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address/201378#201378 + // So we just check for @ + if (!preg_match('/^.+@.+$/', $email ?? '')) { + return false; + } + $email = filter_var($email, FILTER_SANITIZE_EMAIL); + $email = mb_strtolower($email, 'UTF-8'); + // Remove trailing dots. + $email = preg_replace("/\.+$/", '', $email); + // Remove dot before @ + $email = preg_replace("/\.+@/", '@', $email); + + return $email; + } + + public function getNameFromEmail() + { + return explode('@', $this->email)[0]; + } + + public static function create($email, $customer_id, $type = self::TYPE_WORK) + { + try { + $email_obj = new Email(); + $email_obj->email = $email; + $email_obj->type = array_key_exists($type, self::$types) ? $type : self::TYPE_WORK; + $email_obj->customer_id = $customer_id; + $email_obj->save(); + + return $email_obj; + } catch (\Exception $e) { + return null; + } + } +} diff --git a/freescout-dist/app/Events/ConversationCustomerChanged.php b/freescout-dist/app/Events/ConversationCustomerChanged.php new file mode 100644 index 0000000..73cc854 --- /dev/null +++ b/freescout-dist/app/Events/ConversationCustomerChanged.php @@ -0,0 +1,28 @@ +conversation = $conversation; + $this->prev_customer_id = $prev_customer_id; + $this->prev_customer_email = $prev_customer_email; + $this->by_user = $by_user; + $this->by_customer = $by_customer; + } +} diff --git a/freescout-dist/app/Events/ConversationStatusChanged.php b/freescout-dist/app/Events/ConversationStatusChanged.php new file mode 100644 index 0000000..a992c87 --- /dev/null +++ b/freescout-dist/app/Events/ConversationStatusChanged.php @@ -0,0 +1,20 @@ +conversation = $conversation; + } +} diff --git a/freescout-dist/app/Events/ConversationUserChanged.php b/freescout-dist/app/Events/ConversationUserChanged.php new file mode 100644 index 0000000..8b1c028 --- /dev/null +++ b/freescout-dist/app/Events/ConversationUserChanged.php @@ -0,0 +1,23 @@ +conversation = $conversation; + $this->user = $user; + } +} diff --git a/freescout-dist/app/Events/CustomerCreatedConversation.php b/freescout-dist/app/Events/CustomerCreatedConversation.php new file mode 100644 index 0000000..eb2ee36 --- /dev/null +++ b/freescout-dist/app/Events/CustomerCreatedConversation.php @@ -0,0 +1,23 @@ +conversation = $conversation; + $this->last_thread = $last_thread; + } +} diff --git a/freescout-dist/app/Events/CustomerReplied.php b/freescout-dist/app/Events/CustomerReplied.php new file mode 100644 index 0000000..035bae8 --- /dev/null +++ b/freescout-dist/app/Events/CustomerReplied.php @@ -0,0 +1,23 @@ +conversation = $conversation; + $this->thread = $thread; + } +} diff --git a/freescout-dist/app/Events/RealtimeBroadcastNotificationCreated.php b/freescout-dist/app/Events/RealtimeBroadcastNotificationCreated.php new file mode 100644 index 0000000..e5443ad --- /dev/null +++ b/freescout-dist/app/Events/RealtimeBroadcastNotificationCreated.php @@ -0,0 +1,96 @@ +data = $data; + $this->notifiable = $notifiable; + $this->notification = $notification; + } + + /** + * Get the channels the event should broadcast on. + * + * @return Channel|array + */ + public function broadcastOn() + { + $channels = $this->notification->broadcastOn(); + + if (!empty($channels)) { + return $channels; + } + + return [new PrivateChannel($this->channelName())]; + + //return new PrivateChannel('App.User.'.$this->receiver_user_id); + } + + /** + * Get the data to broadcast. + * + * @return array + */ + public function broadcastWith() + { + return array_merge($this->data, [ + 'id' => $this->notification->id, + 'type' => get_class($this->notification), + ]); + // return [ + // 'thread_id' => $this->thread->id + // ]; + } + + /** + * Get the broadcast channel name for the event. + * + * @return string + */ + protected function channelName() + { + if (method_exists($this->notifiable, 'receivesBroadcastNotificationsOn')) { + return $this->notifiable->receivesBroadcastNotificationsOn($this->notification); + } + + $class = str_replace('\\', '.', get_class($this->notifiable)); + + return $class.'.'.$this->notifiable->getKey(); + } +} diff --git a/freescout-dist/app/Events/RealtimeChat.php b/freescout-dist/app/Events/RealtimeChat.php new file mode 100644 index 0000000..1d53ffd --- /dev/null +++ b/freescout-dist/app/Events/RealtimeChat.php @@ -0,0 +1,102 @@ +data = $data; + } + + /** + * Get the channels the event should broadcast on. + * + * @return Channel|array + */ + public function broadcastOn() + { + return new \Illuminate\Broadcasting\Channel($this->channelName()); + } + + /** + * Get the data to broadcast. + * + * @return array + */ + public function broadcastWith() + { + return $this->data; + } + + /** + * Get the broadcast channel name for the event. + * + * @return string + */ + protected function channelName() + { + if (!empty($this->data['mailbox_id'])) { + return 'chat.'.$this->data['mailbox_id']; + } else { + return 'chat.0'; + } + } + + /** + * Helper funciton. + */ + public static function dispatchSelf($mailbox_id) + { + if (!\Helper::isChatModeAvailable()) { + return; + } + $notification_data = [ + 'mailbox_id' => $mailbox_id + ]; + event(new \App\Events\RealtimeChat($notification_data)); + } + + public static function processPayload($payload) + { + $user = auth()->user(); + $mailbox = Mailbox::rememberForever()->find($payload->mailbox_id); + + // Check if user can listen to this event. + if (!$user || !$mailbox || !$user->can('viewCached', $mailbox)) { + return []; + } + + // Chats are retrieved in the template. + $template_data = [ + 'mailbox' => $mailbox, + ]; + + $payload->chats_html = \View::make('mailboxes/partials/chat_list')->with($template_data)->render(); + + return $payload; + } +} diff --git a/freescout-dist/app/Events/RealtimeConvNewThread.php b/freescout-dist/app/Events/RealtimeConvNewThread.php new file mode 100644 index 0000000..2fc71e8 --- /dev/null +++ b/freescout-dist/app/Events/RealtimeConvNewThread.php @@ -0,0 +1,118 @@ +data = $data; + } + + /** + * Get the channels the event should broadcast on. + * + * @return Channel|array + */ + public function broadcastOn() + { + return new \Illuminate\Broadcasting\Channel($this->channelName()); + } + + /** + * Get the data to broadcast. + * + * @return array + */ + public function broadcastWith() + { + return $this->data; + } + + /** + * Get the broadcast channel name for the event. + * + * @return string + */ + protected function channelName() + { + if (!empty($this->data['conversation_id'])) { + return 'conv.'.$this->data['conversation_id']; + } else { + return 'conv.0'; + } + } + + /** + * Helper funciton. + */ + public static function dispatchSelf($thread) + { + if ($thread->state != Thread::STATE_PUBLISHED) { + return; + } + $notification_data = [ + 'thread_id' => $thread->id, + 'conversation_id' => $thread->conversation_id, + // conversation is prefetched in ThreadObserver. + 'mailbox_id' => $thread->conversation->mailbox_id, + //'user_id' => $thread->created_by_user_id, + ]; + event(new \App\Events\RealtimeConvNewThread($notification_data)); + } + + public static function processPayload($payload) + { + $user = auth()->user(); + $mailbox = Mailbox::rememberForever()->find($payload->mailbox_id); + + // Check if user can listen to this event. + if (!$user || !$mailbox || !$user->can('viewCached', $mailbox)) { + return []; + } + + $thread = Thread::find($payload->thread_id); + if (!$thread) { + return $payload; + } + + // Add thread html to the payload. + $template_data = [ + 'conversation' => $thread->conversation, + 'mailbox' => $thread->conversation->mailbox, + 'threads' => [$thread], + ]; + + $payload->thread_html = \View::make('conversations/partials/threads')->with($template_data)->render(); + $payload->conversation_user_id = $thread->conversation->user_id; + $payload->conversation_status = $thread->conversation->status; + $payload->conversation_status_class = Conversation::$status_classes[$thread->conversation->status]; + $payload->conversation_status_icon = Conversation::$status_icons[$thread->conversation->status]; + + return $payload; + } +} diff --git a/freescout-dist/app/Events/RealtimeConvView.php b/freescout-dist/app/Events/RealtimeConvView.php new file mode 100644 index 0000000..05b92de --- /dev/null +++ b/freescout-dist/app/Events/RealtimeConvView.php @@ -0,0 +1,113 @@ +data = $data; + // $this->notifiable = $notifiable; + // $this->notification = $notification; + } + + /** + * Get the channels the event should broadcast on. + * + * @return Channel|array + */ + public function broadcastOn() + { + // $channels = $this->notification->broadcastOn(); + + // if (!empty($channels)) { + // return $channels; + // } + + return new \Illuminate\Broadcasting\Channel($this->channelName()); + //return [new PrivateChannel()]; + } + + /** + * Get the data to broadcast. + * + * @return array + */ + public function broadcastWith() + { + return $this->data; + + /*return array_merge($this->data, [ + 'id' => $this->notification->id, + 'type' => get_class($this->notification), + ]);*/ + } + + /** + * Get the broadcast channel name for the event. + * + * @return string + */ + protected function channelName() + { + // if (method_exists($this->notifiable, 'receivesBroadcastNotificationsOn')) { + // return $this->notifiable->receivesBroadcastNotificationsOn($this->notification); + // } + + return 'conv'; + + // $class = str_replace('\\', '.', get_class($this->notifiable)); + + // return $class.'.'.$this->notifiable->getKey(); + } + + /** + * Helper funciton. + */ + public static function dispatchSelf($conversation_id, $user, $replying = false) + { + $notification_data = [ + 'conversation_id' => $conversation_id, + 'user_id' => $user->id, + 'user_photo_url' => $user->getPhotoUrl(false), + // These has to be encoded to avoid "Unable to JSON encode payload. Error code: 5" + 'user_initials' => htmlentities($user->getInitials()), + 'user_name' => htmlentities($user->getFullName()), + 'replying' => (int)$replying, + ]; + event(new \App\Events\RealtimeConvView($notification_data)); + } +} diff --git a/freescout-dist/app/Events/RealtimeConvViewFinish.php b/freescout-dist/app/Events/RealtimeConvViewFinish.php new file mode 100644 index 0000000..a791b46 --- /dev/null +++ b/freescout-dist/app/Events/RealtimeConvViewFinish.php @@ -0,0 +1,62 @@ +data = $data; + } + + /** + * Get the channels the event should broadcast on. + * + * @return Channel|array + */ + public function broadcastOn() + { + return new \Illuminate\Broadcasting\Channel($this->channelName()); + } + + /** + * Get the data to broadcast. + * + * @return array + */ + public function broadcastWith() + { + return $this->data; + } + + /** + * Get the broadcast channel name for the event. + * + * @return string + */ + protected function channelName() + { + return 'conv'; + } +} diff --git a/freescout-dist/app/Events/RealtimeMailboxNewThread.php b/freescout-dist/app/Events/RealtimeMailboxNewThread.php new file mode 100644 index 0000000..72cd2b9 --- /dev/null +++ b/freescout-dist/app/Events/RealtimeMailboxNewThread.php @@ -0,0 +1,110 @@ +data = $data; + } + + /** + * Get the channels the event should broadcast on. + * + * @return Channel|array + */ + public function broadcastOn() + { + return new \Illuminate\Broadcasting\Channel($this->channelName()); + } + + /** + * Get the data to broadcast. + * + * @return array + */ + public function broadcastWith() + { + return $this->data; + } + + /** + * Get the broadcast channel name for the event. + * + * @return string + */ + protected function channelName() + { + if (!empty($this->data['mailbox_id'])) { + return 'mailbox.'.$this->data['mailbox_id']; + } else { + return 'mailbox.0'; + } + } + + /** + * Helper funciton. + */ + public static function dispatchSelf($mailbox_id) + { + $notification_data = [ + 'mailbox_id' => $mailbox_id + ]; + event(new \App\Events\RealtimeMailboxNewThread($notification_data)); + } + + public static function processPayload($payload) + { + $user = auth()->user(); + $mailbox = Mailbox::rememberForever()->find($payload->mailbox_id); + + // Check if user can listen to this event. + if (!$user || !$mailbox || !$user->can('viewCached', $mailbox)) { + return []; + } + + $folder = null; + $foler_id = Conversation::getFolderParam(); + if ($foler_id) { + $folder = Folder::find($foler_id); + } + // Just in case. + if (!$folder) { + $folder = new Folder(); + } + $template_data = [ + 'folders' => $mailbox->getAssesibleFolders(), + 'folder' => $folder, + 'mailbox' => $mailbox, + ]; + + $payload->folders_html = \View::make('mailboxes/partials/folders')->with($template_data)->render(); + + return $payload; + } +} diff --git a/freescout-dist/app/Events/UserAddedNote.php b/freescout-dist/app/Events/UserAddedNote.php new file mode 100644 index 0000000..888f5d7 --- /dev/null +++ b/freescout-dist/app/Events/UserAddedNote.php @@ -0,0 +1,23 @@ +conversation = $conversation; + $this->thread = $thread; + } +} diff --git a/freescout-dist/app/Events/UserCreatedConversation.php b/freescout-dist/app/Events/UserCreatedConversation.php new file mode 100644 index 0000000..6aed202 --- /dev/null +++ b/freescout-dist/app/Events/UserCreatedConversation.php @@ -0,0 +1,23 @@ +conversation = $conversation; + $this->last_thread = $last_thread; + } +} diff --git a/freescout-dist/app/Events/UserCreatedConversationDraft.php b/freescout-dist/app/Events/UserCreatedConversationDraft.php new file mode 100644 index 0000000..720a689 --- /dev/null +++ b/freescout-dist/app/Events/UserCreatedConversationDraft.php @@ -0,0 +1,23 @@ +conversation = $conversation; + $this->thread = $thread; + } +} diff --git a/freescout-dist/app/Events/UserCreatedThreadDraft.php b/freescout-dist/app/Events/UserCreatedThreadDraft.php new file mode 100644 index 0000000..6a32655 --- /dev/null +++ b/freescout-dist/app/Events/UserCreatedThreadDraft.php @@ -0,0 +1,23 @@ +conversation = $conversation; + $this->thread = $thread; + } +} diff --git a/freescout-dist/app/Events/UserDeleted.php b/freescout-dist/app/Events/UserDeleted.php new file mode 100644 index 0000000..d060191 --- /dev/null +++ b/freescout-dist/app/Events/UserDeleted.php @@ -0,0 +1,24 @@ +by_user = $by_user; + $this->deleted_user = $deleted_user; + + \Eventy::action('user.deleted', $deleted_user, $by_user); + } +} diff --git a/freescout-dist/app/Events/UserReplied.php b/freescout-dist/app/Events/UserReplied.php new file mode 100644 index 0000000..447fabe --- /dev/null +++ b/freescout-dist/app/Events/UserReplied.php @@ -0,0 +1,23 @@ +conversation = $conversation; + $this->thread = $thread; + } +} diff --git a/freescout-dist/app/Exceptions/Handler.php b/freescout-dist/app/Exceptions/Handler.php new file mode 100644 index 0000000..7d3f5c7 --- /dev/null +++ b/freescout-dist/app/Exceptions/Handler.php @@ -0,0 +1,55 @@ +payload_decoded !== null) { + return $this->payload_decoded; + } + + $this->payload_decoded = json_decode($this->payload, true); + + return $this->payload_decoded; + } + + public function getCommand() + { + return \App\Job::getPayloadCommand($this->getPayloadDecoded()); + } + + public static function retry($job_id) + { + \Artisan::call('queue:retry', ['id' => $job_id]); + } +} diff --git a/freescout-dist/app/Folder.php b/freescout-dist/app/Folder.php new file mode 100644 index 0000000..0095293 --- /dev/null +++ b/freescout-dist/app/Folder.php @@ -0,0 +1,456 @@ + 'Unassigned', + self::TYPE_MINE => 'Mine', + self::TYPE_STARRED => 'Starred', + self::TYPE_DRAFTS => 'Drafts', + self::TYPE_ASSIGNED => 'Assigned', + self::TYPE_CLOSED => 'Closed', + self::TYPE_SPAM => 'Spam', + self::TYPE_DELETED => 'Deleted', + ]; + + /** + * https://glyphicons.bootstrapcheatsheets.com/. + */ + public static $type_icons = [ + self::TYPE_UNASSIGNED => 'folder-open', + self::TYPE_MINE => 'hand-right', + self::TYPE_DRAFTS => 'duplicate', + self::TYPE_ASSIGNED => 'user', + self::TYPE_CLOSED => 'lock', // lock + self::TYPE_SPAM => 'ban-circle', + self::TYPE_DELETED => 'trash', + self::TYPE_STARRED => 'star', + ]; + + // Public non-user specific mailbox types + public static $public_types = [ + self::TYPE_UNASSIGNED, + self::TYPE_DRAFTS, + self::TYPE_ASSIGNED, + self::TYPE_CLOSED, + self::TYPE_SPAM, + self::TYPE_DELETED, + ]; + + // Folder types which belong to specific user. + // These folders has user_id specified. + public static $personal_types = [ + self::TYPE_MINE, + self::TYPE_STARRED, + ]; + + // Folder types to which conversations are added via conversation_folder table. + public static $indirect_types = [ + self::TYPE_DRAFTS, + self::TYPE_STARRED, + ]; + + // Counter mode. + const COUNTER_ACTIVE = 1; + const COUNTER_TOTAL = 2; + + public $timestamps = false; + + protected $casts = [ + 'meta' => 'array', + ]; + + /** + * Get the mailbox to which folder belongs. + */ + public function mailbox() + { + return $this->belongsTo('App\Mailbox'); + } + + /** + * Get the user to which folder belongs. + */ + public function user() + { + return $this->belongsTo('App\User'); + } + + /** + * Get starred conversations. + */ + public function conversations() + { + return $this->hasMany('App\Conversation'); + } + + public function getTypeName() + { + // To make name translatable. + switch ($this->type) { + case self::TYPE_UNASSIGNED: + return __('Unassigned'); + case self::TYPE_MINE: + return __('Mine'); + case self::TYPE_DRAFTS: + return __('Drafts'); + case self::TYPE_ASSIGNED: + return __('Assigned'); + case self::TYPE_CLOSED: + return __('Closed'); + case self::TYPE_SPAM: + return __('Spam'); + case self::TYPE_DELETED: + return __('Deleted'); + case self::TYPE_STARRED: + return __('Starred'); + default: + return __(\Eventy::filter('folder.type_name', self::$types[$this->type] ?? '', $this)); + } + } + + public function getTypeIcon() + { + return \Eventy::filter('folder.type_icon', self::$type_icons[$this->type] ?? '', $this); + } + + /** + * Get order by array. + * + * @return array + */ + public function getOrderByArray() + { + $order_by = []; + + switch ($this->type) { + case self::TYPE_UNASSIGNED: + case self::TYPE_MINE: + case self::TYPE_ASSIGNED: + $order_by[] = ['status' => 'asc']; + $order_by[] = ['last_reply_at' => 'desc']; + break; + + case self::TYPE_STARRED: + $order_by[] = ['status' => 'asc']; + $order_by[] = ['last_reply_at' => 'desc']; + break; + + case self::TYPE_DRAFTS: + $order_by = [['updated_at' => 'desc']]; + break; + + case self::TYPE_CLOSED: + $order_by = [['closed_at' => 'desc']]; + break; + + case self::TYPE_SPAM: + $order_by = [['last_reply_at' => 'desc']]; + break; + + case self::TYPE_DELETED: + $order_by = [['user_updated_at' => 'desc']]; + break; + + default: + $order_by = \Eventy::filter('folder.conversations_order_by', $order_by, $this->type); + break; + } + + // Process columns sorting. + $sorting = Conversation::getConvTableSorting(); + if ($sorting['sort_by'] == 'date') { + if ($sorting['order'] != 'desc') { + foreach ($order_by as $block_i => $block) { + foreach ($block as $field => $order) { + if ($field == 'status') { + unset($order_by[$block_i][$field]); + } else { + if ($order == 'desc') { + $order_by[$block_i][$field] = 'asc'; + } + } + } + } + } + } else { + $order_by = [[$sorting['sort_by'] => $sorting['order']]]; + } + + return $order_by; + } + + /** + * Add order by to the query. + */ + public function queryAddOrderBy($query) + { + $order_bys = $this->getOrderByArray(); + foreach ($order_bys as $order_by) { + foreach ($order_by as $field => $sort_order) { + $query->orderBy($field, $sort_order); + } + } + + return $query; + } + + /** + * Is this folder accumulates conversations via conversation_folder table. + */ + public function isIndirect() + { + return in_array($this->type, self::$indirect_types); + } + + public function updateCounters() + { + if (config('app.update_folder_counters_in_background')) { + \App\Jobs\UpdateFolderCounters::dispatch($this); + } else { + $this->updateCountersNow(); + } + } + + public function updateCountersNow() + { + if (\Eventy::filter('folder.update_counters', false, $this)) { + return; + } + if ($this->type == self::TYPE_MINE && $this->user_id) { + $this->active_count = Conversation::where('user_id', $this->user_id) + ->where('mailbox_id', $this->mailbox_id) + ->where('state', Conversation::STATE_PUBLISHED) + ->where('status', Conversation::STATUS_ACTIVE) + ->count(); + $this->total_count = Conversation::where('user_id', $this->user_id) + ->where('mailbox_id', $this->mailbox_id) + ->where('state', Conversation::STATE_PUBLISHED) + ->count(); + } elseif ($this->type == self::TYPE_STARRED) { + $this->active_count = count(Conversation::getUserStarredConversationIds($this->mailbox_id, $this->user_id)); + $this->total_count = $this->active_count; + } elseif ($this->type == self::TYPE_DELETED) { + $this->active_count = $this->conversations()->where('state', Conversation::STATE_DELETED) + ->count(); + $this->total_count = $this->active_count; + } elseif ($this->isIndirect()) { + // Conversation are connected to folder via conversation_folder table. + // Drafts. + $this->active_count = ConversationFolder::where('conversation_folder.folder_id', $this->id) + ->join('conversations', 'conversations.id', '=', 'conversation_folder.conversation_id') + //->where('state', Conversation::STATE_PUBLISHED) + ->count(); + $this->total_count = $this->active_count; + } else { + $this->active_count = $this->conversations() + ->where('state', Conversation::STATE_PUBLISHED) + ->where('status', Conversation::STATUS_ACTIVE) + ->count(); + $this->total_count = $this->conversations() + ->where('state', Conversation::STATE_PUBLISHED) + ->count(); + } + $this->save(); + } + + /** + * Get count to display in folders list. + * + * @param array $folders [description] + * + * @return [type] [description] + */ + public function getCount($folders = []) + { + $counter = \Eventy::filter('folder.counter', self::COUNTER_ACTIVE, $this, $folders); + + $count = \Eventy::filter('folder.count', false, $this, $counter, $folders); + if ($count !== false) { + return $count; + } + + if ($counter == self::COUNTER_TOTAL || $this->type == self::TYPE_STARRED || $this->type == self::TYPE_DRAFTS) { + return $this->total_count; + } else { + return $this->getActiveCount($folders); + } + } + + /** + * Get calculated number of active conversations. + */ + public function getActiveCount($folders = []) + { + $active_count = $this->active_count; + if ($this->type == self::TYPE_ASSIGNED) { + $mine_folder = \Eventy::filter('folder.active_count_mine_folder', null, $this, $folders); + + if (!$mine_folder) { + if ($folders) { + $mine_folder = $folders->firstWhere('type', self::TYPE_MINE); + } elseif ($this->mailbox_id) { + $mine_folder = $this->mailbox->folders()->where('type', self::TYPE_MINE)->first(); + } + } + + if ($mine_folder) { + $active_count = $active_count - $mine_folder->active_count; + if ($active_count < 0) { + $active_count = 0; + } + } + } + + return $active_count; + } + + /** + * Query for waiting since. + */ + public function getWaitingSinceQuery() + { + $query = null; + + if ($this->type == self::TYPE_MINE) { + // Assigned to user. + $query = Conversation::where('user_id', $this->user_id) + ->where('mailbox_id', $this->mailbox_id); + } elseif ($this->isIndirect()) { + // Via intermediate table. + $query = Conversation::join('conversation_folder', 'conversations.id', '=', 'conversation_folder.conversation_id') + ->where('conversation_folder.folder_id', $this->id); + } else { + // All other conversations. + $query = $this->conversations(); + } + + return \Eventy::filter('folder.waiting_since_query', $query, $this); + } + + /** + * Works for main folder only for now. + * + * @return [type] [description] + */ + public function getWaitingSince() + { + // Get oldest active conversation. + $conversation = $this->getWaitingSinceQuery() + ->where('state', Conversation::STATE_PUBLISHED) + ->where('status', Conversation::STATUS_ACTIVE) + ->orderBy($this->getWaitingSinceField(), 'asc') + ->first(); + if ($conversation) { + return $conversation->getWaitingSince($this); + } else { + return ''; + } + } + + /** + * Get conversation field used to detect waiting since time. + * + * @return [type] [description] + */ + public function getWaitingSinceField() + { + if ($this->type == \App\Folder::TYPE_CLOSED) { + return 'closed_at'; + } elseif ($this->type == \App\Folder::TYPE_DRAFTS) { + return 'updated_at'; + } elseif ($this->type == \App\Folder::TYPE_DELETED) { + return 'user_updated_at'; + } else { + return'last_reply_at'; + } + } + + public function url($mailbox_id) + { + return \Eventy::filter('folder.url', route('mailboxes.view.folder', ['id'=>$mailbox_id, 'folder_id'=>$this->id]), $mailbox_id, $this); + } + + public static function create($data, $unique_per_user = true, $save = true) + { + if (!isset($data['mailbox_id']) || !isset($data['type'])) { + return null; + } + $folder = new Folder (); + $folder->mailbox_id = $data['mailbox_id']; + $folder->type = $data['type']; + + if (!empty($data['user_id'])) { + if ($unique_per_user) { + $user_folder = Folder::where('mailbox_id', $data['mailbox_id']) + ->where('user_id', $data['user_id']) + ->where('type', $data['type']) + ->first(); + // User folder already exists. + if ($user_folder) { + return $user_folder; + } + } + $folder->user_id = $data['user_id']; + } + + if ($save) { + $folder->save(); + } + + return $folder; + } + + /** + * Get meta value. + */ + public function getMeta($key, $default = null) + { + $metas = $this->meta; + if (isset($metas[$key])) { + return $metas[$key]; + } else { + return $default; + } + } + + /** + * Set meta value. + */ + public function setMeta($key, $value) + { + $metas = $this->meta; + $metas[$key] = $value; + $this->meta = $metas; + } + + /** + * Unset thread meta value. + */ + public function unsetMeta($key) + { + $metas = $this->meta; + if (isset($metas[$key])) { + unset($metas[$key]); + $this->meta = $metas; + } + } +} diff --git a/freescout-dist/app/Follower.php b/freescout-dist/app/Follower.php new file mode 100644 index 0000000..0df7b9e --- /dev/null +++ b/freescout-dist/app/Follower.php @@ -0,0 +1,10 @@ +middleware('guest'); + } +} diff --git a/freescout-dist/app/Http/Controllers/Auth/LoginController.php b/freescout-dist/app/Http/Controllers/Auth/LoginController.php new file mode 100644 index 0000000..1a63bd4 --- /dev/null +++ b/freescout-dist/app/Http/Controllers/Auth/LoginController.php @@ -0,0 +1,79 @@ +middleware('guest')->except('logout'); + } + + /** + * Handle a login request to the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function login(Request $request) + { + $this->validateLogin($request); + + // If the class is using the ThrottlesLogins trait, we can automatically throttle + // the login attempts for this application. We'll key this by the username and + // the IP address of the client making these requests into this application. + if ($this->hasTooManyLoginAttempts($request)) { + $this->fireLockoutEvent($request); + + return $this->sendLockoutResponse($request); + } + + $custom_errors = \Eventy::filter('login.custom_check', [], $request); + if ($custom_errors) { + throw ValidationException::withMessages($custom_errors); + } + + if ($this->attemptLogin($request)) { + return $this->sendLoginResponse($request); + } + + // If the login attempt was unsuccessful we will increment the number of attempts + // to login and redirect the user back to the login form. Of course, when this + // user surpasses their maximum number of attempts they will get locked out. + $this->incrementLoginAttempts($request); + + return $this->sendFailedLoginResponse($request); + } +} diff --git a/freescout-dist/app/Http/Controllers/Auth/RegisterController.php b/freescout-dist/app/Http/Controllers/Auth/RegisterController.php new file mode 100644 index 0000000..395a007 --- /dev/null +++ b/freescout-dist/app/Http/Controllers/Auth/RegisterController.php @@ -0,0 +1,87 @@ +middleware('guest'); + } + + /** + * Get a validator for an incoming registration request. + * + * @param array $data + * + * @return \Illuminate\Contracts\Validation\Validator + */ + protected function validator(array $data) + { + return Validator::make($data, [ + 'first_name' => 'required|string|max:255', + 'last_name' => 'required|string|max:255', + 'email' => 'required|string|email|max:191|unique:users', + 'password' => 'required|string|min:8|confirmed', + ]); + } + + /** + * Create a new user instance after a valid registration. + * + * @param array $data + * + * @return \App\User + */ + protected function create(array $data) + { + return User::create([ + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], + 'email' => $data['email'], + 'password' => bcrypt($data['password']), + ]); + } + + /** + * Registration is disabled. + */ + public function showRegistrationForm() + { + return redirect('login'); + } + + public function register() + { + } +} diff --git a/freescout-dist/app/Http/Controllers/Auth/ResetPasswordController.php b/freescout-dist/app/Http/Controllers/Auth/ResetPasswordController.php new file mode 100644 index 0000000..c730eec --- /dev/null +++ b/freescout-dist/app/Http/Controllers/Auth/ResetPasswordController.php @@ -0,0 +1,62 @@ +middleware('guest'); + } + + /** + * Reset the given user's password. + * + * @param \Illuminate\Contracts\Auth\CanResetPassword $user + * @param string $password + * @return void + */ + protected function resetPassword($user, $password) + { + $user->password = Hash::make($password); + + $user->setRememberToken(Str::random(60)); + + $user->save(); + + event(new PasswordReset($user)); + + //$this->guard()->login($user); + } +} diff --git a/freescout-dist/app/Http/Controllers/Controller.php b/freescout-dist/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..a0a2a8a --- /dev/null +++ b/freescout-dist/app/Http/Controllers/Controller.php @@ -0,0 +1,13 @@ +middleware('auth'); + } + + /** + * View conversation. + */ + public function view(Request $request, $id) + { + $conversation = Conversation::findOrFail($id); + $this->authorize('viewCached', $conversation); + + $mailbox = $conversation->mailbox; + $customer = $conversation->customer_cached; + $user = auth()->user(); + + // To let other parts of the app easily access. + \Helper::setGlobalEntity('conversation', $conversation); + \Helper::setGlobalEntity('mailbox', $mailbox); + + if ($user->isAdmin()) { + $mailbox->fetchUserSettings($user->id); + } + + // Mark notifications as read + if (!empty($request->mark_as_read)) { + $mark_read_result = $user->unreadNotifications()->where('id', $request->mark_as_read)->update(['read_at' => now()]); + $user->clearWebsiteNotificationsCache(); + } else { + $mark_read_result = $user->unreadNotifications()->where('data', 'like', '%"conversation_id":'.$conversation->id.'%')->update(['read_at' => now()]); + } + if ($mark_read_result) { + $user->clearWebsiteNotificationsCache(); + } + + // Detect folder and redirect if needed + $folder = null; + if (Conversation::getFolderParam()) { + $folder = $conversation->mailbox->folders()->where('folders.id', Conversation::getFolderParam())->first(); + + // Pass some params when redirecting. + $params = []; + if (!empty($request->show_draft)) { + $params['show_draft'] = $request->show_draft; + } + + if ($folder) { + // Check if conversation can be located in the passed folder_id + if (!$conversation->isInFolderAllowed($folder)) { + + // Without reflash green flash will not be displayed on assignee change + \Session::reflash(); + //$request->session()->reflash(); + return redirect()->away($conversation->url($conversation->folder_id, null, $params)); + } + // If conversation assigned to user, select Mine folder instead of Assigned + if ($folder->type == Folder::TYPE_ASSIGNED && $conversation->user_id == $user->id) { + $folder = $conversation->mailbox->folders() + ->where('type', Folder::TYPE_MINE) + ->where('user_id', $user->id) + ->first(); + + \Session::reflash(); + + return redirect()->away($conversation->url($folder->id, null, $params)); + } + } + } + + // Add folder if empty + if (!$folder) { + if ($conversation->user_id == $user->id) { + $folder = $conversation->mailbox->folders() + ->where('type', Folder::TYPE_MINE) + ->where('user_id', $user->id) + ->first(); + } else { + $folder = $conversation->folder; + } + + \Session::reflash(); + + return redirect()->away($conversation->url($folder->id)); + } + + //$after_send = $conversation->mailbox->getUserSettings($user->id)->after_send; + $after_send = $user->mailboxSettings($conversation->mailbox_id)->after_send; + + // Detect customers and emails to which user can reply + $to_customers = []; + // Add all customer emails + $customer_emails = []; + $distinct_emails = []; + + // Add emails of customers from whom there were replies in the conversation + $prev_customers_emails = []; + if ($conversation->customer_email) { + $prev_customers_emails = Thread::select('from', 'customer_id') + ->where('conversation_id', $id) + ->where('type', Thread::TYPE_CUSTOMER) + ->where('from', '<>', $conversation->customer_email) + ->groupBy(['from', 'customer_id']) + ->get(); + } + + foreach ($prev_customers_emails as $prev_customer) { + if (!in_array($prev_customer->from, $distinct_emails) && $prev_customer->customer && $prev_customer->from) { + $to_customers[] = [ + 'customer' => $prev_customer->customer, + 'email' => $prev_customer->from, + ]; + $distinct_emails[] = $prev_customer->from; + } + } + + // Add customer email(s) if there more than one or if there are other emails in threads. + if ($customer) { + $customer_emails = $customer->emails; + } + // This is tricky case - when customer_email is different from the + // currently selected customer. + // 1. Email has been received from a customer. + // 2. Customer has been changed. + // 3. Reply has been sent to the original customer email. + if ($conversation->customer_email + && count($customer_emails) + && !in_array($conversation->customer_email, $customer_emails->pluck('email')->toArray()) + ) { + $extra_customer_added = false; + foreach ($to_customers as $to_customer) { + if ($to_customer['email'] == $conversation->customer_email) { + $extra_customer_added = true; + break; + } + } + if (!$extra_customer_added) { + // Get customer by email. + $extra_customer = Customer::getByEmail($conversation->customer_email); + if ($extra_customer) { + $to_customers[] = [ + 'customer' => $extra_customer, + 'email' => $conversation->customer_email, + ]; + } + } + } + if (count($customer_emails) > 1 || count($to_customers)) { + foreach ($customer_emails as $customer_email) { + $to_customers[] = [ + 'customer' => $customer, + 'email' => $customer_email->email, + ]; + $distinct_emails[] = $customer_email->email; + } + } + + // Exclude mailbox emails from $to_customers. + $mailbox_emails = $mailbox->getEmails(); + foreach ($to_customers as $key => $to_customer) { + if (in_array($to_customer['email'], $mailbox_emails)) { + unset($to_customers[$key]); + } + } + + $threads = $conversation->threads()->orderBy('created_at', 'desc')->get(); + + // Get To for new conversation. + $new_conv_to = []; + if (empty($threads[0]) || empty($threads[0]->to)) { + // Before new conversation To field was stored in $conversation->customer_email. + $emails = Conversation::sanitizeEmails($conversation->customer_email); + // Get customers info for emails. + if (count($emails)) { + $new_conv_to = Customer::emailsToCustomers($emails); + } + } else { + $new_conv_to = Customer::emailsToCustomers($threads[0]->getToArray()); + } + + if (empty($customer) && count($new_conv_to) == 1) { + $customer = Customer::getByEmail(array_key_first($new_conv_to)); + } + + // Previous conversations + $prev_conversations = []; + if ($customer) { + $prev_conversations = $mailbox->conversations() + ->where('customer_id', $customer->id) + ->where('id', '<>', $conversation->id) + ->where('status', '!=', Conversation::STATUS_SPAM) + ->where('state', Conversation::STATE_PUBLISHED) + //->limit(self::PREV_CONVERSATIONS_LIMIT) + ->orderBy('created_at', 'desc') + ->paginate(self::PREV_CONVERSATIONS_LIMIT); + } + + $template = 'conversations/view'; + if ($conversation->state == Conversation::STATE_DRAFT) { + $template = 'conversations/create'; + } + + // CC. + $exclude_array = $conversation->getExcludeArray($mailbox); + $cc = $conversation->getCcArray($exclude_array); + + // If last reply came from customer who was mentioned in CC before, + // we need to add this customer as CC. + // https://github.com/freescout-helpdesk/freescout/issues/3613 + foreach ($threads as $thread) { + if ($thread->isUserMessage() && !$thread->isDraft()) { + break; + } + if ($thread->isCustomerMessage()) { + if ($thread->customer_id != $conversation->customer_id) { + $cc[] = $thread->from; + } + break; + } + } + + // Get data for creating a phone conversation. + $name = []; + $phone = ''; + $to_email = []; + if ($customer) { + if ($customer->getFullName()) { + $name = [$customer->id => $customer->getFullName()]; + } + $last_phone = array_last($customer->getPhones()); + if (!empty($last_phone)) { + $phone = $last_phone['value']; + } + + if ($conversation->customer_email) { + $customer_email = $conversation->customer_email; + } else { + $customer_email = $customer->getMainEmail(); + } + if ($customer_email) { + $to_email = [$customer_email]; + } + } + + // Notify other users that current user is viewing conversation. + // Eventually notification data will be saved in polycast_events table and processes + // in JS in users browsers. + + // $notification = new \App\Notifications\UserViewingConversationNotification( + // $conversation, $user, false + // ); + + // This broadcasts to specific users. + // \Notification::send($mailbox->usersHavingAccess(), $notification); + + // Notification is sent to all via public channel: conview + // If we send notification to each user, applications having thouthans of users + // will be overloaded. + // // https://laravel.com/docs/5.5/broadcasting#broadcasting-events + \App\Events\RealtimeConvView::dispatchSelf($conversation->id, $user, false); + + // Get viewers. + $viewers = []; + $conv_view = \Cache::get('conv_view'); + if ($conv_view && !empty($conv_view[$conversation->id])) { + $viewing_users = User::whereIn('id', array_keys($conv_view[$conversation->id]))->get(); + foreach ($viewing_users as $viewer) { + if (isset($conv_view[$conversation->id][$viewer->id]['r']) && $viewer->id != $user->id) { + $viewers[] = [ + 'user' => $viewer, + 'replying' => (int)$conv_view[$conversation->id][$viewer->id]['r'] + ]; + } + } + // Show replying first. + usort($viewers, function($a, $b) { + return $b['replying'] <=> $a['replying']; + }); + } + + $is_following = $conversation->isUserFollowing($user->id); + + \Eventy::action('conversation.view.start', $conversation, $request); + + // Mailbox aliases. + $from_aliases = $conversation->mailbox->getAliases(true, true); + $from_alias = ''; + + if (count($from_aliases) == 1) { + $from_aliases = []; + } + if ($conversation->isDraft() && !empty($threads[0])) { + $from_alias = $threads[0]->from ?? ''; + } + if (count($from_aliases) && !$from_alias) { + // Preset the last alias used. + $check_initial_thread = true; + foreach ($threads as $thread) { + if ($thread->isUserMessage() && !$thread->isDraft()) { + $check_initial_thread = false; + if ($thread->from) { + $from_alias = $thread->from; + } + break; + } + } + // Maybe the first email has been sent to some mailbox alias. + if (!$from_alias && $check_initial_thread) { + $initial_thread = $threads->last(); + if ($initial_thread && $initial_thread->isCustomerMessage()) { + $initial_recipients = $initial_thread->getToArray(); + $initial_recipients = array_merge($initial_recipients, $initial_thread->getCcArray()); + foreach ($initial_recipients as $initial_recipient) { + foreach ($from_aliases as $from_alias_email => $dummy) { + if ($initial_recipient == $from_alias_email) { + $from_alias = $from_alias_email; + break 2; + } + } + } + } + } + } + + return view($template, [ + 'conversation' => $conversation, + 'mailbox' => $conversation->mailbox, + 'customer' => $customer, + 'threads' => \Eventy::filter('conversation.view.threads', $threads), + 'folder' => $folder, + 'folders' => $conversation->mailbox->getAssesibleFolders(), + 'after_send' => $after_send, + 'to' => $new_conv_to, + 'to_customers' => $to_customers, + 'prev_conversations' => $prev_conversations, + 'cc' => $cc, + 'bcc' => [], //$conversation->getBccArray($exclude_array), + // Data for creating a phone conversation. + 'name' => $name, + 'phone' => $phone, + 'to_email' => $to_email, + 'viewers' => $viewers, + 'is_following' => $is_following, + 'from_aliases' => $from_aliases, + 'from_alias' => $from_alias, + ]); + } + + /** + * New conversation. + */ + public function create(Request $request, $mailbox_id) + { + $mailbox = Mailbox::findOrFail($mailbox_id); + $this->authorize('view', $mailbox); + + $subject = trim($request->get('subject') ?? ''); + + $conversation = new Conversation(); + $conversation->body = ''; + $conversation->mailbox = $mailbox; + + $folder = $mailbox->folders()->where('type', Folder::TYPE_DRAFTS)->first(); + + // todo: use $user->mailboxSettings() + $after_send = $mailbox->getUserSettings(auth()->user()->id)->after_send; + + // Create conversation from thread + $thread = null; + if (!empty($request->from_thread_id)) { + $orig_thread = Thread::find($request->from_thread_id); + if ($orig_thread) { + $subject = $orig_thread->conversation->subject; + $subject = preg_replace('/^Fwd:/i', 'Re: ', $subject); + + $thread = new \App\Thread(); + $thread->body = $orig_thread->body; + // If this is a forwarded message, try to fetch From + preg_match_all("/From:[^<\n]+<([^<\n]+)>/m", html_entity_decode(strip_tags($thread->body)), $m); + + if (!empty($m[1])) { + foreach ($m[1] as $value) { + if (\MailHelper::validateEmail($value)) { + $thread->to = json_encode([$value]); + break; + } + } + } + } + } + + $to = []; + + // Prefill some values. + $prefill_to = \App\Email::sanitizeEmail($request->get('to')); + if ($prefill_to) { + $to = [$prefill_to => $prefill_to]; + } + $conversation->subject = $subject; + + return view('conversations/create', [ + 'conversation' => $conversation, + 'thread' => $thread, + 'mailbox' => $mailbox, + 'folder' => $folder, + 'folders' => $mailbox->getAssesibleFolders(), + 'after_send' => $after_send, + 'to' => $to, + 'from_aliases' => $mailbox->getAliases(true, true), + ]); + } + + /** + * Clone conversation. + */ + public function cloneConversation(Request $request, $mailbox_id, $from_thread_id) + { + $mailbox = Mailbox::findOrFail($mailbox_id); + $this->authorize('view', $mailbox); + + if (!empty($from_thread_id)) { + $orig_thread = Thread::find($from_thread_id); + + if ($orig_thread) { + $orign_conv = $orig_thread->conversation; + $this->authorize('view', $orign_conv); + + + // $thread = $orig_thread->replicate(); + // $thread->id = ''; + // $thread->message_id .= ".clone".crc32(mktime()); + // $thread->status = Thread::STATUS_ACTIVE; + // $thread->conversation_id = $conversation->id; + // $thread->save(); + + + $now = date('Y-m-d H:i:s'); + + $conversation = new Conversation(); + $conversation->type = $orign_conv->type; + $conversation->subject = $orign_conv->subject; + $conversation->mailbox_id = $orign_conv->mailbox_id; + $conversation->preview = ''; + // Preset source_via here to avoid error in PostgreSQL. + $conversation->source_via = $orign_conv->source_via; + $conversation->source_type = $orign_conv->source_type; + $conversation->customer_id = $orign_conv->customer_id; + $conversation->customer_email = $orign_conv->customer->getMainEmail(); + $conversation->status = Conversation::STATUS_ACTIVE; + $conversation->state = Conversation::STATE_PUBLISHED; + $conversation->cc = $orig_thread->cc; + $conversation->bcc = $orig_thread->bcc; + // Set assignee + $conversation->user_id = $orign_conv->user_id; + $conversation->updateFolder(); + $conversation->save(); + + + $thread = Thread::createExtended([ + 'conversation_id' => $orig_thread->conversation_id, + 'user_id' => $orig_thread->user_id, + 'type' => $orig_thread->type, + 'status' => $conversation->status, + 'state' => $conversation->state, + 'body' => $orig_thread->body, + 'headers' => $orig_thread->headers, + 'from' => $orig_thread->from, + 'to' => $orig_thread->to, + 'cc' => $orig_thread->cc, + 'bcc' => $orig_thread->bcc, + //'attachments' => $attachments, + 'has_attachments' => $orig_thread->has_attachments, + 'message_id' => "clone".crc32(microtime()).'-'.$orig_thread->message_id, + 'source_via' => $orig_thread->source_via, + 'source_type' => $orig_thread->source_type, + 'customer_id' => $orig_thread->customer_id, + 'created_by_customer_id' => $orig_thread->created_by_customer_id, + ], + $conversation + ); + + // Clone attachments. + $attachments = Attachment::where('thread_id', $orig_thread->id)->get(); + foreach ($attachments as $attachment) { + $attachment->duplicate($thread->id); + } + + return redirect()->away($conversation->url()); + } else { + return redirect()->away($mailbox->url()); + } + } else { + return redirect()->away($mailbox->url()); + } + } + + /** + * Conversation draft. + */ + // public function draft($id) + // { + // $conversation = Conversation::findOrFail($id); + + // $this->authorize('view', $conversation); + + // return view('conversations/create', [ + // 'conversation' => $conversation, + // 'mailbox' => $conversation->mailbox, + // 'folder' => $conversation->folder, + // 'folders' => $conversation->mailbox->getAssesibleFolders(), + // ]); + // } + + /** + * Conversations ajax controller. + */ + public function ajax(Request $request) + { + $response = [ + 'status' => 'error', + 'msg' => '', // this is error message + ]; + + $user = auth()->user(); + + switch ($request->action) { + + // Change conversation user + case 'conversation_change_user': + $conversation = Conversation::find($request->conversation_id); + + $new_user_id = (int) $request->user_id; + + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } + if (!$response['msg'] && $conversation->user_id == $new_user_id) { + $response['msg'] = __('Assignee already set'); + } + if (!$response['msg'] && !$user->can('update', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + if (!$response['msg'] && (int) $new_user_id != -1 && !$conversation->mailbox->userHasAccess($new_user_id)) { + $response['msg'] = __('Not enough permissions'); + } + if (!$response['msg']) { + // Determine redirect + // Must be done before updating current conversation's status or assignee. + $redirect_same_page = false; + if ($new_user_id == $user->id || $request->x_embed == 1) { + // If user assigned conversation to himself, stay on the current page + $response['redirect_url'] = $conversation->url(); + $redirect_same_page = true; + } else { + $response['redirect_url'] = $this->getRedirectUrl($request, $conversation, $user); + } + + $conversation->changeUser($new_user_id, $user); + + $response['status'] = 'success'; + + // Flash + $flash_message = __('Assignee updated'); + if (!$redirect_same_page || $response['redirect_url'] != $conversation->url()) { + $flash_message .= '  '.__('View').''; + } + \Session::flash('flash_success_floating', $flash_message); + + $response['msg'] = __('Assignee updated'); + } + break; + + // Change conversation status + case 'conversation_change_status': + $conversation = Conversation::find($request->conversation_id); + + if ($request->status == 'not_spam') { + // Find previous status in threads + $new_status = $conversation + ->threads() + ->orderBy('created_at', 'desc') + ->where('status', '!=', Thread::STATUS_SPAM) + ->where('type', Thread::TYPE_LINEITEM) + ->where('action_type', Thread::ACTION_TYPE_STATUS_CHANGED) + ->value('status'); + if (!$new_status) { + $new_status = Thread::STATUS_ACTIVE; + } + } else { + $new_status = (int) $request->status; + } + + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } + if (!$response['msg'] && $conversation->status == $new_status) { + $response['msg'] = __('Status already set'); + } + if (!$response['msg'] && !$user->can('update', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + if (!$response['msg'] && !in_array((int) $new_status, array_keys(Conversation::$statuses))) { + $response['msg'] = __('Incorrect status'); + } + if (!$response['msg']) { + // Determine redirect + // Must be done before updating current conversation's status or assignee. + $redirect_same_page = false; + if ($request->status == 'not_spam' || $request->x_embed == 1) { + // Stay on the current page + $response['redirect_url'] = $conversation->url(); + $redirect_same_page = true; + } else { + $response['redirect_url'] = $this->getRedirectUrl($request, $conversation, $user); + } + + $conversation->changeStatus($new_status, $user); + + $response['status'] = 'success'; + // Flash + $flash_message = __('Status updated'); + if (!$redirect_same_page || $response['redirect_url'] != $conversation->url()) { + $flash_message .= '  '.__('View').''; + } + \Session::flash('flash_success_floating', $flash_message); + + $response['msg'] = __('Status updated'); + } + break; + + // Send reply, new conversation, add note or forward + case 'send_reply': + + $mailbox = Mailbox::findOrFail($request->mailbox_id); + + if (!$response['msg'] && !$user->can('view', $mailbox)) { + $response['msg'] = __('Not enough permissions'); + } + + $conversation = null; + if (!$response['msg'] && !empty($request->conversation_id)) { + $conversation = Conversation::find($request->conversation_id); + if ($conversation && !$user->can('view', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + } + $new = false; + if (empty($request->conversation_id)) { + $new = true; + } + + $is_note = false; + if (!empty($request->is_note)) { + $is_note = true; + } + + // Conversation type. + $type = Conversation::TYPE_EMAIL; + if (!empty($request->type)) { + $type = (int)$request->type; + } elseif ($conversation) { + $type = $conversation->type; + } + + $is_phone = false; + if ($type == Conversation::TYPE_PHONE) { + $is_phone = true; + } + + $is_create = false; + if (!empty($request->is_create)) { + //if ($new || ($from_draft && $conversation->threads_count == 1)) { + $is_create = $request->is_create; + } + + $is_forward = false; + if (!empty($request->subtype) && (int)$request->subtype == Thread::SUBTYPE_FORWARD) { + $is_forward = true; + } + + $is_multiple = false; + if (!empty($request->multiple_conversations)) { + $is_multiple = true; + } + + // If reply is being created from draft, there is already thread created + $thread = null; + $from_draft = false; + if ((!$is_note || $is_phone) && !$response['msg'] && !empty($request->thread_id)) { + $thread = Thread::find($request->thread_id); + if ($thread && (!$conversation || $thread->conversation_id != $conversation->id)) { + $response['msg'] = __('Incorrect thread'); + } else { + $from_draft = true; + } + } + + if (!$response['msg']) { + if ($thread && $from_draft && $thread->state == Thread::STATE_PUBLISHED) { + $response['msg'] = __('Message has been already sent. Please discard this draft.'); + } + } + + // Validate form + if (!$response['msg']) { + if ($new) { + if ($type == Conversation::TYPE_EMAIL) { + $validator = Validator::make($request->all(), [ + 'to' => 'required|array', + 'subject' => 'required|string|max:998', + 'body' => 'required|string', + 'cc' => 'nullable|array', + 'bcc' => 'nullable|array', + ]); + } else { + // Phone conversation. + $validator = Validator::make($request->all(), [ + 'name' => 'required|string', + 'subject' => 'required|string|max:998', + 'body' => 'required|string', + 'phone' => 'nullable|string', + 'to_email' => 'nullable|string', + ]); + } + } else { + $validator = Validator::make($request->all(), [ + 'body' => 'required|string', + 'cc' => 'nullable|array', + 'bcc' => 'nullable|array', + ]); + } + + if ($validator->fails()) { + foreach ($validator->errors()->getMessages()as $errors) { + foreach ($errors as $field => $message) { + $response['msg'] .= $message.' '; + } + } + } + } + + $body = $request->body; + + // Replace base64 images with attachment URLs in case text + // was copy and pasted into the editor. + // https://github.com/freescout-helpdesk/freescout/issues/3057 + $body = Thread::replaceBase64ImagesWithAttachments($body); + + // List of emails. + $to_array = []; + if ($is_forward) { + $to_array = Conversation::sanitizeEmails($request->to_email); + } else { + $to_array = Conversation::sanitizeEmails($request->to); + } + // Check To + if (!$response['msg'] && $new && !$is_phone) { + if (!$to_array) { + $response['msg'] .= __('Incorrect recipients'); + } + } + + // Check max. message size. + if (!$response['msg']) { + $max_message_size = (int)config('app.max_message_size'); + if ($max_message_size) { + // Todo: take into account conversation history. + $message_size = mb_strlen($body, '8bit'); + + // Calculate attachments size. + $attachments_ids = array_merge($request->attachments ?? [], $request->embeds ?? []); + + if (count($attachments_ids)) { + $attachments_to_check = Attachment::select('size')->whereIn('id', $attachments_ids)->get(); + foreach ($attachments_to_check as $attachment) { + $message_size += (int)$attachment->size; + } + } + + if ($message_size > $max_message_size*1024*1024) { + $response['msg'] = __('Message is too large — :info. Please shorten your message or remove some attachments.', ['info' => __('Max. Message Size').': '.$max_message_size.' MB']); + } + } + } + + if (!$response['msg']) { + + // Get attachments info + // Delete removed attachments. + $attachments_info = $this->processReplyAttachments($request); + + // Determine redirect. + // Must be done before updating current conversation's status or assignee. + // Redirect URL for new no saved yet conversation is determined below. + if (!$new) { + $response['redirect_url'] = $this->getRedirectUrl($request, $conversation, $user); + } + + // Conversation + $now = date('Y-m-d H:i:s'); + $status_changed = false; + $user_changed = false; + if ($new) { + // New conversation + $conversation = new Conversation(); + $conversation->type = $type; + $conversation->subject = $request->subject; + $conversation->setPreview($body); + $conversation->mailbox_id = $request->mailbox_id; + $conversation->created_by_user_id = auth()->user()->id; + $conversation->source_via = Conversation::PERSON_USER; + $conversation->source_type = Conversation::SOURCE_TYPE_WEB; + } else { + // Reply or note + if ((int) $request->status != (int) $conversation->status) { + $status_changed = true; + } + if (!empty($request->subject)) { + $conversation->subject = $request->subject; + } + // When switching from regular message to phone and message sent + // without saving a draft type need to be saved here. + // Or vise versa. + if (($conversation->type == Conversation::TYPE_EMAIL && $type == Conversation::TYPE_PHONE) + || ($conversation->type == Conversation::TYPE_PHONE && $type == Conversation::TYPE_EMAIL) + ) { + $conversation->type = $type; + } + // Allow to convert phone conversations into email conversations. + if ($conversation->isPhone() && !$is_note && $conversation->customer + && $customer_email = $conversation->customer->getMainEmail() + ) { + $conversation->type = Conversation::TYPE_EMAIL; + $conversation->customer_email = $customer_email; + $is_phone = false; + } + } + + if ($attachments_info['has_attachments']) { + $conversation->has_attachments = true; + } + + // Customer can be empty in existing conversation if this is a draft. + $customer_email = ''; + $customer = null; + + if ($is_phone && $is_create) { + // Phone. + $phone_customer_data = $this->processPhoneCustomer($request); + + $customer_email = $phone_customer_data['customer_email']; + $customer = $phone_customer_data['customer']; + if (!$conversation->customer_id) { + $conversation->customer_id = $customer->id; + } + } else { + // Email or reply to a phone conversation. + if (!empty($to_array)) { + $customer_email = $to_array[0]; + } elseif (!$conversation->customer_email + && ($conversation->isEmail() || $conversation->isPhone()) + && $conversation->customer_id + && $conversation->customer + ) { + // When replying to a phone conversation, we need to + // set 'customer_email' for the conversation. + $customer_email = $conversation->customer->getMainEmail(); + } + if (!$conversation->customer_id) { + $customer = Customer::create($customer_email); + $conversation->customer_id = $customer->id; + } else { + $customer = $conversation->customer; + } + } + if ($customer_email && !$is_note && !$is_forward) { + $conversation->customer_email = $customer_email; + } + + $prev_status = $conversation->status; + + $conversation->status = $request->status; + + if (($prev_status != $conversation->status || $is_create) + && $conversation->status == Conversation::STATUS_CLOSED + ) { + $conversation->closed_by_user_id = $user->id; + $conversation->closed_at = date('Y-m-d H:i:s'); + } + + // We need to set state, as it may have been a draft. + $prev_state = $conversation->state; + $conversation->state = Conversation::STATE_PUBLISHED; + + // Set assignee + $prev_user_id = $conversation->user_id; + if ((int) $request->user_id != -1) { + // Check if user has access to the current mailbox + if ((int) $conversation->user_id != (int) $request->user_id && $mailbox->userHasAccess($request->user_id)) { + $conversation->user_id = $request->user_id; + $user_changed = true; + } + } else { + $conversation->user_id = null; + } + + // To is a single email string. + $to = ''; + // List of emails. + $to_list = []; + if ($is_forward) { + if (empty($request->to_email[0])) { + $response['msg'] = __('Please specify a recipient.'); + break; + } + $to = $request->to_email[0]; + } else { + if (!empty($request->to)) { + // When creating a new conversation, to is a list of emails. + if (is_array($request->to)) { + $to = $request->to[0]; + } else { + $to = $request->to; + } + } else { + $to = $conversation->customer_email; + } + } + + if (!$is_note && !$is_forward) { + // Save extra recipients to CC + if ($is_create && !$is_multiple && count($to_array) > 1) { + $conversation->setCc(array_merge(Conversation::sanitizeEmails($request->cc), $to_array)); + } else { + if (!$is_multiple) { + $conversation->setCc(array_merge(Conversation::sanitizeEmails($request->cc), [$to])); + } else { + $conversation->setCc(Conversation::sanitizeEmails($request->cc)); + } + } + $conversation->setBcc($request->bcc); + $conversation->last_reply_at = $now; + $conversation->last_reply_from = Conversation::PERSON_USER; + $conversation->user_updated_at = $now; + } + if ($conversation->isPhone() && $is_note) { + $conversation->last_reply_at = $now; + $conversation->last_reply_from = Conversation::PERSON_USER; + } + $conversation->updateFolder(); + if ($from_draft) { + // Increment number of replies in conversation + $conversation->threads_count++; + // We need to set preview here as when conversation is created from draft, + // ThreadObserver::created() method is not called. + $conversation->setPreview($body); + } + $conversation->save(); + + // Redirect URL for new not saved yet conversation must be determined here. + if ($new) { + $response['redirect_url'] = $this->getRedirectUrl($request, $conversation, $user); + } + + // Fire events + \Eventy::action('conversation.send_reply_save', $conversation, $request); + + if (!$new) { + if ($status_changed) { + event(new ConversationStatusChanged($conversation)); + \Eventy::action('conversation.status_changed', $conversation, $user, $changed_on_reply = true, $prev_status); + } + if ($user_changed) { + event(new ConversationUserChanged($conversation, $user)); + \Eventy::action('conversation.user_changed', $conversation, $user, $prev_user_id); + } + } + + if ($conversation->state != $prev_state) { + \Eventy::action('conversation.state_changed', $conversation, $user, $prev_state); + } + + // Create thread + if (!$thread) { + $thread = new Thread(); + $thread->conversation_id = $conversation->id; + if ($is_note || $is_forward) { + $thread->type = Thread::TYPE_NOTE; + } else { + $thread->type = Thread::TYPE_MESSAGE; + } + $thread->source_via = Thread::PERSON_USER; + $thread->source_type = Thread::SOURCE_TYPE_WEB; + } else { + if ($is_forward || $is_phone) { + $thread->type = Thread::TYPE_NOTE; + } else { + $thread->type = Thread::TYPE_MESSAGE; + } + $thread->created_at = $now; + } + if ($new) { + $thread->first = true; + } + $thread->user_id = $conversation->user_id; + $thread->status = $request->status; + $thread->state = Thread::STATE_PUBLISHED; + $thread->customer_id = $customer->id; + $thread->created_by_user_id = auth()->user()->id; + $thread->edited_by_user_id = null; + $thread->edited_at = null; + $thread->body = $body; + if ($is_create && !$is_multiple && count($to_array) > 1) { + $thread->setTo($to_array); + } else { + $thread->setTo($to); + } + // We save CC and BCC as is and filter emails when sending replies + $thread->setCc($request->cc); + $thread->setBcc($request->bcc); + if ($attachments_info['has_attachments'] && !$is_forward) { + $thread->has_attachments = true; + } + if (!empty($request->saved_reply_id)) { + $thread->saved_reply_id = $request->saved_reply_id; + } + + $forwarded_conversations = []; + $forwarded_threads = []; + + if ($is_forward) { + // Create forwarded conversations. + foreach ($to_array as $recipient_email) { + $forwarded_conversation = $conversation->replicate(); + $forwarded_conversation->type = Conversation::TYPE_EMAIL; + $forwarded_conversation->setPreview($thread->body); + $forwarded_conversation->created_by_user_id = auth()->user()->id; + $forwarded_conversation->source_via = Conversation::PERSON_USER; + $forwarded_conversation->source_type = Conversation::SOURCE_TYPE_WEB; + $forwarded_conversation->threads_count = 0; // Counter will be incremented in ThreadObserver. + $forwarded_customer = Customer::create($recipient_email); + $forwarded_conversation->customer_id = $forwarded_customer->id; + // Reload customer object, otherwise it stores previous customer. + $forwarded_conversation->load('customer'); + $forwarded_conversation->customer_email = $recipient_email; + $forwarded_conversation->subject = 'Fwd: '.$forwarded_conversation->subject; + //$forwarded_conversation->setCc(array_merge(Conversation::sanitizeEmails($request->cc), [$to])); + $forwarded_conversation->setCc(Conversation::sanitizeEmails($request->cc)); + $forwarded_conversation->setBcc($request->bcc); + $forwarded_conversation->last_reply_at = $now; + $forwarded_conversation->last_reply_from = Conversation::PERSON_USER; + $forwarded_conversation->user_updated_at = $now; + if ($attachments_info['has_attachments']) { + $forwarded_conversation->has_attachments = true; + } + $forwarded_conversation->updateFolder(); + $forwarded_conversation->save(); + + $forwarded_thread = $thread->replicate(); + + $forwarded_conversations[] = $forwarded_conversation; + $forwarded_threads[] = $forwarded_thread; + } + + // Set forwarding meta data. + // todo: store array of numbers and IDs. + $thread->subtype = Thread::SUBTYPE_FORWARD; + $thread->setMeta(Thread::META_FORWARD_CHILD_CONVERSATION_NUMBER, $forwarded_conversation->number); + $thread->setMeta(Thread::META_FORWARD_CHILD_CONVERSATION_ID, $forwarded_conversation->id); + } + + // Conversation history. + if (!empty($request->conv_history)) { + if ($request->conv_history != 'global') { + if ($is_forward && !empty($forwarded_threads)) { + foreach ($forwarded_threads as $forwarded_thread) { + $forwarded_thread->setMeta(Thread::META_CONVERSATION_HISTORY, $request->conv_history); + } + } else { + $thread->setMeta(Thread::META_CONVERSATION_HISTORY, $request->conv_history); + } + } + } + + // From (mailbox alias). + if (!empty($request->from_alias)) { + $thread->from = $request->from_alias; + } + + \Eventy::action('thread.before_save_from_request', $thread, $request); + $thread->save(); + + // Save forwarded thread. + if ($is_forward) { + foreach ($forwarded_conversations as $i => $forwarded_conversation) { + $forwarded_thread = $forwarded_threads[$i]; + + $forwarded_thread->conversation_id = $forwarded_conversation->id; + $forwarded_thread->type = Thread::TYPE_MESSAGE; + $forwarded_thread->subtype = null; + if ($attachments_info['has_attachments']) { + $forwarded_thread->has_attachments = true; + } + $forwarded_thread->setMeta(Thread::META_FORWARD_PARENT_CONVERSATION_NUMBER, $conversation->number); + $forwarded_thread->setMeta(Thread::META_FORWARD_PARENT_CONVERSATION_ID, $conversation->id); + $forwarded_thread->setMeta(Thread::META_FORWARD_PARENT_THREAD_ID, $thread->id); + \Eventy::action('send_reply.before_save_forwarded_thread', $forwarded_thread, $request); + $forwarded_thread->save(); + } + } + + // If thread has been created from draft, remove the draft + // if ($request->thread_id) { + // $draft_thread = Thread::find($request->thread_id); + // if ($draft_thread) { + // $draft_thread->delete(); + // } + // } + + if ($from_draft) { + // Remove conversation from drafts folder if needed + $conversation->maybeRemoveFromDrafts(); + } + + // Update folders counters + $conversation->mailbox->updateFoldersCounters(); + + $response['status'] = 'success'; + + // Set thread_id for uploaded attachments + if ($attachments_info['attachments']) { + if ($is_forward) { + // Copy attachments for each thread. + if (count($forwarded_threads) > 1) { + $attachments = Attachment::whereIn('id', $attachments_info['attachments'])->get(); + } + foreach ($forwarded_threads as $i => $forwarded_thread) { + if ($i == 0) { + Attachment::whereIn('id', $attachments_info['attachments'])->update(['thread_id' => $forwarded_thread->id]); + } else { + foreach ($attachments as $attachment) { + $attachment->duplicate($forwarded_thread->id); + } + } + } + } else { + Attachment::whereIn('id', $attachments_info['attachments']) + ->where('thread_id', null) + ->update(['thread_id' => $thread->id]); + } + } + + // Follow conversation if it's assigned to someone else. + if (!$is_create && !$new && !$is_forward && !$is_note + && $conversation->user_id != $user->id + ) { + $user->followConversation($conversation->id); + } + + // When user creates a new conversation it may be saved as draft first. + if ($is_create) { + // New conversation. + event(new UserCreatedConversation($conversation, $thread)); + \Eventy::action('conversation.created_by_user_can_undo', $conversation, $thread); + // After Conversation::UNDO_TIMOUT period trigger final event. + \Helper::backgroundAction('conversation.created_by_user', [$conversation, $thread], now()->addSeconds(Conversation::UNDO_TIMOUT)); + } elseif ($is_forward) { + // Forward. + // Notifications to users not sent. + event(new UserAddedNote($conversation, $thread)); + foreach ($forwarded_conversations as $i => $forwarded_conversation) { + $forwarded_thread = $forwarded_threads[$i]; + + // To send email with forwarded conversation. + event(new UserReplied($forwarded_conversation, $forwarded_thread)); + \Eventy::action('conversation.user_forwarded_can_undo', $conversation, $thread, $forwarded_conversation, $forwarded_thread); + // After Conversation::UNDO_TIMOUT period trigger final event. + \Helper::backgroundAction('conversation.user_forwarded', [$conversation, $thread, $forwarded_conversation, $forwarded_thread], now()->addSeconds(Conversation::UNDO_TIMOUT)); + } + } elseif ($is_note) { + // Note. + event(new UserAddedNote($conversation, $thread)); + \Eventy::action('conversation.note_added', $conversation, $thread); + } else { + // Reply. + event(new UserReplied($conversation, $thread)); + \Eventy::action('conversation.user_replied_can_undo', $conversation, $thread); + // After Conversation::UNDO_TIMOUT period trigger final event. + \Helper::backgroundAction('conversation.user_replied', [$conversation, $thread], now()->addSeconds(Conversation::UNDO_TIMOUT)); + } + + // Send new conversation separately to each customer. + if ($is_create && count($to_array) > 1 && $is_multiple) { + $prev_customers_ids = []; + foreach ($to_array as $i => $customer_email) { + // Skip first email, as conversation has already been created for it. + if ($i == 0) { + continue; + } + // Get customer by email. + $customer_tmp = Customer::getByEmail($customer_email); + // Skip same customers. + if ($customer_tmp && in_array($customer_tmp->id, $prev_customers_ids)) { + continue; + } + + if (!$customer_tmp) { + $customer_tmp = Customer::create($customer_email); + } + + $prev_customers_ids[] = $customer_tmp->id; + + // Copy conversation and thread. + $conversation_copy = $conversation->replicate(); + $thread_copy = $thread->replicate(); + + // Save conversation. + $conversation_copy->threads_count = 0; + $conversation_copy->customer_id = $customer_tmp->id; + // Reload customer, otherwise all recipients will have the same name. + $conversation_copy->load('customer'); + $conversation_copy->customer_email = $customer_email; + $conversation_copy->has_attachments = $conversation->has_attachments; + $conversation_copy->push(); + + $thread_copy->conversation_id = $conversation_copy->id; + $thread_copy->customer_id = $customer_tmp->id; + $thread_copy->has_attachments = $conversation->has_attachments; + $thread_copy->setTo($customer_email); + // Reload the conversation, otherwise Thread observer will be + // increasing threads_count for the first conversation. + $thread_copy->load('conversation'); + $thread_copy->push(); + + // Copy attachments. + if (!empty($attachments_info['attachments'])) { + $attachments = Attachment::whereIn('id', $attachments_info['attachments'])->get(); + foreach ($attachments as $attachment) { + $attachment->duplicate($thread_copy->id); + } + } + + // Events. + // todo: allow to undo all emails + event(new UserCreatedConversation($conversation_copy, $thread_copy)); + \Eventy::action('conversation.created_by_user_can_undo', $conversation_copy, $thread_copy); + // After Conversation::UNDO_TIMOUT period trigger final event. + \Helper::backgroundAction('conversation.created_by_user', [$conversation_copy, $thread_copy], now()->addSeconds(Conversation::UNDO_TIMOUT)); + } + } + + // Compose flash message. + $show_view_link = true; + if (!empty($request->after_send) && $request->after_send == MailboxUser::AFTER_SEND_STAY) { + $show_view_link = false; + } + + $flash_vars = ['%tag_start%' => '', '%tag_end%' => '', '%view_start%' => ' ', '%a_end%' => ' ', '%undo_start%' => ' ']; + + if ($is_phone) { + $flash_type = 'warning'; + if ($show_view_link) { + $flash_text = __(':%tag_start%Conversation created:%tag_end% :%view_start%View:%a_end% or :%undo_start%Undo:%a_end%', $flash_vars); + } else { + $flash_text = ''.__('Conversation created').''; + } + } elseif ($is_note) { + $flash_type = 'warning'; + if ($show_view_link) { + $flash_text = __(':%tag_start%Note added:%tag_end% :%view_start%View:%a_end%', $flash_vars); + } else { + $flash_text = ''.__('Note added').''; + } + } else { + $flash_type = 'success'; + if ($show_view_link) { + $flash_text = __(':%tag_start%Email Sent:%tag_end% :%view_start%View:%a_end% or :%undo_start%Undo:%a_end%', $flash_vars); + } else { + $flash_text = __(':%tag_start%Email Sent:%tag_end% :%undo_start%Undo:%a_end%', $flash_vars); + } + } + + \Session::flash('flash_'.$flash_type.'_floating', $flash_text); + } + break; + + // Save draft (automatically or by click) of a new conversation or reply. + case 'save_draft': + + $mailbox = Mailbox::findOrFail($request->mailbox_id); + + if (!$response['msg'] && !$user->can('view', $mailbox)) { + $response['msg'] = __('Not enough permissions'); + } + + $conversation = null; + $new = true; + if (!$response['msg'] && !empty($request->conversation_id)) { + $conversation = Conversation::find($request->conversation_id); + if ($conversation && !$user->can('view', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } else { + $new = false; + } + } + + $is_create = false; + if (!empty($request->is_create)) { + $is_create = true; + } + + $thread = null; + $new_thread = true; + if (!$response['msg'] && !empty($request->thread_id)) { + $thread = Thread::find($request->thread_id); + if ($thread && (!$conversation || $thread->conversation_id != $conversation->id)) { + $response['msg'] = __('Incorrect thread'); + } else { + $new_thread = false; + } + } + + // Check if thread has been sent (in other window for example). + if (!$response['msg']) { + if ($thread && $thread->state == Thread::STATE_PUBLISHED) { + $response['msg'] = __('Message has been already sent. Please discard this draft.'); + } + } + + // To prevent creating draft after reply has been created. + if (!$response['msg'] && $conversation) { + // Check if the last thread has same content as the new one. + $last_thread = $conversation->getLastThread([Thread::TYPE_MESSAGE, Thread::TYPE_NOTE]); + + if ($last_thread + && $last_thread->created_by_user_id == $user->id + && $last_thread->body == $request->body + ) { + //\Log::error("You've already sent this message just recently."); + $response['msg'] = __("You've already sent this message just recently."); + } + } + + // Validation is not needed on draft create, fields can be empty + + if (!$response['msg']) { + + // Get attachments info + $attachments_info = $this->processReplyAttachments($request); + + // Conversation + $now = date('Y-m-d H:i:s'); + + if ($new) { + $conversation = new Conversation(); + } + + // To is a single email or array of emails. + $to = ''; + + if ($new || $is_create) { + // New conversation + $customer_email = ''; + $customer = null; + + $type = Conversation::TYPE_EMAIL; + if (!empty($request->type)) { + $type = (int)$request->type; + } + + if ($type == Conversation::TYPE_PHONE) { + // Phone. + $phone_customer_data = $this->processPhoneCustomer($request); + + $customer_email = $phone_customer_data['customer_email']; + $customer = $phone_customer_data['customer']; + } else { + // Email. + // Now instead of customer_email we store emails in thread->to. + $to_array = Conversation::sanitizeEmails($request->to); + if (count($to_array)) { + if (count($to_array) == 1) { + //$customer_email = array_first($to_array); + $to = array_first($to_array); + $customer = Customer::create($customer_email); + } else { + // Creating a conversation to multiple customers + // In customer_email temporary store a list of customer emails. + //$customer_email = implode(',', $to_array); + $to = $to_array; + + // Keep $customer as null. + // When conversation will be sent, separate conversation + // will be created for each customer. + $customer = null; + } + } + } + + $conversation->type = $type; + $conversation->state = Conversation::STATE_DRAFT; + $conversation->status = $request->status; + $conversation->subject = $request->subject; + $conversation->setPreview($request->body); + if ($attachments_info['has_attachments']) { + $conversation->has_attachments = true; + } + $conversation->mailbox_id = $request->mailbox_id; + // Customer may be empty in draft + if ($customer) { + $conversation->customer_id = $customer->id; + } + $conversation->customer_email = $customer_email; + $conversation->created_by_user_id = auth()->user()->id; + $conversation->source_via = Conversation::PERSON_USER; + $conversation->source_type = Conversation::SOURCE_TYPE_WEB; + } else { + // Reply + $customer = $conversation->customer; + } + + // New draft conversation is not assigned to anybody + //$conversation->user_id = null; + + if (empty($request->to) || !is_array($request->to)) { + if (!empty($request->to)) { + // New conversation. + $to = $request->to; + } elseif (!empty($request->to_email)) { + // Forwarding. + $to = $request->to_email; + } else { + $to = $conversation->customer_email; + } + } + + // Conversation type. + if (!empty($request->type) && array_key_exists((int)$request->type, Conversation::$types)) { + $conversation->type = (int)$request->type; + } + + // Save extra recipients to CC + if ($is_create) { + //$conversation->setCc(array_merge(Conversation::sanitizeEmails($request->cc), (is_array($to) ? $to : [$to]))); + $conversation->setCc($request->cc); + $conversation->setBcc($request->bcc); + } + // $conversation->last_reply_at = $now; + // $conversation->last_reply_from = Conversation::PERSON_USER; + // $conversation->user_updated_at = $now; + $conversation->updateFolder(); + + $conversation->save(); + + // Create thread + if (empty($thread)) { + $thread = new Thread(); + $thread->conversation_id = $conversation->id; + $thread->user_id = auth()->user()->id; + //$thread->type = Thread::TYPE_MESSAGE; + if ($new) { + $thread->first = true; + } + //$thread->status = $request->status; + $thread->state = Thread::STATE_DRAFT; + + $thread->source_via = Thread::PERSON_USER; + $thread->source_type = Thread::SOURCE_TYPE_WEB; + if ($customer) { + $thread->customer_id = $customer->id; + } + $thread->created_by_user_id = auth()->user()->id; + // User is forwarding a conversation. + if (!empty($request->subtype) && (int)$request->subtype) { + $thread->subtype = $request->subtype; + } + } + if ($attachments_info['has_attachments']) { + $thread->has_attachments = true; + } + // Thread type. + if ($is_create && !empty($request->is_note)) { + $thread->type = Thread::TYPE_NOTE; + } else { + $thread->type = Thread::TYPE_MESSAGE; + } + $thread->from = $request->from_alias ?? null; + $thread->body = $request->body; + $thread->setTo($to); + // We save CC and BCC as is and filter emails when sending replies + $thread->setCc($request->cc); + $thread->setBcc($request->bcc); + // Set edited info + if ($thread->created_by_user_id != $user->id) { + $thread->edited_by_user_id = $user->id; + $thread->edited_at = $now; + } + $thread->save(); + + $conversation->addToFolder(Folder::TYPE_DRAFTS); + + $response['conversation_id'] = $conversation->id; + $response['customer_id'] = $conversation->customer_id; + $response['thread_id'] = $thread->id; + $response['number'] = $conversation->number; + + $response['status'] = 'success'; + + // Set thread_id for uploaded attachments + if ($attachments_info['attachments']) { + Attachment::whereIn('id', $attachments_info['attachments']) + ->where('thread_id', null) + ->update(['thread_id' => $thread->id]); + } + + // Update folder counter. + $conversation->mailbox->updateFoldersCounters(Folder::TYPE_DRAFTS); + + if ($new) { + event(new UserCreatedConversationDraft($conversation, $thread)); + } elseif ($new_thread) { + event(new UserCreatedThreadDraft($conversation, $thread)); + } + + $response['status'] = 'success'; + } + + // Reflash session data - otherwise on reply flash alert is not displayed + // https://stackoverflow.com/questions/37019294/laravel-ajax-call-deletes-session-flash-data + \Session::reflash(); + + break; + + // Discard draft (from new conversation, from reply or conversation) + case 'discard_draft': + + $thread = Thread::find($request->thread_id); + + if (!$thread) { + // Discarding nont saved yet draft + $response['status'] = 'success'; + + // Discarding a new conversation being created from thread + if (!empty($request->from_thread_id)) { + $original_thread = Thread::find($request->from_thread_id); + if ($original_thread && $original_thread->conversation_id) { + // Open original conversation + $response['redirect_url'] = route('conversations.view', ['id' => $original_thread->conversation_id]); + } + } + break; + //$response['msg'] = __('Thread not found'); + } + if (!$response['msg'] && !$user->can('view', $thread->conversation)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!$response['msg']) { + $conversation = $thread->conversation; + + if ($conversation->state == Conversation::STATE_DRAFT) { + // New conversation draft being discarded + $folder_id = $conversation->getCurrentFolder(); + $response['redirect_url'] = route('mailboxes.view.folder', ['id' => $conversation->mailbox_id, 'folder_id' => $folder_id]); + + $mailbox = $conversation->mailbox; + + $conversation->removeFromFolder(Folder::TYPE_DRAFTS); + $conversation->removeFromFolder(Folder::TYPE_STARRED, $user->id); + $mailbox->updateFoldersCounters(Folder::TYPE_DRAFTS); + $conversation->deleteThreads(); + $conversation->delete(); + + // Draft may be present in Starred folder. + Conversation::clearStarredByUserCache($user->id, $mailbox->id); + $mailbox->updateFoldersCounters(Folder::TYPE_STARRED); + + $flash_message = __('Deleted draft'); + \Session::flash('flash_success_floating', $flash_message); + } else { + // https://github.com/freescout-helpdesk/freescout/issues/2873 + if ($thread->state == Thread::STATE_DRAFT) { + // Just remove the thread, no need to reload the page + $thread->deleteThread(); + // Remove conversation from drafts folder if needed + $removed_from_folder = $conversation->maybeRemoveFromDrafts(); + if ($removed_from_folder) { + $conversation->mailbox->updateFoldersCounters(Folder::TYPE_DRAFTS); + } + } + } + + $response['status'] = 'success'; + } + break; + + // Save draft (automatically or by click) + case 'load_draft': + $thread = Thread::find($request->thread_id); + if (!$thread) { + $response['msg'] = __('Thread not found'); + } elseif ($thread->state != Thread::STATE_DRAFT) { + $response['msg'] = __('Thread is not in a draft state'); + } else { + if (!$user->can('view', $thread->conversation)) { + $response['msg'] = __('Not enough permissions'); + } + } + + if (!$response['msg']) { + + // Build attachments list. + $attachments = []; + foreach ($thread->attachments as $attachment) { + $attachments[] = [ + 'id' => $attachment->id, + 'name' => $attachment->file_name, + 'size' => $attachment->size, + 'url' => $attachment->url(), + ]; + } + + $response['data'] = [ + 'thread_id' => $thread->id, + 'from_alias' => $thread->from, + 'to' => $thread->getToFirst(), + 'cc' => $thread->getCcArray(), + 'bcc' => $thread->getBccArray(), + 'body' => $thread->body, + 'is_forward' => (int)$thread->isForward(), + 'attachments' => $attachments, + ]; + $response['status'] = 'success'; + } + break; + + // Load attachments from all threads in conversation + // when forwarding or creating a new conversation. + case 'load_attachments': + $conversation = Conversation::find($request->conversation_id); + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } else { + if (!$user->can('view', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + } + + if (!$response['msg']) { + // Build attachments list. + $attachments = []; + + if ($conversation->has_attachments) { + foreach ($conversation->threads as $thread) { + if ($thread->has_attachments) { + foreach ($thread->attachments as $attachment) { + if ($request->is_forwarding == 'true') { + $attachment_copy = $attachment->duplicate($thread->id); + } else { + $attachment_copy = $attachment; + } + + $attachments[] = [ + 'id' => $attachment_copy->id, + 'name' => $attachment_copy->file_name, + 'size' => $attachment_copy->size, + 'url' => $attachment_copy->url(), + ]; + } + } + } + } + + $response['data'] = [ + 'attachments' => $attachments, + ]; + $response['status'] = 'success'; + } + break; + + // Save default redirect + case 'save_after_send': + $mailbox = Mailbox::find($request->mailbox_id); + if (!$mailbox) { + $response['msg'] .= __('Mailbox not found'); + } elseif (!$mailbox->userHasAccess($user->id)) { + $response['msg'] .= __('Action not authorized'); + } + if (!$response['msg']) { + $mailbox_user = $user->mailboxesWithSettings()->where('mailbox_id', $request->mailbox_id)->first(); + if (!$mailbox_user) { + // Admin may not be connected to the mailbox yet + $user->mailboxes()->attach($request->mailbox_id); + // $mailbox_user = new MailboxUser(); + // $mailbox_user->mailbox_id = $mailbox->id; + // $mailbox_user->user_id = $user->id; + $mailbox_user = $user->mailboxesWithSettings()->where('mailbox_id', $request->mailbox_id)->first(); + } + $mailbox_user->settings->after_send = $request->value; + $mailbox_user->settings->save(); + + $response['status'] = 'success'; + } + break; + + // Conversations navigation + case 'conversations_pagination': + if (!empty($request->filter)) { + $response = $this->ajaxConversationsFilter($request, $response, $user); + } else { + $response = $this->ajaxConversationsPagination($request, $response, $user); + } + break; + + // Change conversation customer + case 'conversation_change_customer': + $conversation = Conversation::find($request->conversation_id); + $customer_email = $request->customer_email; + + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } + if (!$response['msg'] && !$user->can('update', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + if (!$response['msg'] && !$conversation->mailbox->userHasAccess($user->id)) { + $response['msg'] = __('Not enough permissions'); + } + + $conversation->changeCustomer($customer_email, null, $user); + + $response['status'] = 'success'; + \Session::flash('flash_success_floating', __('Customer changed')); + + break; + + // Star/unstar conversation + case 'star_conversation': + $conversation = Conversation::find($request->conversation_id); + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } elseif (!$user->can('view', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!$response['msg']) { + if ($request->sub_action == 'star') { + $conversation->star($user); + } else { + $conversation->unstar($user); + } + $response['status'] = 'success'; + } + break; + + // Delete conversation (move to DELETED folder) + case 'delete_conversation': + $conversation = Conversation::find($request->conversation_id); + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } elseif (!$user->can('delete', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!$response['msg']) { + $folder_id = $conversation->getCurrentFolder(); + + $conversation->deleteToFolder($user); + + $response['redirect_url'] = route('mailboxes.view.folder', ['id' => $conversation->mailbox_id, 'folder_id' => $folder_id]); + + $response['status'] = 'success'; + + \Session::flash('flash_success_floating', __('Conversation deleted')); + } + break; + + // Delete conversation forever + case 'delete_conversation_forever': + $conversation = Conversation::find($request->conversation_id); + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } elseif (!$user->can('delete', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!$response['msg']) { + $folder_id = $conversation->getCurrentFolder(); + $mailbox = $conversation->mailbox; + + $conversation->deleteForever(); + + // Recalculate only old and new folders + $mailbox->updateFoldersCounters(); + + $response['redirect_url'] = route('mailboxes.view.folder', ['id' => $conversation->mailbox_id, 'folder_id' => $folder_id]); + + $response['status'] = 'success'; + + \Session::flash('flash_success_floating', __('Conversation deleted')); + } + break; + + // Restore conversation + case 'restore_conversation': + $conversation = Conversation::find($request->conversation_id); + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } elseif (!$user->can('delete', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!$response['msg']) { + $folder_id = $conversation->folder_id; + $prev_state = $conversation->state; + $conversation->state = Conversation::STATE_PUBLISHED; + $conversation->user_updated_at = date('Y-m-d H:i:s'); + $conversation->updateFolder(); + $conversation->save(); + + // Create lineitem thread + $thread = new Thread(); + $thread->conversation_id = $conversation->id; + $thread->user_id = $conversation->user_id; + $thread->type = Thread::TYPE_LINEITEM; + $thread->state = Thread::STATE_PUBLISHED; + $thread->status = Thread::STATUS_NOCHANGE; + $thread->action_type = Thread::ACTION_TYPE_RESTORE_TICKET; + $thread->source_via = Thread::PERSON_USER; + // todo: this need to be changed for API + $thread->source_type = Thread::SOURCE_TYPE_WEB; + $thread->customer_id = $conversation->customer_id; + $thread->created_by_user_id = $user->id; + $thread->save(); + + // Recalculate only old and new folders + $conversation->mailbox->updateFoldersCounters(); + + if ($prev_state != $conversation->state) { + \Eventy::action('conversation.state_changed', $conversation, $user, $prev_state); + } + + $response['status'] = 'success'; + + \Session::flash('flash_success_floating', __('Conversation restored')); + } + break; + + // Load data to edit thread. + case 'load_edit_thread': + $thread = Thread::find($request->thread_id); + if (!$thread) { + $response['msg'] = __('Thread not found'); + } elseif (!$user->can('edit', $thread)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!$response['msg']) { + $data = [ + 'thread' => $thread + ]; + $response['html'] = \View::make('conversations/partials/edit_thread')->with($data)->render(); + + $response['status'] = 'success'; + } + break; + + // Load data to edit thread. + case 'save_edit_thread': + $thread = Thread::find($request->thread_id); + if (!$thread) { + $response['msg'] = __('Conversation not found'); + } elseif (!$user->can('edit', $thread)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!$response['msg']) { + if (!$thread->body_original) { + $thread->body_original = $thread->body; + } + $thread->body = $request->body; + $thread->edited_by_user_id = $user->id; + $thread->edited_at = date('Y-m-d H:i:s'); + $response['body'] = $thread->getCleanBody(); + + if (strip_tags($response['body'])) { + + // Update the preview for the conversation if needed. + $last_thread = $thread->conversation->getLastThread([Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE, Thread::TYPE_NOTE]); + if ($last_thread && $last_thread->id == $thread->id) { + $thread->conversation->setPreview($thread->body); + $thread->conversation->save(); + } + $thread->save(); + + $response['status'] = 'success'; + } else { + $response['msg'] = __('Message cannot be empty'); + } + } + break; + + // Delete thread (note). + case 'delete_thread': + $thread = Thread::find($request->thread_id); + if (!$thread || !$thread->isNote()) { + $response['msg'] = __('Thread not found'); + } elseif (!$user->can('delete', $thread)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!$response['msg']) { + $thread->deleteThread(); + $response['status'] = 'success'; + } + break; + + // Change conversations user + case 'bulk_conversation_change_user': + + $conversations = Conversation::findMany($request->conversation_id); + + $new_user_id = (int) $request->user_id; + + if (!$response['msg']) { + foreach ($conversations as $conversation) { + if (!$user->can('update', $conversation)) { + continue; + } + if ((int) $new_user_id != -1 && !$conversation->mailbox->userHasAccess($new_user_id)) { + continue; + } + + $conversation->changeUser($new_user_id, $user); + } + + $response['status'] = 'success'; + // Flash + $flash_message = __('Assignee updated'); + \Session::flash('flash_success_floating', $flash_message); + + $response['msg'] = __('Assignee updated'); + } + break; + + // Change conversations status + case 'bulk_conversation_change_status': + $conversations = Conversation::findMany($request->conversation_id); + + $new_status = (int) $request->status; + + if (!in_array((int) $request->status, array_keys(Conversation::$statuses))) { + $response['msg'] = __('Incorrect status'); + } + + if (!$response['msg']) { + foreach ($conversations as $conversation) { + if (!$user->can('update', $conversation)) { + continue; + } + + $conversation->changeStatus($new_status, $user); + } + + $response['status'] = 'success'; + // Flash + $flash_message = __('Status updated'); + \Session::flash('flash_success_floating', $flash_message); + + $response['msg'] = __('Status updated'); + } + break; + + // Delete converations. + case 'bulk_delete_conversation': + // At first, check if this user is able to delete conversations + if (!auth()->user()->isAdmin() && !auth()->user()->hasPermission(\App\User::PERM_DELETE_CONVERSATIONS)) { + $response['msg'] = __('Not enough permissions'); + //\Session::flash('flash_success_floating', __('Conversations deleted')); + + return \Response::json($response); + } + + $conversations = Conversation::findMany($request->conversation_id); + $mailboxes_to_recalculate = []; + + foreach ($conversations as $conversation) { + if (!$user->can('delete', $conversation)) { + continue; + } + + if ($conversation->state != Conversation::STATE_DELETED) { + // Move to Deleted folder. + $conversation->state = Conversation::STATE_DELETED; + $conversation->user_updated_at = date('Y-m-d H:i:s'); + $conversation->updateFolder(); + $conversation->save(); + + // Create lineitem thread + $thread = new Thread(); + $thread->conversation_id = $conversation->id; + $thread->user_id = $conversation->user_id; + $thread->type = Thread::TYPE_LINEITEM; + $thread->state = Thread::STATE_PUBLISHED; + $thread->status = Thread::STATUS_NOCHANGE; + $thread->action_type = Thread::ACTION_TYPE_DELETED_TICKET; + $thread->source_via = Thread::PERSON_USER; + $thread->source_type = Thread::SOURCE_TYPE_WEB; + $thread->customer_id = $conversation->customer_id; + $thread->created_by_user_id = $user->id; + $thread->save(); + + // Remove conversation from drafts folder. + $conversation->removeFromFolder(Folder::TYPE_DRAFTS); + } else { + // Delete forever + $conversation->deleteForever(); + } + + if (!array_key_exists($conversation->mailbox_id, $mailboxes_to_recalculate)) { + $mailboxes_to_recalculate[$conversation->mailbox_id] = $conversation->mailbox; + } + } + // Recalculate folders counters for mailboxes. + foreach ($mailboxes_to_recalculate as $mailbox) { + $mailbox->updateFoldersCounters(); + } + + $response['status'] = 'success'; + \Session::flash('flash_success_floating', __('Conversations deleted')); + break; + + // Delete converations in a specific folder. + case 'empty_folder': + // At first, check if this user is able to delete conversations + if (!auth()->user()->isAdmin() && !auth()->user()->hasPermission(\App\User::PERM_DELETE_CONVERSATIONS)) { + $response['msg'] = __('Not enough permissions'); + return \Response::json($response); + } + + $response = \Eventy::filter('conversations.empty_folder', $response, + $request->mailbox_id, + $request->folder_id + ); + + if (empty($response['processed'])) { + $folder = Folder::find($request->folder_id ?? ''); + + if (!$folder) { + $response['msg'] = __('Folder not found'); + } + + if (!$response['msg']) { + $conversation_ids = Conversation::where('folder_id', $folder->id)->pluck('id')->toArray(); + Conversation::deleteConversationsForever($conversation_ids); + if ($folder->mailbox) { + Conversation::clearStarredByUserCache($user->id, $folder->mailbox_id); + $folder->mailbox->updateFoldersCounters(); + } else { + $folder->updateCounters(); + } + } + } + + $response['status'] = 'success'; + \Session::flash('flash_success_floating', __('Conversations deleted')); + break; + + // Move conversation to another mailbox. + case 'conversation_move': + $conversation = Conversation::find($request->conversation_id); + + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } + if (!$response['msg'] && !$user->can('update', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + if (!$response['msg'] && !$conversation->mailbox->userHasAccess($user->id)) { + $response['msg'] = __('Not enough permissions'); + } + + $mailbox = null; + if (!$response['msg']) { + if (!empty($request->mailbox_email)) { + $mailbox = Mailbox::where('email', $request->mailbox_email)->first(); + } else { + $mailbox = Mailbox::find($request->mailbox_id); + } + + if (!$mailbox) { + $response['msg'] = __('Mailbox not found'); + } + } + + if (!$response['msg']) { + $prev_folder_id = Conversation::getFolderParam(); + $prev_mailbox_id = $conversation->mailbox_id; + + $conversation->moveToMailbox($mailbox, $user); + + // If user does not have access to the new mailbox, + // redirect to the previous mailbox. + if (!$mailbox->userHasAccess($user->id)) { + if (!empty($prev_folder_id)) { + $response['redirect_url'] = route('mailboxes.view.folder', ['id' => $prev_mailbox_id, 'folder_id' => $prev_folder_id]); + } else { + $response['redirect_url'] = route('mailboxes.view', ['id' => $prev_mailbox_id]); + } + } + + $response['status'] = 'success'; + \Session::flash('flash_success_floating', __('Conversation moved')); + } + + break; + + // Merge conversations + case 'conversation_merge': + $conversation = Conversation::find($request->conversation_id); + + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } + if (!$response['msg'] && !$user->can('view', $conversation)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!empty($request->merge_conversation_id) && is_array($request->merge_conversation_id)) { + + $sigle_conv = count($request->merge_conversation_id) == 1; + + foreach ($request->merge_conversation_id as $merge_conversation_id) { + $merge_conversation = Conversation::find($merge_conversation_id); + + $response['msg'] = ''; + + if (!$merge_conversation) { + $response['msg'] = __('Conversation not found'); + if ($sigle_conv) { + break; + } + } + if (!$response['msg'] && !$user->can('view', $merge_conversation)) { + $response['msg'] = __('Not enough permissions').': #'.$merge_conversation->number; + if ($sigle_conv) { + break; + } + } + + if (!$response['msg']) { + $conversation->mergeConversations($merge_conversation, $user); + + if ($response['status'] != 'success') { + \Session::flash('flash_success_floating', __('Conversations merged')); + } + $response['status'] = 'success'; + } + } + } + + break; + + // Follow conversation + case 'follow': + case 'unfollow': + $conversation = Conversation::find($request->conversation_id); + + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } + if (!$response['msg'] && !$conversation->mailbox->userHasAccess($user->id)) { + $response['msg'] = __('Not enough permissions'); + } + + if ($request->action == 'follow') { + $user->followConversation($request->conversation_id); + } else { + $follower = Follower::where('conversation_id', $request->conversation_id) + ->where('user_id', $user->id) + ->first(); + if ($follower) { + $follower->delete(); + } + } + + if (!$response['msg']) { + $response['status'] = 'success'; + if ($request->action == 'follow') { + $response['msg_success'] = __('Following'); + } else { + $response['msg_success'] = __('Unfollowed'); + } + } + + break; + + case 'update_subject': + $conversation = Conversation::find($request->conversation_id); + + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } + if (!$response['msg'] && !$conversation->mailbox->userHasAccess($user->id)) { + $response['msg'] = __('Not enough permissions'); + } + + $subject = $request->value ?? ''; + $subject = trim($subject); + + if (!$response['msg'] && $subject) { + $conversation->subject = $subject; + $conversation->save(); + + $response['status'] = 'success'; + } + + break; + + case 'merge_search': + $conversation = Conversation::where(Conversation::numberFieldName(), $request->number)->first(); + + if (!$conversation) { + $response['msg'] = __('Conversation not found'); + } + if (!$response['msg'] && !$user->can('view', $conversation)) { + $response['msg'] = __('Conversation not found'); + } + + if (!$response['msg']) { + $response['html'] = \View::make('conversations/partials/merge_search_result')->with([ + 'conversation' => $conversation + ])->render(); + $response['status'] = 'success'; + } + + break; + + case 'chats_load_more': + $mailbox = Mailbox::find($request->mailbox_id); + + if (!$mailbox) { + $response['msg'] = __('Mailbox not found'); + } elseif (!$mailbox->userHasAccess($user->id)) { + $response['msg'] = __('Action not authorized'); + } + + if (!$response['msg']) { + $response['html'] = \View::make('mailboxes/partials/chat_list')->with([ + 'mailbox' => $mailbox, + 'offset' => $request->offset, + ])->render(); + $response['status'] = 'success'; + } + break; + + case 'retry_send': + $thread = Thread::find($request->thread_id); + + if (!$thread) { + $response['msg'] = __('Thread not found'); + } elseif (!$user->can('view', $thread->conversation)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!$response['msg']) { + $job_id = $thread->getFailedJobId(); + + if ($job_id) { + \App\FailedJob::retry($job_id); + $thread->send_status = SendLog::STATUS_ACCEPTED; + $thread->updateSendStatusData(['msg' => '']); + $thread->save(); + + $response['status'] = 'success'; + } + } + + break; + + case 'load_customer_info': + $customer = Customer::getByEmail($request->customer_email); + + if ($customer) { + // Previous conversations + $prev_conversations = []; + + $mailbox = Mailbox::find($request->mailbox_id); + + if ($mailbox && $mailbox->userHasAccess($user->id)) { + $conversation_id = (int)$request->conversation_id ?? 0; + + $prev_conversations = $mailbox->conversations() + ->where('customer_id', $customer->id) + ->where('id', '<>', $conversation_id) + ->where('status', '!=', Conversation::STATUS_SPAM) + ->where('state', Conversation::STATE_PUBLISHED) + //->limit(self::PREV_CONVERSATIONS_LIMIT) + ->orderBy('created_at', 'desc') + ->paginate(self::PREV_CONVERSATIONS_LIMIT); + } + + $response['html'] = \View::make('conversations/partials/customer_sidebar')->with([ + 'customer' => $customer, + 'prev_conversations' => $prev_conversations, + ])->render(); + $response['status'] = 'success'; + } else { + $response['msg'] = 'Customer not found'; + } + break; + + default: + $response['msg'] = 'Unknown action'; + break; + } + + if ($response['status'] == 'error' && empty($response['msg'])) { + $response['msg'] = 'Unknown error occurred'; + } + + return \Response::json($response); + } + + /** + * Conversations ajax controller. + */ + public function ajaxHtml(Request $request) + { + switch ($request->action) { + case 'send_log': + return $this->ajaxHtmlSendLog(); + case 'show_original': + return $this->ajaxHtmlShowOriginal(); + case 'change_customer': + return $this->ajaxHtmlChangeCustomer(); + case 'move_conv': + return $this->ajaxHtmlMoveConv(); + case 'merge_conv': + return $this->ajaxHtmlMergeConv(); + case 'default_redirect': + return $this->ajaxHtmlDefaultRedirect(); + } + + abort(404); + } + + /** + * Send log. + */ + public function ajaxHtmlSendLog() + { + $thread_id = Input::get('thread_id'); + if (!$thread_id) { + abort(404); + } + + $thread = Thread::find($thread_id); + if (!$thread) { + abort(404); + } + + $user = auth()->user(); + + if (!$user->can('view', $thread->conversation)) { + abort(403); + } + + // Get send log + $log_records = SendLog::where('thread_id', $thread_id) + ->orderBy('created_at', 'desc') + ->get(); + + $customers_log = []; + $users_log = []; + foreach ($log_records as $log_record) { + if ($log_record->user_id) { + $users_log[$log_record->email][] = $log_record; + } else { + $customers_log[$log_record->email][] = $log_record; + } + } + + return view('conversations/ajax_html/send_log', [ + 'customers_log' => $customers_log, + 'users_log' => $users_log, + ]); + } + + /** + * Show original message headers. + */ + public function ajaxHtmlShowOriginal() + { + $thread_id = Input::get('thread_id'); + if (!$thread_id) { + abort(404); + } + + $thread = Thread::find($thread_id); + if (!$thread) { + abort(404); + } + + $user = auth()->user(); + + if (!$user->can('view', $thread->conversation)) { + abort(403); + } + + $fetched = true; + $body_preview = $thread->body; + + if ($thread->isCustomerMessage()) { + $fetched = false; + + // Try to fetch original body by imap. + $body_imap = $thread->fetchBody(); + if ($body_imap) { + $fetched = true; + $body_preview = $body_imap; + } + } + + return view('conversations/ajax_html/show_original', [ + 'thread' => $thread, + 'body_preview' => $body_preview, + 'fetched' => $fetched, + ]); + } + + /** + * Change conversation customer. + */ + public function ajaxHtmlChangeCustomer() + { + $conversation_id = Input::get('conversation_id'); + if (!$conversation_id) { + abort(404); + } + + $conversation = Conversation::find($conversation_id); + if (!$conversation) { + abort(404); + } + + $user = auth()->user(); + + if (!$user->can('view', $conversation)) { + abort(403); + } + + return view('conversations/ajax_html/change_customer', [ + 'conversation' => $conversation, + ]); + } + + /** + * Move conversation to other mailbox. + */ + public function ajaxHtmlMoveConv() + { + $conversation_id = Input::get('conversation_id'); + if (!$conversation_id) { + abort(404); + } + + $conversation = Conversation::find($conversation_id); + if (!$conversation) { + abort(404); + } + + $user = auth()->user(); + + if (!$user->can('view', $conversation)) { + abort(403); + } + + return view('conversations/ajax_html/move_conv', [ + 'conversation' => $conversation, + 'mailboxes' => $user->mailboxesCanView(), + ]); + } + + /** + * Merge conversations. + */ + public function ajaxHtmlMergeConv() + { + $conversation_id = Input::get('conversation_id'); + if (!$conversation_id) { + abort(404); + } + + $conversation = Conversation::find($conversation_id); + if (!$conversation) { + abort(404); + } + + $user = auth()->user(); + + if (!$user->can('view', $conversation)) { + \Helper::denyAccess(); + } + + $prev_conversations = []; + + if ($conversation->customer_id) { + $prev_conversations = $conversation->mailbox->conversations() + ->where('customer_id', $conversation->customer_id) + ->where('id', '<>', $conversation->id) + ->where('status', '!=', Conversation::STATUS_SPAM) + ->where('state', Conversation::STATE_PUBLISHED) + ->orderBy('created_at', 'desc') + ->paginate(500); + } + + return view('conversations/ajax_html/merge_conv', [ + 'conversation' => $conversation, + 'prev_conversations' => $prev_conversations, + + ]); + } + + /** + * Change default redirect for the mailbox. + */ + public function ajaxHtmlDefaultRedirect() + { + $mailbox_id = Input::get('mailbox_id'); + if (!$mailbox_id) { + abort(404); + } + + $mailbox = Mailbox::find($mailbox_id); + if (!$mailbox) { + abort(404); + } + + $user = auth()->user(); + + if (!$user->can('view', $mailbox)) { + abort(403); + } + + return view('conversations/ajax_html/default_redirect', [ + 'after_send' => $user->mailboxSettings($mailbox_id)->after_send, + 'mailbox_id' => $mailbox_id, + ]); + } + + /** + * Get redirect URL after performing an action. + */ + public function getRedirectUrl($request, $conversation, $user) + { + if (!empty($request->after_send)) { + $after_send = $request->after_send; + } else { + // todo: use $user->mailboxSettings() + $after_send = $conversation->mailbox->getUserSettings($user->id)->after_send; + } + + // When creating a new conversation. + if (!empty($request->is_create) && $after_send != MailboxUser::AFTER_SEND_STAY) { + return route('mailboxes.view.folder', ['id' => $conversation->mailbox_id, 'folder_id' => $conversation->folder_id]); + } + // if ($conversation->state == Conversation::STATE_DRAFT) { + // return route('mailboxes.view.folder', ['id' => $conversation->mailbox_id, 'folder_id' => $conversation->folder_id]); + // } + + if (!empty($after_send)) { + switch ($after_send) { + case MailboxUser::AFTER_SEND_STAY: + default: + $redirect_url = $conversation->url(); + break; + case MailboxUser::AFTER_SEND_FOLDER: + $folder_id = Conversation::getFolderParam(); + if (!$folder_id) { + $folder_id = $conversation->folder_id; + } + $redirect_url = route('mailboxes.view.folder', ['id' => $conversation->mailbox_id, 'folder_id' => $folder_id]); + break; + case MailboxUser::AFTER_SEND_NEXT: + // We need to get not any next conversation, but ACTIVE next conversation. + $redirect_url = $conversation->urlNext(Conversation::getFolderParam(), Conversation::STATUS_ACTIVE, true); + break; + } + } else { + // If something went wrong and after_send not set, just show the reply + $redirect_url = $conversation->url(); + } + + return $redirect_url; + } + + /** + * Upload files and images. + */ + public function upload(Request $request) + { + $response = [ + 'status' => 'error', + 'msg' => '', // this is error message + ]; + + $user = auth()->user(); + + if (!$user) { + $response['msg'] = __('Please login to upload file'); + } + + if (!$request->hasFile('file') || !$request->file('file')->isValid() || !$request->file) { + $response['msg'] = __('Error occurred uploading file'); + } + + if (!$response['msg']) { + $embedded = true; + + if (!empty($request->attach) && (int) $request->attach) { + $embedded = false; + } + + $attachment = Attachment::create( + $request->file->getClientOriginalName(), + $request->file->getMimeType(), + null, + '', + $request->file, + $embedded, + null, + $user->id + ); + + if ($attachment) { + $response['status'] = 'success'; + $response['url'] = $attachment->url(); + $response['attachment_id'] = $attachment->id; + } else { + $response['msg'] = __('Error occurred uploading file'); + } + } + + return \Response::json($response); + } + + /** + * Ajax conversation navigation. + */ + public function ajaxConversationsPagination(Request $request, $response, $user) + { + //$mailbox = Mailbox::find($request->mailbox_id); + $folder = null; + $conversations = []; + + if (!$response['msg']) { + $folder = \Eventy::filter('conversations.ajax_pagination_folder', Folder::find($request->folder_id), $request, $response, $user); + if (!$folder) { + $response['msg'] = __('Folder not found'); + } + } + if (!$response['msg'] && !$user->can('view', $folder)) { + $response['msg'] = __('Not enough permissions'); + } + + // We should not use mailbox_id from the request, as it can be changed. + if (!$response['msg'] && !$user->can('view', $folder->mailbox)) { + $response['msg'] = __('Not enough permissions'); + } + + if (!$response['msg']) { + $query_conversations = Conversation::getQueryByFolder($folder, $user->id); + $conversations = $folder->queryAddOrderBy($query_conversations)->paginate(Conversation::DEFAULT_LIST_SIZE, ['*'], 'page', $request->page); + } + + $response['status'] = 'success'; + + $response['html'] = view('conversations/conversations_table', [ + 'folder' => $folder, + 'conversations' => $conversations, + ])->render(); + + return $response; + } + + /** + * Search. + */ + public function search(Request $request) + { + $user = auth()->user(); + $conversations = []; + $customers = []; + + $mode = $this->getSearchMode($request); + + // Search query + $q = $this->getSearchQuery($request); + + // Filters. + $filters = $this->getSearchFilters($request); + $filters_data = []; + // Modify filters is needed. + if (!empty($filters['customer'])) { + // Get customer name. + $filters_data['customer'] = Customer::find($filters['customer']); + } + //$filters = \Eventy::filter('search.filters', $filters, $filters_data, $mode, $q); + + // Remember recent query. + $recent_search_queries = session('recent_search_queries') ?? []; + if ($q && !in_array($q, $recent_search_queries)) { + array_unshift($recent_search_queries, $q); + $recent_search_queries = array_slice($recent_search_queries, 0, 4); + session()->put('recent_search_queries', $recent_search_queries); + } + + $conversations = []; + if (\Eventy::filter('search.is_needed', true, 'conversations')) { + $conversations = $this->searchQuery($user, $q, $filters); + } + + // Jump to the conversation if searching by conversation number. + if (count($conversations) == 1 + && $conversations[0]->number == $q + && empty($filters) + && !$request->x_embed + ) { + return redirect()->away($conversations[0]->url($conversations[0]->folder_id)); + } + + $customers = $this->searchCustomers($request, $user); + + // Dummy folder + $folder = $this->getSearchFolder($conversations); + + // List of available filters. + if ($mode == Conversation::SEARCH_MODE_CONV) { + $filters_list = \Eventy::filter('search.filters_list', Conversation::$search_filters, $mode, $filters, $q); + } else { + $filters_list = \Eventy::filter('search.filters_list_customers', Customer::$search_filters, $mode, $filters, $q); + } + + $mailboxes = \Cache::remember('search_filter_mailboxes_'.$user->id, 5, function () use ($user) { + return $user->mailboxesCanView(); + }); + $users = \Cache::remember('search_filter_users_'.$user->id, 5, function () use ($user, $mailboxes) { + return \Eventy::filter('search.assignees', $user->whichUsersCanView($mailboxes), $user, $mailboxes); + }); + $search_mailbox = null; + if (isset($filters['mailbox'])) { + $mailbox_id = (int)$filters['mailbox']; + if ($mailbox_id && in_array($mailbox_id, $mailboxes->pluck('id')->toArray())) { + foreach ($mailboxes as $mailbox_item) { + if ($mailbox_item->id == $mailbox_id) { + $search_mailbox = $mailbox_item; + break; + } + } + } + } elseif (count($mailboxes) == 1) { + $search_mailbox = $mailboxes[0]; + } + + return view('conversations/search', [ + 'folder' => $folder, + 'q' => $request->q, + 'filters' => $filters, + 'filters_list' => $filters_list, + 'filters_data' => $filters_data, + //'filters_list_all' => $filters_list_all, + 'mode' => $mode, + 'conversations' => $conversations, + 'customers' => $customers, + 'recent' => session('recent_search_queries'), + 'users' => $users, + 'mailboxes' => $mailboxes, + 'search_mailbox' => $search_mailbox, + ]); + } + + /** + * Search conversations. + */ + public function getSearchMode($request) + { + $mode = Conversation::SEARCH_MODE_CONV; + if (!empty($request->mode) && $request->mode == Conversation::SEARCH_MODE_CUSTOMERS) { + $mode = Conversation::SEARCH_MODE_CUSTOMERS; + } + return $mode; + } + + /** + * Search conversations. + */ + public function searchQuery($user, $q, $filters) + { + $conversations = \Eventy::filter('search.conversations.perform', '', $q, $filters, $user); + if ($conversations !== '') { + return $conversations; + } + $query_conversations = Conversation::search($q, $filters, $user); + return $query_conversations->paginate(Conversation::DEFAULT_LIST_SIZE); + } + + /** + * Get and format search query. + */ + public function getSearchQuery($request) + { + $q = ''; + if (!empty($request->q)) { + $q = $request->q; + } elseif (!empty($request->filter) && !empty($request->filter['q'])) { + $q = $request->filter['q']; + } + + return trim($q); + } + + /** + * Get and format search filters. + */ + public function getSearchFilters($request) + { + $filters = []; + + if (!empty($request->f)) { + $filters = $request->f; + } elseif (!empty($request->filter) && !empty($request->filter['f'])) { + $filters = $request->filter['f']; + } + + foreach ($filters as $filter => $value) { + switch ($filter) { + case 'after': + case 'before': + if ($value) { + $filters[$filter] = date('Y-m-d', strtotime($value)); + } + break; + } + } + + $filters = \Eventy::filter('search.filters', $filters, $this->getSearchMode($request), $request); + + return $filters; + } + + /** + * Search conversations. + */ + public function searchCustomers($request, $user) + { + // Get IDs of mailboxes to which user has access + $mailbox_ids = $user->mailboxesIdsCanView(); + + // Filters + $filters = $this->getSearchFilters($request);; + + // Search query + $q = $this->getSearchQuery($request); + + // Like is case insensitive. + $like = '%'.mb_strtolower($q).'%'; + + // We need to use aggregate function for email to avoid "Grouping error" error in PostgreSQL. + $query_customers = Customer::select(['customers.*', \DB::raw('MAX(emails.email)')]) + ->groupby('customers.id') + ->leftJoin('emails', function ($join) { + $join->on('customers.id', '=', 'emails.customer_id'); + }) + ->where(function ($query) use ($like, $q) { + $like_op = 'like'; + if (\Helper::isPgSql()) { + $like_op = 'ilike'; + } + + $query->where('customers.first_name', $like_op, $like) + ->orWhere('customers.last_name', $like_op, $like) + ->orWhere('customers.company', $like_op, $like) + ->orWhere('customers.job_title', $like_op, $like) + ->orWhere('customers.websites', $like_op, $like) + ->orWhere('customers.social_profiles', $like_op, $like) + ->orWhere('customers.address', $like_op, $like) + ->orWhere('customers.city', $like_op, $like) + ->orWhere('customers.state', $like_op, $like) + ->orWhere('customers.zip', $like_op, $like) + ->orWhere('emails.email', $like_op, $like); + + $phone_numeric = \Helper::phoneToNumeric($q); + + if ($phone_numeric) { + $query->orWhere('customers.phones', $like_op, '%"'.$phone_numeric.'"%'); + } + }); + + if (!empty($filters['mailbox']) && in_array($filters['mailbox'], $mailbox_ids)) { + $query_customers->join('conversations', function ($join) use ($filters) { + $join->on('conversations.customer_id', '=', 'customers.id'); + //$join->on('conversations.mailbox_id', '=', $filters['mailbox']); + }); + $query_customers->where('conversations.mailbox_id', '=', $filters['mailbox']); + } + + $query_customers = \Eventy::filter('search.customers.apply_filters', $query_customers, $filters, $q); + + return $query_customers->paginate(50); + } + + /** + * Get dummy folder for search. + */ + public function getSearchFolder($conversations) + { + $folder = new Folder(); + $folder->type = Folder::TYPE_ASSIGNED; + // todo: use select([\DB::raw('SQL_CALC_FOUND_ROWS *')]) to count records + //$folder->total_count = $conversations->count(); + + return $folder; + } + + /** + * Ajax conversations search. + */ + public function ajaxConversationsFilter(Request $request, $response, $user) + { + if (array_key_exists('q', $request->filter)) { + // Search. + $conversations = $this->searchQuery($user, $this->getSearchQuery($request), $this->getSearchFilters($request)); + } else { + // Filters in the mailbox or customer profile. + $conversations = $this->conversationsFilterQuery($request, $user); + } + + $response['status'] = 'success'; + + $response['html'] = view('conversations/conversations_table', [ + 'conversations' => $conversations, + 'params' => $request->params ?? [], + ])->render(); + + return $response; + } + + /** + * Filter conversations according to the request. + */ + public function conversationsFilterQuery($request, $user) + { + // Get IDs of mailboxes to which user has access + $mailbox_ids = $user->mailboxesIdsCanView(); + + $query_conversations = Conversation::whereIn('conversations.mailbox_id', $mailbox_ids) + ->orderBy('conversations.last_reply_at'); + + foreach ($request->filter as $field => $value) { + switch ($field) { + case 'customer_id': + $query_conversations->where('customer_id', $value); + break; + } + } + + return $query_conversations->paginate(Conversation::DEFAULT_LIST_SIZE); + } + + /** + * Process attachments on reply, new conversation, saving draft and forwarding. + */ + public function processReplyAttachments($request) + { + $has_attachments = false; + $attachments = []; + if (!empty($request->attachments_all)) { + $embeds = []; + if (!empty($request->attachments)) { + $attachments = $request->attachments; + } + if (!empty($request->embeds)) { + $embeds = $request->embeds; + } + if (count($attachments) != count($embeds)) { + $has_attachments = true; + } + $attachments_to_remove = array_diff($request->attachments_all, $attachments); + $attachments_to_remove = array_diff($attachments_to_remove, $embeds); + Attachment::deleteByIds($attachments_to_remove); + } + + return [ + 'has_attachments' => $has_attachments, + 'attachments' => $attachments, + ]; + } + + /** + * Undo reply. + */ + public function undoReply(Request $request, $thread_id) + { + $thread = Thread::findOrFail($thread_id); + + if (!$thread) { + abort(404); + } + + $conversation = $thread->conversation; + $this->authorize('view', $conversation); + + // Check undo timeout + if ($thread->created_at->diffInSeconds(now()) > Conversation::UNDO_TIMOUT) { + \Session::flash('flash_error_floating', __('Sending can not be undone')); + + return redirect()->away($conversation->url($conversation->folder_id)); + } + + // Convert reply into draft + $thread->state = Thread::STATE_DRAFT; + $thread->save(); + + // https://github.com/freescout-helpdesk/freescout/issues/3300 + // Cancel all SendReplyToCustomer jobs for this thread. + $jobs_to_cancel = \App\Job::where('queue', 'emails') + ->where('payload', 'like', '{"displayName":"App\\\\\\\\Jobs\\\\\\\\SendReplyToCustomer"%') + ->get(); + + foreach ($jobs_to_cancel as $job) { + $job_thread = $job->getCommandLastThread(); + if ($job_thread && $job_thread->id == $thread->id) { + $job->delete(); + } + } + + // Get penultimate reply + $last_thread = $conversation->threads() + ->where('id', '<>', $thread->id) + ->whereIn('type', [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE]) + ->orderBy('created_at', 'desc') + ->first(); + + $folder_id = $conversation->folder_id; + + // Restore conversation data from penultimate thread + if ($last_thread) { + $conversation->setCc($last_thread->cc); + $conversation->setBcc($last_thread->bcc); + $conversation->last_reply_at = $last_thread->created_at; + $conversation->last_reply_from = $last_thread->source_via; + $conversation->user_updated_at = date('Y-m-d H:i:s'); + } + if ($thread->first) { + // This was a new conversation, move it to drafts + $conversation->state = Thread::STATE_DRAFT; + $conversation->updateFolder(); + $conversation->mailbox->updateFoldersCounters(); + $folder_id = null; + } + $conversation->save(); + + // If forwarding has been undone, we need to remove newly created conversation. + // No need to remove notifications, as they won't work if conversation does not exist. + if ($thread->isForward()) { + $forwarded_conversation = $thread->getForwardChildConversation(); + if ($forwarded_conversation) { + $forwarded_conversation->threads()->delete(); + // todo: maybe perform soft delete of the conversation. + $forwarded_conversation->delete(); + } + } + + Conversation::updatePreview($conversation->id); + + return redirect()->away($conversation->url($folder_id, null, ['show_draft' => $thread->id])); + } + + /** + * Find or create customer when creating a Phone conversation. + */ + public function processPhoneCustomer($request) + { + $customer_data = []; + $customer_email = ''; + $customer = null; + + // Check to prevent creating empty customers. + $request_name = ''; + $request_phone = ''; + if (trim($request->name ?? '') || trim($request->phone ?? '')) { + $request_name = trim($request->name ?? ''); + $request_phone = trim($request->phone ?? ''); + + $name_parts = explode(' ', $request_name); + $customer_data['first_name'] = $name_parts[0]; + if (!empty($name_parts[1])) { + $customer_data['last_name'] = $name_parts[1]; + } + $customer_data['phones'] = [$request_phone]; + } + + // Check if name field contains ID of the customer. + if (!$request->customer_id && is_numeric($request_name)) { + // Try to find customer by ID. + $customer = Customer::find($request_name); + } + + if (!$customer && $request->to_email) { + // Try to get customer by email. + $customer = Customer::getByEmail($request->to_email); + if ($customer) { + $customer_email = $request->to_email; + } + } + + // Try to find customer by phone. + if (!$customer && $request_phone) { + $customer = Customer::findByPhone($request_phone); + if ($customer) { + $customer_email = $customer->getMainEmail(); + } + } + + if (!$customer) { + // Create customer with passed name, email and phone + if (Email::sanitizeEmail($request->to_email)) { + $customer_email = $request->to_email; + // If new email entered, attach email to the current customer + // instead of creating a new customer + if ($request->customer_id) { + $customer = Customer::find($request->customer_id); + if ($customer) { + // Add email to customer. + $customer->addEmail($customer_email, true); + } else { + $customer = Customer::create($customer_email, $customer_data); + } + } else { + $customer = Customer::create($customer_email, $customer_data); + } + } elseif ($customer_data) { + if ($request->customer_id) { + $customer = Customer::find($request->customer_id); + if ($customer) { + $customer->setData($customer_data, false, true); + } + } + + if (!$customer) { + $customer = Customer::createWithoutEmail($customer_data); + } + } + } else { + $customer->setData($customer_data, false, true); + // Add email to customer. + if (Email::sanitizeEmail($request->to_email)) { + $customer->addEmail($request->to_email, true); + } + } + + return [ + 'customer' => $customer, + 'customer_email' => $customer_email, + ]; + } + + /** + * View conversation. + */ + public function chats(Request $request, $mailbox_id) + { + $user = auth()->user(); + + $mailbox = Mailbox::findOrFailWithSettings($mailbox_id, $user->id); + $this->authorize('viewCached', $mailbox); + + // Redirect to the first available chat. + $chats = Conversation::getChats($mailbox_id, 0, 1); + + if (count($chats)) { + if (!\Helper::isChatMode()) { + \Helper::setChatMode(true); + } + + return redirect()->away($chats[0]->url()); + } + + return view('conversations/chats', [ + 'is_in_chat_mode' => true, + 'mailbox' => $mailbox, + ]); + } +} diff --git a/freescout-dist/app/Http/Controllers/CustomersController.php b/freescout-dist/app/Http/Controllers/CustomersController.php new file mode 100644 index 0000000..8e8b247 --- /dev/null +++ b/freescout-dist/app/Http/Controllers/CustomersController.php @@ -0,0 +1,414 @@ +middleware('auth'); + } + + /** + * Edit customer. + */ + public function update($id) + { + $customer = Customer::findOrFail($id); + + $customer_emails = $customer->emails; + if (count($customer_emails)) { + foreach ($customer_emails as $row) { + $emails[] = $row->email; + } + } else { + $emails = ['']; + } + + return view('customers/update', ['customer' => $customer, 'emails' => $emails]); + } + + /** + * Save customer. + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\Response + */ + public function updateSave($id, Request $request) + { + function mb_ucfirst($string) + { + return mb_strtoupper(mb_substr($string, 0, 1)).mb_strtolower(mb_substr($string, 1)); + } + + $customer = Customer::findOrFail($id); + $flash_message = ''; + + // First name or email must be specified + $validator = Validator::make($request->all(), [ + 'first_name' => 'nullable|string|max:255|required_without:emails.0', + 'last_name' => 'nullable|string|max:255', + 'city' => 'nullable|string|max:255', + 'state' => 'nullable|string|max:255', + 'zip' => 'nullable|string|max:12', + 'country' => 'nullable|string|max:2', + //'emails' => 'array|required_without:first_name', + //'emails.1' => 'nullable|email|required_without:first_name', + 'emails.*' => 'nullable|email|distinct|required_without:first_name', + 'photo_url' => 'nullable|image|mimes:jpeg,png,jpg,gif', + ]); + $validator->setAttributeNames([ + 'photo_url' => __('Photo'), + 'emails.*' => __('Email'), + ]); + + // Photo + $validator->after(function ($validator) use ($customer, $request) { + if ($request->hasFile('photo_url')) { + $path_url = $customer->savePhoto($request->file('photo_url')->getRealPath(), $request->file('photo_url')->getMimeType()); + + if ($path_url) { + $customer->photo_url = $path_url; + } else { + $validator->errors()->add('photo_url', __('Error occurred processing the image. Make sure that PHP GD extension is enabled.')); + } + } + }); + + if ($validator->fails()) { + return redirect()->route('customers.update', ['id' => $id]) + ->withErrors($validator) + ->withInput(); + } + + $new_emails = []; + $new_emails_change_customer = []; + $removed_emails = []; + + // Detect new emails added + $customer_emails = $customer->emails()->pluck('email')->toArray(); + foreach ($request->emails as $email) { + if (!in_array($email, $customer_emails)) { + $new_emails[] = $email; + } + } + + // If new email belongs to another customer, let user know about it in the flash message + foreach ($new_emails as $new_email) { + $email = Email::where('email', $new_email)->first(); + if ($email && $email->customer) { + // If customer whose email is removed does not have first name and other emails + // we have to create first name for this customer + if (!$email->customer->first_name && count($email->customer->emails) == 1) { + if ($request->first_name) { + $email->customer->first_name = $request->first_name; + } elseif ($customer->first_name) { + $email->customer->first_name = $customer->first_name; + } else { + $email->customer->first_name = mb_ucfirst($email->getNameFromEmail()); + } + $email->customer->save(); + } + + $flash_message .= __('Email :tag_email_begin:email:tag_email_end has been moved from another customer: :a_begin:customer:a_end.', [ + 'email' => $email->email, + 'tag_email_begin' => '', + 'tag_email_end' => '', + 'customer' => $email->customer->getFullName(), + 'a_begin' => '', + 'a_end' => '', + ]).' '; + + $new_emails_change_customer[] = $email; + } + } + + // Detect removed emails + foreach ($customer_emails as $email) { + if (!in_array($email, $request->emails)) { + $removed_emails[] = $email; + } + } + + $request_data = $request->all(); + + if (isset($request_data['photo_url'])) { + unset($request_data['photo_url']); + } + + $customer->setData($request_data); + // Websites + // if (!empty($request->websites)) { + // $customer->setWebsites($request->websites); + // } + $customer->save(); + + $customer->syncEmails($request->emails); + + // Update customer_id in all conversations added to the current customer. + foreach ($new_emails_change_customer as $new_email) { + if ($new_email->customer_id) { + $conversations_to_change_customer = Conversation::where('customer_id', $new_email->customer_id)->get(); + } else { + // This does not work for phone conversations. + $conversations_to_change_customer = Conversation::where('customer_email', $new_email->email)->get(); + } + foreach ($conversations_to_change_customer as $conversation) { + // We have to pass user to create line item and let others know that customer has changed. + // Conversation may be even in other mailbox where user does not have an access. + $conversation->changeCustomer($new_email->email, $customer, auth()->user()); + } + } + + // Update customer in conversations for emails removed from current customer. + foreach ($removed_emails as $removed_email) { + $email = Email::where('email', $removed_email)->first(); + if ($email) { + $conversations = Conversation::where('customer_email', $email->email)->get(); + foreach ($conversations as $conversation) { + $conversation->changeCustomer($email->email, $email->customer, auth()->user()); + } + } + } + + \Eventy::action('customer.updated', $customer); + + $flash_message = __('Customer saved successfully.').' '.$flash_message; + \Session::flash('flash_success_unescaped', $flash_message); + + \Session::flash('customer.updated', 1); + + return redirect()->route('customers.update', ['id' => $id]); + } + + /** + * User mailboxes. + */ + public function permissions($id) + { + $user = User::findOrFail($id); + $this->authorize('update', $user); + + $mailboxes = Mailbox::all(); + + return view('users/permissions', ['user' => $user, 'mailboxes' => $mailboxes, 'user_mailboxes' => $user->mailboxes]); + } + + /** + * Save user permissions. + * + * @param int $id + * @param \Illuminate\Http\Request $request + */ + public function permissionsSave($id, Request $request) + { + $user = User::findOrFail($id); + $this->authorize('update', $user); + + $user->mailboxes()->sync($request->mailboxes ?: []); + + \Session::flash('flash_success', __('Permissions saved successfully')); + + return redirect()->route('users.permissions', ['id' => $id]); + } + + /** + * View customer conversations. + * + * @param intg $id + */ + public function conversations($id) + { + $customer = Customer::findOrFail($id); + + $conversations = $customer->conversations() + ->where('customer_id', $customer->id) + ->whereIn('mailbox_id', auth()->user()->mailboxesIdsCanView()) + ->orderBy('created_at', 'desc') + ->paginate(Conversation::DEFAULT_LIST_SIZE); + + return view('customers/conversations', [ + 'customer' => $customer, + 'conversations' => $conversations, + ]); + } + + /** + * Customers ajax search. + */ + public function ajaxSearch(Request $request) + { + $response = [ + 'results' => [], + 'pagination' => ['more' => false], + ]; + + $q = $request->q; + + $join_emails = false; + if ($request->search_by == 'all' || $request->search_by == 'email' || $request->exclude_email) { + $join_emails = true; + } + + $select_list = ['customers.id', 'first_name', 'last_name']; + if ($join_emails) { + $select_list[] = 'emails.email'; + } + if ($request->show_fields == 'phone') { + $select_list[] = 'phones'; + } + $customers_query = Customer::select($select_list); + + if ($join_emails) { + if ($request->allow_non_emails) { + $customers_query->leftJoin('emails', 'customers.id', '=', 'emails.customer_id'); + } else { + $customers_query->join('emails', 'customers.id', '=', 'emails.customer_id'); + } + } + + if ($request->search_by == 'all' || $request->search_by == 'email') { + $customers_query->where('emails.email', 'like', '%'.$q.'%'); + } + if ($request->exclude_email) { + $customers_query->where('emails.email', '<>', $request->exclude_email); + } + if ($request->search_by == 'all' || $request->search_by == 'name') { + $customers_query->orWhere('first_name', 'like', '%'.$q.'%') + ->orWhere('last_name', 'like', '%'.$q.'%'); + } + if ($request->search_by == 'phone') { + $customers_query->where('customers.phones', 'like', '%'.$q.'%'); + } + + $customers = $customers_query->paginate(20); + + foreach ($customers as $customer) { + $id = ''; + $text = ''; + + if ($request->show_fields != 'all') { + switch ($request->show_fields) { + case 'email': + $text = $customer->email; + break; + case 'name': + $text = $customer->getFullName(); + break; + case 'phone': + // Get phone which matches + $phones = $customer->getPhones(); + foreach ($phones as $phone) { + if (strstr($phone['value'], $q)) { + $text = $phone['value']; + if ($customer->getFullName()) { + $text .= ' — '.$customer->getFullName(); + } + $id = $phone['value']; + break; + } + } + break; + default: + $text = $customer->getNameAndEmail(); + break; + } + } else { + $text = $customer->getNameAndEmail(); + } + + if (!$id) { + if (!empty($request->use_id)) { + $id = $customer->id; + } else { + $id = $customer->email; + } + } + $response['results'][] = [ + 'id' => $id, + 'text' => $text, + ]; + } + + $response['pagination']['more'] = $customers->hasMorePages(); + + return \Response::json($response); + } + + /** + * Ajax controller. + */ + public function ajax(Request $request) + { + $response = [ + 'status' => 'error', + 'msg' => '', // this is error message + ]; + + $user = auth()->user(); + + switch ($request->action) { + + // Change conversation user + case 'create': + // First name or email must be specified + $validator = Validator::make($request->all(), [ + 'first_name' => 'required|string|max:255', + 'last_name' => 'nullable|string|max:255', + 'email' => 'required|email|unique:emails,email', + ]); + + if ($validator->fails()) { + foreach ($validator->errors()->getMessages()as $errors) { + foreach ($errors as $field => $message) { + $response['msg'] .= $message.' '; + } + } + } + + if (!$response['msg']) { + + $customer = Customer::create($request->email, $request->all()); + if ($customer) { + $response['email'] = $request->email; + $response['status'] = 'success'; + } + } + break; + + // Conversations navigation + case 'customers_pagination': + + $customers = app('App\Http\Controllers\ConversationsController')->searchCustomers($request, $user); + + $response['status'] = 'success'; + + $response['html'] = view('customers/partials/customers_table', [ + 'customers' => $customers, + ])->render(); + break; + + default: + $response['msg'] = 'Unknown action'; + break; + } + + if ($response['status'] == 'error' && empty($response['msg'])) { + $response['msg'] = 'Unknown error occurred'; + } + + return \Response::json($response); + } +} diff --git a/freescout-dist/app/Http/Controllers/MailboxesController.php b/freescout-dist/app/Http/Controllers/MailboxesController.php new file mode 100755 index 0000000..c85cc9b --- /dev/null +++ b/freescout-dist/app/Http/Controllers/MailboxesController.php @@ -0,0 +1,918 @@ +middleware('auth'); + } + + /** + * Mailboxes list. + */ + public function mailboxes() + { + $user = auth()->user(); + + $mailboxes = $user->mailboxesCanView(); + + if (!\Eventy::filter('user.can_view_mailbox_menu', false, $user)) { + foreach ($mailboxes as $i => $mailbox) { + if (!$user->canManageMailbox($mailbox->id)) { + $mailboxes->forget($i); + } + } + } + + return view('mailboxes/mailboxes', ['mailboxes' => $mailboxes]); + } + + /** + * New mailbox. + */ + public function create() + { + $this->authorize('create', 'App\Mailbox'); + + $users = User::nonDeleted()->where('role', '!=', User::ROLE_ADMIN)->get(); + + return view('mailboxes/create', ['users' => $users]); + } + + /** + * Create new mailbox. + * + * @param \Illuminate\Http\Request $request + */ + public function createSave(Request $request) + { + $invalid = false; + + $this->authorize('create', 'App\Mailbox'); + + $validator = Validator::make($request->all(), [ + 'email' => 'required|string|email|max:128|unique:mailboxes', + 'name' => 'required|string|max:40', + ]); + + // //event(new Registered($user = $this->create($request->all()))); + + if (Mailbox::userEmailExists($request->email)) { + $invalid = true; + $validator->errors()->add('email', __('There is a user with such email. Users and mailboxes can not have the same email addresses.')); + } + + if ($invalid || $validator->fails()) { + return redirect()->route('mailboxes.create') + ->withErrors($validator) + ->withInput(); + } + + $mailbox = new Mailbox(); + $mailbox->fill($request->all()); + + $mailbox->save(); + + $mailbox->users()->sync($request->users ?: []); + $mailbox->syncPersonalFolders($request->users); + + \Session::flash('flash_success_floating', __('Mailbox created successfully')); + + return redirect()->route('mailboxes.update', ['id' => $mailbox->id]); + } + + /** + * Edit mailbox. + */ + public function update($id) + { + $mailbox = Mailbox::findOrFail($id); + $user = auth()->user(); + if (!$user->can('updateSettings', $mailbox) && !$user->can('updateEmailSignature', $mailbox)) { + $accessible_route = ''; + + $mailbox_settings = $user->mailboxSettings($mailbox->id); + + if (!is_array($mailbox_settings->access)) { + $access_permissions = json_decode($mailbox_settings->access ?? ''); + } else { + $access_permissions = $mailbox_settings->access; + } + + if ($access_permissions && is_array($access_permissions)) { + foreach ($access_permissions as $perm) { + $accessible_route = Mailbox::getAccessPermissionRoute($perm); + if ($accessible_route) { + break; + } + } + } + if (!$accessible_route) { + $accessible_route = \Eventy::filter('mailbox.accessible_settings_route', '', auth()->user(), $mailbox); + } + if ($accessible_route) { + return redirect()->route($accessible_route, ['id' => $mailbox->id]); + } else { + \Helper::denyAccess(); + } + } + + $user = auth()->user(); + $mailbox_user = $user->mailboxesWithSettings()->where('mailbox_id', $id)->first(); + if (!$mailbox_user && $user->isAdmin()) { + // Admin may not be connected to the mailbox yet + $user->mailboxes()->attach($id); + $mailbox_user = $user->mailboxesWithSettings()->where('mailbox_id', $id)->first(); + } + + //$mailboxes = Mailbox::all()->except($id); + + return view('mailboxes/update', ['mailbox' => $mailbox, 'mailbox_user' => $mailbox_user, 'flashes' => $this->mailboxActiveWarning($mailbox)]); + } + + /** + * Save mailbox. + * + * @param int $id + * @param \Illuminate\Http\Request $request + */ + public function updateSave($id, Request $request) + { + $invalid = false; + $mailbox = Mailbox::findOrFail($id); + + $user = auth()->user(); + + if (!$user->can('updateSettings', $mailbox) && !$user->can('updateEmailSignature', $mailbox)) { + \Helper::denyAccess(); + } + + if ($user->can('updateSettings', $mailbox)) { + + // Checkboxes + $request->merge([ + 'aliases_reply' => ($request->filled('aliases_reply') ?? false), + ]); + + // if not admin, the text only fields don't pass so spike them into the request. + if (!auth()->user()->isAdmin()) { + $request->merge([ + 'name' => $mailbox->name, + 'email' => $mailbox->email + ]); + } + + $validator = Validator::make($request->all(), [ + 'name' => 'required|string|max:40', + 'email' => 'required|string|email|max:128|unique:mailboxes,email,'.$id, + 'aliases' => 'nullable|string|max:255', + 'from_name' => 'required|integer', + 'from_name_custom' => 'nullable|string|max:128', + 'ticket_status' => 'required|integer', + 'template' => 'required|integer', + 'ticket_assignee' => 'required|integer', + ]); + + //event(new Registered($user = $this->create($request->all()))); + if (Mailbox::userEmailExists($request->email)) { + $invalid = true; + $validator->errors()->add('email', __('There is a user with such email. Users and mailboxes can not have the same email addresses.')); + } + + $validator = \Eventy::filter('mailbox.settings_validator', $validator, $mailbox, $request); + + if ($invalid || count($validator->errors()) || $validator->fails()) { + return redirect()->route('mailboxes.update', ['id' => $id]) + ->withErrors($validator) + ->withInput(); + } + } + + if ($user->can('updateEmailSignature', $mailbox)) { + $validator = Validator::make($request->all(), [ + 'signature' => 'nullable|string', + ]); + + if ($validator->fails()) { + return redirect()->route('mailboxes.email_signature', ['id' => $id]) + ->withErrors($validator) + ->withInput(); + } + } + + \Eventy::action('mailbox.settings_before_save', $mailbox, $request); + + $mailbox->fill($request->all()); + + $mailbox->save(); + + \Session::flash('flash_success_floating', __('Mailbox settings saved')); + + return redirect()->route('mailboxes.update', ['id' => $id]); + } + + /** + * Mailbox permissions. + */ + public function permissions($id) + { + $mailbox = Mailbox::findOrFail($id); + + $this->authorize('updatePermissions', $mailbox); + + $users = User::nonDeleted()->where('role', '!=', User::ROLE_ADMIN)->get(); + $users = User::sortUsers($users); + + $managers = User::nonDeleted() + ->select(['users.*', 'mailbox_user.hide', 'mailbox_user.access']) + ->leftJoin('mailbox_user', function ($join) use ($mailbox) { + $join->on('mailbox_user.user_id', '=', 'users.id'); + $join->where('mailbox_user.mailbox_id', $mailbox->id); + })->get(); + $managers = User::sortUsers($managers); + + return view('mailboxes/permissions', [ + 'mailbox' => $mailbox, + 'users' => $users, + 'managers' => $managers, + 'mailbox_users' => $mailbox->users, + ]); + } + + /** + * Save mailbox permissions. + * + * @param int $id + * @param \Illuminate\Http\Request $request + */ + public function permissionsSave($id, Request $request) + { + $mailbox = Mailbox::findOrFail($id); + $this->authorize('updatePermissions', $mailbox); + + $user = auth()->user(); + + $mailbox->users()->sync(\Eventy::filter('mailbox.permission_users', $request->users, $id) ?: []); + $mailbox->syncPersonalFolders($request->users); + + // Save admins settings. + $admins = User::nonDeleted()->where('role', User::ROLE_ADMIN)->get(); + foreach ($admins as $admin) { + $mailbox_user = $admin->mailboxesWithSettings()->where('mailbox_id', $id)->first(); + if (!$mailbox_user) { + // Admin may not be connected to the mailbox yet + $admin->mailboxes()->attach($id); + $mailbox_user = $admin->mailboxesWithSettings()->where('mailbox_id', $id)->first(); + } + $mailbox_user->settings->hide = (isset($request->managers[$admin->id]['hide']) ? (int)$request->managers[$admin->id]['hide'] : false); + $mailbox_user->settings->save(); + } + + // Sets the mailbox_user.access array + $mailbox_users = $mailbox->users; + foreach ($mailbox_users as $mailbox_user) { + $access = []; + $mailbox_with_settings = $mailbox_user->mailboxesWithSettings()->where('mailbox_id', $id)->first(); + + foreach (Mailbox::$access_permissions as $perm) { + if (!empty($request->managers[$mailbox_user->id]['access'][$perm])) { + $access[] = $request->managers[$mailbox_user->id]['access'][$perm]; + } + } + + if ($user->id == $mailbox_user->id && !$user->isAdmin()) { + // User with Permission priv's can't edit their own additional priv's. + } else { + $mailbox_with_settings->settings->access = json_encode($access); + } + $mailbox_with_settings->settings->hide = (isset($request->managers[$mailbox_user->id]['hide']) ? (int)$request->managers[$mailbox_user->id]['hide'] : false); + $mailbox_with_settings->settings->save(); + } + + \Session::flash('flash_success_floating', __('Mailbox permissions saved!')); + + return redirect()->route('mailboxes.permissions', ['id' => $id]); + } + + /** + * Mailbox connection settings. + */ + public function connectionOutgoing($id) + { + $mailbox = Mailbox::findOrFail($id); + $this->authorize('admin', $mailbox); + + return view('mailboxes/connection', ['mailbox' => $mailbox, 'sendmail_path' => ini_get('sendmail_path'), 'flashes' => $this->mailboxActiveWarning($mailbox)]); + } + + /** + * Save mailbox connection settings. + */ + public function connectionOutgoingSave($id, Request $request) + { + $mailbox = Mailbox::findOrFail($id); + $this->authorize('admin', $mailbox); + + if ($request->out_method == Mailbox::OUT_METHOD_SMTP) { + $validator = Validator::make($request->all(), [ + 'out_server' => 'required|string|max:255', + 'out_port' => 'required|integer', + 'out_username' => 'nullable|string|max:100', + 'out_password' => 'nullable|string|max:255', + 'out_encryption' => 'required|integer', + ]); + + if ($validator->fails()) { + return redirect()->route('mailboxes.connection', ['id' => $id]) + ->withErrors($validator) + ->withInput(); + } + } + + // Do not save dummy password. + if (preg_match("/^\*+$/", $request->out_password ?? '')) { + $params = $request->except(['out_password']); + } else { + $params = $request->all(); + } + $mailbox->fill($params); + $mailbox->save(); + + if (!empty($request->send_test_to)) { + \Option::set('send_test_to', $request->send_test_to); + } + + // Sometimes background job continues to use old connection settings. + \Helper::queueWorkerRestart(); + + \Session::flash('flash_success_floating', __('Connection settings saved!')); + + return redirect()->route('mailboxes.connection', ['id' => $id]); + } + + /** + * Mailbox incoming settings. + */ + public function connectionIncoming($id, Request $request) + { + $mailbox = Mailbox::findOrFail($id); + $this->authorize('admin', $mailbox); + + $fields = [ + 'in_server' => $mailbox->in_server ?? '', + 'in_port' => $mailbox->in_port ?? '', + 'in_username' => $mailbox->in_username ?? '', + 'in_password' => $mailbox->in_password ?? '', + ]; + + $validator = Validator::make($fields, [ + 'in_server' => 'required', + 'in_port' => 'required', + 'in_username' => 'required', + 'in_password' => 'required', + ]); + + return view('mailboxes/connection_incoming', ['mailbox' => $mailbox, 'flashes' => $this->mailboxActiveWarning($mailbox)])->withErrors($validator); + } + + /** + * Save mailbox connection settings. + */ + public function connectionIncomingSave($id, Request $request) + { + $mailbox = Mailbox::findOrFail($id); + $this->authorize('admin', $mailbox); + + // $validator = Validator::make($request->all(), [ + // 'in_server' => 'nullable|string|max:255', + // 'in_port' => 'nullable|integer', + // 'in_username' => 'nullable|string|max:100', + // 'in_password' => 'nullable|string|max:255', + // ]); + + // if ($validator->fails()) { + // return redirect()->route('mailboxes.connection.incoming', ['id' => $id]) + // ->withErrors($validator) + // ->withInput(); + // } + + // Checkboxes + $request->merge([ + 'in_validate_cert' => ($request->filled('in_validate_cert') ?? false), + ]); + + // Do not save dummy password. + if (preg_match("/^\*+$/", $request->in_password ?? '')) { + $params = $request->except(['in_password']); + } else { + $params = $request->all(); + } + + \Eventy::action('mailbox.incoming_settings_before_save', $mailbox, $request); + + $mailbox->fill($params); + + // Save IMAP Folders. + // Save all custom folders except INBOX. + $in_imap_folders = []; + if (is_array($request->in_imap_folders)) { + foreach ($request->in_imap_folders as $imap_folder) { + $in_imap_folders[] = $imap_folder; + } + } + $mailbox->setInImapFolders($in_imap_folders); + + $mailbox->save(); + + \Session::flash('flash_success_floating', __('Connection settings saved!')); + + return redirect()->route('mailboxes.connection.incoming', ['id' => $id]); + } + + /** + * View mailbox. + */ + public function view(Request $request, $id, $folder_id = null) + { + $user = auth()->user(); + + $mailbox = Mailbox::findOrFailWithSettings($id, $user->id); + $this->authorize('viewCached', $mailbox); + + $folders = $mailbox->getAssesibleFolders(); + + $folder = null; + if (!empty($folder_id)) { + $folder = $folders->filter(function ($item) use ($folder_id) { + return $item->id == $folder_id; + })->first(); + } + // By default we display Unassigned folder + if (empty($folder)) { + $folder = $folders->filter(function ($item) { + return $item->type == Folder::TYPE_UNASSIGNED; + })->first(); + } + + $this->authorize('view', $folder); + + $query_conversations = Conversation::getQueryByFolder($folder, $user->id); + $conversations = $folder->queryAddOrderBy($query_conversations)->paginate( + Conversation::DEFAULT_LIST_SIZE, ['*'], 'page', $request->get('page') + ); + + return view('mailboxes/view', [ + 'mailbox' => $mailbox, + 'folders' => $folders, + 'folder' => $folder, + 'conversations' => $conversations, + ]); + } + + private function mailboxActiveWarning($mailbox) + { + $flashes = []; + + if ($mailbox && \Auth::user()->can('admin', $mailbox)) { + if (Route::currentRouteName() != 'mailboxes.connection' && !$mailbox->isOutActive()) { + $flashes[] = [ + 'type' => 'warning', + 'text' => __('Sending emails need to be configured for the mailbox in order to send emails to customers and support agents').' ('.__('Connection Settings').' » '.__('Sending Emails').')', + 'unescaped' => true, + ]; + } + if (Route::currentRouteName() != 'mailboxes.connection.incoming' && !$mailbox->isInActive()) { + $flashes[] = [ + 'type' => 'warning', + 'text' => __('Receiving emails need to be configured for the mailbox in order to fetch emails from your support email address').' ('.__('Connection Settings').' » '.__('Receiving Emails').')', + 'unescaped' => true, + ]; + } + } + + return $flashes; + } + + /** + * Auto reply settings. + */ + public function autoReply($id) + { + $mailbox = Mailbox::findOrFail($id); + $this->authorize('updateAutoReply', $mailbox); + + if (!$mailbox->auto_reply_subject) { + $mailbox->auto_reply_subject = 'Re: {%subject%}'; + } + + return view('mailboxes/auto_reply', [ + 'mailbox' => $mailbox, + ]); + } + + /** + * Save auto reply settings. + */ + public function autoReplySave($id, Request $request) + { + $mailbox = Mailbox::findOrFail($id); + +// $this->authorize('update', $mailbox); + $this->authorize('updateAutoReply', $mailbox); + + $request->merge([ + 'auto_reply_enabled' => ($request->filled('auto_reply_enabled') ?? false), + ]); + + if ($request->auto_reply_enabled) { + $post = $request->all(); + $post['auto_reply_message'] = strip_tags($post['auto_reply_message']); + $validator = Validator::make($post, [ + 'auto_reply_subject' => 'required|string|max:128', + 'auto_reply_message' => 'required|string', + ]); + $validator->setAttributeNames([ + 'auto_reply_subject' => __('Subject'), + 'auto_reply_message' => __('Message'), + ]); + + if ($validator->fails()) { + return redirect()->route('mailboxes.auto_reply', ['id' => $id]) + ->withErrors($validator) + ->withInput(); + } + } + + $mailbox->fill($request->all()); + + $mailbox->save(); + + \Session::flash('flash_success_floating', __('Auto Reply status saved')); + + return redirect()->route('mailboxes.auto_reply', ['id' => $id]); + } + + /** + * Auto reply settings. + */ + public function emailSignature($id) + { + $mailbox = Mailbox::findOrFail($id); + $this->authorize('updateAutoReply', $mailbox); + + if (!$mailbox->auto_reply_subject) { + $mailbox->auto_reply_subject = 'Re: {%subject%}'; + } + + return view('mailboxes/email_signature', [ + 'mailbox' => $mailbox, + ]); + } + + + /** + * Users ajax controller. + */ + public function ajax(Request $request) + { + $response = [ + 'status' => 'error', + 'msg' => '', // this is error message + ]; + + $user = auth()->user(); + + switch ($request->action) { + + // Test sending emails from mailbox + case 'send_test': + $mailbox = Mailbox::find($request->mailbox_id); + + if (!$mailbox) { + $response['msg'] = __('Mailbox not found'); + } elseif (!$user->can('admin', $mailbox)) { + $response['msg'] = __('Not enough permissions'); + } elseif (empty($request->to)) { + $response['msg'] = __('Please specify recipient of the test email'); + } + + // Check if outgoing port is open. + if (!$response['msg'] && $mailbox->out_method == Mailbox::OUT_METHOD_SMTP) { + $test_result = \Helper::checkPort($mailbox->out_server, $mailbox->out_port); + if (!$test_result) { + $response['msg'] = __(':host is not available on :port port. Make sure that :host address is correct and that outgoing port :port on YOUR server is open.', ['host' => ''.$mailbox->out_server.'', 'port' => ''.$mailbox->out_port.'']); + } + } + + if (!$response['msg']) { + $test_result = false; + + try { + $test_result = \App\Misc\Mail::sendTestMail($request->to, $mailbox); + } catch (\Exception $e) { + $response['msg'] = $e->getMessage(); + } + + if (!$test_result && !$response['msg']) { + $response['msg'] = __('Error occurred sending email. Please check your mail server logs for more details.'); + } + } + + if (!$response['msg']) { + $response['status'] = 'success'; + } + + // Remember email address + if (!empty($request->to)) { + \App\Option::set('send_test_to', $request->to); + } + break; + + // Test sending emails from mailbox + case 'fetch_test': + $mailbox = Mailbox::find($request->mailbox_id); + + if (!$mailbox) { + $response['msg'] = __('Mailbox not found'); + } elseif (!$user->can('admin', $mailbox)) { + $response['msg'] = __('Not enough permissions'); + } + + $response = \Eventy::filter('mailbox.fetch_test', $response, $mailbox); + + $tested = (isset($response['tested']) && $response['tested'] === true); + + // Check if outgoing port is open. + if (!$response['msg'] && !$tested) { + $test_result = \Helper::checkPort($mailbox->in_server, $mailbox->in_port); + if (!$test_result) { + $response['msg'] = __(':host is not available on :port port. Make sure that :host address is correct and that outgoing port :port on YOUR server is open.', ['host' => ''.$mailbox->in_server.'', 'port' => ''.$mailbox->in_port.'']); + } + } + + if (!$response['msg'] && !$tested) { + $test_result = false; + + try { + $test_result = \MailHelper::fetchTest($mailbox); + } catch (\Exception $e) { + $response['msg'] = $e->getMessage(); + } + + if (!$test_result && !$response['msg']) { + $response['msg'] = __('Error occurred connecting to the server'); + } + } + + if (!$response['msg'] && !$tested) { + $response['status'] = 'success'; + } + break; + + // Retrieve a list of available IMAP folders from server. + case 'imap_folders': + $mailbox = Mailbox::find($request->mailbox_id); + + if (!$mailbox) { + $response['msg'] = __('Mailbox not found'); + } elseif (!$user->can('admin', $mailbox)) { + $response['msg'] = __('Not enough permissions'); + } + + $response['folders'] = []; + + if (!$response['msg']) { + + try { + $client = \MailHelper::getMailboxClient($mailbox); + $client->connect(); + + $imap_folders = $client->getFolders(); + + if (count($imap_folders)) { + foreach ($imap_folders as $imap_folder) { + if (!empty($imap_folder->name)) { + $response['folders'][] = $imap_folder->name; + } + // Maybe we need a recursion here. + if (!empty($imap_folder->children)) { + foreach ($imap_folder->children as $child_imap_folder) { + // Old library. + if (!empty($child_imap_folder->fullName)) { + $response['folders'][] = $child_imap_folder->fullName; + } + // New library. + if (!empty($child_imap_folder->full_name)) { + $response['folders'][] = $child_imap_folder->full_name; + } + } + } + } + } + + if (count($response['folders'])) { + $response['msg_success'] = __('IMAP folders retrieved: '.implode(', ', $response['folders'])); + } else { + $response['msg_success'] = __('Connected, but no IMAP folders found'); + } + + } catch (\Exception $e) { + $response['msg'] = $e->getMessage(); + } + } + + if (!$response['msg']) { + $response['status'] = 'success'; + } + break; + + // Delete mailbox + case 'delete_mailbox': + $mailbox = Mailbox::find($request->mailbox_id); + + if (!$mailbox) { + $response['msg'] = __('Mailbox not found'); + } elseif (!$user->can('admin', $mailbox)) { + $response['msg'] = __('Not enough permissions'); + } elseif (!$user->isDummyPassword() && !Hash::check($request->password ?? '', $user->password)) { + $response['msg'] = __('Please double check your password, and try again'); + } + + if (!$response['msg']) { + + // Remove threads and conversations. + $conversation_ids = $mailbox->conversations()->pluck('id')->toArray(); + + for ($i=0; $i < ceil(count($conversation_ids) / \Helper::IN_LIMIT); $i++) { + $slice_ids = array_slice($conversation_ids, $i*\Helper::IN_LIMIT, \Helper::IN_LIMIT); + Thread::whereIn('conversation_id', $slice_ids)->delete(); + } + + $mailbox->conversations()->delete(); + $mailbox->users()->sync([]); + $mailbox->folders()->delete(); + // Maybe remove notifications on events in this mailbox? + + $mailbox->delete(); + + \Session::flash('flash_success_floating', __('Mailbox deleted')); + + $response['status'] = 'success'; + } + break; + + // Mute notifications + case 'mute': + $mailbox = Mailbox::find($request->mailbox_id); + + if (!$mailbox) { + $response['msg'] = __('Mailbox not found'); + } + + if (!$response['msg']) { + + $mailbox_user = $user->mailboxesWithSettings()->where('mailbox_id', $mailbox->id)->first(); + if (!$mailbox_user) { + // User may not be connected to the mailbox yet + $user->mailboxes()->attach($mailbox->id); + $mailbox_user = $user->mailboxesWithSettings()->where('mailbox_id', $mailbox->id)->first(); + } + $mailbox_user->settings->mute = (bool)$request->mute; + $mailbox_user->settings->save(); + + $response['status'] = 'success'; + } + break; + + default: + $response['msg'] = 'Unknown action'; + break; + } + + if ($response['status'] == 'error' && empty($response['msg'])) { + $response['msg'] = 'Unknown error occurred'; + } + + return \Response::json($response); + } + + public function oauth(Request $request) + { + $mailbox_id = $request->id ?? ''; + $provider = $request->provider ?? ''; + + $state_data = []; + if (!empty($request->state)) { + $state_data = json_decode($request->state, true); + if (!empty($state_data['mailbox_id'])) { + $mailbox_id = $state_data['mailbox_id']; + } + if (!empty($state_data['provider'])) { + $provider = $state_data['provider']; + } + } + + // MS Exchange. + if (!empty($request->error) && $request->error == 'invalid_request' && !empty($request->error_description)) { + return htmlspecialchars($request->error_description); + } + + if (empty($provider)) { + return 'Invalid oAuth Provider'; + } + + $mailbox = Mailbox::findOrFail($mailbox_id); + $this->authorize('admin', $mailbox); + + if (empty($mailbox)) { + return __('Mailbox not found').': '.$mailbox_id; + } + if (empty($mailbox->in_username)) { + return 'Enter oAuth Client ID as Username and save mailbox settings'; + } + if (empty($mailbox->in_password)) { + return 'Enter oAuth Client Secret as Password and save mailbox settings'; + } + + $session_data = []; + if (\Session::get('mailbox_oauth_'.$provider.'_'.$mailbox_id)) { + $session_data = \Session::get('mailbox_oauth_'.$provider.'_'.$mailbox_id); + } + + if (empty($request->code)) { + $state = [ + 'provider' => $provider, + 'mailbox_id' => $mailbox_id, + 'state' => crc32($mailbox->in_username.$mailbox->in_password), + ]; + $url = \MailHelper::oauthGetAuthorizationUrl(\MailHelper::OAUTH_PROVIDER_MICROSOFT, [ + 'state' => json_encode($state), + 'client_id' => $mailbox->in_username, + ]); + if ($url) { + \Session::put('mailbox_oauth_'.$provider.'_'.$mailbox_id, $state); + // [ + // 'provider' => $request->provider, + // 'mailbox_id' => $request->mailbox_id, + // 'state' => $provider->getState(), + // ]); + return redirect()->away($url); + } else { + return 'Could not generate authorization URL: check Client ID (Username) and Client Secret (Password)'; + } + + // Check given state against previously stored one to mitigate CSRF attack + } elseif (empty($request->state) || ($state_data['state'] ?? '') !== ($session_data['state'] ?? '')) { + + \Session::forget('mailbox_oauth_'.$provider.'_'.$mailbox_id); + return 'Invalid oAuth state'; + + } else { + + // Try to get an access token (using the authorization code grant) + $token_data = \MailHelper::oauthGetAccessToken(\MailHelper::OAUTH_PROVIDER_MICROSOFT, [ + 'client_id' => $mailbox->in_username, + 'client_secret' => $mailbox->in_password, + 'code' => $request->code, + ]); + + if (!empty($token_data['a_token'])) { + $mailbox->setMetaParam('oauth', $token_data, true); + } elseif (!empty($token_data['error'])) { + return __('Error occurred').': '.htmlspecialchars($token_data['error']); + } + + return redirect()->route('mailboxes.connection.incoming', ['id' => $mailbox_id]); + } + } + + public function oauthDisconnect(Request $request) + { + $mailbox_id = $request->id ?? ''; + $provider = $request->provider ?? ''; + + $mailbox = Mailbox::findOrFail($mailbox_id); + $this->authorize('admin', $mailbox); + + // oAuth Disconnect. + $mailbox->removeMetaParam('oauth', true); + return \MailHelper::oauthDisconnect($provider, route('mailboxes.connection.incoming', ['id' => $mailbox_id])); + } +} diff --git a/freescout-dist/app/Http/Controllers/ModulesController.php b/freescout-dist/app/Http/Controllers/ModulesController.php new file mode 100644 index 0000000..e59d8e7 --- /dev/null +++ b/freescout-dist/app/Http/Controllers/ModulesController.php @@ -0,0 +1,570 @@ +middleware('auth'); + } + + /** + * Modules. + */ + public function modules(Request $request) + { + $installed_modules = []; + $modules_directory = []; + $third_party_modules = []; + $all_modules = []; + $flashes = []; + $updates_available = false; + + $flash = \Cache::get('modules_flash'); + if ($flash) { + if (is_array($flash) && !isset($flash['text'])) { + $flashes = $flash; + } else { + $flashes[] = $flash; + } + \Cache::forget('modules_flash'); + } + + // Get available modules and cache them + if (\Cache::has('modules_directory')) { + $modules_directory = \Cache::get('modules_directory'); + } + + if (!$modules_directory) { + $modules_directory = WpApi::getModules(); + if ($modules_directory && is_array($modules_directory) && count($modules_directory)) { + \Cache::put('modules_directory', $modules_directory, now()->addMinutes(15)); + } + } + + // Get installed modules + \Module::clearCache(); + $modules = \Module::all(); + foreach ($modules as $module) { + $module_data = [ + 'alias' => $module->getAlias(), + 'name' => $module->getName(), + 'description' => $module->getDescription(), + 'version' => $module->get('version'), + 'detailsUrl' => $module->get('detailsUrl'), + 'author' => $module->get('author'), + 'authorUrl' => $module->get('authorUrl'), + 'requiredAppVersion' => $module->get('requiredAppVersion'), + 'requiredPhpExtensions' => $module->get('requiredPhpExtensions'), + 'requiredPhpExtensionsMissing' => \App\Module::getMissingExtensions($module->get('requiredPhpExtensions')), + 'requiredModulesMissing' => \App\Module::getMissingModules($module->get('requiredModules'), $modules), + 'img' => $module->get('img'), + 'active' => $module->active(), //\App\Module::isActive($module->getAlias()), + 'installed' => true, + 'activated' => \App\Module::isLicenseActivated($module->getAlias(), $module->get('authorUrl')), + 'license' => \App\Module::getLicense($module->getAlias()), + // Determined later + 'new_version' => '', + ]; + $module_data = \App\Module::formatModuleData($module_data); + $installed_modules[] = $module_data; + } + + // No need, as we update modules list on each page load + // Clear modules cache if any module has been added or removed + // if (count($modules) != count(Module::getCached())) { + // $this->clearCache(); + // } + + // Prepare directory modules + if (is_array($modules_directory)) { + foreach ($modules_directory as $i_dir => $dir_module) { + + $modules_directory[$i_dir] = \App\Module::formatModuleData($dir_module); + + // Remove modules without aliases + if (empty($dir_module['alias'])) { + unset($modules_directory[$i_dir]); + } + $all_modules[$dir_module['alias']] = $dir_module['name']; + foreach ($installed_modules as $i_installed => $module) { + if ($dir_module['alias'] == $module['alias']) { + // Set image from director + $installed_modules[$i_installed]['img'] = $dir_module['img']; + // Remove installed modules from modules directory. + unset($modules_directory[$i_dir]); + + // Detect if new version is available + if (!empty($dir_module['version']) && version_compare($dir_module['version'], $module['version'], '>')) { + $installed_modules[$i_installed]['new_version'] = $dir_module['version']; + $updates_available = true; + } + + continue 2; + } + } + + if (empty($dir_module['authorUrl']) || !\App\Module::isOfficial($dir_module['authorUrl'])) { + unset($modules_directory[$i_dir]); + continue; + } + + if (!empty($dir_module['requiredPhpExtensions'])) { + $modules_directory[$i_dir]['requiredPhpExtensionsMissing'] = \App\Module::getMissingExtensions($dir_module['requiredPhpExtensions']); + } + $modules_directory[$i_dir]['active'] = \App\Module::isActive($dir_module['alias']); + $modules_directory[$i_dir]['activated'] = false; + + // Do not show third-party modules in Modules Derectory. + if (\App\Module::isThirdParty($dir_module)) { + $third_party_modules[] = $modules_directory[$i_dir]; + unset($modules_directory[$i_dir]); + } + } + } else { + $modules_directory = []; + } + + // Check modules symlinks. Somestimes instead of symlinks folders with files appear. + + $invalid_symlinks = \App\Module::checkSymlinks( + collect($installed_modules)->where('active', true)->pluck('alias')->toArray() + ); + + return view('modules/modules', [ + 'installed_modules' => $installed_modules, + 'modules_directory' => $modules_directory, + 'third_party_modules' => $third_party_modules, + 'flashes' => $flashes, + 'updates_available' => $updates_available, + 'all_modules' => $all_modules, + 'invalid_symlinks' => $invalid_symlinks, + ]); + } + + /** + * Ajax. + */ + public function ajax(Request $request) + { + $response = [ + 'status' => 'error', + 'msg' => '', // this is error message + ]; + + switch ($request->action) { + + case 'install': + case 'activate_license': + $license = $request->license; + $alias = $request->alias; + + if (!$license) { + $response['msg'] = __('Empty license key'); + } + + if (!$response['msg']) { + $params = [ + 'license' => $license, + 'module_alias' => $alias, + 'url' => \App\Module::getAppUrl(), + ]; + $result = WpApi::activateLicense($params); + + if (WpApi::$lastError) { + $response['msg'] = WpApi::$lastError['message']; + } elseif (!empty($result['code']) && !empty($result['message'])) { + $response['msg'] = $result['message']; + } else { + if (!empty($result['status']) && $result['status'] == 'valid') { + if ($request->action == 'install') { + // Download and install module + $license_details = WpApi::getVersion($params); + + if (WpApi::$lastError) { + $response['msg'] = WpApi::$lastError['message']; + } elseif (!empty($license_details['code']) && !empty($license_details['message'])) { + $response['msg'] = $license_details['message']; + } elseif (!empty($license_details['download_link'])) { + // Download module + $module_archive = \Module::getPath().DIRECTORY_SEPARATOR.$alias.'.zip'; + + try { + \Helper::downloadRemoteFile($license_details['download_link'], $module_archive); + } catch (\Exception $e) { + $response['msg'] = $e->getMessage(); + } + + $download_error = false; + if (!file_exists($module_archive)) { + $download_error = true; + } else { + // Extract + try { + \Helper::unzip($module_archive, \Module::getPath()); + } catch (\Exception $e) { + $response['msg'] = $e->getMessage(); + } + // Check if extracted module exists + \Module::clearCache(); + $module = \Module::findByAlias($alias); + if (!$module) { + $download_error = true; + } + } + + // Remove archive + if (file_exists($module_archive)) { + \File::delete($module_archive); + } + + if (!$response['msg'] && !$download_error) { + // Activate license + \App\Module::activateLicense($alias, $license); + + \Session::flash('flash_success_floating', __('Module successfully installed!')); + $response['status'] = 'success'; + } elseif ($download_error) { + $response['reload'] = true; + + if ($response['msg']) { + \Session::flash('flash_error_floating', $response['msg']); + } + + \Session::flash('flash_error_unescaped', __('Error occurred downloading the module. Please :%a_being%download:%a_end% module manually and extract into :folder', ['%a_being%' => '', '%a_end%' => '', 'folder' => ''.\Module::getPath().''])); + } + } else { + $response['msg'] = __('Error occurred. Please try again later.'); + } + } else { + // Just activate license + \App\Module::activateLicense($alias, $license); + + \Session::flash('flash_success_floating', __('License successfully activated!')); + $response['status'] = 'success'; + } + } elseif (!empty($result['error'])) { + $response['msg'] = \App\Module::getErrorMessage($result['error'], $result); + } else { + $response['msg'] = __('Error occurred. Please try again later.'); + } + } + } + break; + + case 'activate': + $alias = $request->alias; + $module = \Module::findByAlias($alias); + + if (!$module) { + $response['msg'] = __('Module not found').': '.$alias; + } + + // Check license + if (!$response['msg']) { + if (!empty($module->get('authorUrl')) && $module->isOfficial()) { + $params = [ + 'license' => $module->getLicense(), + 'module_alias' => $alias, + 'url' => \App\Module::getAppUrl(), + ]; + $license_result = WpApi::checkLicense($params); + + if (!empty($license_result['code']) && !empty($license_result['message'])) { + // Remove remembered license key and deactivate license in DB + \App\Module::deactivateLicense($alias, ''); + + $response['msg'] = $license_result['message']; + } elseif (!empty($license_result['status']) && $license_result['status'] != 'valid' && $license_result['status'] != 'inactive') { + // Remove remembered license key and deactivate license in DB + \App\Module::deactivateLicense($alias, ''); + + switch ($license_result['status']) { + case 'expired': + $response['msg'] = __('License key has expired'); + break; + case 'disabled': + $response['msg'] = __('License key has been revoked'); + break; + case 'inactive': + $response['msg'] = __('License key has not been activated yet'); + case 'site_inactive': + $response['msg'] = __('No activations left for this license key').' ('.__("Use 'Deactivate License' link above to transfer license key from another domain").')'; + break; + } + } elseif (!empty($license_result['status']) && $license_result['status'] == 'inactive') { + // Activate the license. + $result = WpApi::activateLicense($params); + if (WpApi::$lastError) { + $response['msg'] = WpApi::$lastError['message']; + } elseif (!empty($result['code']) && !empty($result['message'])) { + $response['msg'] = $result['message']; + } else { + if (!empty($result['status']) && $result['status'] == 'valid') { + // Success. + } elseif (!empty($result['error'])) { + $response['msg'] = \App\Module::getErrorMessage($result['error'], $result); + } else { + // Some unknown error. Do nothing. + } + } + } + } + } + + if (!$response['msg']) { + \App\Module::setActive($alias, true); + + $outputLog = new BufferedOutput(); + \Artisan::call('freescout:module-install', ['module_alias' => $alias], $outputLog); + $output = $outputLog->fetch(); + + // Get module name + $name = '?'; + if ($module) { + $name = $module->getName(); + } + + $type = 'danger'; + $msg = __('Error occurred activating ":name" module', ['name' => $name]); + if (session('flashes_floating') && is_array(session('flashes_floating'))) { + // If there was any error, module has been deactivated via modules.register_error filter + $msg = ''; + foreach (session('flashes_floating') as $flash) { + $msg .= $flash['text'].' '; + } + } elseif (strstr($output, 'Configuration cached successfully')) { + $type = 'success'; + $msg = __('":name" module successfully activated!', ['name' => $name]); + } else { + // Deactivate the module. + \App\Module::setActive($alias, false); + \Artisan::call('freescout:clear-cache'); + } + + // Check public folder. + if ($module && file_exists($module->getPath().DIRECTORY_SEPARATOR.'Public')) { + $symlink_path = public_path().\Module::getPublicPath($alias); + if (!file_exists($symlink_path)) { + $type = 'danger'; + $msg = 'Error occurred creating a module symlink ('.$symlink_path.'). Please check folder permissions.'; + \App\Module::setActive($alias, false); + \Artisan::call('freescout:clear-cache'); + } + } + + if ($type == 'success') { + // Migrate again, in case migration did not work in the moment the module was activated. + \Artisan::call('migrate', ['--force' => true]); + } + + // \Session::flash does not work after BufferedOutput + $flash = [ + 'text' => ''.$msg.'
'.$output.'
', + 'unescaped' => true, + 'type' => $type, + ]; + \Cache::forever('modules_flash', $flash); + $response['status'] = 'success'; + } + + break; + + case 'deactivate': + $alias = $request->alias; + \App\Module::setActive($alias, false); + + $outputLog = new BufferedOutput(); + \Artisan::call('freescout:clear-cache', [], $outputLog); + $output = $outputLog->fetch(); + + // Get module name + $module = \Module::findByAlias($alias); + $name = '?'; + if ($module) { + $name = $module->getName(); + } + + $type = 'danger'; + $msg = __('Error occurred deactivating :name module', ['name' => $name]); + if (strstr($output, 'Configuration cached successfully')) { + $type = 'success'; + $msg = __('":name" module successfully Deactivated!', ['name' => $name]); + } + + // \Session::flash does not work after BufferedOutput + $flash = [ + 'text' => ''.$msg.'
'.$output.'
', + 'unescaped' => true, + 'type' => $type, + ]; + \Cache::forever('modules_flash', $flash); + $response['status'] = 'success'; + break; + + case 'deactivate_license': + $license = $request->license; + $alias = $request->alias; + + if (!$license) { + $response['msg'] = __('Empty license key'); + } + + if (!$response['msg']) { + $params = [ + 'license' => $license, + 'module_alias' => $alias, + 'url' => (!empty($request->any_url) ? '*' : \App\Module::getAppUrl()), + ]; + $result = WpApi::deactivateLicense($params); + + if (WpApi::$lastError) { + $response['msg'] = WpApi::$lastError['message']; + } elseif (!empty($result['code']) && !empty($result['message'])) { + $response['msg'] = $result['message']; + } else { + if (!empty($result['status']) && $result['status'] == 'success') { + $db_module = \App\Module::getByAlias($alias); + if ($db_module && trim($db_module->license ?? '') == trim($license ?? '')) { + // Remove remembered license key and deactivate license in DB + \App\Module::deactivateLicense($alias, ''); + + // Deactivate module + \App\Module::setActive($alias, false); + \Artisan::call('freescout:clear-cache', []); + } + + // Flash does not work here. + $flash = [ + 'text' => ''.__('License successfully Deactivated!').'', + 'unescaped' => true, + 'type' => 'success', + ]; + \Cache::forever('modules_flash', $flash); + + $response['status'] = 'success'; + } elseif (!empty($result['error'])) { + $response['msg'] = \App\Module::getErrorMessage($result['error'], $result); + } else { + $response['msg'] = __('Error occurred. Please try again later.'); + } + } + } + break; + + case 'delete': + $alias = $request->alias; + + $module = \Module::findByAlias($alias); + + if ($module) { + + //\App\Module::deactivateLicense($alias, $license); + + $module->delete(); + \Session::flash('flash_success_floating', __('Module deleted')); + } else { + $response['msg'] = __('Module not found').': '.$alias; + } + + $response['status'] = 'success'; + break; + + case 'update': + $update_result = \App\Module::updateModule($request->alias); + + if ($update_result['download_error']) { + $response['reload'] = true; + + if ($update_result['msg']) { + \Session::flash('flash_error_floating', $update_result['msg']); + } + + if ($update_result['download_msg']) { + \Session::flash('flash_error_unescaped', $update_result['download_msg']); + } + } + + // Install updated module. + if ($update_result['output'] || $update_result['status']) { + + $type = 'danger'; + $msg = $update_result['msg']; + + if ($update_result['status'] == 'success') { + $type = 'success'; + $msg = $update_result['msg_success']; + } + + // \Session::flash does not work after BufferedOutput + $flash = [ + 'text' => ''.$msg.'
'.$update_result['output'].'
', + 'unescaped' => true, + 'type' => $type, + ]; + \Cache::forever('modules_flash', $flash); + $response['status'] = 'success'; + } + + break; + + case 'update_all': + $update_all_flashes = []; + + foreach ($request->aliases as $alias) { + $update_result = \App\Module::updateModule($alias); + + $type = 'danger'; + $msg = $update_result['msg']; + + if ($update_result['status'] == 'success') { + $type = 'success'; + $msg = $update_result['msg_success']; + } elseif ($update_result['download_msg']) { + $msg .= '
'.$update_result['download_msg']; + } + + $text = ''.$update_result['module_name'].': '.$msg; + if (trim($update_result['output'])) { + $text .= '
'.$update_result['output'].'
'; + } + + // \Session::flash does not work after BufferedOutput + $update_all_flashes[] = [ + 'text' => $text, + 'unescaped' => true, + 'type' => $type, + ]; + } + if ($update_all_flashes) { + \Cache::forever('modules_flash', $update_all_flashes); + } + $response['status'] = 'success'; + + break; + + default: + $response['msg'] = 'Unknown action'; + break; + } + + if ($response['status'] == 'error' && empty($response['msg'])) { + $response['msg'] = 'Unknown error occurred'; + } + + return \Response::json($response); + } +} diff --git a/freescout-dist/app/Http/Controllers/OpenController.php b/freescout-dist/app/Http/Controllers/OpenController.php new file mode 100644 index 0000000..706e5df --- /dev/null +++ b/freescout-dist/app/Http/Controllers/OpenController.php @@ -0,0 +1,228 @@ +user()) { + return redirect()->route('dashboard'); + } + $user = User::where('invite_hash', $hash)->first(); + + if ($user && $user->locale) { + \Helper::setLocale($user->locale); + } + + return view('open/user_setup', ['user' => $user]); + } + + /** + * Save user from invitation. + */ + public function userSetupSave($hash, Request $request) + { + if (auth()->user()) { + return redirect()->route('dashboard'); + } + $user = User::where('invite_hash', $hash)->first(); + + if (!$user) { + abort(404); + } + + $validator = Validator::make($request->all(), [ + 'email' => 'required|string|email|max:100|unique:users,email,'.$user->id, + 'password' => 'required|string|min:8|confirmed', + 'job_title' => 'max:100', + 'phone' => 'max:60', + 'timezone' => 'required|string|max:255', + 'time_format' => 'required', + 'photo_url' => 'nullable|image|mimes:jpeg,png,jpg,gif', + ]); + $validator->setAttributeNames([ + 'photo_url' => __('Photo'), + ]); + + // Photo + $validator->after(function ($validator) use ($user, $request) { + if ($request->hasFile('photo_url')) { + $path_url = $user->savePhoto($request->file('photo_url')); + + if ($path_url) { + $user->photo_url = $path_url; + } else { + $validator->errors()->add('photo_url', __('Error occurred processing the image. Make sure that PHP GD extension is enabled.')); + } + } + }); + + if ($validator->fails()) { + return redirect()->route('user_setup', ['hash' => $hash]) + ->withErrors($validator) + ->withInput(); + } + + $request_data = $request->all(); + // Do not allow user to set his role + if (isset($request_data['role'])) { + unset($request_data['role']); + } + if (isset($request_data['photo_url'])) { + unset($request_data['photo_url']); + } + $user->fill($request_data); + + $user->password = bcrypt($request->password); + + $user->invite_state = User::INVITE_STATE_ACTIVATED; + $user->invite_hash = ''; + + $user = \Eventy::filter('user.setup_save', $user, $request); + $user->save(); + + // Login user + Auth::guard()->login($user); + + \Session::flash('flash_success_floating', __('Welcome to :company_name!', ['company_name' => Option::getCompanyName()])); + + return redirect()->route('dashboard'); + } + + /* + * Set a thread as read by customer + */ + public function setThreadAsRead($conversation_id, $thread_id) + { + $conversation = Conversation::findOrFail($conversation_id); + $thread = Thread::findOrFail($thread_id); + + // We only track the first opening + if (empty($thread->opened_at)) { + $thread->opened_at = date('Y-m-d H:i:s'); + $thread->save(); + \Eventy::action('thread.opened', $thread, $conversation); + } + + // Create a 1x1 ttransparent pixel and return it + $pixel = sprintf('%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c', 71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 255, 0, 192, 192, 192, 0, 0, 0, 33, 249, 4, 1, 0, 0, 0, 0, 44, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 68, 1, 0, 59); + $response = \Response::make($pixel, 200); + $response->header('Content-type', 'image/gif'); + $response->header('Content-Length', 42); + $response->header('Cache-Control', 'private, no-cache, no-cache=Set-Cookie, proxy-revalidate'); + $response->header('Expires', 'Wed, 11 Jan 2000 12:59:00 GMT'); + $response->header('Last-Modified', 'Wed, 11 Jan 2006 12:59:00 GMT'); + $response->header('Pragma', 'no-cache'); + + return $response; + } + + /** + * Download an attachment. + */ + public function downloadAttachment($dir_1, $dir_2, $dir_3, $file_name, Request $request) + { + $id = $request->query('id', ''); + $token = $request->query('token', ''); + $attachment = null; + + // Old attachments can not be requested by id. + if (!$token && $id) { + return \Helper::denyAccess(); + } + + // Get attachment by id. + if ($id) { + $attachment = Attachment::findOrFail($id); + } + + if (!$attachment) { + $attachment = Attachment::where('file_dir', $dir_1.DIRECTORY_SEPARATOR.$dir_2.DIRECTORY_SEPARATOR.$dir_3.DIRECTORY_SEPARATOR) + ->where('file_name', $file_name) + ->firstOrFail(); + } + + // Only allow download if the attachment is public or if the token matches the hash of the contents + if ($token != $attachment->getToken() && (bool)$attachment->public !== true) { + return \Helper::denyAccess(); + } + + $view_attachment = false; + $file_ext = strtolower(pathinfo($attachment->file_name, PATHINFO_EXTENSION)); + + // Some file type should be viewed in the browser instead of downloading. + if (in_array($file_ext, config('app.viewable_attachments'))) { + $view_attachment = true; + } + // If HTML file is renamed into .txt for example it will be shown by the browser as HTML. + if ($view_attachment && $attachment->mime_type) { + $allowed_mime_type = false; + + foreach (config('app.viewable_mime_types') as $mime_type) { + if (preg_match('#'.$mime_type.'#', $attachment->mime_type)) { + $allowed_mime_type = true; + break; + } + } + if (!$allowed_mime_type) { + $view_attachment = false; + } + } + + if (config('app.download_attachments_via') == 'apache') { + // Send using Apache mod_xsendfile. + $response = response(null) + ->header('Content-Type' , $attachment->mime_type) + ->header('X-Sendfile', $attachment->getLocalFilePath()); + + if (!$view_attachment) { + $response->header('Content-Disposition', 'attachment; filename="'.$attachment->file_name.'"'); + } + } elseif (config('app.download_attachments_via') == 'nginx') { + // Send using Nginx. + $response = response(null) + ->header('Content-Type' , $attachment->mime_type) + ->header('X-Accel-Redirect', $attachment->getLocalFilePath(false)); + + if (!$view_attachment) { + $response->header('Content-Disposition', 'attachment; filename="'.$attachment->file_name.'"'); + } + } else { + $response = $attachment->download($view_attachment); + } + + return $response; + } + + /** + * Needed for the mobile app. + */ + // public function mobilePing() + // { + // echo file_get_contents(public_path('installer/css/fontawesome.css')); + // } +} diff --git a/freescout-dist/app/Http/Controllers/SecureController.php b/freescout-dist/app/Http/Controllers/SecureController.php new file mode 100644 index 0000000..793d55a --- /dev/null +++ b/freescout-dist/app/Http/Controllers/SecureController.php @@ -0,0 +1,236 @@ +middleware('auth'); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Http\Response + */ + public function dashboard() + { + $user = auth()->user(); + if (!$user->isAdmin()) { + $mailboxes = $user->mailboxesCanView(); + } else { + $mailboxes = $user->mailboxesCanViewWithSettings(); + } + + // Sort by name. + $mailboxes = \Eventy::filter('dashboard.mailboxes', $mailboxes->sortBy('name')); + + return view('secure/dashboard', ['mailboxes' => $mailboxes]); + } + + /** + * Logs. + * + * @return \Illuminate\Http\Response + */ + public function logs(Request $request) + { + function addCol($cols, $col) + { + if (!in_array($col, $cols)) { + $cols[] = $col; + } + + return $cols; + } + + // No need to check permissions here, as they are checked in routing + + $names = ActivityLog::select('log_name')->distinct()->pluck('log_name')->toArray(); + + $activities = []; + $cols = []; + $page_size = 20; + $name = ''; + + if (!empty($request->name)) { + $activities = ActivityLog::inLog($request->name)->orderBy('created_at', 'desc')->paginate($page_size); + $name = $request->name; + } elseif (count($names)) { + $name = ActivityLog::NAME_OUT_EMAILS; + // $activities = ActivityLog::inLog($names[0])->orderBy('created_at', 'desc')->paginate($page_size); + // $name = $names[0]; + } + + if ($name != ActivityLog::NAME_OUT_EMAILS) { + $logs = []; + $cols = ['date']; + foreach ($activities as $activity) { + $log = []; + $log['date'] = $activity->created_at; + if ($activity->causer) { + if ($activity->causer_type == 'App\User') { + $cols = addCol($cols, 'user'); + $log['user'] = $activity->causer; + } else { + $cols = addCol($cols, 'customer'); + $log['customer'] = $activity->causer; + } + } + $log['event'] = $activity->getEventDescription(); + + $cols = addCol($cols, 'event'); + + foreach ($activity->properties as $property_name => $property_value) { + if (!is_string($property_value)) { + $property_value = json_encode($property_value); + } + $log[$property_name] = $property_value; + $cols = addCol($cols, $property_name); + } + + $logs[] = $log; + } + } else { + // Outgoing emails are displayed from send log + $logs = []; + $cols = [ + 'date', + 'type', + 'email', + 'status', + 'message', + 'user', + 'customer', + ]; + + $activities_query = SendLog::orderBy('created_at', 'desc'); + if ($request->get('thread_id')) { + $activities_query->where('thread_id', $request->get('thread_id')); + } + $activities = $activities_query->paginate($page_size); + + foreach ($activities as $record) { + $conversation = ''; + if ($record->thread_id) { + $conversation = Thread::find($record->thread_id); + } + + $status = $record->getStatusName(); + if ($record->status_message) { + $status .= '. '.$record->status_message; + if ($record->status == SendLog::STATUS_SEND_ERROR) { + $status .= '. Message-ID: '.$record->message_id; + } + } + if ($record->smtp_queue_id) { + $status .= '. SMTP ID: '.$record->smtp_queue_id; + } + + $logs[] = [ + 'date' => $record->created_at, + 'type' => $record->getMailTypeName(), + 'email' => $record->email, + 'status' => $status, + 'message' => $conversation, + 'user' => $record->user, + 'customer' => $record->customer, + ]; + } + } + + array_unshift($names, ActivityLog::NAME_OUT_EMAILS); + array_push($names, ActivityLog::NAME_APP_LOGS); + + if (!in_array($name, $names)) { + $names[] = $name; + } + + return view('secure/logs', [ + 'logs' => $logs, + 'names' => $names, + 'current_name' => $name, + 'cols' => $cols, + 'activities' => $activities, + ]); + } + + /** + * Logs page submitted. + */ + public function logsSubmit(Request $request) + { + // No need to check permissions here, as they are checked in routing + + $name = ''; + if (!empty($request->name)) { + //$activities = ActivityLog::inLog($request->name)->orderBy('created_at', 'desc')->get(); + $name = $request->name; + } elseif (count($names = ActivityLog::select('log_name')->distinct()->get()->pluck('log_name'))) { + $name = ActivityLog::NAME_OUT_EMAILS; + // $activities = ActivityLog::inLog($names[0])->orderBy('created_at', 'desc')->get(); + // $name = $names[0]; + } + + switch ($request->action) { + case 'clean': + if ($name && $name != ActivityLog::NAME_OUT_EMAILS) { + ActivityLog::where('log_name', $name)->delete(); + \Session::flash('flash_success_floating', __('Log successfully cleared')); + } + break; + } + + return redirect()->route('logs', ['name' => $name]); + } + + /** + * Upload files and images. + */ + public function upload(Request $request, $allowed_exts = []) + { + // 'jpg','gif','png' + $response = [ + 'status' => 'error', + 'msg' => '', // this is error message + ]; + + $user = auth()->user(); + + if (!$user) { + $response['msg'] = __('Please login to upload file'); + } + + if (!$request->hasFile('file') || !$request->file('file')->isValid() || !$request->file) { + $response['msg'] = __('Error occurred uploading file'); + } + + if (!$response['msg']) { + + $upload = Helper::uploadFile($request->file, $allowed_exts); + $filename = basename($upload); + + if ($upload) { + $response['status'] = 'success'; + $response['url'] = Helper::uploadedFileUrl($filename); + } else { + $response['msg'] = __('Error occurred uploading file'); + } + } + + return \Response::json($response); + } +} diff --git a/freescout-dist/app/Http/Controllers/SettingsController.php b/freescout-dist/app/Http/Controllers/SettingsController.php new file mode 100644 index 0000000..2ebcb22 --- /dev/null +++ b/freescout-dist/app/Http/Controllers/SettingsController.php @@ -0,0 +1,431 @@ +middleware('auth'); + } + + /** + * General settings. + * + * @return \Illuminate\Http\Response + */ + public function view($section = 'general') + { + $settings = $this->getSectionSettings($section); + + if (!$settings) { + abort(404); + } + + $sections = $this->getSections(); + + $template_vars = [ + 'settings' => $settings, + 'section' => $section, + 'sections' => $this->getSections(), + 'section_name' => $sections[$section]['title'], + ]; + $template_vars = $this->getTemplateVars($section, $template_vars); + + return view('settings/view', $template_vars); + } + + public function getValidator($section) + { + $rules = $this->getSectionParams($section, 'validator_rules'); + + if (!empty($rules)) { + return Validator::make(request()->all(), $rules); + } + } + + public function getTemplateVars($section, $template_vars) + { + $section_vars = $this->getSectionParams($section, 'template_vars'); + + if ($section_vars && is_array($section_vars)) { + return array_merge($template_vars, $section_vars); + } else { + return $template_vars; + } + } + + /** + * Parameters of the sections settings. + * + * If in settings parameter `env` is set, option will be saved into .env file + * instead of DB. + * + * @param [type] $section [description] + * @param string $param [description] + * + * @return [type] [description] + */ + public function getSectionParams($section, $param = '') + { + $params = []; + + switch ($section) { + case 'emails': + $params = [ + 'template_vars' => [ + 'sendmail_path' => ini_get('sendmail_path'), + 'mail_drivers' => [ + 'mail' => __("PHP's mail() function"), + 'sendmail' => __('Sendmail'), + 'smtp' => 'SMTP', + ], + ], + 'validator_rules' => [ + 'settings.mail_from' => 'required|email', + ], + 'settings' => [ + 'fetch_schedule' => [ + 'env' => 'APP_FETCH_SCHEDULE', + ], + 'mail_password' => [ + 'safe_password' => true, + 'encrypt' => true, + ], + // 'use_mail_date_on_fetching' => [ + // 'env' => 'APP_USE_MAIL_DATE_ON_FETCHING', + // ], + ], + ]; + break; + case 'general': + $params = [ + 'settings' => [ + 'custom_number' => [ + 'env' => 'APP_CUSTOM_NUMBER', + ], + 'max_message_size' => [ + 'env' => 'APP_MAX_MESSAGE_SIZE', + ], + 'email_conv_history' => [ + 'env' => 'APP_EMAIL_CONV_HISTORY', + ], + 'email_user_history' => [ + 'env' => 'APP_EMAIL_USER_HISTORY', + ], + 'locale' => [ + 'env' => 'APP_LOCALE', + ], + 'timezone' => [ + 'env' => 'APP_TIMEZONE', + ], + 'user_permissions' => [ + 'env' => 'APP_USER_PERMISSIONS', + 'env_encode' => true, + ], + ], + ]; + break; + case 'alerts': + $subscriptions_defaults = Subscription::getDefaultSubscriptions(); + $subscriptions = array(); + foreach ($subscriptions_defaults as $medium => $subscriptions_for_medium) { + foreach ($subscriptions_defaults[$medium] as $subscription) { + $subscriptions[] = (object) array("medium" => $medium, "event" => $subscription); + } + } + $params = [ + 'template_vars' => [ + 'logs' => \App\ActivityLog::getAvailableLogs(), + 'person' => null, + 'subscriptions' => $subscriptions, + 'mobile_available' => \Eventy::filter('notifications.mobile_available', false), + ], + 'settings' => [ + 'alert_logs' => [ + 'env' => 'APP_ALERT_LOGS', + ], + 'alert_logs_period' => [ + 'env' => 'APP_ALERT_LOGS_PERIOD', + ], + ], + ]; + + // todo: monitor App Logs + foreach ($params['template_vars']['logs'] as $i => $log) { + if ($log == \App\ActivityLog::NAME_APP_LOGS || $log == \App\ActivityLog::NAME_OUT_EMAILS) { + unset($params['template_vars']['logs'][$i]); + } + } + + break; + default: + $params = \Eventy::filter('settings.section_params', $params, $section); + break; + } + + $params = \Eventy::filter('settings.alter_section_params', $params, $section); + + if ($param) { + if (isset($params[$param])) { + return $params[$param]; + } else { + return; + } + } else { + return $params; + } + } + + public function getSectionSettings($section) + { + $settings = []; + + switch ($section) { + case 'general': + $settings = [ + 'company_name' => Option::get('company_name', \Config::get('app.name')), + 'next_ticket' => (Option::get('next_ticket') >= Conversation::max('number') + 1) ? Option::get('next_ticket') : Conversation::max('number') + 1, + 'custom_number' => (int)config('app.custom_number'), + 'user_permissions' => User::getGlobalUserPermissions(), + 'email_branding' => Option::get('email_branding'), + 'open_tracking' => Option::get('open_tracking'), + 'email_conv_history' => config('app.email_conv_history'), + 'max_message_size' => config('app.max_message_size'), + 'email_user_history' => config('app.email_user_history'), + 'enrich_customer_data' => Option::get('enrich_customer_data'), + 'time_format' => Option::get('time_format', User::TIME_FORMAT_24), + 'locale' => \Helper::getRealAppLocale(), + 'timezone' => config('app.timezone'), + ]; + break; + case 'emails': + $settings = [ + 'mail_from' => \App\Misc\Mail::getSystemMailFrom(), + 'mail_driver' => Option::get('mail_driver', \Config::get('mail.driver')), + 'mail_host' => Option::get('mail_host', \Config::get('mail.host')), + 'mail_port' => Option::get('mail_port', \Config::get('mail.port')), + 'mail_username' => Option::get('mail_username', \Config::get('mail.username')), + 'mail_password' => \Helper::decrypt(Option::get('mail_password', \Config::get('mail.password'))), + 'mail_encryption' => Option::get('mail_encryption', \Config::get('mail.encryption')), + 'fetch_schedule' => config('app.fetch_schedule'), + //'use_mail_date_on_fetching' => config('app.use_mail_date_on_fetching'), + ]; + break; + case 'alerts': + $settings = Option::getOptions([ + 'alert_recipients', + 'alert_fetch', + 'alert_fetch_period', + 'alert_logs', + 'alert_logs_names', + 'alert_logs_period', + 'subscription_defaults', + ], [ + 'alert_logs_names' => [], + 'alert_logs' => config('app.alert_logs'), + 'alert_logs_period' => config('app.alert_logs_period'), + ]); + break; + default: + $settings = \Eventy::filter('settings.section_settings', $settings, $section); + break; + } + + $settings = \Eventy::filter('settings.alter_section_settings', $settings, $section); + + return $settings; + } + + public function getSections() + { + $sections = [ + // todo: order + 'general' => ['title' => __('General'), 'icon' => 'cog', 'order' => 100], + 'emails' => ['title' => __('Mail Settings'), 'icon' => 'transfer', 'order' => 200], + 'alerts' => ['title' => __('Alerts'), 'icon' => 'bell', 'order' => 300], + ]; + $sections = \Eventy::filter('settings.sections', $sections); + + return $sections; + } + + /** + * Save general settings. + * + * @param \Illuminate\Http\Request $request + */ + public function save($section = 'general') + { + $settings = $this->getSectionSettings($section); + + if (!$settings) { + abort(404); + } + + return $this->processSave($section, array_keys($settings)); + } + + public function processSave($section, $settings) + { + // Validate + $validator = $this->getValidator($section); + + if ($validator && $validator->fails()) { + return redirect()->route('settings', ['section' => $section]) + ->withErrors($validator) + ->withInput(); + } + + $request = request(); + + $request = \Eventy::filter('settings.before_save', $request, $section, $settings); + + $cc_required = false; + $settings_params = $this->getSectionParams($section, 'settings'); + foreach ($settings as $i => $option_name) { + // Do not save dummy passwords. + if (!empty($settings_params[$option_name]) + && !empty($settings_params[$option_name]['safe_password']) + && $request->settings[$option_name] + && preg_match("/^\*+$/", $request->settings[$option_name]) + ) { + continue; + } + + // Option has to be saved to .env file. + if (!empty($settings_params[$option_name]) && !empty($settings_params[$option_name]['env'])) { + $env_value = $request->settings[$option_name] ?? ''; + + if (is_array($env_value)) { + $env_value = json_encode($env_value); + } + + if (!empty($settings_params[$option_name]['encrypt'])) { + $env_value = encrypt($env_value); + } + + if (!empty($settings_params[$option_name]['env_encode'])) { + $env_value = base64_encode($env_value); + } + + \Helper::setEnvFileVar($settings_params[$option_name]['env'], $env_value); + + config($option_name, $env_value); + $cc_required = true; + continue; + } + + // By some reason isset() does not work for empty elements. + if (isset($request->settings) && array_key_exists($option_name, $request->settings)) { + $option_value = $request->settings[$option_name]; + + if (!empty($settings_params[$option_name]['encrypt'])) { + $option_value = encrypt($option_value); + } + + Option::set($option_name, $option_value); + } else { + // If option does not exist, default will be used, + // so we can not just remove bool settings. + if (isset($settings_params[$option_name]['default'])) { + $default = $settings_params[$option_name]['default']; + } else { + $default = \Option::getDefault($option_name, null); + } + if ($default === true) { + Option::set($option_name, false); + } elseif (is_array(\Option::getDefault($option_name, -1))) { + Option::set($option_name, []); + } else { + Option::remove($option_name); + } + } + } + + // Clear cache if some options have been saved to .env file. + // Clearing the cache also restarts queue:work as it also + // needs to get new .env parameters. + if ($cc_required) { + \Helper::clearCache(['--doNotGenerateVars' => true]); + } + + // \Helper::clearCache prevents \Session::flash() from displaying. + $request->session()->flash('flash_success_floating', __('Settings updated')); + + $response = redirect()->route('settings', ['section' => $section]); + + $response = \Eventy::filter('settings.after_save', $response, $request, $section, $settings); + + return $response; + } + + /** + * Users ajax controller. + */ + public function ajax(Request $request) + { + $response = [ + 'status' => 'error', + 'msg' => '', // this is error message + ]; + + $user = auth()->user(); + + switch ($request->action) { + + // Test sending emails from mailbox + case 'send_test': + + if (empty($request->to)) { + $response['msg'] = __('Please specify recipient of the test email'); + } + + if (!$response['msg']) { + $test_result = false; + + try { + $test_result = \MailHelper::sendTestMail($request->to); + } catch (\Exception $e) { + $response['msg'] = $e->getMessage(); + } + + if (!$test_result && !$response['msg']) { + $response['msg'] = __('Error occurred sending email. Please check your mail server logs for more details.'); + } + } + + if (!$response['msg']) { + $response['status'] = 'success'; + } + + // Remember email address + if (!empty($request->to)) { + \App\Option::set('send_test_to', $request->to); + } + break; + + default: + $response['msg'] = 'Unknown action'; + break; + } + + if ($response['status'] == 'error' && empty($response['msg'])) { + $response['msg'] = 'Unknown error occurred'; + } + + return \Response::json($response); + } +} diff --git a/freescout-dist/app/Http/Controllers/SystemController.php b/freescout-dist/app/Http/Controllers/SystemController.php new file mode 100644 index 0000000..fd3f2b9 --- /dev/null +++ b/freescout-dist/app/Http/Controllers/SystemController.php @@ -0,0 +1,416 @@ +middleware('auth', ['except' => [ + 'cron' + ]]); + } + + /** + * System status. + */ + public function status(Request $request) + { + // PHP extensions. + $php_extensions = \Helper::checkRequiredExtensions(); + + // Functions. + $functions = \Helper::checkRequiredFunctions(); + + // Permissions. + $permissions = []; + foreach (config('installer.permissions') as $perm_path => $perm_value) { + $path = base_path($perm_path); + $value = ''; + if (file_exists($path)) { + $value = substr(sprintf('%o', fileperms($path)), -4); + } + $permissions[$perm_path] = [ + 'status' => \Helper::isFolderWritable($path), + 'value' => $value, + ]; + } + + // Check if cache files are writable. + $non_writable_cache_file = ''; + if (function_exists('shell_exec')) { + $non_writable_cache_file = shell_exec('find '.base_path('storage/framework/cache/data/').' -type f | xargs -I {} sh -c \'[ ! -w "{}" ] && echo {}\' 2>&1 | head -n 1'); + $non_writable_cache_file = trim($non_writable_cache_file ?? ''); + // Leave only one line (in case head -n 1 does not work) + $non_writable_cache_file = preg_replace("#[\r\n].+#m", '', $non_writable_cache_file); + if (!strstr($non_writable_cache_file, base_path('storage/framework/cache/data/'))) { + $non_writable_cache_file = ''; + } + } + + + // Check if public symlink exists, if not, try to create. + $public_symlink_exists = true; + $public_path = public_path('storage'); + $public_test = $public_path.DIRECTORY_SEPARATOR.'.gitignore'; + + if (!file_exists($public_test) || !file_get_contents($public_test)) { + \File::delete($public_path); + \Artisan::call('storage:link'); + if (!file_exists($public_test) || !file_get_contents($public_test)) { + $public_symlink_exists = false; + } + } + + // Check if .env is writable. + $env_is_writable = is_writable(base_path('.env')); + + // Jobs + $queued_jobs = \App\Job::orderBy('created_at', 'desc')->get(); + $failed_jobs = \App\FailedJob::orderBy('failed_at', 'desc')->get(); + $failed_queues = $failed_jobs->pluck('queue')->unique(); + + // Commands + $commands_list = [ + 'freescout:fetch-emails' => 'freescout:fetch-emails', + \Helper::getWorkerIdentifier() => 'queue:work' + ]; + foreach ($commands_list as $command_identifier => $command_name) { + $status_texts = []; + + // Check if command is running now + if (function_exists('shell_exec')) { + $running_commands = 0; + + try { + $processes = preg_split("/[\r\n]/", shell_exec("ps aux | grep '{$command_identifier}'")); + $pids = []; + foreach ($processes as $process) { + $process = trim($process); + preg_match("/^[\S]+\s+([\d]+)\s+/", $process, $m); + if (empty($m)) { + // Another format (used in Docker image). + // 1713 nginx 0:00 /usr/bin/php82... + preg_match("/^([\d]+)\s+[\S]+\s+/", $process, $m); + } + if (!preg_match("/(sh \-c|grep )/", $process) && !empty($m[1])) { + $running_commands++; + $pids[] = $m[1]; + } + } + } catch (\Exception $e) { + // Do nothing + } + if ($running_commands == 1) { + $commands[] = [ + 'name' => $command_name, + 'status' => 'success', + 'status_text' => __('Running'), + ]; + continue; + } elseif ($running_commands > 1) { + // queue:work command is stopped by settings a cache key + if ($command_name == 'queue:work') { + \Helper::queueWorkerRestart(); + $commands[] = [ + 'name' => $command_name, + 'status' => 'error', + 'status_text' => __(':number commands were running at the same time. Commands have been restarted', ['number' => $running_commands]), + ]; + } else { + unset($pids[0]); + $commands[] = [ + 'name' => $command_name, + 'status' => 'error', + 'status_text' => __(':number commands are running at the same time. Please stop extra commands by executing the following console command:', ['number' => $running_commands]).' kill '.implode(' | kill ', $pids), + ]; + } + continue; + } + } + // Check last run + $option_name = str_replace('freescout_', '', preg_replace('/[^a-zA-Z0-9]/', '_', $command_name)); + + $date_text = '?'; + $last_run = Option::get($option_name.'_last_run'); + if ($last_run) { + $date = Carbon::createFromTimestamp($last_run); + $date_text = User::dateFormat($date); + } + $status_texts[] = __('Last run:').' '.$date_text; + + $date_text = '?'; + $last_successful_run = Option::get($option_name.'_last_successful_run'); + if ($last_successful_run) { + $date_ = Carbon::createFromTimestamp($last_successful_run); + $date_text = User::dateFormat($date); + } + $status_texts[] = __('Last successful run:').' '.$date_text; + + $status = 'error'; + if ($last_successful_run && $last_run && (int) $last_successful_run >= (int) $last_run) { + unset($status_texts[0]); + $status = 'success'; + } + + // If queue:work is not running, clear cache to let it start if something is wrong with the mutex + if ($command_name == 'queue:work' && !$last_successful_run) { + $status_texts[] = __('Try to :%a_start%clear cache:%a_end% to force command to start.', ['%a_start%' => '', '%a_end%' => '']); + // This sometimes makes Status page open as non logged in user. + //\Artisan::call('freescout:clear-cache', ['--doNotGenerateVars' => true]); + } + + $commands[] = [ + 'name' => $command_name, + 'status' => $status, + 'status_text' => implode(' ', $status_texts), + ]; + } + + // Check new version if enabled + $new_version_available = false; + if (!\Config::get('app.disable_updating')) { + $latest_version = \Cache::remember('latest_version', 15, function () { + try { + return \Updater::getVersionAvailable(); + } catch (\Exception $e) { + SystemController::$latest_version_error = $e->getMessage(); + return ''; + } + }); + + if ($latest_version && version_compare($latest_version, \Config::get('app.version'), '>')) { + $new_version_available = true; + } + } else { + $latest_version = \Config::get('app.version'); + } + + // Detect missing migrations. + $migrations_output = \Helper::runCommand('migrate:status'); + preg_match_all("#\| N \| ([^\|]+)\|#", $migrations_output, $migrations_m); + $missing_migrations = $migrations_m[1] ?? []; + + return view('system/status', [ + 'commands' => $commands, + 'queued_jobs' => $queued_jobs, + 'failed_jobs' => $failed_jobs, + 'failed_queues' => $failed_queues, + 'php_extensions' => $php_extensions, + 'functions' => $functions, + 'permissions' => $permissions, + 'new_version_available' => $new_version_available, + 'latest_version' => $latest_version, + 'latest_version_error' => SystemController::$latest_version_error, + 'public_symlink_exists' => $public_symlink_exists, + 'env_is_writable' => $env_is_writable, + 'non_writable_cache_file' => $non_writable_cache_file, + 'missing_migrations' => $missing_migrations, + 'invalid_symlinks' => \App\Module::checkSymlinks(), + ]); + } + + public function action(Request $request) + { + switch ($request->action) { + case 'cancel_job': + \App\Job::where('id', $request->job_id)->delete(); + \Session::flash('flash_success_floating', __('Done')); + break; + + case 'retry_job': + \App\Job::where('id', $request->job_id)->update(['available_at' => time()]); + sleep(1); + \Session::flash('flash_success_floating', __('Done')); + break; + + case 'delete_failed_jobs': + \App\FailedJob::where('queue', $request->failed_queue)->delete(); + \Session::flash('flash_success_floating', __('Failed jobs deleted')); + break; + + case 'retry_failed_jobs': + $jobs = \App\FailedJob::where('queue', $request->failed_queue)->get(); + foreach ($jobs as $job) { + \Artisan::call('queue:retry', ['id' => $job->id]); + } + \Session::flash('flash_success_floating', __('Failed jobs restarted')); + break; + } + + return redirect()->route('system'); + } + + /** + * System tools. + */ + public function tools(Request $request) + { + $output = \Cache::get('tools_execute_output'); + if ($output) { + \Cache::forget('tools_execute_output'); + } + + return view('system/tools', [ + 'output' => $output, + ]); + } + + /** + * Execute tools action. + * + * @param Request $request [description] + * + * @return [type] [description] + */ + public function toolsExecute(Request $request) + { + $outputLog = new BufferedOutput(); + + switch ($request->action) { + case 'clear_cache': + \Artisan::call('freescout:clear-cache', [], $outputLog); + break; + + case 'fetch_emails': + $params = []; + $params['--days'] = (int)$request->days; + $params['--unseen'] = (int)$request->unseen; + \Artisan::call('freescout:fetch-emails', $params, $outputLog); + break; + + case 'migrate_db': + \Artisan::call('migrate', ['--force' => true], $outputLog); + break; + + case 'logout_users': + \Artisan::call('freescout:logout-users', [], $outputLog); + break; + } + + $output = $outputLog->fetch(); + unset($outputLog); + + if ($output) { + // \Session::flash does not work after BufferedOutput + \Cache::forever('tools_execute_output', $output); + } + + return redirect()->route('system.tools')->withInput($request->input()); + } + + /** + * Ajax. + */ + public function ajax(Request $request) + { + $response = [ + 'status' => 'error', + 'msg' => '', // this is error message + ]; + + switch ($request->action) { + + case 'update': + try { + $status = \Updater::update(); + + // Artisan::output() + } catch (\Exception $e) { + $response['msg'] = __('Error occurred. Please try again or try another :%a_start%update method:%a_end%', ['%a_start%' => '', '%a_end%' => '']); + $response['msg'] .= '

'.$e->getMessage(); + + \Helper::logException($e); + } + if (!$response['msg'] && $status) { + // Adding session flash is useless as cache is cleared + $response['msg_success'] = __('Application successfully updated'); + $response['status'] = 'success'; + } + break; + + case 'check_updates': + if (!\Config::get('app.disable_updating')) { + try { + $response['new_version_available'] = \Updater::isNewVersionAvailable(config('app.version')); + $response['status'] = 'success'; + } catch (\Exception $e) { + $response['msg'] = __('Error occurred').': '.$e->getMessage(); + } + if (!$response['msg'] && !$response['new_version_available']) { + // Adding session flash is useless as cache is cleated + $response['msg_success'] = __('You have the latest version installed'); + } + } else { + $response['msg_success'] = __('You have the latest version installed'); + } + break; + + default: + $response['msg'] = 'Unknown action'; + break; + } + + if ($response['status'] == 'error' && empty($response['msg'])) { + $response['msg'] = 'Unknown error occurred'; + } + + return \Response::json($response); + } + + /** + * Web Cron. + */ + public function cron(Request $request) + { + if (empty($request->hash) || $request->hash != \Helper::getWebCronHash()) { + abort(404); + } + $outputLog = new BufferedOutput(); + \Artisan::call('schedule:run', [], $outputLog); + $output = $outputLog->fetch(); + + return response($output, 200)->header('Content-Type', 'text/plain'); + } + + /** + * Ajax HTML. + */ + public function ajaxHtml(Request $request) + { + switch ($request->action) { + case 'job_details': + $job = \App\FailedJob::find($request->param); + if (!$job) { + abort(404); + } + + $html = ''; + $payload = json_decode($job->payload, true); + + if (!empty($payload['data']['command'])) { + $html .= '
'.print_r(unserialize($payload['data']['command']), 1).'
'; + } + + $html .= '
'.$job->exception.'
'; + + return response($html); + } + + abort(404); + } +} diff --git a/freescout-dist/app/Http/Controllers/TranslateController.php b/freescout-dist/app/Http/Controllers/TranslateController.php new file mode 100644 index 0000000..8756de2 --- /dev/null +++ b/freescout-dist/app/Http/Controllers/TranslateController.php @@ -0,0 +1,94 @@ +where('status', Translation::STATUS_CHANGED) + ->groupBy(['locale', 'group']) + ->get() + ->toArray(); + + $this->manager->exportTranslations('*', false); + + // Archive langs folder + try { + $archive_path = \Helper::createZipArchive(base_path().DIRECTORY_SEPARATOR.'resources/lang', 'lang.zip', 'lang'); + } catch (\Exception $e) { + return [ + 'status' => 'error', + 'error_msg' => $e->getMessage(), + ]; + } + + if ($archive_path) { + $attachments[] = $archive_path; + + // Send archive to developers + $result = \MailHelper::sendEmailToDevs('Translations', json_encode($changed_data), $attachments, auth()->user()); + } + + if ($result) { + return ['status' => 'ok']; + } else { + abort(500); + } + } + + /** + * Remove all translations which has not been published yet. + * + * @return [type] [description] + */ + public function postRemoveUnpublished() + { + \Barryvdh\TranslationManager\Models\Translation::truncate(); + + return ['status' => 'ok']; + } + + /** + * Download as ZIP. + * + * @return [type] [description] + */ + public function postDownload() + { + $this->manager->exportTranslations('*', false); + $file_name = 'lang.zip'; + // Archive langs folder + $archive_path = \Helper::createZipArchive(base_path().DIRECTORY_SEPARATOR.'resources/lang', $file_name, 'lang'); + $public_path = storage_path('app/public/'.$file_name); + + \File::copy($archive_path, $public_path); + + $headers = [ + 'Content-Type: application/zip', + ]; + + return \Response::download($public_path, $file_name, $headers); + } + + /** + * List of strings to translate. + */ + public function stringsToTranslate() + { + __(':field is required'); + __('The following modules have to be installed and activated: :modules'); + } +} diff --git a/freescout-dist/app/Http/Controllers/UsersController.php b/freescout-dist/app/Http/Controllers/UsersController.php new file mode 100644 index 0000000..8fb4810 --- /dev/null +++ b/freescout-dist/app/Http/Controllers/UsersController.php @@ -0,0 +1,660 @@ +middleware('auth'); + } + + /** + * Users list. + */ + public function users() + { + $this->authorize('create', 'App\User'); + + $users = User::nonDeleted()->get(); + $users = User::sortUsers($users); + + return view('users/users', ['users' => $users]); + } + + /** + * New user. + */ + public function create() + { + $this->authorize('create', 'App\User'); + $mailboxes = Mailbox::all(); + + return view('users/create', ['mailboxes' => $mailboxes]); + } + + /** + * Create new user. + * + * @param \Illuminate\Http\Request $request + */ + public function createSave(Request $request) + { + $invalid = false; + $this->authorize('create', 'App\User'); + $auth_user = auth()->user(); + + $rules = [ + 'first_name' => 'required|string|max:20', + 'last_name' => 'required|string|max:30', + 'email' => 'required|string|email|max:100|unique:users', + //'role' => ['required', Rule::in(array_keys(User::$roles))], + ]; + if ($auth_user->isAdmin()) { + $rules['role'] = ['required', Rule::in(array_keys(User::$roles))]; + } + if (empty($request->send_invite)) { + $rules['password'] = 'required|string|max:255'; + } + $validator = Validator::make($request->all(), $rules); + + if (User::mailboxEmailExists($request->email)) { + $invalid = true; + $validator->errors()->add('email', __('There is a mailbox with such email. Users and mailboxes can not have the same email addresses.')); + } + + if ($invalid || $validator->fails()) { + return redirect()->route('users.create') + ->withErrors($validator) + ->withInput(); + } + + $user = new User(); + $user->fill($request->all()); + if (!$auth_user->can('changeRole', $user)) { + $user->role = User::ROLE_USER; + } + if (empty($request->send_invite)) { + // Set password from request + $user->password = Hash::make($request->password); + } else { + // Set some random password before sending invite + $user->password = Hash::make($user->generateRandomPassword()); + } + // Set system timezone. + $user->timezone = config('app.timezone') ?: User::DEFAULT_TIMEZONE; + $user = \Eventy::filter('user.create_save', $user, $request); + $user->save(); + + $user->mailboxes()->sync($request->mailboxes ?: []); + $user->syncPersonalFolders($request->mailboxes); + + // Send invite + if (!empty($request->send_invite)) { + try { + $user->sendInvite(true); + } catch (\Exception $e) { + // Admin is allowed to see exceptions + \Session::flash('flash_error_floating', $e->getMessage().' — '.__('Check mail settings in "Manage » Settings » Mail Settings"')); + } + } + + \Session::flash('flash_success_floating', __('User created successfully')); + + return redirect()->route('users.profile', ['id' => $user->id]); + } + + /** + * User profile. + */ + public function profile($id) + { + $user = User::findOrFail($id); + if ($user->isDeleted()) { + abort(404); + } + + $this->authorize('update', $user); + + $users = $this->getUsersForSidebar($id); + + return view('users/profile', ['user' => $user, 'users' => $users]); + } + + public function getUsersForSidebar($except_id) + { + if (auth()->user()->isAdmin()) { + return User::sortUsers(User::nonDeleted()->get());/*->except($except_id)*/; + } else { + return []; + } + } + + /** + * Handle a registration request for the application. + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\Response + */ + public function profileSave($id, Request $request) + { + $invalid = false; + + $user = User::findOrFail($id); + $this->authorize('update', $user); + + // This is also present in PublicController::userSetup + $validator = Validator::make($request->all(), [ + 'first_name' => 'required|string|max:20', + 'last_name' => 'required|string|max:30', + 'email' => 'required|string|email|max:100|unique:users,email,'.$id, + //'emails' => 'max:100', + 'job_title' => 'max:100', + 'phone' => 'max:60', + 'timezone' => 'required|string|max:255', + 'time_format' => 'required', + 'role' => ['nullable', Rule::in(array_keys(User::$roles))], + 'photo_url' => 'nullable|image|mimes:jpeg,png,jpg,gif', + ]); + $validator->setAttributeNames([ + 'photo_url' => __('Photo'), + ]); + + // Photo + $validator->after(function ($validator) use ($user, $request) { + if ($request->hasFile('photo_url')) { + $path_url = $user->savePhoto($request->file('photo_url')); + + if ($path_url) { + $user->photo_url = $path_url; + } else { + $invalid = true; + $validator->errors()->add('photo_url', __('Error occurred processing the image. Make sure that PHP GD extension is enabled.')); + } + } + + // Do not allow to remove last administrator + if ($user->isAdmin() && isset($request->role) && $request->role != User::ROLE_ADMIN) { + $admins_count = User::where('role', User::ROLE_ADMIN)->count(); + if ($admins_count < 2) { + $invalid = true; + $validator->errors()->add('role', __('Role of the only one administrator can not be changed.')); + } + } + }); + + if (User::mailboxEmailExists($request->email)) { + $invalid = true; + $validator->errors()->add('email', __('There is a mailbox with such email. Users and mailboxes can not have the same email addresses.')); + } + + if ($invalid || $validator->fails()) { + return redirect()->route('users.profile', ['id' => $id]) + ->withErrors($validator) + ->withInput(); + } + + // Save language into session. + if (auth()->user()->id == $id && $request->locale) { + session()->put('user_locale', $request->locale); + } + + $request_data = $request->all(); + + if (isset($request_data['photo_url'])) { + unset($request_data['photo_url']); + } + if (!auth()->user()->can('changeRole', $user)) { + unset($request_data['role']); + } + if ($user->status != User::STATUS_DELETED) { + if (!empty($request_data['disabled'])) { + $request_data['status'] = User::STATUS_DISABLED; + } else { + $request_data['status'] = User::STATUS_ACTIVE; + } + } + $user->setData($request_data); + + if (empty($request->input('enable_kb_shortcuts'))) { + $user->enable_kb_shortcuts = false; + } + + $user = \Eventy::filter('user.save_profile', $user, $request); + + $user->save(); + + \Session::flash('flash_success_floating', __('Profile saved successfully')); + + return redirect()->route('users.profile', ['id' => $id]); + } + + /** + * User permissions. + */ + public function permissions($id) + { + $user = auth()->user(); + if (!$user->isAdmin()) { + abort(403); + } + + $user = User::findOrFail($id); + + $mailboxes = Mailbox::all(); + + $users = $this->getUsersForSidebar($id); + + return view('users/permissions', [ + 'user' => $user, + 'mailboxes' => $mailboxes, + 'user_mailboxes' => $user->mailboxes, + 'users' => $users, + ]); + } + + /** + * Save user permissions. + * + * @param int $id + * @param \Illuminate\Http\Request $request + */ + public function permissionsSave($id, Request $request) + { + $user = auth()->user(); + if (!$user->isAdmin()) { + abort(403); + } + + $user = User::findOrFail($id); + + $user->mailboxes()->sync($request->mailboxes ?: []); + $user->syncPersonalFolders($request->mailboxes); + + // Save permissions. + $user_permissions = $request->user_permissions ?? []; + $permissions = []; + + foreach (User::getUserPermissionsList() as $permission_id) { + $new_has_permission = in_array($permission_id, $user_permissions); + + if ($user->hasPermission($permission_id, false) != $new_has_permission) { + $permissions[$permission_id] = (int)(bool)$new_has_permission; + $save_user = true; + } + } + $user->permissions = $permissions; + $user->save(); + + \Session::flash('flash_success_floating', __('Permissions saved successfully')); + + return redirect()->route('users.permissions', ['id' => $id]); + } + + /** + * User notifications settings. + */ + public function notifications($id) + { + $user = User::findOrFail($id); + $this->authorize('update', $user); + + $subscriptions = $user->subscriptions()->select('medium', 'event')->get(); + + $person = ''; + if ($id != auth()->user()->id) { + $person = $user->getFirstName(true); + } + + $users = $this->getUsersForSidebar($id); + + return view('users/notifications', [ + 'user' => $user, + 'subscriptions' => $subscriptions, + 'person' => $person, + 'users' => $users, + 'mobile_available' => \Eventy::filter('notifications.mobile_available', false), + ]); + } + + /** + * Save user notifications settings. + * + * @param int $id + * @param \Illuminate\Http\Request $request + */ + public function notificationsSave($id, Request $request) + { + $user = User::findOrFail($id); + $this->authorize('update', $user); + + Subscription::saveFromArray($request->subscriptions, $user->id); + + \Session::flash('flash_success_floating', __('Notifications saved successfully')); + + return redirect()->route('users.notifications', ['id' => $id]); + } + + /** + * Users ajax controller. + */ + public function ajax(Request $request) + { + $response = [ + 'status' => 'error', + 'msg' => '', // this is error message + ]; + + $auth_user = auth()->user(); + + switch ($request->action) { + + // Both send and resend + case 'send_invite': + if (!$auth_user->isAdmin()) { + $response['msg'] = __('Not enough permissions'); + } + if (empty($request->user_id)) { + $response['msg'] = __('Incorrect user'); + } + if (!$response['msg']) { + $user = User::find($request->user_id); + if (!$user) { + $response['msg'] = __('User not found'); + } elseif ($user->invite_state == User::INVITE_STATE_ACTIVATED) { + $response['msg'] = __('User already accepted invitation'); + } + } + + if (!$response['msg']) { + try { + $user->sendInvite(true); + + $response['status'] = 'success'; + } catch (\Exception $e) { + // Admin is allowed to see exceptions. + $response['msg'] = $e->getMessage().' — '.__('Check mail settings in "Manage » Settings » Mail Settings"'); + } + } + break; + + // Reset password + case 'reset_password': + if (!auth()->user()->isAdmin()) { + $response['msg'] = __('Not enough permissions'); + } + if (empty($request->user_id)) { + $response['msg'] = __('Incorrect user'); + } + if (!$response['msg']) { + $user = User::find($request->user_id); + if (!$user) { + $response['msg'] = __('User not found'); + } + } + + if (!$response['msg']) { + $reset_result = Password::broker()->sendResetLink( + //['id' => $request->user_id] + ['id' => $request->user_id] + ); + + if ($reset_result == Password::RESET_LINK_SENT) { + $response['status'] = 'success'; + $response['msg_success'] = __('Password reset email has been sent'); + } + } + break; + + // Load website notifications + case 'web_notifications': + if (!$auth_user) { + $response['msg'] = __('You are not logged in'); + } + if (!$response['msg']) { + $web_notifications_info = $auth_user->getWebsiteNotificationsInfo(false); + $response['html'] = view('users/partials/web_notifications', [ + 'web_notifications_info_data' => $web_notifications_info['data'], + ])->render(); + + $response['has_more_pages'] = (int) $web_notifications_info['notifications']->hasMorePages(); + + $response['status'] = 'success'; + } + break; + + // Mark all user website notifications as read + case 'mark_notifications_as_read': + if (!$auth_user) { + $response['msg'] = __('You are not logged in'); + } + if (!$response['msg']) { + $auth_user->unreadNotifications()->update(['read_at' => now()]); + $auth_user->clearWebsiteNotificationsCache(); + + $response['status'] = 'success'; + } + break; + + // Delete user photo + case 'delete_photo': + $user = User::find($request->user_id); + + if (!$user) { + $response['msg'] = __('User not found'); + } elseif (!$auth_user->can('update', $user)) { + $response['msg'] = __('Not enough permissions'); + } + if (!$response['msg']) { + $user->removePhoto(); + $user->save(); + + $response['status'] = 'success'; + } + break; + + // Delete user + case 'delete_user': + $user = User::find($request->user_id); + + if (!$user) { + $response['msg'] = __('User not found'); + } elseif (!$auth_user->can('delete', $user)) { + $response['msg'] = __('Not enough permissions'); + } + + // Check if the user is the only one admin + if (!$response['msg'] && $user->isAdmin()) { + $admins_count = User::where('role', User::ROLE_ADMIN)->count(); + if ($admins_count < 2) { + $response['msg'] = __('Administrator can not be deleted'); + } + } + + if (!$response['msg']) { + + // We have to process conversations one by one to move them to Unassigned folder, + // as conversations may be in different mailboxes + // $user->conversations()->update(['user_id' => null, 'folder_id' => ]); + $mailbox_unassigned_folders = []; + + $user->conversations->each(function ($conversation) use ($auth_user, $request) { + // We don't fire ConversationUserChanged event to avoid sending notifications to users + if (!empty($request->assign_user) + && !empty($request->assign_user[$conversation->mailbox_id]) + && (int) $request->assign_user[$conversation->mailbox_id] != -1 + ) { + // Set assignee. + // In this case conversation stays assigned, just assignee changes. + $conversation->user_id = $request->assign_user[$conversation->mailbox_id]; + + } else { + + // Make convesation Unassigned. + + // Unset assignee. + // Maybe use changeUser() here. + $conversation->user_id = null; + + if ($conversation->isPublished() + && ($conversation->isActive() || $conversation->isPending()) + ) { + // Change conversation folder to UNASSIGNED. + $folder_id = null; + if (!empty($mailbox_unassigned_folders[$conversation->mailbox_id])) { + $folder_id = $mailbox_unassigned_folders[$conversation->mailbox_id]; + } else { + $folder = $conversation->mailbox->folders() + ->where('type', Folder::TYPE_UNASSIGNED) + ->first(); + + if ($folder) { + $folder_id = $folder->id; + $mailbox_unassigned_folders[$conversation->mailbox_id] = $folder_id; + } + } + if ($folder_id) { + $conversation->folder_id = $folder_id; + } + } + } + + $conversation->save(); + + // Create lineitem thread + $thread = new Thread(); + $thread->conversation_id = $conversation->id; + $thread->user_id = $conversation->user_id; + $thread->type = Thread::TYPE_LINEITEM; + $thread->state = Thread::STATE_PUBLISHED; + $thread->status = Thread::STATUS_NOCHANGE; + $thread->action_type = Thread::ACTION_TYPE_USER_CHANGED; + $thread->source_via = Thread::PERSON_USER; + $thread->source_type = Thread::SOURCE_TYPE_WEB; + $thread->customer_id = $conversation->customer_id; + $thread->created_by_user_id = $auth_user->id; + $thread->save(); + }); + + // Recalculate counters for folders + //if ($user->isAdmin()) { + // Admin has access to all mailboxes + Mailbox::all()->each(function ($mailbox) { + $mailbox->updateFoldersCounters(); + }); + // } else { + // $user->mailboxes->each(function ($mailbox) { + // $mailbox->updateFoldersCounters(); + // }); + // } + + // Disconnect user from mailboxes. + $user->mailboxes()->sync([]); + $user->folders()->delete(); + + $user->status = \App\User::STATUS_DELETED; + // Update email. + $email_suffix = User::EMAIL_DELETED_SUFFIX.date('YmdHis'); + // We have to truncate email to avoid "Data too long" error. + $user->email = mb_substr($user->email, 0, User::EMAIL_MAX_LENGTH - mb_strlen($email_suffix)).$email_suffix; + + $user->save(); + + event(new UserDeleted($user, $auth_user)); + + \Session::flash('flash_success_floating', __('User deleted').': '.$user->getFullName()); + + $response['status'] = 'success'; + } + break; + + default: + $response['msg'] = 'Unknown action'; + break; + } + + if ($response['status'] == 'error' && empty($response['msg'])) { + $response['msg'] = 'Unknown error occurred'; + } + + return \Response::json($response); + } + + /** + * Change user password. + */ + public function password($id) + { + $user = User::findOrFail($id); + $this->authorize('update', $user); + + $users = User::all()->except($id); + + return view('users/password', ['user' => $user, 'users' => $users]); + } + + /** + * Save changed user password. + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\Response + */ + public function passwordSave($id, Request $request) + { + // It is allowed to edit only your own password + $user = auth()->user(); + if ($user->id != $id) { + abort(403); + } + + // This is also present in PublicController::userSetup + $validator = Validator::make($request->all(), [ + 'password_current' => 'required|string', + 'password' => 'required|string|min:8|confirmed', + ]); + + $validator->after(function ($validator) use ($user, $request) { + // Check current password + if (!Hash::check($request->password_current, $user->password)) { + $validator->errors()->add('password_current', __('This password is incorrect.')); + } elseif (Hash::check($request->password, $user->password)) { + // Check new password + $validator->errors()->add('password', __('The new password is the same as the old password.')); + } + }); + + if ($validator->fails()) { + return redirect()->route('users.password', ['id' => $id]) + ->withErrors($validator) + ->withInput(); + } + + $user->password = bcrypt($request->password); + $user->save(); + + $user->sendPasswordChanged(); + + \Session::flash('flash_success_floating', __('Password saved successfully!')); + + return redirect()->route('users.profile', ['id' => $id]); + } +} diff --git a/freescout-dist/app/Http/Kernel.php b/freescout-dist/app/Http/Kernel.php new file mode 100644 index 0000000..5474ded --- /dev/null +++ b/freescout-dist/app/Http/Kernel.php @@ -0,0 +1,70 @@ + [ + \App\Http\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + \App\Http\Middleware\TokenAuth::class, + // \Illuminate\Session\Middleware\AuthenticateSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \App\Http\Middleware\VerifyCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + \App\Http\Middleware\HttpsRedirect::class, + \App\Http\Middleware\Localize::class, + \App\Http\Middleware\LogoutIfDeleted::class, + \App\Http\Middleware\FrameGuard::class, + \App\Http\Middleware\CustomHandle::class, + ], + + // 'api' => [ + // 'throttle:60,1', + // 'bindings', + // ], + ]; + + /** + * The application's route middleware. + * + * These middleware may be assigned to groups or used individually. + * + * @var array + */ + protected $routeMiddleware = [ + 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'roles' => \App\Http\Middleware\CheckRole::class, + ]; +} diff --git a/freescout-dist/app/Http/Middleware/CheckRole.php b/freescout-dist/app/Http/Middleware/CheckRole.php new file mode 100644 index 0000000..ae9b7ea --- /dev/null +++ b/freescout-dist/app/Http/Middleware/CheckRole.php @@ -0,0 +1,43 @@ +getRequiredRoleForRoute($request->route()); + + // Check if a role is required for the route, and + // if so, ensure that the user has that role. + if (!$roles || in_array($request->user()->getRoleName(), $roles)) { + return $next($request); + } + abort(403, __('You are not authorized to access this resource.')); + // return response([ + // 'error' => [ + // 'code' => 'INSUFFICIENT_ROLE', + // 'description' => + // ] + // ], 401); + } + + private function getRequiredRoleForRoute($route) + { + $actions = $route->getAction(); + + return isset($actions['roles']) ? $actions['roles'] : null; + } +} diff --git a/freescout-dist/app/Http/Middleware/CustomHandle.php b/freescout-dist/app/Http/Middleware/CustomHandle.php new file mode 100644 index 0000000..7fd9681 --- /dev/null +++ b/freescout-dist/app/Http/Middleware/CustomHandle.php @@ -0,0 +1,29 @@ +exists('chat_mode')) { + \Helper::setChatMode((int)$request->chat_mode); + } + + // Hook. + \Eventy::action('middleware.web.custom_handle', $request); + + return \Eventy::filter('middleware.web.custom_handle.response', $next($request), $request, $next); + } +} diff --git a/freescout-dist/app/Http/Middleware/EncryptCookies.php b/freescout-dist/app/Http/Middleware/EncryptCookies.php new file mode 100644 index 0000000..1dcaf75 --- /dev/null +++ b/freescout-dist/app/Http/Middleware/EncryptCookies.php @@ -0,0 +1,17 @@ +headers->set('X-Frame-Options', $value, false); + } + + return $response; + } +} diff --git a/freescout-dist/app/Http/Middleware/HttpsRedirect.php b/freescout-dist/app/Http/Middleware/HttpsRedirect.php new file mode 100644 index 0000000..e737803 --- /dev/null +++ b/freescout-dist/app/Http/Middleware/HttpsRedirect.php @@ -0,0 +1,48 @@ + 'FORWARDED', + // Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', + // Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', + // Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', + // Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', + // ]; + + public function handle($request, Closure $next) + { + if (\Helper::isHttps()) { + //$request->setTrustedProxies( [ $request->getClientIp() ], array_keys($this->headers)); + //!$request->secure() + if (!\Helper::isCurrentUrlHttps()) { + return redirect()->secure($request->getRequestUri()); + } + } + + // Correct protocol in $_SERVER + if (\Helper::isHttps() + //&& !$request->secure() + && strtolower($_SERVER['HTTPS'] ?? '') != 'on' + ) { + $_SERVER['HTTPS'] = 'on'; + } + return $next($request); + } +} \ No newline at end of file diff --git a/freescout-dist/app/Http/Middleware/Localize.php b/freescout-dist/app/Http/Middleware/Localize.php new file mode 100644 index 0000000..329c6c1 --- /dev/null +++ b/freescout-dist/app/Http/Middleware/Localize.php @@ -0,0 +1,29 @@ +isDeleted() || $user->isDisabled())) { + Auth::logout(); + return Redirect::route('login'); + } + + return $next($request); + } +} diff --git a/freescout-dist/app/Http/Middleware/RedirectIfAuthenticated.php b/freescout-dist/app/Http/Middleware/RedirectIfAuthenticated.php new file mode 100644 index 0000000..afe1c26 --- /dev/null +++ b/freescout-dist/app/Http/Middleware/RedirectIfAuthenticated.php @@ -0,0 +1,27 @@ +check()) { + return redirect('/home'); + } + + return $next($request); + } +} diff --git a/freescout-dist/app/Http/Middleware/ResponseHeaders.php b/freescout-dist/app/Http/Middleware/ResponseHeaders.php new file mode 100644 index 0000000..68e9465 --- /dev/null +++ b/freescout-dist/app/Http/Middleware/ResponseHeaders.php @@ -0,0 +1,21 @@ +header('Pragma', 'no-cache'); + $response->header('Cache-Control', 'no-cache, max-age=0, must-revalidate, no-store'); + } + + return $response; + } +} diff --git a/freescout-dist/app/Http/Middleware/TerminateHandler.php b/freescout-dist/app/Http/Middleware/TerminateHandler.php new file mode 100644 index 0000000..e4106e1 --- /dev/null +++ b/freescout-dist/app/Http/Middleware/TerminateHandler.php @@ -0,0 +1,20 @@ +user() && !empty($request->auth_token) && $request->cookie('in_app')) { + try { + $user = User::where(\DB::raw('md5(CONCAT(id, created_at, "'.config('app.key').'"))'), $request->auth_token) + ->first(); + } catch (\Exception $e) { + \Helper::logException($e, '[TokenAuth]'); + } + if (!empty($user)) { + \Auth::login($user); + } + } + return $next($request); + } +} diff --git a/freescout-dist/app/Http/Middleware/TrimStrings.php b/freescout-dist/app/Http/Middleware/TrimStrings.php new file mode 100644 index 0000000..5a50e7b --- /dev/null +++ b/freescout-dist/app/Http/Middleware/TrimStrings.php @@ -0,0 +1,18 @@ + 'FORWARDED', + Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', + Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', + Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', + Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', + ]; +} diff --git a/freescout-dist/app/Http/Middleware/VerifyCsrfToken.php b/freescout-dist/app/Http/Middleware/VerifyCsrfToken.php new file mode 100644 index 0000000..0c13b85 --- /dev/null +++ b/freescout-dist/app/Http/Middleware/VerifyCsrfToken.php @@ -0,0 +1,17 @@ +payload_decoded !== null) { + return $this->payload_decoded; + } + + $this->payload_decoded = json_decode($this->payload, true); + + return $this->payload_decoded; + } + + public function getCommand() + { + return self::getPayloadCommand($this->getPayloadDecoded()); + } + + public function getCommandLastThread() + { + $command = $this->getCommand(); + if ($command && !empty($command->threads)) { + return Thread::getLastThread($command->threads); + } + + return null; + } + + public static function getPayloadCommand($payload) + { + if (empty($payload['data']) || empty($payload['data']['command'])) { + return null; + } + try { + // If some record has been deleted from DB, there will be an error: + // No query results for model [App\Conversation]. + return unserialize($payload['data']['command']); + } catch (\Exception $e) { + return null; + } + } +} diff --git a/freescout-dist/app/Jobs/RestartQueueWorker.php b/freescout-dist/app/Jobs/RestartQueueWorker.php new file mode 100644 index 0000000..d32dafd --- /dev/null +++ b/freescout-dist/app/Jobs/RestartQueueWorker.php @@ -0,0 +1,41 @@ +delete(); + // register_shutdown_function() is called on exit(), + // so commands mutexes are removed. + exit(); + } +} diff --git a/freescout-dist/app/Jobs/SendAlert.php b/freescout-dist/app/Jobs/SendAlert.php new file mode 100644 index 0000000..da9ffb7 --- /dev/null +++ b/freescout-dist/app/Jobs/SendAlert.php @@ -0,0 +1,105 @@ +text = $text; + $this->title = $title; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + // Configure mail driver according to Mailbox settings + \MailHelper::setSystemMailDriver(); + + $recipients = User::nonDeleted() + ->where('role', User::ROLE_ADMIN) + ->where('invite_state', User::INVITE_STATE_ACTIVATED) + ->pluck('email') + ->toArray(); + + $extra = \MailHelper::sanitizeEmails(\Option::get('alert_recipients')); + if ($extra) { + $recipients = array_unique(array_merge($recipients, $extra)); + } + + foreach ($recipients as $recipient) { + $exception = null; + + try { + Mail::to([['name' => '', 'email' => $recipient]]) + ->send(new Alert($this->text, $this->title)); + } catch (\Exception $e) { + // We come here in case SMTP server unavailable for example + activity() + ->withProperties([ + 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')', + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_ALERT); + + $exception = $e; + } + + $status_message = ''; + if ($exception) { + $status = SendLog::STATUS_SEND_ERROR; + $status_message = $exception->getMessage(); + } else { + $failures = Mail::failures(); + + if (!empty($failures)) { + $status = SendLog::STATUS_SEND_ERROR; + } else { + $status = SendLog::STATUS_ACCEPTED; + } + } + + SendLog::log(null, null, $recipient, SendLog::MAIL_TYPE_ALERT, $status, null, null, $status_message); + } + + if ($exception) { + throw $exception; + } + } +} diff --git a/freescout-dist/app/Jobs/SendAutoReply.php b/freescout-dist/app/Jobs/SendAutoReply.php new file mode 100644 index 0000000..07899e7 --- /dev/null +++ b/freescout-dist/app/Jobs/SendAutoReply.php @@ -0,0 +1,148 @@ +conversation = $conversation; + $this->thread = $thread; + $this->mailbox = $mailbox; + $this->customer = $customer; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + // Auto reply disabled. + if (!empty($this->conversation->meta['ar_off'])) { + return; + } + + // Configure mail driver according to Mailbox settings + \App\Misc\Mail::setMailDriver($this->mailbox, null, $this->conversation); + + // Auto reply appears as reply in customer's mailbox + $headers['In-Reply-To'] = '<'.$this->thread->message_id.'>'; + $headers['References'] = '<'.$this->thread->message_id.'>'; + + // Create Message-ID for the auto reply + $message_id = \App\Misc\Mail::MESSAGE_ID_PREFIX_AUTO_REPLY.'-'.$this->thread->id.'-'.\MailHelper::getMessageIdHash($this->thread->id).'@'.$this->mailbox->getEmailDomain(); + $headers['Message-ID'] = $message_id; + + $customer_email = $this->conversation->customer_email; + + if (!$customer_email) { + // When message is received via Chat, customer has no email adddress. + return; + } + $recipients = [$customer_email]; + $failures = []; + $exception = null; + + try { + Mail::to([['name' => $this->customer->getFullName(), 'email' => $customer_email]]) + ->send(new AutoReply($this->conversation, $this->mailbox, $this->customer, $headers)); + } catch (\Exception $e) { + // We come here in case SMTP server unavailable for example + activity() + ->causedBy($this->customer) + ->withProperties([ + 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')', + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER); + + // Failures will be saved to send log when retry attempts will finish + $failures = $recipients; + + $exception = $e; + } + + foreach ($recipients as $recipient) { + $status_message = ''; + if ($exception) { + $status = SendLog::STATUS_SEND_ERROR; + $status_message = $exception->getMessage(); + } else { + $failures = Mail::failures(); + + // Status for send log + if (!empty($failures) && in_array($recipient, $failures)) { + $status = SendLog::STATUS_SEND_ERROR; + } else { + $status = SendLog::STATUS_ACCEPTED; + } + } + if ($customer_email == $recipient) { + $customer_id = $this->customer->id; + } else { + $customer_id = null; + } + + SendLog::log($this->thread->id, $message_id, $recipient, SendLog::MAIL_TYPE_AUTO_REPLY, $status, $customer_id, null, $status_message); + } + + if ($exception) { + throw $exception; + } + } + + /** + * The job failed to process. + * This method is called after attempts had finished. + * At this stage method has access only to variables passed in constructor. + * + * @param Exception $exception + * + * @return void + */ + public function failed(\Exception $e) + { + // Write to activity log + activity() + ->causedBy($this->customer) + ->withProperties([ + 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')', + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER); + } +} diff --git a/freescout-dist/app/Jobs/SendEmailReplyError.php b/freescout-dist/app/Jobs/SendEmailReplyError.php new file mode 100644 index 0000000..ae86cf6 --- /dev/null +++ b/freescout-dist/app/Jobs/SendEmailReplyError.php @@ -0,0 +1,95 @@ +from = $from; + $this->user = $user; + $this->mailbox = $mailbox; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + // Configure mail driver according to Mailbox settings + \App\Misc\Mail::setMailDriver($this->mailbox); + + $exception = null; + + try { + Mail::to([['name' => '', 'email' => $this->from]]) + ->send(new UserEmailReplyError()); + } catch (\Exception $e) { + // We come here in case SMTP server unavailable for example + activity() + ->withProperties([ + 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')', + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_WRONG_EMAIL); + + $exception = $e; + } + + $status_message = ''; + if ($exception) { + $status = SendLog::STATUS_SEND_ERROR; + $status_message = $exception->getMessage(); + } else { + $failures = Mail::failures(); + + // Save to send log + if (!empty($failures)) { + $status = SendLog::STATUS_SEND_ERROR; + } else { + $status = SendLog::STATUS_ACCEPTED; + } + } + + SendLog::log(null, null, $this->from, SendLog::MAIL_TYPE_WRONG_USER_EMAIL_MESSAGE, $status, null, $this->user->id, $status_message); + + if ($exception) { + throw $exception; + } + } +} diff --git a/freescout-dist/app/Jobs/SendNotificationToUsers.php b/freescout-dist/app/Jobs/SendNotificationToUsers.php new file mode 100644 index 0000000..046fe7e --- /dev/null +++ b/freescout-dist/app/Jobs/SendNotificationToUsers.php @@ -0,0 +1,223 @@ +users = $users; + $this->conversation = $conversation; + $this->threads = $threads; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $mailbox = $this->conversation->mailbox; + + // Configure mail driver according to Mailbox settings + \App\Misc\Mail::setMailDriver($mailbox, null, $this->conversation); + + // Threads has to be sorted here, if sorted before, they come here in wrong order + $this->threads = Thread::sortThreads($this->threads); + + $headers = []; + $last_thread = $this->threads->first(); + + if (!$last_thread) { + return; + } + + // If thread is draft, it means it has been undone + if ($last_thread->isDraft()) { + return; + } + + // Limit conversation history + if (config('app.email_user_history') == 'last') { + $this->threads = $this->threads->slice(0, 2); + } + + if (config('app.email_user_history') == 'none') { + $this->threads = $this->threads->slice(0, 1); + } + + // All notification for the same conversation has same dummy Message-ID + $prev_message_id = \App\Misc\Mail::MESSAGE_ID_PREFIX_NOTIFICATION_IN_REPLY.'-'.$this->conversation->id.'-'.md5($this->conversation->id).'@'.$mailbox->getEmailDomain(); + $headers['In-Reply-To'] = '<'.$prev_message_id.'>'; + $headers['References'] = '<'.$prev_message_id.'>'; + // https://github.com/freescout-helpdesk/freescout/issues/2488 + $headers['X-Auto-Response-Suppress'] = 'All'; + + // We throw an exception if any of the send attempts throws an exception (connection error, etc) + $global_exception = null; + + foreach ($this->users as $user) { + + // User can ne deleted from DB. + if (!isset($user->id)) { + continue; + } + + if ($user->isDeleted()) { + continue; + } + + // If for one user sending fails the job is marked as failed and retried after some time. + // So we have to check if notification email has already been successfully sent to this user. + if ($this->attempts() > 1) { + // Maybe add indexes to the table. + $already_sent = SendLog::where('thread_id', $last_thread->id) + ->where('mail_type', SendLog::MAIL_TYPE_USER_NOTIFICATION) + ->where('user_id', $user->id) + ->whereIn('status', SendLog::$sent_success) + ->exists(); + if ($already_sent) { + continue; + } + } + + $message_id = \App\Misc\Mail::MESSAGE_ID_PREFIX_NOTIFICATION.'-'.$last_thread->id.'-'.$user->id.'-'.time().'@'.$mailbox->getEmailDomain(); + $headers['Message-ID'] = $message_id; + + // If this is notification on message from customer, set customer as sender name + $from_name = ''; + if ($last_thread->type == Thread::TYPE_CUSTOMER) { + $from_name = ''; + if ($last_thread->customer) { + $from_name = $last_thread->customer->getFullName(true, true); + } + if ($from_name) { + $from_name = $from_name.' '.__('via').' '.$mailbox->name; + } + } + if (!$from_name) { + $from_name = $mailbox->name; + } + $from = ['address' => $mailbox->email, 'name' => $from_name]; + + // Set user language + app()->setLocale($user->getLocale()); + + $headers['X-FreeScout-Mail-Type'] = 'user.notification'; + $headers = \Eventy::filter('jobs.send_reply_to_customer.headers', $headers, $user, $mailbox, $this->conversation, $this->threads, $from); + + $exception = null; + + try { + Mail::to([['name' => $user->getFullName(), 'email' => $user->email]]) + ->send(new UserNotification($user, $this->conversation, $this->threads, $headers, $from, $mailbox)); + } catch (\Exception $e) { + // We come here in case SMTP server unavailable for example + activity() + ->causedBy($user) + ->withProperties([ + 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')', + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_USER); + + $exception = $e; + $global_exception = $e; + } + + $status_message = ''; + if ($exception) { + $status = SendLog::STATUS_SEND_ERROR; + $status_message = $exception->getMessage(); + } else { + $failures = Mail::failures(); + + // Save to send log + if (!empty($failures) && in_array($user->email, $failures)) { + $status = SendLog::STATUS_SEND_ERROR; + } else { + $status = SendLog::STATUS_ACCEPTED; + } + } + + SendLog::log($last_thread->id, $message_id, $user->email, SendLog::MAIL_TYPE_USER_NOTIFICATION, $status, null, $user->id, $status_message); + } + + if ($global_exception) { + // Retry job with delay. + // https://stackoverflow.com/questions/35258175/how-can-i-create-delays-between-failed-queued-job-attempts-in-laravel + // We do not try to resend Bounce messages: https://github.com/freescout-helpdesk/freescout/issues/3156 + if ($this->attempts() < $this->tries && !$last_thread->isBounce()) { + if ($this->attempts() == 1) { + // Second attempt after 5 min. + $this->release(300); + } else { + // Others - after 1 hour. + $this->release(3600); + } + + throw $global_exception; + } else { + $this->fail($global_exception); + + return; + } + } + } + + /** + * The job failed to process. + * This method is called after attempts had finished. + * At this stage method has access only to variables passed in constructor. + * + * @param Exception $exception + * + * @return void + */ + public function failed(\Exception $e) + { + // Write to activity log + activity() + //->causedBy($this->customer) + ->withProperties([ + 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')', + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_USER); + } +} diff --git a/freescout-dist/app/Jobs/SendReplyToCustomer.php b/freescout-dist/app/Jobs/SendReplyToCustomer.php new file mode 100644 index 0000000..6f813c1 --- /dev/null +++ b/freescout-dist/app/Jobs/SendReplyToCustomer.php @@ -0,0 +1,552 @@ +conversation = $conversation; + $this->threads = $threads; + $this->customer = $customer; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $send_previous_messages = false; + $is_forward = false; + + // When forwarding conversation is undone, new conversation is deleted. + if (!$this->conversation) { + return; + } + + $mailbox = $this->conversation->mailbox; + + // Mailbox may be deleted. + if (!$mailbox) { + return; + } + + // Add forwarded conversation replies. + if ($this->conversation->threads_count == 1 && count($this->threads) == 1) { + $forward_child_thread = $this->threads[0]; + if ($forward_child_thread->isForwarded() && $forward_child_thread->getForwardParentConversation()) { + + // Add replies from original conversation. + $forwarded_replies = $forward_child_thread->getForwardParentConversation()->getReplies(); + $forwarded_replies = Thread::sortThreads($forwarded_replies); + $forward_parent_thread = Thread::find($forward_child_thread->getMetaFw(Thread::META_FORWARD_PARENT_THREAD_ID)); + + if ($forward_parent_thread) { + // Remove threads created after forwarding. + foreach ($forwarded_replies as $i => $thread) { + if ($thread->created_at > $forward_parent_thread->created_at) { + $forwarded_replies->forget($i); + } + } + $this->threads = $this->threads->merge($forwarded_replies); + $is_forward = true; + } + } + } + + // Threads has to be sorted here, if sorted before, they come here in wrong order + $this->threads = Thread::sortThreads($this->threads); + + $new = false; + $headers = []; + + $this->last_thread = $this->threads->first(); + + if ($this->last_thread === null) { + return; + } + $last_customer_thread = null; + + // If thread is draft, it means it has been undone + if ($this->last_thread->isDraft()) { + return; + } + + if (count($this->threads) == 1) { + $new = true; + } + if (!$new) { + $i = 0; + foreach ($this->threads as $thread) { + if ($i > 0 && $thread->type == Thread::TYPE_CUSTOMER) { + $last_customer_thread = $thread; + break; + } + $i++; + } + } + + // In-Reply-To and References headers. + $references = ''; + if (!$new && !empty($last_customer_thread) && $last_customer_thread->message_id) { + + $headers['In-Reply-To'] = '<'.$last_customer_thread->message_id.'>'; + //$headers['References'] = '<'.$last_customer_thread->message_id.'>'; + // https://github.com/freescout-helpdesk/freescout/issues/3175 + $i = 0; + $references_array = []; + foreach ($this->threads as $thread) { + if ($i > 0) { + $reference = $thread->getMessageId(); + if ($reference) { + $references_array[] = $reference; + } + } + $i++; + } + if ($references_array) { + $references = '<'.implode('> <', array_reverse($references_array)).'>'; + } + if ($references) { + $headers['References'] = $references; + } + } + + // Conversation history. + $email_conv_history = config('app.email_conv_history'); + + $threads_count = count($this->threads); + + $meta_conv_history = $this->last_thread->getMeta(Thread::META_CONVERSATION_HISTORY); + if (!empty($meta_conv_history)) { + $email_conv_history = $meta_conv_history; + } + + if ($is_forward && $email_conv_history == 'global') { + $email_conv_history = 'full'; + } + + if ($is_forward && $email_conv_history == 'none') { + $email_conv_history = 'full'; + } + + if ($email_conv_history == 'full') { + $send_previous_messages = true; + } + + if ($email_conv_history == 'last') { + $send_previous_messages = true; + $this->threads = $this->threads->slice(0, 2); + } + + if ($email_conv_history == 'none') { + $send_previous_messages = false; + } + + if (!$is_forward) { + $send_previous_messages = \Eventy::filter('jobs.send_reply_to_customer.send_previous_messages', $send_previous_messages, $this->last_thread, $this->threads, $this->conversation, $this->customer); + } + + // Remove previous messages. + if (!$send_previous_messages) { + $this->threads = $this->threads->slice(0, 1); + } + + // Configure mail driver according to Mailbox settings + \MailHelper::setMailDriver($mailbox, $this->last_thread->created_by_user, $this->conversation); + + // https://github.com/freescout-helpdesk/freescout/issues/3330 + if (!\MailHelper::$smtp_queue_id_plugin_registered) { + \Mail::getSwiftMailer()->registerPlugin(new SwiftGetSmtpQueueId()); + \MailHelper::$smtp_queue_id_plugin_registered = true; + } + + $this->message_id = $this->last_thread->getMessageId($mailbox); + $headers['Message-ID'] = $this->message_id; + + $this->customer_email = $this->conversation->customer_email; + + // For phone conversations we may need to get customer email. + // https://github.com/freescout-helpdesk/freescout/issues/3270 + if (!$this->customer_email && $this->conversation->isPhone()) { + $this->customer_email = $this->conversation->customer->getMainEmail(); + if (!$this->customer_email) { + return; + } + } + + $to_array = $mailbox->removeMailboxEmailsFromList($this->last_thread->getToArray()); + $cc_array = $mailbox->removeMailboxEmailsFromList($this->last_thread->getCcArray()); + $bcc_array = $mailbox->removeMailboxEmailsFromList($this->last_thread->getBccArray()); + + // Remove customer email from CC and BCC + $cc_array = \App\Misc\Mail::removeEmailFromArray($cc_array, $this->customer_email); + $bcc_array = \App\Misc\Mail::removeEmailFromArray($bcc_array, $this->customer_email); + + // Auto Bcc. + if ($mailbox->auto_bcc) { + $auto_bcc = \MailHelper::sanitizeEmails($mailbox->auto_bcc); + if ($auto_bcc) { + $bcc_array = array_merge($bcc_array, $auto_bcc); + } + } + + // Remove from BCC emails which are present in CC + foreach ($cc_array as $cc_email) { + $bcc_array = \App\Misc\Mail::removeEmailFromArray($bcc_array, $cc_email); + } + + $this->recipients = array_merge($to_array, $cc_array, $bcc_array); + + $to = []; + if (count($to_array) > 1) { + $to = $to_array; + } else { + $to = [['name' => $this->customer->getFullName(), 'email' => $this->customer_email]]; + } + + // If sending fails, all recipiens fail. + // if ($this->attempts() > 1) { + // $cc_array = []; + // $bcc_array = []; + // } + + $subject = $this->conversation->subject; + if (!$new && !$is_forward) { + $subject = 'Re: '.$subject; + } + $subject = \Eventy::filter('email.reply_to_customer.subject', $subject, $this->conversation, $this->last_thread); + $this->threads = \Eventy::filter('email.reply_to_customer.threads', $this->threads, $this->conversation, $mailbox); + + $headers['X-FreeScout-Mail-Type'] = 'customer.message'; + + $reply_mail = new ReplyToCustomer($this->conversation, $this->threads, $headers, $mailbox, $subject, $threads_count); + + $smtp_queue_id = null; + + try { + Mail::to($to) + ->cc($cc_array) + ->bcc($bcc_array) + ->send($reply_mail); + + $smtp_queue_id = SwiftGetSmtpQueueId::$last_smtp_queue_id; + } catch (\Exception $e) { + // We come here in case SMTP server unavailable for example + if ($this->attempts() == 1) { + activity() + ->causedBy($this->customer) + ->withProperties([ + 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')', + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER); + } + + // Failures will be saved to send log when retry attempts will finish + // Mail::failures() is empty in case of connection error. + $this->failures = $this->recipients; + + // Save to send log (only first attempt). + if ($this->attempts() == 1) { + $this->saveToSendLog($e->getMessage()); + } + + $error_message = $e->getMessage(); + + // Remove stack trace from error message. + $error_message = preg_replace('#[\r\n]*" in /.*#s', '"', $error_message); + + // SMTP response code is stored in the exception message: + // Expected response code 235 but got code "535", with message... + preg_match('#but got code "(\d+)",#', $error_message, $response_m); + $response_code = (int)($response_m[1] ?? 0); + + // Retry job with delay. + // https://stackoverflow.com/questions/35258175/how-can-i-create-delays-between-failed-queued-job-attempts-in-laravel + if ($this->attempts() < $this->tries && !preg_match("/".config("app.no_retry_mail_errors")."/i", $error_message)) { + if ($this->attempts() == 1) { + // Second attempt after 5 min. + $this->release(300); + } else { + // Others - after 1 hour. + $this->release(3600); + } + + // If an email has not been sent after 1 hour - show an error message to support agent. + if ($this->attempts() >= 3 || $response_code >= 500) { + $this->last_thread->send_status = SendLog::STATUS_SEND_INTERMEDIATE_ERROR; + $this->last_thread->updateSendStatusData(['msg' => $error_message]); + $this->last_thread->save(); + } + + throw $e; + } else { + $this->last_thread->send_status = SendLog::STATUS_SEND_ERROR; + $this->last_thread->updateSendStatusData(['msg' => $error_message]); + $this->last_thread->save(); + + // This executes $this->failed(). + $this->fail($e); + + return; + } + } + + SwiftGetSmtpQueueId::$last_smtp_queue_id = null; + + // Clean error message if email finally has been sent. + if ($this->last_thread->send_status == SendLog::STATUS_SEND_ERROR) { + $this->last_thread->send_status = null; + $this->last_thread->updateSendStatusData(['msg' => '']); + $this->last_thread->save(); + } + + $imap_sent_folder = $mailbox->imap_sent_folder; + if ($imap_sent_folder) { + try { + $client = \MailHelper::getMailboxClient($mailbox); + + $client->connect(); + + $envelope['from'] = $mailbox->getMailFrom(null, $this->conversation)['address']; + $envelope['to'] = $this->customer_email; + $envelope['subject'] = $subject; + $envelope['date'] = now()->toRfc2822String(); + $envelope['message_id'] = $this->message_id; + + // CC. + if (count($cc_array)) { + $envelope['cc'] = implode(',', $cc_array); + } + + // Get penultimate email Message-Id if reply + if (!$new && !empty($last_customer_thread) && $last_customer_thread->message_id) { + $envelope['custom_headers'] = [ + 'In-Reply-To: <'.$last_customer_thread->message_id.'>', + 'References: '.$references, + ]; + } + // Remove new lines to avoid "imap_mail_compose(): header injection attempt in subject". + foreach ($envelope as $i => $envelope_value) { + $envelope[$i] = preg_replace("/[\r\n]/", '', $envelope_value); + } + + $parts = []; + + // Multipart flag. + if ($this->last_thread->has_attachments) { + $multipart = []; + $multipart["type"] = TYPEMULTIPART; + $multipart["subtype"] = "alternative"; + $parts[] = $multipart; + } + + // Body. + $part_body['type'] = TYPETEXT; + $part_body['subtype'] = 'html'; + $part_body['contents.data'] = $reply_mail->render(); + $part_body['charset'] = 'utf-8'; + + $parts[] = $part_body; + + // Add attachments. + if ($this->last_thread->has_attachments) { + + foreach ($this->last_thread->attachments as $attachment) { + + if ($attachment->embedded) { + continue; + } + + if ($attachment->fileExists()) { + $part = []; + $part["type"] = 'APPLICATION'; + $part["encoding"] = ENCBASE64; + $part["subtype"] = "octet-stream"; + $part["description"] = $attachment->file_name; + $part['disposition.type'] = 'attachment'; + $part['disposition'] = array('filename' => $attachment->file_name); + $part['type.parameters'] = array('name' => $attachment->file_name); + $part["description"] = ''; + $part["contents.data"] = base64_encode($attachment->getFileContents()); + + $parts[] = $part; + } else { + \Log::error('[IMAP Folder To Save Outgoing Replies] Thread: '.$this->last_thread->id.'. Attachment file not find on disk: '.$attachment->getLocalFilePath()); + } + } + } + + try { + // https://github.com/freescout-helpdesk/freescout/issues/3502 + $imap_sent_folder = mb_convert_encoding($imap_sent_folder, "UTF7-IMAP","UTF-8"); + + // https://github.com/Webklex/php-imap/issues/380 + if (method_exists($client, 'getFolderByPath')) { + $folder = $client->getFolderByPath($imap_sent_folder); + } else { + $folder = $client->getFolder($imap_sent_folder); + } + // Get folder method does not work if sent folder has spaces. + if ($folder) { + try { + $save_result = $this->saveEmailToFolder($client, $folder, $envelope, $parts, $bcc_array); + + // Sometimes emails with attachments by some reason are not saved. + // https://github.com/freescout-helpdesk/freescout/issues/2749 + if (!$save_result) { + // Save without attachments. + $save_result = $this->saveEmailToFolder($client, $folder, $envelope, [$part_body], $bcc_array); + if (!$save_result) { + \Log::error($this->getImapSaveErrorPrefix($mailbox).'Could not save outgoing reply to the IMAP folder (check folder name and make sure IMAP folder does not have spaces - folders with spaces do not work): '.$imap_sent_folder); + } + } + } catch (\Exception $e) { + // Just log error and continue. + \Helper::logException($e, $this->getImapSaveErrorPrefix($mailbox).'Could not save outgoing reply to the IMAP folder: '); + } + } else { + \Log::error($this->getImapSaveErrorPrefix($mailbox).'Could not save outgoing reply to the IMAP folder (check folder name and make sure IMAP folder does not have spaces - folders with spaces do not work): '.$imap_sent_folder); + } + } catch (\Exception $e) { + // Just log error and continue. + \Helper::logException($e, $this->getImapSaveErrorPrefix($mailbox).'Could not save outgoing reply to the IMAP folder, IMAP folder not found: '.$imap_sent_folder.' - '); + //$this->saveToSendLog('['.date('Y-m-d H:i:s').'] Could not save outgoing reply to the IMAP folder: '.$imap_sent_folder); + } + } catch (\Exception $e) { + // Just log error and continue. + //$this->saveToSendLog('['.date('Y-m-d H:i:s').'] Could not get mailbox IMAP folder: '.$imap_sent_folder); + \Helper::logException($e, $this->getImapSaveErrorPrefix($mailbox).'Could not save outgoing reply to the IMAP folder: '.$imap_sent_folder.' - '); + } + } + + // In message_id we are storing Message-ID of the incoming email which created the thread + // Outcoming message_id can be generated for each thread by thread->id + // $this->last_thread->message_id = $message_id; + // $this->last_thread->save(); + + // Laravel tells us exactly what email addresses failed + $this->failures = Mail::failures(); + + // Save to send log + $this->saveToSendLog('', $smtp_queue_id); + } + + public function getImapSaveErrorPrefix($mailbox) + { + return '['.$mailbox->name.' » Connection Settings » Fetching Emails » IMAP Folder To Save Outgoing Replies] '; + } + + // Save an email to IMAP folder. + public function saveEmailToFolder($client, $folder, $envelope, $parts, $bcc = []) + { + $envelope_str = imap_mail_compose($envelope, $parts); + + // Add BCC. + // https://stackoverflow.com/questions/47353938/php-imap-append-with-bcc + if (!empty($bcc)) { + // There will be a "To:" parameter for sure. + $to_pos = strpos($envelope_str , "To:"); + if ($to_pos !== false) { + $bcc_str = "Bcc: " . implode(',', $bcc) . "\r\n"; + $envelope_str = substr_replace($envelope_str , $bcc_str, $to_pos, 0); + } + } + + if (get_class($client) == 'Webklex\PHPIMAP\Client') { + return $folder->appendMessage($envelope_str, ['\Seen'], now()->format('d-M-Y H:i:s O')); + } else { + return $folder->appendMessage($envelope_str, '\Seen', now()->format('d-M-Y H:i:s O')); + } + } + + /** + * The job failed to process. + * This method is called after attempts had finished. + * At this stage method has access only to variables passed in constructor. + * + * @param Exception $exception + * + * @return void + */ + public function failed(\Exception $e) + { + activity() + ->causedBy($this->customer) + ->withProperties([ + 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')', + 'to' => $this->customer_email, + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER); + + $this->saveToSendLog(); + } + + /** + * Save emails to send log. + */ + public function saveToSendLog($error_message = '', $smtp_queue_id = '') + { + foreach ($this->recipients as $recipient) { + if (in_array($recipient, $this->failures)) { + $status = SendLog::STATUS_SEND_ERROR; + $status_message = $error_message; + } else { + $status = SendLog::STATUS_ACCEPTED; + $status_message = ''; + } + if ($this->customer_email == $recipient) { + $customer_id = $this->customer->id; + } else { + $customer_id = null; + } + SendLog::log($this->last_thread->id, $this->message_id, $recipient, SendLog::MAIL_TYPE_EMAIL_TO_CUSTOMER, $status, $customer_id, null, $status_message, $smtp_queue_id); + } + } +} diff --git a/freescout-dist/app/Jobs/TriggerAction.php b/freescout-dist/app/Jobs/TriggerAction.php new file mode 100644 index 0000000..92bee7c --- /dev/null +++ b/freescout-dist/app/Jobs/TriggerAction.php @@ -0,0 +1,44 @@ +action = $action; + $this->params = $params; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $args = $this->params; + array_unshift($args, $this->action); + + call_user_func_array("\Eventy::action", $args); + } +} diff --git a/freescout-dist/app/Jobs/UpdateFolderCounters.php b/freescout-dist/app/Jobs/UpdateFolderCounters.php new file mode 100644 index 0000000..a012dbd --- /dev/null +++ b/freescout-dist/app/Jobs/UpdateFolderCounters.php @@ -0,0 +1,42 @@ +folder = $folder; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $this->folder->updateCountersNow(); + } +} diff --git a/freescout-dist/app/Listeners/ActivateUser.php b/freescout-dist/app/Listeners/ActivateUser.php new file mode 100644 index 0000000..5d1c002 --- /dev/null +++ b/freescout-dist/app/Listeners/ActivateUser.php @@ -0,0 +1,35 @@ +user->invite_state != User::INVITE_STATE_ACTIVATED) { + $event->user->invite_state = User::INVITE_STATE_ACTIVATED; + $event->user->invite_hash = ''; + $event->user->save(); + } + } +} diff --git a/freescout-dist/app/Listeners/LogFailedLogin.php b/freescout-dist/app/Listeners/LogFailedLogin.php new file mode 100644 index 0000000..9057fbc --- /dev/null +++ b/freescout-dist/app/Listeners/LogFailedLogin.php @@ -0,0 +1,34 @@ +causedBy($event->user) + ->withProperties(['ip' => app('request')->ip(), 'email' => request()->email]) + ->useLog(\App\ActivityLog::NAME_USER) + ->log(\App\ActivityLog::DESCRIPTION_USER_LOGIN_FAILED); + } +} diff --git a/freescout-dist/app/Listeners/LogLockout.php b/freescout-dist/app/Listeners/LogLockout.php new file mode 100644 index 0000000..32be985 --- /dev/null +++ b/freescout-dist/app/Listeners/LogLockout.php @@ -0,0 +1,34 @@ +causedBy($event->user) + ->withProperties(['ip' => app('request')->ip(), 'email' => $event->request->email]) + ->useLog(\App\ActivityLog::NAME_USER) + ->log(\App\ActivityLog::DESCRIPTION_USER_LOCKED); + } +} diff --git a/freescout-dist/app/Listeners/LogPasswordReset.php b/freescout-dist/app/Listeners/LogPasswordReset.php new file mode 100644 index 0000000..9cb72c2 --- /dev/null +++ b/freescout-dist/app/Listeners/LogPasswordReset.php @@ -0,0 +1,34 @@ +causedBy($event->user) + ->withProperties(['ip' => app('request')->ip()]) + ->useLog(\App\ActivityLog::NAME_USER) + ->log(\App\ActivityLog::DESCRIPTION_USER_PASSWORD_RESET); + } +} diff --git a/freescout-dist/app/Listeners/LogRegisteredUser.php b/freescout-dist/app/Listeners/LogRegisteredUser.php new file mode 100644 index 0000000..253a7dd --- /dev/null +++ b/freescout-dist/app/Listeners/LogRegisteredUser.php @@ -0,0 +1,34 @@ +causedBy($event->user) + ->withProperties(['ip' => app('request')->ip()]) + ->useLog(\App\ActivityLog::NAME_USER) + ->log(\App\ActivityLog::DESCRIPTION_USER_REGISTER); + } +} diff --git a/freescout-dist/app/Listeners/LogSuccessfulLogin.php b/freescout-dist/app/Listeners/LogSuccessfulLogin.php new file mode 100644 index 0000000..f1e8cd5 --- /dev/null +++ b/freescout-dist/app/Listeners/LogSuccessfulLogin.php @@ -0,0 +1,34 @@ +causedBy($event->user) + ->withProperties(['ip' => app('request')->ip()]) + ->useLog(\App\ActivityLog::NAME_USER) + ->log(\App\ActivityLog::DESCRIPTION_USER_LOGIN); + } +} diff --git a/freescout-dist/app/Listeners/LogSuccessfulLogout.php b/freescout-dist/app/Listeners/LogSuccessfulLogout.php new file mode 100644 index 0000000..7672d66 --- /dev/null +++ b/freescout-dist/app/Listeners/LogSuccessfulLogout.php @@ -0,0 +1,34 @@ +causedBy($event->user) + ->withProperties(['ip' => app('request')->ip()]) + ->useLog(\App\ActivityLog::NAME_USER) + ->log(\App\ActivityLog::DESCRIPTION_USER_LOGOUT); + } +} diff --git a/freescout-dist/app/Listeners/LogUserDeletion.php b/freescout-dist/app/Listeners/LogUserDeletion.php new file mode 100644 index 0000000..9cee7b4 --- /dev/null +++ b/freescout-dist/app/Listeners/LogUserDeletion.php @@ -0,0 +1,34 @@ +causedBy($event->by_user) + ->withProperties(['deleted_user' => $event->deleted_user->getFullName().' ['.$event->deleted_user->id.']']) + ->useLog(\App\ActivityLog::NAME_USER) + ->log(\App\ActivityLog::DESCRIPTION_USER_DELETED); + } +} diff --git a/freescout-dist/app/Listeners/ProcessSwiftMessage.php b/freescout-dist/app/Listeners/ProcessSwiftMessage.php new file mode 100644 index 0000000..459a31b --- /dev/null +++ b/freescout-dist/app/Listeners/ProcessSwiftMessage.php @@ -0,0 +1,25 @@ +message); + } +} diff --git a/freescout-dist/app/Listeners/RefreshConversations.php b/freescout-dist/app/Listeners/RefreshConversations.php new file mode 100644 index 0000000..3297967 --- /dev/null +++ b/freescout-dist/app/Listeners/RefreshConversations.php @@ -0,0 +1,30 @@ +conversation, $event->thread ?? $event->last_thread); + } +} diff --git a/freescout-dist/app/Listeners/RememberUserLocale.php b/freescout-dist/app/Listeners/RememberUserLocale.php new file mode 100644 index 0000000..944aa79 --- /dev/null +++ b/freescout-dist/app/Listeners/RememberUserLocale.php @@ -0,0 +1,31 @@ +put('user_locale', $event->user->getLocale()); + } +} diff --git a/freescout-dist/app/Listeners/RestartSwiftMailer.php b/freescout-dist/app/Listeners/RestartSwiftMailer.php new file mode 100644 index 0000000..0277bf7 --- /dev/null +++ b/freescout-dist/app/Listeners/RestartSwiftMailer.php @@ -0,0 +1,30 @@ +conversation; + + // no_autoreply meta value is checked in the SendAutoReply job. + + if (!$conversation->imported + && $conversation->mailbox->auto_reply_enabled + ) { + $thread = $conversation->threads()->first(); + + // Do not send auto reply to auto responders. + if ($thread->isAutoResponder()) { + return; + } + // Do not send auto replies to bounces. + if ($thread->isBounce()) { + return; + } + + // Do not send auto replies to spam messages. + if ($conversation->status == Conversation::STATUS_SPAM) { + return; + } + + if (!\Eventy::filter('autoreply.should_send', true, $conversation)) { + return; + } + + // We can not send auto reply to incoming bounce messages, as it will lead to the infinite loop: + // application will be sending auto replies and mail server will be sending bounce messages to auto replies. + // Bounce detection can not be 100% reliable. + // So to prevent infinite loop, we are checking number of auto replies sent to the customer recently. + $created_at = \Illuminate\Support\Carbon::now()->subMinutes(self::CHECK_PERIOD); + + $auto_replies_sent = SendLog::where('customer_id', $conversation->customer_id) + ->where('mail_type', SendLog::MAIL_TYPE_AUTO_REPLY) + ->where('created_at', '>', $created_at) + ->count(); + + if ($auto_replies_sent >= 10) { + return; + } + + if ($auto_replies_sent >= 2) { + // Find conversations from this customer + $prev_conversations = Conversation::select('subject', 'id') + ->where('customer_id', $conversation->customer_id) + ->where('created_at', '>', $created_at) + ->get(); + + foreach ($prev_conversations as $prev_conv) { + if ($prev_conv->subject == $conversation->subject && $prev_conv->id != $conversation->id) { + return; + } + } + } + + // Do not send autoreplies to own mailboxes. + if ($conversation->customer_email) { + $is_internal_email = Mailbox::where('email', $conversation->customer_email)->exists(); + if ($is_internal_email) { + return; + } + } + + // 24h limit has been disabled: https://github.com/freescout-helpdesk/freescout/pull/95 + // Send auto reply once in 24h + /*$created_at = \Illuminate\Support\Carbon::now()->subDays(1); + $auto_reply_sent = SendLog::where('customer_id', $conversation->customer_id) + ->where('mail_type', SendLog::MAIL_TYPE_AUTO_REPLY) + ->where('created_at', '>', $created_at) + ->first(); + + if ($auto_reply_sent) { + return; + }*/ + + \App\Jobs\SendAutoReply::dispatch($conversation, $thread, $conversation->mailbox, $conversation->customer) + ->onQueue('emails'); + } + } +} diff --git a/freescout-dist/app/Listeners/SendNotificationToUsers.php b/freescout-dist/app/Listeners/SendNotificationToUsers.php new file mode 100644 index 0000000..454a7bf --- /dev/null +++ b/freescout-dist/app/Listeners/SendNotificationToUsers.php @@ -0,0 +1,75 @@ +thread->created_by_user_id; + $event_type = Subscription::EVENT_TYPE_USER_REPLIED; + break; + case 'App\Events\UserAddedNote': + $caused_by_user_id = $event->thread->created_by_user_id; + // When conversation is forwarded only notification + // about child forward conversation is sent. + if (!$event->thread->isForward()) { + $event_type = Subscription::EVENT_TYPE_USER_ADDED_NOTE; + } + break; + case 'App\Events\UserCreatedConversation': + $caused_by_user_id = $event->conversation->created_by_user_id; + $event_type = Subscription::EVENT_TYPE_NEW; + break; + case 'App\Events\CustomerCreatedConversation': + // Do not send notification if conversation is spam. + if ($event->conversation->status != Conversation::STATUS_SPAM) { + $event_type = Subscription::EVENT_TYPE_NEW; + } + break; + case 'App\Events\ConversationUserChanged': + $caused_by_user_id = $event->user->id; + $event_type = Subscription::EVENT_TYPE_ASSIGNED; + break; + case 'App\Events\CustomerReplied': + $event_type = Subscription::EVENT_TYPE_CUSTOMER_REPLIED; + break; + } + if (empty($event->conversation) || !$event_type) { + return; + } + + // Ignore imported threads. + if (!empty($event->thread) && $event->thread->imported) { + return; + } + $conversation = $event->conversation; + + // Using the last argument you can make event to be processed immediately + Subscription::registerEvent($event_type, $conversation, $caused_by_user_id/*, true*/); + } +} diff --git a/freescout-dist/app/Listeners/SendPasswordChanged.php b/freescout-dist/app/Listeners/SendPasswordChanged.php new file mode 100644 index 0000000..44ab7c9 --- /dev/null +++ b/freescout-dist/app/Listeners/SendPasswordChanged.php @@ -0,0 +1,30 @@ +user->sendPasswordChanged(); + } +} diff --git a/freescout-dist/app/Listeners/SendReplyToCustomer.php b/freescout-dist/app/Listeners/SendReplyToCustomer.php new file mode 100644 index 0000000..306e890 --- /dev/null +++ b/freescout-dist/app/Listeners/SendReplyToCustomer.php @@ -0,0 +1,67 @@ +conversation; + + // Do not send email if this is a Phone conversation and customer has no email. + if ($conversation->isPhone()) { + if (!$conversation->customer->getMainEmail()) { + return; + } + } + + $replies = $conversation->getReplies(); + + // Ignore imported messages. + if ($replies && $replies->first() && $replies->first()->imported) { + return; + } + + // Remove threads added after this event had fired. + $thread = $event->last_thread ?? $event->thread ?? null; + if ($thread) { + foreach ($replies as $i => $reply) { + if ($reply->id == $thread->id) { + break; + } else { + $replies->forget($i); + } + } + } + + // Chat conversation. + if ($conversation->isChat()) { + \Helper::backgroundAction('chat_conversation.send_reply', [$conversation, $replies, $conversation->customer], now()->addSeconds(Conversation::UNDO_TIMOUT)); + return; + } + + // We can not check imported here, as after conversation has been imported via API + // notifications has to be sent. + //if (!$conversation->imported) { + $delay = \Eventy::filter('conversation.send_reply_to_customer_delay', now()->addSeconds(Conversation::UNDO_TIMOUT), $conversation, $replies); + + \App\Jobs\SendReplyToCustomer::dispatch($conversation, $replies, $conversation->customer) + ->delay($delay) + ->onQueue('emails'); + } +} diff --git a/freescout-dist/app/Listeners/UpdateMailboxCounters.php b/freescout-dist/app/Listeners/UpdateMailboxCounters.php new file mode 100644 index 0000000..481aa44 --- /dev/null +++ b/freescout-dist/app/Listeners/UpdateMailboxCounters.php @@ -0,0 +1,28 @@ +conversation->mailbox->updateFoldersCounters(); + } +} diff --git a/freescout-dist/app/Mail/Alert.php b/freescout-dist/app/Mail/Alert.php new file mode 100644 index 0000000..6e34a94 --- /dev/null +++ b/freescout-dist/app/Mail/Alert.php @@ -0,0 +1,50 @@ +text = $text; + $this->title = $title; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + \MailHelper::prepareMailable($this); + + $subject = '['.\Config::get('app.name').'] '; + if (!empty($this->title)) { + $subject .= $this->title; + } else { + // System emails are not translated + $subject .= 'Alert'; + } + $subject .= ' - '.\Helper::getDomain(); + $message = $this->subject($subject) + ->view('emails/user/alert', ['text' => $this->text, 'title' => $this->title]); + + return $message; + } +} diff --git a/freescout-dist/app/Mail/AutoReply.php b/freescout-dist/app/Mail/AutoReply.php new file mode 100644 index 0000000..cbda1ca --- /dev/null +++ b/freescout-dist/app/Mail/AutoReply.php @@ -0,0 +1,96 @@ +conversation = $conversation; + $this->mailbox = $mailbox; + $this->customer = $customer; + $this->headers = $headers; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + \MailHelper::prepareMailable($this); + + $view_params = []; + + // Set headers + $this->setHeaders(); + + $data = [ + 'mailbox' => $this->mailbox, + 'conversation' => $this->conversation, + 'customer' => $this->customer, + ]; + + // Set variables + $subject = \MailHelper::replaceMailVars($this->mailbox->auto_reply_subject, $data); + $view_params['auto_reply_message'] = \MailHelper::replaceMailVars($this->mailbox->auto_reply_message, $data); + + $subject = \Eventy::filter('email.auto_reply.subject', $subject, $this->conversation); + + $message = $this->subject($subject) + ->view('emails/customer/auto_reply', $view_params) + ->text('emails/customer/auto_reply_text', $view_params); + + return $message; + } + + /** + * Set headers. + * Settings via $this->addCustomHeaders does not work. + */ + public function setHeaders() + { + $new_headers = $this->headers; + if (!empty($new_headers)) { + $this->withSwiftMessage(function ($swiftmessage) use ($new_headers) { + if (!empty($new_headers['Message-ID'])) { + $swiftmessage->setId($new_headers['Message-ID']); + } + $headers = $swiftmessage->getHeaders(); + foreach ($new_headers as $header => $value) { + if ($header != 'Message-ID') { + $headers->addTextHeader($header, $value); + } + } + + return $swiftmessage; + }); + } + } +} diff --git a/freescout-dist/app/Mail/PasswordChanged.php b/freescout-dist/app/Mail/PasswordChanged.php new file mode 100644 index 0000000..befa836 --- /dev/null +++ b/freescout-dist/app/Mail/PasswordChanged.php @@ -0,0 +1,37 @@ +user = $user; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + \MailHelper::prepareMailable($this); + + $message = $this->subject(__('Password Changed')) + ->view('emails/user/password_changed') + ->text('emails/user/password_changed_text'); + + return $message; + } +} diff --git a/freescout-dist/app/Mail/ReplyToCustomer.php b/freescout-dist/app/Mail/ReplyToCustomer.php new file mode 100644 index 0000000..1850a8f --- /dev/null +++ b/freescout-dist/app/Mail/ReplyToCustomer.php @@ -0,0 +1,188 @@ +conversation = $conversation; + $this->threads = $threads; + $this->headers = $headers; + $this->mailbox = $mailbox; + $this->subject = $subject; + $this->threads_count = $threads_count; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + \MailHelper::prepareMailable($this); + + $thread = $this->threads->first(); + $from_alias = trim($thread->from ?? ''); + + // Set Message-ID + // Settings via $this->addCustomHeaders does not work + $new_headers = $this->headers; + if (!empty($new_headers) || $from_alias) { + $mailbox = $this->mailbox; + $this->withSwiftMessage(function ($swiftmessage) use ($new_headers, $from_alias, $mailbox, $thread) { + if (!empty($new_headers)) { + if (!empty($new_headers['Message-ID'])) { + $swiftmessage->setId($new_headers['Message-ID']); + } + $headers = $swiftmessage->getHeaders(); + foreach ($new_headers as $header => $value) { + if ($header != 'Message-ID') { + $headers->addTextHeader($header, $value); + } + } + } + if (!empty($from_alias)) { + $aliases = $mailbox->getAliases(); + + // Make sure that the From contains a mailbox alias, + // as user thread may have From specified when a user + // replies to an email notification. + if (array_key_exists($from_alias, $aliases)) { + + $from_alias_name = $aliases[$from_alias] ?? ''; + + // Take into account mailbox From Name setting. + $mailbox_mail_from = $mailbox->getMailFrom($thread->created_by_user, $thread->conversation); + if ($mailbox_mail_from['name'] == $mailbox->name && $from_alias_name) { + // Use name from alias. + } else { + // User name or custom. + $from_alias_name = $mailbox_mail_from['name']; + } + + $swift_from = $headers->get('From'); + + if ($from_alias_name) { + $swift_from->setNameAddresses([ + $from_alias => $from_alias_name + ]); + } else { + $swift_from->setAddresses([ + $from_alias + ]); + } + } + } + + return $swiftmessage; + }); + } + + // from($this->from) Sets only email, name stays empty. + // So we set from in Mail::setMailDriver + $message = $this->subject($this->subject) + ->view('emails/customer/reply_fancy') + ->text('emails/customer/reply_fancy_text'); + + if ($thread->has_attachments) { + foreach ($thread->attachments as $attachment) { + if ($attachment->fileExists()) { + $message->attach($attachment->getLocalFilePath()); + } else { + \Log::error('[ReplyToCustomer] Thread: '.$thread->id.'. Attachment file not find on disk: '.$attachment->getLocalFilePath()); + } + } + } + + return $message; + } + + /* + * Send the message using the given mailer. + * + * @param \Illuminate\Contracts\Mail\Mailer $mailer + * @return void + */ + // public function send(MailerContract $mailer) + // { + // Container::getInstance()->call([$this, 'build']); + + // $mailer->send($this->buildView(), $this->buildViewData(), function ($message) { + // $this->buildFrom($message) + // ->buildRecipients($message) + // ->buildSubject($message) + // ->buildAttachments($message) + // ->addCustomHeaders($message) // This is new! + // ->runCallbacks($message); + // }); + // } + + /* + * Add custom headers to the message. + * + * @param \Illuminate\Mail\Message $message + * @return $this + */ + // protected function addCustomHeaders($message) + // { + // $swift = $message->getSwiftMessage(); + // $headers = $swift->getHeaders(); + + // // By some reason $this->headers are empty here + // foreach ($this->headers as $header => $value) { + // $headers->addTextHeader($header, $value); + // } + // return $this; + // } +} diff --git a/freescout-dist/app/Mail/Test.php b/freescout-dist/app/Mail/Test.php new file mode 100644 index 0000000..2c20ce1 --- /dev/null +++ b/freescout-dist/app/Mail/Test.php @@ -0,0 +1,44 @@ +mailbox = $mailbox; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + \MailHelper::prepareMailable($this); + + $this->withSwiftMessage(function ($swiftmessage) { + $headers = $swiftmessage->getHeaders(); + $headers->addTextHeader('X-FreeScout-Mail-Type', 'test.mailbox'); + + return $swiftmessage; + }); + + $message = $this->subject(__(':app_name Test Email', ['app_name' => \Config::get('app.name')])); + if ($this->mailbox) { + $message->view('emails/user/test', ['mailbox' => $this->mailbox]); + } else { + $message->view('emails/user/test_system'); + } + + return $message; + } +} diff --git a/freescout-dist/app/Mail/UserEmailReplyError.php b/freescout-dist/app/Mail/UserEmailReplyError.php new file mode 100644 index 0000000..0bd4de3 --- /dev/null +++ b/freescout-dist/app/Mail/UserEmailReplyError.php @@ -0,0 +1,33 @@ +subject(__('Unable to process your update')) + ->view('emails/user/email_reply_error'); + } +} diff --git a/freescout-dist/app/Mail/UserInvite.php b/freescout-dist/app/Mail/UserInvite.php new file mode 100644 index 0000000..4d5dd54 --- /dev/null +++ b/freescout-dist/app/Mail/UserInvite.php @@ -0,0 +1,38 @@ +user = $user; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + \MailHelper::prepareMailable($this); + + $message = $this->subject(__('Welcome to :company_name!', ['company_name' => Option::getCompanyName()])) + ->view('emails/user/user_invite') + ->text('emails/user/user_invite_text'); + + return $message; + } +} diff --git a/freescout-dist/app/Mail/UserNotification.php b/freescout-dist/app/Mail/UserNotification.php new file mode 100644 index 0000000..b1e5937 --- /dev/null +++ b/freescout-dist/app/Mail/UserNotification.php @@ -0,0 +1,105 @@ +user = $user; + $this->conversation = $conversation; + $this->threads = $threads; + $this->headers = $headers; + $this->from = $from; + $this->mailbox = $mailbox; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + \MailHelper::prepareMailable($this); + + // Set Message-ID + // Settings via $this->addCustomHeaders does not work + $new_headers = $this->headers; + if (!empty($new_headers)) { + $this->withSwiftMessage(function ($swiftmessage) use ($new_headers) { + if (!empty($new_headers['Message-ID'])) { + $swiftmessage->setId($new_headers['Message-ID']); + } + $headers = $swiftmessage->getHeaders(); + foreach ($new_headers as $header => $value) { + if ($header != 'Message-ID') { + $headers->addTextHeader($header, $value); + } + } + + return $swiftmessage; + }); + } + + $subject = '[#'.$this->conversation->number.'] '.$this->conversation->subject; + + $customer = $this->conversation->customer; + + $thread = $this->threads->first(); + + return $this->subject($subject) + ->from($this->from['address'], $this->from['name']) + ->view('emails/user/notification', ['customer' => $customer, 'thread' => $thread, 'mailbox' => $this->mailbox]) + ->text('emails/user/notification_text', ['customer' => $customer, 'thread' => $thread, 'mailbox' => $this->mailbox]); + } +} diff --git a/freescout-dist/app/Mailbox.php b/freescout-dist/app/Mailbox.php new file mode 100644 index 0000000..7286737 --- /dev/null +++ b/freescout-dist/app/Mailbox.php @@ -0,0 +1,978 @@ + '', + self::OUT_ENCRYPTION_SSL => 'ssl', + self::OUT_ENCRYPTION_TLS => 'tls', + ]; + + /** + * Incoming protocol. + */ + const IN_PROTOCOL_IMAP = 1; + const IN_PROTOCOL_POP3 = 2; + + public static $in_protocols = [ + self::IN_PROTOCOL_IMAP => 'imap', + self::IN_PROTOCOL_POP3 => 'pop3', + ]; + + /** + * Incoming encryption. + */ + const IN_ENCRYPTION_NONE = 1; + const IN_ENCRYPTION_SSL = 2; + const IN_ENCRYPTION_TLS = 3; + + public static $in_encryptions = [ + self::IN_ENCRYPTION_NONE => '', + self::IN_ENCRYPTION_SSL => 'ssl', + self::IN_ENCRYPTION_TLS => 'tls', + ]; + + /** + * Ratings Playcement: place ratings text above/below signature. + */ + const RATINGS_PLACEMENT_ABOVE = 1; + const RATINGS_PLACEMENT_BELOW = 2; + + /** + * Access permissions. + */ + const ACCESS_PERM_EDIT = 'edit'; + const ACCESS_PERM_PERMISSIONS = 'perm'; + const ACCESS_PERM_AUTO_REPLIES = 'auto'; + const ACCESS_PERM_SIGNATURE = 'sig'; + const ACCESS_PERM_ASSIGNED = 'asg'; + + public static $access_permissions = [ + self::ACCESS_PERM_EDIT, + self::ACCESS_PERM_PERMISSIONS, + self::ACCESS_PERM_AUTO_REPLIES, + self::ACCESS_PERM_SIGNATURE, + ]; + + public static $access_routes = [ + self::ACCESS_PERM_EDIT => 'mailboxes.update', + self::ACCESS_PERM_PERMISSIONS => 'mailboxes.permissions', + self::ACCESS_PERM_AUTO_REPLIES => 'mailboxes.auto_reply', + self::ACCESS_PERM_SIGNATURE => 'mailboxes.update', + ]; + + /** + * Default signature set when mailbox created. + */ + const DEFAULT_SIGNATURE = '
--
+{%mailbox.name%}
'; + + /** + * Default values. + */ + protected $attributes = [ + 'signature' => self::DEFAULT_SIGNATURE, + ]; + + protected $casts = [ + 'meta' => 'array', + ]; + + /** + * Attributes fillable using fill() method. + * + * @var [type] + */ + protected $fillable = ['name', 'email', 'aliases', 'aliases_reply', 'auto_bcc', 'from_name', 'from_name_custom', 'ticket_status', 'ticket_assignee', 'template', 'before_reply', 'signature', 'out_method', 'out_server', 'out_username', 'out_password', 'out_port', 'out_encryption', 'in_server', 'in_port', 'in_username', 'in_password', 'in_protocol', 'in_encryption', 'in_validate_cert', 'auto_reply_enabled', 'auto_reply_subject', 'auto_reply_message', 'office_hours_enabled', 'ratings', 'ratings_placement', 'ratings_text', 'imap_sent_folder']; + + protected static function boot() + { + parent::boot(); + + // self::created(function (Mailbox $model) { + // $model->slug = strtolower(substr(md5(Hash::make($model->id)), 0, 16)); + // }); + } + + /** + * Automatically encrypt password on save. + */ + public function setInPasswordAttribute($value) + { + if ($value != '') { + $this->attributes['in_password'] = encrypt($value); + } else { + $this->attributes['in_password'] = ''; + } + } + + /** + * Automatically decrypt password on read. + */ + public function getInPasswordAttribute($value) + { + if (!$value) { + return ''; + } + + try { + return decrypt($value); + } catch (\Exception $e) { + // do nothing if decrypt wasn't succefull + return ''; + } + } + + /** + * Automatically encrypt password on save. + */ + public function setOutPasswordAttribute($value) + { + if ($value != '') { + $this->attributes['out_password'] = encrypt($value); + } else { + $this->attributes['out_password'] = ''; + } + } + + /** + * Automatically decrypt password on read. + */ + public function getOutPasswordAttribute($value) + { + if (!$value) { + return ''; + } + + try { + return decrypt($value); + } catch (\Exception $e) { + // do nothing if decrypt wasn't succefull + return ''; + } + } + + /** + * Get users having access to the mailbox. + */ + public function users() + { + return $this->belongsToMany('App\User'); + } + + public function usersWithSettings() + { + return $this->belongsToMany('App\User')->as('settings') + ->withPivot('after_send') + ->withPivot('hide') + ->withPivot('mute') + ->withPivot('access'); + } + + /** + * Get users having access to the mailbox. + */ + public function users_cached() + { + return $this->users()->rememberForever(); + } + + /** + * Get mailbox conversations. + */ + public function conversations() + { + return $this->hasMany('App\Conversation'); + } + + /** + * Get mailbox folders. + */ + public function folders() + { + return $this->hasMany('App\Folder'); + } + + /** + * Create personal folders for users. + * + * @param mixed $users + */ + public function syncPersonalFolders($users = null) + { + if (!empty($users) && is_array($users)) { + $user_ids = $users; + } else { + $user_ids = $this->users()->pluck('users.id')->toArray(); + } + + // Add admins + $admin_user_ids = User::where('role', User::ROLE_ADMIN)->pluck('id')->toArray(); + $user_ids = array_merge($user_ids, $admin_user_ids); + + self::createUsersFolders($user_ids, $this->id, Folder::$personal_types); + } + + /** + * Created folders of specific type for passed users. + */ + public static function createUsersFolders($user_ids, $mailbox_id, $folder_types) + { + $cur_users = Folder::select('user_id') + ->where('mailbox_id', $mailbox_id) + ->whereIn('user_id', $user_ids) + ->groupBy('user_id') + ->pluck('user_id') + ->toArray(); + + foreach ($user_ids as $user_id) { + if (in_array($user_id, $cur_users)) { + continue; + } + foreach ($folder_types as $type) { + Folder::create([ + 'mailbox_id' => $mailbox_id, + 'user_id' => $user_id, + 'type' => $type, + ]); + } + } + } + + public function createPublicFolders() + { + foreach (Folder::$public_types as $type) { + $folder = new Folder(); + $folder->mailbox_id = $this->id; + $folder->type = $type; + $folder->save(); + } + } + + public function createAdminPersonalFolders() + { + $user_ids = User::where('role', User::ROLE_ADMIN)->pluck('id')->toArray(); + self::createUsersFolders($user_ids, $this->id, Folder::$personal_types); + } + + public static function createAdminPersonalFoldersAllMailboxes($user_ids = null) + { + if (empty($user_ids)) { + $user_ids = User::where('role', User::ROLE_ADMIN)->pluck('id')->toArray(); + } + $mailbox_ids = self::pluck('id'); + foreach ($mailbox_ids as $mailbox_id) { + self::createUsersFolders($user_ids, $mailbox_id, Folder::$personal_types); + } + } + + /** + * Get folders for the dashboard. + */ + public function getMainFolders() + { + $main_folders = \Eventy::filter('mailbox.main_folders', [], $this); + if ($main_folders) { + return $main_folders; + } + + return $this->folders() + ->where(function ($query) { + $query->whereIn('type', [Folder::TYPE_UNASSIGNED, Folder::TYPE_ASSIGNED, Folder::TYPE_DRAFTS]) + ->orWhere(function ($query2) { + $query2->where(['type' => Folder::TYPE_MINE]); + $query2->where(['user_id' => auth()->user()->id]); + }) + ->orWhere(function ($query3) { + $query3->where(['type' => Folder::TYPE_STARRED]); + $query3->where(['user_id' => auth()->user()->id]); + }); + }) + ->orderBy('type') + ->get(); + } + + /** + * Get folder by it's type. + */ + public function getFolderByType($type) + { + return $this->folders() + ->where('type', $type) + ->first(); + } + + /** + * Get folders available for the current user. + */ + public function getAssesibleFolders() + { + $folders = $this->folders() + ->where(function ($query) { + $query->whereIn('type', \Eventy::filter('mailbox.folders.public_types', Folder::$public_types)) + ->orWhere(function ($query2) { + $query2->whereIn('type', Folder::$personal_types); + $query2->where(['user_id' => auth()->user()->id]); + }); + }) + ->orderBy('type') + ->get(); + + return \Eventy::filter('mailbox.folders', $folders, $this); + } + + /** + * Update total and active counters for folders. + */ + public function updateFoldersCounters($folder_type = null) + { + if (!$folder_type) { + $folders = $this->folders; + } else { + $folders = $this->folders()->where('folders.type', $folder_type)->get(); + } + + foreach ($folders as $folder) { + $folder->updateCounters(); + } + } + + /** + * Is mailbox available for using. + * + * @return bool + */ + public function isActive() + { + return $this->isInActive() && $this->isOutActive(); + } + + /** + * Is receiving emails configured for the mailbox. + * + * @return bool + */ + public function isInActive() + { + $in_active = \Eventy::filter('mailbox.in_active', null, $this); + + if (is_bool($in_active)) { + return $in_active; + } + + if ($this->in_protocol && $this->in_server && $this->in_port && $this->in_username && $this->in_password) { + return true; + } else { + return false; + } + } + + /** + * Is sending emails configured for the mailbox. + * + * @return bool + */ + public function isOutActive() + { + if ($this->out_method != self::OUT_METHOD_PHP_MAIL && $this->out_method != self::OUT_METHOD_SENDMAIL + && (!$this->out_server /*|| !$this->out_username || !$this->out_password*/) + ) { + return false; + } else { + return true; + } + } + + /** + * Get users who have access to the mailbox. + */ + public function usersHavingAccess($cache = false, $fields = 'users.*', $sort = true) + { + $admins = User::where('role', User::ROLE_ADMIN)->select($fields)->remember(\Helper::cacheTime($cache))->get(); + + $users = $this->users()->select($fields)->remember(\Helper::cacheTime($cache))->get()->merge($admins)->unique(); + + // Exclude deleted users (better to do it in PHP). + foreach ($users as $i => $user) { + if (!$user->isActive()) { + $users->forget($i); + } + } + + // Sort by full name + if ($sort) { + $users = User::sortUsers($users); + } + + $users = \Eventy::filter('mailbox.users_having_access', $users, $this, $cache, $fields, $sort); + + return $users; + } + + public function usersAssignable($cache = true, $exclude_hidden = true) + { + // Exclude hidden admins. + $mailbox_id = $this->id; + $admins = User::select(['users.*', 'mailbox_user.hide']) + ->leftJoin('mailbox_user', function ($join) use ($mailbox_id) { + $join->on('mailbox_user.user_id', '=', 'users.id'); + $join->where('mailbox_user.mailbox_id', $mailbox_id); + }) + ->where('role', User::ROLE_ADMIN) + ->remember(\Helper::cacheTime($cache)) + ->get(); + + $users = $this->users()->select(['users.*', 'mailbox_user.hide']) + ->remember(\Helper::cacheTime($cache)) + ->get() + ->merge($admins) + ->unique(); + + if ($exclude_hidden) { + foreach ($users as $i => $user) { + if (!empty($user->hide)) { + $users->forget($i); + } + } + } + + // Exclude deleted users (better to do it in PHP). + foreach ($users as $i => $user) { + if (!$user->isActive()) { + $users->forget($i); + } + } + + // Sort by full name + $users = User::sortUsers($users); + + $users = \Eventy::filter('mailbox.users_assignable', $users, $this, $cache); + + return $users; + } + + /** + * Get users IDs who have access to the mailbox. + */ + public function userIdsHavingAccess() + { + return $this->usersHavingAccess(false, ['users.id', 'users.status'])->pluck('id')->toArray(); + + /*$user_ids = $this->users()->pluck('users.id'); + $admin_ids = User::where('role', User::ROLE_ADMIN)->pluck('id'); + + return $user_ids->merge($admin_ids)->unique()->toArray();*/ + } + + /** + * Check if user has access to the mailbox. + * + * @return bool + */ + public function userHasAccess($user_id, $user = null) + { + if (!$user) { + if ($user_id instanceof \App\User) { + $user = $user_id; + } else { + $user = User::find($user_id); + } + } + $filter = \Eventy::filter('mailbox.user_has_access', -1, $this, $user); + if ($filter != -1) { + return (bool)$filter; + } elseif ($user && $user->isAdmin()) { + return true; + } else { + return (bool) $this->users()->where('users.id', $user_id)->count(); + } + } + + /** + * Get From array for the Mail function. + * + * @param App\User $from_user + * @param App\Conversation $conversation + * + * @return array + */ + public function getMailFrom($from_user = null, $conversation = null) + { + // Mailbox name by default + $name = $this->name; + + if ($this->from_name == self::FROM_NAME_CUSTOM && $this->from_name_custom) { + $data = [ + 'mailbox' => $this, + 'mailbox_from_name' => '', // To avoid recursion. + 'conversation' => $conversation, + 'user' => $from_user ?: auth()->user(), + ]; + $name = \MailHelper::replaceMailVars($this->from_name_custom, $data, false, true); + } elseif ($this->from_name == self::FROM_NAME_USER && $from_user) { + $name = $from_user->getFullName(); + } + + return [ + 'address' => \Eventy::filter('mailbox.get_mail_from_address', $this->email, $from_user, $conversation), + 'name' => \Eventy::filter('mailbox.get_mail_from_name', $name, $from_user, $conversation) + ]; + } + + /** + * Get corresponding Laravel mail driver name. + */ + public function getMailDriverName() + { + switch ($this->out_method) { + case self::OUT_METHOD_PHP_MAIL: + return 'mail'; + + case self::OUT_METHOD_SENDMAIL: + return 'sendmail'; + + case self::OUT_METHOD_SMTP: + return 'smtp'; + + default: + return 'mail'; + } + } + + /** + * Get domain part of the mailbox email. + * + * @return string + */ + public function getEmailDomain() + { + return explode('@', $this->email)[1]; + } + + /** + * Get outgoing email encryption protocol. + * + * @return string + */ + public function getOutEncryptionName() + { + return self::$out_encryptions[$this->out_encryption]; + } + + /** + * Get incoming email encryption protocol. + * + * @return string + */ + public function getInEncryptionName() + { + return self::$in_encryptions[$this->in_encryption]; + } + + /** + * Get incoming protocol name. + * + * @return string + */ + public function getInProtocolName() + { + return $this->getInProtocols()[$this->in_protocol] ?? ''; + } + + /** + * Get incoming protocol display name for the UI. + * + * @return array + */ + public static function getInProtocolDisplayNames() + { + $display_names = self::$in_protocols; + + $display_names[self::IN_PROTOCOL_IMAP] = 'IMAP'; + $display_names[self::IN_PROTOCOL_POP3] = 'POP3'; + + return \Eventy::filter('mailbox.in_protocols.display_names', $display_names); + } + + /** + * Get available incoming protocols. + * + * @return array + */ + public static function getInProtocols() + { + return \Eventy::filter('mailbox.in_protocols', self::$in_protocols); + } + + /** + * Get pivot table parameters for the user. + */ + public function getUserSettings($user_id) + { + $mailbox_user = $this->usersWithSettings()->where('users.id', $user_id)->first(); + if ($mailbox_user) { + return $mailbox_user->settings; + } else { + // Admin may have no record in mailbox_user table + return self::getDummySettings(); + } + } + + /** + * Create dummy object with default parameters + * @return [type] [description] + */ + public static function getDummySettings() + { + $settings = new \StdClass(); + $settings->after_send = MailboxUser::AFTER_SEND_NEXT; + $settings->hide = false; + $settings->mute = false; + $settings->access = []; + + return $settings; + } + + public function fetchUserSettings($user_id) + { + $settings = $this->getUserSettings($user_id); + + $this->after_send = $settings->after_send; + $this->hide = $settings->hide; + $this->mute = $settings->mute; + $this->access = $settings->access; + } + + /** + * Get main email and aliases. + * + * @return array + */ + public function getEmails() + { + $emails = [$this->email]; + + if ($this->aliases) { + $aliases = explode(',', $this->aliases); + foreach ($aliases as $alias) { + $alias = trim($alias); + $alias = preg_replace("#\(.*#", '', $alias); + + $alias = Email::sanitizeEmail($alias); + if ($alias) { + $emails[] = $alias; + } + } + } + + return $emails; + } + + /** + * Get mailbox aliases as an associative array. + */ + public function getAliases($include_mailbox_email = true, $check_aliases_reply = false) + { + if ($check_aliases_reply && !$this->aliases_reply) { + return []; + } + + if ($include_mailbox_email) { + $emails = [$this->email => $this->name]; + } else { + $emails = []; + } + + if ($this->aliases) { + $aliases = explode(',', $this->aliases); + foreach ($aliases as $alias) { + $name = ''; + $alias = trim($alias); + preg_match("#[^\(]+\((.*)\)#", $alias, $m); + if (!empty($m[1])) { + $name = $m[1]; + $alias = preg_replace("#\(.*#", '', $alias); + } + + $alias = Email::sanitizeEmail($alias); + if ($alias) { + $emails[$alias] = $name; + } + } + } + + return $emails; + } + + /** + * Remove mailbox email and aliases from the list of emails. + * + * @param array $list + * @param Mailbox $mailbox + * + * @return array + */ + public function removeMailboxEmailsFromList($list) + { + if (!is_array($list)) { + return []; + } + $mailbox_emails = $this->getEmails(); + foreach ($list as $i => $email) { + if (in_array($email, $mailbox_emails)) { + unset($list[$i]); + } + } + + return $list; + } + + /** + * Get all active mailboxes. + * + * @return [type] [description] + */ + public static function getActiveMailboxes() + { + $active = []; + + // It is more effective to retrive all mailboxes and filter them in PHP. + $mailboxes = self::all(); + foreach ($mailboxes as $mailbox) { + if ($mailbox->isActive()) { + $active[] = $mailbox; + } + } + + return $active; + } + + /** + * Get mailbox URL. + * + * @return [type] [description] + */ + public function url() + { + return \Eventy::filter('mailbox.url', route('mailboxes.view', ['id' => $this->id]), $this); + } + + /** + * Fill the model with an array of attributes. + * + * @param array $attributes [description] + * + * @return [type] [description] + */ + public function fill(array $attributes) + { + $this->fillable(array_merge($this->getFillable(), \Eventy::filter('mailbox.fillable_fields', []))); + + return parent::fill($attributes); + } + + /** + * Set phones as JSON. + * + * @param array $phones_array + */ + public function setInImapFolders(array $in_imap_folders) + { + $this->in_imap_folders = json_encode($in_imap_folders); + } + + /** + * Get list of imap folders. + */ + public function getInImapFolders() + { + $in_imap_folders = \Helper::jsonToArray($this->in_imap_folders); + if (count($in_imap_folders)) { + return $in_imap_folders; + } else { + return ["INBOX"]; + } + } + + public function outPasswordSafe() + { + return \Helper::safePassword($this->out_password); + } + + public function inPasswordSafe() + { + return \Helper::safePassword($this->in_password); + } + + public function getReplySeparator() + { + return $this->before_reply ?: \MailHelper::REPLY_SEPARATOR_TEXT; + } + + public static function findOrFailWithSettings($id, $user_id) + { + return Mailbox::select(['mailboxes.*', 'mailbox_user.hide', 'mailbox_user.mute', 'mailbox_user.access']) + ->where('mailboxes.id', $id) + ->leftJoin('mailbox_user', function ($join) use ($user_id) { + $join->on('mailbox_user.mailbox_id', '=', 'mailboxes.id'); + $join->where('mailbox_user.user_id', $user_id); + })->firstOrFail(); + } + + /*public static function getUserSettings($mailbox_id, $user_id) + { + return MailboxUser::where('mailbox_id', $mailbox_id) + ->where('user_id', $user_id) + ->first(); + }*/ + + public static function getAccessPermissionName($perm) + { + $access_permissions = [ + self::ACCESS_PERM_EDIT => __('Edit Mailbox'), + self::ACCESS_PERM_PERMISSIONS => __('Permissions'), + self::ACCESS_PERM_AUTO_REPLIES => __('Auto Replies'), + self::ACCESS_PERM_SIGNATURE => __('Email Signature'), + ]; + $access_permissions = \Eventy::filter('mailbox.access_permissions_list', $access_permissions); + + return $access_permissions[$perm] ?? ''; + } + + public static function getAccessPermissionRoute($perm) + { + $route = self::$access_routes[$perm] ?? ''; + $route = \Eventy::filter('mailbox.access_permissions_route', $route, $perm); + + return $route; + } + + public function getMeta($key, $default = null) + { + if (isset($this->meta[$key])) { + return $this->meta[$key]; + } else { + return $default; + } + } + + public function setMeta($key, $value) + { + $meta = $this->meta; + $meta[$key] = $value; + $this->meta = $meta; + } + + public function setMetaParam($param, $value, $save = false) + { + $meta = $this->meta; + $meta[$param] = $value; + $this->meta = $meta; + + if ($save) { + $this->save(); + } + } + + public function removeMetaParam($param, $save = false) + { + $meta = $this->meta; + if (isset($meta[$param])) { + unset($meta[$param]); + } + $this->meta = $meta; + + if ($save) { + $this->save(); + } + } + + /** + * Check if there is a user with specified email. + */ + public static function userEmailExists($email) + { + $email = Email::sanitizeEmail($email); + $user = User::where('email', $email)->first(); + + if ($user) { + return true; + } else { + return false; + } + } + + public function oauthEnabled() + { + return !empty($this->meta['oauth']['provider']); + } + + public function oauthGetParam($param) + { + return $this->meta['oauth'][$param] ?? ''; + } + + public function setEmailAttribute($value) + { + if ($value) { + $this->attributes['email'] = Email::sanitizeEmail($value); + } + } +} diff --git a/freescout-dist/app/MailboxUser.php b/freescout-dist/app/MailboxUser.php new file mode 100644 index 0000000..31029c3 --- /dev/null +++ b/freescout-dist/app/MailboxUser.php @@ -0,0 +1,27 @@ + 'array', + // ]; +} diff --git a/freescout-dist/app/Misc/Helper.php b/freescout-dist/app/Misc/Helper.php new file mode 100644 index 0000000..f63d01b --- /dev/null +++ b/freescout-dist/app/Misc/Helper.php @@ -0,0 +1,2122 @@ + 'dashboard', + 'mailbox' => [ + 'mailboxes.view', + 'mailboxes.view.folder', + 'conversations.view', + 'conversations.create', + 'conversations.draft', + //'conversations.search', + ], + 'manage' => [ + 'settings' => 'settings', + 'mailboxes' => [ + 'mailboxes', + 'mailboxes.update', + 'mailboxes.create', + 'mailboxes.connection', + 'mailboxes.connection.incoming', + 'mailboxes.permissions', + 'mailboxes.auto_reply', + ], + 'users' => [ + 'users', + 'users.create', + 'users.profile', + 'users.permissions', + 'users.notifications', + 'users.password', + ], + 'logs' => [ + 'logs', + 'logs.app', + ], + 'system' => [ + 'system', + 'system.tools', + ], + ], + // No menu item selected + 'customers' => [], + ]; + + /** + * Locales data. + * + * @var [type] + */ + public static $locales = [ + 'af' => ['name' => 'Afrikaans', + 'name_en' => 'Afrikaans', + ], + 'sq' => ['name' => 'Shqip', + 'name_en' => 'Albanian', + ], + 'ar' => ['name' => 'العربية', + 'name_en' => 'Arabic', + ], + 'ar-IQ' => ['name' => 'العربية', + 'name_en' => 'Arabic (Iraq)', + ], + 'ar-LY' => ['name' => 'العربية', + 'name_en' => 'Arabic (Libya)', + ], + 'ar-MA' => ['name' => 'العربية', + 'name_en' => 'Arabic (Morocco)', + ], + 'ar-OM' => ['name' => 'العربية', + 'name_en' => 'Arabic (Oman)', + ], + 'ar-SY' => ['name' => 'العربية', + 'name_en' => 'Arabic (Syria)', + ], + 'ar-LB' => ['name' => 'العربية', + 'name_en' => 'Arabic (Lebanon)', + ], + 'ar-AE' => ['name' => 'العربية', + 'name_en' => 'Arabic (U.A.E.)', + ], + 'ar-QA' => ['name' => 'العربية', + 'name_en' => 'Arabic (Qatar)', + ], + 'ar-SA' => ['name' => 'العربية', + 'name_en' => 'Arabic (Saudi Arabia)', + ], + 'ar-EG' => ['name' => 'العربية', + 'name_en' => 'Arabic (Egypt)', + ], + 'ar-DZ' => ['name' => 'العربية', + 'name_en' => 'Arabic (Algeria)', + ], + 'ar-TN' => ['name' => 'العربية', + 'name_en' => 'Arabic (Tunisia)', + ], + 'ar-YE' => ['name' => 'العربية', + 'name_en' => 'Arabic (Yemen)', + ], + 'ar-JO' => ['name' => 'العربية', + 'name_en' => 'Arabic (Jordan)', + ], + 'ar-KW' => ['name' => 'العربية', + 'name_en' => 'Arabic (Kuwait)', + ], + 'ar-BH' => ['name' => 'العربية', + 'name_en' => 'Arabic (Bahrain)', + ], + 'az' => ['name' => 'Azerbaijani', + 'name_en' => 'Azerbaijani', + ], + 'eu' => ['name' => 'Euskara', + 'name_en' => 'Basque', + ], + 'be' => ['name' => 'Беларуская', + 'name_en' => 'Belarusian', + ], + 'bn' => ['name' => 'বাংলা', + 'name_en' => 'Bengali', + ], + 'bg' => ['name' => 'Български език', + 'name_en' => 'Bulgarian', + ], + 'ca' => ['name' => 'Català', + 'name_en' => 'Catalan', + ], + 'zh-CN' => ['name' => '简体中文', + 'name_en' => 'Chinese (Simplified)', + ], + 'zh-SG' => ['name' => '简体中文', + 'name_en' => 'Chinese (Singapore)', + ], + 'zh-TW' => ['name' => '简体中文', + 'name_en' => 'Chinese (Traditional)', + ], + 'zh-HK' => ['name' => '简体中文', + 'name_en' => 'Chinese (Hong Kong SAR)', + ], + 'hr' => ['name' => 'Hrvatski', + 'name_en' => 'Croatian', + ], + 'cs' => ['name' => 'Čeština', + 'name_en' => 'Czech', + ], + 'da' => ['name' => 'Dansk', + 'name_en' => 'Danish', + ], + 'nl' => ['name' => 'Nederlands', + 'name_en' => 'Dutch', + ], + // 'nl_BE' => ['name' => 'Nederlands', + // 'name_en' => 'Dutch (Belgium)', + // ], + // 'en_US' => ['name' => 'English', + // 'name_en' => 'English (United States)', + // ], + // 'en_AU' => ['name' => '', + // 'name_en' => 'English (Australia)', + // ], + // 'en_NZ' => ['name' => '', + // 'name_en' => 'English (New Zealand)', + // ], + // 'en_ZA' => ['name' => '', + // 'name_en' => 'English (South Africa)', + // ], + 'en' => ['name' => 'English', + 'name_en' => 'English', + ], + // 'en_TT' => ['name' => '', + // 'name_en' => 'English (Trinidad)', + // ], + // 'en_GB' => ['name' => '', + // 'name_en' => 'English (United Kingdom)', + // ], + // 'en_CA' => ['name' => '', + // 'name_en' => 'English (Canada)', + // ], + // 'en_IE' => ['name' => '', + // 'name_en' => 'English (Ireland)', + // ], + // 'en_JM' => ['name' => '', + // 'name_en' => 'English (Jamaica)', + // ], + // 'en_BZ' => ['name' => '', + // 'name_en' => 'English (Belize)', + // ], + 'et' => ['name' => 'Eesti', + 'name_en' => 'Estonian', + ], + 'fo' => ['name' => 'Føroyskt', + 'name_en' => 'Faeroese', + ], + 'fa' => ['name' => 'فارسی', + 'name_en' => 'Farsi', + ], + 'fi' => ['name' => 'Suomi', + 'name_en' => 'Finnish', + ], + 'fr' => ['name' => 'Français', + 'name_en' => 'French', + ], + // 'fr_CA' => ['name' => '', + // 'name_en' => 'French (Canada)', + // ], + // 'fr_LU' => ['name' => '', + // 'name_en' => 'French (Luxembourg)', + // ], + // 'fr_BE' => ['name' => '', + // 'name_en' => 'French (Belgium)', + // ], + // 'fr_CH' => ['name' => '', + // 'name_en' => 'French (Switzerland)', + // ], + 'gd' => ['name' => 'Gàidhlig', + 'name_en' => 'Gaelic (Scotland)', + ], + 'de' => ['name' => 'Deutsch', + 'name_en' => 'German', + ], + // 'de_CH' => ['name' => '', + // 'name_en' => 'German (Switzerland)', + // ], + // 'de_LU' => ['name' => '', + // 'name_en' => 'German (Luxembourg)', + // ], + // 'de_AT' => ['name' => '', + // 'name_en' => 'German (Austria)', + // ], + // 'de_LI' => ['name' => '', + // 'name_en' => 'German (Liechtenstein)', + // ], + 'el' => ['name' => 'Ελληνικά', + 'name_en' => 'Greek', + ], + 'he' => ['name' => 'עברית', + 'name_en' => 'Hebrew', + ], + 'hi' => ['name' => 'हिन्दी', + 'name_en' => 'Hindi', + ], + 'hu' => ['name' => 'Magyar', + 'name_en' => 'Hungarian', + ], + 'is' => ['name' => 'Íslenska', + 'name_en' => 'Icelandic', + ], + 'id' => ['name' => 'Bahasa Indonesia', + 'name_en' => 'Indonesian', + ], + 'ga' => ['name' => 'Gaeilge', + 'name_en' => 'Irish', + ], + 'it' => ['name' => 'Italiano', + 'name_en' => 'Italian', + ], + // 'it_CH' => ['name' => 'Italiano', + // 'name_en' => 'Italian (Switzerland)', + // ], + 'ja' => ['name' => '日本語', + 'name_en' => 'Japanese', + ], + 'ko' => ['name' => '한국어 (韓國語)', + 'name_en' => 'Korean (Johab)', + ], + 'lv' => ['name' => 'Latviešu valoda', + 'name_en' => 'Latvian', + ], + 'lt' => ['name' => 'Lietuvių kalba', + 'name_en' => 'Lithuanian', + ], + 'mk' => ['name' => 'Македонски јазик', + 'name_en' => 'Macedonian (FYROM)', + ], + 'ms' => ['name' => 'Bahasa Melayu, بهاس ملايو', + 'name_en' => 'Malay', + ], + 'mt' => ['name' => 'Malti', + 'name_en' => 'Maltese', + ], + 'ne' => ['name' => 'नेपाली', + 'name_en' => 'Nepali', + ], + 'no' => ['name' => 'Norsk bokmål', + 'name_en' => 'Norwegian (Bokmal)', + ], + 'pl' => ['name' => 'Polski', + 'name_en' => 'Polish', + ], + 'pt-PT' => ['name' => 'Português', + 'name_en' => 'Portuguese (Portugal)', + ], + 'pt-BR' => ['name' => 'Português do Brasil', + 'name_en' => 'Portuguese (Brazil)', + ], + 'ro' => ['name' => 'Română', + 'name_en' => 'Romanian', + ], + // 'ro_MO' => ['name' => 'Română', + // 'name_en' => 'Romanian (Republic of Moldova)', + // ], + 'rm' => ['name' => 'Rumantsch grischun', + 'name_en' => 'Romansh', + ], + 'ru' => ['name' => 'Русский', + 'name_en' => 'Russian', + ], + // 'ru_MO' => ['name' => '', + // 'name_en' => 'Russian (Republic of Moldova)', + // ], + 'sz' => ['name' => 'Davvisámegiella', + 'name_en' => 'Sami (Lappish)', + ], + 'sr' => ['name' => 'Српски језик', + 'name_en' => 'Serbian (Latin)', + ], + 'sk' => ['name' => 'Slovenčina', + 'name_en' => 'Slovak', + ], + 'sl' => ['name' => 'Slovenščina', + 'name_en' => 'Slovenian', + ], + /*'sb' => ['name' => 'Serbsce', + 'name_en' => 'Sorbian', + ],*/ + 'es' => ['name' => 'Español', + 'name_en' => 'Spanish', + ], + // 'es_GT' => ['name' => '', + // 'name_en' => 'Spanish (Guatemala)', + // ], + // 'es_PA' => ['name' => '', + // 'name_en' => 'Spanish (Panama)', + // ], + // 'es_VE' => ['name' => '', + // 'name_en' => 'Spanish (Venezuela)', + // ], + // 'es_PE' => ['name' => '', + // 'name_en' => 'Spanish (Peru)', + // ], + // 'es_EC' => ['name' => '', + // 'name_en' => 'Spanish (Ecuador)', + // ], + // 'es_UY' => ['name' => '', + // 'name_en' => 'Spanish (Uruguay)', + // ], + // 'es_BO' => ['name' => '', + // 'name_en' => 'Spanish (Bolivia)', + // ], + // 'es_HN' => ['name' => '', + // 'name_en' => 'Spanish (Honduras)', + // ], + // 'es_PR' => ['name' => '', + // 'name_en' => 'Spanish (Puerto Rico)', + // ], + // 'es_MX' => ['name' => '', + // 'name_en' => 'Spanish (Mexico)', + // ], + // 'es_CR' => ['name' => '', + // 'name_en' => 'Spanish (Costa Rica)', + // ], + // 'es_DO' => ['name' => '', + // 'name_en' => 'Spanish (Dominican Republic)', + // ], + // 'es_CO' => ['name' => '', + // 'name_en' => 'Spanish (Colombia)', + // ], + // 'es_AR' => ['name' => '', + // 'name_en' => 'Spanish (Argentina)', + // ], + // 'es_CL' => ['name' => '', + // 'name_en' => 'Spanish (Chile)', + // ], + // 'es_PY' => ['name' => '', + // 'name_en' => 'Spanish (Paraguay)', + // ], + // 'es_SV' => ['name' => '', + // 'name_en' => 'Spanish (El Salvador)', + // ], + // 'es_NI' => ['name' => '', + // 'name_en' => 'Spanish (Nicaragua)', + // ], + 'sv' => ['name' => 'Svenska', + 'name_en' => 'Swedish', + ], + // unknown + // 'sx' => ['name' => '', + // 'name_en' => 'Sutu', + // ], + // 'sv_FI' => ['name' => '', + // 'name_en' => 'Swedish (Finland)', + // ], + 'th' => ['name' => 'ไทย', + 'name_en' => 'Thai', + ], + 'ts' => ['name' => 'Xitsonga', + 'name_en' => 'Tsonga', + ], + 'tn' => ['name' => 'Setswana', + 'name_en' => 'Tswana', + ], + 'tr' => ['name' => 'Türkçe', + 'name_en' => 'Turkish', + ], + 'uk' => ['name' => 'українська', + 'name_en' => 'Ukrainian', + ], + 'ur' => ['name' => 'اردو', + 'name_en' => 'Urdu', + ], + 've' => ['name' => 'Tshivenḓa', + 'name_en' => 'Venda', + ], + 'vi' => ['name' => 'Tiếng Việt', + 'name_en' => 'Vietnamese', + ], + 'xh' => ['name' => 'isiXhosa', + 'name_en' => 'Xhosa', + ], + 'ji' => ['name' => 'ייִדיש', + 'name_en' => 'Yiddish', + ], + 'zu' => ['name' => 'isiZulu', + 'name_en' => 'Zulu', + ], + ]; + + /** + * Cache time of the DB query. + */ + public static function cacheTime($enabled = true) + { + if ($enabled) { + return self::QUERY_CACHE_TIME; + } else { + return 0; + } + } + + public static function setGlobalEntity($name, $entity) + { + self::$global_entities[$name] = $entity; + } + + public static function getGlobalEntity($name) + { + return self::$global_entities[$name] ?? null; + } + + /** + * Remove from text all tags, double spaces, etc. + */ + public static function stripTags($text) + { + // Remove all kinds of spaces after tags. + // https://stackoverflow.com/questions/3230623/filter-all-types-of-whitespace-in-php + $text = preg_replace("/^(.*)>[\r\n]*\s+/mu", '$1>', $text ?? ''); + + // Remove #is', '', $text); + $text = preg_replace('#(.*?)#is', '', $text); + + // Remove tags. + $text = strip_tags($text); + $text = preg_replace('/\s+/mu', ' ', $text); + + // Trim + $text = trim($text); + $text = preg_replace('/^\s+/mu', '', $text); + + // Causes "General error: 1366 Incorrect string value" + // Remove "undetectable" whitespaces + // $whitespaces = ['%81', '%7F', '%C5%8D', '%8D', '%8F', '%C2%90', '%C2', '%90', '%9D', '%C2%A0', '%A0', '%C2%AD', '%AD', '%08', '%09', '%0A', '%0D']; + // $text = urlencode($text); + // foreach ($whitespaces as $char) { + // $text = str_replace($char, ' ', $text); + // } + // $text = urldecode($text); + + $text = trim(preg_replace('/[ ]+/', ' ', $text)); + + return $text; + } + + /** + * Get preview of the text in a plain form. + */ + public static function textPreview($text, $length = self::PREVIEW_MAXLENGTH) + { + $text = strtr($text ?? '', [ + '' => ' ', + '

' => '

' + ]); + + $text = self::stripTags($text); + + $text = mb_substr($text, 0, $length); + + return $text; + } + + /** + * Check if passed route name equals to the current one. + */ + public static function isCurrentRoute($route_name) + { + if (\Request::route()->getName() == $route_name) { + return true; + } else { + return false; + } + } + + /** + * Check if menu item is selected. + * Each menu item has a mnemonic name. + */ + public static function isMenuSelected($menu_item_name) + { + $current_route = \Request::route()->getName(); + + $menu = \Eventy::filter('menu.selected', self::$menu); + + foreach ($menu as $primary_name => $primary_items) { + if (!is_array($primary_items)) { + if ($current_route == $primary_items) { + return $primary_name == $menu_item_name; + } + if ($primary_name == $menu_item_name) { + return false; + } + continue; + } + foreach ($primary_items as $secondary_name => $secondary_routes) { + if (is_array($secondary_routes)) { + if (in_array($current_route, $secondary_routes)) { + return $secondary_name == $menu_item_name || $primary_name == $menu_item_name; + } + } elseif (is_string($secondary_name)) { + if ($current_route == $secondary_routes) { + return $secondary_name == $menu_item_name || $primary_name == $menu_item_name; + } + } else { + if ($current_route == $secondary_routes) { + return $primary_name == $menu_item_name || $menu_item_name == $secondary_routes; + } + } + } + } + + return false; + } + + public static function menuSelectedHtml($menu_item_name) + { + if (self::isMenuSelected($menu_item_name)) { + return 'active'; + } else { + return ''; + } + } + + /** + * Resize image without using Intervention package. + */ + public static function resizeImage($file, $mime_type, $thumb_width, $thumb_height) + { + list($width, $height) = getimagesize($file); + if (!$width) { + return false; + } + + if (preg_match('/png/i', $mime_type)) { + $src = imagecreatefrompng($file); + + $kek = imagecolorallocate($src, 255, 255, 255); + imagefill($src, 0, 0, $kek); + } elseif (preg_match('/gif/i', $mime_type)) { + $src = imagecreatefromgif($file); + + $kek = imagecolorallocate($src, 255, 255, 255); + imagefill($src, 0, 0, $kek); + } elseif (preg_match('/bmp/i', $mime_type)) { + $src = imagecreatefrombmp($file); + } else { + $src = imagecreatefromjpeg($file); + } + + $original_aspect = $width / $height; + $thumb_aspect = $thumb_width / $thumb_height; + if ($original_aspect == $thumb_aspect) { + $new_height = $thumb_height; + $new_width = $thumb_width; + } elseif ($original_aspect > $thumb_aspect) { + // If image is wider than thumbnail (in aspect ratio sense) + $new_height = $thumb_height; + $new_width = $width / ($height / $thumb_height); + } else { + // If the thumbnail is wider than the image + $new_width = $thumb_width; + $new_height = $height / ($width / $thumb_width); + } + + $thumb = imagecreatetruecolor($thumb_width, $thumb_height); + // Resize and crop + imagecopyresampled($thumb, + $src, + ceil(0 - ($new_width - $thumb_width) / 2), // Center the image horizontally + ceil(0 - ($new_height - $thumb_height) / 2), // Center the image vertically + 0, 0, + ceil($new_width), + ceil($new_height), + $width, $height); + imagedestroy($src); + + return $thumb; + } + + public static function jsonToArray($json, $exclude_array = []) + { + if ($json) { + $array = json_decode($json, true); + if (json_last_error()) { + $array = [$json]; + } + if ($array && $exclude_array) { + $array = array_diff($array, $exclude_array); + } + + return $array; + } else { + return []; + } + } + + public static function getDomain() + { + return parse_url(\Config::get('app.url'), PHP_URL_HOST); + } + + /** + * Create zip archive. + * Source example: public/files/* + * File name example: test.zip. + * storage_path without app/ + */ + public static function createZipArchive($source, $file_name, $folder = '', $storage_file_path = '') + { + if (!$source || !$file_name) { + return false; + } + $files = glob($source); + + if (!$storage_file_path) { + $storage_file_path = 'zipper'.DIRECTORY_SEPARATOR.$file_name; + } else { + // if (!self::getPrivateStorage()->exists($storage_path)) { + // self::getPrivateStorage()->makeDirectory($storage_path); + // } + } + $dest_path = storage_path().DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.$storage_file_path; + + // If file exists it has to be deleted, otherwise Zipper will add file to the existing archive + if (self::getPrivateStorage()->exists($storage_file_path)) { + self::getPrivateStorage()->delete($storage_file_path); + } + + \Chumper\Zipper\Facades\Zipper::make($dest_path)->folder($folder)->add($files)->close(); + + return $dest_path; + } + + public static function getPrivateStorage() + { + return \Storage::disk('private'); + } + + public static function getPublicStorage() + { + return \Storage::disk('public'); + } + + public static function formatException($e) + { + return 'Error: '.$e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')'; + } + + public static function denyAccess($msg = '') + { + abort(403, $msg ?: 'This action is unauthorized.'); + } + + /** + * Check if application version. + * + * @param [type] $ver [description] + * + * @return [type] [description] + */ + public static function checkAppVersion($version2, $operator = '>=') + { + return version_compare(\Config::get('app.version'), $version2, $operator); + } + + /** + * Download remote file and save as file. + */ + public static function downloadRemoteFile($url, $destinationFilePath) + { + $client = new \GuzzleHttp\Client(); + + try { + $client->request('GET', $url, \Helper::setGuzzleDefaultOptions([ + 'sink' => $destinationFilePath, + 'timeout' => 300, // seconds + 'connect_timeout' => 7, + ])); + } catch (\Exception $e) { + self::logException($e); + } + } + + /** + * Extract ZIP archive. + * to: must be apsolute path, otherwise extracted into /public/$to. + */ + public static function unzip($archive, $to) + { + \Chumper\Zipper\Facades\Zipper::make($archive)->extractTo($to); + } + + public static function logException($e, $prefix = '') + { + if ($prefix) { + $prefix .= ' '; + } + \Log::error($prefix.self::formatException($e)); + } + + /** + * Safely decrypt. + * + * @param [type] $e [description] + * + * @return [type] [description] + */ + public static function decrypt($value) + { + try { + $value = decrypt($value); + } catch (\Exception $e) { + // Do nothing. + } + + return $value; + } + + /** + * Log custom data to activity log. + * + * @param [type] $log_name [description] + * @param [type] $data [description] + * @param [type] $code [description] + * + * @return [type] [description] + */ + public static function log($log_name, $description, $properties = []) + { + activity() + ->withProperties($properties) + ->useLog($log_name) + ->log($description); + } + + /** + * Log exception to activity log. + */ + public static function logExceptionToActivityLog($e, $log_name, $description, $properties = []) + { + $properties['error'] = self::formatException($e); + activity() + ->withProperties($properties) + ->useLog($log_name) + ->log($description); + } + + /** + * Check if folder is writable. + * + * @param [type] $path [description] + * + * @return bool [description] + */ + public static function isFolderWritable($path) + { + if (!file_exists($path)) { + return false; + } + $path = rtrim($path, DIRECTORY_SEPARATOR); + + try { + $file = $path.DIRECTORY_SEPARATOR.'.writable_test'; + if ($file && file_put_contents($file, 'test')) { + unlink($file); + + return true; + } else { + return false; + } + } catch (\Exception $e) { + return false; + } + } + + public static function setLocale($locale) + { + if (in_array($locale, config('app.locales'))) { + app()->setLocale($locale); + } + } + + /** + * Get locale's data. + * + * @param [type] $locale [description] + * @param string $param [description] + * + * @return [type] [description] + */ + public static function getLocaleData($locale, $param = '') + { + if (is_string($locale) && isset(self::$locales[$locale])) { + $data = self::$locales[$locale]; + } else { + return; + } + + if ($param) { + if (isset(self::$locales[$locale])) { + return self::$locales[$locale][$param]; + } else { + return; + } + } else { + return $data; + } + } + + /** + * Clear application cache. + * + * @return [type] [description] + */ + public static function clearCache($options = []) + { + \Artisan::call('freescout:clear-cache', $options); + } + + /** + * Set variable in .evn file. + */ + public static function setEnvFileVar($key, $value) + { + $env_path = app()->environmentFilePath(); + $contents = file_get_contents($env_path); + + if (strstr($value, '"')) { + // Escape quotes. + $value = '"'.str_replace('"', '\"', $value).'"'; + } elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $value) && $value !== '') { + // Add quotes. + $value = '"'.$value.'"'; + } + + $old_value = ''; + // Match the given key at the beginning of a line + preg_match("/^{$key}=[^\r\n]*/m", $contents, $matches); + if (count($matches)) { + $old_value = substr($matches[0], strlen($key) + 1); + } + + if ($old_value) { + // Replace. + $contents = str_replace("{$key}={$old_value}", "{$key}={$value}", $contents); + } else { + // Add or empty value + preg_match("/^{$key}=[\r\n]/m", $contents, $matches); + if (count($matches)) { + // Replace empty value + $contents = str_replace("{$key}=", "{$key}={$value}", $contents); + } else { + // Add. + $contents = $contents."\n{$key}={$value}\n"; + } + } + \File::put($env_path, $contents); + } + + /** + * User may add an extra translation to the app on Translate page. + * + * @return [type] [description] + */ + public static function getCustomLocales() + { + return \Barryvdh\TranslationManager\Models\Translation::distinct()->pluck('locale')->toArray(); + } + + /** + * Get built in and custom locales. + * + * @return [type] [description] + */ + public static function getAllLocales() + { + $app_locales = config('app.locales'); + + // User may add an extra translation to the app on Translate page, + // we should allow user to see his custom translations. + $custom_locales = []; + try { + $custom_locales = \Helper::getCustomLocales(); + } catch (\Exception $e) { + // During installation it throws an error as there is no tables yet. + } + + if (count($custom_locales)) { + $app_locales = array_unique(array_merge($app_locales, $custom_locales)); + } + + return $app_locales; + } + + /** + * app()->setLocale() in Localize middleware also changes config('app.locale'), + * so we are keeping real app locale in real_locale parameter. + */ + public static function getRealAppLocale() + { + return config('app.real_locale'); + } + + /** + * Create a backgound job executing specified action. + * + * @return [type] [description] + */ + public static function backgroundAction($action, $params, $delay = 0) + { + $delay = \Eventy::filter('backgound_action.dispatch_delay', $delay, $action, $params); + + $job = \App\Jobs\TriggerAction::dispatch($action, $params); + if ($delay) { + $job->delay($delay); + } + $job->onQueue('default'); + } + + /** + * Convert HTML into the text with \n. + * + * @param [type] $text [description] + */ + public static function htmlToText($text, $embed_images = false, $options = ['width' => 0]) + { + // Process blockquotes. + $text = str_ireplace('
', '
', $text); + $text = str_ireplace('
', '', $text); + + if ($embed_images) { + // Replace embedded images with their urls. + $text = preg_replace( '/]*src=\"([^>"]+)\"[^>]*>/i', "
$1
", $text); + } + return (new \Html2Text\Html2Text($text, $options))->getText(); + } + + /** + * Trim text removing non-breaking spaces also. + * + * @param [type] $text [description] + * @return [type] [description] + */ + public static function trim($text) + { + $text = preg_replace("/^\s+/u", '', $text); + $text = preg_replace("/\s+$/u", '', $text); + + return $text; + } + + /** + * Unicode escape sequences like “\u00ed” to proper UTF-8 encoded characters. + * + * @param [type] $text [description] + * @return [type] [description] + */ + public static function entities2utf8($text) + { + try { + return json_decode('"'.str_replace('"', '\\"', $text).'"'); + } catch(\Exception $e) { + return $text; + } + } + + /** + * Get app subdirectory in /subdirectory/1/2/ format. + */ + public static function getSubdirectory($keep_trailing_slash = false, $keep_front_slash = false) + { + $subdirectory = ''; + + $app_url = config('app.url'); + + // Check host to ignore default values. + $app_host = parse_url($app_url, PHP_URL_HOST); + + if ($app_url && !in_array($app_host, ['localhost', 'example.com'])) { + $subdirectory = parse_url($app_url, PHP_URL_PATH); + } else { + // Before app is installed + $subdirectory = $_SERVER['PHP_SELF']; + + $filename = basename($_SERVER['SCRIPT_FILENAME']); + + if (basename($_SERVER['SCRIPT_NAME']) === $filename) { + $subdirectory = $_SERVER['SCRIPT_NAME']; + } elseif (basename($_SERVER['PHP_SELF']) === $filename) { + $subdirectory = $_SERVER['PHP_SELF']; + } elseif (array_key_exists('ORIG_SCRIPT_NAME', $_SERVER) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) { + $subdirectory = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = $_SERVER['PHP_SELF']; + $file = $_SERVER['SCRIPT_FILENAME']; + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = \count($segs); + $subdirectory = ''; + do { + $seg = $segs[$index]; + $subdirectory = '/'.$seg.$subdirectory; + ++$index; + } while ($last > $index && (false !== $pos = strpos($path, $subdirectory)) && 0 != $pos); + } + } + + if ($subdirectory === null) { + $subdirectory = ''; + } + + $subdirectory = str_replace('public/index.php', '', $subdirectory); + $subdirectory = str_replace('index.php', '', $subdirectory); + + $subdirectory = trim($subdirectory, '/'); + if ($keep_trailing_slash) { + $subdirectory .= '/'; + } + + if ($keep_front_slash && $subdirectory != '/') { + $subdirectory = '/'.$subdirectory; + } + + return $subdirectory; + } + + /** + * Check current route. + */ + public static function isRoute($route_name) + { + $route = \Route::current(); + if (!$route) { + return false; + } + $current = $route->getName(); + + if (is_array($route_name)) { + return in_array($current, $route_name); + } else { + return ($current == $route_name); + } + } + + /** + * Check if passed app URL has default Laravel value. + */ + public static function isDefaultAppUrl($app_url) + { + $app_host = parse_url($app_url, PHP_URL_HOST); + + if ($app_url && !in_array($app_host, ['localhost', 'example.com'])) { + return false; + } else { + return true; + } + } + + /** + * Stop all queue:work processes. + */ + public static function queueWorkerRestart() + { + \Cache::forever('illuminate:queue:restart', Carbon::now()->getTimestamp()); + // In some systems queue:work runs on a separate file system, + // so those queue:work processes may not get illuminate:queue:restart. + $job_exists = \App\Job::where('queue', 'default') + ->where('payload', 'like', '{"displayName":"App\\\\\\\\Jobs\\\\\\\\RestartQueueWorker"%') + ->exists(); + if (!$job_exists) { + \App\Jobs\RestartQueueWorker::dispatch()->onQueue('default'); + } + } + + /** + * UTF-8 split text into parts with max. length. + */ + public static function strSplitKeepWords($str, $max_length = 75) + { + $array_words = explode(' ', $str); + + $currentLength = 0; + + $index = 0; + + $array_output = ['']; + + foreach ($array_words as $word) { + // +1 because the word will receive back the space in the end that it loses in explode() + $wordLength = strlen($word) + 1; + + if (($currentLength + $wordLength) <= $max_length) { + $array_output[$index] .= $word . ' '; + + $currentLength += $wordLength; + } else { + $index += 1; + + $currentLength = $wordLength; + + $array_output[$index] = $word; + } + } + + return $array_output; + } + + /** + * Replace new line with doble
. + */ + public static function nl2brDouble($text) + { + return str_replace('
', '

', nl2br($text)); + } + + /** + * Decode \u00ed. + */ + public static function decodeUnicode($str) + { + $str = preg_replace_callback('/\\\\u([0-9a-fA-F]{4})/', function ($match) { + return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE'); + }, $str); + + return $str; + } + + /** + * Convert text into json without converting chars into \u0411. + */ + public static function jsonEncodeUtf8($text) + { + return json_encode($text, JSON_UNESCAPED_UNICODE); + } + + /** + * It looks like this is not used anywhere. + * Json encode to avoid "Unable to JSON encode payload. Error code: 5" + */ + // public static function jsonEncodeSafe($value, $options = 0, $depth = 512, $utfErrorFlag = false) + // { + // $encoded = json_encode($value, $options, $depth); + // switch (json_last_error()) { + // case JSON_ERROR_NONE: + // return $encoded; + // // case JSON_ERROR_DEPTH: + // // return 'Maximum stack depth exceeded'; // or trigger_error() or throw new Exception() + // // case JSON_ERROR_STATE_MISMATCH: + // // return 'Underflow or the modes mismatch'; // or trigger_error() or throw new Exception() + // // case JSON_ERROR_CTRL_CHAR: + // // return 'Unexpected control character found'; + // // case JSON_ERROR_SYNTAX: + // // return 'Syntax error, malformed JSON'; // or trigger_error() or throw new Exception() + // case JSON_ERROR_UTF8: + // $clean = self::utf8ize($value); + // if ($utfErrorFlag) { + // //return 'UTF8 encoding error'; // or trigger_error() or throw new Exception() + // } + // return self::jsonEncodeSafe($clean, $options, $depth, true); + // // default: + // // return 'Unknown error'; // or trigger_error() or throw new Exception() + + // } + // } + + // public static function utf8ize($mixed) + // { + // if (is_array($mixed)) { + // foreach ($mixed as $key => $value) { + // $mixed[$key] = self::utf8ize($value); + // } + // } else if (is_string ($mixed)) { + // return utf8_encode($mixed); + // } + // return $mixed; + // } + + /** + * Check if host is available on the port specified. + */ + public static function checkPort($host, $port, $timeout = 10) + { + $connection = @fsockopen($host, $port); + if (is_resource($connection)) { + fclose($connection); + return true; + } else { + return false; + } + } + + public static function purifyHtml($html) + { + if (!$html) { + return $html; + } + + $html = \Purifier::clean($html); + + // It's not clear why it was needed to remove spaces after tags. + // + // Remove all kinds of spaces after tags + // https://stackoverflow.com/questions/3230623/filter-all-types-of-whitespace-in-php + //$html = preg_replace("/^(.*)>[\r\n]*\s+/mu", '$1>', $html); + + return $html; + } + + /** + * Replace password with asterisks. + */ + public static function safePassword($password) + { + return str_repeat("*", mb_strlen($password ?? '')); + } + + /** + * Turn all URLs in clickable links. + * Released under public domain + * https://gist.github.com/jasny/2000705 + * + * @param string $value + * @param array $protocols http/https, ftp, mail + * @param array $attributes + * @return string + */ + public static function linkify($value, $protocols = ['http', 'mail'], array $attributes = []) + { + // Link attributes + $attr = ''; + foreach ($attributes as $key => $val) { + $attr .= ' ' . $key . '="' . htmlentities($val) . '"'; + } + + $links = array(); + + // Extract existing links and tags + $value = preg_replace_callback('~(.*?|<.*?>)~i', function ($match) use (&$links) { return '<' . array_push($links, $match[1]) . '>'; }, $value ?? '') ?: $value; + + $value = $value ?? ''; + + // Extract text links for each protocol + foreach ((array)$protocols as $protocol) { + switch ($protocol) { + case 'http': + case 'https': + //$value = preg_replace_callback('~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))%s', function ($match) use ($protocol, &$links, $attr) { + // https://github.com/freescout-helpdesk/freescout/issues/3402 + $value = preg_replace_callback('%([>\r\n\s:;\( ]|^)((([\w-]+)://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))%s', function ($match) use ($protocol, &$links, $attr) { + if ($match[4]) { + $protocol = $match[4]; + } + $link = $match[2]; + $link = substr($link, strlen($match[3])); + //return '<' . array_push($links, "$protocol://$link") . '>'; + return $match[1].'<' . array_push($links, "".$match[2]."") . '>'; + }, $value) ?: $value; + break; + case 'mail': $value = preg_replace_callback('~([^\s<>]+?@[^\s<]+?\.[^\s<]+)(?{$match[1]}") . '>'; }, $value) ?: $value; + break; + default: $value = preg_replace_callback('~' . preg_quote($protocol, '~') . '://([^\s<]+?)(?$protocol://{$match[1]}") . '>'; }, $value) ?: $value; + break; + } + } + + // Insert all links + return preg_replace_callback('/<(\d+)>/', function ($match) use (&$links) { return $links[$match[1] - 1]; }, $value ?? '') ?: $value; + } + + /** + * Generates unique ID of the application. + */ + public static function getAppIdentifier() + { + $identifier = md5(config('app.key').parse_url(config('app.url'), PHP_URL_HOST)); + + return $identifier; + } + + /** + * Are we in the mobile app. + */ + public static function isInApp() + { + return (int)app('request')->cookie('in_app'); + } + + /** + * Get identifier for queue:work + */ + public static function getWorkerIdentifier($salt = '') + { + return md5((config('app.key') ?? '').$salt); + } + + /** + * Get pids of the specified processes. + */ + public static function getRunningProcesses($search = '') + { + if (empty($search)) { + $search = \Helper::getWorkerIdentifier(); + } + + $pids = []; + + try { + $processes = preg_split("/[\r\n]/", shell_exec("ps aux | grep '".$search."'")); + foreach ($processes as $process) { + $process = trim($process); + preg_match("/^[\S]+\s+([\d]+)\s+/", $process, $m); + if (empty($m)) { + // Another format (used in Docker image). + // 1713 nginx 0:00 /usr/bin/php82... + preg_match("/^([\d]+)\s+[\S]+\s+/", $process, $m); + } + if (!preg_match("/(sh \-c|grep )/", $process) && !empty($m[1])) { + $pids[] = $m[1]; + } + } + } catch (\Exception $e) { + // Do nothing + } + return $pids; + } + + public static function uploadFile($file, $allowed_exts = [], $allowed_mimes = []) + { + $ext = strtolower($file->getClientOriginalExtension()); + + if ($allowed_exts) { + if (!in_array($ext, $allowed_exts)) { + throw new \Exception(__('Unsupported file type'), 1); + } + } + + if ($allowed_mimes) { + $mime_type = $file->getMimeType(); + if (!in_array($mime_type, $allowed_mimes)) { + throw new \Exception(__('Unsupported file type'), 1); + } + } + $file_name = \Str::random(25).'.'.$ext; + + $file_name = \Helper::sanitizeUploadedFileName($file_name, $file); + + $file->storeAs('uploads', $file_name); + + self::sanitizeUploadedFileData('uploads'.DIRECTORY_SEPARATOR.$file_name, self::getPublicStorage()); + + return self::uploadedFilePath($file_name); + } + + public static function sanitizeUploadedFileData($file_path, $storage, $content = null) + { + // Remove #is', '', $content); + } + $storage->put($file_path, $clean_content); + } + } + } + + public static function uploadedFileRemove($name) + { + \Storage::delete('uploads/'.$name); + } + + public static function uploadedFilePath($name) + { + return storage_path('uploads/'.$name); + } + + public static function uploadedFileUrl($name) + { + return \Storage::url('uploads/'.$name); + } + + public static function addSessionError($text, $key = 'default') + { + $errors = \Session::get('errors', new \Illuminate\Support\ViewErrorBag); + + if (! $errors instanceof \Illuminate\Support\ViewErrorBag) { + $errors = new \Illuminate\Support\ViewErrorBag; + } + + $message_bag = new \Illuminate\Support\MessageBag; + $message_bag->add($key, $text); + + \Session::flash( + 'errors', $errors->put('default', $message_bag) + ); + } + + public static function addFloatingFlash($text, $type = 'danger', $role = '') + { + $flashes = \Session::get('flashes_floating', []); + + $flashes[] = [ + 'text' => $text, + 'type' => $type, + 'role' => $role, + ]; + + \Session::flash('flashes_floating', $flashes); + } + + public static function isMySql() + { + return \DB::connection()->getPDO()->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'mysql'; + } + + public static function isPgSql() + { + return \DB::connection()->getPDO()->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'pgsql'; + } + + public static function sqlLikeOperator() + { + return self::isPgSql() ? 'ilike' : 'like'; + } + + // PostgreSQL truncates string if it contains \u0000 symbol starting from this symbol. + // https://stackoverflow.com/questions/31671634/handling-unicode-sequences-in-postgresql + // https://github.com/freescout-helpdesk/freescout/issues/3485 + public static function sqlSanitizeString($string) + { + return str_replace(json_decode('"\u0000"'), "", $string); + } + + public static function humanFileSize($size, $unit="") + { + if ((!$unit && $size >= 1<<30) || $unit == "GB") { + return number_format($size/(1<<30),2)."GB"; + } + if ((!$unit && $size >= 1<<20) || $unit == "MB") { + return number_format($size/(1<<20),2)."MB"; + } + //if ((!$unit && $size >= 1<<10) || $unit == "KB") { + return number_format($size/(1<<10),2)."KB"; + // } + // return number_format($size)." bytes"; + } + + public static function isPrint() + { + return (bool)app('request')->input('print'); + } + + public static function isDev() + { + return config('app.env') != 'production'; + } + + public static function substrUnicode($str, $s, $l = null) + { + return join("", array_slice(preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY), $s, $l)); + } + + /** + * Disable sql_require_primary_key option to avoid errors when migrating. + * Only for MySQL. + */ + public static function disableSqlRequirePrimaryKey() + { + if (!self::isMySql()) { + return; + } + try { + \DB::statement('SET SESSION sql_require_primary_key=0'); + } catch (\Exception $e) { + // General error: 1193 Unknown system variable 'sql_require_primary_key'. + // Do nothing. + } + } + + public static function downloadRemoteFileAsTmp($uri) + { + try { + $contents = self::getRemoteFileContents($uri); + + if (!$contents) { + return false; + } + + $temp_file = self::getTempFileName(); + + \File::put($temp_file, $contents); + + return $temp_file; + + } catch (\Exception $e) { + + \Helper::logException($e, 'Error downloading a remote file ('.$uri.'): '); + + return false; + } + } + + // Replacement for file_get_contents() as some hostings + // do not allow reading remote files via allow_url_fopen option. + public static function getRemoteFileContents($url) + { + try { + $headers = get_headers($url); + + // 307 - Temporary Redirect. + if (!preg_match("/(200|301|302|307)/", $headers[0])) { + return false; + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_URL, $url); + \Helper::setCurlDefaultOptions($ch); + curl_setopt($ch, CURLOPT_TIMEOUT, 180); + $contents = curl_exec($ch); + + if (curl_errno($ch)) { + throw new \Exception(curl_errno($ch).' '.curl_error($ch), 1); + } + + curl_close($ch); + + if (!$contents) { + return false; + } + + return $contents; + + } catch (\Exception $e) { + + \Helper::logException($e, 'Error downloading a remote file ('.$url.'): '); + + return false; + } + } + + public static function getTempDir() + { + return sys_get_temp_dir() ?: '/tmp'; + } + + public static function getTempFileName() + { + return tempnam(self::getTempDir(), self::getTempFilePrefix()); + } + + public static function getTempFilePrefix() + { + return 'fs-'.substr(md5(config('app.key').'temp_prefix'), 0, 8).'_'; + } + + // Keep in mind that $uploaded_file->getClientMimeType() returns + // incorrect mime type for images: application/octet-stream + public static function downloadRemoteFileAsTmpFile($uri) + { + $file_path = self::downloadRemoteFileAsTmp($uri); + if ($file_path) { + return new \Illuminate\Http\UploadedFile( + $file_path, basename($file_path), + null, null, true + ); + } else { + return null; + } + } + + public static function sanitizeUploadedFileName($file_name, $uploaded_file = null, $contents = null) + { + // Check extension. + $ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION)); + + if (preg_match('/('.implode('|', self::$restricted_extensions).')/', $ext)) { + // Add underscore to the extension if file has restricted extension. + $file_name = $file_name.'_'; + } elseif ($ext == 'pdf') { + // Rename PDF to avoid running embedded JavaScript. + if ($uploaded_file && !$contents) { + $contents = file_get_contents($uploaded_file->getRealPath()); + } + if ($contents && strstr($contents, '/JavaScript')) { + $file_name = $file_name.'_'; + } + } + + // Remove illegal chars. + $illegal_chars = [ + // Unix. + '/', + chr(0), + // Windows. + '<', + '>', + ':', + '"', + '/', + '\\', + '|', + '?', + '*', + // Macos. + ':', + ]; + // 0-31 (ASCII control characters) for Windows. + for ($i = 0; $i < 32; $i++) { + $illegal_chars[] = chr($i); + } + + $escaped_regex = preg_quote(implode('', $illegal_chars), '/'); + + // https://github.com/freescout-helpdesk/freescout/issues/3377 + $file_name = mb_convert_encoding($file_name, 'UTF-8', 'UTF-8'); + $file_name = preg_replace('/[' . $escaped_regex . ']/', '_', $file_name); + $file_name = preg_replace("/[\t\r\n]/", '', $file_name); + + return $file_name; + } + + public static function remoteFileName($file_url) + { + return preg_replace("/\?.*/", '', basename($file_url)); + } + + public static function binaryDataMimeType($data) + { + $finfo = new \finfo(FILEINFO_MIME_TYPE); + return $finfo->buffer($data); + } + + /** + * https://php.watch/versions/8.0/deprecated-reflectionparameter-methods + */ + public static function getClass($param) + { + return $param->getType() && !$param->getType()->isBuiltin() ? new \ReflectionClass(method_exists($param->getType(), 'getName') ? $param->getType()->getName() : $param->getClass()->name) : null; + } + + /** + * https://php.watch/versions/8.0/deprecated-reflectionparameter-methods + */ + public static function getClassName($param) + { + return $param->getType() && !$param->getType()->isBuiltin() ? method_exists($param->getType(), 'getName') ? $param->getType()->getName() : $param->getClass()->name : null; + } + + public static function getWebCronHash() + { + return md5(config('app.key').'web_cron_hash'); + } + + public static function getProtocol($url = '') + { + return mb_strtolower(parse_url($url ?: config('app.url'), PHP_URL_SCHEME) ?: 'http'); + } + + public static function isHttps($url = '') + { + if (\Helper::isInstaller()) { + // In the Installer we determine HTTPS from URL. + return self::isCurrentUrlHttps(); + } else { + return self::getProtocol($url) == 'https'; + } + } + + public static function isInstaller() + { + $request_uri = $_SERVER['REQUEST_URI'] ?? ''; + $request_uri = preg_replace("#\?.*#", '', $request_uri); + + return strstr($request_uri, '/install/') || preg_match("#/install$#", $request_uri); + } + + public static function isCurrentUrlHttps() + { + if (in_array(strtolower($_SERVER['X_FORWARDED_PROTO'] ?? ''), array('https', 'on', 'ssl', '1'), true) + || strtolower($_SERVER['HTTPS'] ?? '') == 'on' + || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') == 'https' + || ($_SERVER['HTTP_CF_VISITOR'] ?? '') == '{"scheme":"https"}' + ) { + return true; + } else { + return false; + } + } + + public static function fixProtocol($url) + { + if (self::getProtocol() == 'http' && parse_url($url, PHP_URL_SCHEME) != 'http') { + return str_replace('https://', 'http://', $url); + } + + if (self::getProtocol() == 'https' && parse_url($url, PHP_URL_SCHEME) != 'https') { + return str_replace('http://', 'https://', $url); + } + + return $url; + } + + /** + * Fix and parse date to Carbon. + */ + public static function parseDateToCarbon($date, $current_if_invalid = true) + { + if (preg_match('/\+0580/', $date)) { + $date = str_replace('+0580', '+0530', $date); + } + $date = trim(rtrim($date)); + $date = preg_replace('/[<>]/', '', $date); + $date = str_replace('_', ' ', $date); + try { + return Carbon::parse($date); + } catch (\Exception $e) { + switch (true) { + case preg_match('/([0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ UT)+$/i', $date) > 0: + case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ UT)+$/i', $date) > 0: + $date .= 'C'; + break; + case preg_match('/([A-Z]{2,3}[\,|\ \,]\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}.*)+$/i', $date) > 0: + case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ [\-|\+][0-9]{4}\ \(.*)\)+$/i', $date) > 0: + case preg_match('/([A-Z]{2,3}\, \ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ [\-|\+][0-9]{4}\ \(.*)\)+$/i', $date) > 0: + case preg_match('/([0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{2,4}\ [0-9]{2}\:[0-9]{2}\:[0-9]{2}\ [A-Z]{2}\ \-[0-9]{2}\:[0-9]{2}\ \([A-Z]{2,3}\ \-[0-9]{2}:[0-9]{2}\))+$/i', $date) > 0: + $array = explode('(', $date); + $array = array_reverse($array); + $date = trim(array_pop($array)); + break; + } + try { + return Carbon::parse($date); + } catch (\Exception $_e) { + if ($current_if_invalid) { + return Carbon::now(); + } else { + return null; + } + } + } + + return null; + } + + public static function urlHome() + { + return \Config::get('app.url'); + // $url = rtrim($url, "/"); + // return $url.'/home'; + } + + /** + * Request::url() may return URL with incorrect protocol. + * Use \Request::getRequestUri() instead. + */ + /*public static function currentUrl() + { + $url = \Request::urlFull(); + if (\Str::startsWith(config('app.url'), 'http://') && !\Str::startsWith($url, 'http://')) { + $url = str_replace('https://', 'http://', $url); + } + if (\Str::startsWith(config('app.url'), 'https://') && !\Str::startsWith($url, 'https://')) { + $url = str_replace('http://', 'https://', $url); + } + return $url; + }*/ + + public static function isLocaleRtl(): bool + { + return in_array(app()->getLocale(), config("app.locales_rtl") ?? []); + } + + public static function phoneToNumeric($phone) + { + $phone = preg_replace("/[^0-9]/", '', $phone); + return (string)$phone; + } + + public static function checkRequiredExtensions() + { + $php_extensions = []; + $required_extensions = \Config::get('installer.requirements.php'); + + // Optional. + $required_extensions[] = 'intl'; + + foreach ($required_extensions as $extension_name) { + $alternatives = explode('/', $extension_name); + if ($alternatives) { + foreach ($alternatives as $alternative) { + $php_extensions[$extension_name] = extension_loaded(trim($alternative)); + if ($php_extensions[$extension_name]) { + break; + } + } + } else { + $php_extensions[$extension_name] = extension_loaded($extension_name); + } + } + + // Required in console. + if (self::isConsole() || !function_exists('shell_exec')) { + $pcntl_enabled = extension_loaded('pcntl'); + } else { + $pcntl_enabled = preg_match("/enable/m", shell_exec("php -i | grep pcntl") ?? ''); + } + $php_extensions['pcntl (console PHP)'] = $pcntl_enabled; + + return $php_extensions; + } + + public static function checkRequiredFunctions() + { + return [ + 'shell_exec (PHP)' => function_exists('shell_exec'), + 'proc_open (PHP)' => function_exists('proc_open'), + 'fpassthru (PHP)' => function_exists('fpassthru'), + 'symlink (PHP)' => function_exists('symlink'), + 'pcntl_signal (console PHP)' => function_exists('shell_exec') ? (int)shell_exec('php -r "echo (int)function_exists(\'pcntl_signal\');"') : false, + 'ps (shell)' => function_exists('shell_exec') ? shell_exec('ps') : false, + ]; + } + + public static function isInstalled() + { + return file_exists(storage_path().DIRECTORY_SEPARATOR.'.installed'); + } + + public static function isConsole() + { + return app()->runningInConsole(); + } + + /** + * Show a warning when background jobs sending emails + * are not processed for some time. + * https://github.com/freescout-helpdesk/freescout/issues/2808 + */ + public static function maybeShowSendingProblemsAlert() + { + $flashes = []; + + if (\Option::get('send_emails_problem')) { + $flashes[] = [ + 'type' => 'warning', + 'text' => __('There is a problem processing outgoing mail queue — an admin should check :%a_begin%System Status:%a_end% and :%a_begin_recommendations%Recommendations:%a_end%', ['%a_begin%' => '', '%a_end%' => '', /*'%a_begin_logs%' => '',*/ '%a_begin_recommendations%' => '']), + 'unescaped' => true, + ]; + } + + return $flashes; + } + + public static function mbUcfirst($string, $encoding = 'UTF-8') + { + $first_char = mb_substr($string, 0, 1, $encoding); + $then = mb_substr($string, 1, null, $encoding); + return mb_strtoupper($first_char, $encoding) . $then; + } + + /** + * This is needed to allow using regexes for large texts. + */ + public static function setPcreBacktrackLimit() + { + if ((int)ini_get('pcre.backtrack_limit') <= 1000000) { + ini_set('pcre.backtrack_limit', 1000000000); + } + } + + /** + * Get client IP address. + */ + public static function getClientIp() + { + // Fix for CloudFlare: https://laracasts.com/discuss/channels/laravel/cloudflare-and-user-ip + // But if CloudFlare is not used any value can be set to "Cf-Connecting-Ip" header. + // if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { + // $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"]; + // } + return request()->ip(); + } + + public static function getTimeFormat() + { + $user = auth()->user(); + + if ($user) { + return $user->time_format; + } else { + return Option::get('time_format', User::TIME_FORMAT_24); + } + } + + public static function isTimeFormat24() + { + return self::getTimeFormat() == User::TIME_FORMAT_24; + } + + /** + * Runs artisan command and returns it's output. + */ + public static function runCommand($command, $options = []) + { + $output_buffer = new BufferedOutput(); + \Artisan::call($command, $options, $output_buffer); + + return $output_buffer->fetch(); + } + + public static function setCurlDefaultOptions($ch) + { + // Curl has default CURLOPT_CONNECTTIMEOUT=30 seconds. + curl_setopt($ch, CURLOPT_TIMEOUT, config('app.curl_timeout')); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, config('app.curl_connect_timeout')); + curl_setopt($ch, CURLOPT_PROXY, config('app.proxy')); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, config('app.curl_ssl_verifypeer')); + } + + public static function setGuzzleDefaultOptions($params) + { + $default_params = [ + 'timeout' => config('app.curl_timeout'), + 'connect_timeout' => config('app.curl_connect_timeout'), + 'proxy' => config('app.proxy'), + // https://docs.guzzlephp.org/en/6.5/request-options.html#verify + 'verify' => config('app.curl_ssl_verifypeer'), + ]; + + return array_merge($default_params, $params); + } + + public static function cspNonce() + { + if (self::$csp_nonce === null) { + self::$csp_nonce = \Str::random(25); + } + + return self::$csp_nonce; + } + + public static function cspMetaTag() + { + if (!config('app.csp_enabled')) { + return ''; + } + + $nonce = \Helper::cspNonce(); + + return ""; + //"; + } + + public static function cspNonceAttr() + { + if (!config('app.csp_enabled')) { + return ''; + } + + return ' nonce="'.\Helper::cspNonce().'"'; + } + + public static function isChatModeAvailable() + { + return count(CustomerChannel::getChannels()); + } + + public static function isChatMode() + { + return (int)\Session::get('chat_mode', 0); + } + + public static function setChatMode($is_on) + { + if ((int)$is_on) { + \Session::put('chat_mode', 1); + } else { + \Session::forget('chat_mode'); + } + } + + public static function detectCloudFlare() + { + if (!empty($_SERVER['HTTP_CF_IPCOUNTRY']) + || !empty($_SERVER['HTTP_CF_CONNECTING_IP']) + || !empty($_SERVER['HTTP_CF_VISITOR']) + || !empty($_SERVER['HTTP_CF_RAY']) + || ($_SERVER['HTTP_CDN_LOOP'] ?? '') == 'cloudflare' + ) { + return true; + } else { + return false; + } + } + + // Correct format: 2023-12-14 19:21 + // Datepicker with enableTime option enabled + // may return value in different format on iOS Safari: 2023-12-14T11:25 + public static function sanitizeDatepickerDatetime($datetime) + { + return str_replace('T', ' ', $datetime); + } +} diff --git a/freescout-dist/app/Misc/Mail.php b/freescout-dist/app/Misc/Mail.php new file mode 100644 index 0000000..56b05a6 --- /dev/null +++ b/freescout-dist/app/Misc/Mail.php @@ -0,0 +1,1044 @@ + to avoid false positives. + * Regex separators has "regex:" in the beginning. + */ + public static $alternative_reply_separators = [ + self::REPLY_SEPARATOR_HTML, // Our HTML separator + self::REPLY_SEPARATOR_TEXT, // Our plain text separator + + // Email service providers specific separators. + '
', // Gmail + '
', // Outlook / Live / Hotmail / Microsoft + '
[^<]*

/', // MS Outlook + + // General separators. + 'regex:/])*>/', // General sepator. Should skip Gmail's

. + '', + '‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐', + '--------------- Original Message ---------------', + '-------- Αρχικό μήνυμα --------', // Greek + ]; + + /** + * md5 of the last applied mail config. + */ + public static $last_mail_config_hash = ''; + + /** + * Used to get SMTP queue id when sending emails to customers. + */ + public static $smtp_queue_id_plugin_registered = false; + + /** + * Configure mail sending parameters. + * + * @param App\Mailbox $mailbox + * @param App\User $user_from + * @param App\Conversation $conversation + */ + public static function setMailDriver($mailbox = null, $user_from = null, $conversation = null) + { + if ($mailbox) { + // Configure mail driver according to Mailbox settings + \Config::set('mail.driver', $mailbox->getMailDriverName()); + \Config::set('mail.from', $mailbox->getMailFrom($user_from, $conversation)); + + // SMTP + if ($mailbox->out_method == Mailbox::OUT_METHOD_SMTP) { + \Config::set('mail.host', $mailbox->out_server); + \Config::set('mail.port', $mailbox->out_port); + if (!$mailbox->out_username) { + \Config::set('mail.username', null); + \Config::set('mail.password', null); + } else { + \Config::set('mail.username', $mailbox->out_username); + \Config::set('mail.password', $mailbox->out_password); + } + \Config::set('mail.encryption', $mailbox->getOutEncryptionName()); + } + } else { + // Use default settings + \Config::set('mail.driver', \Config::get('mail.driver')); + \Config::set('mail.from', ['address' => self::getSystemMailFrom(), 'name' => '']); + } + + self::reapplyMailConfig(); + } + + /** + * Reapply new mail config. + */ + public static function reapplyMailConfig() + { + // Check hash to avoid recreating MailServiceProvider. + $mail_config_hash = md5(json_encode(\Config::get('mail'))); + + if (self::$last_mail_config_hash != $mail_config_hash) { + self::$last_mail_config_hash = $mail_config_hash; + } else { + return false; + } + + // Without doing this, Swift mailer uses old config values + // if there were emails sent with previous config. + \App::forgetInstance('mailer'); + \App::forgetInstance('swift.mailer'); + \App::forgetInstance('swift.transport'); + + (new \Illuminate\Mail\MailServiceProvider(app()))->register(); + // We have to update Mailer facade manually, as it does not happen automatically + // and previous instance of app('mailer') is used. + \Mail::swap(app('mailer')); + + \Eventy::action('mail.reapply_mail_config'); + } + + /** + * Set system mail driver for sending system emails to users. + * + * @param App\Mailbox $mailbox + */ + public static function setSystemMailDriver() + { + \Config::set('mail.driver', self::getSystemMailDriver()); + \Config::set('mail.from', [ + 'address' => self::getSystemMailFrom(), + 'name' => Option::get('company_name', \Config::get('app.name')), + ]); + + // SMTP + if (\Config::get('mail.driver') == self::MAIL_DRIVER_SMTP) { + \Config::set('mail.host', Option::get('mail_host')); + \Config::set('mail.port', Option::get('mail_port')); + if (!Option::get('mail_username')) { + \Config::set('mail.username', null); + \Config::set('mail.password', null); + } else { + \Config::set('mail.username', Option::get('mail_username')); + \Config::set('mail.password', \Helper::decrypt(Option::get('mail_password'))); + } + \Config::set('mail.encryption', Option::get('mail_encryption')); + } + + self::reapplyMailConfig(); + } + + /** + * Replace mail vars in the text. + */ + public static function replaceMailVars($text, $data = [], $escape = false, $remove_non_replaced = false) + { + // Available variables to insert into email in UI. + $vars = []; + + if (!empty($data['conversation'])) { + $vars['{%subject%}'] = $data['conversation']->subject; + $vars['{%conversation.number%}'] = $data['conversation']->number; + $vars['{%customer.email%}'] = $data['conversation']->customer_email; + } + if (!empty($data['mailbox'])) { + $vars['{%mailbox.email%}'] = $data['mailbox']->email; + $vars['{%mailbox.name%}'] = $data['mailbox']->name; + // To avoid recursion. + if (isset($data['mailbox_from_name'])) { + $vars['{%mailbox.fromName%}'] = $data['mailbox_from_name']; + } else { + $vars['{%mailbox.fromName%}'] = $data['mailbox']->getMailFrom(!empty($data['user']) ? $data['user'] : null)['name']; + } + } + if (!empty($data['customer'])) { + $vars['{%customer.fullName%}'] = $data['customer']->getFullName(true); + $vars['{%customer.firstName%}'] = $data['customer']->getFirstName(true); + $vars['{%customer.lastName%}'] = $data['customer']->last_name; + $vars['{%customer.company%}'] = $data['customer']->company; + } + if (!empty($data['user'])) { + $vars['{%user.fullName%}'] = $data['user']->getFullName(); + $vars['{%user.firstName%}'] = $data['user']->getFirstName(); + $vars['{%user.phone%}'] = $data['user']->phone; + $vars['{%user.email%}'] = $data['user']->email; + $vars['{%user.jobTitle%}'] = $data['user']->job_title; + $vars['{%user.lastName%}'] = $data['user']->last_name; + $vars['{%user.photoUrl%}'] = $data['user']->getPhotoUrl(); + } + + $vars = \Eventy::filter('mail_vars.replace', $vars, $data); + + if ($escape) { + foreach ($vars as $i => $var) { + $vars[$i] = htmlspecialchars($var ?? ''); + $vars[$i] = nl2br($vars[$i]); + } + } else { + foreach ($vars as $i => $var) { + $vars[$i] = nl2br($var ?? ''); + } + } + + $result = strtr($text, $vars); + + // Remove non-replaced placeholders. + if ($remove_non_replaced) { + $result = preg_replace('#\{%[^\.%\}]+\.[^%\}]+%\}#', '', $result ?? ''); + $result = trim($result); + } + + return $result; + } + + /** + * Check if text has vars in it. + */ + public static function hasVars($text) + { + return preg_match('/({%|%})/', $text ?? ''); + } + + /** + * Remove email from a list of emails. + */ + public static function removeEmailFromArray($list, $email) + { + return array_diff($list, [$email]); + } + + /** + * From address for sending system emails. + */ + public static function getSystemMailFrom() + { + $mail_from = Option::get('mail_from'); + if (!$mail_from) { + $mail_from = 'freescout@'.\Helper::getDomain(); + } + + return $mail_from; + } + + /** + * Mail driver for sending system emails. + */ + public static function getSystemMailDriver() + { + return Option::get('mail_driver', 'mail'); + } + + /** + * Send test email from mailbox. + */ + public static function sendTestMail($to, $mailbox = null) + { + if ($mailbox) { + // Configure mail driver according to Mailbox settings + \MailHelper::setMailDriver($mailbox); + + $status_message = ''; + + try { + \Mail::to([$to])->send(new \App\Mail\Test($mailbox)); + } catch (\Exception $e) { + // We come here in case SMTP server unavailable for example + $status_message = $e->getMessage(); + } + } else { + // System email + \MailHelper::setSystemMailDriver(); + + $status_message = ''; + + try { + \Mail::to([['name' => '', 'email' => $to]]) + ->send(new \App\Mail\Test()); + } catch (\Exception $e) { + // We come here in case SMTP server unavailable for example + $status_message = $e->getMessage(); + } + } + + if (\Mail::failures() || $status_message) { + SendLog::log(null, null, $to, SendLog::MAIL_TYPE_TEST, SendLog::STATUS_SEND_ERROR, null, null, $status_message); + if ($status_message) { + throw new \Exception($status_message, 1); + } else { + return false; + } + } else { + SendLog::log(null, null, $to, SendLog::MAIL_TYPE_TEST, SendLog::STATUS_ACCEPTED); + + return true; + } + } + + /** + * Check POP3/IMAP connection to the mailbox. + */ + public static function fetchTest($mailbox) + { + $client = \MailHelper::getMailboxClient($mailbox); + + // Connect to the Server + $client->connect(); + + // Get folder + $folder = $client->getFolder('INBOX'); + + if (!$folder) { + throw new \Exception('Could not get mailbox folder: INBOX', 1); + } + // Get unseen messages for a period + $messages = $folder->query()->unseen()->since(now()->subDays(1))->leaveUnread()->get(); + + $last_error = ''; + if (method_exists($client, 'getLastError')) { + $last_error = $client->getLastError(); + } + + if ($last_error && stristr($last_error, 'The specified charset is not supported')) { + // Solution for MS mailboxes. + // https://github.com/freescout-helpdesk/freescout/issues/176 + $messages = $folder->query()->unseen()->since(now()->subDays(1))->leaveUnread()->setCharset(null)->get(); + if (count($client->getErrors()) > 1) { + $last_error = $client->getLastError(); + } else { + $last_error = null; + } + } + + if ($last_error) { + throw new \Exception($last_error, 1); + } else { + return true; + } + } + + /** + * Convert list of emails to array. + * + * @return array + */ + public static function sanitizeEmails($emails) + { + $emails_array = []; + + if (is_array($emails)) { + $emails_array = $emails; + } else { + $emails_array = explode(',', $emails ?? ''); + } + + foreach ($emails_array as $i => $email) { + $emails_array[$i] = \App\Email::sanitizeEmail($email); + if (!$emails_array[$i]) { + unset($emails_array[$i]); + } + } + + return $emails_array; + } + + /** + * Check if email format is valid. + * + * @param [type] $email [description] + * + * @return [type] [description] + */ + public static function validateEmail($email) + { + return filter_var($email, FILTER_VALIDATE_EMAIL); + } + + /** + * Send system alert to super admin. + */ + public static function sendAlertMail($text, $title = '') + { + \App\Jobs\SendAlert::dispatch($text, $title)->onQueue('emails'); + } + + /** + * Send email to developers team. + */ + public static function sendEmailToDevs($subject, $body, $attachments = [], $from_user = null) + { + // Configure mail driver according to Mailbox settings + \MailHelper::setSystemMailDriver(); + + $status_message = ''; + + try { + \Mail::raw($body, function ($message) use ($subject, $attachments, $from_user) { + $message + ->subject($subject) + ->to(\Config::get('app.freescout_email')); + if ($attachments) { + foreach ($attachments as $attachment) { + $message->attach($attachment); + } + } + // Set user as Reply-To + if ($from_user) { + $message->replyTo($from_user->email, $from_user->getFullName()); + } + }); + } catch (\Exception $e) { + \Log::error(\Helper::formatException($e)); + // We come here in case SMTP server unavailable for example + return false; + } + + if (\Mail::failures()) { + return false; + } else { + return true; + } + } + + /** + * Get email marker for the outgoing email to track replies + * in case Message-ID header is removed by mail service provider. + * + * @param [type] $message_id [description] + * + * @return [type] [description] + */ + public static function getMessageMarker($message_id) + { + // It has to be BASE64, as Gmail converts it into link. + return '{#FS:'.base64_encode($message_id).'#}'; + } + + /** + * Fetch Message-ID from incoming email body. + * + * @param [type] $message_id [description] + * + * @return [type] [description] + */ + public static function fetchMessageMarkerValue($body) + { + preg_match('/{#FS:([^#]+)#}/', $body ?? '', $matches); + if (!empty($matches[1]) && base64_decode($matches[1])) { + // Return first found marker. + return base64_decode($matches[1]); + } + + return ''; + } + + public static function getMessageIdHash($thread_id) + { + return substr(md5($thread_id.config('app.key')), 0, 16); + } + + /** + * Detect autoresponder by headers. + * https://github.com/jpmckinney/multi_mail/wiki/Detecting-autoresponders + * https://www.jitbit.com/maxblog/18-detecting-outlook-autoreplyout-of-office-emails-and-x-auto-response-suppress-header/. + * + * @return bool [description] + */ + public static function isAutoResponder($headers_str) + { + $autoresponder_headers = [ + 'x-autoreply' => '', + 'x-autorespond' => '', + 'auto-submitted' => '', // this can be auto-replied, auto-generated, etc. + 'precedence' => ['auto_reply', 'bulk', 'junk'], + 'x-precedence' => ['auto_reply', 'bulk', 'junk'], + ]; + $headers = explode("\n", $headers_str ?? ''); + + foreach ($autoresponder_headers as $auto_header => $auto_header_value) { + foreach ($headers as $header) { + $parts = explode(':', $header, 2); + if (count($parts) == 2) { + $name = trim(strtolower($parts[0])); + $value = trim($parts[1]); + } else { + continue; + } + if (strtolower($name) == $auto_header) { + if (!$auto_header_value) { + return true; + } elseif (is_array($auto_header_value)) { + foreach ($auto_header_value as $auto_header_value_item) { + if ($value == $auto_header_value_item) { + return true; + } + } + } elseif ($value == $auto_header_value) { + return true; + } + } + } + } + + return false; + } + + /** + * Check Content-Type header. + * This is not 100% reliable, detects only standard DSN bounces. + * + * @param [type] $headers [description] + * + * @return [type] [description] + */ + public static function detectBounceByHeaders($headers) + { + if (preg_match("/Content-Type:((?:[^\n]|\n[\t ])+)(?:\n[^\t ]|$)/i", $headers, $match) + && preg_match("/multipart\/report/i", $match[1]) + && preg_match("/report-type=[\"']?delivery-status[\"']?/i", $match[1]) + ) { + return true; + } else { + return false; + } + } + + /** + * Parse email headers. + * + * @param [type] $headers_str [description] + * + * @return [type] [description] + */ + public static function parseHeaders($headers_str) + { + try { + return imap_rfc822_parse_headers($headers_str); + } catch (\Exception $e) { + return; + } + } + + public static function getHeader($headers_str, $header) + { + $headers = self::parseHeaders($headers_str); + if (!$headers) { + return; + } + $value = null; + if (property_exists($headers, $header)) { + $value = $headers->$header; + } else { + return; + } + switch ($header) { + case 'message_id': + $value = str_replace(['<', '>'], '', $value); + break; + } + + return $value; + } + + /** + * Get client for fetching emails. + */ + public static function getMailboxClient($mailbox) + { + $oauth = $mailbox->oauthEnabled(); + $new_library = config('app.new_fetching_library'); + + if (!$oauth && !$new_library) { + return new \Webklex\IMAP\Client([ + 'host' => $mailbox->in_server, + 'port' => $mailbox->in_port, + 'encryption' => $mailbox->getInEncryptionName(), + 'validate_cert' => $mailbox->in_validate_cert, + 'username' => $mailbox->in_username, + 'password' => $mailbox->in_password, + 'protocol' => $mailbox->getInProtocolName(), + ]); + } else { + + if ($oauth) { + \Config::set('imap.accounts.default', [ + 'host' => $mailbox->in_server, + 'port' => $mailbox->in_port, + 'encryption' => $mailbox->getInEncryptionName(), + 'validate_cert' => $mailbox->in_validate_cert, + 'username' => $mailbox->email, + 'password' => $mailbox->oauthGetParam('a_token'), + 'protocol' => $mailbox->getInProtocolName(), + 'authentication' => 'oauth', + ]); + } else { + \Config::set('imap.accounts.default', [ + 'host' => $mailbox->in_server, + 'port' => $mailbox->in_port, + 'encryption' => $mailbox->getInEncryptionName(), + 'validate_cert' => $mailbox->in_validate_cert, + // 'username' => $mailbox->email, + // 'password' => $mailbox->oauthGetParam('a_token'), + // 'protocol' => $mailbox->getInProtocolName(), + // 'authentication' => 'oauth', + 'username' => $mailbox->in_username, + 'password' => $mailbox->in_password, + 'protocol' => $mailbox->getInProtocolName(), + ]); + } + // To enable debug: /vendor/webklex/php-imap/src/Connection/Protocols + // Debug in console + if (app()->runningInConsole()) { + \Config::set('imap.options.debug', config('app.debug')); + } + + $cm = new \Webklex\PHPIMAP\ClientManager(config('imap')); + + // Refresh Access Token. + if ($oauth) { + if ((strtotime($mailbox->oauthGetParam('issued_on')) + (int)$mailbox->oauthGetParam('expires_in')) < time()) { + // Try to get an access token (using the authorization code grant) + $token_data = \MailHelper::oauthGetAccessToken(\MailHelper::OAUTH_PROVIDER_MICROSOFT, [ + 'client_id' => $mailbox->in_username, + 'client_secret' => $mailbox->in_password, + 'refresh_token' => $mailbox->oauthGetParam('r_token'), + ]); + + if (!empty($token_data['a_token'])) { + $mailbox->setMetaParam('oauth', $token_data, true); + } elseif (!empty($token_data['error'])) { + $error_message = 'Error occurred refreshing oAuth Access Token: '.$token_data['error']; + \Helper::log(\App\ActivityLog::NAME_EMAILS_FETCHING, + \App\ActivityLog::DESCRIPTION_EMAILS_FETCHING_ERROR, [ + 'error' => $error_message, + 'mailbox' => $mailbox->name, + ]); + throw new \Exception($error_message, 1); + } + } + } + + // This makes it authenticate two times. + //$cm->setTimeout(60); + + return $cm->account('default'); + } + } + + /** + * Generate artificial Message-ID. + */ + public static function generateMessageId($email_address, $raw_body = '') + { + $hash = str_random(16); + if ($raw_body) { + $hash = md5(strval($raw_body)); + } + + return 'fs-'.$hash.'@'.preg_replace("/.*@/", '', $email_address); + } + + /** + * Fetch IMAP message by Message-ID. + */ + public static function fetchMessage($mailbox, $message_id, $message_date = null) + { + $no_charset = false; + + if (!$message_id) { + return null; + } + + try { + $client = \MailHelper::getMailboxClient($mailbox); + $client->connect(); + } catch (\Exception $e) { + \Helper::logException($e, '('.$mailbox->name.') Could not fetch specific message by Message-ID via IMAP:'); + return null; + } + + $imap_folders = \Eventy::filter('mail.fetch_message.imap_folders', $mailbox->getInImapFolders(), $mailbox); + + foreach ($imap_folders as $folder_name) { + try { + $folder = self::getImapFolder($client, $folder_name); + // Message-ID: <123@123.com> + $query = $folder->query() + ->text('<'.$message_id.'>') + ->leaveUnread() + ->limit(1); + + // Limit using date to speed up the search. + if ($message_date) { + $query->since($message_date->subDays(7)); + // Here we should add 14 days, as previous line subtracts 7 days. + $query->before($message_date->addDays(14)); + } + + if ($no_charset) { + $query->setCharset(null); + } + + $messages = $query->get(); + + $last_error = ''; + if (method_exists($client, 'getLastError')) { + $last_error = $client->getLastError(); + } + + if ($last_error && stristr($last_error, 'The specified charset is not supported')) { + // Solution for MS mailboxes. + // https://github.com/freescout-helpdesk/freescout/issues/176 + $query = $folder->query()->text('<'.$message_id.'>')->leaveUnread()->limit(1)->setCharset(null); + if ($message_date) { + $query->since($message_date->subDays(7)); + $query->before($message_date->addDays(7)); + } + $messages = $query->get(); + $no_charset = true; + } + + if (count($messages)) { + return $messages->first(); + } + + } catch (\Exception $e) { + \Helper::logException($e, '('.$mailbox->name.') Could not fetch specific message by Message-ID via IMAP:'); + } + } + + return null; + } + + public static function oauthGetAuthorizationUrl($provider_code, $params) + { + $args = []; + + switch ($provider_code) { + case self::OAUTH_PROVIDER_MICROSOFT: + // https://docs.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth + $args = [ + 'scope' => 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send', + 'response_type' => 'code', + 'approval_prompt' => 'auto', + 'redirect_uri' => route('mailboxes.oauth_callback'), + ]; + $args = array_merge($args, $params); + $url = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?'.http_build_query($args); + break; + } + + return $url; + } + + public static function oauthGetAccessToken($provider_code, $params) + { + $token_data = []; + $post_params = []; + + switch ($provider_code) { + case self::OAUTH_PROVIDER_MICROSOFT: + $post_params = [ + 'scope' => 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send', + "grant_type" => "authorization_code", + 'redirect_uri' => route('mailboxes.oauth_callback'), + ]; + + $post_params = array_merge($post_params, $params); + + // Refreshing Access Token. + if (!empty($post_params['refresh_token'])) { + $post_params['grant_type'] = 'refresh_token'; + } + + // $postUrl = "/common/oauth2/token"; + // $hostname = "login.microsoftonline.com"; + $full_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; + + // $headers = array( + // // "POST " . $postUrl . " HTTP/1.1", + // // "Host: login.windows.net", + // "Content-type: application/x-www-form-urlencoded", + // ); + + $curl = curl_init($full_url); + + curl_setopt($curl, CURLOPT_POST, true); + //curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_POSTFIELDS, $post_params); + curl_setopt($curl, CURLOPT_HTTPHEADER, array("application/x-www-form-urlencoded")); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + \Helper::setCurlDefaultOptions($curl); + curl_setopt($curl, CURLOPT_TIMEOUT, 180); + + $response = curl_exec($curl); + + if ($response) { + $result = json_decode($response, true); + + // [token_type] => Bearer + // [scope] => IMAP.AccessAsUser.All offline_access SMTP.Send User.Read + // [expires_in] => 4514 + // [ext_expires_in] => 4514 + // [expires_on] => 1646122657 + // [not_before] => 1646117842 + // [resource] => 00000002-0000-0000-c000-000000000000 + // [access_token] => dd + // [refresh_token] => dd + // [id_token] => dd + if (!empty($result['access_token'])) { + $token_data['provider'] = self::OAUTH_PROVIDER_MICROSOFT; + $token_data['a_token'] = $result['access_token']; + $token_data['r_token'] = $result['refresh_token']; + //$token_data['id_token'] = $result['id_token']; + $token_data['issued_on'] = now()->toDateTimeString(); + $token_data['expires_in'] = $result['expires_in']; + } elseif ($response) { + $token_data['error'] = $response; + } else { + $token_data['error'] = 'Response code: '.curl_getinfo($curl, CURLINFO_HTTP_CODE); + } + } + curl_close($curl); + + break; + } + + return $token_data; + } + + public static function oauthDisconnect($provider_code, $redirect_uri) + { + switch ($provider_code) { + case self::OAUTH_PROVIDER_MICROSOFT: + return redirect()->away('https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri='.urlencode($redirect_uri)); + break; + } + } + + public static function prepareMailable($mailable) + { + $custom_headers_str = config('app.custom_mail_headers'); + + if (empty($custom_headers_str)) { + return; + } + + $custom_headers = explode(';', $custom_headers_str); + + $mailable->withSwiftMessage(function ($swiftmessage) use ($custom_headers) { + $headers = $swiftmessage->getHeaders(); + + foreach ($custom_headers as $custom_header) { + $header_parts = explode(':', $custom_header); + + $header_name = trim($header_parts[0] ?? ''); + $header_value = trim($header_parts[1] ?? ''); + if ($header_name && $header_value) { + $headers->addTextHeader($header_name, $header_value); + } + } + return $swiftmessage; + }); + } + + public static function getImapFolder($client, $folder_name) + { + // https://github.com/freescout-helpdesk/freescout/issues/3502 + $folder_name = mb_convert_encoding($folder_name, "UTF7-IMAP","UTF-8"); + + if (method_exists($client, 'getFolderByPath')) { + return $client->getFolderByPath($folder_name); + } else { + return $client->getFolder($folder_name); + } + } + + /** + * This function is used to decode email subjects and attachment names in Webklex libraries. + */ + public static function decodeSubject($subject) + { + // Remove new lines as iconv_mime_decode() may loose a part separated by new line: + // =?utf-8?Q?Gesch=C3=A4ftskonto?= erstellen =?utf-8?Q?f=C3=BCr?= + // 249143 + $subject = preg_replace("/[\r\n]/", '', $subject); + // https://github.com/freescout-helpdesk/freescout/issues/3185 + $subject = str_ireplace('=?iso-2022-jp?', '=?iso-2022-jp-ms?', $subject); + + // Sometimes imap_utf8() can't decode the subject, for example: + // =?iso-2022-jp?B?GyRCIXlCaBsoQjEzMhskQjlmISEhViUsITwlRyVzGyhCJhskQiUoJS8lOSVGJWolIiFXQGxMZ0U5JE4kPyRhJE4jURsoQiYbJEIjQSU1JW0lcyEhIVo3bjQpJSglLyU5JUYlaiUiISYlbyE8JS8hWxsoQg==?= + // and sometimes iconv_mime_decode() can't decode the subject. + // So we are using both. + // + // We are trying iconv_mime_decode() first because imap_utf8() + // decodes umlauts into two symbols: + // https://github.com/freescout-helpdesk/freescout/issues/2965 + + // Sometimes subject is split into parts and each part is base63 encoded. + // And sometimes it's first encoded and after that split. + // https://github.com/freescout-helpdesk/freescout/issues/3066 + + // Step 1. Abnormal way - text is encoded and split into parts. + + // Only one type of encoding should be used. + preg_match_all("/(=\?[^\?]+\?[BQ]\?)([^\?]+)(\?=)/i", $subject, $m); + $encodings = $m[1] ?? []; + array_walk($encodings, function($value) { + $value = strtolower($value); + }); + $one_encoding = count(array_unique($encodings)) == 1; + + if ($one_encoding) { + // First try to join all lines and parts. + // Keep in mind that there can be non-encoded parts also: + // =?utf-8?Q?Gesch=C3=A4ftskonto?= erstellen =?utf-8?Q?f=C3=BCr?= + preg_match_all("/(=\?[^\?]+\?[BQ]\?)([^\?]+)(\?=)[\r\n\t ]*/i", $subject, $m); + + $joined_parts = ''; + if (count($m[1]) > 1 && !empty($m[2]) && !preg_match("/[\r\n\t ]+[^=]/i", $subject)) { + // Example: GyRCQGlNVTtZRTkhIT4uTlMbKEI= + $joined_parts = $m[1][0].implode('', $m[2]).$m[3][0]; + + // Base64 and URL encoded string can't contain "=" in the middle + // https://stackoverflow.com/questions/6916805/why-does-a-base64-encoded-string-have-an-sign-at-the-end + $has_equal_in_the_middle = preg_match("#=+([^$\? =])#", $joined_parts); + + if (!$has_equal_in_the_middle) { + $subject_decoded = iconv_mime_decode($joined_parts, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, "UTF-8"); + + if ($subject_decoded + && trim($subject_decoded) != trim($joined_parts) + && trim($subject_decoded) != trim(rtrim($joined_parts, '=')) + && !self::isNotYetFullyDecoded($subject_decoded) + ) { + return $subject_decoded; + } + + // Try imap_utf8(). + // =?iso-2022-jp?B?IBskQiFaSEcyPDpuQ?= =?iso-2022-jp?B?C4wTU1qIVs3Mkp2JSIlLyU3JSItahsoQg==?= + $subject_decoded = \imap_utf8($joined_parts); + + if ($subject_decoded + && trim($subject_decoded) != trim($joined_parts) + && trim($subject_decoded) != trim(rtrim($joined_parts, '=')) + && !self::isNotYetFullyDecoded($subject_decoded) + ) { + return $subject_decoded; + } + } + } + } + + // Step 2. Standard way - each part is encoded separately. + + // iconv_mime_decode() can't decode: + // =?iso-2022-jp?B?IBskQiFaSEcyPDpuQC4wTU1qIVs3Mkp2JSIlLyU3JSItahsoQg==?= + $subject_decoded = iconv_mime_decode($subject, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, "UTF-8"); + + // Sometimes iconv_mime_decode() can't decode some parts of the subject: + // =?iso-2022-jp?B?IBskQiFaSEcyPDpuQC4wTU1qIVs3Mkp2JSIlLyU3JSItahsoQg==?= + // =?iso-2022-jp?B?GyRCQGlNVTtZRTkhIT4uTlMbKEI=?= + if (self::isNotYetFullyDecoded($subject_decoded)) { + $subject_decoded = \imap_utf8($subject); + } + + // All previous functions could not decode text. + // mb_decode_mimeheader() properly decodes umlauts into one unice symbol. + // But we use mb_decode_mimeheader() as a last resort as it may garble some symbols. + // Example: =?ISO-8859-1?Q?Vorgang 538336029: M=F6chten Sie Ihre E-Mail-Adresse =E4ndern??= + if (self::isNotYetFullyDecoded($subject_decoded)) { + $subject_decoded = mb_decode_mimeheader($subject); + } + + if (!$subject_decoded) { + $subject_decoded = $subject; + } + + return $subject_decoded; + } + + public static function isNotYetFullyDecoded($subject_decoded) { + // https://stackoverflow.com/questions/15276191/why-does-a-diamond-with-a-questionmark-in-it-appear-in-my-html + $invalid_utf_symbols = ['�']; + + return preg_match_all("/=\?[^\?]+\?[BQ]\?/i", $subject_decoded) + || !mb_check_encoding($subject_decoded, 'UTF-8') + || \Str::contains($subject_decoded, $invalid_utf_symbols); + } + + // public static function oauthGetProvider($provider_code, $params) + // { + // $provider = null; + + // switch ($provider_code) { + // case self::OAUTH_PROVIDER_MICROSOFT: + // $provider = new \Stevenmaguire\OAuth2\Client\Provider\Microsoft([ + // // Required + // 'clientId' => $params['client_id'], + // 'clientSecret' => $params['client_secret'], + // 'redirectUri' => route('mailboxes.oauth_callback'), + // //https://login.microsoftonline.com/common/oauth2/authorize'; + // 'urlAuthorize' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + // 'urlAccessToken' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + // 'urlResourceOwnerDetails' => 'https://outlook.office.com/api/v1.0/me' + // ]); + // break; + // } + + // return $provider; + // } +} diff --git a/freescout-dist/app/Misc/SwiftGetSmtpQueueId.php b/freescout-dist/app/Misc/SwiftGetSmtpQueueId.php new file mode 100644 index 0000000..8ac5b11 --- /dev/null +++ b/freescout-dist/app/Misc/SwiftGetSmtpQueueId.php @@ -0,0 +1,19 @@ +getResponse(); + if (strpos($response_text, 'queued') !== false) { + preg_match("#queued as ([^\$\r\n ]+)[$\r\n]#", $response_text, $m); + if (!empty($m[1]) && trim($m[1])) { + self::$last_smtp_queue_id = trim($m[1]); + } + } + } +} \ No newline at end of file diff --git a/freescout-dist/app/Misc/WpApi.php b/freescout-dist/app/Misc/WpApi.php new file mode 100644 index 0000000..be289c2 --- /dev/null +++ b/freescout-dist/app/Misc/WpApi.php @@ -0,0 +1,176 @@ +request('POST', $url, \Helper::setGuzzleDefaultOptions([ + 'connect_timeout' => 10, + 'form_params' => $params, + ])); + } else { + $params['v'] = config('app.version'); + return $client->request('GET', $url, \Helper::setGuzzleDefaultOptions([ + 'connect_timeout' => 10, + 'query' => $params, + ])); + } + } + + /** + * API request. + */ + public static function request($method, $endpoint, $params = [], $alternative_api = false) + { + self::$lastError = null; + + try { + $response = self::httpRequest($method, self::url($endpoint, $alternative_api), $params); + } catch (\Exception $e) { + if (!$alternative_api) { + return self::request($method, $endpoint, $params, true); + } + \Helper::logException($e, 'WpApi'); + self::$lastError = [ + 'code' => $e->getCode(), + 'message' => $e->getMessage(), + ]; + + return []; + } + + // https://guzzle3.readthedocs.io/http-client/response.html + if ($response->getStatusCode() < 500) { + $json = \Helper::jsonToArray($response->getBody()); + + if (!empty($json['code']) && !empty($json['message']) && + !empty($json['data']) && !empty($json['data']['status']) && $json['data']['status'] != 200 + ) { + self::$lastError = $json; + // Maybe log error here + return []; + } else { + return $json; + } + } else { + return []; + } + } + + /** + * Get modules. + */ + public static function getModules() + { + return self::request(self::METHOD_GET, self::ENDPOINT_MODULES); + } + + /** + * Check module license. + */ + public static function checkLicense($params) + { + $params['action'] = self::ACTION_CHECK_LICENSE; + + $endpoint = self::ENDPOINT_MODULES; + + if (!empty($params['module_alias'])) { + $endpoint .= '/'.$params['module_alias']; + } + + return self::request(self::METHOD_POST, $endpoint, $params); + } + + /** + * Check module license. + */ + public static function checkLicenses($params) + { + $params['action'] = self::ACTION_CHECK_LICENSES; + + $endpoint = self::ENDPOINT_MODULES; + + return self::request(self::METHOD_POST, $endpoint, $params); + } + + /** + * Activate module license. + */ + public static function activateLicense($params) + { + $params['action'] = self::ACTION_ACTIVATE_LICENSE; + + $endpoint = self::ENDPOINT_MODULES; + + if (!empty($params['module_alias'])) { + $endpoint .= '/'.$params['module_alias']; + } + + return self::request(self::METHOD_POST, $endpoint, $params); + } + + /** + * Deactivate module license. + */ + public static function deactivateLicense($params) + { + $params['action'] = self::ACTION_DEACTIVATE_LICENSE; + + $endpoint = self::ENDPOINT_MODULES; + + if (!empty($params['module_alias'])) { + $endpoint .= '/'.$params['module_alias']; + } + + return self::request(self::METHOD_POST, $endpoint, $params); + } + + /** + * Get license details. + */ + public static function getVersion($params) + { + $params['action'] = self::ACTION_GET_VERSION; + + $endpoint = self::ENDPOINT_MODULES; + + if (!empty($params['module_alias'])) { + $endpoint .= '/'.$params['module_alias']; + } + + return self::request(self::METHOD_POST, $endpoint, $params); + } +} diff --git a/freescout-dist/app/Module.php b/freescout-dist/app/Module.php new file mode 100644 index 0000000..c6da4c4 --- /dev/null +++ b/freescout-dist/app/Module.php @@ -0,0 +1,544 @@ +active; + } else { + return false; + } + } + + public static function setActive($alias, $active, $save = true) + { + $module = self::getByAliasOrCreate($alias); + $module->active = $active; + if ($save) { + $module->save(); + } + + return true; + } + + /** + * Is module license activated. + */ + public static function isLicenseActivated($alias, $author_url) + { + // If module is from modules directory, license activation is required + if ($author_url && self::isOfficial($author_url)) { + $module = self::getByAlias($alias); + if ($module) { + return $module->activated; + } else { + return false; + } + } else { + return true; + } + } + + public static function isOfficial($author_url) + { + return parse_url($author_url ?? '', PHP_URL_HOST) == parse_url(\Config::get('app.freescout_url') ?? '', PHP_URL_HOST); + } + + /** + * Activate module license. + * + * @param [type] $alias [description] + * @param [type] $details_url [description] + * + * @return bool [description] + */ + public static function activateLicense($alias, $license) + { + $module = self::getByAliasOrCreate($alias); + $module->license = $license; + $module->activated = true; + $module->save(); + } + + public static function deactivateLicense($alias, $license) + { + $module = self::getByAliasOrCreate($alias); + $module->license = $license; + $module->activated = false; + $module->save(); + } + + public static function getByAliasOrCreate($alias) + { + $module = self::getByAlias($alias); + if (!$module) { + $module = new self(); + $module->alias = $alias; + } + + return $module; + } + + /** + * Get module license. + */ + public static function getLicense($alias) + { + $module = self::getByAlias($alias); + if ($module) { + return $module->license; + } else { + return ''; + } + } + + public static function setLicense($alias, $license) + { + $module = self::getByAliasOrCreate($alias); + $module->license = $license; + $module->save(); + } + + public static function normalizeAlias($alias) + { + return trim(strtolower($alias)); + } + + public static function getByAlias($alias) + { + $modules = self::getCached(); + if ($modules) { + return self::getCached()->where('alias', $alias)->first(); + } else { + return; + } + } + + /** + * Deactivate module and update modules cache. + */ + public static function deactiveModule($alias, $clear_app_cache = true) + { + self::setActive($alias, false); + // Update modules cache + \Module::clearCache(); + if ($clear_app_cache) { + \Artisan::call('freescout:clear-cache'); + } + } + + /** + * Get URL used to active and check license. + * + * @return [type] [description] + */ + public static function getAppUrl() + { + return parse_url(\Config::get('app.url'), PHP_URL_HOST); + } + + /** + * Check missing extensions among required by module. + * + * @param [type] $required_extensions [description] + * + * @return [type] [description] + */ + public static function getMissingExtensions($required_extensions) + { + $missing = []; + + $list = explode(',', $required_extensions ?? ''); + if (!is_array($list) || !count($list)) { + return []; + } + foreach ($list as $ext) { + $ext = trim($ext); + if ($ext && !extension_loaded($ext)) { + $missing[] = $ext; + } + } + + return $missing; + } + + /** + * Check missing modules required by the module. + */ + public static function getMissingModules($required_modules, $modules = []) + { + $missing = []; + + if (!$modules) { + $modules = \Module::all(); + } + + if (!is_array($required_modules) || !count($required_modules)) { + return []; + } + foreach ($required_modules as $alias => $version) { + $module = null; + foreach ($modules as $module_item) { + if ($module_item->alias == $alias) { + $module = $module_item; + } + } + if (!$module) { + $missing[$alias] = $version; + continue; + } + + if (!self::isActive($alias) || !version_compare($module->version, $version, '>=')) { + $missing[$alias] = $version; + } + } + + return $missing; + } + + public static function formatName($name) + { + return preg_replace("/ Module($|.*\[.*\]$)/", '$1', $name); + } + + public static function formatModuleData($module_data) + { + // Add (Third-Party). + if (\App\Module::isOfficial($module_data['authorUrl']) + && $module_data['author'] != 'FreeScout' + && mb_substr(trim($module_data['name']), -1) != ']' + ) { + $module_data['name'] = $module_data['name'].' ['.__('Third-Party').']'; + } + return $module_data; + } + + public static function isThirdParty($module_data) + { + if (\App\Module::isOfficial($module_data['authorUrl']) + && $module_data['author'] != 'FreeScout' + ) { + return true; + } else { + return false; + } + } + + public static function getSymlinkPath($alias) + { + return public_path().\Module::getPublicPath($alias); + } + + // Check and try to fix invalid or missing symlinks. + public static function checkSymlinks($module_aliases = null) + { + $invalid_symlinks = []; + + if ($module_aliases === null) { + // Get all active modules. + foreach (\Module::all() as $module) { + if ($module->active()) { + $module_aliases[] = $module->getAlias(); + } + } + } + if ($module_aliases && count($module_aliases)) { + foreach ($module_aliases as $module_alias) { + $from = self::getSymlinkPath($module_alias); + + $create = false; + + // file_exists() also checks if symlink target exists. + // file_exists() and is_dir() may throw "open_basedir restriction in effect". + try { + if (!file_exists($from) || !is_link($from)) { + if (is_dir($from)) { + @rename($from, $from.'_'.date('YmdHis')); + } else { + @unlink($from); + } + $create = true; + } + } catch (\Exception $e) { + $create = true; + } + + // Skip this check. + // elseif (is_link($from) && readlink($symlink_path) != '') { + // // Symlink leads to the wrong place. + // $create = true; + // } + + // Try to create the symlink. + if ($create) { + $to = self::createModuleSymlink($module_alias); + + if ($to && (!is_link($from) || is_link($to) || !file_exists($from))) { + $invalid_symlinks[$from] = $to; + } + } + } + } + + return $invalid_symlinks; + } + + // There is similar function in ModuleInstall.php + public static function createModuleSymlink($alias) + { + $from = self::getSymlinkPath($alias); + + $module = \Module::findByAlias($alias); + if (!$module) { + return false; + } + + $to = $module->getExtraPath('Public'); + + // file_exists() may throw "open_basedir restriction in effect". + try { + // If module's Public is symlink. + if (is_link($to)) { + @unlink($to); + } + + // Symlimk may exist but lead to the module folder in a wrong case. + // So we need first try to remove it. + if (!file_exists($from)) { + @unlink($from); + } + + if (file_exists($from)) { + return $to; + } + + if (!file_exists($to)) { + // Try to create Public folder. + try { + \File::makeDirectory($to, \Helper::DIR_PERMISSIONS); + } catch (\Exception $e) { + // If it's a broken symlink. + if (is_link($to)) { + @unlink($to); + } + } + } + + try { + symlink($to, $from); + } catch (\Exception $e) { + \Log::error('Error occurred creating ['.$from.' » '.$to.'] symlink: '.$e->getMessage()); + //return false; + } + } catch (\Exception $e) { + return false; + } + + return $to; + } + + public static function updateModule($alias) + { + $result = [ + 'status' => 'error', + // Error message. + 'msg' => '', + 'msg_success' => '', + 'download_error' => false, + // Error message with the link for downloading the module. + 'download_msg' => '', + 'output' => '', + 'module_name' => '', + ]; + + $module = \Module::findByAlias($alias); + + if (!$module) { + $result['msg'] = __('Module not found').': '.$alias; + } + + // Get module name. + $name = '?'; + if ($module) { + $name = $module->getName(); + $result['module_name'] = $name; + } + + // Download new version. + if (!$result['msg']) { + $params = [ + 'license' => self::getLicense($alias), + 'module_alias' => $alias, + 'url' => self::getAppUrl(), + ]; + $license_details = WpApi::getVersion($params); + + if (WpApi::$lastError) { + $result['msg'] = WpApi::$lastError['message']; + } elseif (!empty($license_details['code']) && !empty($license_details['message'])) { + $result['msg'] = $license_details['message']; + } elseif (!empty($license_details['required_app_version']) && !\Helper::checkAppVersion($license_details['required_app_version'])) { + $result['msg'] = 'Module requires app version:'.' '.$license_details['required_app_version']; + } elseif (!empty($license_details['download_link'])) { + // Download module. + $module_archive = \Module::getPath().DIRECTORY_SEPARATOR.$alias.'.zip'; + + try { + \Helper::downloadRemoteFile($license_details['download_link'], $module_archive); + } catch (\Exception $e) { + $result['msg'] = $e->getMessage(); + } + + if (!file_exists($module_archive)) { + $result['download_error'] = true; + } else { + // Extract. + try { + // Sometimes by some reason Public folder becomes a symlink leading to itself. + // It causes an error during updating process. + // https://github.com/freescout-helpdesk/freescout/issues/2709 + $public_folder = $module->getPath().DIRECTORY_SEPARATOR.'Public'; + try { + if (is_link($public_folder)) { + unlink($public_folder); + } + } catch (\Exception $e) { + // Do nothing. + } + + \Helper::unzip($module_archive, \Module::getPath()); + } catch (\Exception $e) { + $result['msg'] = $e->getMessage(); + } + // Check if extracted module exists. + \Module::clearCache(); + $module = \Module::findByAlias($alias); + if (!$module) { + $result['download_error'] = true; + } + } + + // Remove archive. + if (file_exists($module_archive)) { + \File::delete($module_archive); + } + + if ($result['download_error']) { + $result['download_msg'] = __('Error occurred downloading the module. Please :%a_being%download:%a_end% module manually and extract into :folder', ['%a_being%' => '', '%a_end%' => '', 'folder' => ''.\Module::getPath().'']); + } + } elseif ($license_details['status'] && $result['msg'] = self::getErrorMessage($license_details['status'])) { + //$result['msg'] = ; + } else { + $result['msg'] = __('Error occurred').': '.json_encode($license_details); + } + } + + // Run post-update instructions. + if (!$result['msg'] && !$result['download_error']) { + + $output_log = new BufferedOutput(); + \Artisan::call('freescout:module-install', ['module_alias' => $alias], $output_log); + $result['output'] = $output_log->fetch() ?: ' '; + + $result['msg'] = __('Error occurred activating ":name" module', ['name' => $name]); + + if (session('flashes_floating') && is_array(session('flashes_floating'))) { + // Error. + // If there was any error, module has been deactivated via modules.register_error filter + $result['msg'] = ''; + foreach (session('flashes_floating') as $flash) { + $result['msg'] .= $flash['text'].' '; + } + } elseif (strstr($result['output'], 'Configuration cached successfully')) { + // Success. + $result['status'] = 'success'; + $result['msg'] = ''; + $result['msg_success'] = __('":name" module successfully updated!', ['name' => $name]); + } else { + // Error. + // Deactivate module. + \App\Module::setActive($alias, false); + \Artisan::call('freescout:clear-cache'); + } + } + + return $result; + } + + public static function getErrorMessage($code, $result = null) + { + $msg = ''; + + switch ($code) { + case 'missing': + $msg = __('License key does not exist'); + break; + case 'license_not_activable': + $msg = __("You have to activate each bundle's module separately"); + break; + case 'disabled': + $msg = __('License key has been revoked'); + break; + case 'no_activations_left': + $msg = __('No activations left for this license key').' ('.__("Use 'Deactivate License' link above to transfer license key from another domain").')'; + break; + case 'expired': + $msg = __('License key has expired'); + break; + case 'key_mismatch': + $msg = __('License key belongs to another module'); + break; + // This also happens when entering a valid license key for wrong module. + case 'invalid_item_id': + $msg = __('Invalid license key'); + //$msg = __('Module not found in the modules directory'); + break; + case 'site_inactive': + $msg = __('License key is activated on another domain.').' '.__("Use 'Deactivate License' link above to transfer license key from another domain"); + //$msg = __('Module not found in the modules directory'); + break; + default: + if ($result && !empty($result['error'])) { + $msg = __('Error code:'.' '.$result['error']); + } + break; + } + + return $msg; + } +} diff --git a/freescout-dist/app/Notifications/BroadcastNotification.php b/freescout-dist/app/Notifications/BroadcastNotification.php new file mode 100644 index 0000000..45bd049 --- /dev/null +++ b/freescout-dist/app/Notifications/BroadcastNotification.php @@ -0,0 +1,127 @@ +conversation = $conversation; + $this->thread = $thread; + $this->mediums = $mediums; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $user + * + * @return array + */ + public function via($user) + { + return [\App\Channels\RealtimeBroadcastChannel::class]; + // Standard "broadcast" channel creates a queuable event which runs broadcast for the broadcaster. + //return ['broadcast']; + } + + /** + * Get the broadcastable representation of the notification. + * + * @param mixed $notifiable + * + * @return BroadcastMessage + */ + public function toBroadcast($user) + { + return new BroadcastMessage([ + 'thread_id' => $this->thread->id, + 'number' => $this->conversation->number, + 'mediums' => $this->mediums, + ]); + } + + public static function fetchPayloadData($payload) + { + $data = []; + + if (empty($payload->thread_id) || empty($payload->mediums)) { + return $data; + } + + // Try to convert to array. + $mediums = (array)$payload->mediums; + + $thread = Thread::find($payload->thread_id); + + if (empty($thread)) { + return $data; + } + + // Dummy DB notification to pass to the template + $db_notification = new \Illuminate\Notifications\DatabaseNotification(); + + // HTML for the menu notification (uses same medium as for email) + if (in_array(Subscription::MEDIUM_EMAIL, $mediums)) { + $web_notifications_info = []; + + // Get last reply or note of the conversation to display it's text + $last_thread_body = ''; + $last_thread = Thread::where('conversation_id', $thread->conversation_id) + // Select must contain all fields from orderBy() to avoid: + // General error: 3065 Expression #1 of ORDER BY clause is not in SELECT + ->select(['body', 'created_at']) + ->whereIn('type', [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE, Thread::TYPE_NOTE]) + ->orderBy('created_at') + ->first(); + + if ($last_thread) { + $last_thread_body = $last_thread->body; + } + + //$db_notification->id = 'dummy'; + $web_notifications_info['notification'] = $db_notification; + $web_notifications_info['created_at'] = \Carbon\Carbon::now(); + // ['notification']->read_at + // ['notification']->id + $web_notifications_info['conversation'] = $thread->conversation; + $web_notifications_info['thread'] = $thread; + $web_notifications_info['last_thread_body'] = $last_thread_body; + + $data['web']['html'] = view('users/partials/web_notifications', [ + 'web_notifications_info_data' => [$web_notifications_info], + ])->render(); + } + + // Text and url for the browser push notification + if (in_array(Subscription::MEDIUM_BROWSER, $mediums)) { + $data['browser']['text'] = strip_tags($thread->getActionDescription($thread->conversation->number)); + $data['browser']['url'] = $thread->conversation->url(null, $thread->id, ['mark_as_read' => $db_notification->id]); + } + + return $data; + } +} diff --git a/freescout-dist/app/Notifications/WebsiteNotification.php b/freescout-dist/app/Notifications/WebsiteNotification.php new file mode 100644 index 0000000..629b4ea --- /dev/null +++ b/freescout-dist/app/Notifications/WebsiteNotification.php @@ -0,0 +1,158 @@ +conversation = $conversation; + $this->thread = $thread; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $user + * + * @return array + */ + public function via($user) + { + return ['database']; + } + + /** + * Get the array representation of the notification. + * + * @param mixed $user + * + * @return array + */ + public function toArray($user) + { + return [ + 'thread_id' => $this->thread->id, + 'conversation_id' => $this->conversation->id, + ]; + } + + /** + * Fetch data from DB for notifications list to display it. + */ + public static function fetchNotificationsData($notifications) + { + $data = []; + + $threads = []; + //$conversations = []; + + //$conversation_ids = []; + $thread_ids = []; + + // Get threads with their customers and users + foreach ($notifications as $notification) { + if (!empty($notification->data['thread_id'])) { + $thread_ids[] = $notification->data['thread_id']; + } + } + if ($thread_ids) { + $threads = Thread::whereIn('id', $thread_ids) + ->with('conversation') + ->with('created_by_user') + ->with('created_by_customer') + ->with('user') + ->get(); + } + + // Get last reply or note of the conversation to display it's text + // if ($threads) { + // $last_threads = Thread::whereIn('conversation_id', $threads->pluck('conversation_id')->unique()->toArray()) + // // Select must contain all fields from orderBy() to avoid: + // // General error: 3065 Expression #1 of ORDER BY clause is not in SELECT + // ->select(['id', 'conversation_id', 'body', 'created_at']) + // ->whereIn('type', [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE, Thread::TYPE_NOTE]) + // ->distinct('conversation_id') + // ->orderBy('created_at', 'desc') + // // We can not use groupBy because of "isn't in GROUP BY" + // //->groupBy('conversation_id') + // ->get(); + // } + + // Populate all collected data into array + foreach ($notifications as $notification) { + $conversation_number = ''; + if (!empty($notification->data['number'])) { + $conversation_number = $notification->data['number']; + } + + $thread = null; + $user = null; + $created_by_user = null; + $created_by_customer = null; + + if (!empty($notification->data['thread_id'])) { + $thread = $threads->firstWhere('id', $notification->data['thread_id']); + if (empty($thread)) { + continue; + } + if ($thread->user_id) { + $user = $thread->user; + } + if ($thread->created_by_user_id) { + $created_by_user = $thread->created_by_user_id; + } + if ($thread->created_by_customer_id) { + $created_by_customer = $thread->created_by_customer_id; + } + } else { + continue; + } + + // $last_thread_body = ''; + // $conversation = null; + + // $last_thread = $last_threads->firstWhere('conversation_id', $thread->conversation_id); + // if ($last_thread) { + $last_thread_body = $thread->body; + $conversation = $thread->conversation; + //} + if (empty($conversation)) { + continue; + } + + $data[] = [ + 'notification' => $notification, + 'created_at' => $notification->created_at, + 'conversation' => $conversation, + 'thread' => $thread, + 'last_thread_body' => $last_thread_body, + 'user' => $user, + 'created_by_user' => $created_by_user, + 'created_by_customer' => $created_by_customer, + ]; + } + + return $data; + } +} diff --git a/freescout-dist/app/Observers/AttachmentObserver.php b/freescout-dist/app/Observers/AttachmentObserver.php new file mode 100644 index 0000000..36f7016 --- /dev/null +++ b/freescout-dist/app/Observers/AttachmentObserver.php @@ -0,0 +1,18 @@ +source_via == Conversation::PERSON_USER) { + $conversation->read_by_user = true; + } + } + + /** + * On create. + * + * @param Conversation $conversation + */ + public function created(Conversation $conversation) + { + // Better to do it manually + //$conversation->mailbox->updateFoldersCounters(); + } + + /** + * On conversation delete. + * + * @param Conversation $conversation + */ + public function deleting(Conversation $conversation) + { + $conversation->threads()->delete(); + $conversation->followers()->delete(); + + \Eventy::action('conversation.deleting', $conversation); + } + + public function updated(Conversation $conversation) + { + \Eventy::action('conversation.updated', $conversation); + } +} diff --git a/freescout-dist/app/Observers/CustomerObserver.php b/freescout-dist/app/Observers/CustomerObserver.php new file mode 100644 index 0000000..8edbd1e --- /dev/null +++ b/freescout-dist/app/Observers/CustomerObserver.php @@ -0,0 +1,33 @@ +getPhones(); + // Set numeric phones. + $customer->setPhones($phones); + } + + public function deleting(Customer $customer) + { + \Eventy::action('customer.deleting', $customer); + } + + public function created(Customer $customer) + { + if ($customer->channel && $customer->channel_id) { + CustomerChannel::create($customer->id, $customer->channel, $customer->channel_id); + } + } + + public function deleted(Customer $customer) + { + CustomerChannel::where('customer_id', $customer->id)->delete(); + } +} diff --git a/freescout-dist/app/Observers/DatabaseNotificationObserver.php b/freescout-dist/app/Observers/DatabaseNotificationObserver.php new file mode 100644 index 0000000..eca3bf7 --- /dev/null +++ b/freescout-dist/app/Observers/DatabaseNotificationObserver.php @@ -0,0 +1,18 @@ +notifiable->clearWebsiteNotificationsCache(); + } +} diff --git a/freescout-dist/app/Observers/EmailObserver.php b/freescout-dist/app/Observers/EmailObserver.php new file mode 100644 index 0000000..62414fb --- /dev/null +++ b/freescout-dist/app/Observers/EmailObserver.php @@ -0,0 +1,18 @@ +createPublicFolders(); + $mailbox->syncPersonalFolders(); + $mailbox->createAdminPersonalFolders(); + } + + /** + * Delete the following on mailbox delete: + * - folders + * - conversations + * - user permissions. + * + * @param Mailbox $mailbox + * + * @return [type] [description] + */ + public function deleting(Mailbox $mailbox) + { + $mailbox->users()->delete(); + $mailbox->conversations()->delete(); + $mailbox->folders()->delete(); + + \Eventy::action('mailbox.before_delete', $mailbox); + } +} diff --git a/freescout-dist/app/Observers/SendLogObserver.php b/freescout-dist/app/Observers/SendLogObserver.php new file mode 100644 index 0000000..8cf4f69 --- /dev/null +++ b/freescout-dist/app/Observers/SendLogObserver.php @@ -0,0 +1,20 @@ +thread_id && ($send_log->customer_id || ($send_log->user_id && $send_log->user_id == $send_log->thread->user_id))) { + $send_log->thread->send_status = $send_log->status; + $send_log->thread->save(); + } + } +} diff --git a/freescout-dist/app/Observers/ThreadObserver.php b/freescout-dist/app/Observers/ThreadObserver.php new file mode 100644 index 0000000..7bc6628 --- /dev/null +++ b/freescout-dist/app/Observers/ThreadObserver.php @@ -0,0 +1,98 @@ +conversation; + + if (!$conversation) { + return; + } + + // Fetch date & time setting. + $use_mail_date_on_fetching = config('app.use_mail_date_on_fetching'); + + if ($use_mail_date_on_fetching) { + $now = $thread->created_at; + }else{ + $now = date('Y-m-d H:i:s'); + } + + if (!in_array($thread->type, [Thread::TYPE_LINEITEM, Thread::TYPE_NOTE]) && $thread->state == Thread::STATE_PUBLISHED) { + $conversation->threads_count++; + } + if (!in_array($thread->type, [Thread::TYPE_CUSTOMER])) { + $conversation->user_updated_at = $now; + } + + if ((in_array($thread->type, [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE]) + || ($conversation->isPhone() && in_array($thread->type, [Thread::TYPE_NOTE]))) + && $thread->state == Thread::STATE_PUBLISHED + ) { + // $conversation->cc = $thread->cc; + // $conversation->bcc = $thread->bcc; + $conversation->last_reply_at = $now; + $conversation->last_reply_from = $thread->source_via; + } + if ($conversation->source_via == Conversation::PERSON_CUSTOMER) { + $conversation->read_by_user = false; + } + + // Update preview. + if (in_array($thread->type, [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE, Thread::TYPE_NOTE]) + && $thread->state == Thread::STATE_PUBLISHED + && !$thread->isForward() + // Otherwise preview is not set when conversation is created + // outside of the web interface. + //&& ($conversation->threads_count > 1 || $thread->type == Thread::TYPE_NOTE) + ) { + $conversation->setPreview($thread->body); + } + + $conversation->save(); + + // $is_new_conversation = false; + // if ($conversation->threads_count == 0 + // && in_array($thread->type, [Thread::TYPE_CUSTOMER, Thread::TYPE_MESSAGE, Thread::TYPE_NOTE]) + // && $thread->state == Thread::STATE_PUBLISHED + // ) { + // $is_new_conversation = true; + // } + + // User threads are created as drafts first. + // if ($thread->state == Thread::STATE_PUBLISHED) { + // \Eventy::action('thread.created', $thread); + // } + + // Real time for user notifications is sent using events. + if ($thread->type == Thread::TYPE_CUSTOMER + || ($thread->type == Thread::TYPE_MESSAGE && $thread->state == Thread::STATE_DRAFT) + ) { + Conversation::refreshConversations($conversation, $thread); + } + + \Eventy::action('thread.created', $thread); + } + + public function deleting(Thread $thread) + { + \Eventy::action('thread.deleting', $thread); + } + + public function updated(Thread $thread) + { + \Eventy::action('thread.updated', $thread); + } +} diff --git a/freescout-dist/app/Observers/UserObserver.php b/freescout-dist/app/Observers/UserObserver.php new file mode 100644 index 0000000..5b76374 --- /dev/null +++ b/freescout-dist/app/Observers/UserObserver.php @@ -0,0 +1,46 @@ +id); + } + + public function creating(User $user) + { + // This is a hack for backward compatibility. + if ($user->type == 0 && preg_match("#^fs.*@example\.org$#", $user->email) + ) { + $user->type = User::TYPE_ROBOT; + } + } + + /** + * On user delete. + * + * @param User $user + */ + public function deleting(User $user) + { + $user->folders()->delete(); + Follower::whereIn('user_id', $user->id)->delete(); + } +} diff --git a/freescout-dist/app/Option.php b/freescout-dist/app/Option.php new file mode 100644 index 0000000..f4d8a6f --- /dev/null +++ b/freescout-dist/app/Option.php @@ -0,0 +1,305 @@ + $name], ['value' => $serialized_value] + ); + + $old_value = $option['value']; + + if ($value === $old_value || self::maybeSerialize($value) === self::maybeSerialize($old_value)) { + return false; + } + + $option->value = $serialized_value; + $option->save(); + } + + /** + * Get option. + * + * @param string $name + * + * @return string + */ + public static function get($name, $default = false, $decode = true, $use_cache = true) + { + // If not passed, get default value from config + if (func_num_args() == 1) { + $default = self::getDefault($name, $default); + } + + if ($use_cache && isset(self::$cache[$name])) { + if (self::$cache[$name] == self::CACHE_DEFAULT_VALUE) { + return $default; + } else { + return self::$cache[$name]; + } + } + + $option = self::where('name', (string) $name)->first(); + if ($option) { + if ($decode) { + $value = self::maybeUnserialize($option->value); + } else { + $value = $option->value; + } + self::$cache[$name] = $value; + } else { + $value = $default; + self::$cache[$name] = self::CACHE_DEFAULT_VALUE; + } + + return $value; + } + + public static function getDefault($option_name, $default = false) + { + $options = \Config::get('app.options'); + + if (isset($options[$option_name]) && isset($options[$option_name]['default'])) { + return $options[$option_name]['default']; + } else { + // Try to get default option value from module's config. + preg_match("/^([a-z_]+)\.(.*)/", $option_name, $m); + + if (!empty($m[1]) && !empty($m[2])) { + $module_alias = $m[1]; + $modle_option_name = $m[2]; + $module_options = \Config::get($module_alias.'.options'); + if (isset($module_options[$modle_option_name]) && isset($module_options[$modle_option_name]['default'])) { + return $module_options[$modle_option_name]['default']; + } + } + + return $default; + } + } + + public static function isDefaultSet($option_name) + { + $options = \Config::get('app.options'); + + return isset($options[$option_name]) && isset($options[$option_name]['default']); + } + + /** + * Get multiple options. + * + * @param [type] $name [description] + * @param bool $default [description] + * @param bool $decode [description] + * + * @return [type] [description] + */ + public static function getOptions($options, $defaults = [], $decode = []) + { + $values = []; + + // Check in cache first + // Return if we can get all options from cache + foreach ($options as $name) { + if (isset(self::$cache[$name])) { + if (self::$cache[$name] == self::CACHE_DEFAULT_VALUE) { + if (!isset($defaults[$name])) { + $default = self::getDefault($name); + } else { + $default = $defaults[$name]; + } + $values[$name] = $default; + } else { + $values[$name] = self::$cache[$name]; + } + } + } + if (count($values) == count($options)) { + return $values; + } else { + $values = []; + } + + $db_options = self::whereIn('name', $options)->get(); + foreach ($options as $name) { + // If not passed, get default value from config + if (!isset($defaults[$name])) { + $default = self::getDefault($name); + } else { + $default = $defaults[$name]; + } + $db_option = $db_options->where('name', $name)->first(); + if ($db_option) { + // todo: decode + if (1 || $decode) { + $value = self::maybeUnserialize($db_option->value); + } else { + $value = $db_option->value; + } + self::$cache[$name] = $value; + } else { + $value = $default; + self::$cache[$name] = self::CACHE_DEFAULT_VALUE; + } + + $values[$name] = $value; + } + + return $values; + } + + public static function remove($name) + { + self::where('name', (string) $name)->delete(); + } + + /** + * Serialize data, if needed. + */ + public static function maybeSerialize($data) + { + if (is_array($data) || is_object($data)) { + return serialize($data); + } + + return $data; + } + + /** + * Unserialize data. + */ + public static function maybeUnserialize($original) + { + if (self::isSerialized($original)) { + try { + $original = unserialize($original); + } catch (\Exception $e) { + // Do nothing + } + + return $original; + } + + return $original; + } + + /** + * Check value to find if it was serialized. + * Serialized data is always a string. + */ + public static function isSerialized($data, $strict = true) + { + // if it isn't a string, it isn't serialized. + if (!is_string($data)) { + return false; + } + $data = trim($data); + if ('N;' == $data) { + return true; + } + if (strlen($data) < 4) { + return false; + } + if (':' !== $data[1]) { + return false; + } + if ($strict) { + $lastc = substr($data, -1); + if (';' !== $lastc && '}' !== $lastc) { + return false; + } + } else { + $semicolon = strpos($data, ';'); + $brace = strpos($data, '}'); + // Either ; or } must exist. + if (false === $semicolon && false === $brace) { + return false; + } + // But neither must be in the first X characters. + if (false !== $semicolon && $semicolon < 3) { + return false; + } + if (false !== $brace && $brace < 4) { + return false; + } + } + $token = $data[0]; + switch ($token) { + case 's': + if ($strict) { + if ('"' !== substr($data, -2, 1)) { + return false; + } + } elseif (false === strpos($data, '"')) { + return false; + } + // or else fall through + case 'a': + case 'O': + return (bool) preg_match("/^{$token}:[0-9]+:/s", $data); + case 'b': + case 'i': + case 'd': + $end = $strict ? '$' : ''; + + return (bool) preg_match("/^{$token}:[0-9.E-]+;$end/", $data); + } + + return false; + } + + /** + * Get company name. + */ + public static function getCompanyName() + { + return self::get('company_name', \Config::get('app.name')); + } +} diff --git a/freescout-dist/app/Policies/ConversationPolicy.php b/freescout-dist/app/Policies/ConversationPolicy.php new file mode 100644 index 0000000..b87fd3b --- /dev/null +++ b/freescout-dist/app/Policies/ConversationPolicy.php @@ -0,0 +1,118 @@ +isAdmin()) { + return true; + } else { + if ($conversation->mailbox->users->contains($user)) { + // Maybe user can see only assigned conversations. + if (!\Eventy::filter('conversation.is_user_assignee', $conversation->user_id == $user->id, $conversation, $user->id) + && $user->hasManageMailboxPermission($conversation->mailbox_id, Mailbox::ACCESS_PERM_ASSIGNED) + ) { + return false; + } else { + return true; + } + } else { + return false; + } + } + } + + /** + * Cached version. + * + * @param User $user [description] + * @param Conversation $conversation [description] + * @return [type] [description] + */ + public function viewCached(User $user, Conversation $conversation) + { + if ($user->isAdmin()) { + return true; + } else { + if ($conversation->mailbox->users_cached->contains($user)) { + // Maybe user can see only assigned conversations. + if (!\Eventy::filter('conversation.is_user_assignee', $conversation->user_id == $user->id, $conversation, $user->id) + && $user->hasManageMailboxPermission($conversation->mailbox_id, Mailbox::ACCESS_PERM_ASSIGNED) + ) { + return false; + } else { + return true; + } + } else { + return false; + } + } + } + + /** + * Determine whether the user can update the conversation. + * + * @param \App\User $user + * @param \App\Conversation $conversation + * + * @return bool + */ + public function update(User $user, Conversation $conversation) + { + if ($user->isAdmin()) { + return true; + } else { + if ($conversation->mailbox->users->contains($user)) { + return true; + } else { + return false; + } + } + } + + /** + * Check if user can delete conversation. + */ + public function delete(User $user, Conversation $conversation) + { + if ($user->isAdmin()) { + return true; + } else { + return $user->hasPermission(User::PERM_DELETE_CONVERSATIONS); + } + } + + /** + * Determine whether current user can move conversations + * + * @param \App\User $user + * @param \App\Mailbox $mailbox + * + * @return mixed + */ + public function move(User $user) + { + // First check this, because it is cached in conversation page + if (count($user->mailboxesCanView(true)) > 1) { + return true; + } + return Mailbox::count() > 1; + } +} diff --git a/freescout-dist/app/Policies/FolderPolicy.php b/freescout-dist/app/Policies/FolderPolicy.php new file mode 100644 index 0000000..3166eaf --- /dev/null +++ b/freescout-dist/app/Policies/FolderPolicy.php @@ -0,0 +1,33 @@ +isAdmin()) { + return true; + } else { + if ($folder->user_id == $user->id || $user->mailboxesSettings()->pluck('mailbox_id')->contains($folder->mailbox_id)) { + return true; + } else { + return false; + } + } + } +} diff --git a/freescout-dist/app/Policies/MailboxPolicy.php b/freescout-dist/app/Policies/MailboxPolicy.php new file mode 100644 index 0000000..190fc3f --- /dev/null +++ b/freescout-dist/app/Policies/MailboxPolicy.php @@ -0,0 +1,184 @@ +isAdmin()) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can view mailbox conversations. + * + * @param \App\User $user + * + * @return mixed + */ + public function view(User $user, Mailbox $mailbox) + { + if ($user->isAdmin()) { + return true; + } else { + if ($mailbox->users->contains($user)) { + return true; + } else { + return false; + } + } + } + + /** + * Determine whether the user can view mailbox conversations. + * + * @param \App\User $user + * + * @return mixed + */ + public function viewCached(User $user, Mailbox $mailbox) + { + if ($user->isAdmin()) { + return true; + } else { + // Use cached users for Realtime events + if ($mailbox->users_cached->contains($user)) { + return true; + } else { + return false; + } + } + } + + /** + * Determine whether the user can admin the mailbox. + * + * @param \App\User $user + * @param \App\Mailbox $mailbox + * + * @return mixed + */ + public function admin(User $user, Mailbox $mailbox) + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can update the mailbox. + * + * @param \App\User $user + * @param \App\Mailbox $mailbox + * + * @return mixed + */ + public function update(User $user, Mailbox $mailbox) + { + if ($user->isAdmin() || $user->canManageMailbox($mailbox->id)) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can update the mailbox auto reply. + * + * @param \App\User $user + * @param \App\Mailbox $mailbox + * + * @return mixed + */ + public function updateAutoReply(User $user, Mailbox $mailbox) + { + if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, Mailbox::ACCESS_PERM_AUTO_REPLIES)) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can update the mailbox Permissions. + * + * @param \App\User $user + * @param \App\Mailbox $mailbox + * + * @return mixed + */ + public function updatePermissions(User $user, Mailbox $mailbox) + { + if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, Mailbox::ACCESS_PERM_PERMISSIONS)) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can update the mailbox Permissions. + * + * @param \App\User $user + * @param \App\Mailbox $mailbox + * + * @return mixed + */ + public function updateSettings(User $user, Mailbox $mailbox) + { + if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, Mailbox::ACCESS_PERM_EDIT)) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can update the mailbox Email Signature. + * + * @param \App\User $user + * @param \App\Mailbox $mailbox + * + * @return mixed + */ + public function updateEmailSignature(User $user, Mailbox $mailbox) + { + if ($user->isAdmin() || $user->hasManageMailboxPermission($mailbox->id, Mailbox::ACCESS_PERM_SIGNATURE)) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can delete the mailbox. + * + * @param \App\User $user + * @param \App\Mailbox $mailbox + * + * @return mixed + */ + public function delete(User $user, Mailbox $mailbox) + { + if ($user->isAdmin()) { + return true; + } else { + return false; + } + } +} diff --git a/freescout-dist/app/Policies/ThreadPolicy.php b/freescout-dist/app/Policies/ThreadPolicy.php new file mode 100644 index 0000000..c5d7953 --- /dev/null +++ b/freescout-dist/app/Policies/ThreadPolicy.php @@ -0,0 +1,42 @@ +created_by_user_id + && in_array($thread->type, [Thread::TYPE_MESSAGE, Thread::TYPE_NOTE]) + && ($user->isAdmin() || ($user->hasPermission(User::PERM_EDIT_CONVERSATIONS) && $thread->created_by_user_id == $user->id))) + || ($thread->created_by_customer_id && in_array($thread->type, [Thread::TYPE_CUSTOMER])) + ) { + return true; + } else { + return false; + } + } + + public function delete(User $user, Thread $thread) + { + if ($thread->created_by_user_id == $user->id) { + return true; + } else { + return false; + } + } +} diff --git a/freescout-dist/app/Policies/UserPolicy.php b/freescout-dist/app/Policies/UserPolicy.php new file mode 100644 index 0000000..69384ce --- /dev/null +++ b/freescout-dist/app/Policies/UserPolicy.php @@ -0,0 +1,121 @@ +isAdmin() || $user->id == $model->id) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can create models. + * + * @param \App\User $user + * + * @return mixed + */ + public function create(User $user) + { + if ($user->isAdmin() || $user->hasPermission(User::PERM_EDIT_USERS)) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can update the model. + * + * @param \App\User $user + * @param \App\User $model + * + * @return mixed + */ + public function update(User $user, User $model) + { + if ($user->isAdmin() + || $user->id == $model->id + || $user->hasPermission(User::PERM_EDIT_USERS) + || $user->canManageMailbox($model->id) + ) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can delete the model. + * + * @param \App\User $user + * @param \App\User $model + * + * @return mixed + */ + public function delete(User $user, User $model) + { + if ($user->isAdmin() /*|| $user->id == $model->id*/) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can change role of the user. + * + * @param \App\User $user + * + * @return mixed + */ + public function changeRole(User $user, User $model) + { + if ($user->isAdmin()) { + return true; + } else { + return false; + } + } + + /** + * Determine whether the user can view mailboxes menu. + * + * @param \App\User $user + * + * @return mixed + */ + public function viewMailboxMenu(User $user) + { + return true; + + // if ($user->isAdmin() || \Eventy::filter('user.can_view_mailbox_menu', false, $user)) { + // return true; + // // hasManageMailboxAccess creates an extra query on each page, + // // to avoid this we don't show Manage menu to users, + // // user can manage mailboxes from dashboard. + // } else if ($user->hasManageMailboxAccess()) { + // return true; + // } else { + // return false; + // } + } +} diff --git a/freescout-dist/app/Providers/AppServiceProvider.php b/freescout-dist/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..7b4bd65 --- /dev/null +++ b/freescout-dist/app/Providers/AppServiceProvider.php @@ -0,0 +1,96 @@ +app['url']->forceScheme('https'); + } + + // If APP_KEY is not set, redirect to /install.php + if (!\Config::get('app.key') && !app()->runningInConsole() && !file_exists(storage_path('.installed'))) { + // Not defined here yet + //\Artisan::call("freescout:clear-cache"); + redirect(\Helper::getSubdirectory().'/install.php')->send(); + } + + // Process module registration error - disable module and show error to admin + \Eventy::addFilter('modules.register_error', function ($exception, $module) { + + $msg = __('The :module_name module has been deactivated due to an error: :error_message', ['module_name' => $module->getName(), 'error_message' => $exception->getMessage()]); + + \Log::error($msg); + + // request() does is empty at this stage + if (!empty($_POST['action']) && $_POST['action'] == 'activate') { + + // During module activation in case of any error we have to deactivate module. + \App\Module::deactiveModule($module->getAlias()); + + \Session::flash('flashes_floating', [[ + 'text' => $msg, + 'type' => 'danger', + 'role' => \App\User::ROLE_ADMIN, + ]]); + + return; + } elseif (empty($_POST)) { + + // failed to open stream: No such file or directory + if (strstr($exception->getMessage(), 'No such file or directory')) { + \App\Module::deactiveModule($module->getAlias()); + + \Session::flash('flashes_floating', [[ + 'text' => $msg, + 'type' => 'danger', + 'role' => \App\User::ROLE_ADMIN, + ]]); + } + + return; + } + + return $exception; + }, 10, 2); + } +} diff --git a/freescout-dist/app/Providers/AuthServiceProvider.php b/freescout-dist/app/Providers/AuthServiceProvider.php new file mode 100644 index 0000000..8a0c6ba --- /dev/null +++ b/freescout-dist/app/Providers/AuthServiceProvider.php @@ -0,0 +1,33 @@ + 'App\Policies\UserPolicy', + 'App\Mailbox' => 'App\Policies\MailboxPolicy', + 'App\Folder' => 'App\Policies\FolderPolicy', + 'App\Conversation' => 'App\Policies\ConversationPolicy', + 'App\Thread' => 'App\Policies\ThreadPolicy', + ]; + + /** + * Register any authentication / authorization services. + * + * @return void + */ + public function boot() + { + $this->registerPolicies(); + + // + } +} diff --git a/freescout-dist/app/Providers/BroadcastServiceProvider.php b/freescout-dist/app/Providers/BroadcastServiceProvider.php new file mode 100644 index 0000000..e326d98 --- /dev/null +++ b/freescout-dist/app/Providers/BroadcastServiceProvider.php @@ -0,0 +1,27 @@ +app[\Illuminate\Broadcasting\BroadcastManager::class]->extend('polycast', function ($app, array $config) { + return new \App\Broadcasting\Broadcasters\PolycastBroadcaster(); + }); + + // This is not needed as we define routes in PolyastServiceProvider + //Broadcast::routes(); + + require base_path('routes/channels.php'); + } +} diff --git a/freescout-dist/app/Providers/EventServiceProvider.php b/freescout-dist/app/Providers/EventServiceProvider.php new file mode 100644 index 0000000..c519ba2 --- /dev/null +++ b/freescout-dist/app/Providers/EventServiceProvider.php @@ -0,0 +1,110 @@ + [ + 'App\Listeners\ProcessSwiftMessage', + ], + + 'Illuminate\Mail\Events\MessageSent' => [ + 'App\Listeners\RestartSwiftMailer', + ], + + 'Illuminate\Auth\Events\Registered' => [ + 'App\Listeners\LogRegisteredUser', + ], + + 'Illuminate\Auth\Events\Login' => [ + 'App\Listeners\RememberUserLocale', + 'App\Listeners\LogSuccessfulLogin', + 'App\Listeners\ActivateUser', + ], + + 'Illuminate\Auth\Events\Failed' => [ + 'App\Listeners\LogFailedLogin', + ], + + 'Illuminate\Auth\Events\Logout' => [ + 'App\Listeners\LogSuccessfulLogout', + ], + + 'Illuminate\Auth\Events\Lockout' => [ + 'App\Listeners\LogLockout', + ], + + 'Illuminate\Auth\Events\PasswordReset' => [ + 'App\Listeners\LogPasswordReset', + 'App\Listeners\SendPasswordChanged', + ], + + 'App\Events\UserDeleted' => [ + 'App\Listeners\LogUserDeletion', + ], + + 'App\Events\ConversationStatusChanged' => [ + 'App\Listeners\UpdateMailboxCounters', + ], + + 'App\Events\ConversationUserChanged' => [ + 'App\Listeners\UpdateMailboxCounters', + 'App\Listeners\SendNotificationToUsers', + ], + + 'App\Events\UserCreatedConversationDraft' => [ + + ], + + 'App\Events\UserCreatedThreadDraft' => [ + + ], + + 'App\Events\UserReplied' => [ + 'App\Listeners\SendReplyToCustomer', + 'App\Listeners\SendNotificationToUsers', + 'App\Listeners\RefreshConversations', + ], + + 'App\Events\CustomerReplied' => [ + 'App\Listeners\SendNotificationToUsers', + ], + + 'App\Events\UserCreatedConversation' => [ + 'App\Listeners\SendReplyToCustomer', + 'App\Listeners\SendNotificationToUsers', + 'App\Listeners\RefreshConversations', + ], + + 'App\Events\CustomerCreatedConversation' => [ + 'App\Listeners\SendAutoReply', + 'App\Listeners\SendNotificationToUsers', + ], + + 'App\Events\UserAddedNote' => [ + 'App\Listeners\SendNotificationToUsers', + 'App\Listeners\RefreshConversations', + ], + ]; + + /** + * Register any events for your application. + * + * @return void + */ + public function boot() + { + parent::boot(); + + // + } +} diff --git a/freescout-dist/app/Providers/PolycastServiceProvider.php b/freescout-dist/app/Providers/PolycastServiceProvider.php new file mode 100644 index 0000000..a270cbd --- /dev/null +++ b/freescout-dist/app/Providers/PolycastServiceProvider.php @@ -0,0 +1,197 @@ +extend('polycast', function(/*Application $app*/){ + // return new PolycastBroadcaster(); + // }); + //$factory->extend('polycast', function (Application $app, $config) { + //app('Illuminate\Broadcasting\BroadcastManager')->extend('polycast', function (array $config) { + //$broadcastManager->extend('polycast', function (array $config) { + + // This has to be done in BroadcastServiceProvider to avoid "Driver [driver] is not supported" error + // $this->app[BroadcastManager::class]->extend('polycast', function (array $config) { + // echo 'we are in extend'; + // return new \App\Misc\PolycastBroadcaster(); + // }); + + // $this->publishes([ + // __DIR__.'/../dist/js/polycast.js' => public_path('vendor/polycast/polycast.js'), + // __DIR__.'/../dist/js/polycast.min.js' => public_path('vendor/polycast/polycast.min.js'), + // ], 'public'); + + // $this->publishes([ + // __DIR__.'/../migrations/' => database_path('migrations') + // ], 'migrations'); + + $this->app['router']->group(['middleware' => ['web'], 'prefix' => \Helper::getSubdirectory()], function ($router) { + + // establish connection and send current time + $this->app['router']->post('polycast/connect', function (Request $request) { + return ['status' => 'success', 'time' => Carbon::now()->toDateTimeString()]; + }); + + // send payloads to requested browser + $this->app['router']->post('polycast/receive', function (Request $request) { + \Broadcast::auth($request); + + $query = \DB::table('polycast_events') + ->select('*'); + + $channels = $request->get('channels', []); + + foreach ($channels as $channel => $events) { + foreach ($events as $event) { + // No need to add index to DB for this query. + $query->orWhere(function ($query) use ($channel, $event, $request) { + $query->where('channels', 'like', '%"'.$channel.'"%') + ->where('event', '=', $event) + // Recors are fetched starting from opening the page or from the last event. + ->where('created_at', '>=', $request->get('time')); + }); + } + } + + $collection = collect($query->get()); + + $payload = $collection->map(function ($item, $key) use ($request) { + $created = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $item->created_at); + $requested = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $request->get('time')); + $item->channels = json_decode($item->channels, false); + $item->payload = json_decode($item->payload, false); + // Add extra data to the payload + // This works only if payload has medius and thread_id + $item->data = BroadcastNotification::fetchPayloadData($item->payload); + + $event_class = '\\'.$item->event; + if (method_exists($event_class, "processPayload")) { + // If user is not allowed to access this event, data will be sent to empty array. + $item->payload = $event_class::processPayload($item->payload); + } + + $item->delay = $requested->diffInSeconds($created); + $item->requested_at = $requested->toDateTimeString(); + + return $item; + }); + + // Reflash session data - otherwise on reply flash alert is not displayed + // https://stackoverflow.com/questions/37019294/laravel-ajax-call-deletes-session-flash-data + \Session::reflash(); + + $this->processConvView($request); + + \Eventy::action('polycast.receive', $request, $collection, $payload); + + return ['status' => 'success', 'time' => Carbon::now()->toDateTimeString(), 'payloads' => $payload]; + }); + }); + } + + /** + * Process conversation viewers. + */ + public function processConvView($request) + { + // Periodically save info indicating that user is still viewing the conversation. + $viewing_conversation_id = null; + if (!empty($request->data) && !empty($request->data['conversation_id'])) { + $viewing_conversation_id = $request->data['conversation_id']; + + if (!empty($request->data['conversation_view_focus'])) { + $conversation = Conversation::find($request->data['conversation_id']); + if ($conversation) { + \Eventy::action('conversation.view.focus', $conversation); + } + } + } + if ($viewing_conversation_id) { + + $user = auth()->user(); + $now = Carbon::now(); + + $cache_key = 'conv_view_'.$user->id.'_'.$viewing_conversation_id; + $cache_data = \Cache::get($cache_key); + $view_date = null; + $replying_changed = false; + + // t - date. + // r - replying. + if ($cache_data) { + if (isset($cache_data['t']) && isset($cache_data['r'])) { + $view_date = Carbon::createFromFormat('Y-m-d H:i:s', $cache_data['t']); + + // Let other users know that user started to reply. + if (!(int)$cache_data['r'] && (int)$request->data['replying']) { + // Started to reply. + \App\Events\RealtimeConvView::dispatchSelf($viewing_conversation_id, $user, true); + $replying_changed = true; + } elseif ((int)$cache_data['r'] && !(int)$request->data['replying']) { + // Finished to reply. + \App\Events\RealtimeConvView::dispatchSelf($viewing_conversation_id, $user, false); + $replying_changed = true; + } + } else { + $replying_changed = true; + } + } + + if (!$cache_data || $replying_changed || ($view_date && $now->diffInSeconds($view_date) > 15)) { + // Remember date of the last view in the cache. + // Store for 2 minutes. + $cache_data = [ + 't' => $now->toDateTimeString(), + 'r' => (int)$request->data['replying'] + ]; + \Cache::put($cache_key, $cache_data, 1); + + // Job could not detect when user finishes to view converrsation. + // We are using cron. + // \App\Jobs\CheckConvView::dispatch($viewing_conversation_id, $user->id) + // ->delay(now()->addSeconds(25)) + // ->onQueue(\Helper::QUEUE_DEFAULT); + + $conv_key = 'conv_view'; + $conv_data = \Cache::get($conv_key) ?? []; + $conv_data[$viewing_conversation_id][$user->id] = $cache_data; + \Cache::put($conv_key, $conv_data, 20 /*minutes*/); + + // \DB::table('polycast_events')->insert([ + // 'channels' => json_encode([['name' => 'conv.view']]), + // 'event' => 'App\Events\RealtimeConvView', + // 'payload' => json_encode([ + // 'conversation_id' => $viewing_conversation_id, + // 'reiterating' => true + // ]), + // 'created_at' => Carbon::now()->toDateTimeString(), + // ]); + } + } + } + + public function register() + { + } +} diff --git a/freescout-dist/app/Providers/RouteServiceProvider.php b/freescout-dist/app/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..d4c0f60 --- /dev/null +++ b/freescout-dist/app/Providers/RouteServiceProvider.php @@ -0,0 +1,81 @@ +mapApiRoutes(); + + $this->mapWebRoutes(); + + // + } + + /** + * Define the "web" routes for the application. + * + * These routes all receive session state, CSRF protection, etc. + * + * @return void + */ + protected function mapWebRoutes() + { + $subdirectory = \Helper::getSubdirectory(); + + if ($subdirectory) { + $route = Route::prefix($subdirectory) + ->middleware('web'); + } else { + $route = Route::middleware('web'); + } + + $route->namespace($this->namespace) + ->group(base_path('routes/web.php')); + } + + /** + * Define the "api" routes for the application. + * + * These routes are typically stateless. + * + * @return void + */ + // protected function mapApiRoutes() + // { + // Route::prefix('api') + // ->middleware('api') + // ->namespace($this->namespace) + // ->group(base_path('routes/api.php')); + // } +} diff --git a/freescout-dist/app/SendLog.php b/freescout-dist/app/SendLog.php new file mode 100644 index 0000000..7342f85 --- /dev/null +++ b/freescout-dist/app/SendLog.php @@ -0,0 +1,187 @@ +belongsTo('App\Customer'); + } + + /** + * User. + */ + public function user() + { + return $this->belongsTo('App\User'); + } + + /** + * Thread. + */ + public function thread() + { + return $this->belongsTo('App\Thread'); + } + + /** + * Save log record. + */ + public static function log($thread_id, $message_id, $email, $mail_type, $status, $customer_id = null, $user_id = null, $status_message = null, $smtp_queue_id = null) + { + $send_log = new self(); + $send_log->thread_id = $thread_id; + $send_log->message_id = $message_id; + $send_log->email = $email; + $send_log->mail_type = $mail_type; + $send_log->status = $status; + $send_log->customer_id = $customer_id; + $send_log->user_id = $user_id; + $send_log->status_message = $status_message; + if ($smtp_queue_id) { + $send_log->smtp_queue_id = $smtp_queue_id; + } + try { + $send_log->save(); + } catch (\Exception $e) { + \Helper::logException($e, 'Error occurred saving a record to `send_logs` table. '); + return false; + } + + return true; + } + + /** + * Get name of the status. + */ + public function getStatusName() + { + switch ($this->status) { + case self::STATUS_ACCEPTED: + return __('Accepted for delivery'); + case self::STATUS_SEND_ERROR: + return __('Send error'); + case self::STATUS_DELIVERY_SUCCESS: + return __('Successfully delivered'); + case self::STATUS_DELIVERY_ERROR: + return __('Delivery error'); + case self::STATUS_OPENED: + return __('Recipient opened the message'); + case self::STATUS_CLICKED: + return __('Recipient clicked a link in the message'); + case self::STATUS_UNSUBSCRIBED: + return __('Recipient unsubscribed'); + case self::STATUS_COMPLAINED: + return __('Recipient complained'); + default: + return __('Unknown'); + } + } + + public function isErrorStatus() + { + if (in_array($this->status, self::$status_errors)) { + return true; + } else { + return false; + } + } + + public function isSuccessStatus() + { + if (in_array($this->status, [self::STATUS_DELIVERY_SUCCESS])) { + return true; + } else { + return false; + } + } + + public function getMailTypeName() + { + switch ($this->mail_type) { + case self::MAIL_TYPE_EMAIL_TO_CUSTOMER: + return __('Email to customer'); + case self::MAIL_TYPE_USER_NOTIFICATION: + return __('User notification'); + case self::MAIL_TYPE_AUTO_REPLY: + return __('Auto reply to customer'); + case self::MAIL_TYPE_INVITE: + return __('User invite'); + case self::MAIL_TYPE_PASSWORD_CHANGED: + return __('Password changed notification'); + case self::MAIL_TYPE_WRONG_USER_EMAIL_MESSAGE: + return __('User replied from wrong email address'); + case self::MAIL_TYPE_TEST: + return __('Test email'); + case self::MAIL_TYPE_ALERT: + return __('Alert email'); + } + } +} diff --git a/freescout-dist/app/Sendmail.php b/freescout-dist/app/Sendmail.php new file mode 100644 index 0000000..5201c63 --- /dev/null +++ b/freescout-dist/app/Sendmail.php @@ -0,0 +1,34 @@ +belongsTo('App\Customer'); + } + + /** + * User. + */ + public function user() + { + return $this->belongsTo('App\User'); + } +} diff --git a/freescout-dist/app/Subscription.php b/freescout-dist/app/Subscription.php new file mode 100644 index 0000000..684ed1c --- /dev/null +++ b/freescout-dist/app/Subscription.php @@ -0,0 +1,471 @@ + [ + self::EVENT_CONVERSATION_ASSIGNED_TO_ME, + self::EVENT_FOLLOWED_CONVERSATION_UPDATED, + //self::EVENT_MY_TEAM_MENTIONED, + self::EVENT_CUSTOMER_REPLIED_TO_MY, + self::EVENT_USER_REPLIED_TO_MY, + ], + self::MEDIUM_BROWSER => [ + self::EVENT_CONVERSATION_ASSIGNED_TO_ME, + self::EVENT_FOLLOWED_CONVERSATION_UPDATED, + //self::EVENT_MY_TEAM_MENTIONED, + self::EVENT_CUSTOMER_REPLIED_TO_MY, + self::EVENT_USER_REPLIED_TO_MY, + ], + ]; + + /** + * List of events that occurred. + */ + public static $occurred_events = []; + + public $timestamps = false; + + /** + * The attributes that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id']; + + /** + * Subscribed user. + */ + public function user() + { + return $this->belongsTo('App\User'); + } + + /** + * Add default subscriptions for user. + * + * @param int $user_id + */ + public static function addDefaultSubscriptions($user_id) + { + self::saveFromArray(self::getDefaultSubscriptions(), $user_id); + } + + public static function getDefaultSubscriptions() + { + return Option::get('subscription_defaults', self::$default_subscriptions); + } + + /** + * Save subscriptions from passed array. + * + * @param array $subscriptions [description] + * + * @return [type] [description] + */ + public static function saveFromArray($new_subscriptions, $user_id) + { + $subscriptions = []; + + if (is_array($new_subscriptions)) { + foreach ($new_subscriptions as $medium => $events) { + foreach ($events as $event) { + $subscriptions[] = [ + 'user_id' => $user_id, + 'medium' => $medium, + 'event' => $event, + ]; + } + } + } + + self::where('user_id', $user_id)->delete(); + self::insert($subscriptions); + } + + /** + * Check if subscription exists. + */ + public static function exists(array $params, $subscriptions = null) + { + if ($subscriptions) { + // Look in the passed list + foreach ($subscriptions as $subscription) { + foreach ($params as $param_name => $param_value) { + if ($subscription->$param_name != $param_value) { + continue 2; + } + } + + return true; + } + } else { + // Search in DB + } + + return false; + } + + /** + * Detect users to notify by medium. + */ + public static function usersToNotify($event_type, $conversation, $threads, $mailbox_user_ids = null) + { + $users_to_notify = []; + $thread = null; + + if (isset($threads[0])) { + $thread = $threads[0]; + } elseif (count($threads)) { + $thread = array_shift(array_values($threads)); + } + + if (!$thread) { + return $users_to_notify; + } + + // Ignore imported threads. + if ($thread->imported) { + return true; + } + + // Detect events + $events = []; + + $prev_thread = null; + if (!empty($threads[1])) { + $prev_thread = $threads[1]; + } + + switch ($event_type) { + case self::EVENT_TYPE_NEW: + $events[] = self::EVENT_NEW_CONVERSATION; + break; + + case self::EVENT_TYPE_ASSIGNED: + $events[] = self::EVENT_CONVERSATION_ASSIGNED_TO_ME; + $events[] = self::EVENT_CONVERSATION_ASSIGNED; + break; + + case self::EVENT_TYPE_CUSTOMER_REPLIED: + $events[] = self::EVENT_FOLLOWED_CONVERSATION_UPDATED; + if (!empty($prev_thread) && $prev_thread->user_id) { + $events[] = self::EVENT_CUSTOMER_REPLIED_TO_MY; + $events[] = self::EVENT_CUSTOMER_REPLIED_TO_ASSIGNED; + } else { + $events[] = self::EVENT_CUSTOMER_REPLIED_TO_UNASSIGNED; + } + break; + + case self::EVENT_TYPE_USER_REPLIED: + case self::EVENT_TYPE_USER_ADDED_NOTE: + $events[] = self::EVENT_FOLLOWED_CONVERSATION_UPDATED; + if (!empty($prev_thread) && $prev_thread->user_id) { + $events[] = self::EVENT_USER_REPLIED_TO_MY; + $events[] = self::EVENT_USER_REPLIED_TO_ASSIGNED; + } else { + $events[] = self::EVENT_USER_REPLIED_TO_UNASSIGNED; + } + break; + } + + $events = \Eventy::filter('subscription.events_by_type', $events, $event_type, $thread); + + // Check if assigned user changed + $user_changed = false; + if ($event_type != self::EVENT_TYPE_ASSIGNED && $event_type != self::EVENT_TYPE_NEW) { + if ($thread->type == Thread::TYPE_LINEITEM && $thread->action_type == Thread::ACTION_TYPE_USER_CHANGED) { + $user_changed = true; + } elseif ($prev_thread) { + if ($prev_thread->user_id != $thread->user_id) { + $user_changed = true; + } + } else { + // Get prev thread + if ($prev_thread && $prev_thread->user_id != $thread->user_id) { + $user_changed = true; + } + } + } + if ($user_changed) { + $events[] = self::EVENT_CONVERSATION_ASSIGNED_TO_ME; + $events[] = self::EVENT_CONVERSATION_ASSIGNED; + $events[] = self::EVENT_FOLLOWED_CONVERSATION_UPDATED; + } + $events = array_unique($events); + + // Detect subscribed users + if (!$mailbox_user_ids) { + $mailbox_user_ids = $conversation->mailbox->userIdsHavingAccess(); + } + + $subscriptions = self::whereIn('user_id', $mailbox_user_ids) + ->whereIn('event', $events) + ->get(); + + $subscriptions = \Eventy::filter('subscription.subscriptions', $subscriptions, $conversation, $events, $thread); + + // Filter subscribers + foreach ($subscriptions as $i => $subscription) { + // Actions on conversation where user is assignee + if (in_array($subscription->event, [self::EVENT_CONVERSATION_ASSIGNED_TO_ME, self::EVENT_CUSTOMER_REPLIED_TO_MY, self::EVENT_USER_REPLIED_TO_MY]) + && ($conversation->user_id != $subscription->user_id && !\Eventy::filter('subscription.is_user_assignee', false, $subscription, $conversation)) + ) { + continue; + } + + // Check if user is following this conversation. + if ($subscription->event == self::EVENT_FOLLOWED_CONVERSATION_UPDATED + && !$conversation->isUserFollowing($subscription->user_id) + ) { + continue; + } + + // Skip if user muted notifications for this mailbox + //if ($subscription->user->isAdmin()) { + + // Mute notifications for events not related directly to the user. + if (!in_array($subscription->event, [self::EVENT_CONVERSATION_ASSIGNED_TO_ME, self::EVENT_FOLLOWED_CONVERSATION_UPDATED, self::EVENT_CUSTOMER_REPLIED_TO_MY, self::EVENT_USER_REPLIED_TO_MY]) + && !\Eventy::filter('subscription.is_related_to_user', false, $subscription, $thread) + ) { + $mailbox_settings = $conversation->mailbox->getUserSettings($subscription->user_id); + + if (!empty($mailbox_settings->mute)) { + continue; + } + } + //} + + if (\Eventy::filter('subscription.filter_out', false, $subscription, $thread)) { + continue; + } + + $users_to_notify[$subscription->medium][] = $subscription->user; + $users_to_notify[$subscription->medium] = array_unique($users_to_notify[$subscription->medium]); + } + + // Add menu notifications, for example. + $users_to_notify = \Eventy::filter('subscription.users_to_notify', $users_to_notify, $event_type, $events, $thread); + + return $users_to_notify; + } + + /** + * Process events which occurred. + */ + public static function processEvents() + { + $notify = []; + + $delay = now()->addSeconds(Conversation::UNDO_TIMOUT); + + // Collect into notify array information about all users who need to be notified + foreach (self::$occurred_events as $event) { + // Get mailbox users ids + $mailbox_user_ids = []; + foreach (self::$mediums as $medium) { + if (!empty($notify[$medium])) { + foreach ($notify[$medium] as $conversation_id => $notify_info) { + if ($notify_info['conversation']->mailbox_id == $event['conversation']->mailbox_id) { + $mailbox_user_ids = $notify_info['mailbox_user_ids']; + break 2; + } + } + } + } + + // Get users and threads from previous results to avoid repeated SQL queries. + $users = []; + $threads = []; + foreach (self::$mediums as $medium) { + if (empty($notify[$medium][$event['conversation']->id])) { + $threads = $event['conversation']->getThreads(); + break; + } else { + $users[$medium] = $notify[$medium][$event['conversation']->id]['users']; + $threads = $notify[$medium][$event['conversation']->id]['threads']; + } + } + + $users_to_notify = self::usersToNotify($event['event_type'], $event['conversation'], $threads, $mailbox_user_ids); + + if (!$users_to_notify || !is_array($users_to_notify)) { + continue; + } + + foreach ($users_to_notify as $medium => $medium_users_to_notify) { + + // Remove current user from recipients if action caused by current user + foreach ($medium_users_to_notify as $i => $user) { + if ($user->id == $event['caused_by_user_id']) { + unset($medium_users_to_notify[$i]); + } + } + + if (count($medium_users_to_notify)) { + $notify[$medium][$event['conversation']->id] = [ + // Users subarray contains all users who need to receive notification + // for all events for the media. + 'users' => array_unique(array_merge($users[$medium] ?? [], $medium_users_to_notify)), + 'conversation' => $event['conversation'], + 'threads' => $threads, + 'mailbox_user_ids' => $mailbox_user_ids, + ]; + } + } + } + + // - Email notification (better to create them first) + if (!empty($notify[self::MEDIUM_EMAIL])) { + foreach ($notify[self::MEDIUM_EMAIL] as $conversation_id => $notify_info) { + \App\Jobs\SendNotificationToUsers::dispatch($notify_info['users'], $notify_info['conversation'], $notify_info['threads']) + ->delay($delay) + ->onQueue('emails'); + } + } + + // - Menu notification (uses same medium as for Email, if email notifications are disabled - use Browser notificaitons) + if (!empty($notify[self::MEDIUM_EMAIL]) + || !empty($notify[self::MEDIUM_BROWSER]) + || !empty($notify[self::MEDIUM_MENU]) + ) { + if (!empty($notify[self::MEDIUM_EMAIL])) { + $notify_menu = $notify[self::MEDIUM_EMAIL] ?? []; + } else { + $notify_menu = $notify[self::MEDIUM_BROWSER] ?? []; + } + $notify_menu = $notify_menu + ($notify[self::MEDIUM_MENU] ?? []); + foreach ($notify_menu as $notify_info) { + $website_notification = new WebsiteNotification($notify_info['conversation'], self::chooseThread($notify_info['threads'])); + $website_notification->delay($delay); + \Notification::send($notify_info['users'], $website_notification); + } + } + + // Send broadcast notifications: + // - Browser push notification + $broadcasts = []; + foreach ([self::MEDIUM_EMAIL, self::MEDIUM_BROWSER] as $medium) { + if (empty($notify[$medium])) { + continue; + } + foreach ($notify[$medium] as $notify_info) { + $thread_id = self::chooseThread($notify_info['threads'])->id; + + foreach ($notify_info['users'] as $user) { + $mediums = [$medium]; + if (!empty($broadcasts[$thread_id]['mediums'])) { + $mediums = array_unique(array_merge($mediums, $broadcasts[$thread_id]['mediums'])); + } + $broadcasts[$thread_id] = [ + 'user' => $user, + 'conversation' => $notify_info['conversation'], + 'threads' => $notify_info['threads'], + 'mediums' => $mediums, + ]; + } + } + } + // \Notification::sendNow($notify_info['users'], new BroadcastNotification($notify_info['conversation'], $notify_info['threads'][0])); + foreach ($broadcasts as $thread_id => $to_broadcast) { + $broadcast_notification = new BroadcastNotification($to_broadcast['conversation'], self::chooseThread($to_broadcast['threads']), $to_broadcast['mediums']); + $broadcast_notification->delay($delay); + $to_broadcast['user']->notify($broadcast_notification); + } + + // - Mobile + \Eventy::action('subscription.process_events', $notify); + + self::$occurred_events = []; + } + + /** + * Get fist meaningful thread for the notification. + */ + public static function chooseThread($threads) + { + $actions_types = [ + Thread::ACTION_TYPE_USER_CHANGED, + ]; + // First thread is the newest. + foreach ($threads as $thread) { + if ($thread->type == Thread::TYPE_LINEITEM && !in_array($thread->action_type, $actions_types)) { + continue; + } else { + return $thread; + } + } + return $threads[0]; + } + + /** + * Remember event type to process in ProcessSubscriptionEvents middleware on terminate. + */ + public static function registerEvent($event_type, $conversation, $caused_by_user_id, $process_now = false) + { + self::$occurred_events[] = [ + 'event_type' => $event_type, + 'conversation' => $conversation, + 'caused_by_user_id' => $caused_by_user_id, + ]; + + // Automatically add EVENT_TYPE_UPDATED + if (!in_array($event_type, [self::EVENT_TYPE_UPDATED, self::EVENT_TYPE_NEW])) { + self::$occurred_events[] = [ + 'event_type' => self::EVENT_TYPE_UPDATED, + 'conversation' => $conversation, + 'caused_by_user_id' => $caused_by_user_id, + ]; + } + if ($process_now) { + self::processEvents(); + } + } +} diff --git a/freescout-dist/app/Thread.php b/freescout-dist/app/Thread.php new file mode 100644 index 0000000..2d75913 --- /dev/null +++ b/freescout-dist/app/Thread.php @@ -0,0 +1,1541 @@ + 'customer', + self::PERSON_USER => 'user', + ]; + + /** + * Thread types. + */ + // Email from customer + const TYPE_CUSTOMER = 1; + // Thead created by user + const TYPE_MESSAGE = 2; + const TYPE_NOTE = 3; + // Thread status change + const TYPE_LINEITEM = 4; + //const TYPE_PHONE = 5; + // Forwarded threads - used in API only. + //const TYPE_FORWARDPARENT = 6; + //const TYPE_FORWARDCHILD = 7; + const TYPE_CHAT = 8; + + public static $types = [ + // Thread by customer + self::TYPE_CUSTOMER => 'customer', + // Thread by user + self::TYPE_MESSAGE => 'message', + self::TYPE_NOTE => 'note', + // lineitem represents a change of state on the conversation. This could include, but not limited to, the conversation was assigned, the status changed, the conversation was moved from one mailbox to another, etc. A line item won’t have a body, to/cc/bcc lists, or attachments. + self::TYPE_LINEITEM => 'lineitem', + //self::TYPE_PHONE => 'phone', + // When a conversation is forwarded, a new conversation is created to represent the forwarded conversation. + // forwardparent is the type set on the thread of the original conversation that initiated the forward event. + //self::TYPE_FORWARDPARENT => 'forwardparent', + // forwardchild is the type set on the first thread of the new forwarded conversation. + //self::TYPE_FORWARDCHILD => 'forwardchild', + // Not used. + self::TYPE_CHAT => 'chat', + ]; + + /** + * Subtypes (for notes mostly) + */ + const SUBTYPE_FORWARD = 1; + const SUBTYPE_PHONE = 2; + + /** + * Statuses (code must be equal to conversations statuses). + */ + const STATUS_ACTIVE = 1; + const STATUS_PENDING = 2; + const STATUS_CLOSED = 3; + const STATUS_SPAM = 4; + const STATUS_NOCHANGE = 6; + + public static $statuses = [ + self::STATUS_ACTIVE => 'active', + self::STATUS_CLOSED => 'closed', + self::STATUS_NOCHANGE => 'nochange', + self::STATUS_PENDING => 'pending', + self::STATUS_SPAM => 'spam', + ]; + + /** + * States. + */ + const STATE_DRAFT = 1; + const STATE_PUBLISHED = 2; + const STATE_HIDDEN = 3; + // A state of review means the thread has been stopped by Traffic Cop and is waiting + // to be confirmed (or discarded) by the person that created the thread. + const STATE_REVIEW = 4; + + public static $states = [ + self::STATE_DRAFT => 'draft', + self::STATE_PUBLISHED => 'published', + self::STATE_HIDDEN => 'hidden', + self::STATE_REVIEW => 'review', + ]; + + /** + * Action associated with the line item. + * It is recommended to add custom action types between 100 and 1000 + */ + // Conversation's status changed + const ACTION_TYPE_STATUS_CHANGED = 1; + // Conversation's assignee changed + const ACTION_TYPE_USER_CHANGED = 2; + // The conversation was moved from another mailbox + const ACTION_TYPE_MOVED_FROM_MAILBOX = 3; + // Another conversation was merged with this conversation + const ACTION_TYPE_MERGED = 4; + // The conversation was imported (no email notifications were sent) + const ACTION_TYPE_IMPORTED = 5; + // A workflow was run on this conversation (either automatic or manual) + // const ACTION_TYPE_WORKFLOW_MANUAL = 6; + // const ACTION_TYPE_WORKFLOW_AUTO = 7; + // The ticket was imported from an external Service + const ACTION_TYPE_IMPORTED_EXTERNAL = 8; + // Conversation customer changed + const ACTION_TYPE_CUSTOMER_CHANGED = 9; + // The ticket was deleted + const ACTION_TYPE_DELETED_TICKET = 10; + // The ticket was restored + const ACTION_TYPE_RESTORE_TICKET = 11; + + // Describes an optional action associated with the line item + public static $action_types = [ + self::ACTION_TYPE_STATUS_CHANGED => 'changed-ticket-status', + self::ACTION_TYPE_USER_CHANGED => 'changed-ticket-assignee', + self::ACTION_TYPE_MOVED_FROM_MAILBOX => 'moved-from-mailbox', + self::ACTION_TYPE_MERGED => 'merged', + self::ACTION_TYPE_IMPORTED => 'imported', + // self::ACTION_TYPE_WORKFLOW_MANUAL => 'manual-workflow', + // self::ACTION_TYPE_WORKFLOW_AUTO => 'automatic-workflow', + self::ACTION_TYPE_IMPORTED_EXTERNAL => 'imported-external', + self::ACTION_TYPE_CUSTOMER_CHANGED => 'changed-ticket-customer', + self::ACTION_TYPE_DELETED_TICKET => 'deleted-ticket', + self::ACTION_TYPE_RESTORE_TICKET => 'restore-ticket', + ]; + + /** + * Source types (equal to thread source types). + */ + const SOURCE_TYPE_EMAIL = 1; + const SOURCE_TYPE_WEB = 2; + const SOURCE_TYPE_API = 3; + + public static $source_types = [ + self::SOURCE_TYPE_EMAIL => 'email', + self::SOURCE_TYPE_WEB => 'web', + self::SOURCE_TYPE_API => 'api', + ]; + + // Metas. + const META_CONVERSATION_HISTORY = 'ch'; + const META_PREV_CONVERSATION = 'pc'; + const META_MERGED_WITH_CONV = 'mwc'; + const META_MERGED_INTO_CONV = 'mic'; + const META_FORWARD_PARENT_CONVERSATION_NUMBER = 'fw_pcn'; + const META_FORWARD_PARENT_CONVERSATION_ID = 'fw_pci'; + const META_FORWARD_PARENT_THREAD_ID = 'fw_pti'; + const META_FORWARD_CHILD_CONVERSATION_NUMBER = 'fw_ccn'; + const META_FORWARD_CHILD_CONVERSATION_ID = 'fw_cci'; + + // At some stage metas have been renamed. + public static $meta_fw_backward_compat = [ + self::META_FORWARD_PARENT_CONVERSATION_NUMBER => 'forward_parent_conversation_number', + self::META_FORWARD_PARENT_CONVERSATION_ID => 'forward_parent_conversation_id', + self::META_FORWARD_PARENT_THREAD_ID => 'forward_parent_thread_id', + self::META_FORWARD_CHILD_CONVERSATION_NUMBER => 'forward_child_conversation_number', + self::META_FORWARD_CHILD_CONVERSATION_ID => 'forward_child_conversation_id', + ]; + + protected $dates = [ + 'opened_at', + 'created_at', + 'updated_at', + 'deleted_at', + 'edited_at', + ]; + + protected $casts = [ + 'meta' => 'array', + ]; + + /** + * The user assigned to this thread (assignedTo). + */ + public function user() + { + return $this->belongsTo('App\User'); + } + + /** + * The user assigned to this thread (cached). + */ + public function user_cached() + { + return $this->user()->rememberForever(); + } + + /** + * Get the thread customer. + */ + public function customer() + { + return $this->belongsTo('App\Customer'); + } + + /** + * Get the thread customer (cached). + */ + public function customer_cached() + { + return $this->customer()->rememberForever(); + } + + /** + * Get conversation. + */ + public function conversation() + { + return $this->belongsTo('App\Conversation'); + } + + /** + * Get thread attachmets. + */ + public function attachments() + { + return $this->hasMany('App\Attachment')->where('embedded', false); + //return $this->hasMany('App\Attachment'); + } + + /** + * Get thread embedded attachments. + */ + public function embeds() + { + return $this->hasMany('App\Attachment')->where('embedded', true); + } + + /** + * All kinds of attachments including embedded. + */ + public function all_attachments() + { + return $this->hasMany('App\Attachment'); + } + + /** + * Get user who created the thread. + */ + public function created_by_user() + { + return $this->belongsTo('App\User'); + } + + /** + * Get user who created the thread (cached). + */ + public function created_by_user_cached() + { + return $this->created_by_user()->rememberForever(); + } + + /** + * Get customer who created the thread. + */ + public function created_by_customer() + { + return $this->belongsTo('App\Customer'); + } + + /** + * Get user who edited thread. + */ + public function edited_by_user() + { + return $this->belongsTo('App\User'); + } + + /** + * Get user who edited thread (cached). + */ + public function edited_by_user_cached() + { + return $this->edited_by_user()->rememberForever(); + } + + /** + * Get sanitized body HTML. + * + * @return string + */ + public function getCleanBody($body = '') + { + if (!$body) { + $body = $this->body; + } + + if ($body === null) { + $body = ''; + } + + // Change "background:" to "background-color:". + // https://github.com/freescout-helpdesk/freescout/issues/2560 + // Keep in mind that with large texts preg_replace() may return null. + $body = preg_replace("/(<[^<>]+style=[\"'][^\"']*)background: *([^;() ]+[;\"'])/", '$1background-color:$2', $body) ?: $body; + + // Cut out "collapse" class as it hides elements. + $body = preg_replace("/(<[^<>\r\n]+class=([\"'][^\"']* |[\"']))(collapse|hidden)([\"' ])/", '$1$4', $body) ?: $body; + + return \Helper::purifyHtml($body); + } + + /** + * Convert body to plain text. + */ + public function getBodyAsText($options = ['width' => 0]) + { + return \Helper::htmlToText($this->body, true, $options); + } + + public function getBodyWithFormatedLinks(string $body = '') :string + { + if (!$body) { + $body = $this->body; + } + + $body = \Helper::linkify($this->getCleanBody($body)); + + // Add target="_blank" to links. + $pattern = '//i'; + + $body = preg_replace_callback($pattern, function($m){ + $tpl = array_shift($m); + $href = isset($m[1]) ? $m[1] : null; + + if (preg_match('/target=[\'"]?(.*?)[\'"]?/i', $tpl)) { + return $tpl; + } + + if (trim($href) && 0 === strpos($href, '#')) { + // Anchor links. + return $tpl; + } + + return preg_replace_callback('/href=/i', function($m2){ + return sprintf('target="_blank" %s', array_shift($m2)); + }, $tpl); + + }, $body) ?: $body; + + return $body; + } + + /** + * Get sanitized body HTML. + * + * @return string + */ + public function getCleanBodyOriginal() + { + return $this->getCleanBody($this->body_original); + } + + /** + * Get thread recipients. + * + * @return array + */ + public function getToArray($exclude_array = []) + { + return \App\Misc\Helper::jsonToArray($this->to, $exclude_array); + } + + public function getToString($exclude_array = []) + { + return implode(', ', $this->getToArray($exclude_array)); + } + + /** + * Get first address from the To list. + */ + public function getToFirst() + { + $to = $this->getToArray(); + + return array_shift($to); + } + + /** + * Get type name. + */ + public function getTypeName() + { + return self::$types[$this->type]; + } + + /** + * Get thread CC recipients. + * + * @return array + */ + public function getCcArray($exclude_array = []) + { + return \App\Misc\Helper::jsonToArray($this->cc, $exclude_array); + } + + public function getCcString($exclude_array = []) + { + return implode(', ', $this->getCcArray($exclude_array)); + } + + /** + * Get thread BCC recipients. + * + * @return array + */ + public function getBccArray($exclude_array = []) + { + return \App\Misc\Helper::jsonToArray($this->bcc, $exclude_array); + } + + public function getBccString($exclude_array = []) + { + return implode(', ', $this->getBccArray($exclude_array)); + } + + /** + * Set to as JSON. + */ + public function setTo($emails) + { + $emails_array = Conversation::sanitizeEmails($emails); + if ($emails_array) { + $emails_array = array_unique($emails_array); + $this->to = \Helper::jsonEncodeUtf8($emails_array); + } else { + $this->to = null; + } + } + + public function setCc($emails) + { + $emails_array = Conversation::sanitizeEmails($emails); + if ($emails_array) { + $emails_array = array_unique($emails_array); + $this->cc = \Helper::jsonEncodeUtf8($emails_array); + } else { + $this->cc = null; + } + } + + public function setBcc($emails) + { + $emails_array = Conversation::sanitizeEmails($emails); + if ($emails_array) { + $emails_array = array_unique($emails_array); + $this->bcc = \Helper::jsonEncodeUtf8($emails_array); + } else { + $this->bcc = null; + } + } + + /** + * Get thread's status name. + * + * @return string + */ + public function getStatusName() + { + return self::statusCodeToName($this->status); + } + + /** + * Get status name. Made as a function to allow status names translation. + * + * @param int $status + * + * @return string + */ + public static function statusCodeToName($status) + { + switch ($status) { + case self::STATUS_ACTIVE: + return __('Active'); + break; + + case self::STATUS_PENDING: + return __('Pending'); + break; + + case self::STATUS_CLOSED: + return __('Closed'); + break; + + case self::STATUS_SPAM: + return __('Spam'); + break; + + case self::STATUS_NOCHANGE: + return __('Not changed'); + break; + + default: + return ''; + break; + } + } + + /** + * Get text for the assignee in line item. + * + * @return string + */ + public function getAssigneeName($ucfirst = false, $by_user = null) + { + if (!$by_user) { + $by_user = auth()->user(); + } + if (!$this->user_id) { + if ($ucfirst) { + return __('Anyone'); + } else { + return __('anyone'); + } + } elseif ($by_user && $this->user_id == $by_user->id) { + if ($this->created_by_user_id && $this->created_by_user_id == $this->user_id) { + $name = __('yourself'); + } else { + $name = __('you'); + } + if ($ucfirst) { + $name = ucfirst($name); + } + + return $name; + } else { + // User may be deleted + if ($this->user) { + return $this->user->getFullName(); + } else { + return ''; + } + } + } + + /** + * Get user or customer who created the thead. + */ + public function getCreatedBy() + { + if (!empty($this->created_by_user_id)) { + // User can be deleted + if ($this->created_by_user) { + return $this->created_by_user; + } else { + return \App\User::getDeletedUser(); + } + } else { + return $this->created_by_customer; + } + } + + /** + * Get creator of the thread. + */ + public function getPerson($cached = false) + { + if ($this->type == self::TYPE_CUSTOMER) { + if ($cached) { + return $this->customer_cached; + } else { + return $this->customer; + } + } else { + if ($cached) { + return $this->created_by_user_cached; + } else { + return $this->created_by_user; + } + } + } + + /** + * Get action's person. + */ + public function getActionPerson($conversation_number = '') + { + $person = ''; + + if ($this->type == self::TYPE_CUSTOMER) { + if ($this->customer_cached) { + $person = $this->customer_cached->getFullName(true); + } + } elseif ($this->state == self::STATE_DRAFT && !empty($this->edited_by_user_id)) { + // Draft + if (auth()->user() && $this->edited_by_user_id == auth()->user()->id) { + $person = __('you'); + } else { + $person = $this->edited_by_user->getFullName(); + } + } elseif ($this->created_by_user_cached) { + if ($this->created_by_user_id && auth()->user() && $this->created_by_user_cached->id == auth()->user()->id) { + $person = __('you'); + } else { + $person = $this->created_by_user_cached->getFullName(); + } + } + + // https://github.com/tormjens/eventy/issues/19 + $person = \Eventy::filter('thread.action_person', $person, $this, $conversation_number); + + return $person; + } + + /** + * Get action text. + * $by_user - user who performed the action. + * $person must be already escaped. + */ + public function getActionText($conversation_number = '', $escape = false, $strip_tags = false, $by_user = null, $person = '', $viewed_by_user = null) + { + $did_this = ''; + + // Did this + if ($this->type == self::TYPE_LINEITEM) { + + if ($this->action_type == self::ACTION_TYPE_STATUS_CHANGED) { + if ($conversation_number) { + $did_this = __(':person marked as :status_name conversation #:conversation_number', ['status_name' => $this->getStatusName(), 'conversation_number' => $conversation_number]); + } else { + $did_this = __(":person marked as :status_name", ['status_name' => $this->getStatusName()]); + } + } elseif ($this->action_type == self::ACTION_TYPE_USER_CHANGED) { + $assignee = $this->getAssigneeName(false, $by_user); + if ($escape) { + $assignee = htmlspecialchars($assignee); + } + if ($conversation_number) { + $did_this = __(':person assigned :assignee conversation #:conversation_number', ['assignee' => $assignee, 'conversation_number' => $conversation_number]); + } else { + $did_this = __(":person assigned to :assignee", ['assignee' => $assignee]); + } + } elseif ($this->action_type == self::ACTION_TYPE_CUSTOMER_CHANGED) { + if ($conversation_number) { + $did_this = __(':person changed the customer to :customer in conversation #:conversation_number', ['customer' => $this->customer->getFullName(true), 'conversation_number' => $conversation_number]); + } else { + $customer_name = ''; + if ($this->customer_cached) { + $customer_name = $this->customer_cached->getFullName(true); + } + if ($escape) { + $customer_name = htmlspecialchars($customer_name); + } + $did_this = __(":person changed the customer to :customer", ['customer' => ''.$customer_name.'']); + } + } elseif ($this->action_type == self::ACTION_TYPE_DELETED_TICKET) { + $did_this = __(":person deleted"); + } elseif ($this->action_type == self::ACTION_TYPE_RESTORE_TICKET) { + $did_this = __(":person restored"); + } elseif ($this->action_type == self::ACTION_TYPE_MOVED_FROM_MAILBOX) { + $did_this = __(":person moved conversation from another mailbox"); + } elseif ($this->action_type == self::ACTION_TYPE_MERGED) { + if (!empty($this->getMeta(Thread::META_MERGED_WITH_CONV))) { + $did_this = __(":person merged with another conversation"); + } else { + $merge_conversation = Conversation::find($this->getMeta(Thread::META_MERGED_INTO_CONV)); + $merge_conversation_number = ''; + if ($merge_conversation) { + $merge_conversation_number = $merge_conversation->number; + } + if ($merge_conversation) { + $did_this = __(":person merged into conversation #:conversation_number", ['conversation_number' => ''.$merge_conversation_number.'']); + } else { + $did_this = __(":person merged into conversation #:conversation_number", ['conversation_number' => $merge_conversation_number]); + } + } + } + } elseif ($this->state == self::STATE_DRAFT) { + if (empty($this->edited_by_user_id)) { + $did_this = __(':person created a draft'); + } else { + $did_this = __(":person edited :creator's draft", ['creator' => $this->created_by_user_cached->getFirstName()]); + } + } else { + if ($this->isForwarded()) { + $did_this = __(':person forwarded a conversation #:forward_parent_conversation_number', ['forward_parent_conversation_number' => $this->getMetaFw(self::META_FORWARD_PARENT_CONVERSATION_NUMBER)]); + } elseif ($this->first) { + $did_this = __(':person started a new conversation #:conversation_number', ['conversation_number' => $conversation_number]); + } elseif ($this->type == self::TYPE_NOTE) { + $did_this = __(':person added a note to conversation #:conversation_number', ['conversation_number' => $conversation_number]); + } else { + $did_this = __(':person replied to conversation #:conversation_number', ['conversation_number' => $conversation_number]); + } + } + + $did_this = \Eventy::filter('thread.action_text', $did_this, $this, $conversation_number, $escape, $viewed_by_user); + + if ($strip_tags) { + $did_this = strip_tags($did_this); + } + + if ($person) { + // This causes double escaping. + // if ($escape) { + // $person = htmlspecialchars($person); + // } + $did_this = str_replace(':person', $person, $did_this); + } + + return $did_this; + } + + /** + * Description of what happened. + */ + public function getActionDescription($conversation_number, $escape = true, $viewed_by_user = null) + { + // Person + $person = $this->getActionPerson($conversation_number); + $did_this = $this->getActionText($conversation_number, false, false, null, '', $viewed_by_user); + + if ($escape) { + $person = htmlspecialchars($person); + $did_this = htmlspecialchars($did_this); + } + + return __($did_this, [ + 'person' => ''.$person.'', + 'did_this' => $did_this, + ]); + } + + /** + * Get thread state name. + */ + public function getStateName() + { + return self::$states[$this->state]; + } + + public function deleteThread() + { + $this->deteleAttachments(); + $this->delete(); + + if ($this->isNote()) { + Conversation::updatePreview($this->conversation_id); + } + } + + /** + * Delete thread attachments. + */ + public function deteleAttachments() + { + Attachment::deleteByIds($this->all_attachments()->pluck('id')->toArray()); + } + + public function isDraft() + { + return $this->state == self::STATE_DRAFT; + } + + /** + * Get original body or body. + */ + public function getBodyOriginal() + { + if (!empty($this->body_original)) { + return $this->body_original; + } else { + return $this->body; + } + } + + /** + * Get name for the reply to customer. + * + * @param [type] $mailbox [description] + * + * @return [type] [description] + */ + public function getFromName($mailbox = null) + { + // Created by customer + if ($this->source_via == self::PERSON_CUSTOMER) { + if ($this->getCreatedBy()) { + return $this->getCreatedBy()->getFirstName(true); + } else { + return ''; + } + } + + // Created by user + if (empty($mailbox)) { + $mailbox = $this->conversation->mailbox; + } + // Mailbox name by default + $name = $mailbox->name; + + if ($mailbox->from_name == Mailbox::FROM_NAME_CUSTOM && $mailbox->from_name_custom) { + $name = $mailbox->from_name_custom; + } elseif ($mailbox->from_name == Mailbox::FROM_NAME_USER && $this->getCreatedBy()) { + $name = $this->getCreatedBy()->getFirstName(true); + } + + return $name; + } + + /** + * Check if thread is a reply from customer or user. + * + * @return bool [description] + */ + public function isReply() + { + return in_array($this->type, [\App\Thread::TYPE_MESSAGE, \App\Thread::TYPE_CUSTOMER]); + } + + /** + * Is this thread created from auto responder email. + * + * @return bool [description] + */ + public function isAutoResponder() + { + return \MailHelper::isAutoResponder($this->headers); + } + + /** + * Is thread created from incoming bounce email. + * + * @return bool [description] + */ + public function isBounce() + { + if (!empty($this->getSendStatusData()['is_bounce'])) { + return true; + } else { + return false; + } + } + + /** + * Send status data mayb contain the following information: + * - bounce info (status_code, action, diagnostic_code, is_bounce, bounce_for_thread, bounce_for_conversation, bounced_by_thread, bounced_by_conversation) + * - send error message + * - click date + * - unsubscribe date + * - complain date. + * + * @return [type] [description] + */ + public function getSendStatusData() + { + return \Helper::jsonToArray($this->send_status_data); + } + + public function updateSendStatusData($new_data) + { + if ($new_data) { + $send_status_data = $this->getSendStatusData(); + if ($send_status_data) { + $send_status_data = array_merge($send_status_data, $new_data); + } else { + $send_status_data = $new_data; + } + $this->send_status_data = \Helper::jsonEncodeUtf8($send_status_data); + } else { + $this->send_status_data = null; + } + } + + public function isSendStatusError() + { + return in_array($this->send_status, \App\SendLog::$status_errors); + } + + /** + * Create thread. + * + * @param [type] $conversation_id [description] + * @param [type] $text [description] + * @param array $data [description] + * @return [type] [description] + */ + public static function create($conversation, $type, $body, $data = [], $save = true) + { + $thread = new Thread(); + $thread->conversation_id = $conversation->id; + $thread->type = $type; + $thread->body = $body; + $thread->status = $conversation->status; + $thread->state = Thread::STATE_PUBLISHED; + + // Assigned to. + if (!empty($data['user_id'])) { + $thread->user_id = $data['user_id']; + } + if (!empty($data['message_id'])) { + $thread->message_id = $data['message_id']; + } + if (!empty($data['headers'])) { + $thread->headers = $data['headers']; + } + if (!empty($data['from'])) { + $thread->from = $data['from']; + } + if (!empty($data['to'])) { + $thread->setTo($data['to']); + } + if (!empty($data['cc'])) { + $thread->setCc($data['cc']); + } + if (!empty($data['bcc'])) { + $thread->setBcc($data['bcc']); + } + if (isset($data['first'])) { + $thread->from = $data['first']; + } + if (isset($data['source_via'])) { + $thread->source_via = $data['source_via']; + } + if (isset($data['source_type'])) { + $thread->source_type = $data['source_type']; + } + if (!empty($data['customer_id'])) { + $thread->customer_id = $data['customer_id']; + } + if (!empty($data['created_by_customer_id'])) { + $thread->created_by_customer_id = $data['created_by_customer_id']; + } + if (!empty($data['created_by_user_id'])) { + $thread->created_by_user_id = $data['created_by_user_id']; + } + if (!empty($data['action_type'])) { + $thread->action_type = $data['action_type']; + } + if (!empty($data['meta'])) { + $thread->setMetas($data['meta']); + } + + if ($save) { + $thread->save(); + } + + return $thread; + } + + public static function createExtended($data = [], $conversation = null, $customer = null, $update_conv = true) + { + if (empty($data['type']) || empty($data['body'])) { + return false; + } + + $is_customer = ($data['type'] == Thread::TYPE_CUSTOMER); + + if (!$customer && !empty($data['customer_id'])) { + $customer = Customer::find($data['customer_id']); + } + if (!$customer) { + $customer = $conversation->customer; + } + + // User which creatd the thread should be passed in created_by_user_id. + // user_id is check for backward compatibility. + $user_id = $data['created_by_user_id'] ?? $data['user_id'] ?? null; + + // Check type. + if ($data['type'] == Thread::TYPE_CUSTOMER && empty($customer)) { + return false; + //return $this->getErrorResponse('`customer` parameter is required', 'customer'); + } + if (($data['type'] == Thread::TYPE_MESSAGE || $data['type'] == Thread::TYPE_NOTE) && empty($user_id)) { + return false; + //return $this->getErrorResponse('`user` parameter is required', 'user'); + } + + // Create thread. + $now = date('Y-m-d H:i:s'); + + // New conversation. + $new = !$conversation->threads_count; + + $thread = new Thread(); + $thread->conversation_id = $conversation->id; + $thread->type = $data['type']; + if ($is_customer) { + $thread->source_via = Thread::PERSON_CUSTOMER; + $thread->created_by_customer_id = $customer->id; + } else { + $thread->source_via = Thread::PERSON_USER; + $thread->created_by_user_id = $user_id; + $thread->edited_by_user_id = null; + $thread->edited_at = null; + } + $thread->source_type = Thread::SOURCE_TYPE_API; + $thread->state = $data['state'] ?? Thread::STATE_PUBLISHED; + $thread->customer_id = $customer->id ?? $conversation->customer_id ?? null; + $thread->body = $data['body']; + if (!$is_customer) { + $thread->setTo([$customer->getMainEmail()]); + } + + $cc = \MailHelper::sanitizeEmails($data['cc'] ?? []); + $thread->setCc($cc); + + $bcc = \MailHelper::sanitizeEmails($data['bcc'] ?? []); + $thread->setBcc($bcc); + $thread->imported = (int)($data['imported'] ?? false); + if ($thread->imported && !empty($data['created_at'])) { + $thread->created_at = self::utcStringToServerDate($data['created_at']); + } + if ($new) { + $thread->first = true; + } + + // Assignee. + if (empty($data['user_id'])) { + $thread->user_id = $conversation->user_id; + } + + // Process attachments. + if (!empty($data['attachments'])) { + $has_attachments = false; + foreach ($data['attachments'] as $attachment) { + + $content = null; + $uploaded_file = null; + + if (is_object($attachment) && get_class($attachment) == 'Illuminate\Http\UploadedFile') { + + $uploaded_file = $attachment; + $attachment = []; + $attachment['file_name'] = $uploaded_file->getClientOriginalName(); + $attachment['mime_type'] = $uploaded_file->getMimeType(); + + } else { + + if (empty($attachment['file_name']) + //|| empty($attachment['mime_type']) + || (empty($attachment['data']) && empty($attachment['file_url'])) + ) { + continue; + } + + if (!empty($attachment['data'])) { + // BASE64 string. + $content = base64_decode($attachment['data']); + if (!$content) { + continue; + } + if (empty($attachment['mime_type'])) { + $f = finfo_open(); + $attachment['mime_type'] = finfo_buffer($f, $content, FILEINFO_MIME_TYPE); + } + } else { + // URL. + $file_path = \Helper::downloadRemoteFileAsTmp($attachment['file_url']); + if (!$file_path) { + continue; + } + $uploaded_file = new \Illuminate\Http\UploadedFile( + $file_path, basename($file_path), + null, null, true + ); + if (empty($attachment['mime_type'])) { + $attachment['mime_type'] = mime_content_type($file_path); + if (empty($attachment['mime_type'])) { + $attachment['mime_type'] = $uploaded_file->getMimeType(); + } + } + } + } + if (!$has_attachments) { + $thread->save(); + } + $attachment = Attachment::create( + $attachment['file_name'], + $attachment['mime_type'], + null, + $content, + $uploaded_file, + $embedded = false, + $thread->id, + $user_id ?? null + ); + + if ($attachment) { + $has_attachments = true; + } + } + if ($has_attachments) { + $thread->has_attachments = true; + $conversation->has_attachments = true; + } + } else { + $has_attachments = $data['has_attachments'] ?? false; + $thread->has_attachments = $has_attachments; + $conversation->has_attachments = $has_attachments; + } + + $thread->save(); + + if ($new) { + if ($is_customer) { + $conversation->source_via = Conversation::PERSON_CUSTOMER; + $conversation->created_by_customer_id = $customer->id; + } else { + $conversation->source_via = Conversation::PERSON_USER; + $conversation->created_by_user_id = $user_id; + } + } + + $conversation->setCc($cc); + // BCC should keep BCC of the first email, + // so we change BCC only if it contains emails. + if ($bcc) { + $conversation->setBcc($bcc); + } + + $update_folder = false; + + if ($thread->isReply()) { + $conversation->last_reply_at = $now; + if ($is_customer) { + $conversation->last_reply_from = Conversation::PERSON_CUSTOMER; + + // Set specific status + if (!empty($data['status'])) { + if ((int)$conversation->status != (int)$data['status']) { + $update_folder = true; + } + $conversation->status = $data['status']; + } else { + if ((int)$conversation->status != Conversation::STATUS_ACTIVE) { + $update_folder = true; + } + // Reply from customer makes conversation active + $conversation->status = Conversation::STATUS_ACTIVE; + } + } else { + $conversation->last_reply_from = Conversation::PERSON_USER; + $conversation->user_updated_at = $now; + + if (!empty($data['status'])) { + if ((int)$conversation->status != (int)$data['status']) { + $update_folder = true; + } + $conversation->status = $data['status']; + } else { + if ((int)$conversation->status != Conversation::STATUS_PENDING) { + $update_folder = true; + } + // Reply from customer makes conversation active + $conversation->status = Conversation::STATUS_PENDING; + } + } + } + + // Reply from customer to deleted conversation should undelete it. + if ($data['type'] == Thread::TYPE_CUSTOMER && $conversation->state == Conversation::STATE_DELETED) { + $conversation->state = Conversation::STATE_PUBLISHED; + $update_folder = true; + } + + if ($update_conv) { + $conversation->customer_id = $customer->id; + + if ($is_customer) { + $conversation->customer_email = $customer->getMainEmail(); + } + + if ($update_folder) { + $conversation->updateFolder(); + } + } + + // Update conversation here if needed. + if ($is_customer) { + if ($new) { + $conversation = \Eventy::filter('conversation.created_by_customer', $conversation, $thread, $customer); + } else { + $conversation = \Eventy::filter('conversation.customer_replied', $conversation, $thread, $customer); + } + } + // save() will check if something in the model has changed. If it hasn't it won't run a db query. + $conversation->save(); + + // Update folders counters + if (!$new) { + // Update folders counters + $conversation->mailbox->updateFoldersCounters(); + } + + // Events. + + // Conversation customer changed + // Not used anywhere + // if ($prev_customer_id) { + // event(new ConversationCustomerChanged($conversation, $prev_customer_id, $prev_customer_email, null, $customer)); + // } + + if ($new) { + if ($is_customer) { + event(new CustomerCreatedConversation($conversation, $thread)); + \Eventy::action('conversation.created_by_customer', $conversation, $thread, $customer); + } else { + // New conversation. + event(new UserCreatedConversation($conversation, $thread)); + \Eventy::action('conversation.created_by_user_can_undo', $conversation, $thread); + // After Conversation::UNDO_TIMOUT period trigger final event. + \Helper::backgroundAction('conversation.created_by_user', [$conversation, $thread], now()->addSeconds(Conversation::UNDO_TIMOUT)); + } + } elseif ($data['type'] == Thread::TYPE_NOTE) { + // Note. + event(new UserAddedNote($conversation, $thread)); + \Eventy::action('conversation.note_added', $conversation, $thread); + } else { + // Reply. + if ($is_customer) { + event(new CustomerReplied($conversation, $thread)); + \Eventy::action('conversation.customer_replied', $conversation, $thread, $customer); + } else { + event(new UserReplied($conversation, $thread)); + \Eventy::action('conversation.user_replied_can_undo', $conversation, $thread); + // After Conversation::UNDO_TIMOUT period trigger final event. + \Helper::backgroundAction('conversation.user_replied', [$conversation, $thread], now()->addSeconds(Conversation::UNDO_TIMOUT)); + } + } + + return $thread; + } + + /** + * Get full name of the user who edited thread. + */ + public function getEditedByUserName() + { + $name = ''; + + if (!$this->edited_by_user_id) { + return ''; + } + + if (auth()->user() && $this->edited_by_user_id == auth()->user()->id) { + $name = __('you'); + } else { + $name = $this->edited_by_user_cached->getFullName(); + } + + return $name; + } + + /** + * Get thread meta data as array. + */ + public function getMetas() + { + return $this->meta; + //return \Helper::jsonToArray($this->meta); + } + + /** + * Set thread meta value. + */ + public function setMetas($data) + { + $this->meta = $data; + //$this->meta = \Helper::jsonEncodeUtf8($data); + } + + /** + * Get thread meta value. + */ + public function getMeta($key, $default = null) + { + $metas = $this->getMetas(); + if (isset($metas[$key])) { + return $metas[$key]; + } else { + return $default; + } + } + + /** + * Set thread meta value. + */ + public function setMeta($key, $value) + { + $metas = $this->getMetas(); + $metas[$key] = $value; + $this->setMetas($metas); + } + + public function getMetaFw($key, $default = null) + { + $meta = $this->getMeta($key, $default); + if (!$meta) { + $meta = $this->getMeta(self::$meta_fw_backward_compat[$key], $default); + } + return $meta; + } + + /** + * Unset thread meta value. + */ + public function unsetMeta($key) + { + $metas = $this->getMetas(); + if (isset($metas[$key])) { + unset($metas[$key]); + $this->setMetas($metas); + } + } + + /** + * Get full name of the user who forwarded conversation. + */ + public function getForwardByFullName($by_user = null) + { + if (!$by_user) { + $by_user = auth()->user(); + } + if ($by_user && $this->created_by_user_id == $by_user->id) { + $name = __('you'); + } else { + $name = $this->created_by_user->getFullName(); + } + + return $name; + } + + /** + * Is this a note informing that conversation has been forwarded. + */ + public function isForward() + { + return ($this->subtype == \App\Thread::SUBTYPE_FORWARD); + } + + /** + * Is this a forwarded conversation. + */ + public function isForwarded() + { + if ($this->getMetaFw(self::META_FORWARD_PARENT_CONVERSATION_ID)) { + return true; + } else { + return false; + } + } + + /** + * Is this thread a note. + */ + public function isNote() + { + return ($this->type == \App\Thread::TYPE_NOTE); + } + + /** + * Get forwarded conversation. + */ + public function getForwardParentConversation() + { + return Conversation::where('id', $this->getMetaFw(self::META_FORWARD_PARENT_CONVERSATION_ID)) + ->rememberForever() + ->first(); + } + + /** + * Get forward child conversation. + */ + public function getForwardChildConversation() + { + return Conversation::where('id', $this->getMetaFw(self::META_FORWARD_CHILD_CONVERSATION_ID)) + ->first(); + } + + /** + * Fetch body via IMAP. + */ + public function fetchBody() + { + $message = \MailHelper::fetchMessage($this->conversation->mailbox, $this->message_id, $this->getMailDate()); + + if (!$message) { + return ''; + } + + $body = $message->getHTMLBody(); + + if (!$body) { + $body = $message->getTextBody(); + } + + return $body; + } + + public function parseHeaders() + { + return \MailHelper::parseHeaders($this->headers); + } + + public function getMailDate() + { + $data = $this->parseHeaders(); + + if (empty($data->date)) { + return null; + } + + return \Helper::parseDateToCarbon($data->date); + } + + public function getActionTypeName() + { + if (!$this->action_type) { + return ''; + } + + $action_types = \Eventy::filter('thread.action_types', self::$action_types); + + return self::$action_types[$this->action_type] ?? ''; + } + + public function isCustomerMessage() + { + return $this->type == self::TYPE_CUSTOMER; + } + + public function isUserMessage() + { + return $this->type == self::TYPE_MESSAGE; + } + + public static function replaceBase64ImagesWithAttachments($body, $user_id = null) + { + \Helper::setPcreBacktrackLimit(); + + $body = preg_replace_callback("#(]+src=[\"'])data:image/([^;]+);base64,([^\"']+)([\"'])#", + function ($match) { + $attachment = null; + $data = base64_decode($match[3]); + + if ($data) { + $attachment = Attachment::create( + $file_name = number_format(microtime(true), 4, '', '').'.'.$match[2], + $mime_type = 'image/'.$match[2], + $type = Attachment::TYPE_IMAGE, + $data, + $uploaded_file = null, + $embedded = true, + $thread_id = null, + $user_id = \Auth::id() + ); + } + if ($attachment) { + return $match[1].$attachment->url().$match[4]; + } else { + return $match[0]; + } + }, $body + ); + + return $body; + } + + public function getMessageId($mailbox = null) + { + if ($this->isCustomerMessage() && $this->message_id) { + return $this->message_id; + } + if ($this->isUserMessage()) { + if (!$mailbox) { + $mailbox = $this->conversation->mailbox; + } + return \MailHelper::MESSAGE_ID_PREFIX_REPLY_TO_CUSTOMER.'-'.$this->id.'-'.\MailHelper::getMessageIdHash($this->id).'@'.$mailbox->getEmailDomain(); + } + + return ''; + } + + // Sorts threads in desc order by created_at and ID. + // + // Threads has to be sorted by created_at and not by id. + // https://github.com/freescout-helpdesk/freescout/issues/2938 + // Sometimes thread.created_at may be the same, + // in such cases we also need to sort by thread ID. + public static function sortThreads($threads) + { + return $threads->sort(function ($a, $b) { + $a_ts = $a->created_at->getTimestamp(); + $b_ts = $b->created_at->getTimestamp(); + if ($a_ts == $b_ts) { + if ($a->id < $b->id) { + return 1; + } else { + return -1; + } + } else { + return ($a_ts < $b_ts) ? 1 : -1; + } + }); + } + + public static function getLastThread($threads) + { + $threads = self::sortThreads($threads); + return $threads->first(); + } + + public function canRetrySend() + { + if (!in_array($this->send_status, [SendLog::STATUS_SEND_ERROR, SendLog::STATUS_DELIVERY_ERROR])) { + return false; + } + // Check if failed_job still exists. + if (!$this->getFailedJobId()) { + return false; + } + + return true; + } + + public function getFailedJobId() + { + return \App\FailedJob::where('queue', 'emails') + ->where('payload', 'like', '{"displayName":"App\\\\\\\\Jobs\\\\\\\\SendReplyToCustomer"%{i:0;i:'.$this->id.';%') + ->value('id'); + } +} diff --git a/freescout-dist/app/User.php b/freescout-dist/app/User.php new file mode 100644 index 0000000..67940a8 --- /dev/null +++ b/freescout-dist/app/User.php @@ -0,0 +1,1229 @@ + 'admin', + self::ROLE_USER => 'user', + ]; + + /** + * Types. + */ + const TYPE_USER = 1; + const TYPE_ROBOT = 2; // Workflows, teams, etc. + + /** + * Statuses. + */ + const STATUS_ACTIVE = 1; + const STATUS_DISABLED = 2; + const STATUS_DELETED = 3; + + /** + * Invite states. + */ + const INVITE_STATE_ACTIVATED = 1; + const INVITE_STATE_SENT = 2; + const INVITE_STATE_NOT_INVITED = 3; + + /** + * Time formats. + */ + const TIME_FORMAT_12 = 1; + const TIME_FORMAT_24 = 2; + + /** + * Global user permissions. + */ + const PERM_DELETE_CONVERSATIONS = 1; + const PERM_EDIT_CONVERSATIONS = 2; + const PERM_EDIT_SAVED_REPLIES = 3; + const PERM_EDIT_TAGS = 4; + const PERM_EDIT_CUSTOM_FOLDERS = 5; + const PERM_EDIT_USERS = 10; + + public static $user_permissions = [ + self::PERM_DELETE_CONVERSATIONS, + self::PERM_EDIT_CONVERSATIONS, + self::PERM_EDIT_SAVED_REPLIES, + self::PERM_EDIT_TAGS, + self::PERM_EDIT_CUSTOM_FOLDERS, + self::PERM_EDIT_USERS, + ]; + + const WEBSITE_NOTIFICATIONS_PAGE_SIZE = 25; + const WEBSITE_NOTIFICATIONS_PAGE_PARAM = 'wp_page'; + + /** + * The attributes that are not mass assignable. + * + * @var array + */ + protected $guarded = ['role']; + + /** + * The attributes that should be hidden for arrays, excluded from the model's JSON form. + * + * @var array + */ + protected $hidden = [ + 'password', 'remember_token', + ]; + + /** + * Attributes fillable using fill() method. + * + * @var [type] + */ + protected $fillable = ['role', 'status', 'first_name', 'last_name', 'email', 'password', 'timezone', 'photo_url', 'type', 'emails', 'job_title', 'phone', 'time_format', 'enable_kb_shortcuts', 'locale']; + + protected $casts = [ + 'permissions' => 'array', + ]; + + public function __construct(array $attributes = array()) + { + $this->setRawAttributes(array_merge($this->attributes, array( + 'timezone' => config('app.timezone') ?: User::DEFAULT_TIMEZONE + )), true); + parent::__construct($attributes); + } + + /** + * For array_unique function. + * + * @return string + */ + public function __toString() + { + return $this->id.''; + } + + /** + * Get mailboxes to which usre has access. + */ + public function mailboxes() + { + return $this->belongsToMany('App\Mailbox'); + } + + /** + * Cached mailboxes. + */ + public function mailboxes_cached() + { + return $this->mailboxes()->rememberForever(); + } + + public function mailboxesWithSettings() + { + return $this->belongsToMany('App\Mailbox')->as('settings') + ->withPivot('after_send') + ->withPivot('hide') + ->withPivot('mute') + ->withPivot('access'); + } + + /** + * Get conversations assigned to user. + */ + public function conversations() + { + return $this->hasMany('App\Conversation'); + } + + /** + * User's folders. + */ + public function folders() + { + return $this->hasMany('App\Folder'); + } + + /** + * User's subscriptions. + */ + public function subscriptions() + { + return $this->hasMany('App\Subscription'); + } + + /** + * Get user role. + * + * @return string + */ + public function getRoleName($ucfirst = false) + { + $role_name = self::$roles[$this->role]; + if ($ucfirst) { + $role_name = ucfirst($role_name); + } + + return $role_name; + } + + /** + * Check if user is admin. + * + * @return bool + */ + public function isAdmin() + { + return $this->role == self::ROLE_ADMIN; + } + + /** + * Get user full name. + * + * @return string + */ + public function getFullName() + { + return \Eventy::filter('user.full_name', $this->first_name.' '.$this->last_name, $this); + } + + /** + * Get user first name. + * + * @return string + */ + public function getFirstName() + { + return $this->first_name; + } + + /** + * Get mailboxes to which user has access. + */ + public function mailboxesCanView($cache = false) + { + if ($this->isAdmin()) { + if ($cache) { + $mailboxes = Mailbox::rememberForever()->get(); + } else { + $mailboxes = Mailbox::all(); + } + } else { + if ($cache) { + $mailboxes = $this->mailboxes_cached; + } else { + $mailboxes = $this->mailboxes; + } + } + + return $mailboxes->sortBy('name'); + } + + /** + * Get mailboxes to which user has access. + */ + public function mailboxesCanViewWithSettings($cache = false) + { + $user = $this; + + if ($this->isAdmin()) { + $query = Mailbox::select(['mailboxes.*', 'mailbox_user.hide', 'mailbox_user.mute', 'mailbox_user.access']) + ->leftJoin('mailbox_user', function ($join) use ($user) { + $join->on('mailbox_user.mailbox_id', '=', 'mailboxes.id'); + $join->where('mailbox_user.user_id', $user->id); + }); + } else { + $query = Mailbox::select(['mailboxes.*', 'mailbox_user.hide', 'mailbox_user.mute', 'mailbox_user.access']) + ->join('mailbox_user', function ($join) use ($user) { + $join->on('mailbox_user.mailbox_id', '=', 'mailboxes.id'); + $join->where('mailbox_user.user_id', $user->id); + }); + } + if ($cache) { + return $query->rememberForever()->get(); + } else { + return $query->get(); + } + } + + public function mailboxesSettings($cache = true) + { + $user = $this; + + $query = MailboxUser::where('user_id', $user->id); + + if ($cache) { + return $query->rememberForever()->get(); + } else { + return $query->get(); + } + } + + public function mailboxSettings($mailbox_id) + { + $settings = $this->mailboxesSettings()->where('mailbox_id', $mailbox_id)->first(); + + if (!$settings) { + return Mailbox::getDummySettings(); + } + + return $settings; + } + /** + * Get IDs of mailboxes to which user has access. + */ + public function mailboxesIdsCanView() + { + if ($this->isAdmin()) { + return Mailbox::pluck('id')->toArray(); + } else { + return $this->mailboxes()->pluck('mailboxes.id')->toArray(); + } + } + + public function hasAccessToMailbox($mailbox_id) + { + $ids = $this->mailboxesIdsCanView(); + return in_array($mailbox_id, $ids); + } + + /** + * Check to see if the user can manage any mailboxes + */ + public function hasManageMailboxAccess() { + if ($this->isAdmin()) { + return true; + } else { + //$mailboxes = $this->mailboxesCanViewWithSettings(true); + $mailboxes = $this->mailboxesSettings(); + foreach ($mailboxes as $mailbox) { + if (!empty($mailbox->access) && !empty(json_decode($mailbox->access))) { + return true; + } + }; + } + } + + /** + * Check to see if the user can manage a specific mailbox + */ + public function canManageMailbox($mailbox_id) + { + if ($this->isAdmin()) { + return true; + } else { + //$mailbox = $this->mailboxesCanViewWithSettings(true)->where('id', $mailbox_id)->first(); + $mailbox = $this->mailboxesSettings()->where('mailbox_id', $mailbox_id)->first(); + if ($mailbox && !empty(json_decode($mailbox->access ?? ''))) { + return true; + } + } + } + + /** + * Main function to check if user has some exta access permission + * for a given mailbox. + */ + public function hasManageMailboxPermission($mailbox_id, $perm) { + // Experimental feature. + // This option does not affect admin users. + if ($perm == Mailbox::ACCESS_PERM_ASSIGNED) { + if ($this->isAdmin()) { + return false; + } else { + $show_only_assigned_conversations = config('app.show_only_assigned_conversations') ?? ''; + if (in_array($this->id, explode(',', $show_only_assigned_conversations))) { + return true; + } else { + return false; + } + } + } + + if ($this->isAdmin()) { + return true; + } else { + //$mailbox = $this->mailboxesCanViewWithSettings(true)->where('id', $mailbox_id)->first(); + $mailbox = $this->mailboxesSettings()->where('mailbox_id', $mailbox_id)->first(); + if ($mailbox && !empty($mailbox->access) && in_array($perm, json_decode($mailbox->access))) { + return true; + } else { + return false; + } + } + } + + + + /** + * Generate random password for the user. + * + * @param int $length + * + * @return string + */ + public static function generateRandomPassword($length = 8) + { + return str_random($length); + } + + /** + * This password indicates that the user has not set the password by himself. + */ + public static function getDummyPassword() + { + return encrypt('dummy_'.str_random(8)); + } + + public function isDummyPassword() + { + $decrypted_password = \Helper::decrypt($this->password); + return preg_match("#^dummy_#", $decrypted_password); + //return Hash::check($this->getDummyPassword(), $this->password); + } + + /** + * Get URL for editing user. + * + * @return string + */ + public function url() + { + return route('users.profile', ['id'=>$this->id]); + } + + /** + * Get URL for settings up an account from invitation. + * + * @return string + */ + public function urlSetup() + { + return route('user_setup', ['hash' => $this->invite_hash]); + } + + /** + * Create personal folders for user mailboxes. + * + * @param int $mailbox_id + * @param mixed $users + */ + public function syncPersonalFolders($mailboxes) + { + if ($this->isAdmin()) { + // For admin we get all mailboxes + $mailbox_ids = Mailbox::pluck('mailboxes.id'); + } else { + if (is_array($mailboxes)) { + $mailbox_ids = $mailboxes; + } else { + $mailbox_ids = $this->mailboxes()->pluck('mailboxes.id'); + } + } + + $cur_mailboxes = Folder::select('mailbox_id') + ->where('user_id', $this->id) + ->whereIn('mailbox_id', $mailbox_ids) + ->groupBy('mailbox_id') + ->pluck('mailbox_id') + ->toArray(); + + foreach ($mailbox_ids as $mailbox_id) { + if (in_array($mailbox_id, $cur_mailboxes)) { + continue; + } + foreach (Folder::$personal_types as $type) { + Folder::create([ + 'mailbox_id' => $mailbox_id, + 'user_id' => $this->id, + 'type' => $type, + ]); + } + } + } + + /** + * Format date according to user's timezone and time format. + * + * @param Carbon $date + * @param string $format + * + * @return string + */ + public static function dateFormat($date, $format = 'M j, Y H:i', $user = null, $modify_format = true, $use_user_timezone = true) + { + if (!$user) { + $user = auth()->user(); + } + if (is_string($date)) { + // Convert string in to Carbon + try { + $date = Carbon::parse($date); + } catch (\Exception $e) { + $date = null; + } + } + + if (!$date) { + return ''; + } + + if (!$format) { + $format = 'M j, Y H:i'; + } + + if ($user && $user !== false) { + if ($modify_format) { + if ($user->time_format == self::TIME_FORMAT_12) { + $format = strtr($format, [ + 'H' => 'h', + 'G' => 'g', + ':i' => ':ia', + ':i:s' => ':i:sa', + ':ia:s' => ':i:sa', + ]); + } else { + $format = strtr($format, [ + 'h' => 'H', + 'g' => 'G', + ':ia' => ':i', + ':i:sa' => ':i:s', + ]); + } + } + // todo: formatLocalized has to be used here and below, + // but it returns $format value instead of formatted date + if ($use_user_timezone) { + $date->setTimezone($user->timezone); + } + } + + if (class_exists('IntlDateFormatter')) { + + // Convert `strftime` format to `IntlDateFormatter` pattern. + // https://unicode-org.github.io/icu/userguide/format_parse/datetime/ + $format = strtr($format, [ + 'M' => 'MMM', + 'm' => 'MM', + 'j' => 'd', + 'd' => 'dd', + 'H' => 'HH', + 'h' => 'hh', + 'i' => 'mm', + 's' => 'ss', + 'l' => 'cccc', + 'O' => 'xx', + ]); + + // Remove dot from month name. + $formatted = $date->formatLocalized($format); + if (!strstr($format, '.')) { + $formatted = str_replace('.', '', $formatted); + } + + // AM/PM to am/pm. + $formatted = preg_replace_callback('#\d+(AM|PM)$#', function ($m) { + return strtolower($m[0] ?? ''); + }, $formatted); + + return \Helper::mbUcfirst($formatted); + } else { + return $date->format($format); + } + } + + /** + * Convert date into human readable format. + * + * @param Carbon $date + * + * @return string + */ + public static function dateDiffForHumans($date) + { + if (!$date) { + return ''; + } + + if (is_string($date)) { + // Convert string in to Carbon + $date = Carbon::parse($date); + } + + $user = auth()->user(); + if ($user) { + $date->setTimezone($user->timezone); + } + + if ($date->diffInSeconds(Carbon::now()) <= 60) { + return __('Just now'); + } elseif ($date->diffInDays(Carbon::now()) > 7) { + // Exact date + if (Carbon::now()->year == $date->year) { + return self::dateFormat($date, 'M j'); + } else { + return self::dateFormat($date, 'M j, Y'); + } + } else { + $diff_text = $date->diffForHumans(); + $diff_text = preg_replace('/minute[sn]?/', 'min', $diff_text); + + return $diff_text; + } + } + + /** + * Convert date into human readable format with minutes and hours. + * + * @param Carbon $date + * + * @return string + */ + public static function dateDiffForHumansWithHours($date) + { + $dateForHuman = self::dateDiffForHumans($date); + + if (!$dateForHuman) { + return ''; + } + + if (stripos($dateForHuman, 'just') === false) { + return __(':date @ :time', ['date' => $dateForHuman, 'time' => $date->format('H:i')]); + } else { + return $dateForHuman; + } + } + + public static function getUserPermissionName($user_permission) + { + $user_permission_names = [ + self::PERM_DELETE_CONVERSATIONS => __('Users are allowed to delete conversations'), + self::PERM_EDIT_CONVERSATIONS => __('Users are allowed to edit notes/replies'), + self::PERM_EDIT_SAVED_REPLIES => __('Users are allowed to edit/delete saved replies'), + self::PERM_EDIT_TAGS => __('Users are allowed to manage tags'), + self::PERM_EDIT_CUSTOM_FOLDERS => __('Users are allowed to manage custom folders'), + self::PERM_EDIT_USERS => __('Users are allowed to manage users'), + ]; + + if (!empty($user_permission_names[$user_permission])) { + return $user_permission_names[$user_permission]; + } else { + return \Eventy::filter('user_permissions.name', '', $user_permission); + } + } + + public function getInviteStateName() + { + $names = [ + self::INVITE_STATE_ACTIVATED => __('Active'), + self::INVITE_STATE_SENT => __('Invited'), + self::INVITE_STATE_NOT_INVITED => __('Not Invited'), + ]; + if (!isset($names[$this->invite_state])) { + return $names[self::INVITE_STATE_ACTIVATED]; + } else { + return $names[$this->invite_state]; + } + } + + /** + * Send invitation to this user. + */ + public function sendInvite($throw_exceptions = false) + { + function saveToSendLog($user, $status) + { + SendLog::log(null, null, $user->email, SendLog::MAIL_TYPE_INVITE, $status, null, $user->id); + } + + if ($this->invite_state == self::INVITE_STATE_ACTIVATED) { + return false; + } + // We are using remember_token as a hash for invite + if (!$this->invite_hash) { + $this->setInviteHash(); + $this->save(); + } + + try { + \App\Misc\Mail::setSystemMailDriver(); + + \Mail::to([['name' => $this->getFullName(), 'email' => $this->email]]) + ->send(new UserInvite($this)); + } catch (\Exception $e) { + // We come here in case SMTP server unavailable for example + // But Mail does not through an exception if you specify incorrect SMTP details for example + activity() + ->causedBy($this) + ->withProperties([ + 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')', + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_INVITE); + + saveToSendLog($this, SendLog::STATUS_SEND_ERROR); + + if ($throw_exceptions) { + throw $e; + } else { + return false; + } + } + + if (\Mail::failures()) { + saveToSendLog($this, SendLog::STATUS_SEND_ERROR); + + if ($throw_exceptions) { + throw new \Exception(__('Error occurred sending email to :email. Please check logs for more details.', ['email' => $this->email]), 1); + } else { + return false; + } + } + + if ($this->invite_state != self::INVITE_STATE_SENT) { + $this->invite_state = self::INVITE_STATE_SENT; + $this->save(); + } + + saveToSendLog($this, SendLog::STATUS_ACCEPTED); + + return true; + } + + /** + * Generate and set password. + */ + public function setPassword($password = null) + { + if ($password === null) { + $password = $this->generateRandomPassword(); + } + $this->password = Hash::make($password); + } + + /** + * Generate and set invite_hash. + */ + public function setInviteHash() + { + $this->invite_hash = Str::random(60); + } + + /** + * Send password changed noitfication. + */ + public function sendPasswordChanged() + { + function saveToSendLog($user, $status) + { + SendLog::log(null, null, $user->email, SendLog::MAIL_TYPE_PASSWORD_CHANGED, $status, null, $user->id); + } + + try { + \App\Misc\Mail::setSystemMailDriver(); + + \Mail::to([['name' => $this->getFullName(), 'email' => $this->email]]) + ->send(new PasswordChanged($this)); + } catch (\Exception $e) { + // We come here in case SMTP server unavailable for example + // But Mail does not through an exception if you specify incorrect SMTP details for example + activity() + ->causedBy($this) + ->withProperties([ + 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')', + ]) + ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING) + ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_PASSWORD_CHANGED); + + saveToSendLog($this, SendLog::STATUS_SEND_ERROR); + + return false; + } + + if (\Mail::failures()) { + saveToSendLog($this, SendLog::STATUS_SEND_ERROR); + + return false; + } + + saveToSendLog($this, SendLog::STATUS_ACCEPTED); + + return true; + } + + /** + * Send the password reset notification. + * + * @param string $token + * + * @return void + */ + public function sendPasswordResetNotification($token) + { + \App\Misc\Mail::setSystemMailDriver(); + + $this->notify(new ResetPasswordNotification($token)); + } + + public function getWebsiteNotifications() + { + return $this->notifications()->paginate(self::WEBSITE_NOTIFICATIONS_PAGE_SIZE, ['*'], self::WEBSITE_NOTIFICATIONS_PAGE_PARAM, request()->wn_page); + } + + public function getWebsiteNotificationsInfo($cache = true) + { + if ($cache) { + // Get from cache + $user = $this; + + return \Cache::rememberForever('user_web_notifications_'.$user->id, function () use ($user) { + $notifications = $user->getWebsiteNotifications(); + + $info = [ + 'data' => WebsiteNotification::fetchNotificationsData($notifications), + 'notifications' => $notifications, + 'unread_count' => $user->unreadNotifications()->count(), + ]; + + $info['html'] = view('users/partials/web_notifications', ['web_notifications_info_data' => $info['data']])->render(); + + return $info; + }); + } else { + $notifications = $this->getWebsiteNotifications(); + + $info = [ + 'data' => WebsiteNotification::fetchNotificationsData($notifications), + 'notifications' => $notifications, + 'unread_count' => $this->unreadNotifications()->count(), + ]; + + return $info; + } + } + + public function clearWebsiteNotificationsCache() + { + \Cache::forget('user_web_notifications_'.$this->id); + } + + public function getPhotoUrl($default_if_empty = true) + { + if (!empty($this->photo_url) || !$default_if_empty) { + if (!empty($this->photo_url)) { + return Storage::url(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$this->photo_url); + } else { + return ''; + } + } else { + return asset('/img/default-avatar.png'); + } + } + + /** + * Resize and save user photo. + * + * $uploaded_file can be \File or string. + */ + public function savePhoto($uploaded_file, $mime_type = '') + { + $real_path = $uploaded_file; + if (!is_string($uploaded_file)) { + $real_path = $uploaded_file->getRealPath(); + $mime_type = $uploaded_file->getMimeType(); + } + + $photo_size = config('app.user_photo_size'); + $resized_image = \App\Misc\Helper::resizeImage($real_path, $mime_type, $photo_size, $photo_size); + + if (!$resized_image) { + return false; + } + + $file_name = md5(Hash::make($this->id)).'.jpg'; + $dest_path = Storage::path(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$file_name); + + $dest_dir = pathinfo($dest_path, PATHINFO_DIRNAME); + if (!file_exists($dest_dir)) { + \File::makeDirectory($dest_dir, 0755); + } + + // Remove current photo + if ($this->photo_url) { + Storage::delete(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$this->photo_url); + } + + imagejpeg($resized_image, $dest_path, self::PHOTO_QUALITY); + // $photo_url = $request->file('photo_url')->storeAs( + // User::PHOTO_DIRECTORY, !Hash::make($user->id).'.jpg' + // ); + + return $file_name; + } + + /** + * Remove user photo. + */ + public function removePhoto() + { + if ($this->photo_url) { + Storage::delete(self::PHOTO_DIRECTORY.DIRECTORY_SEPARATOR.$this->photo_url); + } + $this->photo_url = ''; + } + + public function hasPermission($permission, $check_own_permissions = true) + { + $has_permission = false; + + $global_permissions = self::getGlobalUserPermissions(); + + if (!empty($global_permissions) && is_array($global_permissions) && in_array($permission, $global_permissions)) { + $has_permission = true; + } + + if ($check_own_permissions && !empty($this->permissions)) { + if (isset($this->permissions[$permission])) { + $has_permission = (bool)$this->permissions[$permission]; + } + } + + return $has_permission; + } + + public static function getGlobalUserPermissions() + { + $permissions = []; + $permissions_json = config('app.user_permissions'); + + if ($permissions_json) { + $permissions_json = base64_decode($permissions_json); + try { + $permissions = json_decode($permissions_json, true); + } catch (\Exception $e) { + // Do nothing. + } + } + + if (!is_array($permissions)) { + $permissions = []; + } + + return $permissions; + } + + /** + * Todo: implement super admin role. + * For now we return just first admin. + * + * @return [type] [description] + */ + public static function getSuperAdmin() + { + return self::nonDeleted()->where('role', self::ROLE_ADMIN)->first(); + } + + /** + * Create user. + */ + public static function create($data) + { + $user = new self(); + + if (empty($data['email']) || empty($data['password'])) { + return null; + } + + $user->setData($data); + + try { + $user->save(); + } catch (\Exception $e) { + return null; + } + + return $user; + } + + /** + * Set fields. + */ + public function setData($data, $replace_data = true, $save = false) + { + if (isset($data['email'])) { + $data['email'] = Email::sanitizeEmail($data['email']); + } + if (isset($data['password']) && empty($data['no_password_hashing'])) { + $data['password'] = \Hash::make($data['password']); + } + + if ($replace_data) { + $this->fill($data); + } else { + // Update empty fields. + foreach ($data as $key => $value) { + if (in_array($key, $this->fillable) && empty($this->$key)) { + $this->$key = $value; + } + } + } + + \Eventy::action('user.set_data', $this, $data, $replace_data); + + if ($save) { + $this->save(); + } + } + + /** + * Check if current user's role is higher than passed. + */ + public static function checkRole($role) + { + $user = auth()->user(); + if ($user) { + return $user->role >= $role; + } else { + return false; + } + } + + /** + * Get dummy user, for example, when real user has been deleted. + */ + public static function getDeletedUser() + { + $user = new self(); + $user->first_name = 'DELETED'; + $user->last_name = 'DELETED'; + $user->email = 'deleted@example.org'; + + return $user; + } + + /** + * Get user locale. + * + * @return [type] [description] + */ + public function getLocale() + { + if ($this->locale) { + return $this->locale; + } else { + return \Helper::getRealAppLocale(); + } + } + + /** + * Get query to fetch non-deleted users. + * Some modules may extend this condition, to allow this user $extended parameter. + * + * @return [type] [description] + */ + public static function nonDeleted($extended = false) + { + $condition = self::where('status', '!=', self::STATUS_DELETED); + + return \Eventy::filter('user.non_deleted_condition', $condition, $extended); + } + + public function isActive() + { + return $this->status == self::STATUS_ACTIVE; + } + + public function isDisabled() + { + return $this->status == self::STATUS_DISABLED; + } + + public function isDeleted() + { + return $this->status == self::STATUS_DELETED; + } + + /** + * Get users which current user can see. + */ + public function whichUsersCanView($mailboxes = null, $sort = true) + { + if ($this->isAdmin()) { + $users = User::nonDeleted()->get(); + } else { + // Get user mailboxes. + if ($mailboxes == null) { + $mailbox_ids = $this->mailboxesIdsCanView(); + } else { + $mailbox_ids = $mailboxes->pluck('id')->toArray(); + } + + // Get users + $users = User::nonDeleted()->select('users.*') + ->join('mailbox_user', function ($join) { + $join->on('mailbox_user.user_id', '=', 'users.id'); + }) + ->whereIn('mailbox_user.mailbox_id', $mailbox_ids) + ->groupBy('users.id') + ->get(); + } + + if ($sort) { + $users = User::sortUsers($users); + } + + return $users; + } + + /** + * Get user initials: FL. + */ + public function getInitials($length = 2) + { + if ($length == 2) { + return strtoupper(mb_substr($this->first_name, 0, 1)).strtoupper(mb_substr($this->last_name, 0, 1)); + } else { + return strtoupper(mb_substr($this->first_name, 0, 1)); + } + } + + public function getAuthToken() + { + return md5($this->id.''.$this->created_at.config('app.key')); + } + + public static function findNonDeleted($id, $extended = false) + { + return User::nonDeleted($extended)->where('id', $id)->first(); + } + + /** + * Sorting users alphabetically. + * It has to be done in PHP. + */ + public static function sortUsers($users) + { + $users = $users->sortBy(function ($user, $i) { + return $user->getFullName(); + }, SORT_STRING | SORT_FLAG_CASE); + + return $users; + } + + public static function getUserPermissionsList() + { + return \Eventy::filter('user_permissions.list', self::$user_permissions); + } + + /** + * Check user main and alternate emails. + */ + public function hasEmail($email) + { + $email = Email::sanitizeEmail($email); + + if ($this->email == $email) { + return true; + } + $alt_emails = explode(',', $this->emails ?? ''); + + foreach ($alt_emails as $alt_email) { + if (Email::sanitizeEmail($alt_email) == $email) { + return true; + } + } + + return false; + } + + /** + * Check if there is a mailbox with specified email. + */ + public static function mailboxEmailExists($email) + { + $email = Email::sanitizeEmail($email); + $mailbox = Mailbox::where('email', $email)->first(); + + if ($mailbox) { + return true; + } else { + return false; + } + } + + public function followConversation($conversation_id) + { + try { + $follower = new Follower(); + $follower->conversation_id = $conversation_id; + $follower->user_id = $this->id; + $follower->save(); + } catch (\Exception $e) { + // Already exists + } + } + + // If there will be some issues, extra "robot" field + // may need to be added to Users table. + public static function getRobotsCondition() + { + return User::where('type', User::TYPE_ROBOT); + } + + // Truncate fields to their max lengths to avoid PostgreSQL error: + // SQLSTATE[22001]: String data, right truncated: 7 ERROR: value too long for type character varying(100). + // https://github.com/freescout-helpdesk/freescout/issues/3489 + public function setFirstNameAttribute($first_name) + { + $this->attributes['first_name'] = mb_substr($first_name ?? '', 0, 20); + } + public function setLastNameAttribute($last_name) + { + $this->attributes['last_name'] = mb_substr($last_name ?? '', 0, 30); + } + public function setEmailAttribute($email) + { + $this->attributes['email'] = mb_substr($email ?? '', 0, 100); + } + public function setJobTitleAttribute($job_title) + { + $this->attributes['job_title'] = mb_substr($job_title ?? '', 0, 100); + } +} diff --git a/freescout-dist/artisan b/freescout-dist/artisan new file mode 100644 index 0000000..c67e810 --- /dev/null +++ b/freescout-dist/artisan @@ -0,0 +1,78 @@ +#!/usr/bin/env php +=')) { + echo "\e[31mPHP 7.x is required to run FreeScout.\e[0m\n"; + exit(); +} + +define('LARAVEL_START', microtime(true)); + +// PHP 8.1 fix. +if (! function_exists('e')) { + /** + * Escape HTML special characters in a string. + * + * @param \Illuminate\Contracts\Support\Htmlable|string $value + * @param bool $doubleEncode + * @return string + */ + function e($value, $doubleEncode = false) + { + if ($value instanceof \Illuminate\Contracts\Support\Htmlable) { + return $value->toHtml(); + } + + return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8', $doubleEncode); + } +} + +/* +|-------------------------------------------------------------------------- +| Register The Auto Loader +|-------------------------------------------------------------------------- +| +| Composer provides a convenient, automatically generated class loader +| for our application. We just need to utilize it! We'll require it +| into the script here so that we do not have to worry about the +| loading of any our classes "manually". Feels great to relax. +| +*/ + +require __DIR__.'/vendor/autoload.php'; + +$app = require_once __DIR__.'/bootstrap/app.php'; + +/* +|-------------------------------------------------------------------------- +| Run The Artisan Application +|-------------------------------------------------------------------------- +| +| When we run the console application, the current CLI command will be +| executed in this console and the response sent back to a terminal +| or another output device for the developers. Here goes nothing! +| +*/ + +$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); + +$status = $kernel->handle( + $input = new Symfony\Component\Console\Input\ArgvInput, + new Symfony\Component\Console\Output\ConsoleOutput +); + +/* +|-------------------------------------------------------------------------- +| Shutdown The Application +|-------------------------------------------------------------------------- +| +| Once Artisan has finished running, we will fire off the shutdown events +| so that any final work may be done by the application before we shut +| down the process. This is the last thing to happen to the request. +| +*/ + +$kernel->terminate($input, $status); + +exit($status); diff --git a/freescout-dist/bootstrap/app.php b/freescout-dist/bootstrap/app.php new file mode 100644 index 0000000..f2801ad --- /dev/null +++ b/freescout-dist/bootstrap/app.php @@ -0,0 +1,55 @@ +singleton( + Illuminate\Contracts\Http\Kernel::class, + App\Http\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Console\Kernel::class, + App\Console\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Debug\ExceptionHandler::class, + App\Exceptions\Handler::class +); + +/* +|-------------------------------------------------------------------------- +| Return The Application +|-------------------------------------------------------------------------- +| +| This script returns the application instance. The instance is given to +| the calling script so we can separate the building of the instances +| from the actual running of the application and sending responses. +| +*/ + +return $app; diff --git a/freescout-dist/bootstrap/cache/.gitignore b/freescout-dist/bootstrap/cache/.gitignore new file mode 100755 index 0000000..d6b7ef3 --- /dev/null +++ b/freescout-dist/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/freescout-dist/composer.json b/freescout-dist/composer.json new file mode 100644 index 0000000..a32e7de --- /dev/null +++ b/freescout-dist/composer.json @@ -0,0 +1,347 @@ +{ + "name": "freescout-helpdesk/freescout", + "description": "Free self-hosted helpdesk and shared mailbox (Zendesk / Help Scout alternative)", + "keywords": ["helpdesk", "help desk", "shared mailbox"], + "license": "AGPL-3.0", + "authors": [ + { + "name": "The FreeScout Team", + "email": "support@freescout.net" + } + ], + "require": { + "php": ">=7.1.0", + + "symfony/debug": "v3.4.18", + "devfactory/minify": "1.0.7", + "fideloper/proxy": "3.3.4", + "laravel/framework": "v5.5.40", + "laravel/tinker": "v1.0.7", + "mews/purifier": "3.2.2", + "spatie/laravel-activitylog": "2.7.0", + "lord/laroute": "2.4.7", + "axn/laravel-laroute": "1.3.0", + "html2text/html2text": "4.1.0", + "webklex/laravel-imap": "1.2.7", + "watson/rememberable": "2.0.4", + "nwidart/laravel-modules": "2.7.0", + "tormjens/eventy": "0.5.4", + "barryvdh/laravel-translation-manager": "v0.5.0", + "chumper/zipper": "v1.0.2", + "rachidlaasri/laravel-installer": "4.0.2", + "rap2hpoutre/laravel-log-viewer": "v2.0.0", + "codedge/laravel-selfupdater": "1.4.3", + "doctrine/dbal": "2.12.1", + "egulias/email-validator": "2.1.10", + "symfony/console": "v3.4.47", + "javoscript/laravel-macroable-models": "1.0.4", + "ramsey/uuid": "3.9.6", + "symfony/polyfill-mbstring": "v1.24.0", + "webklex/php-imap": "4.1.1", + "enshrined/svg-sanitize": "0.15.4" + }, + "require-dev": { + "barryvdh/laravel-debugbar": "v3.2.0", + "filp/whoops": "2.14.5", + "fzaninotto/faker": "v1.9.2", + "mockery/mockery": "1.1.0", + "phpunit/phpunit": "9.5.28", + + "symfony/polyfill-ctype": "v1.10.*", + "vlucas/phpdotenv": "v2.5.1", + "symfony/css-selector": "v3.4.18", + "symfony/var-dumper": "v3.4.18", + "symfony/routing": "v3.4.18", + "symfony/process": "v3.4.18", + "symfony/polyfill-php70": "v1.10.*", + "symfony/http-foundation": "v3.4.18", + "symfony/event-dispatcher": "v3.4.18", + "psr/log": "1.0.*", + "symfony/http-kernel": "v3.4.18", + "symfony/finder": "v3.4.18", + "doctrine/lexer": "v1.0.1", + "swiftmailer/swiftmailer": "v6.1.*", + "symfony/translation": "v3.4.18", + "nesbot/carbon": "1.35.*", + "league/flysystem": "1.1.4", + "erusev/parsedown": "1.7.2", + "doctrine/inflector": "v1.2.*", + "guzzlehttp/guzzle": "6.5.8", + "guzzlehttp/psr7": "1.9.1", + "tedivm/jshrink": "1.4.0", + "nikic/php-parser": "^4.1", + "doctrine/annotations": "v1.4.*", + "doctrine/cache": "v1.6.*", + "doctrine/collections": "v1.4.*", + "doctrine/instantiator": "1.3.1", + "myclabs/deep-copy": "1.10.1", + "webmozart/assert": "1.3.0", + "psr/container": "1.0.0", + "psr/http-message": "1.0.1" + }, + "autoload": { + "classmap": [ + "database/seeds", + "database/factories", + "overrides/natxet/cssmin/src/" + ], + "psr-0": { + "Swift_": "overrides/swiftmailer/swiftmailer/lib/classes", + "HTMLPurifier_": "overrides/ezyang/htmlpurifier/library" + }, + "fs-comment": "Above new lines are listed files containing custom updates. Below - only PHP compatibility updates.", + "psr-4": { + "App\\": "app/", + "Modules\\": "Modules/", + "Axn\\Laroute\\Routes\\": "overrides/axn/laravel-laroute/src/Routes/", + "Webklex\\IMAP\\": "overrides/webklex/laravel-imap/src/IMAP/", + "Webklex\\PHPIMAP\\": "overrides/webklex/php-imap/src/", + "RachidLaasri\\LaravelInstaller\\Helpers\\": "overrides/rachidlaasri/laravel-installer/src/Helpers/", + "RachidLaasri\\LaravelInstaller\\Middleware\\": "overrides/rachidlaasri/laravel-installer/src/Middleware/", + "RachidLaasri\\LaravelInstaller\\Controllers\\": "overrides/rachidlaasri/laravel-installer/src/Controllers/", + "RachidLaasri\\LaravelInstaller\\Providers\\": "overrides/rachidlaasri/laravel-installer/src/Providers/", + "RachidLaasri\\LaravelInstaller\\Events\\": "overrides/rachidlaasri/laravel-installer/src/Events/", + "Nwidart\\Modules\\": "overrides/nwidart/laravel-modules/src/", + "Codedge\\Updater\\SourceRepositoryTypes\\": "overrides/codedge/laravel-selfupdater/src/SourceRepositoryTypes/", + "Barryvdh\\TranslationManager\\": "overrides/barryvdh/laravel-translation-manager/src/", + "Illuminate\\Foundation\\": "overrides/laravel/framework/src/Illuminate/Foundation/", + "Illuminate\\Foundation\\Testing\\": "overrides/laravel/framework/src/Illuminate/Foundation/Testing/", + "Illuminate\\Foundation\\Http\\Middleware\\": "overrides/laravel/framework/src/Illuminate/Foundation/Http/Middleware/", + "Illuminate\\Routing\\": "overrides/laravel/framework/src/Illuminate/Routing/", + "Illuminate\\Broadcasting\\Broadcasters\\": "overrides/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/", + "Illuminate\\Validation\\Concerns\\": "overrides/laravel/framework/src/Illuminate/Validation/Concerns/", + "Illuminate\\Mail\\": "overrides/laravel/framework/src/Illuminate/Mail/", + "Illuminate\\Auth\\": "overrides/laravel/framework/src/Illuminate/Auth/", + "Illuminate\\Auth\\Middleware\\": "overrides/laravel/framework/src/Illuminate/Auth/Middleware/", + "Illuminate\\Container\\": "overrides/laravel/framework/src/Illuminate/Container/", + "Illuminate\\Filesystem\\": "overrides/laravel/framework/src/Illuminate/Filesystem/", + "Illuminate\\Cookie\\": "overrides/laravel/framework/src/Illuminate/Cookie/", + "Illuminate\\Cookie\\Middleware\\": "overrides/laravel/framework/src/Illuminate/Cookie/Middleware/", + "Lord\\Laroute\\Routes\\": "overrides/lord/laroute/src/Routes/", + "TorMorten\\Eventy\\": "overrides/tormjens/eventy/src/", + "Symfony\\Component\\Debug\\": "overrides/symfony/debug/", + "Symfony\\Component\\HttpFoundation\\": "overrides/symfony/http-foundation/", + "Symfony\\Component\\HttpFoundation\\File\\MimeType\\": "overrides/symfony/http-foundation/File/MimeType/", + "Chumper\\Zipper\\Repositories\\": "overrides/chumper/zipper/src/Chumper/Zipper/Repositories/", + "Faker\\Provider\\": "overrides/fzaninotto/faker/src/Faker/Provider/", + + "Illuminate\\Support\\": "overrides/laravel/framework/src/Illuminate/Support/", + "Illuminate\\Http\\": "overrides/laravel/framework/src/Illuminate/Http/", + "Illuminate\\Database\\Eloquent\\": "overrides/laravel/framework/src/Illuminate/Database/Eloquent/", + "Illuminate\\Database\\Eloquent\\Concerns\\": "overrides/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/", + "Illuminate\\Pagination\\": "overrides/laravel/framework/src/Illuminate/Pagination/", + "Illuminate\\Session\\": "overrides/laravel/framework/src/Illuminate/Session/", + "Illuminate\\Queue\\": "overrides/laravel/framework/src/Illuminate/Queue/", + "Carbon\\": "overrides/nesbot/carbon/src/Carbon/", + "Illuminate\\Cache\\": "overrides/laravel/framework/src/Illuminate/Cache/", + "Illuminate\\Config\\": "overrides/laravel/framework/src/Illuminate/Config/", + "Doctrine\\DBAL\\Driver\\": "overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/", + "Doctrine\\DBAL\\Schema\\": "overrides/doctrine/dbal/lib/Doctrine/DBAL/Schema/", + "Doctrine\\DBAL\\Platforms\\": "overrides/doctrine/dbal/lib/Doctrine/DBAL/Platforms/", + "Symfony\\Component\\Finder\\Iterator\\": "overrides/symfony/finder/Iterator/", + "Symfony\\Component\\Finder\\": "overrides/symfony/finder/", + "Symfony\\Component\\Console\\Helper\\": "overrides/symfony/console/Helper/", + "DebugBar\\": "overrides/maximebf/debugbar/src/DebugBar/", + "DebugBar\\DataFormatter\\": "overrides/maximebf/debugbar/src/DataFormatter/DataFormatter/", + "Illuminate\\Cache\\Console\\": "overrides/laravel/framework/src/Illuminate/Cache/Console/", + "Dotenv\\": "overrides/vlucas/phpdotenv/src/", + "Illuminate\\View\\": "overrides/laravel/framework/src/Illuminate/View/", + "Illuminate\\View\\Compilers\\": "overrides/laravel/framework/src/Illuminate/View/Compilers/", + "Illuminate\\View\\Compilers\\Concerns\\": "overrides/laravel/framework/src/Illuminate/View/Compilers/Concerns/", + "Illuminate\\View\\Concerns\\": "overrides/laravel/framework/src/Illuminate/View/Concerns/", + "Symfony\\Component\\Routing\\": "overrides/symfony/routing/", + "Symfony\\Component\\VarDumper\\Cloner\\": "overrides/symfony/var-dumper/Cloner/", + "Symfony\\Component\\VarDumper\\Dumper\\": "overrides/symfony/var-dumper/Dumper/", + "Devfactory\\Minify\\Providers\\": "overrides/devfactory/minify/src/Providers/", + "Barryvdh\\Debugbar\\": "overrides/barryvdh/laravel-debugbar/src/", + "Barryvdh\\Debugbar\\DataFormatter\\": "overrides/barryvdh/laravel-debugbar/src/DataFormatter/", + "Symfony\\Component\\Process\\": "overrides/symfony/process/", + "Symfony\\Component\\HttpKernel\\": "overrides/symfony/http-kernel/", + "Symfony\\Component\\HttpKernel\\Exception\\": "overrides/symfony/http-kernel/Exception/", + "Symfony\\Component\\HttpKernel\\HttpCache\\": "overrides/symfony/http-kernel/HttpCache/", + "Spatie\\String\\": "overrides/spatie/string/src/", + "GuzzleHttp\\": "overrides/guzzlehttp/guzzle/src/", + "GuzzleHttp\\Cookie\\": "overrides/guzzlehttp/guzzle/src/Cookie/", + "GuzzleHttp\\Psr7\\": "overrides/guzzlehttp/psr7/src/", + "Ramsey\\Uuid\\": "overrides/ramsey/uuid/src/", + "Rap2hpoutre\\LaravelLogViewer\\": "overrides/rap2hpoutre/laravel-log-viewer/src/Rap2hpoutre/LaravelLogViewer/", + "Symfony\\Component\\Console\\Descriptor\\": "overrides/symfony/console/Descriptor", + "Symfony\\Component\\CssSelector\\XPath\\Extension\\": "overrides/symfony/css-selector/XPath/Extension/", + "Javoscript\\MacroableModels\\": "overrides/javoscript/laravel-macroable-models/src/" + }, + "exclude-from-classmap": [ + "vendor/axn/laravel-laroute/src/Routes/Collection.php", + "vendor/webklex/laravel-imap/src/IMAP/Message.php", + "vendor/webklex/laravel-imap/src/IMAP/Attachment.php", + "vendor/webklex/laravel-imap/src/IMAP/Client.php", + "vendor/rachidlaasri/laravel-installer/src/Helpers/RequirementsChecker.php", + "vendor/rachidlaasri/laravel-installer/src/Helpers/PermissionsChecker.php", + "vendor/rachidlaasri/laravel-installer/src/Helpers/EnvironmentManager.php", + "vendor/rachidlaasri/laravel-installer/src/Helpers/FinalInstallManager.php", + "vendor/rachidlaasri/laravel-installer/src/Helpers/InstalledFileManager.php", + "vendor/rachidlaasri/laravel-installer/src/Helpers/DatabaseManager.php", + "vendor/rachidlaasri/laravel-installer/src/Middleware/canInstall.php", + "vendor/rachidlaasri/laravel-installer/src/Controllers/EnvironmentController.php", + "vendor/rachidlaasri/laravel-installer/src/Controllers/FinalController.php", + "vendor/rachidlaasri/laravel-installer/src/Events/EnvironmentSaved.php", + "vendor/nwidart/laravel-modules/src/Module.php", + "vendor/nwidart/laravel-modules/src/Repository.php", + "vendor/nwidart/laravel-modules/src/Json.php", + "vendor/codedge/laravel-selfupdater/src/SourceRepositoryTypes/GithubRepositoryType.php", + "vendor/barryvdh/laravel-translation-manager/src/Manager.php", + "vendor/barryvdh/laravel-translation-manager/src/Controller.php", + "vendor/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php", + "vendor/laravel/framework/src/Illuminate/Foundation/PackageManifest.php", + "vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php", + "vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php", + "vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php", + "vendor/laravel/framework/src/Illuminate/Routing/RouteSignatureParameters.php", + "vendor/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php", + "vendor/laravel/framework/src/Illuminate/Routing/Controller.php", + "vendor/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php", + "vendor/laravel/framework/src/Illuminate/Validation/Concerns/FormatsMessages.php", + "vendor/laravel/framework/src/Illuminate/Mail/TransportManager.php", + "vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php", + "vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php", + "vendor/laravel/framework/src/Illuminate/Container/Container.php", + "vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php", + "vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php", + "vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php", + "vendor/rachidlaasri/laravel-installer/src/Providers/LaravelInstallerServiceProvider.php", + "vendor/lord/laroute/src/Routes/Collection.php", + "vendor/tormjens/eventy/src/Filter.php", + "vendor/tormjens/eventy/src/Action.php", + "vendor/tormjens/eventy/src/Event.php", + "vendor/symfony/debug/ExceptionHandler.php", + "vendor/symfony/http-foundation/ResponseHeaderBag.php", + "vendor/symfony/http-foundation/Cookie.php", + "vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php", + "vendor/chumper/zipper/src/Chumper/Zipper/Repositories/ZipRepository.php", + "vendor/fzaninotto/faker/src/Faker/Provider/Base.php", + "vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php", + + "vendor/laravel/framework/src/Illuminate/Support/Collection.php", + "vendor/laravel/framework/src/Illuminate/Http/Request.php", + "vendor/symfony/http-foundation/ParameterBag.php", + "vendor/symfony/http-foundation/HeaderBag.php", + "vendor/symfony/http-foundation/Response.php", + "vendor/symfony/http-foundation/AcceptHeader.php", + "vendor/symfony/http-foundation/FileBag.php", + "vendor/symfony/http-foundation/Request.php", + "vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php", + "vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php", + "vendor/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php", + "vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php", + "vendor/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php", + "vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php", + "vendor/laravel/framework/src/Illuminate/Session/FileSessionHandler.php", + "vendor/laravel/framework/src/Illuminate/Queue/Listener.php", + "vendor/nesbot/carbon/src/Carbon/Carbon.php", + "vendor/laravel/framework/src/Illuminate/Cache/Repository.php", + "vendor/laravel/framework/src/Illuminate/Config/Repository.php", + "vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php", + "vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOQueryImplementation.php", + "vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php", + "vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatementImplementations.php", + "vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php", + "vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php", + "vendor/laravel/framework/src/Illuminate/Support/Carbon.php", + "vendor/laravel/framework/src/Illuminate/Support/Str.php", + "vendor/laravel/framework/src/Illuminate/Support/ViewErrorBag.php", + "vendor/laravel/framework/src/Illuminate/Support/MessageBag.php", + "vendor/laravel/framework/src/Illuminate/Support/Optional.php", + "vendor/laravel/framework/src/Illuminate/Support/Fluent.php", + "vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factory.php", + "vendor/symfony/finder/Finder.php", + "vendor/symfony/finder/Iterator/FilterIterator.php", + "vendor/symfony/finder/Iterator/FileTypeFilterIterator.php", + "vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php", + "vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php", + "vendor/symfony/finder/Iterator/FilenameFilterIterator.php", + "vendor/symfony/finder/Iterator/PathFilterIterator.php", + "vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php", + "vendor/symfony/finder/Iterator/DateRangeFilterIterator.php", + "vendor/symfony/console/Helper/HelperSet.php", + "vendor/maximebf/debugbar/src/DebugBar/DebugBar.php", + "vendor/maximebf/debugbar/src/DebugBar/JavascriptRenderer.php", + "vendor/maximebf/debugbar/src/DebugBar/DataFormatter/DataFormatter.php", + "vendor/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php", + "vendor/vlucas/phpdotenv/src/Loader.php", + "vendor/laravel/framework/src/Illuminate/View/View.php", + "vendor/laravel/framework/src/Illuminate/View/Compilers/Compiler.php", + "vendor/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesLayouts.php", + "vendor/laravel/framework/src/Illuminate/View/Concerns/ManagesLayouts.php", + "vendor/symfony/routing/Route.php", + "vendor/symfony/routing/CompiledRoute.php", + "vendor/symfony/var-dumper/Cloner/Data.php", + "vendor/symfony/var-dumper/Cloner/Stub.php", + "vendor/symfony/var-dumper/Dumper/HtmlDumper.php", + "vendor/devfactory/minify/src/Providers/BaseProvider.php", + "vendor/barryvdh/laravel-debugbar/src/DataFormatter/QueryFormatter.php", + "vendor/symfony/process/Process.php", + "vendor/symfony/http-kernel/UriSigner.php", + "vendor/symfony/http-kernel/Exception/HttpException.php", + "vendor/symfony/http-kernel/HttpCache/Store.php", + "vendor/barryvdh/laravel-debugbar/src/JavascriptRenderer.php", + "vendor/spatie/string/src/Str.php", + "vendor/guzzlehttp/guzzle/src/Client.php", + "vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php", + "vendor/guzzlehttp/psr7/src/LazyOpenStream.php", + "vendor/ramsey/uuid/src/Uuid.php", + "vendor/laravel/framework/src/Illuminate/Routing/Router.php", + "vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php", + "vendor/rap2hpoutre/laravel-log-viewer/src/Rap2hpoutre/LaravelLogViewer/LaravelLogViewer.php", + "vendor/symfony/console/Descriptor/TextDescriptor.php", + "vendor/symfony/console/Helper/Helper.php", + "vendor/symfony/finder/Iterator/SortableIterator.php", + "vendor/symfony/css-selector/XPath/Extension/NodeExtension.php", + "vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php", + "vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php", + "vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php", + "vendor/javoscript/laravel-macroable-models/src/MacroableModels.php", + "vendor/swiftmailer/swiftmailer/lib/classes/Swift/EmbeddedFile.php", + "vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php", + "vendor/natxet/cssmin/src/CssMin.php", + "vendor/webklex/php-imap/src/Header.php", + "vendor/webklex/php-imap/src/Structure.php", + "vendor/webklex/php-imap/src/Message.php", + "vendor/webklex/php-imap/src/Attachment.php", + "vendor/webklex/php-imap/src/Connection/Protocols/ImapProtocol.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "extra": { + "laravel": { + "dont-discover": [ + ] + } + }, + "scripts": { + "post-root-package-install": [ + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + ], + "post-create-project-cmd": [ + "@php artisan key:generate" + ], + "pre-install-cmd": [ + "@php -r \"@mkdir('vendor/natxet/cssmin/src', 775);\"" + ], + "post-autoload-dump": [ + "@php -r \"file_put_contents('vendor/composer/ClassLoader.php', preg_replace('/function includeFile.*/s', base64_decode('DQpmdW5jdGlvbiBpbmNsdWRlRmlsZSgkZmlsZSkNCnsNCiAgICB0cnkgew0KICAgICAgICBpbmNsdWRlICRmaWxlOw0KICAgIH0gY2F0Y2ggKFxFeGNlcHRpb24gJGUpIHsNCiAgICAgICAgJG1zZyA9ICRlLT5nZXRNZXNzYWdlKCk7DQogICAgICAgIGlmIChzdHJzdHIoJG1zZywgJ05vIHN1Y2ggZmlsZSBvciBkaXJlY3RvcnknKSkgew0KICAgICAgICAgICAgaWYgKHN0cnN0cigkbXNnLCAnL3ZlbmRvci9jb21wb3Nlci8uLi8uLi9vdmVycmlkZXMvJykpIHsNCiAgICAgICAgICAgICAgICAkbmV3X2ZpbGUgPSBzdHJfcmVwbGFjZSgnL3ZlbmRvci9jb21wb3Nlci8uLi8uLi9vdmVycmlkZXMvJywgJy92ZW5kb3IvY29tcG9zZXIvLi4vJywgJGZpbGUpOw0KICAgICAgICAgICAgfSBlbHNlIHsNCiAgICAgICAgICAgICAgICAkbmV3X2ZpbGUgPSBzdHJfcmVwbGFjZSgnL3ZlbmRvci9jb21wb3Nlci8uLi8nLCAnL3ZlbmRvci9jb21wb3Nlci8uLi8uLi9vdmVycmlkZXMvJywgJGZpbGUpOw0KICAgICAgICAgICAgfQ0KICAgICAgICAgICAgaW5jbHVkZSAkbmV3X2ZpbGU7DQogICAgICAgIH0gZWxzZSB7DQogICAgICAgICAgICB0aHJvdyAkZTsNCiAgICAgICAgfQ0KICAgIH0NCn0='), file_get_contents('vendor/composer/ClassLoader.php')));\"", + "@php -r \"!error_reporting(0) || array_map('unlink', json_decode(file_get_contents('composer.json'), true)['autoload']['exclude-from-classmap']);\"", + "@php -r \"file_put_contents('vendor/webklex/php-imap/src/config/imap.php', preg_replace('/, \\\\'inline\\\\'],/s', '/*, \\\\'inline\\\\'*/],', file_get_contents('vendor/webklex/php-imap/src/config/imap.php')));\"", + "@php -r \"copy('vendor/nesbot/carbon/src/Carbon/Lang/pt.php', 'vendor/nesbot/carbon/src/Carbon/Lang/pt_PT.php');\"", + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php artisan package:discover" + ] + }, + "config": { + "preferred-install": "dist", + "sort-packages": true, + "optimize-autoloader": true + } +} diff --git a/freescout-dist/composer.lock b/freescout-dist/composer.lock new file mode 100644 index 0000000..8c4540e --- /dev/null +++ b/freescout-dist/composer.lock @@ -0,0 +1,7233 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c7330f366803c1cd633e140c041fbbdb", + "packages": [ + { + "name": "anahkiasen/underscore-php", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/Anahkiasen/underscore-php.git", + "reference": "48f97b295c82d99c1fe10d8b0684c43f051b5580" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Anahkiasen/underscore-php/zipball/48f97b295c82d99c1fe10d8b0684c43f051b5580", + "reference": "48f97b295c82d99c1fe10d8b0684c43f051b5580", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^1.0", + "patchwork/utf8": "^1.2", + "php": ">=5.4.0" + }, + "require-dev": { + "fabpot/php-cs-fixer": "2.0.*@dev", + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Underscore\\": [ + "src", + "tests" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Fabre", + "email": "ehtnam6@gmail.com" + } + ], + "description": "A redacted port of Underscore.js for PHP", + "keywords": [ + "internals", + "laravel", + "toolkit" + ], + "support": { + "issues": "https://github.com/Anahkiasen/underscore-php/issues", + "source": "https://github.com/Anahkiasen/underscore-php/tree/develop" + }, + "abandoned": true, + "time": "2015-05-16T19:24:58+00:00" + }, + { + "name": "axn/laravel-laroute", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/AXN-Informatique/laravel-laroute.git", + "reference": "40ccb656a5ca380c4cd7de7f80667f1d6cd9aeeb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/AXN-Informatique/laravel-laroute/zipball/40ccb656a5ca380c4cd7de7f80667f1d6cd9aeeb", + "reference": "40ccb656a5ca380c4cd7de7f80667f1d6cd9aeeb", + "shasum": "" + }, + "require": { + "illuminate/support": "5.4.*|5.5.*|5.6.*", + "lord/laroute": "^2.4" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Axn\\Laroute\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Axn\\Laroute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "AXN Informatique", + "email": "developpement@axn.fr" + } + ], + "description": "Extend aaronlord/laroute package.", + "homepage": "https://github.com/AXN-Informatique/laravel-laroute", + "support": { + "issues": "https://github.com/AXN-Informatique/laravel-laroute/issues", + "source": "https://github.com/AXN-Informatique/laravel-laroute/tree/master" + }, + "time": "2018-07-04T10:38:09+00:00" + }, + { + "name": "barryvdh/laravel-translation-manager", + "version": "v0.5.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-translation-manager.git", + "reference": "857334ea6ffa2511982442495460c9c3ff096160" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-translation-manager/zipball/857334ea6ffa2511982442495460c9c3ff096160", + "reference": "857334ea6ffa2511982442495460c9c3ff096160", + "shasum": "" + }, + "require": { + "illuminate/support": "5.5.x|5.6.x|5.7.x", + "illuminate/translation": "5.5.x|5.6.x|5.7.x", + "php": ">=7", + "symfony/finder": "~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\TranslationManager\\ManagerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\TranslationManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Manage Laravel Translations", + "keywords": [ + "laravel", + "translations", + "translator" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-translation-manager/issues", + "source": "https://github.com/barryvdh/laravel-translation-manager/tree/master" + }, + "time": "2018-08-31T13:25:15+00:00" + }, + { + "name": "chumper/zipper", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Chumper/Zipper.git", + "reference": "6a1733c34d67c3952b8439afb36ad4ea5c3ceacb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Chumper/Zipper/zipball/6a1733c34d67c3952b8439afb36ad4ea5c3ceacb", + "reference": "6a1733c34d67c3952b8439afb36ad4ea5c3ceacb", + "shasum": "" + }, + "require": { + "illuminate/filesystem": "^5.0", + "illuminate/support": "^5.0", + "php": ">=5.6.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Chumper\\Zipper\\ZipperServiceProvider" + ], + "aliases": { + "Zipper": "Chumper\\Zipper\\Zipper" + } + } + }, + "autoload": { + "psr-4": { + "Chumper\\Zipper\\": "src/Chumper/Zipper" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "Nils Plaschke", + "email": "github@nilsplaschke.de", + "homepage": "http://nilsplaschke.de", + "role": "Developer" + } + ], + "description": "This is a little neat helper for the ZipArchive methods with handy functions", + "homepage": "http://github.com/Chumper/zipper", + "keywords": [ + "archive", + "laravel", + "zip" + ], + "support": { + "issues": "https://github.com/Chumper/Zipper/issues", + "source": "https://github.com/Chumper/Zipper/tree/master" + }, + "abandoned": true, + "time": "2017-07-17T08:05:10+00:00" + }, + { + "name": "codedge/laravel-selfupdater", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/codedge/laravel-selfupdater.git", + "reference": "084a1dd447d9dcd532042dfc3296b195903c2530" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codedge/laravel-selfupdater/zipball/084a1dd447d9dcd532042dfc3296b195903c2530", + "reference": "084a1dd447d9dcd532042dfc3296b195903c2530", + "shasum": "" + }, + "require": { + "ext-zip": "*", + "guzzlehttp/guzzle": "6.*", + "illuminate/support": "5.*", + "php": ">=5.5" + }, + "require-dev": { + "mockery/mockery": "^0.9.5", + "orchestra/testbench": "3.2.*", + "phpunit/phpunit": "^4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codedge\\Updater\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Holger Lösken", + "email": "holger.loesken@codedge.de", + "homepage": "http://codedge.de", + "role": "Developer" + } + ], + "description": "Providing an auto-updating functionality for your self-hosted Laravel application.", + "keywords": [ + "auto update", + "auto-update", + "laravel", + "laravel application", + "self update", + "self-hosted laravel application", + "self-update", + "update" + ], + "support": { + "issues": "https://github.com/codedge/laravel-selfupdater/issues", + "source": "https://github.com/codedge/laravel-selfupdater" + }, + "time": "2017-04-09T12:12:08+00:00" + }, + { + "name": "devfactory/minify", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DevFactoryCH/minify.git", + "reference": "95d518081aa29c91b8c89243c61a7b1fc726120e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DevFactoryCH/minify/zipball/95d518081aa29c91b8c89243c61a7b1fc726120e", + "reference": "95d518081aa29c91b8c89243c61a7b1fc726120e", + "shasum": "" + }, + "require": { + "illuminate/filesystem": "~5.0", + "illuminate/support": "~5.0", + "natxet/cssmin": "3.*", + "php": ">=5.4.0", + "tedivm/jshrink": "~1.0" + }, + "require-dev": { + "mikey179/vfsstream": "1.2.*", + "phpspec/phpspec": "2.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Devfactory\\Minify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Da Costa Alcindo", + "email": "alcindo.dacosta@devfactory.ch" + } + ], + "description": "A package for minifying styles and javascript for laravel 5 ", + "keywords": [ + "laravel5", + "minify" + ], + "support": { + "issues": "https://github.com/DevFactoryCH/minify/issues", + "source": "https://github.com/DevFactoryCH/minify/tree/master" + }, + "time": "2016-01-04T12:36:52+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/cache", + "version": "v1.6.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/eb152c5100571c7a45470ff2a35095ab3f3b900b", + "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b", + "shasum": "" + }, + "require": { + "php": "~5.5|~7.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|~5.0", + "predis/predis": "~1.0", + "satooshi/php-coveralls": "~0.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/1.6.x" + }, + "time": "2017-07-22T12:49:21+00:00" + }, + { + "name": "doctrine/dbal", + "version": "2.12.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "adce7a954a1c2f14f85e94aed90c8489af204086" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/adce7a954a1c2f14f85e94aed90c8489af204086", + "reference": "adce7a954a1c2f14f85e94aed90c8489af204086", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.0", + "doctrine/event-manager": "^1.0", + "ext-pdo": "*", + "php": "^7.3 || ^8" + }, + "require-dev": { + "doctrine/coding-standard": "^8.1", + "jetbrains/phpstorm-stubs": "^2019.1", + "phpstan/phpstan": "^0.12.40", + "phpunit/phpunit": "^9.4", + "psalm/plugin-phpunit": "^0.10.0", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", + "vimeo/psalm": "^3.17.2" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlanywhere", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/2.12.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2020-11-14T20:26:58+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" + }, + "time": "2023-06-03T09:27:29+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/95aa4cb529f1e96576f3fda9f5705ada4056a520", + "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^0.5.3 || ^1", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.8", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.24" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.2.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2022-10-12T20:51:15+00:00" + }, + { + "name": "doctrine/inflector", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/e11d84c6e018beedd929cff5220969a3c6d1d462", + "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ], + "support": { + "source": "https://github.com/doctrine/inflector/tree/master" + }, + "time": "2017-07-22T12:18:28+00:00" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "support": { + "source": "https://github.com/doctrine/lexer/tree/master" + }, + "time": "2014-09-09T13:34:57+00:00" + }, + { + "name": "egulias/email-validator", + "version": "2.1.10", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "a6c8d7101b19a451c1707b1b79bbbc56e4bdb7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/a6c8d7101b19a451c1707b1b79bbbc56e4bdb7ec", + "reference": "a6c8d7101b19a451c1707b1b79bbbc56e4bdb7ec", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1.0.1", + "php": ">= 5.5" + }, + "require-dev": { + "dominicsayers/isemail": "dev-master", + "phpunit/phpunit": "^4.8.35||^5.7||^6.0", + "satooshi/php-coveralls": "^1.0.1", + "symfony/phpunit-bridge": "^4.4@dev" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "EmailValidator" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/2.1.10" + }, + "time": "2019-07-19T20:52:08+00:00" + }, + { + "name": "enshrined/svg-sanitize", + "version": "0.15.4", + "source": { + "type": "git", + "url": "https://github.com/darylldoyle/svg-sanitizer.git", + "reference": "e50b83a2f1f296ca61394fe88fbfe3e896a84cf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/e50b83a2f1f296ca61394fe88fbfe3e896a84cf4", + "reference": "e50b83a2f1f296ca61394fe88fbfe3e896a84cf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5 || ^8.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "enshrined\\svgSanitize\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Daryll Doyle", + "email": "daryll@enshrined.co.uk" + } + ], + "description": "An SVG sanitizer for PHP", + "support": { + "issues": "https://github.com/darylldoyle/svg-sanitizer/issues", + "source": "https://github.com/darylldoyle/svg-sanitizer/tree/0.15.4" + }, + "time": "2022-02-21T09:13:59+00:00" + }, + { + "name": "erusev/parsedown", + "version": "v1.7.2", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/d60bcdc46978357759ecb13cb4b078da783f8faf", + "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/1.7.x" + }, + "time": "2019-03-17T17:19:46+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.12.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "a617e55bc62a87eec73bd456d146d134ad716f03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/a617e55bc62a87eec73bd456d146d134ad716f03", + "reference": "a617e55bc62a87eec73bd456d146d134ad716f03", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "simpletest/simpletest": "dev-master#72de02a7b80c6bb8864ef9bf66d41d2f58f826bd" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/master" + }, + "time": "2019-10-28T03:44:26+00:00" + }, + { + "name": "fideloper/proxy", + "version": "3.3.4", + "source": { + "type": "git", + "url": "https://github.com/fideloper/TrustedProxy.git", + "reference": "9cdf6f118af58d89764249bbcc7bb260c132924f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/9cdf6f118af58d89764249bbcc7bb260c132924f", + "reference": "9cdf6f118af58d89764249bbcc7bb260c132924f", + "shasum": "" + }, + "require": { + "illuminate/contracts": "~5.0", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/http": "~5.0", + "mockery/mockery": "~0.9.3", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + }, + "laravel": { + "providers": [ + "Fideloper\\Proxy\\TrustedProxyServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Fideloper\\Proxy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Fidao", + "email": "fideloper@gmail.com" + } + ], + "description": "Set trusted proxies for Laravel", + "keywords": [ + "load balancing", + "proxy", + "trusted proxy" + ], + "support": { + "issues": "https://github.com/fideloper/TrustedProxy/issues", + "source": "https://github.com/fideloper/TrustedProxy/tree/master" + }, + "time": "2017-06-15T17:19:42+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.5.8", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a52f0440530b54fa079ce76e8c5d196a42cad981", + "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.9", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.17" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "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": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "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" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/6.5.8" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-06-20T22:16:07+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e", + "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "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": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-05-21T12:31:43+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b", + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "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" + } + ], + "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/1.9.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": "2023-04-17T16:00:37+00:00" + }, + { + "name": "html2text/html2text", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/mtibben/html2text.git", + "reference": "bf613dc8d3389d70c891cd847954346acab16574" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mtibben/html2text/zipball/bf613dc8d3389d70c891cd847954346acab16574", + "reference": "bf613dc8d3389d70c891cd847954346acab16574", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-mbstring": "For best performance", + "symfony/polyfill-mbstring": "If you can't install ext-mbstring" + }, + "type": "library", + "autoload": { + "psr-4": { + "Html2Text\\": [ + "src/", + "test/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPLv2" + ], + "description": "Converts HTML to formatted plain text", + "support": { + "issues": "https://github.com/mtibben/html2text/issues", + "source": "https://github.com/mtibben/html2text/tree/master" + }, + "time": "2017-05-15T01:50:59+00:00" + }, + { + "name": "jakub-onderka/php-console-color", + "version": "v0.2", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191", + "reference": "d5deaecff52a0d61ccb613bb3804088da0307191", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "1.0", + "jakub-onderka/php-parallel-lint": "1.0", + "jakub-onderka/php-var-dump-check": "0.*", + "phpunit/phpunit": "~4.3", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "JakubOnderka\\PhpConsoleColor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "support": { + "issues": "https://github.com/JakubOnderka/PHP-Console-Color/issues", + "source": "https://github.com/JakubOnderka/PHP-Console-Color/tree/master" + }, + "abandoned": "php-parallel-lint/php-console-color", + "time": "2018-09-29T17:23:10+00:00" + }, + { + "name": "jakub-onderka/php-console-highlighter", + "version": "v0.4", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", + "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/9f7a229a69d52506914b4bc61bfdb199d90c5547", + "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "jakub-onderka/php-console-color": "~0.2", + "php": ">=5.4.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "~1.0", + "jakub-onderka/php-parallel-lint": "~1.0", + "jakub-onderka/php-var-dump-check": "~0.1", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "JakubOnderka\\PhpConsoleHighlighter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "description": "Highlight PHP code in terminal", + "support": { + "issues": "https://github.com/JakubOnderka/PHP-Console-Highlighter/issues", + "source": "https://github.com/JakubOnderka/PHP-Console-Highlighter/tree/master" + }, + "abandoned": "php-parallel-lint/php-console-highlighter", + "time": "2018-09-29T18:48:56+00:00" + }, + { + "name": "javoscript/laravel-macroable-models", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/javoscript/laravel-macroable-models.git", + "reference": "2ade1984f33362c0a304da6352f95c407d5c761f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/javoscript/laravel-macroable-models/zipball/2ade1984f33362c0a304da6352f95c407d5c761f", + "reference": "2ade1984f33362c0a304da6352f95c407d5c761f", + "shasum": "" + }, + "require-dev": { + "orchestra/testbench": "^4.6", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Javoscript\\MacroableModels\\MacroableModelsServiceProvider" + ], + "aliases": { + "MacroableModels": "Javoscript\\MacroableModels\\Facades\\MacroableModels" + } + } + }, + "autoload": { + "psr-4": { + "Javoscript\\MacroableModels\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Javier Ugarte", + "email": "javougarte@gmail.com" + } + ], + "description": "A package for adding methods to Laravel models on the fly", + "support": { + "issues": "https://github.com/javoscript/laravel-macroable-models/issues", + "source": "https://github.com/javoscript/laravel-macroable-models/tree/1.0.4" + }, + "time": "2020-03-10T01:19:26+00:00" + }, + { + "name": "laravel/framework", + "version": "v5.5.40", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "d724ce0aa61bbd9adf658215eec484f5dd6711d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/d724ce0aa61bbd9adf658215eec484f5dd6711d6", + "reference": "d724ce0aa61bbd9adf658215eec484f5dd6711d6", + "shasum": "" + }, + "require": { + "doctrine/inflector": "~1.1", + "erusev/parsedown": "~1.7", + "ext-mbstring": "*", + "ext-openssl": "*", + "league/flysystem": "^1.0.8", + "monolog/monolog": "~1.12", + "mtdowling/cron-expression": "~1.0", + "nesbot/carbon": "^1.24.1", + "php": ">=7.0", + "psr/container": "~1.0", + "psr/simple-cache": "^1.0", + "ramsey/uuid": "~3.0", + "swiftmailer/swiftmailer": "~6.0", + "symfony/console": "~3.3", + "symfony/debug": "~3.3", + "symfony/finder": "~3.3", + "symfony/http-foundation": "~3.3", + "symfony/http-kernel": "~3.3", + "symfony/process": "~3.3", + "symfony/routing": "~3.3", + "symfony/var-dumper": "~3.3", + "tijsverkoyen/css-to-inline-styles": "~2.2", + "vlucas/phpdotenv": "~2.2" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "tightenco/collect": "<5.5.33" + }, + "require-dev": { + "aws/aws-sdk-php": "~3.0", + "doctrine/dbal": "~2.5", + "filp/whoops": "^2.1.4", + "mockery/mockery": "~1.0", + "orchestra/testbench-core": "3.5.*", + "pda/pheanstalk": "~3.0", + "phpunit/phpunit": "~6.0", + "predis/predis": "^1.1.1", + "symfony/css-selector": "~3.3", + "symfony/dom-crawler": "~3.3" + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.5).", + "ext-pcntl": "Required to use all features of the queue worker.", + "ext-posix": "Required to use all features of the queue worker.", + "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).", + "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~6.0).", + "laravel/tinker": "Required to use the tinker console command (~1.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", + "league/flysystem-cached-adapter": "Required to use Flysystem caching (~1.0).", + "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).", + "nexmo/client": "Required to use the Nexmo transport (~1.0).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).", + "predis/predis": "Required to use the redis cache and queue drivers (~1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~3.0).", + "symfony/css-selector": "Required to use some of the crawler integration testing tools (~3.3).", + "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (~3.3).", + "symfony/psr-http-message-bridge": "Required to psr7 bridging features (~1.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.5-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2018-03-30T13:29:30+00:00" + }, + { + "name": "laravel/tinker", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "e3086ee8cb1f54a39ae8dcb72d1c37d10128997d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/e3086ee8cb1f54a39ae8dcb72d1c37d10128997d", + "reference": "e3086ee8cb1f54a39ae8dcb72d1c37d10128997d", + "shasum": "" + }, + "require": { + "illuminate/console": "~5.1", + "illuminate/contracts": "~5.1", + "illuminate/support": "~5.1", + "php": ">=5.5.9", + "psy/psysh": "0.7.*|0.8.*|0.9.*", + "symfony/var-dumper": "~3.0|~4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (~5.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v1.0.7" + }, + "time": "2018-05-17T13:42:07+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.4" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2021-06-23T21:56:05+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/a6dfb1194a2946fcdc1f38219445234f65b35c96", + "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.13.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2023-08-05T12:09:49+00:00" + }, + { + "name": "lord/laroute", + "version": "2.4.7", + "source": { + "type": "git", + "url": "https://github.com/aaronlord/laroute.git", + "reference": "915501506ee5dfd07b9f9716537a16e1c66d4125" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aaronlord/laroute/zipball/915501506ee5dfd07b9f9716537a16e1c66d4125", + "reference": "915501506ee5dfd07b9f9716537a16e1c66d4125", + "shasum": "" + }, + "require": { + "illuminate/config": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*", + "illuminate/console": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*", + "illuminate/filesystem": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*", + "illuminate/routing": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*", + "illuminate/support": "5.0.*|5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "dev-master", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lord\\Laroute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Lord", + "email": "hello@aaronlord.is" + } + ], + "description": "Access Laravels URL/Route helper functions, from JavaScript.", + "keywords": [ + "javascript", + "laravel", + "routes", + "routing" + ], + "support": { + "issues": "https://github.com/aaronlord/laroute/issues", + "source": "https://github.com/aaronlord/laroute/tree/master" + }, + "time": "2018-02-10T01:17:07+00:00" + }, + { + "name": "mews/purifier", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/mewebstudio/Purifier.git", + "reference": "75e4d9a0553b31c1fd31aef65f9561c30dbe5e5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mewebstudio/Purifier/zipball/75e4d9a0553b31c1fd31aef65f9561c30dbe5e5e", + "reference": "75e4d9a0553b31c1fd31aef65f9561c30dbe5e5e", + "shasum": "" + }, + "require": { + "ezyang/htmlpurifier": "4.12.*", + "illuminate/config": "^5.1|^6.0|^7.0", + "illuminate/filesystem": "^5.1|^6.0|^7.0", + "illuminate/support": "^5.1|^6.0|^7.0", + "php": "^7.2" + }, + "require-dev": { + "graham-campbell/testbench": "^3.2", + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "^4.8|^5.0", + "scrutinizer/ocular": "^1.3" + }, + "suggest": { + "laravel/framework": "To test the Laravel bindings", + "laravel/lumen-framework": "To test the Lumen bindings" + }, + "type": "package", + "extra": { + "laravel": { + "providers": [ + "Mews\\Purifier\\PurifierServiceProvider" + ], + "aliases": { + "Purifier": "Mews\\Purifier\\Facades\\Purifier" + } + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Mews\\Purifier\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Muharrem ERİN", + "email": "me@mewebstudio.com", + "homepage": "https://github.com/mewebstudio", + "role": "Developer" + } + ], + "description": "Laravel 5/6/7 HtmlPurifier Package", + "homepage": "https://github.com/mewebstudio/purifier", + "keywords": [ + "Purifier", + "htmlpurifier", + "laravel5 HtmlPurifier", + "laravel5 Purifier", + "laravel5 Security", + "laravel6 HtmlPurifier", + "laravel6 Purifier", + "laravel6 Security", + "security", + "xss" + ], + "support": { + "issues": "https://github.com/mewebstudio/Purifier/issues", + "source": "https://github.com/mewebstudio/Purifier/tree/3.2.2" + }, + "time": "2020-04-10T19:42:16+00:00" + }, + { + "name": "monolog/monolog", + "version": "1.27.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "904713c5929655dc9b97288b69cfeedad610c9a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/904713c5929655dc9b97288b69cfeedad610c9a1", + "reference": "904713c5929655dc9b97288b69cfeedad610c9a1", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpstan/phpstan": "^0.12.59", + "phpunit/phpunit": "~4.5", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/1.27.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2022-06-09T08:53:42+00:00" + }, + { + "name": "mtdowling/cron-expression", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/mtdowling/cron-expression.git", + "reference": "9be552eebcc1ceec9776378f7dcc085246cacca6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/9be552eebcc1ceec9776378f7dcc085246cacca6", + "reference": "9be552eebcc1ceec9776378f7dcc085246cacca6", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/mtdowling/cron-expression/issues", + "source": "https://github.com/mtdowling/cron-expression/tree/v1.2.3" + }, + "abandoned": "dragonmantank/cron-expression", + "time": "2019-12-28T04:23:06+00:00" + }, + { + "name": "natxet/cssmin", + "version": "v3.0.6", + "source": { + "type": "git", + "url": "https://github.com/natxet/CssMin.git", + "reference": "d5d9f4c3e5cedb1ae96a95a21731f8790e38f1dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/natxet/CssMin/zipball/d5d9f4c3e5cedb1ae96a95a21731f8790e38f1dd", + "reference": "d5d9f4c3e5cedb1ae96a95a21731f8790e38f1dd", + "shasum": "" + }, + "require": { + "php": ">=5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joe Scylla", + "email": "joe.scylla@gmail.com", + "homepage": "https://profiles.google.com/joe.scylla" + } + ], + "description": "Minifying CSS", + "homepage": "http://code.google.com/p/cssmin/", + "keywords": [ + "css", + "minify" + ], + "support": { + "issues": "https://github.com/natxet/CssMin/issues", + "source": "https://github.com/natxet/CssMin/tree/master" + }, + "time": "2018-01-09T11:15:01+00:00" + }, + { + "name": "nesbot/carbon", + "version": "1.35.1", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "5c05a2be472b22f63291d192410df9f0e0de3b19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/5c05a2be472b22f63291d192410df9f0e0de3b19", + "reference": "5c05a2be472b22f63291d192410df9f0e0de3b19", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/translation": "~2.6 || ~3.0 || ~4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7" + }, + "suggest": { + "friendsofphp/php-cs-fixer": "Needed for the `composer phpcs` command. Allow to automatically fix code style.", + "phpstan/phpstan": "Needed for the `composer phpstan` command. Allow to detect potential errors." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "time": "2018-11-14T21:55:58+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.17.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + }, + "time": "2023-08-13T19:53:39+00:00" + }, + { + "name": "nwidart/laravel-modules", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/nWidart/laravel-modules.git", + "reference": "30c1475f99ef8b439a1cf7d8a9c55787c80bbf71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/30c1475f99ef8b439a1cf7d8a9c55787c80bbf71", + "reference": "30c1475f99ef8b439a1cf7d8a9c55787c80bbf71", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.7", + "laravel/framework": "5.5.*", + "mockery/mockery": "~1.0", + "orchestra/testbench": "^3.5", + "phpunit/phpunit": "~6.0", + "spatie/phpunit-snapshot-assertions": "^1.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Nwidart\\Modules\\LaravelModulesServiceProvider" + ], + "aliases": { + "Module": "Nwidart\\Modules\\Facades\\Module" + } + }, + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Nwidart\\Modules\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Widart", + "email": "n.widart@gmail.com", + "homepage": "https://nicolaswidart.com", + "role": "Developer" + } + ], + "description": "Laravel Module management", + "keywords": [ + "laravel", + "module", + "modules", + "nwidart", + "rad" + ], + "support": { + "issues": "https://github.com/nWidart/laravel-modules/issues", + "source": "https://github.com/nWidart/laravel-modules/tree/master" + }, + "time": "2018-01-13T20:11:46+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": "patchwork/utf8", + "version": "v1.3.3", + "source": { + "type": "git", + "url": "https://github.com/tchwork/utf8.git", + "reference": "e1fa4d4a57896d074c9a8d01742b688d5db4e9d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tchwork/utf8/zipball/e1fa4d4a57896d074c9a8d01742b688d5db4e9d5", + "reference": "e1fa4d4a57896d074c9a8d01742b688d5db4e9d5", + "shasum": "" + }, + "require": { + "lib-pcre": ">=7.3", + "php": ">=5.3.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.4|^4.4" + }, + "suggest": { + "ext-iconv": "Use iconv for best performance", + "ext-intl": "Use Intl for best performance", + "ext-mbstring": "Use Mbstring for best performance", + "ext-wfio": "Use WFIO for UTF-8 filesystem access on Windows" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Patchwork\\": "src/Patchwork/" + }, + "classmap": [ + "src/Normalizer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "(Apache-2.0 or GPL-2.0)" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + } + ], + "description": "Portable and performant UTF-8, Unicode and Grapheme Clusters for PHP", + "homepage": "https://github.com/tchwork/utf8", + "keywords": [ + "grapheme", + "i18n", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "issues": "https://github.com/tchwork/utf8/issues", + "source": "https://github.com/tchwork/utf8/tree/v1.3.3" + }, + "funding": [ + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/patchwork/utf8", + "type": "tidelift" + } + ], + "abandoned": "symfony/polyfill-mbstring or symfony/string", + "time": "2021-01-07T16:38:58+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/master" + }, + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/master" + }, + "time": "2016-10-10T12:19:37+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.9.12", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "90da7f37568aee36b116a030c5f99c915267edd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/90da7f37568aee36b116a030c5f99c915267edd4", + "reference": "90da7f37568aee36b116a030c5f99c915267edd4", + "shasum": "" + }, + "require": { + "dnoegel/php-xdg-base-dir": "0.1.*", + "ext-json": "*", + "ext-tokenizer": "*", + "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*", + "nikic/php-parser": "~1.3|~2.0|~3.0|~4.0", + "php": ">=5.4.0", + "symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0|~5.0", + "symfony/var-dumper": "~2.7|~3.0|~4.0|~5.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "hoa/console": "~2.15|~3.16", + "phpunit/phpunit": "~4.8.35|~5.0|~6.0|~7.0" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", + "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.9.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.9.12" + }, + "time": "2019-12-06T14:19:43+00:00" + }, + { + "name": "rachidlaasri/laravel-installer", + "version": "4.0.2", + "source": { + "type": "git", + "url": "https://github.com/RachidLaasri/LaravelInstaller.git", + "reference": "8063eda959fdee8b337e6ea543603e169e843897" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RachidLaasri/LaravelInstaller/zipball/8063eda959fdee8b337e6ea543603e169e843897", + "reference": "8063eda959fdee8b337e6ea543603e169e843897", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "RachidLaasri\\LaravelInstaller\\Providers\\LaravelInstallerServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Helpers/functions.php" + ], + "psr-4": { + "RachidLaasri\\LaravelInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rachid Laasri", + "email": "rashidlaasri@gmail.com" + }, + { + "name": "Jeremy Kenedy", + "email": "jeremykenedy@gmail.com" + } + ], + "description": "Laravel web installer", + "support": { + "issues": "https://github.com/RachidLaasri/LaravelInstaller/issues", + "source": "https://github.com/RachidLaasri/LaravelInstaller/tree/master" + }, + "time": "2018-04-30T18:57:04+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/uuid", + "version": "3.9.6", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "ffa80ab953edd85d5b6c004f96181a538aad35a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/ffa80ab953edd85d5b6c004f96181a538aad35a3", + "reference": "ffa80ab953edd85d5b6c004f96181a538aad35a3", + "shasum": "" + }, + "require": { + "ext-json": "*", + "paragonie/random_compat": "^1 | ^2 | ^9.99.99", + "php": "^5.4 | ^7.0 | ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1 | ^2", + "doctrine/annotations": "^1.2", + "goaop/framework": "1.0.0-alpha.2 | ^1 | >=2.1.0 <=2.3.2", + "mockery/mockery": "^0.9.11 | ^1", + "moontoast/math": "^1.1", + "nikic/php-parser": "<=4.5.0", + "paragonie/random-lib": "^2", + "php-mock/php-mock-phpunit": "^0.3 | ^1.1 | ^2.6", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpunit/phpunit": ">=4.8.36 <9.0.0 | >=9.3.0", + "squizlabs/php_codesniffer": "^3.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "rss": "https://github.com/ramsey/uuid/releases.atom", + "source": "https://github.com/ramsey/uuid", + "wiki": "https://github.com/ramsey/uuid/wiki" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2021-09-25T23:07:42+00:00" + }, + { + "name": "rap2hpoutre/laravel-log-viewer", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/rap2hpoutre/laravel-log-viewer.git", + "reference": "4fa9b45738095fc5febeed6e44f1516e93a8de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rap2hpoutre/laravel-log-viewer/zipball/4fa9b45738095fc5febeed6e44f1516e93a8de8c", + "reference": "4fa9b45738095fc5febeed6e44f1516e93a8de8c", + "shasum": "" + }, + "require": { + "illuminate/support": "4.2.*|5.*|^6.0|^7.0|^8.0", + "php": ">=5.4.0" + }, + "require-dev": { + "orchestra/testbench": "3.7.*", + "phpunit/phpunit": "^7" + }, + "type": "laravel-package", + "extra": { + "laravel": { + "providers": [ + "Rap2hpoutre\\LaravelLogViewer\\LaravelLogViewerServiceProvider" + ] + } + }, + "autoload": { + "psr-0": { + "Rap2hpoutre\\LaravelLogViewer\\": "src/" + }, + "classmap": [ + "src/controllers" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "rap2hpoutre", + "email": "raphaelht@gmail.com" + } + ], + "description": "A Laravel log reader", + "keywords": [ + "laravel", + "log", + "log-reader", + "log-viewer", + "logging", + "lumen" + ], + "support": { + "issues": "https://github.com/rap2hpoutre/laravel-log-viewer/issues", + "source": "https://github.com/rap2hpoutre/laravel-log-viewer/tree/v2.0.0" + }, + "time": "2021-11-04T12:40:05+00:00" + }, + { + "name": "spatie/laravel-activitylog", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-activitylog.git", + "reference": "e354e84cee54565d59873dfd5826c57fff338c57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/e354e84cee54565d59873dfd5826c57fff338c57", + "reference": "e354e84cee54565d59873dfd5826c57fff338c57", + "shasum": "" + }, + "require": { + "illuminate/config": "~5.5.0|~5.6.0", + "illuminate/database": "~5.5.0|~5.6.0", + "illuminate/support": "~5.5.0|~5.6.0", + "php": "^7.0", + "spatie/string": "^2.1" + }, + "require-dev": { + "orchestra/testbench": "~3.5.0|~3.6.0", + "phpunit/phpunit": "^6.2|^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Activitylog\\ActivitylogServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Activitylog\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A very simple activity logger to monitor the users of your website or application", + "homepage": "https://github.com/spatie/activitylog", + "keywords": [ + "activity", + "laravel", + "log", + "spatie", + "user" + ], + "support": { + "issues": "https://github.com/spatie/laravel-activitylog/issues", + "source": "https://github.com/spatie/laravel-activitylog/tree/master" + }, + "time": "2018-06-18T09:59:53+00:00" + }, + { + "name": "spatie/string", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/string.git", + "reference": "79ed501c8d624fb85bf71da4254e1878fb616c51" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/string/zipball/79ed501c8d624fb85bf71da4254e1878fb616c51", + "reference": "79ed501c8d624fb85bf71da4254e1878fb616c51", + "shasum": "" + }, + "require": { + "anahkiasen/underscore-php": "^2.0", + "php": "^7.0|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "autoload": { + "files": [ + "src/string_functions.php" + ], + "psr-4": { + "Spatie\\String\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "String handling evolved", + "homepage": "https://github.com/spatie/string", + "keywords": [ + "handling", + "handy", + "spatie", + "string" + ], + "support": { + "issues": "https://github.com/spatie/string/issues", + "source": "https://github.com/spatie/string/tree/2.2.3" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2020-11-28T22:24:20+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v6.1.3", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8ddcb66ac10c392d3beb54829eef8ac1438595f4", + "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4", + "shasum": "" + }, + "require": { + "egulias/email-validator": "~2.0", + "php": ">=7.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.3@dev" + }, + "suggest": { + "ext-intl": "Needed to support internationalized email addresses", + "true/punycode": "Needed to support internationalized email addresses, if ext-intl is not installed" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.1-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "support": { + "issues": "https://github.com/swiftmailer/swiftmailer/issues", + "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.1.3" + }, + "abandoned": "symfony/mailer", + "time": "2018-09-11T07:12:52+00:00" + }, + { + "name": "symfony/console", + "version": "v3.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "a10b1da6fc93080c180bba7219b5ff5b7518fe81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/a10b1da6fc93080c180bba7219b5ff5b7518fe81", + "reference": "a10b1da6fc93080c180bba7219b5ff5b7518fe81", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.3|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/console/tree/v3.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T10:57:07+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v3.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "3503415d4aafabc31cd08c3a4ebac7f43fde8feb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/3503415d4aafabc31cd08c3a4ebac7f43fde8feb", + "reference": "3503415d4aafabc31cd08c3a4ebac7f43fde8feb", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/3.4" + }, + "time": "2018-10-02T16:33:53+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "fe9793af008b651c5441bdeab21ede8172dab097" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/fe9793af008b651c5441bdeab21ede8172dab097", + "reference": "fe9793af008b651c5441bdeab21ede8172dab097", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug/tree/3.4" + }, + "abandoned": "symfony/error-handler", + "time": "2018-10-31T09:06:03+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "db9e829c8f34c3d35cf37fcd4cdb4293bc4a2f14" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/db9e829c8f34c3d35cf37fcd4cdb4293bc4a2f14", + "reference": "db9e829c8f34c3d35cf37fcd4cdb4293bc4a2f14", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/3.4" + }, + "time": "2018-10-30T16:50:50+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "54ba444dddc5bd5708a34bd095ea67c6eb54644d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/54ba444dddc5bd5708a34bd095ea67c6eb54644d", + "reference": "54ba444dddc5bd5708a34bd095ea67c6eb54644d", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/3.4" + }, + "time": "2018-10-03T08:46:40+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v3.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "5aea7a86ca3203dd7a257e765b4b9c9cfd01c6c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5aea7a86ca3203dd7a257e765b4b9c9cfd01c6c0", + "reference": "5aea7a86ca3203dd7a257e765b4b9c9cfd01c6c0", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/3.4" + }, + "time": "2018-10-31T08:57:11+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v3.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "4bf0be7c7fe63eff6a5eae2f21c83e77e31a56fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4bf0be7c7fe63eff6a5eae2f21c83e77e31a56fb", + "reference": "4bf0be7c7fe63eff6a5eae2f21c83e77e31a56fb", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.4.10|<4.0.10,>=4", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0|~4.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/console": "~2.8|~3.0|~4.0", + "symfony/css-selector": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "^3.4.10|^4.0.10", + "symfony/dom-crawler": "~2.8|~3.0|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/process": "~2.8|~3.0|~4.0", + "symfony/routing": "~3.4|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0", + "symfony/templating": "~2.8|~3.0|~4.0", + "symfony/translation": "~2.8|~3.0|~4.0", + "symfony/var-dumper": "~3.3|~4.0" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/3.4" + }, + "time": "2018-11-03T10:03:02+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.9.0" + }, + "time": "2018-08-06T14:22:27+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:30:37+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/6b88000cdd431cd2e940caa2cb569201f3f84224", + "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php70/tree/master" + }, + "time": "2018-09-21T06:26:08+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, + { + "name": "symfony/process", + "version": "v3.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "35c2914a9f50519bd207164c353ae4d59182c2cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/35c2914a9f50519bd207164c353ae4d59182c2cb", + "reference": "35c2914a9f50519bd207164c353ae4d59182c2cb", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/3.4" + }, + "time": "2018-10-14T17:33:21+00:00" + }, + { + "name": "symfony/routing", + "version": "v3.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "585f6e2d740393d546978769dd56e496a6233e0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/585f6e2d740393d546978769dd56e496a6233e0b", + "reference": "585f6e2d740393d546978769dd56e496a6233e0b", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/config": "<3.3.1", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "psr/log": "~1.0", + "symfony/config": "^3.3.1|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/3.4" + }, + "time": "2018-10-02T12:28:39+00:00" + }, + { + "name": "symfony/translation", + "version": "v3.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "94bc3a79008e6640defedf5e14eb3b4f20048352" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/94bc3a79008e6640defedf5e14eb3b4f20048352", + "reference": "94bc3a79008e6640defedf5e14eb3b4f20048352", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/intl": "^2.8.18|^3.2.5|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/3.4" + }, + "time": "2018-10-02T16:33:53+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v3.4.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "ff8ac19e97e5c7c3979236b584719a1190f84181" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ff8ac19e97e5c7c3979236b584719a1190f84181", + "reference": "ff8ac19e97e5c7c3979236b584719a1190f84181", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + }, + "require-dev": { + "ext-iconv": "*", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/3.4" + }, + "time": "2018-10-02T16:33:53+00:00" + }, + { + "name": "tedivm/jshrink", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/tedious/JShrink.git", + "reference": "0513ba1407b1f235518a939455855e6952a48bbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tedious/JShrink/zipball/0513ba1407b1f235518a939455855e6952a48bbc", + "reference": "0513ba1407b1f235518a939455855e6952a48bbc", + "shasum": "" + }, + "require": { + "php": "^5.6|^7.0|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.8", + "php-coveralls/php-coveralls": "^1.1.0", + "phpunit/phpunit": "^6" + }, + "type": "library", + "autoload": { + "psr-0": { + "JShrink": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Robert Hafner", + "email": "tedivm@tedivm.com" + } + ], + "description": "Javascript Minifier built in PHP", + "homepage": "http://github.com/tedious/JShrink", + "keywords": [ + "javascript", + "minifier" + ], + "support": { + "issues": "https://github.com/tedious/JShrink/issues", + "source": "https://github.com/tedious/JShrink/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/tedivm/jshrink", + "type": "tidelift" + } + ], + "time": "2020-11-30T18:10:21+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "2.2.6", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/c42125b83a4fa63b187fdf29f9c93cb7733da30c", + "reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.5 || ^7.0 || ^8.0", + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.6" + }, + "time": "2023-01-03T09:29:04+00:00" + }, + { + "name": "tormjens/eventy", + "version": "0.5.4", + "source": { + "type": "git", + "url": "https://github.com/tormjens/eventy.git", + "reference": "ed48fe39107168c21199c8abfee558cd3370d971" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tormjens/eventy/zipball/ed48fe39107168c21199c8abfee558cd3370d971", + "reference": "ed48fe39107168c21199c8abfee558cd3370d971", + "shasum": "" + }, + "require": { + "illuminate/support": ">=5.3", + "php": ">=5.6.4" + }, + "require-dev": { + "phpunit/phpunit": ">=5.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "TorMorten\\Eventy\\EventServiceProvider", + "TorMorten\\Eventy\\EventBladeServiceProvider" + ], + "aliases": { + "Eventy": "TorMorten\\Eventy\\Facades\\Events" + } + } + }, + "autoload": { + "psr-4": { + "TorMorten\\Eventy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tor Morten Jensen", + "homepage": "https://tormorten.no" + } + ], + "description": "The WordPress filter/action system in Laravel", + "homepage": "https://github.com/tormjens/eventy", + "keywords": [ + "HOOK", + "action", + "actions", + "event", + "events", + "filter", + "filters", + "hooks", + "laravel", + "wordpress" + ], + "support": { + "issues": "https://github.com/tormjens/eventy/issues", + "source": "https://github.com/tormjens/eventy/tree/master" + }, + "time": "2018-03-14T13:04:12+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/master" + }, + "time": "2018-07-29T20:33:41+00:00" + }, + { + "name": "watson/rememberable", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/dwightwatson/rememberable.git", + "reference": "8718614835370f67f58d86387a26bfc315fa9a8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dwightwatson/rememberable/zipball/8718614835370f67f58d86387a26bfc315fa9a8b", + "reference": "8718614835370f67f58d86387a26bfc315fa9a8b", + "shasum": "" + }, + "require": { + "illuminate/database": "~5.0", + "illuminate/support": "~5.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "4.2.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Watson\\Rememberable\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dwight Watson", + "email": "dwight@studiousapp.com" + } + ], + "description": "Query caching for Laravel 5", + "keywords": [ + "caching", + "eloquent", + "laravel", + "remember" + ], + "support": { + "issues": "https://github.com/dwightwatson/rememberable/issues", + "source": "https://github.com/dwightwatson/rememberable/tree/master" + }, + "time": "2017-12-21T01:09:18+00:00" + }, + { + "name": "webklex/laravel-imap", + "version": "1.2.7", + "source": { + "type": "git", + "url": "https://github.com/Webklex/laravel-imap.git", + "reference": "4a0e0af8b0f4b293abe82fac0f1323b19a7f6969" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Webklex/laravel-imap/zipball/4a0e0af8b0f4b293abe82fac0f1323b19a7f6969", + "reference": "4a0e0af8b0f4b293abe82fac0f1323b19a7f6969", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "ext-iconv": "*", + "ext-imap": "*", + "ext-mbstring": "*", + "laravel/framework": ">=5.0.0", + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "laravel": { + "providers": [ + "Webklex\\IMAP\\Providers\\LaravelServiceProvider" + ], + "aliases": { + "Client": "Webklex\\IMAP\\Facades\\Client" + } + } + }, + "autoload": { + "psr-4": { + "Webklex\\IMAP\\": "src/IMAP" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Malte Goldenbaum", + "email": "github@webklex.com", + "role": "Developer" + } + ], + "description": "Laravel IMAP client", + "homepage": "https://github.com/webklex/laravel-imap", + "keywords": [ + "imap", + "laravel", + "laravel-imap", + "mail", + "webklex" + ], + "support": { + "issues": "https://github.com/Webklex/laravel-imap/issues", + "source": "https://github.com/Webklex/laravel-imap/tree/master" + }, + "time": "2018-08-06T19:26:50+00:00" + }, + { + "name": "webklex/php-imap", + "version": "4.1.1", + "source": { + "type": "git", + "url": "https://github.com/Webklex/php-imap.git", + "reference": "5415d520d235ce8585e7f3c03c2b937bd8662a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Webklex/php-imap/zipball/5415d520d235ce8585e7f3c03c2b937bd8662a3e", + "reference": "5415d520d235ce8585e7f3c03c2b937bd8662a3e", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "ext-iconv": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "illuminate/pagination": ">=5.0.0", + "nesbot/carbon": ">=1.0", + "php": ">=7.0.0", + "symfony/http-foundation": ">=2.8.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "symfony/mime": "Recomended for better extension support" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webklex\\PHPIMAP\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Malte Goldenbaum", + "email": "github@webklex.com", + "role": "Developer" + } + ], + "description": "PHP IMAP client", + "homepage": "https://github.com/webklex/php-imap", + "keywords": [ + "imap", + "mail", + "php-imap", + "pop3", + "webklex" + ], + "support": { + "issues": "https://github.com/Webklex/php-imap/issues", + "source": "https://github.com/Webklex/php-imap/tree/4.1.1" + }, + "funding": [ + { + "url": "https://www.buymeacoffee.com/webklex", + "type": "custom" + }, + { + "url": "https://ko-fi.com/webklex", + "type": "ko_fi" + } + ], + "time": "2022-11-16T07:27:09+00:00" + } + ], + "packages-dev": [ + { + "name": "barryvdh/laravel-debugbar", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-debugbar.git", + "reference": "5b68f3972083a7eeec0d6f161962fcda71a127c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/5b68f3972083a7eeec0d6f161962fcda71a127c0", + "reference": "5b68f3972083a7eeec0d6f161962fcda71a127c0", + "shasum": "" + }, + "require": { + "illuminate/routing": "5.5.x|5.6.x|5.7.x", + "illuminate/session": "5.5.x|5.6.x|5.7.x", + "illuminate/support": "5.5.x|5.6.x|5.7.x", + "maximebf/debugbar": "~1.15.0", + "php": ">=7.0", + "symfony/debug": "^3|^4", + "symfony/finder": "^3|^4" + }, + "require-dev": { + "laravel/framework": "5.5.x" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ], + "aliases": { + "Debugbar": "Barryvdh\\Debugbar\\Facade" + } + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "PHP Debugbar integration for Laravel", + "keywords": [ + "debug", + "debugbar", + "laravel", + "profiler", + "webprofiler" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-debugbar/issues", + "source": "https://github.com/barryvdh/laravel-debugbar/tree/master" + }, + "time": "2018-08-22T11:06:19+00:00" + }, + { + "name": "doctrine/annotations", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/v1.4.0" + }, + "time": "2017-02-24T16:22:25+00:00" + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/master" + }, + "time": "2017-01-03T10:49:41+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.3.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-05-29T17:27:14+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "fzaninotto/faker", + "version": "v1.9.2", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "848d8125239d7dbf8ab25cb7f054f1a630e68c2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/848d8125239d7dbf8ab25cb7f054f1a630e68c2e", + "reference": "848d8125239d7dbf8ab25cb7f054f1a630e68c2e", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^2.9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/fzaninotto/Faker/issues", + "source": "https://github.com/fzaninotto/Faker/tree/v1.9.2" + }, + "abandoned": true, + "time": "2020-12-11T09:56:16+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "maximebf/debugbar", + "version": "v1.15.1", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "6c4277f6117e4864966c9cb58fb835cee8c74a1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/6c4277f6117e4864966c9cb58fb835cee8c74a1e", + "reference": "6c4277f6117e4864966c9cb58fb835cee8c74a1e", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "psr/log": "^1.0", + "symfony/var-dumper": "^2.6|^3|^4" + }, + "require-dev": { + "phpunit/phpunit": "^5" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\": "src/DebugBar/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/maximebf/php-debugbar", + "keywords": [ + "debug", + "debugbar" + ], + "support": { + "issues": "https://github.com/maximebf/php-debugbar/issues", + "source": "https://github.com/maximebf/php-debugbar/tree/v1.15.1" + }, + "time": "2019-09-24T14:55:42+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "99e29d3596b16dabe4982548527d5ddf90232e99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/99e29d3596b16dabe4982548527d5ddf90232e99", + "reference": "99e29d3596b16dabe4982548527d5ddf90232e99", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~2.0", + "lib-pcre": ">=7.0", + "php": ">=5.6.0" + }, + "require-dev": { + "phpdocumentor/phpdocumentor": "^2.9", + "phpunit/phpunit": "~5.7.10|~6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/master" + }, + "time": "2018-05-08T08:54:48+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.x" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-06-29T13:22:24+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.29", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.15", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-09-19T04:57:46+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.28", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2023-01-14T12:32:24+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-05-07T05:35:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bde739e7565280bda77be70044ac1047bc007e34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-02T09:26:13+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.3.0" + }, + "time": "2018-01-29T19:49:41+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.1.0" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/freescout-dist/config/activitylog.php b/freescout-dist/config/activitylog.php new file mode 100644 index 0000000..91526bc --- /dev/null +++ b/freescout-dist/config/activitylog.php @@ -0,0 +1,44 @@ + env('ACTIVITY_LOGGER_ENABLED', true), + + /* + * When the clean-command is executed, all recording activities older than + * the number of days specified here will be deleted. + */ + 'delete_records_older_than_days' => env('ACTIVITY_LOGGER_DELETE_RECORDS_OLDER_THAN_DAYS', 30), + + /* + * If no log name is passed to the activity() helper + * we use this default log name. + */ + 'default_log_name' => 'default', + + /* + * You can specify an auth driver here that gets user models. + * If this is null we'll use the default Laravel auth driver. + */ + 'default_auth_driver' => null, + + /* + * If set to true, the subject returns soft deleted models. + */ + 'subject_returns_soft_deleted_models' => false, + + /* + * This model will be used to log activity. The only requirement is that + * it should be or extend the Spatie\Activitylog\Models\Activity model. + */ + 'activity_model' => App\ActivityLog::class, + + /* + * This is the name of the table that will be created by the migration and + * used by the Activity model shipped with this package. + */ + 'table_name' => 'activity_logs', +]; diff --git a/freescout-dist/config/app.php b/freescout-dist/config/app.php new file mode 100644 index 0000000..76cdc2d --- /dev/null +++ b/freescout-dist/config/app.php @@ -0,0 +1,610 @@ + '1.8.114', + + /* + |-------------------------------------------------------------------------- + | Application Name + |-------------------------------------------------------------------------- + | + | This value is the name of your application. This value is used when the + | framework needs to place the application's name in a notification or + | any other location as required by the application or its packages. + | + */ + + 'name' => 'FreeScout', + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services your application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | your application so that it is used when running Artisan tasks. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. We have gone + | ahead and set this to a sensible default for you out of the box. + | + */ + + 'timezone' => env('APP_TIMEZONE', date_default_timezone_get()), + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + | locales: available locales + */ + + 'locale' => env('APP_LOCALE', 'en'), + 'locales' => ['en', 'zh-CN', 'hr', 'cs', 'da', 'nl', 'fi', 'fr', 'de', 'it', 'ja', 'ko', 'no', 'fa', 'pl', 'pt-PT', 'pt-BR', 'ru', 'es', 'sk', 'sv'], + 'locales_rtl' => ['fa'], + 'default_locale' => 'en', + + /* + | app()->setLocale() in Localize middleware also changes config('app.locale'), + | so we are keeping real app locale in real_locale parameter. + */ + 'real_locale' => env('APP_LOCALE', 'en'), + + /* + |-------------------------------------------------------------------------- + | Application Fallback Locale + |-------------------------------------------------------------------------- + | + | The fallback locale determines the locale to use when the current one + | is not available. You may change the value to correspond to any of + | the language folders that are provided through your application. + | + */ + + 'fallback_locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + + 'key' => $key, + + 'cipher' => 'AES-256-CBC', + + /* + |-------------------------------------------------------------------------- + | Logging Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure the log settings for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Settings: "single", "daily", "syslog", "errorlog" + | + */ + + 'log' => env('APP_LOG', 'daily'), // by default logs for 5 days are kept + + 'log_level' => env('APP_LOG_LEVEL', 'error'), + + /* + |-------------------------------------------------------------------------- + | FreeScout website + |------------------------------------------------------------------------- + */ + 'freescout_url' => 'https://freescout.net', + + /* + |-------------------------------------------------------------------------- + | FreeScout API + |------------------------------------------------------------------------- + */ + 'freescout_api' => 'https://freescout.net/wp-json/', + 'freescout_alt_api' => 'https://cdn.freescout.net/wp-json/', + + /* + |-------------------------------------------------------------------------- + | FreeScout eepository + |------------------------------------------------------------------------- + */ + 'freescout_repo' => 'https://github.com/freescout-helpdesk/freescout', + + /* + |-------------------------------------------------------------------------- + | FreeScout email + |------------------------------------------------------------------------- + */ + 'freescout_email' => 'support@freescout.net', + + /* + |-------------------------------------------------------------------------- + | Parameters used to run queued jobs processing. + | Checks for new jobs every --sleep seconds. + | If --tries is set and job fails it is being processed right away without any delay. + | --delay parameter does not work to set delays between retry attempts. + | --timeout parameter sets job timeout and is used to avoid queue:work freezing. + | + | Jobs sending emails are retried manually in handle(). + | Number of retries is set in each job class. + |------------------------------------------------------------------------- + */ + 'queue_work_params' => ['--queue' => 'emails,default', '--sleep' => '5', '--tries' => '1', '--timeout' => '1800'], + + /* + |-------------------------------------------------------------------------- + | PHP extensions required by the app + | Replaced with installer.requirements.php + |------------------------------------------------------------------------- + */ + //'required_extensions' => ['mysql / mysqli', 'mbstring', 'xml', 'imap', /*'mcrypt' mcrypt is deprecated*/ 'json', 'gd', 'fileinfo', 'openssl', 'zip', 'tokenizer', 'curl', 'iconv'/*, 'dom', 'xmlwriter', 'libxml', 'phar'*/], + + /* + |-------------------------------------------------------------------------- + | Logs monitoring parameters. + | These settings must be stored to avoid DB query in Kenel.php + |------------------------------------------------------------------------- + */ + 'alert_logs' => env('APP_ALERT_LOGS', false), + 'alert_logs_period' => env('APP_ALERT_LOGS_PERIOD', ''), + + /* + |-------------------------------------------------------------------------- + | Fetch Mail Schedule. + |------------------------------------------------------------------------- + */ + 'fetch_schedule' => env('APP_FETCH_SCHEDULE', 1), + + /* + |-------------------------------------------------------------------------- + | App colors. + |-------------------------------------------------------------------------- + */ + 'colors' => [ + 'main_light' => '#0078d7', + 'main_dark' => '#005a9e', + 'note' => '#ffc646', + 'text_note' => '#e6b216', + 'text_customer' => '#8d959b', + 'text_user' => '#8d959b', + 'bg_user_reply' => '#f4f8fd', + 'bg_note' => '#fffbf1', + ], + + /* + |-------------------------------------------------------------------------- + | Default options values for \Option::get() + |-------------------------------------------------------------------------- + */ + 'options' => [ + 'alert_fetch' => ['default' => false], + 'alert_fetch_period' => ['default' => 15], // min + 'email_branding' => ['default' => true], + 'open_tracking' => ['default' => true], + 'subscription_defaults' => ['default' => []], + ], + + /* + |-------------------------------------------------------------------------- + | php - attachments are downloaded via PHP. + | + | apache - attachments are downloaded via Apache's mod_xsendfile. + | + | nginx - attachments are downloaded via nginx's X-Accel-Redirect. + |------------------------------------------------------------------------- + */ + 'download_attachments_via' => env('APP_DOWNLOAD_ATTACHMENTS_VIA', 'php'), + + /* + |-------------------------------------------------------------------------- + | File types which should be viewed in the browser instead of downloading. + | SVG images are not viewable to avid XSS. + | The list should be in sync with /storage/app/public/uploads/.htaccess and nginx config. + |------------------------------------------------------------------------- + */ + 'viewable_attachments' => env('APP_VIEWABLE_ATTACHMENTS') + ? explode(',', env('APP_VIEWABLE_ATTACHMENTS')) + : ['jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp', 'apng', 'bmp', 'gif', 'ico', 'cur', 'png', 'tif', 'tiff', 'webp', 'pdf', 'txt', 'diff', 'patch', 'json', 'mp3', 'wav', 'ogg', 'wma'], + + // Additional restriction by mime type. + // If HTML file is renamed into .txt for example it will be shown by the browser as HTML. + // Regular expressions (#...#) + 'viewable_mime_types' => env('APP_VIEWABLE_MIME_TYPES', ['image/.*', 'application/pdf', 'text/plain', 'text/x-diff', 'application/json', 'audio/.*']), + + /* + |-------------------------------------------------------------------------- + | Case insensitive regular expression, containing a list of + | mail server error responses, returned when a mail server can not deliver an email + | to one or more recipients. If FreeScout receives one of the listed + | error responses from the mail server, it does not try to resend the email + | to avoid sending multiple duplicate emails to other recipients. + | + | https://github.com/freescout-helpdesk/freescout/issues/870#issuecomment-786477909 + | + |------------------------------------------------------------------------- + */ + 'no_retry_mail_errors' => env('APP_NO_RETRY_MAIL_ERRORS', '(no valid recipients|does not comply with RFC|message file too big)'), + + /* + |-------------------------------------------------------------------------- + | none - send to the customer only agent's reply in the email. + | + | last - send to the customer the last message in the email. + | + | full - send to the customer full conversation history in the email. + | + |------------------------------------------------------------------------- + */ + 'email_conv_history' => env('APP_EMAIL_CONV_HISTORY', 'none'), + + /* + |-------------------------------------------------------------------------- + | Maximum size of the message which can be sent to the customer (MB). + | + |------------------------------------------------------------------------- + */ + 'max_message_size' => env('APP_MAX_MESSAGE_SIZE', '20'), + + /* + |-------------------------------------------------------------------------- + | none - send to the user only agent's reply in the email. + | + | last - send to the user the last message in the email. + | + | full - send to the user full conversation history in the email. + | + |------------------------------------------------------------------------- + */ + 'email_user_history' => env('APP_EMAIL_USER_HISTORY', 'full'), + + /* + |-------------------------------------------------------------------------- + | JSON containing user permissions. + | + |------------------------------------------------------------------------- + */ + 'user_permissions' => env('APP_USER_PERMISSIONS', ''), + + /* + |-------------------------------------------------------------------------- + | Use date from mail header on fetching. + | + |------------------------------------------------------------------------- + */ + 'use_mail_date_on_fetching' => env('APP_USE_MAIL_DATE_ON_FETCHING', false), + + /* + |-------------------------------------------------------------------------- + | Dashboard path. + | + |------------------------------------------------------------------------- + */ + 'dashboard_path' => env('APP_DASHBOARD_PATH', ''), + + /* + |-------------------------------------------------------------------------- + | Dashboard path. + | + |------------------------------------------------------------------------- + */ + 'login_path' => env('APP_LOGIN_PATH', 'login'), + + /* + |-------------------------------------------------------------------------- + | Home page controller. + | + |------------------------------------------------------------------------- + */ + 'home_controller' => env('APP_HOMEPAGE_CONTROLLER', 'SecureController@dashboard'), + + /* + |-------------------------------------------------------------------------- + | Disable update checker + |-------------------------------------------------------------------------- + */ + 'disable_updating' => env('APP_DISABLE_UPDATING', false), + + /* + |-------------------------------------------------------------------------- + | Use custom conversation numbers instead of conversation ID. + |-------------------------------------------------------------------------- + */ + 'custom_number' => env('APP_CUSTOM_NUMBER', false), + + /* + |-------------------------------------------------------------------------- + | Enter your proxy address in .env file if freescout.net is not available from your server + | (access to freescout.net is required to obtain official modules) + |-------------------------------------------------------------------------- + */ + 'proxy' => env('APP_PROXY', ''), + + /* + |-------------------------------------------------------------------------- + | Custom headers to add to all outgoing emails. + | https://github.com/freescout-helpdesk/freescout/issues/2546#issuecomment-1380414908 + |-------------------------------------------------------------------------- + */ + 'custom_mail_headers' => env('APP_CUSTOM_MAIL_HEADERS', ''), + + /* + |-------------------------------------------------------------------------- + | Library used to fetch emails: webklex/laravel-imap, webklex/php-imap + |------------------------------------------------------------------------- + */ + 'new_fetching_library' => env('APP_NEW_FETCHING_LIBRARY', false), + + /* + |-------------------------------------------------------------------------- + | Timeout for curl and GuzzleHttp. + |------------------------------------------------------------------------- + */ + // Should be set for curl and Guzzle. + 'curl_timeout' => env('APP_CURL_TIMEOUT', 40), + // Should be set for Guzzle. Curl has default CURLOPT_CONNECTTIMEOUT=30 sec. + 'curl_connect_timeout' => env('APP_CURL_CONNECTION_TIMEOUT', 30), + // CloudFlare may block requests without user agent. + // Need to be set for curl. Guzzle sends it's own user agent: GuzzleHttp/6.3.3 curl/7.58.0 PHP/8.2.5 + 'curl_user_agent' => env('APP_CURL_USER_AGENT', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 7_1_4) AppleWebKit/603.26 (KHTML, like Gecko) Chrome/55.0.3544.220 Safari/534'), + // Should be set for curl and Guzzle. + 'curl_ssl_verifypeer' => env('APP_CURL_SSL_VERIFYPEER', false), + + /* + |-------------------------------------------------------------------------- + | Customer photo size (px). + | https://github.com/freescout-helpdesk/freescout/issues/2919 + |------------------------------------------------------------------------- + */ + 'customer_photo_size' => env('APP_CUSTOMER_PHOTO_SIZE', 64), + + + /* + |-------------------------------------------------------------------------- + | User photo size (px). + |------------------------------------------------------------------------- + */ + 'user_photo_size' => env('APP_USER_PHOTO_SIZE', 50), + + /* + |-------------------------------------------------------------------------- + | Use this option if you have many folders and you are experiencing + | performance issues. When this option is enabled sometimes it may take + | several seconds for folders counters to update in the interface. + | + | https://github.com/freescout-helpdesk/freescout/pull/2982 + |------------------------------------------------------------------------- + */ + 'update_folder_counters_in_background' => env('APP_UPDATE_FOLDER_COUNTERS_IN_BACKGROUND', false), + + /* + |-------------------------------------------------------------------------- + | Experimental feature allowing to specify users who can see only conversations + | assigned to themselves. For such users only Mine folder shows actual number of conversations. + | This option does not affect admin users. + | + | The option should be specified as a comma separated list of user IDs which + | can be found in the their profile URL (/users/profile/7). + | + | Example: 7,5,31 + |------------------------------------------------------------------------- + */ + 'show_only_assigned_conversations' => env('APP_SHOW_ONLY_ASSIGNED_CONVERSATIONS', ''), + + /* + |-------------------------------------------------------------------------- + | By default X-Frame-Options header is enabled and set to SAMEORIGIN. + | Via this option you can disable it (APP_X_FRAME_OPTIONS=false) or set custom value: + | - DENY + | - ALLOW-FROM example.org + |------------------------------------------------------------------------- + */ + 'x_frame_options' => env('APP_X_FRAME_OPTIONS', true), + + /* + |-------------------------------------------------------------------------- + | Enable Content-Security-Policy meta tag to prevent possible XSS attacks. + |------------------------------------------------------------------------- + */ + 'csp_enabled' => env('APP_CSP_ENABLED', true), + 'csp_script_src' => env('APP_CSP_SCRIPT_SRC', ''), + + /* + |-------------------------------------------------------------------------- + | Let the application know that CloudFlare is used (for proper client IP detection). + |------------------------------------------------------------------------- + */ + 'cloudflare_is_used' => env('APP_CLOUDFLARE_IS_USED', false), + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => [ + + /* + * Laravel Framework Service Providers... + */ + Illuminate\Auth\AuthServiceProvider::class, + Illuminate\Broadcasting\BroadcastServiceProvider::class, + Illuminate\Bus\BusServiceProvider::class, + Illuminate\Cache\CacheServiceProvider::class, + Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, + Illuminate\Cookie\CookieServiceProvider::class, + Illuminate\Database\DatabaseServiceProvider::class, + Illuminate\Encryption\EncryptionServiceProvider::class, + Illuminate\Filesystem\FilesystemServiceProvider::class, + Illuminate\Foundation\Providers\FoundationServiceProvider::class, + Illuminate\Hashing\HashServiceProvider::class, + Illuminate\Mail\MailServiceProvider::class, + Illuminate\Notifications\NotificationServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, + Illuminate\Pipeline\PipelineServiceProvider::class, + Illuminate\Queue\QueueServiceProvider::class, + Illuminate\Redis\RedisServiceProvider::class, + Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, + Illuminate\Session\SessionServiceProvider::class, + Illuminate\Translation\TranslationServiceProvider::class, + Illuminate\Validation\ValidationServiceProvider::class, + Illuminate\View\ViewServiceProvider::class, + + /* + * Package Service Providers... + */ + Devfactory\Minify\MinifyServiceProvider::class, + // Debugbar is enabled only if APP_DEBUG=true + //Barryvdh\Debugbar\ServiceProvider::class, + + /* + * Application Service Providers... + */ + App\Providers\AppServiceProvider::class, + App\Providers\AuthServiceProvider::class, + App\Providers\BroadcastServiceProvider::class, + App\Providers\EventServiceProvider::class, + App\Providers\RouteServiceProvider::class, + App\Providers\PolycastServiceProvider::class, + + /* + * Custom Service Providers... + */ + // We can freely add or remove providers from this file. + // Updating will work without problems. + + // Autodiscovery did not work for this one, becasuse it's composer.json + // does not have a `extra` section. + Codedge\Updater\UpdaterServiceProvider::class, + ], + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + + 'aliases' => [ + + 'App' => Illuminate\Support\Facades\App::class, + 'Artisan' => Illuminate\Support\Facades\Artisan::class, + 'Auth' => Illuminate\Support\Facades\Auth::class, + 'Blade' => Illuminate\Support\Facades\Blade::class, + 'Broadcast' => Illuminate\Support\Facades\Broadcast::class, + 'Bus' => Illuminate\Support\Facades\Bus::class, + 'Cache' => Illuminate\Support\Facades\Cache::class, + 'Config' => Illuminate\Support\Facades\Config::class, + 'Cookie' => Illuminate\Support\Facades\Cookie::class, + 'Crypt' => Illuminate\Support\Facades\Crypt::class, + 'DB' => Illuminate\Support\Facades\DB::class, + 'Eloquent' => Illuminate\Database\Eloquent\Model::class, + 'Event' => Illuminate\Support\Facades\Event::class, + 'File' => Illuminate\Support\Facades\File::class, + 'Gate' => Illuminate\Support\Facades\Gate::class, + 'Hash' => Illuminate\Support\Facades\Hash::class, + 'Lang' => Illuminate\Support\Facades\Lang::class, + 'Log' => Illuminate\Support\Facades\Log::class, + 'Mail' => Illuminate\Support\Facades\Mail::class, + 'Notification' => Illuminate\Support\Facades\Notification::class, + 'Password' => Illuminate\Support\Facades\Password::class, + 'Queue' => Illuminate\Support\Facades\Queue::class, + 'Redirect' => Illuminate\Support\Facades\Redirect::class, + 'Redis' => Illuminate\Support\Facades\Redis::class, + 'Request' => Illuminate\Support\Facades\Request::class, + 'Response' => Illuminate\Support\Facades\Response::class, + 'Route' => Illuminate\Support\Facades\Route::class, + 'Schema' => Illuminate\Support\Facades\Schema::class, + 'Session' => Illuminate\Support\Facades\Session::class, + 'Storage' => Illuminate\Support\Facades\Storage::class, + 'URL' => Illuminate\Support\Facades\URL::class, + 'Validator' => Illuminate\Support\Facades\Validator::class, + 'View' => Illuminate\Support\Facades\View::class, + 'Minify' => Devfactory\Minify\Facades\MinifyFacade::class, + + // Custom + 'Helper' => App\Misc\Helper::class, + 'MailHelper' => App\Misc\Mail::class, + 'ModuleHelper' => App\Module::class, + 'WpApi' => App\Misc\WpApi::class, + 'Option' => App\Option::class, + 'Str' => Illuminate\Support\Str::class, + // Autodiscovery did not work for this one, becasuse it's composer.json + // does not have a `extra` section. + 'Updater' => Codedge\Updater\UpdaterFacade::class, + ], + +]; diff --git a/freescout-dist/config/auth.php b/freescout-dist/config/auth.php new file mode 100644 index 0000000..9c58048 --- /dev/null +++ b/freescout-dist/config/auth.php @@ -0,0 +1,103 @@ + [ + 'guard' => 'web', + 'passwords' => 'users', + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | here which uses session storage and the Eloquent user provider. + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | Supported: "session", "token" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + + // 'api' => [ + // 'driver' => 'token', + // 'provider' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | If you have multiple user tables or models you may configure multiple + | sources which represent each model / table. These sources may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => App\User::class, + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | You may specify multiple password reset configurations if you have more + | than one user table or model in the application and you want to have + | separate password reset settings based on the specific user types. + | + | The expire time is the number of minutes that the reset token should be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => 'password_resets', + 'expire' => 60, + 'throttle' => 60, + ], + ], + +]; diff --git a/freescout-dist/config/broadcasting.php b/freescout-dist/config/broadcasting.php new file mode 100644 index 0000000..4e70199 --- /dev/null +++ b/freescout-dist/config/broadcasting.php @@ -0,0 +1,65 @@ + env('BROADCAST_DRIVER', 'polycast'), + + /* + |-------------------------------------------------------------------------- + | Broadcast Connections + |-------------------------------------------------------------------------- + | + | Here you may define all of the broadcast connections that will be used + | to broadcast events to other systems or over websockets. Samples of + | each available type of connection are provided inside this array. + | + */ + + 'connections' => [ + + 'polycast' => [ + 'driver' => 'polycast', + // this deletes old events after 2 minutes, this can be changed to leave them in the db longer if required + 'delete_old' => 2, + ], + + 'pusher' => [ + 'driver' => 'pusher', + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'encrypted' => true, + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + ], + + 'log' => [ + 'driver' => 'log', + ], + + 'null' => [ + 'driver' => 'null', + ], + + ], + +]; diff --git a/freescout-dist/config/cache.php b/freescout-dist/config/cache.php new file mode 100644 index 0000000..236e295 --- /dev/null +++ b/freescout-dist/config/cache.php @@ -0,0 +1,94 @@ + env('CACHE_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + */ + + 'stores' => [ + + 'apc' => [ + 'driver' => 'apc', + ], + + 'array' => [ + 'driver' => 'array', + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'cache', + 'connection' => null, + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing a RAM based store such as APC or Memcached, there might + | be other applications utilizing the same cache. So, we'll specify a + | value to get prefixed to all our keys so we can avoid collisions. + | + */ + + 'prefix' => env( + 'CACHE_PREFIX', + str_slug(env('APP_NAME', 'laravel'), '_').'_cache' + ), + +]; diff --git a/freescout-dist/config/database.php b/freescout-dist/config/database.php new file mode 100644 index 0000000..e14678b --- /dev/null +++ b/freescout-dist/config/database.php @@ -0,0 +1,153 @@ + env('DB_CONNECTION', 'mysql'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => env('DB_TABLE_PREFIX', ''), + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => env('DB_TABLE_PREFIX', ''), + 'strict' => false, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('DB_MYSQL_ATTR_SSL_CA'), + PDO::MYSQL_ATTR_SSL_CERT => env('DB_MYSQL_ATTR_SSL_CERT'), + ]) : [], + ], + + 'testing' => [ + 'driver' => 'mysql', + //'url' => env('DB_TEST_DATABASE_URL'), + 'host' => '127.0.0.1', + 'database' => 'freescout-test', + 'username' => env('DB_TEST_USERNAME', 'freescout-test'), + 'password' => env('DB_TEST_PASSWORD', 'freescout-test'), + //'port' => env('DB_TEST_PORT', '3306'), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => env('DB_TABLE_PREFIX', ''), + //'prefix_indexes' => true, + 'strict' => false, + 'engine' => null, + ], + + 'testing_pgsql' => [ + 'driver' => 'pgsql', + 'host' => 'localhost', + 'port' => '5432', + 'database' => 'freescout-test', + 'username' => env('DB_TEST_USERNAME', 'freescout-test'), + 'password' => env('DB_TEST_PASSWORD', 'freescout-test'), + 'charset' => 'utf8', + 'prefix' => env('DB_TABLE_PREFIX', ''), + 'schema' => 'public', + 'sslmode' => 'prefer', + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => env('DB_TABLE_PREFIX', ''), + 'schema' => 'public', + 'sslmode' => env('DB_PGSQL_SSLMODE', 'prefer'), + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => env('DB_TABLE_PREFIX', ''), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run in the database. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer set of commands than a typical key-value systems + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => [ + + 'client' => 'predis', + + 'default' => [ + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', 6379), + 'database' => 0, + ], + + ], + +]; diff --git a/freescout-dist/config/filesystems.php b/freescout-dist/config/filesystems.php new file mode 100644 index 0000000..4c0eb79 --- /dev/null +++ b/freescout-dist/config/filesystems.php @@ -0,0 +1,78 @@ + env('FILESYSTEM_DRIVER', 'local'), + + /* + |-------------------------------------------------------------------------- + | Default Cloud Filesystem Disk + |-------------------------------------------------------------------------- + | + | Many applications store files both locally and in the cloud. For this + | reason, you may specify a default "cloud" driver here. This driver + | will be bound as the Cloud disk implementation in the container. + | + */ + + 'cloud' => env('FILESYSTEM_CLOUD', 's3'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Here you may configure as many filesystem "disks" as you wish, and you + | may even configure multiple disks of the same driver. Defaults have + | been setup for each driver as an example of the required options. + | + | Supported Drivers: "local", "ftp", "s3", "rackspace" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + ], + + // To store file in the storage/app folder + 'private' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + ], + + ], + +]; diff --git a/freescout-dist/config/installer.php b/freescout-dist/config/installer.php new file mode 100644 index 0000000..afadbad --- /dev/null +++ b/freescout-dist/config/installer.php @@ -0,0 +1,171 @@ + [ + 'minPhpVersion' => '7.1.0', + 'maxPhpVersion' => '8.99.99', + ], + 'final' => [ + 'key' => false, + 'publish' => false, + ], + // It must be equal to app.required_extensions + 'requirements' => [ + 'php' => [ + 'OpenSSL', + 'PDO', + 'Mbstring', + 'Tokenizer', + 'JSON', + 'XML', + 'IMAP', + 'GD', + 'fileinfo', + 'ZIP', + 'iconv', + 'cURL', + 'DOM', + 'libxml', + //'pcntl', + // We keep it as optional, as it's only used to translate dates. + //'intl', + ], + // 'apache' => [ + // 'mod_rewrite', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Folders Permissions + |-------------------------------------------------------------------------- + | + | This is the default Laravel folders permissions, if your application + | requires more permissions just add them to the array list bellow. + | + */ + 'permissions' => [ + 'storage/app/' => '775', + 'storage/framework/' => '775', + 'storage/framework/cache/data/' => '775', + 'storage/logs/' => '775', + 'bootstrap/cache/' => '775', + 'public/css/builds/' => '775', + 'public/js/builds/' => '775', + 'public/modules/' => '775', + 'Modules/' => '775', + ], + + /* + |-------------------------------------------------------------------------- + | Environment Form Wizard Validation Rules & Messages + |-------------------------------------------------------------------------- + | + | This are the default form vield validation rules. Available Rules: + | https://laravel.com/docs/5.4/validation#available-validation-rules + | + */ + 'environment' => [ + 'form' => [ + 'rules' => [ + // 'app_name' => 'required|string|max:50', + // 'environment' => 'required|string|max:50', + // 'environment_custom' => 'required_if:environment,other|max:50', + // // 'app_debug' => [ + // // 'required', + // // Rule::in(['true', 'false']), + // // ], + // 'app_log_level' => 'required|string|max:50', + 'app_url' => 'required|url', + 'database_connection' => 'required|string|max:1000', + 'database_hostname' => 'required|string|max:1000', + 'database_port' => 'required|numeric', + 'database_name' => 'required|string|max:1000', + 'database_username' => 'required|string|max:1000', + 'database_password' => 'required|string|max:1000', + 'admin_email' => 'required|email', + 'admin_first_name' => 'required|string|max:20', + 'admin_last_name' => 'required|string|max:30', + 'admin_password' => 'required|string', + // 'broadcast_driver' => 'required|string|max:50', + // 'cache_driver' => 'required|string|max:50', + // 'session_driver' => 'required|string|max:50', + // 'queue_driver' => 'required|string|max:50', + // 'redis_hostname' => 'required|string|max:50', + // 'redis_password' => 'required|string|max:50', + // 'redis_port' => 'required|numeric', + // 'mail_driver' => 'required|string|max:50', + // 'mail_host' => 'required|string|max:50', + // 'mail_port' => 'required|string|max:50', + // 'mail_username' => 'required|string|max:50', + // 'mail_password' => 'required|string|max:50', + // 'mail_encryption' => 'required|string|max:50', + // 'pusher_app_id' => 'max:50', + // 'pusher_app_key' => 'max:50', + // 'pusher_app_secret' => 'max:50', + ], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Installed Middlware Options + |-------------------------------------------------------------------------- + | Different available status switch configuration for the + | canInstall middleware located in `canInstall.php`. + | + */ + 'installed' => [ + 'redirectOptions' => [ + 'route' => [ + 'name' => 'dashboard', + 'data' => [], + ], + 'abort' => [ + 'type' => '404', + ], + 'dump' => [ + 'data' => 'Dumping a not found message.', + ], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Selected Installed Middlware Option + |-------------------------------------------------------------------------- + | The selected option fo what happens when an installer intance has been + | Default output is to `/resources/views/error/404.blade.php` if none. + | The available middleware options include: + | route, abort, dump, 404, default, '' + | + */ + 'installedAlreadyAction' => 'route', + + /* + |-------------------------------------------------------------------------- + | Updater Enabled + |-------------------------------------------------------------------------- + | Can the application run the '/update' route with the migrations. + | The default option is set to False if none is present. + | Boolean value + | + */ + 'updaterEnabled' => 'false', + +]; diff --git a/freescout-dist/config/laroute.php b/freescout-dist/config/laroute.php new file mode 100644 index 0000000..36fe979 --- /dev/null +++ b/freescout-dist/config/laroute.php @@ -0,0 +1,60 @@ + 'public/js', + + /* + * The destination filename for the javascript file. + */ + 'filename' => 'laroute', + + /* + * The namespace for the helper functions. By default this will bind them to + * `window.laroute`. + */ + 'namespace' => 'laroute', + + /* + * Generate absolute URLs + * + * Set the Application URL in config/app.php + * + * Has to be absolute for the app to work in a subdirectory. + */ + 'absolute' => true, + + /* + * The Filter Method + * + * 'all' => All routes except "'laroute' => false" + * 'only' => Only "'laroute' => true" routes + * 'force' => All routes, ignored "laroute" route parameter + */ + 'filter' => 'only', + + /* + * Controller Namespace + * + * Set here your controller namespace (see RouteServiceProvider -> $namespace) for cleaner action calls + * e.g. 'App\Http\Controllers' + */ + 'action_namespace' => '', + + /* + * The path to the template `laroute.js` file. This is the file that contains + * the ported helper Laravel url/route functions and the route data to go + * with them. + */ + 'template' => 'resources/assets/js/laroute.js', + + /* + * Appends a prefix to URLs. By default the prefix is an empty string. + * + */ + 'prefix' => '', + +]; diff --git a/freescout-dist/config/mail.php b/freescout-dist/config/mail.php new file mode 100644 index 0000000..75711f5 --- /dev/null +++ b/freescout-dist/config/mail.php @@ -0,0 +1,132 @@ + env('MAIL_DRIVER', 'mail'), + + /* + |-------------------------------------------------------------------------- + | SMTP Host Address + |-------------------------------------------------------------------------- + | + | Here you may provide the host address of the SMTP server used by your + | applications. A default option is provided that is compatible with + | the Mailgun mail service which will provide reliable deliveries. + | + */ + + 'host' => env('MAIL_HOST'), + + /* + |-------------------------------------------------------------------------- + | SMTP Host Port + |-------------------------------------------------------------------------- + | + | This is the SMTP port used by your application to deliver e-mails to + | users of the application. Like the host we have set this value to + | stay compatible with the Mailgun e-mail application by default. + | + */ + + 'port' => env('MAIL_PORT'), + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all e-mails sent by your application to be sent from + | the same address. Here, you may specify a name and address that is + | used globally for all e-mails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + + /* + |-------------------------------------------------------------------------- + | E-Mail Encryption Protocol + |-------------------------------------------------------------------------- + | + | Here you may specify the encryption protocol that should be used when + | the application send e-mail messages. A sensible default using the + | transport layer security protocol should provide great security. + | + */ + + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + + /* + |-------------------------------------------------------------------------- + | SMTP Server Username + |-------------------------------------------------------------------------- + | + | If your SMTP server requires a username for authentication, you should + | set it here. This will get used to authenticate with your server on + | connection. You may also set the "password" value below this one. + | + */ + + 'username' => env('MAIL_USERNAME'), + + 'password' => env('MAIL_PASSWORD'), + + /* + |-------------------------------------------------------------------------- + | Sendmail System Path + |-------------------------------------------------------------------------- + | + | When using the "sendmail" driver to send e-mails, we will need to know + | the path to where Sendmail lives on this server. A default path has + | been provided here, which will work well on most of your systems. + | + */ + + 'sendmail' => '/usr/sbin/sendmail -bs', + + /* + |-------------------------------------------------------------------------- + | Markdown Mail Settings + |-------------------------------------------------------------------------- + | + | If you are using Markdown based email rendering, you may configure your + | theme and component paths here, allowing you to customize the design + | of the emails. Or, you may simply stick with the Laravel defaults! + | + */ + + 'markdown' => [ + 'theme' => 'default', + + 'paths' => [ + resource_path('views/vendor/mail'), + ], + ], + + /* + |-------------------------------------------------------------------------- + | Swiftmailer SMTP timeout. + |-------------------------------------------------------------------------- + | + */ + + 'smtp_timeout' => env('MAIL_SMTP_TIMEOUT', 30), + +]; diff --git a/freescout-dist/config/minify.config.php b/freescout-dist/config/minify.config.php new file mode 100644 index 0000000..c8db92d --- /dev/null +++ b/freescout-dist/config/minify.config.php @@ -0,0 +1,84 @@ + true, + + /* + |-------------------------------------------------------------------------- + | App environments to not minify + |-------------------------------------------------------------------------- + | + | These environments will not be minified and all individual files are + | returned + | + */ + + 'ignore_environments' => [ + 'local', + ], + + /* + |-------------------------------------------------------------------------- + | CSS build path + |-------------------------------------------------------------------------- + | + | Minify is an extension that can minify your css files into one build file. + | The css_builds_path property is the location where the builded files are + | stored. This is relative to your public path. Notice the trailing slash. + | Note that this directory must be writeable. + | + */ + + 'css_build_path' => '/css/builds/', + 'css_url_path' => env('APP_URL').'/css/builds/', + + /* + |-------------------------------------------------------------------------- + | JS build path + |-------------------------------------------------------------------------- + | + | Minify is an extension that can minify your js files into one build file. + | The js_build_path property is the location where the builded files are + | stored. This is relative to your public path. Notice the trailing slash. + | Note that this directory must be writeable. + | + */ + + 'js_build_path' => '/js/builds/', + 'js_url_path' => env('APP_URL').'/js/builds/', + + /* + |-------------------------------------------------------------------------- + | Hash modification + |-------------------------------------------------------------------------- + | + | You can disable usage of modification time (mtime) for hash build and + | add additional salt (exp. commit hash) for hash build + | + */ + + 'disable_mtime' => false, + 'hash_salt' => '', + + /* + |-------------------------------------------------------------------------- + | Base URL + |-------------------------------------------------------------------------- + | + | You can set the base URL for the links generated with the configuration + | value. By default if empty HTTP_HOST would be used. + | + */ + 'base_url' => '', +]; diff --git a/freescout-dist/config/modules.php b/freescout-dist/config/modules.php new file mode 100644 index 0000000..ac3f309 --- /dev/null +++ b/freescout-dist/config/modules.php @@ -0,0 +1,186 @@ + 'Modules', + + /* + |-------------------------------------------------------------------------- + | Module Stubs + |-------------------------------------------------------------------------- + | + | Default module stubs. + | + */ + + 'stubs' => [ + 'enabled' => true, + 'path' => base_path().'/overrides/nwidart/laravel-modules/src/Commands/stubs', + 'files' => [ + 'start' => 'start.php', + 'routes' => 'Http/routes.php', + 'views/index' => 'Resources/views/index.blade.php', + 'views/master' => 'Resources/views/layouts/master.blade.php', + 'scaffold/config' => 'Config/config.php', + 'composer' => 'composer.json', + ], + 'replacements' => [ + 'start' => ['LOWER_NAME', 'ROUTES_LOCATION'], + 'routes' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE'], + 'json' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE'], + 'views/index' => ['LOWER_NAME'], + 'views/master' => ['STUDLY_NAME'], + 'scaffold/config' => ['STUDLY_NAME'], + 'composer' => [ + 'LOWER_NAME', + 'STUDLY_NAME', + 'VENDOR', + 'AUTHOR_NAME', + 'AUTHOR_EMAIL', + 'MODULE_NAMESPACE', + ], + ], + 'gitkeep' => true, + ], + 'paths' => [ + /* + |-------------------------------------------------------------------------- + | Modules path + |-------------------------------------------------------------------------- + | + | This path used for save the generated module. This path also will be added + | automatically to list of scanned folders. + | + */ + + 'modules' => base_path('Modules'), + /* + |-------------------------------------------------------------------------- + | Modules assets path + |-------------------------------------------------------------------------- + | + | Here you may update the modules assets path. + | + */ + + 'assets' => public_path('modules'), + /* + |-------------------------------------------------------------------------- + | The migrations path + |-------------------------------------------------------------------------- + | + | Where you run 'module:publish-migration' command, where do you publish the + | the migration files? + | + */ + + 'migration' => base_path('database/migrations'), + /* + |-------------------------------------------------------------------------- + | Generator path + |-------------------------------------------------------------------------- + | Customise the paths where the folders will be generated. + | Set the generate key to false to not generate that folder + */ + 'generator' => [ + 'config' => ['path' => 'Config', 'generate' => true], + 'command' => ['path' => 'Console', 'generate' => true], + 'migration' => ['path' => 'Database/Migrations', 'generate' => true], + 'seeder' => ['path' => 'Database/Seeders', 'generate' => true], + 'factory' => ['path' => 'Database/factories', 'generate' => true], + 'model' => ['path' => 'Entities', 'generate' => true], + 'controller' => ['path' => 'Http/Controllers', 'generate' => true], + 'filter' => ['path' => 'Http/Middleware', 'generate' => true], + 'request' => ['path' => 'Http/Requests', 'generate' => true], + 'provider' => ['path' => 'Providers', 'generate' => true], + 'assets' => ['path' => 'Resources/assets', 'generate' => true], + 'lang' => ['path' => 'Resources/lang', 'generate' => true], + 'views' => ['path' => 'Resources/views', 'generate' => true], + 'test' => ['path' => 'Tests', 'generate' => true], + 'repository' => ['path' => 'Repositories', 'generate' => false], + 'event' => ['path' => 'Events', 'generate' => false], + 'listener' => ['path' => 'Listeners', 'generate' => false], + 'policies' => ['path' => 'Policies', 'generate' => false], + 'rules' => ['path' => 'Rules', 'generate' => false], + 'jobs' => ['path' => 'Jobs', 'generate' => false], + 'emails' => ['path' => 'Emails', 'generate' => false], + 'notifications' => ['path' => 'Notifications', 'generate' => false], + 'resource' => ['path' => 'Transformers', 'generate' => false], + 'public' => ['path' => 'Public', 'generate' => true], + ], + ], + /* + |-------------------------------------------------------------------------- + | Scan Path + |-------------------------------------------------------------------------- + | + | Here you define which folder will be scanned. By default will scan vendor + | directory. This is useful if you host the package in packagist website. + | + */ + + 'scan' => [ + 'enabled' => false, + 'paths' => [ + base_path('vendor/*/*'), + ], + ], + /* + |-------------------------------------------------------------------------- + | Composer File Template + |-------------------------------------------------------------------------- + | + | Here is the config for composer.json file, generated by this package + | + */ + + 'composer' => [ + 'vendor' => 'freescout', + 'author' => [ + 'name' => 'FreeScout', + 'email' => 'support@freescout.net', + ], + ], + /* + |-------------------------------------------------------------------------- + | Caching + |-------------------------------------------------------------------------- + | + | Here is the config for setting up caching feature. + | + */ + 'cache' => [ + // Modules 'active' flag is stored in DB in modules, so we have to cache modules info + 'enabled' => true, + 'key' => 'laravel-modules', + // Minues + 'lifetime' => 60, + ], + /* + |-------------------------------------------------------------------------- + | Choose what laravel-modules will register as custom namespaces. + | Setting one to false will require you to register that part + | in your own Service Provider class. + |-------------------------------------------------------------------------- + */ + 'register' => [ + 'translations' => true, + /* + * load files on boot or register method + * + * Note: boot not compatible with asgardcms + * + * @example boot|register + */ + 'files' => 'register', + ], +]; diff --git a/freescout-dist/config/purifier.php b/freescout-dist/config/purifier.php new file mode 100644 index 0000000..9435e4f --- /dev/null +++ b/freescout-dist/config/purifier.php @@ -0,0 +1,116 @@ +set('Core.Encoding', $this->config->get('purifier.encoding')); + * $config->set('Cache.SerializerPath', $this->config->get('purifier.cachePath')); + * if ( ! $this->config->get('purifier.finalize')) { + * $config->autoFinalize = false; + * } + * $config->loadArray($this->getConfig());. + * + * You must NOT delete the default settings + * anything in settings should be compacted with params that needed to instance HTMLPurifier_Config. + * + * @link http://htmlpurifier.org/live/configdoc/plain.html + */ + +return [ + 'encoding' => 'UTF-8', + 'finalize' => true, + 'cachePath' => storage_path('app/purifier'), + 'cacheFileMode' => 0755, + 'settings' => [ + 'default' => [ + 'HTML.Doctype' => 'HTML 4.01 Transitional', + 'HTML.Allowed' => 'div[style],b,strong,i,em,u,a[href|title],ul,ol,li,p[style],br,span[style],img[width|height|alt|src],table[width|class],tr[bgcolor],td[style|colspan|rowspan|width],th[style|colspan|rowspan],thead,tfoot,tbody,blockquote,pre,s,strike,font[style|color],h1,h2,h3,h4,h5,h6', + //'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align', + 'CSS.AllowedProperties' => 'font-weight,font-style,text-decoration,color,background-color,text-align,border,border-top,border-left,border-bottom,border-right', + 'AutoFormat.AutoParagraph' => true, + 'AutoFormat.RemoveEmpty' => true, + 'URI.AllowedSchemes' => [ + 'http' => true, + 'https' => true, + 'mailto' => true, + 'ftp' => true, + 'nntp' => true, + 'news' => true, + 'tel' => true, + 'data' => true, + ], + ], + 'test' => [ + 'Attr.EnableID' => 'true', + ], + 'youtube' => [ + 'HTML.SafeIframe' => 'true', + 'URI.SafeIframeRegexp' => '%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/)%', + ], + 'custom_definition' => [ + 'id' => 'html5-definitions', + 'rev' => 1, + 'debug' => false, + 'elements' => [ + // http://developers.whatwg.org/sections.html + ['section', 'Block', 'Flow', 'Common'], + ['nav', 'Block', 'Flow', 'Common'], + ['article', 'Block', 'Flow', 'Common'], + ['aside', 'Block', 'Flow', 'Common'], + ['header', 'Block', 'Flow', 'Common'], + ['footer', 'Block', 'Flow', 'Common'], + + // Content model actually excludes several tags, not modelled here + ['address', 'Block', 'Flow', 'Common'], + ['hgroup', 'Block', 'Required: h1 | h2 | h3 | h4 | h5 | h6', 'Common'], + + // http://developers.whatwg.org/grouping-content.html + ['figure', 'Block', 'Optional: (figcaption, Flow) | (Flow, figcaption) | Flow', 'Common'], + ['figcaption', 'Inline', 'Flow', 'Common'], + + // http://developers.whatwg.org/the-video-element.html#the-video-element + ['video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', [ + 'src' => 'URI', + 'type' => 'Text', + 'width' => 'Length', + 'height' => 'Length', + 'poster' => 'URI', + 'preload' => 'Enum#auto,metadata,none', + 'controls' => 'Bool', + ]], + ['source', 'Block', 'Flow', 'Common', [ + 'src' => 'URI', + 'type' => 'Text', + ]], + + // http://developers.whatwg.org/text-level-semantics.html + ['s', 'Inline', 'Inline', 'Common'], + ['var', 'Inline', 'Inline', 'Common'], + ['sub', 'Inline', 'Inline', 'Common'], + ['sup', 'Inline', 'Inline', 'Common'], + ['mark', 'Inline', 'Inline', 'Common'], + ['wbr', 'Inline', 'Empty', 'Core'], + + // http://developers.whatwg.org/edits.html + ['ins', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']], + ['del', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']], + ], + 'attributes' => [ + ['iframe', 'allowfullscreen', 'Bool'], + ['table', 'height', 'Text'], + ['td', 'border', 'Text'], + ['th', 'border', 'Text'], + ['tr', 'width', 'Text'], + ['tr', 'height', 'Text'], + ['tr', 'border', 'Text'], + ], + ], + 'custom_attributes' => [ + ['a', 'target', 'Enum#_blank,_self,_target,_top'], + ], + 'custom_elements' => [ + ['u', 'Inline', 'Inline', 'Common'], + ], + ], + +]; diff --git a/freescout-dist/config/queue.php b/freescout-dist/config/queue.php new file mode 100644 index 0000000..9d7162e --- /dev/null +++ b/freescout-dist/config/queue.php @@ -0,0 +1,86 @@ + env('QUEUE_DRIVER', 'database'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection information for each server that + | is used by your application. A default configuration has been added + | for each back-end shipped with Laravel. You are free to add more. + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'jobs', + 'queue' => 'default', + // Retry after does not work as delay between retry attempts. + 'retry_after' => 90, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => 'localhost', + 'queue' => 'default', + 'retry_after' => 90, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('SQS_KEY', 'your-public-key'), + 'secret' => env('SQS_SECRET', 'your-secret-key'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'your-queue-name'), + 'region' => env('SQS_REGION', 'us-east-1'), + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + 'queue' => 'default', + 'retry_after' => 90, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control which database and table are used to store the jobs that + | have failed. You may change them to any database / table you wish. + | + */ + + 'failed' => [ + 'database' => env('DB_CONNECTION', 'mysql'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/freescout-dist/config/self-update.php b/freescout-dist/config/self-update.php new file mode 100644 index 0000000..56ee697 --- /dev/null +++ b/freescout-dist/config/self-update.php @@ -0,0 +1,134 @@ + env('SELF_UPDATER_SOURCE', 'github'), + + /* + |-------------------------------------------------------------------------- + | Version installed + |-------------------------------------------------------------------------- + | + | Set this to the version of your software installed on your system. + | + */ + + 'version_installed' => config('app.version'), + + /* + |-------------------------------------------------------------------------- + | Repository types + |-------------------------------------------------------------------------- + | + | A repository can be of different types, which can be specified here. + | Current options: + | - github + | + */ + + 'repository_types' => [ + 'github' => [ + 'type' => 'github', + 'repository_vendor' => 'freescout-helpdesk', //env('SELF_UPDATER_REPO_VENDOR', ''), + 'repository_name' => 'freescout', //env('SELF_UPDATER_REPO_NAME', ''), + 'repository_url' => '', + 'download_path' => storage_path().DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.'updater', //env('SELF_UPDATER_DOWNLOAD_PATH', sys_get_temp_dir()), + ], + ], + + /* + |-------------------------------------------------------------------------- + | Exclude folders from update + |-------------------------------------------------------------------------- + | + | Specifiy folders which should not be updated and will be skipped during the + | update process. + | + | Here's already a list of good examples to skip. You may want to keep those. + | + */ + + 'exclude_folders' => [ + '.git', + 'node_modules', + 'bootstrap/cache', + 'bower', + 'storage/app', + 'storage/framework', + 'storage/logs', + 'storage/self-update', + 'vendor', + ], + + /* + |-------------------------------------------------------------------------- + | Event Logging + |-------------------------------------------------------------------------- + | + | Configure if fired events should be logged + | + */ + + 'log_events' => env('SELF_UPDATER_LOG_EVENTS', false), + + /* + |-------------------------------------------------------------------------- + | Mail To Settings + |-------------------------------------------------------------------------- + | + | Configure if fired events should be logged + | + */ + + 'mail_to' => [ + 'address' => env('SELF_UPDATER_MAILTO_ADDRESS', ''), + 'name' => env('SELF_UPDATER_MAILTO_NAME', ''), + 'subject_update_available' => env('SELF_UPDATER_MAILTO_UPDATE_AVAILABLE_SUBJECT', 'Update available'), + 'subject_update_succeeded' => env('SELF_UPDATER_MAILTO_UPDATE_SUCCEEDED_SUBJECT', 'Update succeeded'), + ], + + /* + |--------------------------------------------------------------------------- + | Register custom artisan commands + |--------------------------------------------------------------------------- + */ + + 'artisan_commands' => [ + 'pre_update' => [ + //'command:signature' => [ + // 'class' => Command class + // 'params' => [] + //] + ], + 'post_update' => [ + 'freescout:after-app-update' => [ + 'class' => \App\Console\Commands\AfterAppUpdate::class, + 'params' => [], + ], + // 'freescout:clear-cache' => [ + // 'class' => \App\Console\Commands\ClearCache::class, + // 'params' => [], + // ], + // 'migrate' => [ + // 'class' => \Illuminate\Database\Console\Migrations\MigrateCommand::class, + // 'params' => [ + // '--force' => true, + // ] + // ], + // 'queue:restart' => [ + // 'class' => \Illuminate\Queue\Console\RestartCommand::class, + // 'params' => [], + // ], + ], + ], + +]; diff --git a/freescout-dist/config/services.php b/freescout-dist/config/services.php new file mode 100644 index 0000000..6bb0952 --- /dev/null +++ b/freescout-dist/config/services.php @@ -0,0 +1,38 @@ + [ + 'domain' => env('MAILGUN_DOMAIN'), + 'secret' => env('MAILGUN_SECRET'), + ], + + 'ses' => [ + 'key' => env('SES_KEY'), + 'secret' => env('SES_SECRET'), + 'region' => 'us-east-1', + ], + + 'sparkpost' => [ + 'secret' => env('SPARKPOST_SECRET'), + ], + + 'stripe' => [ + 'model' => App\User::class, + 'key' => env('STRIPE_KEY'), + 'secret' => env('STRIPE_SECRET'), + ], + +]; diff --git a/freescout-dist/config/session.php b/freescout-dist/config/session.php new file mode 100644 index 0000000..736fb3c --- /dev/null +++ b/freescout-dist/config/session.php @@ -0,0 +1,197 @@ + env('SESSION_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to immediately expire on the browser closing, set that option. + | + */ + + 'lifetime' => env('SESSION_LIFETIME', 120), + + 'expire_on_close' => false, + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it is stored. All encryption will be run + | automatically by Laravel and you can use the Session like normal. + | + */ + + 'encrypt' => false, + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When using the native session driver, we need a location where session + | files may be stored. A default has been set for you but a different + | location may be specified. This is only needed for file sessions. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => null, + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table we + | should use to manage the sessions. Of course, a sensible default is + | provided for you; however, you are free to change this as needed. + | + */ + + 'table' => 'sessions', + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | When using the "apc" or "memcached" session drivers, you may specify a + | cache store that should be used for these sessions. This value must + | correspond with one of the application's configured cache stores. + | + */ + + 'store' => null, + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the cookie used to identify a session + | instance by ID. The name specified here will get used every time a + | new session cookie is created by the framework for every driver. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + str_slug(env('APP_NAME', 'laravel'), '_').'_session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application but you are free to change this when necessary. + | + */ + + 'path' => '/', + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | Here you may change the domain of the cookie used to identify a session + | in your application. This will determine which domains the cookie is + | available to in your application. A sensible default has been set. + | + */ + + 'domain' => env('SESSION_DOMAIN', null), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you if it can not be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE', false), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. You are free to modify this option if needed. + | + */ + + 'http_only' => true, + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | do not enable this as other CSRF protection services are in place. + | + | Supported: "lax", "strict" + | + */ + + 'same_site' => null, + +]; diff --git a/freescout-dist/config/subscriptions.php b/freescout-dist/config/subscriptions.php new file mode 100644 index 0000000..ee7757a --- /dev/null +++ b/freescout-dist/config/subscriptions.php @@ -0,0 +1,31 @@ + [ + \App\Subscription::MEDIUM_EMAIL => [ + \App\Subscription::EVENT_CONVERSATION_ASSIGNED_TO_ME, + \App\Subscription::EVENT_FOLLOWED_CONVERSATION_UPDATED, + //\App\Subscription::EVENT_MY_TEAM_MENTIONED, + \App\Subscription::EVENT_CUSTOMER_REPLIED_TO_MY, + \App\Subscription::EVENT_USER_REPLIED_TO_MY, + ], + \App\Subscription::MEDIUM_BROWSER => [ + \App\Subscription::EVENT_CONVERSATION_ASSIGNED_TO_ME, + \App\Subscription::EVENT_FOLLOWED_CONVERSATION_UPDATED, + //\App\Subscription::EVENT_MY_TEAM_MENTIONED, + \App\Subscription::EVENT_CUSTOMER_REPLIED_TO_MY, + \App\Subscription::EVENT_USER_REPLIED_TO_MY, + ], + ], + +]; diff --git a/freescout-dist/config/translation-manager.php b/freescout-dist/config/translation-manager.php new file mode 100644 index 0000000..e12ce54 --- /dev/null +++ b/freescout-dist/config/translation-manager.php @@ -0,0 +1,88 @@ + [ + 'prefix' => trim(parse_url(env('APP_URL', ''), PHP_URL_PATH) ?: '', '/').'/translations', + 'middleware' => [ + 'web', + 'auth', + 'roles', + ], + // todo: replace admin with superadmin + 'roles' => ['admin'], + ], + + /* + * Enable deletion of translations + * + * @type boolean + */ + 'delete_enabled' => true, + + /* + * Exclude specific groups from Laravel Translation Manager. + * This is useful if, for example, you want to avoid editing the official Laravel language files. + * + * @type array + * + * array( + * 'pagination', + * 'reminders', + * 'validation', + * ) + */ + 'exclude_groups' => [ + 'auth', + 'reminders', + 'pagination', + 'passwords', + 'validation', + 'installer_messages', + ], + + /* + * Regular expression may determine goup incorrectly, for example for 'e.g' + */ + 'incorrect_groups' => [ + 'e', + ], + /* + * Exclude specific languages from Laravel Translation Manager. + * + * @type array + * + * array( + * 'fr', + * 'de', + * ) + */ + 'exclude_langs' => [], + + /* + * Export translations with keys output alphabetically. + */ + 'sort_keys ' => true, + + 'trans_functions' => [ + 'trans', + 'trans_choice', + 'Lang::get', + 'Lang::choice', + 'Lang::trans', + 'Lang::transChoice', + '@lang', + '@choice', + '__', + '$trans.get', + ], + +]; diff --git a/freescout-dist/config/trustedproxy.php b/freescout-dist/config/trustedproxy.php new file mode 100644 index 0000000..79a7d8f --- /dev/null +++ b/freescout-dist/config/trustedproxy.php @@ -0,0 +1,74 @@ +getClientIp() + * always gets the originating client IP, no matter + * how many proxies that client's request has + * subsequently passed through. + */ + // 'proxies' => [ + // '192.168.1.10', + // ], + 'proxies' => preg_match("#^\*{1,2}$#", env('APP_TRUSTED_PROXIES', '')) + ? env('APP_TRUSTED_PROXIES', '') + : explode(',', env('APP_TRUSTED_PROXIES', '')), + + /* + * Or, to trust all proxies that connect + * directly to your server, uncomment this: + */ + # 'proxies' => '*', + + /* + * Or, to trust ALL proxies, including those that + * are in a chain of forwarding, uncomment this: + */ + # 'proxies' => '**', + + /* + * Default Header Names + * + * Change these if the proxy does + * not send the default header names. + * + * Note that headers such as X-Forwarded-For + * are transformed to HTTP_X_FORWARDED_FOR format. + * + * The following are Symfony defaults, found in + * \Symfony\Component\HttpFoundation\Request::$trustedHeaders + * + * You may optionally set headers to 'null' here if you'd like + * for them to be considered untrusted instead. Ex: + * + * Illuminate\Http\Request::HEADER_CLIENT_HOST => null, + * + * WARNING: If you're using AWS Elastic Load Balancing or Heroku, + * the FORWARDED and X_FORWARDED_HOST headers should be set to null + * as they are currently unsupported there. + */ + 'headers' => [ + (defined('Illuminate\Http\Request::HEADER_FORWARDED') ? Illuminate\Http\Request::HEADER_FORWARDED : 'forwarded') => 'FORWARDED', + Illuminate\Http\Request::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + Illuminate\Http\Request::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + Illuminate\Http\Request::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', + Illuminate\Http\Request::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + ] +]; diff --git a/freescout-dist/config/view.php b/freescout-dist/config/view.php new file mode 100644 index 0000000..2acfd9c --- /dev/null +++ b/freescout-dist/config/view.php @@ -0,0 +1,33 @@ + [ + resource_path('views'), + ], + + /* + |-------------------------------------------------------------------------- + | Compiled View Path + |-------------------------------------------------------------------------- + | + | This option determines where all the compiled Blade templates will be + | stored for your application. Typically, this is within the storage + | directory. However, as usual, you are free to change this value. + | + */ + + 'compiled' => realpath(storage_path('framework/views')), + +]; diff --git a/freescout-dist/database/.gitignore b/freescout-dist/database/.gitignore new file mode 100644 index 0000000..9b1dffd --- /dev/null +++ b/freescout-dist/database/.gitignore @@ -0,0 +1 @@ +*.sqlite diff --git a/freescout-dist/database/factories/ConversationFactory.php b/freescout-dist/database/factories/ConversationFactory.php new file mode 100644 index 0000000..42663e6 --- /dev/null +++ b/freescout-dist/database/factories/ConversationFactory.php @@ -0,0 +1,44 @@ +define(Conversation::class, function (Faker $faker, $params) { + if (!empty($params['created_by_user_id'])) { + $created_by_user_id = $params['created_by_user_id']; + } else { + // Pick random user + $created_by_user_id = App\User::inRandomOrder()->first()->id; + } + $folder_id = null; + if (!empty($params['folder_id'])) { + $folder_id = $params['folder_id']; + } elseif (!empty($params['mailbox_id'])) { + // Pick folder + $folder = App\Folder::where(['mailbox_id' => $params['mailbox_id'], 'type' => App\Folder::TYPE_UNASSIGNED])->first(); + if ($folder) { + $folder_id = $folder->id; + } else { + $folder_id = factory(App\Folder::class)->create()->id; + } + } + $customer_email = $faker->unique()->safeEmail; + if (!empty($params['customer_email'])) { + $customer_email = $params['customer_email']; + } + + return [ + 'type' => $faker->randomElement([Conversation::TYPE_EMAIL, Conversation::TYPE_PHONE]), + 'folder_id' => $folder_id, + 'state' => Conversation::STATE_PUBLISHED, // $faker->randomElement(array_keys(Conversation::$states)), + 'subject' => $faker->sentence(7), + 'customer_email' => $customer_email, + 'cc' => json_encode([$faker->unique()->safeEmail]), + 'bcc' => json_encode([$faker->unique()->safeEmail]), + 'preview' => $faker->text(Conversation::PREVIEW_MAXLENGTH), + 'imported' => true, + 'created_by_user_id' => $created_by_user_id, + 'source_via' => Conversation::PERSON_CUSTOMER, + 'source_type' => Conversation::SOURCE_TYPE_EMAIL, + ]; +}); diff --git a/freescout-dist/database/factories/CustomerFactory.php b/freescout-dist/database/factories/CustomerFactory.php new file mode 100644 index 0000000..20d541c --- /dev/null +++ b/freescout-dist/database/factories/CustomerFactory.php @@ -0,0 +1,13 @@ +define(App\Customer::class, function (Faker $faker) { + return [ + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'job_title' => $faker->jobTitle, + 'phones' => json_encode(Customer::formatPhones([['value' => $faker->phoneNumber, 'type' => Customer::PHONE_TYPE_WORK]])), + ]; +}); diff --git a/freescout-dist/database/factories/EmailFactory.php b/freescout-dist/database/factories/EmailFactory.php new file mode 100644 index 0000000..55029d4 --- /dev/null +++ b/freescout-dist/database/factories/EmailFactory.php @@ -0,0 +1,9 @@ +define(App\Email::class, function (Faker $faker) { + return [ + 'email' => $faker->unique()->safeEmail, + ]; +}); diff --git a/freescout-dist/database/factories/FolderFactory.php b/freescout-dist/database/factories/FolderFactory.php new file mode 100644 index 0000000..ac9ca32 --- /dev/null +++ b/freescout-dist/database/factories/FolderFactory.php @@ -0,0 +1,21 @@ +define(App\Folder::class, function (Faker $faker, $params) { + $mailbox_id = null; + if (!empty($params['mailbox_id'])) { + $mailbox_id = $params['mailbox_id']; + } else { + $mailbox = App\Mailbox::inRandomOrder()->first(); + if ($mailbox) { + $mailbox_id = $mailbox->id; + } + } + + return [ + 'mailbox_id' => $mailbox_id, + 'type' => Folder::TYPE_UNASSIGNED, + ]; +}); diff --git a/freescout-dist/database/factories/MailboxFactory.php b/freescout-dist/database/factories/MailboxFactory.php new file mode 100644 index 0000000..e0f1b13 --- /dev/null +++ b/freescout-dist/database/factories/MailboxFactory.php @@ -0,0 +1,16 @@ +define(App\Mailbox::class, function (Faker $faker) { + $name = $faker->company; + $email = $faker->unique()->companyEmail; + $domain = explode('@', $email)[1]; + + return [ + 'name' => $name, + 'email' => $email, + 'aliases' => 'support@'.$domain.',help@'.$domain.', contact@'.$domain, + //'signature' => '--
'.$name, + ]; +}); diff --git a/freescout-dist/database/factories/ThreadFactory.php b/freescout-dist/database/factories/ThreadFactory.php new file mode 100644 index 0000000..9d48f39 --- /dev/null +++ b/freescout-dist/database/factories/ThreadFactory.php @@ -0,0 +1,39 @@ +define(Thread::class, function (Faker $faker, $params) { + if (!empty($params['customer_id'])) { + $customer_id = $params['customer_id']; + } else { + // Pick random customer + //$customer_id = $faker->randomElement(App\Customer::pluck('id')->toArray()); + $customer = User::inRandomOrder()->first(); + if (!$customer) { + $customer = factory(App\Customer::class)->create(); + } + $customer_id = $customer->id; + } + if (!empty($params['to'])) { + $to = $params['to']; + } elseif ($customer) { + $to = $customer->getMainEmail(); + } else { + $to = json_encode([$faker->unique()->safeEmail]); + } + + return [ + 'type' => Thread::TYPE_CUSTOMER, + //'conversation_id' => , + 'customer_id' => $customer_id, + 'state' => Thread::STATE_PUBLISHED, + 'body' => $faker->text(500), + 'to' => json_encode([$to]), + 'cc' => json_encode([$faker->unique()->safeEmail]), + 'bcc' => json_encode([$faker->unique()->safeEmail]), + 'source_via' => Thread::PERSON_CUSTOMER, + 'source_type' => Thread::SOURCE_TYPE_EMAIL, + 'created_by_customer_id' => $customer_id, + ]; +}); diff --git a/freescout-dist/database/factories/UserFactory.php b/freescout-dist/database/factories/UserFactory.php new file mode 100644 index 0000000..750c95e --- /dev/null +++ b/freescout-dist/database/factories/UserFactory.php @@ -0,0 +1,25 @@ +define(App\User::class, function (Faker $faker) { + return [ + 'first_name' => $faker->firstName, + 'last_name' => $faker->lastName, + 'email' => $faker->unique()->safeEmail, + 'job_title' => $faker->jobTitle, + 'phone' => $faker->phoneNumber, + 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret + ]; +}); diff --git a/freescout-dist/database/migrations/2014_04_02_193005_create_translations_table.php b/freescout-dist/database/migrations/2014_04_02_193005_create_translations_table.php new file mode 100644 index 0000000..5ca185c --- /dev/null +++ b/freescout-dist/database/migrations/2014_04_02_193005_create_translations_table.php @@ -0,0 +1,35 @@ +increments('id'); + $table->integer('status')->default(0); + $table->string('locale'); + $table->string('group'); + $table->text('key'); + $table->text('value')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('ltm_translations'); + } +} diff --git a/freescout-dist/database/migrations/2018_06_10_000000_create_users_table.php b/freescout-dist/database/migrations/2018_06_10_000000_create_users_table.php new file mode 100644 index 0000000..2507f6d --- /dev/null +++ b/freescout-dist/database/migrations/2018_06_10_000000_create_users_table.php @@ -0,0 +1,54 @@ +increments('id'); + $table->string('first_name', 20); + $table->string('last_name', 30); + $table->string('email', User::EMAIL_MAX_LENGTH)->unique(); + $table->string('password', 255); + $table->unsignedTinyInteger('role')->default(User::ROLE_USER)->index(); // admin/user + $table->string('timezone', 255)->default('UTC'); + $table->string('photo_url', 255)->nullable(); + $table->unsignedTinyInteger('type')->default(User::TYPE_USER); // team/user + $table->unsignedTinyInteger('invite_state')->default(User::INVITE_STATE_NOT_INVITED); + $table->string('invite_hash', 100)->nullable(); + // It is not clear how alternate user emails should be used. + // For now they are not used in the app and there is no uniqueness check. + $table->string('emails', 100)->nullable(); + $table->string('job_title', 100)->nullable(); + $table->string('phone', 60)->nullable(); + $table->unsignedTinyInteger('time_format')->default(User::TIME_FORMAT_24); + $table->boolean('enable_kb_shortcuts')->default(true); + //$table->boolean('is_user_workflow_related')->default(false); + // Not used for now. + $table->boolean('locked')->default(false); + $table->rememberToken(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('users'); + } +} diff --git a/freescout-dist/database/migrations/2018_06_10_100000_create_password_resets_table.php b/freescout-dist/database/migrations/2018_06_10_100000_create_password_resets_table.php new file mode 100644 index 0000000..6a78ba4 --- /dev/null +++ b/freescout-dist/database/migrations/2018_06_10_100000_create_password_resets_table.php @@ -0,0 +1,34 @@ +string('email', 191)->index(); + $table->string('token', 255); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('password_resets'); + } +} diff --git a/freescout-dist/database/migrations/2018_06_25_065719_create_mailboxes_table.php b/freescout-dist/database/migrations/2018_06_25_065719_create_mailboxes_table.php new file mode 100644 index 0000000..cbc60a6 --- /dev/null +++ b/freescout-dist/database/migrations/2018_06_25_065719_create_mailboxes_table.php @@ -0,0 +1,65 @@ +increments('id'); + $table->string('name', 40); + // Not used + $table->string('slug', 16)->unique()->nullable(); + $table->string('email', 128)->unique(); + $table->string('aliases', 255)->nullable(); + $table->unsignedTinyInteger('from_name')->default(Mailbox::FROM_NAME_MAILBOX); + $table->string('from_name_custom', 128)->nullable(); + $table->unsignedTinyInteger('ticket_status')->default(Mailbox::TICKET_STATUS_PENDING); + $table->unsignedTinyInteger('ticket_assignee')->default(Mailbox::TICKET_ASSIGNEE_REPLYING_UNASSIGNED); + $table->unsignedTinyInteger('template')->default(Mailbox::TEMPLATE_FANCY); + $table->text('signature')->nullable(); + $table->unsignedTinyInteger('out_method')->default(Mailbox::OUT_METHOD_PHP_MAIL); + $table->string('out_server', 255)->nullable(); + $table->string('out_username', 100)->nullable(); + $table->text('out_password')->nullable(); + $table->unsignedInteger('out_port')->nullable(); + $table->unsignedTinyInteger('out_encryption')->default(Mailbox::OUT_ENCRYPTION_NONE); + $table->string('in_server', 255)->nullable(); + $table->unsignedInteger('in_port')->default(143); // default IMAP port + $table->string('in_username', 100)->nullable(); + $table->text('in_password')->nullable(); + $table->unsignedTinyInteger('in_protocol')->default(Mailbox::IN_PROTOCOL_IMAP); + $table->unsignedTinyInteger('in_encryption')->default(Mailbox::IN_ENCRYPTION_NONE); + $table->boolean('auto_reply_enabled')->default(false); + $table->string('auto_reply_subject', 128)->nullable(); + $table->text('auto_reply_message')->nullable(); + // todo + $table->boolean('office_hours_enabled')->default(false); + $table->boolean('ratings')->default(false); + $table->unsignedTinyInteger('ratings_placement')->default(Mailbox::RATINGS_PLACEMENT_ABOVE); + $table->text('ratings_text')->nullable(); + // todo: translate ratings + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('mailboxes'); + } +} diff --git a/freescout-dist/database/migrations/2018_06_29_041002_create_mailbox_user_table.php b/freescout-dist/database/migrations/2018_06_29_041002_create_mailbox_user_table.php new file mode 100644 index 0000000..83178ca --- /dev/null +++ b/freescout-dist/database/migrations/2018_06_29_041002_create_mailbox_user_table.php @@ -0,0 +1,38 @@ +increments('id'); + $table->integer('mailbox_id'); + $table->integer('user_id'); + $table->unsignedTinyInteger('after_send')->default(MailboxUser::AFTER_SEND_NEXT); + + // Indexes + $table->unique(['user_id', 'mailbox_id']); + $table->index(['mailbox_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('mailbox_user'); + } +} diff --git a/freescout-dist/database/migrations/2018_07_07_071443_create_activity_logs_table.php b/freescout-dist/database/migrations/2018_07_07_071443_create_activity_logs_table.php new file mode 100644 index 0000000..eb4ee9b --- /dev/null +++ b/freescout-dist/database/migrations/2018_07_07_071443_create_activity_logs_table.php @@ -0,0 +1,35 @@ +increments('id'); + $table->string('log_name', 191)->nullable(); + $table->text('description'); + $table->integer('subject_id')->nullable(); + $table->string('subject_type', 255)->nullable(); + $table->integer('causer_id')->nullable(); + $table->string('causer_type', 55)->nullable(); + $table->text('properties')->nullable(); + $table->timestamps(); + + $table->index('log_name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::drop(config('activitylog.table_name')); + } +} diff --git a/freescout-dist/database/migrations/2018_07_09_052314_create_emails_table.php b/freescout-dist/database/migrations/2018_07_09_052314_create_emails_table.php new file mode 100644 index 0000000..6ee4ccf --- /dev/null +++ b/freescout-dist/database/migrations/2018_07_09_052314_create_emails_table.php @@ -0,0 +1,38 @@ +increments('id'); + $table->integer('customer_id')->index(); + // Max email length is 255, but if we specify 255, we get can not create an index: + // SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes + $table->string('email', 191)->unique(); + // Type is not used in the web interface, but appears in API + $table->unsignedTinyInteger('type')->default(Email::TYPE_WORK); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('emails'); + } +} diff --git a/freescout-dist/database/migrations/2018_07_09_053559_create_customers_table.php b/freescout-dist/database/migrations/2018_07_09_053559_create_customers_table.php new file mode 100644 index 0000000..36560ff --- /dev/null +++ b/freescout-dist/database/migrations/2018_07_09_053559_create_customers_table.php @@ -0,0 +1,59 @@ +increments('id'); + $table->string('first_name', 255)->nullable(); + $table->string('last_name', 255)->nullable(); + $table->string('company', 255)->nullable(); + $table->string('job_title', 255)->nullable(); + $table->unsignedTinyInteger('photo_type')->nullable(); + $table->string('photo_url', 255)->nullable(); + // Age and gender do not exist in the web interface, but exist in the API + $table->string('age', 7)->nullable(); + $table->unsignedTinyInteger('gender')->nullable(); + $table->text('phones')->nullable(); // JSON + $table->text('websites')->nullable(); // JSON + $table->text('social_profiles')->nullable(); // JSON + $table->text('chats')->nullable(); // JSON + $table->text('background')->nullable(); + $table->text('address')->nullable(); + $table->string('city', 255)->nullable(); + $table->string('state', 255)->nullable(); + $table->string('zip', 12)->nullable(); + $table->string('country', 2)->nullable(); + $table->timestamps(); + + // Indexes + // For ajax search + if (DB::connection()->getPDO()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') { + $table->index([DB::raw('first_name(80)'), DB::raw('last_name(80)')]); + } else { + $table->index(['first_name', 'last_name']); + } + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('customers'); + } +} diff --git a/freescout-dist/database/migrations/2018_07_11_010333_create_conversations_table.php b/freescout-dist/database/migrations/2018_07_11_010333_create_conversations_table.php new file mode 100644 index 0000000..e3ca49c --- /dev/null +++ b/freescout-dist/database/migrations/2018_07_11_010333_create_conversations_table.php @@ -0,0 +1,91 @@ +increments('id'); + // Conversation Number + $table->unsignedInteger('number'); + // Number of threads the conversation has + // Lineitems and notes are not counted + $table->unsignedInteger('threads_count')->default(0); + $table->unsignedTinyInteger('type'); + $table->integer('folder_id'); + $table->unsignedTinyInteger('status')->default(Conversation::STATUS_ACTIVE); + $table->unsignedTinyInteger('state')->default(Conversation::STATE_DRAFT); + // It has to be optional in order to create empty drafts. + $table->string('subject', 998)->nullable(); + // Customer's email to which replies from users are sent. + // Not used when fetching emails. + // Customer may have several emails, so we need to know which + // email to use for each conversation. + $table->string('customer_email', 191)->nullable(); + // CC and BCC store values from the last reply from customer or user + // For incoming messages values are stored as is + $table->text('cc')->nullable(); // JSON + $table->text('bcc')->nullable(); // JSON + // Preview stores the body of the latest reply or note. + $table->string('preview', Conversation::PREVIEW_MAXLENGTH); + // The imported field enables conversation to be created for historical purposes + // (i.e. if moving from a different platform, you can import your history). + // When imported is set to true, no outgoing emails or notifications will be generated. + $table->boolean('imported')->default(false); + $table->boolean('has_attachments')->default(false); + $table->integer('mailbox_id'); + // assignee - Who the conversation is assigned to + $table->integer('user_id')->nullable(); + // primaryCustomer + // It has to be optional in order to create empty drafts. + $table->integer('customer_id')->nullable(); + // Originating source of the conversation - user or customer + // ID of the customer or user who created the conversation + // createdBy in the API + $table->integer('created_by_user_id')->nullable(); + $table->integer('created_by_customer_id')->nullable(); + // source.via - Originating source of the conversation - user or customer + $table->unsignedTinyInteger('source_via'); + // source.type - Originating type of the conversation (email, web, API etc) + $table->unsignedTinyInteger('source_type'); + // closedBy - ID of the user who closed the conversation + $table->integer('closed_by_user_id')->nullable(); + // UTC time when the conversation was closed + $table->timestamp('closed_at')->nullable(); + // UTC time when the last user update occurred + $table->timestamp('user_updated_at')->nullable(); + // customerWaitingSince - reply by customer or user + $table->timestamp('last_reply_at')->nullable(); + // Whether the last reply was from a user or a customer + $table->unsignedTinyInteger('last_reply_from')->nullable(); + // Thread read by any user (used to display spam folder) - not used + $table->boolean('read_by_user')->default(false); + $table->timestamps(); + + // Indexes + $table->index(['folder_id', 'status']); + $table->index(['mailbox_id', 'customer_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('conversations'); + } +} diff --git a/freescout-dist/database/migrations/2018_07_11_074558_create_folders_table.php b/freescout-dist/database/migrations/2018_07_11_074558_create_folders_table.php new file mode 100644 index 0000000..6de6e7b --- /dev/null +++ b/freescout-dist/database/migrations/2018_07_11_074558_create_folders_table.php @@ -0,0 +1,37 @@ +increments('id'); + $table->integer('mailbox_id'); + $table->integer('user_id')->nullable(); + $table->unsignedTinyInteger('type'); + $table->integer('total_count')->default(0); + $table->integer('active_count')->default(0); + + $table->unique(['mailbox_id', 'user_id', 'type']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('folders'); + } +} diff --git a/freescout-dist/database/migrations/2018_07_11_081928_create_conversation_folder_table.php b/freescout-dist/database/migrations/2018_07_11_081928_create_conversation_folder_table.php new file mode 100644 index 0000000..454ec44 --- /dev/null +++ b/freescout-dist/database/migrations/2018_07_11_081928_create_conversation_folder_table.php @@ -0,0 +1,37 @@ +increments('id'); + $table->integer('folder_id'); + $table->integer('conversation_id'); + + // Indexes + $table->unique(['folder_id', 'conversation_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('conversation_folder'); + } +} diff --git a/freescout-dist/database/migrations/2018_07_12_003318_create_threads_table.php b/freescout-dist/database/migrations/2018_07_12_003318_create_threads_table.php new file mode 100644 index 0000000..ae2bc86 --- /dev/null +++ b/freescout-dist/database/migrations/2018_07_12_003318_create_threads_table.php @@ -0,0 +1,96 @@ +increments('id'); + $table->integer('conversation_id'); + // assignedTo - The user assigned to this thread + // used to display user who was assigned to the thread in the conversation + $table->integer('user_id')->nullable(); + $table->unsignedTinyInteger('type'); + $table->unsignedTinyInteger('status')->default(Thread::STATUS_ACTIVE); + $table->unsignedTinyInteger('state')->default(Thread::STATE_DRAFT); + // Describes an optional action associated with the line item + $table->unsignedTinyInteger('action_type')->nullable(); + // Stores extra data for each action + $table->string('action_data', 255)->nullable(); + // lineitems do not have body + $table->longText('body')->nullable(); + $table->text('headers')->nullable(); + // Email from + $table->string('from', 191)->nullable(); + // To, CC and BCC are storing original incoming message values. + // Used for display only. + // For messages from user To must be customer's email. + $table->text('to')->nullable(); // JSON + $table->text('cc')->nullable(); // JSON + $table->text('bcc')->nullable(); // JSON + $table->boolean('has_attachments')->default(false); + // Email Message-ID header for email received from customer or uer + // In message_id we are storing Message-ID of the incoming email which created the thread + // Outcoming message_id can be generated for each thread by thread->id + $table->string('message_id', 998)->nullable(); + // source.via - Originating source of the thread - user or customer + $table->unsignedTinyInteger('source_via'); + // source.type - Originating type of the thread (email, web, API etc) + $table->unsignedTinyInteger('source_type'); + // customer - If thread type is message, this is the customer associated with the thread. + // If thread type is customer, this is the the customer who initiated the thread. + // It has to be optional in order to create empty drafts. + $table->integer('customer_id')->nullable(); + // Who created this thread. The source_via property will specify whether it was created by a user or a customer. + // See source_via + $table->integer('created_by_user_id')->nullable(); + $table->integer('created_by_customer_id')->nullable(); + // Thread has been adited by user + $table->integer('edited_by_user_id')->nullable(); + $table->timestamp('edited_at')->nullable(); + // Original body after thread text is changed + $table->longText('body_original')->nullable(); + // First thread in conversation + $table->boolean('first')->default(false); + // ID of Saved reply that was used to create this Thread (savedReplyId) + $table->integer('saved_reply_id')->nullable(); + // Status of the email sent to the customer or user, to whom the thread is assigned. + // Stores status of the latest event, values are stored in SendLog. + $table->unsignedTinyInteger('send_status')->nullable(); + // Email opened by customer + $table->timestamp('opened_at')->nullable(); + $table->timestamps(); + + if (DB::connection()->getPDO()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') { + // https://github.com/laravel/framework/issues/9293#issuecomment-373229281 + $table->unique([DB::raw('message_id(191)')], 'threads_message_id_index'); + } else { + $table->unique('message_id', 'threads_message_id_index'); + } + + // On conversation page + $table->index(['conversation_id', 'type', 'from', 'customer_id']); + $table->index(['conversation_id', 'created_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('threads'); + } +} diff --git a/freescout-dist/database/migrations/2018_07_30_153206_create_jobs_table.php b/freescout-dist/database/migrations/2018_07_30_153206_create_jobs_table.php new file mode 100644 index 0000000..1be9e8a --- /dev/null +++ b/freescout-dist/database/migrations/2018_07_30_153206_create_jobs_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('jobs'); + } +} diff --git a/freescout-dist/database/migrations/2018_07_30_165237_create_failed_jobs_table.php b/freescout-dist/database/migrations/2018_07_30_165237_create_failed_jobs_table.php new file mode 100644 index 0000000..389bdf7 --- /dev/null +++ b/freescout-dist/database/migrations/2018_07_30_165237_create_failed_jobs_table.php @@ -0,0 +1,35 @@ +bigIncrements('id'); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('failed_jobs'); + } +} diff --git a/freescout-dist/database/migrations/2018_08_04_063414_create_attachments_table.php b/freescout-dist/database/migrations/2018_08_04_063414_create_attachments_table.php new file mode 100644 index 0000000..eecb379 --- /dev/null +++ b/freescout-dist/database/migrations/2018_08_04_063414_create_attachments_table.php @@ -0,0 +1,41 @@ +increments('id'); + $table->integer('thread_id')->nullable(); + $table->integer('user_id')->nullable(); + $table->string('file_dir', 20)->nullable(); // examples: 1/2, 1/2/3 + $table->string('file_name', 255); + $table->string('mime_type', 127); + $table->unsignedInteger('type'); + $table->unsignedInteger('size')->nullable(); + $table->boolean('embedded')->default(false); + + // Indexes + $table->index(['thread_id', 'embedded']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('attachments'); + } +} diff --git a/freescout-dist/database/migrations/2018_08_05_045458_create_options_table.php b/freescout-dist/database/migrations/2018_08_05_045458_create_options_table.php new file mode 100644 index 0000000..8fdadc8 --- /dev/null +++ b/freescout-dist/database/migrations/2018_08_05_045458_create_options_table.php @@ -0,0 +1,32 @@ +increments('id'); + $table->string('name', 191)->unique(); + $table->longText('value'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('options'); + } +} diff --git a/freescout-dist/database/migrations/2018_08_05_153518_create_subscriptions_table.php b/freescout-dist/database/migrations/2018_08_05_153518_create_subscriptions_table.php new file mode 100644 index 0000000..ad74b8c --- /dev/null +++ b/freescout-dist/database/migrations/2018_08_05_153518_create_subscriptions_table.php @@ -0,0 +1,37 @@ +increments('id'); + $table->integer('user_id'); + $table->unsignedTinyInteger('medium'); + $table->unsignedTinyInteger('event'); + + // Indexes + $table->unique(['user_id', 'medium', 'event']); + $table->index(['user_id', 'event']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('subscriptions'); + } +} diff --git a/freescout-dist/database/migrations/2018_08_06_114901_create_send_logs_table.php b/freescout-dist/database/migrations/2018_08_06_114901_create_send_logs_table.php new file mode 100644 index 0000000..e66a1ae --- /dev/null +++ b/freescout-dist/database/migrations/2018_08_06_114901_create_send_logs_table.php @@ -0,0 +1,55 @@ +increments('id'); + $table->integer('thread_id')->nullable()->index(); + // Customer ID is set only if email sent to the main conversation customer + $table->integer('customer_id')->nullable(); + $table->integer('user_id')->nullable(); + // Message-ID header of the outgoing email + $table->string('message_id', 998)->nullable(); + // We have to keep email as customer's or user's email may change + $table->string('email', 191); + $table->unsignedTinyInteger('mail_type'); + $table->unsignedTinyInteger('status'); + $table->string('status_message', 255)->nullable(); + $table->timestamps(); + + // Indexes + if (DB::connection()->getPDO()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') { + // https://github.com/laravel/framework/issues/9293#issuecomment-373229281 + $table->index([DB::raw('message_id(191)')], 'send_logs_message_id_index'); + } else { + $table->index(['message_id'], 'send_logs_message_id_index'); + } + + // Used when sending auto reply + $table->index(['customer_id', 'mail_type', 'created_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('send_logs'); + } +} diff --git a/freescout-dist/database/migrations/2018_09_05_024109_create_notifications_table.php b/freescout-dist/database/migrations/2018_09_05_024109_create_notifications_table.php new file mode 100644 index 0000000..781b9f5 --- /dev/null +++ b/freescout-dist/database/migrations/2018_09_05_024109_create_notifications_table.php @@ -0,0 +1,37 @@ +uuid('id')->primary(); + $table->string('type'); + $table->morphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('notifications'); + } +} diff --git a/freescout-dist/database/migrations/2018_09_05_033609_create_polycast_events_table.php b/freescout-dist/database/migrations/2018_09_05_033609_create_polycast_events_table.php new file mode 100644 index 0000000..b00136c --- /dev/null +++ b/freescout-dist/database/migrations/2018_09_05_033609_create_polycast_events_table.php @@ -0,0 +1,36 @@ +engine = 'InnoDB'; + + $table->increments('id'); + $table->text('channels'); + $table->text('event'); + $table->text('payload'); + $table->timestamp('created_at')->nullable()->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('polycast_events'); + } +} diff --git a/freescout-dist/database/migrations/2018_11_04_113009_create_modules_table.php b/freescout-dist/database/migrations/2018_11_04_113009_create_modules_table.php new file mode 100644 index 0000000..481bfc6 --- /dev/null +++ b/freescout-dist/database/migrations/2018_11_04_113009_create_modules_table.php @@ -0,0 +1,35 @@ +increments('id'); + $table->string('alias', 191)->unique(); + $table->boolean('active')->default(false); + // Module license is activated + $table->boolean('activated')->default(false); + $table->string('license', 32)->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('modules'); + } +} diff --git a/freescout-dist/database/migrations/2018_11_13_143000_encrypt_mailbox_password.php b/freescout-dist/database/migrations/2018_11_13_143000_encrypt_mailbox_password.php new file mode 100644 index 0000000..697171d --- /dev/null +++ b/freescout-dist/database/migrations/2018_11_13_143000_encrypt_mailbox_password.php @@ -0,0 +1,48 @@ +string('in_password', 512)->nullable()->change(); + }); + + foreach (\App\Mailbox::whereNotNull('in_password')->get() as $Mailbox) { + $Mailbox->in_password = $Mailbox->getOriginal('in_password'); + $Mailbox->save(); + } + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + if (version_compare(config('app.version'), '1.0.6', '<')) { + foreach (\App\Mailbox::whereNotNull('in_password')->get() as $Mailbox) { + $attributes = $Mailbox->getAttributes(); + $attributes = array_merge($attributes, ['in_password' => $Mailbox->in_password]); + $Mailbox->setRawAttributes($attributes); + $Mailbox->save(); + } + + Schema::table('mailboxes', function (Blueprint $table) { + $table->string('in_password', 255)->nullable()->change(); + }); + } + } +} diff --git a/freescout-dist/database/migrations/2018_11_26_122617_add_locale_column_to_users_table.php b/freescout-dist/database/migrations/2018_11_26_122617_add_locale_column_to_users_table.php new file mode 100644 index 0000000..3bff528 --- /dev/null +++ b/freescout-dist/database/migrations/2018_11_26_122617_add_locale_column_to_users_table.php @@ -0,0 +1,32 @@ +string('locale', 191)->after('remember_token')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('locale'); + }); + } +} diff --git a/freescout-dist/database/migrations/2018_12_11_130728_add_status_column_to_users_table.php b/freescout-dist/database/migrations/2018_12_11_130728_add_status_column_to_users_table.php new file mode 100644 index 0000000..f801d78 --- /dev/null +++ b/freescout-dist/database/migrations/2018_12_11_130728_add_status_column_to_users_table.php @@ -0,0 +1,34 @@ +unsignedTinyInteger('status')->after('type')->default(1)->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('status'); + }); + } +} diff --git a/freescout-dist/database/migrations/2018_12_15_151003_add_send_status_data_column_to_threads_table.php b/freescout-dist/database/migrations/2018_12_15_151003_add_send_status_data_column_to_threads_table.php new file mode 100644 index 0000000..1721d23 --- /dev/null +++ b/freescout-dist/database/migrations/2018_12_15_151003_add_send_status_data_column_to_threads_table.php @@ -0,0 +1,33 @@ +text('send_status_data')->after('send_status')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('threads', function (Blueprint $table) { + $table->dropColumn('send_status_data'); + }); + } +} diff --git a/freescout-dist/database/migrations/2019_06_16_124000_add_in_validate_cert_column_to_mailboxes_table.php b/freescout-dist/database/migrations/2019_06_16_124000_add_in_validate_cert_column_to_mailboxes_table.php new file mode 100644 index 0000000..3e22f00 --- /dev/null +++ b/freescout-dist/database/migrations/2019_06_16_124000_add_in_validate_cert_column_to_mailboxes_table.php @@ -0,0 +1,32 @@ +boolean('in_validate_cert')->default(true); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailboxes', function (Blueprint $table) { + $table->dropColumn('in_validate_cert'); + }); + } +} diff --git a/freescout-dist/database/migrations/2019_06_21_130200_add_meta_subtype_columns_to_threads_table.php b/freescout-dist/database/migrations/2019_06_21_130200_add_meta_subtype_columns_to_threads_table.php new file mode 100644 index 0000000..31d0a25 --- /dev/null +++ b/freescout-dist/database/migrations/2019_06_21_130200_add_meta_subtype_columns_to_threads_table.php @@ -0,0 +1,35 @@ +unsignedTinyInteger('subtype')->after('type')->nullable(); + // Meta data in JSON format. + $table->text('meta')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('threads', function (Blueprint $table) { + $table->dropColumn('subtype'); + $table->dropColumn('meta'); + }); + } +} diff --git a/freescout-dist/database/migrations/2019_06_25_105200_change_status_message_column_in_send_logs_table.php b/freescout-dist/database/migrations/2019_06_25_105200_change_status_message_column_in_send_logs_table.php new file mode 100644 index 0000000..ee77c2d --- /dev/null +++ b/freescout-dist/database/migrations/2019_06_25_105200_change_status_message_column_in_send_logs_table.php @@ -0,0 +1,32 @@ +text('status_message')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('send_logs', function (Blueprint $table) { + $table->string('status_message', 255)->nullable()->change(); + }); + } +} diff --git a/freescout-dist/database/migrations/2019_07_05_370100_add_in_imap_folders_column_to_mailboxes_table.php b/freescout-dist/database/migrations/2019_07_05_370100_add_in_imap_folders_column_to_mailboxes_table.php new file mode 100644 index 0000000..a0a7987 --- /dev/null +++ b/freescout-dist/database/migrations/2019_07_05_370100_add_in_imap_folders_column_to_mailboxes_table.php @@ -0,0 +1,32 @@ +text('in_imap_folders')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailboxes', function (Blueprint $table) { + $table->dropColumn('in_imap_folders'); + }); + } +} diff --git a/freescout-dist/database/migrations/2019_10_06_123000_add_auto_bcc_column_to_mailboxes_table.php b/freescout-dist/database/migrations/2019_10_06_123000_add_auto_bcc_column_to_mailboxes_table.php new file mode 100644 index 0000000..c5765a0 --- /dev/null +++ b/freescout-dist/database/migrations/2019_10_06_123000_add_auto_bcc_column_to_mailboxes_table.php @@ -0,0 +1,32 @@ +string('auto_bcc', 255)->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailboxes', function (Blueprint $table) { + $table->dropColumn('auto_bcc'); + }); + } +} diff --git a/freescout-dist/database/migrations/2019_12_10_0856000_add_before_reply_column_to_mailboxes_table.php b/freescout-dist/database/migrations/2019_12_10_0856000_add_before_reply_column_to_mailboxes_table.php new file mode 100644 index 0000000..814588d --- /dev/null +++ b/freescout-dist/database/migrations/2019_12_10_0856000_add_before_reply_column_to_mailboxes_table.php @@ -0,0 +1,42 @@ +text('out_password')->nullable()->change(); + $table->text('in_password')->nullable()->change(); + }); + } + + Schema::table('mailboxes', function (Blueprint $table) { + $table->text('before_reply')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailboxes', function (Blueprint $table) { + $table->dropColumn('before_reply'); + }); + } +} diff --git a/freescout-dist/database/migrations/2019_12_19_183015_add_meta_column_to_folders_table.php b/freescout-dist/database/migrations/2019_12_19_183015_add_meta_column_to_folders_table.php new file mode 100644 index 0000000..a3e1b30 --- /dev/null +++ b/freescout-dist/database/migrations/2019_12_19_183015_add_meta_column_to_folders_table.php @@ -0,0 +1,33 @@ +text('meta')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('folders', function (Blueprint $table) { + $table->dropColumn('meta'); + }); + } +} diff --git a/freescout-dist/database/migrations/2019_12_22_111025_change_passwords_types_in_mailboxes_table.php b/freescout-dist/database/migrations/2019_12_22_111025_change_passwords_types_in_mailboxes_table.php new file mode 100644 index 0000000..b87ee20 --- /dev/null +++ b/freescout-dist/database/migrations/2019_12_22_111025_change_passwords_types_in_mailboxes_table.php @@ -0,0 +1,34 @@ +text('out_password')->nullable()->change(); + $table->text('in_password')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/freescout-dist/database/migrations/2019_12_24_155120_create_followers_table.php b/freescout-dist/database/migrations/2019_12_24_155120_create_followers_table.php new file mode 100644 index 0000000..2e576e6 --- /dev/null +++ b/freescout-dist/database/migrations/2019_12_24_155120_create_followers_table.php @@ -0,0 +1,34 @@ +increments('id'); + $table->integer('conversation_id'); + $table->integer('user_id'); + $table->integer('added_by_user_id')->nullable(); + $table->unique(['conversation_id', 'user_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('followers'); + } +} diff --git a/freescout-dist/database/migrations/2020_02_06_103815_add_hide_column_to_mailbox_user_table.php b/freescout-dist/database/migrations/2020_02_06_103815_add_hide_column_to_mailbox_user_table.php new file mode 100644 index 0000000..add8e8d --- /dev/null +++ b/freescout-dist/database/migrations/2020_02_06_103815_add_hide_column_to_mailbox_user_table.php @@ -0,0 +1,33 @@ +boolean('hide')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailbox_user', function (Blueprint $table) { + $table->dropColumn('hide'); + }); + } +} diff --git a/freescout-dist/database/migrations/2020_02_16_121001_add_mute_column_to_mailbox_user_table.php b/freescout-dist/database/migrations/2020_02_16_121001_add_mute_column_to_mailbox_user_table.php new file mode 100644 index 0000000..b237583 --- /dev/null +++ b/freescout-dist/database/migrations/2020_02_16_121001_add_mute_column_to_mailbox_user_table.php @@ -0,0 +1,33 @@ +boolean('mute')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailbox_user', function (Blueprint $table) { + $table->dropColumn('mute'); + }); + } +} diff --git a/freescout-dist/database/migrations/2020_03_06_100100_add_public_column_to_attachments_table.php b/freescout-dist/database/migrations/2020_03_06_100100_add_public_column_to_attachments_table.php new file mode 100644 index 0000000..eee7127 --- /dev/null +++ b/freescout-dist/database/migrations/2020_03_06_100100_add_public_column_to_attachments_table.php @@ -0,0 +1,45 @@ +boolean('public')->default(false); + }); + DB::table('attachments')->update(['public' => true]); + + $old_path = storage_path('app'.DIRECTORY_SEPARATOR.'public'.DIRECTORY_SEPARATOR.'attachment'); + $new_path = storage_path('app'.DIRECTORY_SEPARATOR.'attachment'); + + // Move attachments. + try { + if (File::exists($old_path) && File::isDirectory($old_path) && !File::exists($new_path)) { + File::move($old_path, $new_path); + } + } catch (\Exception $e) { + \Log::error('Migration Error: '.$e->getMessage()); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('attachments', function (Blueprint $table) { + $table->dropColumn('public'); + }); + } +} diff --git a/freescout-dist/database/migrations/2020_03_29_095201_update_in_imap_folders_in_mailboxes_table.php b/freescout-dist/database/migrations/2020_03_29_095201_update_in_imap_folders_in_mailboxes_table.php new file mode 100644 index 0000000..e5b6775 --- /dev/null +++ b/freescout-dist/database/migrations/2020_03_29_095201_update_in_imap_folders_in_mailboxes_table.php @@ -0,0 +1,37 @@ +get(); + foreach ($mailboxes as $mailbox) { + $in_imap_folders = $mailbox->getInImapFolders(); + if (count($in_imap_folders) && !in_array('INBOX', $in_imap_folders)) { + array_unshift($in_imap_folders, 'INBOX'); + $mailbox->setInImapFolders($in_imap_folders); + $mailbox->save(); + } + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/freescout-dist/database/migrations/2020_04_16_122803_add_imap_sent_folder_column_to_mailboxes_table.php b/freescout-dist/database/migrations/2020_04_16_122803_add_imap_sent_folder_column_to_mailboxes_table.php new file mode 100644 index 0000000..d7d9055 --- /dev/null +++ b/freescout-dist/database/migrations/2020_04_16_122803_add_imap_sent_folder_column_to_mailboxes_table.php @@ -0,0 +1,32 @@ +string('imap_sent_folder', 25)->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailboxes', function (Blueprint $table) { + $table->dropColumn('imap_sent_folder'); + }); + } +} diff --git a/freescout-dist/database/migrations/2020_05_28_095100_drop_slug_column_in_mailboxes_table.php b/freescout-dist/database/migrations/2020_05_28_095100_drop_slug_column_in_mailboxes_table.php new file mode 100644 index 0000000..46424ec --- /dev/null +++ b/freescout-dist/database/migrations/2020_05_28_095100_drop_slug_column_in_mailboxes_table.php @@ -0,0 +1,34 @@ +dropColumn('slug'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailboxes', function (Blueprint $table) { + $table->string('slug', 16)->unique()->nullable(); + }); + } +} diff --git a/freescout-dist/database/migrations/2020_06_26_080258_add_email_history_column_to_conversations_table.php b/freescout-dist/database/migrations/2020_06_26_080258_add_email_history_column_to_conversations_table.php new file mode 100644 index 0000000..fc68a22 --- /dev/null +++ b/freescout-dist/database/migrations/2020_06_26_080258_add_email_history_column_to_conversations_table.php @@ -0,0 +1,32 @@ +unsignedTinyInteger('email_history')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('conversations', function (Blueprint $table) { + $table->dropColumn('email_history'); + }); + } +} diff --git a/freescout-dist/database/migrations/2020_09_18_123314_add_access_column_to_mailbox_user_table.php b/freescout-dist/database/migrations/2020_09_18_123314_add_access_column_to_mailbox_user_table.php new file mode 100644 index 0000000..6bdb9f8 --- /dev/null +++ b/freescout-dist/database/migrations/2020_09_18_123314_add_access_column_to_mailbox_user_table.php @@ -0,0 +1,32 @@ +text('access')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailbox_user', function (Blueprint $table) { + $table->dropColumn('access'); + }); + } +} diff --git a/freescout-dist/database/migrations/2020_09_20_010000_drop_email_history_column_in_conversations_table.php b/freescout-dist/database/migrations/2020_09_20_010000_drop_email_history_column_in_conversations_table.php new file mode 100644 index 0000000..a496342 --- /dev/null +++ b/freescout-dist/database/migrations/2020_09_20_010000_drop_email_history_column_in_conversations_table.php @@ -0,0 +1,33 @@ +dropColumn('email_history'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('conversations', function (Blueprint $table) { + $table->unsignedTinyInteger('email_history')->default(0); + }); + } +} diff --git a/freescout-dist/database/migrations/2020_11_04_140000_change_foreign_keys_types.php b/freescout-dist/database/migrations/2020_11_04_140000_change_foreign_keys_types.php new file mode 100644 index 0000000..3b95c10 --- /dev/null +++ b/freescout-dist/database/migrations/2020_11_04_140000_change_foreign_keys_types.php @@ -0,0 +1,73 @@ +unsignedInteger('causer_id')->nullable()->change(); + }); + Schema::table('attachments', function (Blueprint $table) { + $table->unsignedInteger('thread_id')->nullable()->change(); + $table->unsignedInteger('user_id')->nullable()->change(); + }); + Schema::table('conversations', function (Blueprint $table) { + $table->unsignedInteger('folder_id')->change(); + $table->unsignedInteger('mailbox_id')->change(); + $table->unsignedInteger('user_id')->nullable()->change(); + $table->unsignedInteger('customer_id')->nullable()->change(); + $table->unsignedInteger('created_by_user_id')->nullable()->change(); + $table->unsignedInteger('created_by_customer_id')->nullable()->change(); + $table->unsignedInteger('closed_by_user_id')->nullable()->change(); + }); + Schema::table('conversation_folder', function (Blueprint $table) { + $table->unsignedInteger('folder_id')->change(); + $table->unsignedInteger('conversation_id')->change(); + }); + Schema::table('emails', function (Blueprint $table) { + $table->unsignedInteger('customer_id')->change(); + }); + Schema::table('folders', function (Blueprint $table) { + $table->unsignedInteger('mailbox_id')->change(); + $table->unsignedInteger('user_id')->nullable()->change(); + }); + Schema::table('mailbox_user', function (Blueprint $table) { + $table->unsignedInteger('mailbox_id')->change(); + $table->unsignedInteger('user_id')->change(); + }); + Schema::table('send_logs', function (Blueprint $table) { + $table->unsignedInteger('thread_id')->nullable()->change(); + $table->unsignedInteger('customer_id')->nullable()->change(); + $table->unsignedInteger('user_id')->nullable()->change(); + }); + Schema::table('subscriptions', function (Blueprint $table) { + $table->unsignedInteger('user_id')->change(); + }); + Schema::table('threads', function (Blueprint $table) { + $table->unsignedInteger('conversation_id')->change(); + $table->unsignedInteger('user_id')->nullable()->change(); + $table->unsignedInteger('customer_id')->nullable()->change(); + $table->unsignedInteger('created_by_user_id')->nullable()->change(); + $table->unsignedInteger('created_by_customer_id')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/freescout-dist/database/migrations/2020_11_19_070000_update_customers_table.php b/freescout-dist/database/migrations/2020_11_19_070000_update_customers_table.php new file mode 100644 index 0000000..516b01a --- /dev/null +++ b/freescout-dist/database/migrations/2020_11_19_070000_update_customers_table.php @@ -0,0 +1,36 @@ +dropColumn('age'); + $table->dropColumn('gender'); + $table->renameColumn('background', 'notes'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('customers', function (Blueprint $table) { + $table->string('age', 7)->nullable(); + $table->unsignedTinyInteger('gender')->nullable(); + $table->renameColumn('notes', 'background'); + }); + } +} diff --git a/freescout-dist/database/migrations/2020_12_22_070000_move_user_permissions_to_env.php b/freescout-dist/database/migrations/2020_12_22_070000_move_user_permissions_to_env.php new file mode 100644 index 0000000..3822b1b --- /dev/null +++ b/freescout-dist/database/migrations/2020_12_22_070000_move_user_permissions_to_env.php @@ -0,0 +1,35 @@ + true]); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/freescout-dist/database/migrations/2020_12_22_080000_add_permissions_column_to_users_table.php b/freescout-dist/database/migrations/2020_12_22_080000_add_permissions_column_to_users_table.php new file mode 100644 index 0000000..c07b079 --- /dev/null +++ b/freescout-dist/database/migrations/2020_12_22_080000_add_permissions_column_to_users_table.php @@ -0,0 +1,32 @@ +text('permissions')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('permissions'); + }); + } +} diff --git a/freescout-dist/database/migrations/2020_12_30_010000_add_imported_column_to_threads_table.php b/freescout-dist/database/migrations/2020_12_30_010000_add_imported_column_to_threads_table.php new file mode 100644 index 0000000..3cf670b --- /dev/null +++ b/freescout-dist/database/migrations/2020_12_30_010000_add_imported_column_to_threads_table.php @@ -0,0 +1,32 @@ +boolean('imported')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('threads', function (Blueprint $table) { + $table->dropColumn('imported'); + }); + } +} diff --git a/freescout-dist/database/migrations/2021_02_06_010101_add_meta_column_to_mailboxes_table.php b/freescout-dist/database/migrations/2021_02_06_010101_add_meta_column_to_mailboxes_table.php new file mode 100644 index 0000000..6d03d3e --- /dev/null +++ b/freescout-dist/database/migrations/2021_02_06_010101_add_meta_column_to_mailboxes_table.php @@ -0,0 +1,38 @@ +text('in_server')->nullable()->change(); + $table->text('out_server')->nullable()->change(); + $table->text('auto_bcc')->nullable()->change(); + + // Meta data in JSON format. + $table->text('meta')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailboxes', function (Blueprint $table) { + $table->dropColumn('meta'); + }); + } +} diff --git a/freescout-dist/database/migrations/2021_02_09_010101_add_hash_column_to_ltm_translations_table.php b/freescout-dist/database/migrations/2021_02_09_010101_add_hash_column_to_ltm_translations_table.php new file mode 100644 index 0000000..713289c --- /dev/null +++ b/freescout-dist/database/migrations/2021_02_09_010101_add_hash_column_to_ltm_translations_table.php @@ -0,0 +1,32 @@ +string('hash')->nullable()->unique(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('ltm_translations', function (Blueprint $table) { + $table->dropColumn('hash'); + }); + } +} diff --git a/freescout-dist/database/migrations/2021_02_17_010101_change_string_columns_in_mailboxes_table.php b/freescout-dist/database/migrations/2021_02_17_010101_change_string_columns_in_mailboxes_table.php new file mode 100644 index 0000000..33c6b7d --- /dev/null +++ b/freescout-dist/database/migrations/2021_02_17_010101_change_string_columns_in_mailboxes_table.php @@ -0,0 +1,33 @@ +text('in_server')->nullable()->change(); + $table->text('out_server')->nullable()->change(); + $table->text('auto_bcc')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/freescout-dist/database/migrations/2021_03_01_010101_add_channel_column_to_conversations_table.php b/freescout-dist/database/migrations/2021_03_01_010101_add_channel_column_to_conversations_table.php new file mode 100644 index 0000000..21ff76e --- /dev/null +++ b/freescout-dist/database/migrations/2021_03_01_010101_add_channel_column_to_conversations_table.php @@ -0,0 +1,32 @@ +unsignedTinyInteger('channel')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('conversations', function (Blueprint $table) { + $table->dropColumn('channel'); + }); + } +} diff --git a/freescout-dist/database/migrations/2021_03_01_010101_add_channel_columns_to_customers_table.php b/freescout-dist/database/migrations/2021_03_01_010101_add_channel_columns_to_customers_table.php new file mode 100644 index 0000000..d04c1ce --- /dev/null +++ b/freescout-dist/database/migrations/2021_03_01_010101_add_channel_columns_to_customers_table.php @@ -0,0 +1,43 @@ +dropColumn('chats'); + $table->unsignedTinyInteger('channel')->nullable(); + // It may have any length. + // Now it's not clear why it may have any length, + // so in customer_channel table it's varchar. + $table->text('channel_id')->nullable(); + + // We are not adding index, as requests are made in the background, + // so performance here not very critical. + //$table->index(['channel', DB::raw('channel_id(5)')]); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('customers', function (Blueprint $table) { + $table->text('chats')->nullable(); // JSON + $table->dropColumn('channel'); + $table->dropColumn('channel_id'); + }); + } +} diff --git a/freescout-dist/database/migrations/2021_04_15_010101_add_meta_column_to_customers_table.php b/freescout-dist/database/migrations/2021_04_15_010101_add_meta_column_to_customers_table.php new file mode 100644 index 0000000..385028e --- /dev/null +++ b/freescout-dist/database/migrations/2021_04_15_010101_add_meta_column_to_customers_table.php @@ -0,0 +1,40 @@ +text('company')->nullable()->change(); + $table->text('job_title')->nullable()->change(); + + // Meta data in JSON format. + $table->text('meta')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('customers', function (Blueprint $table) { + $table->dropColumn('meta'); + + $table->string('company', 255)->nullable()->change(); + $table->string('job_title', 255)->nullable()->change(); + }); + } +} diff --git a/freescout-dist/database/migrations/2021_05_21_090000_encrypt_mailbox_out_password.php b/freescout-dist/database/migrations/2021_05_21_090000_encrypt_mailbox_out_password.php new file mode 100644 index 0000000..e0f34a0 --- /dev/null +++ b/freescout-dist/database/migrations/2021_05_21_090000_encrypt_mailbox_out_password.php @@ -0,0 +1,41 @@ +get() as $Mailbox) { + $unencrypted_password = $Mailbox->getOriginal('out_password'); + if ($unencrypted_password) { + $attributes = $Mailbox->getAttributes(); + $attributes = array_merge($attributes, ['out_password' => encrypt($unencrypted_password)]); + $Mailbox->setRawAttributes($attributes); + } + $Mailbox->save(); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + foreach (\App\Mailbox::whereNotNull('out_password')->get() as $Mailbox) { + $attributes = $Mailbox->getAttributes(); + $attributes = array_merge($attributes, ['out_password' => $Mailbox->out_password]); + $Mailbox->setRawAttributes($attributes); + $Mailbox->save(); + } + } +} \ No newline at end of file diff --git a/freescout-dist/database/migrations/2021_05_21_105200_encrypt_mail_password.php b/freescout-dist/database/migrations/2021_05_21_105200_encrypt_mail_password.php new file mode 100644 index 0000000..468af18 --- /dev/null +++ b/freescout-dist/database/migrations/2021_05_21_105200_encrypt_mail_password.php @@ -0,0 +1,34 @@ +index(['user_id', 'mailbox_id', 'state', 'status']); + $table->index(['folder_id', 'state']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('conversations', function (Blueprint $table) { + $table->dropIndex(['folder_id', 'state']); + $table->dropIndex(['user_id', 'mailbox_id', 'state', 'status']); + }); + } +} diff --git a/freescout-dist/database/migrations/2021_11_30_010101_remove_unique_index_in_folders_table.php b/freescout-dist/database/migrations/2021_11_30_010101_remove_unique_index_in_folders_table.php new file mode 100644 index 0000000..c3d188a --- /dev/null +++ b/freescout-dist/database/migrations/2021_11_30_010101_remove_unique_index_in_folders_table.php @@ -0,0 +1,34 @@ +dropUnique(['mailbox_id', 'user_id', 'type']); + $table->index(['mailbox_id', 'user_id', 'type']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('folders', function (Blueprint $table) { + $table->dropIndex(['mailbox_id', 'user_id', 'type']); + $table->unique(['mailbox_id', 'user_id', 'type']); + }); + } +} diff --git a/freescout-dist/database/migrations/2021_12_25_010101_change_emails_column_in_users_table.php b/freescout-dist/database/migrations/2021_12_25_010101_change_emails_column_in_users_table.php new file mode 100644 index 0000000..3dd765b --- /dev/null +++ b/freescout-dist/database/migrations/2021_12_25_010101_change_emails_column_in_users_table.php @@ -0,0 +1,30 @@ +text('emails')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/freescout-dist/database/migrations/2022_12_17_010101_add_meta_column_to_conversations_table.php b/freescout-dist/database/migrations/2022_12_17_010101_add_meta_column_to_conversations_table.php new file mode 100644 index 0000000..732efa9 --- /dev/null +++ b/freescout-dist/database/migrations/2022_12_17_010101_add_meta_column_to_conversations_table.php @@ -0,0 +1,33 @@ +text('meta')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('customers', function (Blueprint $table) { + $table->dropColumn('meta'); + }); + } +} diff --git a/freescout-dist/database/migrations/2022_12_18_010101_set_user_type_field.php b/freescout-dist/database/migrations/2022_12_18_010101_set_user_type_field.php new file mode 100644 index 0000000..642105d --- /dev/null +++ b/freescout-dist/database/migrations/2022_12_18_010101_set_user_type_field.php @@ -0,0 +1,32 @@ +where('status', 3) // User::STATUS_DELETED + ->where('email', 'like', 'fs%@example.org%') + ->update(['type' => 2]); // User::TYPE_ROBOT + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + DB::table('users')->where('status', 3) // User::STATUS_DELETED + ->where('email', 'like', 'fs%@example.org%') + ->update(['type' => 1]); + } +} diff --git a/freescout-dist/database/migrations/2022_12_25_010101_set_numeric_phones_in_customers_table.php b/freescout-dist/database/migrations/2022_12_25_010101_set_numeric_phones_in_customers_table.php new file mode 100644 index 0000000..39f805b --- /dev/null +++ b/freescout-dist/database/migrations/2022_12_25_010101_set_numeric_phones_in_customers_table.php @@ -0,0 +1,39 @@ +where('phones', 'not like', '%"n":%') + ->limit(100) + ->get(); + foreach ($customers as $customer) { + $phones = $customer->getPhones(); + $customer->setPhones($phones); + $customer->save(); + } + } while(count($customers)); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/freescout-dist/database/migrations/2023_01_14_010101_change_deleted_folder_index.php b/freescout-dist/database/migrations/2023_01_14_010101_change_deleted_folder_index.php new file mode 100644 index 0000000..a2a0fe0 --- /dev/null +++ b/freescout-dist/database/migrations/2023_01_14_010101_change_deleted_folder_index.php @@ -0,0 +1,30 @@ +where('type', 110) + ->update(['type' => 70]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + DB::table('folders')->where('type', 70) + ->update(['type' => 110]); + } +} diff --git a/freescout-dist/database/migrations/2023_05_09_010101_add_aliases_reply_column_to_mailboxes_table.php b/freescout-dist/database/migrations/2023_05_09_010101_add_aliases_reply_column_to_mailboxes_table.php new file mode 100644 index 0000000..f83b901 --- /dev/null +++ b/freescout-dist/database/migrations/2023_05_09_010101_add_aliases_reply_column_to_mailboxes_table.php @@ -0,0 +1,33 @@ +boolean('aliases_reply')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('mailboxes', function (Blueprint $table) { + $table->dropColumn('aliases_reply'); + }); + } +} diff --git a/freescout-dist/database/migrations/2023_08_19_010101_create_customer_channel_table.php b/freescout-dist/database/migrations/2023_08_19_010101_create_customer_channel_table.php new file mode 100644 index 0000000..f4cb3ba --- /dev/null +++ b/freescout-dist/database/migrations/2023_08_19_010101_create_customer_channel_table.php @@ -0,0 +1,37 @@ +increments('id'); + $table->unsignedInteger('customer_id')->index(); + $table->unsignedTinyInteger('channel'); + $table->string('channel_id', 64); + $table->unique(['channel', 'channel_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('customer_channel'); + } +} diff --git a/freescout-dist/database/migrations/2023_08_19_020202_populate_customer_channel_table.php b/freescout-dist/database/migrations/2023_08_19_020202_populate_customer_channel_table.php new file mode 100644 index 0000000..2f41778 --- /dev/null +++ b/freescout-dist/database/migrations/2023_08_19_020202_populate_customer_channel_table.php @@ -0,0 +1,53 @@ +whereNotNull('channel') + ->whereNotNull('channel_id') + ->skip($i*100) + ->limit(100) + ->get(); + foreach ($customers as $customer) { + if (!$customer->channel || !$customer->channel_id) { + continue; + } + try { + $customer_channel = new CustomerChannel(); + $customer_channel->customer_id = $customer->id; + $customer_channel->channel = $customer->channel; + $customer_channel->channel_id = $customer->channel_id; + $customer_channel->save(); + } catch (\Exception $e) { + // Already exists. + } + } + $i++; + } while(count($customers)); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + CustomerChannel::truncate(); + } +} diff --git a/freescout-dist/database/migrations/2023_08_29_010101_add_id_column_to_customer_channel_table.php b/freescout-dist/database/migrations/2023_08_29_010101_add_id_column_to_customer_channel_table.php new file mode 100644 index 0000000..eb95a01 --- /dev/null +++ b/freescout-dist/database/migrations/2023_08_29_010101_add_id_column_to_customer_channel_table.php @@ -0,0 +1,32 @@ +increments('id'); + } + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/freescout-dist/database/migrations/2023_09_05_010101_add_smtp_queue_id_column_to_send_logs_table.php b/freescout-dist/database/migrations/2023_09_05_010101_add_smtp_queue_id_column_to_send_logs_table.php new file mode 100644 index 0000000..bde32a4 --- /dev/null +++ b/freescout-dist/database/migrations/2023_09_05_010101_add_smtp_queue_id_column_to_send_logs_table.php @@ -0,0 +1,32 @@ +text('smtp_queue_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('send_logs', function (Blueprint $table) { + $table->dropColumn('smtp_queue_id'); + }); + } +} diff --git a/freescout-dist/database/migrations/2023_11_14_010101_change_aliases_column_in_mailboxes_table.php b/freescout-dist/database/migrations/2023_11_14_010101_change_aliases_column_in_mailboxes_table.php new file mode 100644 index 0000000..10ca46a --- /dev/null +++ b/freescout-dist/database/migrations/2023_11_14_010101_change_aliases_column_in_mailboxes_table.php @@ -0,0 +1,31 @@ +text('aliases')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/freescout-dist/database/seeds/CustomersTableSeeder.php b/freescout-dist/database/seeds/CustomersTableSeeder.php new file mode 100644 index 0000000..5ed7612 --- /dev/null +++ b/freescout-dist/database/seeds/CustomersTableSeeder.php @@ -0,0 +1,19 @@ +create()->each(function ($m) { + $m->emails()->save(factory(App\Email::class)->make()); + $m->emails()->save(factory(App\Email::class)->make()); + }); + } +} diff --git a/freescout-dist/database/seeds/DatabaseSeeder.php b/freescout-dist/database/seeds/DatabaseSeeder.php new file mode 100644 index 0000000..826b93f --- /dev/null +++ b/freescout-dist/database/seeds/DatabaseSeeder.php @@ -0,0 +1,47 @@ +create(); + + // Create mailboxes, conversations, etc + factory(App\Mailbox::class, 3)->create()->each(function ($m) { + $user = factory(App\User::class)->create(); + $m->users()->save($user); + + for ($i = 0; $i < 7; $i++) { + $customer = factory(App\Customer::class)->create(); + + $email = factory(App\Email::class)->make(); + $customer->emails()->save($email); + + $conversation = factory(App\Conversation::class)->create([ + 'created_by_user_id' => $user->id, + 'mailbox_id' => $m->id, + 'customer_id' => $customer->id, + 'customer_email' => $email->email, + 'user_id' => $user->id, + 'status' => array_rand([Conversation::STATUS_ACTIVE => 1, Conversation::STATUS_PENDING => 1]), + ]); + + $thread = factory(App\Thread::class)->make([ + 'customer_id' => $customer->id, + 'to' => $email->email, + 'conversation_id' => $conversation->id, + ]); + $conversation->threads()->save($thread); + } + }); + } +} diff --git a/freescout-dist/database/seeds/MailboxesTableSeeder.php b/freescout-dist/database/seeds/MailboxesTableSeeder.php new file mode 100644 index 0000000..f2a5f8c --- /dev/null +++ b/freescout-dist/database/seeds/MailboxesTableSeeder.php @@ -0,0 +1,16 @@ +create(); + } +} diff --git a/freescout-dist/database/seeds/UsersTableSeeder.php b/freescout-dist/database/seeds/UsersTableSeeder.php new file mode 100644 index 0000000..7f701a2 --- /dev/null +++ b/freescout-dist/database/seeds/UsersTableSeeder.php @@ -0,0 +1,16 @@ +create(); + } +} diff --git a/freescout-dist/overrides/axn/laravel-laroute/src/Routes/Collection.php b/freescout-dist/overrides/axn/laravel-laroute/src/Routes/Collection.php new file mode 100644 index 0000000..d064e27 --- /dev/null +++ b/freescout-dist/overrides/axn/laravel-laroute/src/Routes/Collection.php @@ -0,0 +1,75 @@ +module = $module; + $this->items = $this->parseRoutes($routes, $filter, $namespace); + } + + /** + * Get the collection of items as JSON **pretty printed**. + * + * @return string + */ + public function toJson($options = 0) + { + $options = JSON_PRETTY_PRINT | $options; + + return json_encode($this->toArray(), $options); + } + + /** + * Get the route information for a given route. + * + * @param $route \Illuminate\Routing\Route + * @param $filter string + * @param $namespace string + * + * @return array + */ + protected function getRouteInformation(Route $route, $filter, $namespace) + { + $data = parent::getRouteInformation($route, $filter, $namespace); + + if (!$data || empty($data['name'])) { + return; + } + + // If `module` parameter is set for the route we choose only routes from this module + $action = $route->getAction(); + + if ($action && !empty($action['controller'])) { + preg_match('/^Modules\\\([^\\\]+)\\\/', $action['controller'], $m); + + if (!empty($m[1])) { + if (!$this->module) { + // We are generating routes for the main application, + // missing route of the module + return; + } else { + // Route belongs to another module + if ($this->module != strtolower($m[1])) { + return; + } + } + } elseif ($this->module) { + // Include only module routes into module JS file + return; + } + } + + return array_only($data, ['uri', 'name']); + } +} diff --git a/freescout-dist/overrides/barryvdh/laravel-debugbar/src/DataFormatter/QueryFormatter.php b/freescout-dist/overrides/barryvdh/laravel-debugbar/src/DataFormatter/QueryFormatter.php new file mode 100644 index 0000000..262dfd7 --- /dev/null +++ b/freescout-dist/overrides/barryvdh/laravel-debugbar/src/DataFormatter/QueryFormatter.php @@ -0,0 +1,76 @@ +namespace) { + $parts['namespace'] = $source->namespace . '::'; + } + + $parts['name'] = $source->name; + $parts['line'] = ':' . $source->line; + + return implode($parts); + } +} diff --git a/freescout-dist/overrides/barryvdh/laravel-debugbar/src/JavascriptRenderer.php b/freescout-dist/overrides/barryvdh/laravel-debugbar/src/JavascriptRenderer.php new file mode 100644 index 0000000..d2c5fe6 --- /dev/null +++ b/freescout-dist/overrides/barryvdh/laravel-debugbar/src/JavascriptRenderer.php @@ -0,0 +1,142 @@ +cssFiles['laravel'] = __DIR__ . '/../../../../vendor/barryvdh/laravel-debugbar/src/Resources/laravel-debugbar.css'; + $this->cssVendors['fontawesome'] = __DIR__ . '/../../../../vendor/barryvdh/laravel-debugbar/src/Resources/vendor/font-awesome/style.css'; + $this->jsFiles['laravel-sql'] = __DIR__ . '/../../../../vendor/barryvdh/laravel-debugbar/src/Resources/sqlqueries/widget.js'; + $this->jsFiles['laravel-cache'] = __DIR__ . '/../../../../vendor/barryvdh/laravel-debugbar/src/Resources/cache/widget.js'; + } + + /** + * Set the URL Generator + * + * @param \Illuminate\Routing\UrlGenerator $url + * @deprecated + */ + public function setUrlGenerator($url) + { + + } + + /** + * {@inheritdoc} + */ + public function renderHead() + { + $cssRoute = route('debugbar.assets.css', [ + 'v' => $this->getModifiedTime('css') + ]); + + $jsRoute = route('debugbar.assets.js', [ + 'v' => $this->getModifiedTime('js') + ]); + + $cssRoute = preg_replace('/\Ahttps?:/', '', $cssRoute); + $jsRoute = preg_replace('/\Ahttps?:/', '', $jsRoute); + + $html = ""; + $html .= ""; + + if ($this->isJqueryNoConflictEnabled()) { + $html .= '' . "\n"; + } + + $html .= $this->getInlineHtml(); + + + return $html; + } + + protected function getInlineHtml() + { + $html = ''; + + foreach (['head', 'css', 'js'] as $asset) { + foreach ($this->getAssets('inline_' . $asset) as $item) { + $html .= $item . "\n"; + } + } + + return $html; + } + /** + * Get the last modified time of any assets. + * + * @param string $type 'js' or 'css' + * @return int + */ + protected function getModifiedTime($type) + { + $files = $this->getAssets($type); + + $latest = 0; + foreach ($files as $file) { + $mtime = filemtime($file); + if ($mtime > $latest) { + $latest = $mtime; + } + } + return $latest; + } + + /** + * Return assets as a string + * + * @param string $type 'js' or 'css' + * @return string + */ + public function dumpAssetsToString($type) + { + $files = $this->getAssets($type); + + $content = ''; + foreach ($files as $file) { + $content .= file_get_contents($file) . "\n"; + } + + return $content; + } + + /** + * Makes a URI relative to another + * + * @param string|array $uri + * @param string $root + * @return string + */ + protected function makeUriRelativeTo($uri, $root) + { + if (!$root) { + return $uri; + } + + if (is_array($uri)) { + $uris = []; + foreach ($uri as $u) { + $uris[] = $this->makeUriRelativeTo($u, $root); + } + return $uris; + } + + if (substr($uri ?? '', 0, 1) === '/' || preg_match('/^([a-zA-Z]+:\/\/|[a-zA-Z]:\/|[a-zA-Z]:\\\)/', $uri ?? '')) { + return $uri; + } + return rtrim($root, '/') . "/$uri"; + } +} diff --git a/freescout-dist/overrides/barryvdh/laravel-translation-manager/src/Controller.php b/freescout-dist/overrides/barryvdh/laravel-translation-manager/src/Controller.php new file mode 100644 index 0000000..5c0f7d4 --- /dev/null +++ b/freescout-dist/overrides/barryvdh/laravel-translation-manager/src/Controller.php @@ -0,0 +1,230 @@ +manager = $manager; + } + + public function getIndex($group = null) + { + $locales = $this->manager->getLocales(); + $groups = Translation::groupBy('group'); + $excludedGroups = $this->manager->getConfig('exclude_groups'); + if($excludedGroups){ + $groups->whereNotIn('group', $excludedGroups); + } + + $groups = $groups->select('group')->orderBy('group')->get()->pluck('group', 'group'); + if ($groups instanceof Collection) { + $groups = $groups->all(); + } + $groups = [''=>'Choose a group'] + $groups; + + $selected_locale = request()->input('locale'); + // if ($selected_locale == 'en') { + // $selected_locale = ''; + // } + if (!$selected_locale) { + foreach ($locales as $locale) { + //if ($locale != 'en') { + $selected_locale = $locale; + break; + //} + } + } + + $numChanged = Translation::where('group', $group) + ->where('status', Translation::STATUS_CHANGED) + ->where('locale', $selected_locale) + ->count(); + + $allTranslations = Translation::where('group', $group)->orderBy('key', 'asc')->get(); + + $translations = []; + $numTranslations = 0; + $numDone = 0; + foreach($allTranslations as $translation){ + if ($translation->locale != $selected_locale && $translation->locale != 'en') { + continue; + } + $translations[$translation->key][$translation->locale] = $translation; + if ($translation->locale == $selected_locale && $translation->value) { + $numDone++; + } + if ($translation->locale == 'en') { + $numTranslations++; + } + } + + $numTodo = $numTranslations - $numDone; + + return view('translation-manager::index') + ->with('translations', $translations) + ->with('locales', $locales) + ->with('groups', $groups) + ->with('group', $group) + ->with('selected_locale', $selected_locale) + ->with('numTranslations', $numTranslations) + ->with('numTodo', $numTodo) + ->with('numChanged', $numChanged) + ->with('editUrl', action('\Barryvdh\TranslationManager\Controller@postEdit', [$group])) + ->with('deleteEnabled', $this->manager->getConfig('delete_enabled')); + } + + public function getView($group = null) + { + return $this->getIndex($group); + } + + protected function loadLocales() + { + //Set the default locale as the first one. + $locales = Translation::groupBy('locale') + ->select('locale') + ->get() + ->pluck('locale'); + + if ($locales instanceof Collection) { + $locales = $locales->all(); + } + $locales = array_merge([\Helper::getRealAppLocale()], $locales); + return array_unique($locales); + } + + public function postAdd($group = null) + { + $keys = explode("\n", request()->get('keys')); + + foreach($keys as $key){ + $key = trim($key); + if($group && $key){ + $this->manager->missingKey('*', $group, $key); + } + } + return redirect()->back(); + } + + public function postEdit($group = null) + { + if(!in_array($group, $this->manager->getConfig('exclude_groups'))) { + $name = request()->get('name'); + $value = request()->get('value'); + + list($locale, $key) = explode('|', $name, 2); + // $translation = Translation::firstOrNew([ + // 'locale' => $locale, + // 'group' => $group, + // 'key' => $key, + // ]); + try { + $translation = Translation::where('locale', $locale) + ->where('group', $group) + ->where(\DB::raw('BINARY `key`'), $key) + ->first(); + if (!$translation) { + $translation = new Translation(); + $translation->locale = $locale; + $translation->group = $group; + $translation->key = $key; + $translation->hash = md5($locale.$group.$key); + } + } catch (\Exception $e) { + $translation = Translation::firstOrNew([ + 'locale' => $locale, + 'group' => $group, + 'key' => $key, + ]); + } + $translation->value = (string) $value ?: null; + $translation->status = Translation::STATUS_CHANGED; + $translation->save(); + return array('status' => 'ok'); + } + } + + public function postDelete($group, $key) + { + if(!in_array($group, $this->manager->getConfig('exclude_groups')) && $this->manager->getConfig('delete_enabled')) { + Translation::where('group', $group)->where('key', $key)->delete(); + return ['status' => 'ok']; + } + } + + public function postImport(Request $request) + { + $replace = $request->get('replace', false); + + set_time_limit(0); + ini_set('max_execution_time', '600'); + ini_set('memory_limit', '500M'); + + // Clean translations table. + Translation::truncate(); + + $counter = $this->manager->importTranslations($replace); + + return ['status' => 'ok', 'counter' => $counter]; + } + + public function postFind() + { + $numFound = $this->manager->findTranslations(); + + return ['status' => 'ok', 'counter' => (int) $numFound]; + } + + public function postPublish($group = null) + { + $json = false; + + if($group === '_json'){ + $json = true; + } + + $this->manager->exportTranslations($group, $json); + + return ['status' => 'ok']; + } + + public function postAddGroup(Request $request) + { + $group = str_replace(".", '', $request->input('new-group')); + if ($group) + { + return redirect()->action('\Barryvdh\TranslationManager\Controller@getView',$group); + } + else + { + return redirect()->back(); + } + } + + public function postAddLocale(Request $request) + { + $locales = $this->manager->getLocales(); + $newLocale = str_replace([], '-', trim($request->input('new-locale'))); + if (!$newLocale || in_array($newLocale, $locales)) { + return redirect()->back(); + } + $this->manager->addLocale($newLocale); + return redirect()->back(); + } + + public function postRemoveLocale(Request $request) + { + foreach ($request->input('remove-locale', []) as $locale => $val) { + $this->manager->removeLocale($locale); + } + return redirect()->back(); + } +} diff --git a/freescout-dist/overrides/barryvdh/laravel-translation-manager/src/Manager.php b/freescout-dist/overrides/barryvdh/laravel-translation-manager/src/Manager.php new file mode 100644 index 0000000..824f0b3 --- /dev/null +++ b/freescout-dist/overrides/barryvdh/laravel-translation-manager/src/Manager.php @@ -0,0 +1,720 @@ +app = $app; + $this->files = $files; + $this->events = $events; + $this->config = $app['config']['translation-manager']; + $this->ignoreFilePath = storage_path('.ignore_locales'); + $this->locales = []; + $this->ignoreLocales = $this->getIgnoredLocales(); + } + + protected function getIgnoredLocales() + { + if (!$this->files->exists($this->ignoreFilePath)) { + return []; + } + $result = json_decode($this->files->get($this->ignoreFilePath)); + + return ($result && is_array($result)) ? $result : []; + } + + public function importTranslations($replace = false, $base = null) + { + $counter = 0; + //allows for vendor lang files to be properly recorded through recursion. + $vendor = true; + if ($base == null) { + $base = $this->app['path.lang']; + $vendor = false; + } + + // PHP translations in /resources/lang/. + foreach ($this->files->directories($base) as $langPath) { + $locale = basename($langPath); + + //import langfiles for each vendor + if ($locale == 'vendor') { + foreach ($this->files->directories($langPath) as $vendor) { + $counter += $this->importTranslations($replace, $vendor); + } + continue; + } + $vendorName = $this->files->name($this->files->dirname($langPath)); + foreach ($this->files->allfiles($langPath) as $file) { + $info = pathinfo($file); + $group = $info['filename']; + + if (in_array($group, $this->config['exclude_groups'])) { + continue; + } + $subLangPath = str_replace($langPath.DIRECTORY_SEPARATOR, '', $info['dirname']); + $subLangPath = str_replace(DIRECTORY_SEPARATOR, '/', $subLangPath); + $langPath = str_replace(DIRECTORY_SEPARATOR, '/', $langPath); + + if ($subLangPath != $langPath) { + $group = $subLangPath.'/'.$group; + } + + if (!$vendor) { + $translations = \Lang::getLoader()->load($locale, $group); + } else { + $translations = include $file; + $group = 'vendor/'.$vendorName; + } + + if ($translations && is_array($translations)) { + foreach (array_dot($translations) as $key => $value) { + $importedTranslation = $this->importTranslation($key, $value, $locale, $group, $replace); + $counter += $importedTranslation ? 1 : 0; + } + } + } + } + + // Import app json translations. + //$loader = new \Illuminate\Translation\FileLoader($this->files, $this->app[ 'path.lang' ]); + foreach ($this->files->files($this->app['path.lang']) as $jsonTranslationFile) { + if (strpos($jsonTranslationFile, '.json') === false) { + continue; + } + $locale = basename($jsonTranslationFile, '.json'); + // Ignore module translations backup files. + if (!preg_match('/^[a-zA-Z_\-]+$/', $locale)) { + continue; + } + + $group = self::JSON_GROUP; + // Retrieves JSON entries of the given locale only. + // No need to use - Modules JSON translations are also loaded. + //$translations = \Lang::getLoader()->load($locale, '*', '*'); + + //$translations = $loader->load( $locale, '*', '*' ); + + $translations = $this->readTranslationsFromJsonFile($jsonTranslationFile); + if ($translations && is_array($translations)) { + foreach ($translations as $key => $value) { + $importedTranslation = $this->importTranslation($key, $value, $locale, $group, $replace); + $counter += $importedTranslation ? 1 : 0; + } + } + unset($translations); + } + + // Import modules translations. + // Saving translations to DB takes a lot of time and memory. + $modules = \Module::all(); + foreach ($modules as $key => $module) { + $moduleLangPath = $module->getPath().'/Resources/lang/'; + if (!$this->files->exists($moduleLangPath) || !$this->files->isDirectory($moduleLangPath)) { + continue; + } + + foreach ($this->files->files($moduleLangPath) as $jsonTranslationFile) { + if (strpos($jsonTranslationFile, '.json') === false) { + continue; + } + + $locale = basename($jsonTranslationFile, '.json'); + // Miss incorrect locales. + if (!preg_match('/^[a-zA-Z_]+$/', $locale)) { + continue; + } + + $group = '_'.$module->getAlias(); + + // $loader = new \Illuminate\Translation\FileLoader($this->files, $moduleLangPath); + // $translations = \Lang::getLoader()->load($locale, '*', '*'); + $translations = $this->readTranslationsFromJsonFile($jsonTranslationFile); + + if ($translations && is_array($translations)) { + foreach ($translations as $key => $value) { + $importedTranslation = $this->importTranslation($key, $value, $locale, $group, $replace); + $counter += $importedTranslation ? 1 : 0; + } + } + unset($translations); + } + } + + // Find translations in files. + // This is done quickly. + $existingTranslations = $this->findTranslations(); + + // Remove translations which do not exist in files. + $this->removeNonexisting($existingTranslations); + + return $counter; + } + + public function readTranslationsFromJsonFile($path) + { + if (!file_exists($path)) { + return []; + } + $decoded = json_decode(file_get_contents($path), true); + + if (is_null($decoded) || json_last_error() !== JSON_ERROR_NONE) { + return []; + } + + return $decoded; + } + + /** + * Remove translations which do not exist in files. + * + * @param [type] $existingTranslations [description] + * + * @return [type] [description] + */ + public function removeNonexisting($existingTranslations) + { + // First process translations belonging to Modules one by one. + // $moduleAliases = []; + // foreach ($existingTranslations as $translation) { + // preg_match("/^_([^\.])+\./", $translation, $matches); + // if (empty($matches[1]) || in_array($matches[1], $moduleAliases)) { + // continue; + // } else { + // $moduleAliases[] = $matches[1]; + // } + // } + + $existingGroups = []; + // Get unique groups. + foreach ($existingTranslations as $key) { + preg_match("/^([a-zA-Z0-9_]+)\./", $key, $matches); + // try { + // list( $group, $item ) = explode( '.', $key); + // } catch (\Exception $e) { + // continue; + // } + if (empty($matches[1]) || in_array($matches[1], $existingGroups)) { + continue; + } else { + $existingGroups[] = $matches[1]; + } + } + + $existingGroups[] = self::JSON_GROUP; + + // Process translations by groups. + // Get from DB translations for each module and compare to existing. + foreach ($existingGroups as $group) { + $dbTranslations = Translation::where('group', $group)->get(); + + foreach ($dbTranslations as $dbTranslation) { + $found = false; + foreach ($existingTranslations as $existingKey) { + if ($group == self::JSON_GROUP) { + if ($dbTranslation['key'] == $existingKey) { + $found = true; + break; + } + } else { + if ($dbTranslation['group'].'.'.$dbTranslation['key'] == $existingKey) { + $found = true; + break; + } + } + } + if (!$found) { + $dbTranslation->delete(); + } + } + } + } + + public function importTranslation($key, $value, $locale, $group, $replace = false) + { + + // process only string values + if (is_array($value)) { + return false; + } + + // Miss modules translations: fr.module.json + if (!preg_match('/^[a-zA-Z_\-]+$/', $locale)) { + return false; + } + + $value = (string) $value; + // $translation = Translation::firstOrNew([ + // 'locale' => $locale, + // 'group' => $group, + // 'key' => $key, + // ]); + $hash = md5($locale.$group.$key); + + $data = [ + 'locale' => $locale, + 'group' => $group, + 'key' => $key, + 'value' => $value, + 'hash' => $hash, + ]; + $inserted = 0; + try { + $inserted = \DB::table('ltm_translations')->insert($data); + } catch(\Exception $e) { + + } + + if ($replace && !$inserted) { + Translation::where('hash', $hash)->update(['value' => $value]); + } + + return true; + + // Consumes too much memory. + /*try { + // $translation = Translation::where('locale', $locale) + // ->where('group', $group) + // ->where(\DB::raw('BINARY `key`'), $key) + // ->first(); + $translation = Translation::where('hash', $hash)->first(); + + if (!$translation) { + $translation = new Translation(); + $translation->locale = $locale; + $translation->group = $group; + $translation->key = $key; + $translation->hash = $hash; + } + } catch (\Exception $e) { + // $translation = Translation::firstOrNew([ + // 'locale' => $locale, + // 'group' => $group, + // 'key' => $key, + // ]); + $translation = Translation::firstOrNew(['hash' => $hash], [ + 'locale' => $locale, + 'group' => $group, + 'key' => $key, + 'hash' => $hash, + ]); + } + + // Check if the database is different then the files + // $newStatus = $translation->value === $value ? Translation::STATUS_SAVED : Translation::STATUS_CHANGED; + // if ($newStatus !== (int) $translation->status) { + // $translation->status = $newStatus; + // } + + // Only replace when empty, or explicitly told so + if ($replace || !$translation->value) { + $translation->value = $value; + } + + $translation->save(); + unset($translation); + + return true;*/ + } + + public function findTranslations($path = null) + { + $path = $path ?: base_path(); + $groupKeys = []; + $stringKeys = []; + $functions = $this->config['trans_functions']; + + $groupPattern = // See http://regexr.com/392hu + "[^\w|>]". // Must not have an alphanum or _ or > before real method + '('.implode('|', $functions).')'. // Must start with one of the functions + "\(". // Match opening parenthesis + "[\'\"]". // Match " or ' + '('. // Start a new group to match: + '[a-zA-Z0-9_-]+'. // Must start with group + "([.|\/](?! )[^\1)]+)+". // Be followed by one or more items/keys + ')'. // Close group + "[\'\"]". // Closing quote + "[\),]"; // Close parentheses or new parameter + + $stringPattern = + "[^\w|>]". // Must not have an alphanum or _ or > before real method + '('.implode('|', $functions).')'. // Must start with one of the functions + "\(". // Match opening parenthesis + "(?P['\"])". // Match " or ' and store in {quote} + "(?P(?:\\\k{quote}|(?!\k{quote}).)*)". // Match any string that can be {quote} escaped + "\k{quote}". // Match " or ' previously matched + "[\),]"; // Close parentheses or new parameter + + // Find all PHP + Twig files in the app folder, except for storage + $finder = new Finder(); + $searchInModules = false; + if (!empty(request()->submit) && request()->submit == 'modules') { + // Find in modules. + $searchInModules = true; + $finder->in($path.DIRECTORY_SEPARATOR.'Modules')->exclude('vendor')->exclude('.git')->name('*.php')->name('*.twig')->name('*.vue')->files(); + } else { + // Find in core. + $exclude = ['.git', 'bootstrap', 'config', 'database', 'public', 'routes', 'storage', 'tests', 'tools', 'vendor']; + $finder->in($path)->exclude($exclude)->name('*.php')->name('*.twig')->name('*.vue')->files(); + // foreach ($exclude as $exclude_folder) { + // $finder->exclude($exclude_folder); + // } + // $finder->files(); + } + + /** @var \Symfony\Component\Finder\SplFileInfo $file */ + foreach ($finder as $file) { + // Search the current file for the pattern + if (preg_match_all("/$groupPattern/siU", $file->getContents(), $matches)) { + // Get all matches + foreach ($matches[2] as $key) { + $group = explode('.', $key)[0]; + if (!in_array($group, $this->config['incorrect_groups'])) { + $groupKeys[] = $key; + } + } + } + + if (preg_match_all("/$stringPattern/siU", $file->getContents(), $matches)) { + $moduleAlias = ''; + preg_match("/Modules\/([^\/]+)\//", $file->getPathname(), $m); + if (!empty($m[1])) { + $moduleAlias = strtolower($m[1]); + } + + foreach ($matches['string'] as $key) { + if (preg_match("/(^[a-zA-Z0-9_-]+([.][^\1)\ ]+)+$)/siU", $key, $groupMatches)) { + // group{.group}.key format, already in $groupKeys but also matched here + // do nothing, it has to be treated as a group + continue; + } + + //TODO: This can probably be done in the regex, but I couldn't do it. + //skip keys which contain namespacing characters, unless they also contain a + //space, which makes it JSON. + if (!(str_contains($key, '::') && str_contains($key, '.')) + || str_contains($key, ' ')) { + + // Modules + //if ($searchInModules) { + if ($moduleAlias) { + $groupKeys[] = '_'.$moduleAlias.'.'.$key; + } else { + $stringKeys[] = $key; + } + } + } + } + } + + // Remove modules strings existing among the app strings. + foreach ($groupKeys as $i => $groupKey) { + + if (in_array(preg_replace("/^[^\.]+\./", '', $groupKey), $stringKeys)) { + unset($groupKeys[$i]); + } + } + + // Remove duplicates + $groupKeys = array_unique($groupKeys); + $stringKeys = array_unique($stringKeys); + + // Add the translations to the database, if not existing. + // Modules translations are added here too. + foreach ($groupKeys as $key) { + // Split the group and item + list($group, $item) = explode('.', $key, 2); + $this->missingKey('', $group, $item); + } + + foreach ($stringKeys as $key) { + $group = self::JSON_GROUP; + $item = $key; + $this->missingKey('', $group, $item); + } + + // Return the number of found translations + //return count( $groupKeys + $stringKeys ); + //return $groupKeys + $stringKeys; + return array_merge($groupKeys, $stringKeys); + } + + public function missingKey($namespace, $group, $key) + { + if (!in_array($group, $this->config['exclude_groups'])) { + try { + $translation = Translation::where('locale', \Helper::getRealAppLocale()) + ->where('group', $group) + ->where(\DB::raw('BINARY `key`'), $key) + ->first(); + if (!$translation) { + $translation = new Translation(); + $translation->locale = \Helper::getRealAppLocale(); + $translation->group = $group; + $translation->key = $key; + $translation->save(); + } + } catch (\Exception $e) { + Translation::firstOrCreate([ + 'locale' => \Helper::getRealAppLocale(), + 'group' => $group, + 'key' => $key, + ]); + } + } + } + + public function exportTranslations($group = null /*, $json = false*/) + { + $basePath = $this->app['path.lang']; + + $json = false; + // Detect json groups automatically. + if ($group && $group[0] == '_') { + $json = true; + } + + $moduleAlias = ''; + if ($json && $group != self::JSON_GROUP) { + $moduleAlias = substr($group, 1); + } + + if (!is_null($group) && !$json) { + if (!in_array($group, $this->config['exclude_groups'])) { + $vendor = false; + if ($group == '*') { + return $this->exportAllTranslations(); + } else { + if (starts_with($group, 'vendor')) { + $vendor = true; + } + } + + $tree = $this->makeTree(Translation::ofTranslatedGroup($group) + ->orderByGroupKeys(array_get($this->config, 'sort_keys', false)) + ->get()); + + foreach ($tree as $locale => $groups) { + if (isset($groups[$group])) { + $translations = $groups[$group]; + $path = $this->app['path.lang']; + + $locale_path = $locale.DIRECTORY_SEPARATOR.$group; + if ($vendor) { + $path = $basePath.'/'.$group.'/'.$locale; + $locale_path = str_after($group, '/'); + } + $subfolders = explode(DIRECTORY_SEPARATOR, $locale_path); + array_pop($subfolders); + + $subfolder_level = ''; + foreach ($subfolders as $subfolder) { + $subfolder_level = $subfolder_level.$subfolder.DIRECTORY_SEPARATOR; + + $temp_path = rtrim($path.DIRECTORY_SEPARATOR.$subfolder_level, DIRECTORY_SEPARATOR); + if (!is_dir($temp_path)) { + mkdir($temp_path, 0777, true); + } + } + + $path = $path.DIRECTORY_SEPARATOR.$locale.DIRECTORY_SEPARATOR.$group.'.php'; + + $output = "files->put($path, $output); + } + } + Translation::ofTranslatedGroup($group)->update(['status' => Translation::STATUS_SAVED]); + } + } + + if ($json) { + $tree = $this->makeTree(Translation::ofTranslatedGroup($group) + ->orderByGroupKeys(array_get($this->config, 'sort_keys', false)) + ->get(), true); + + foreach ($tree as $locale => $groups) { + if (!$moduleAlias) { + // _json + $path = $this->app['path.lang'].'/'.$locale.'.json'; + } else { + // Export module translations into module's folder + $modulePath = \Module::getModulePathByAlias($moduleAlias); + // Module not found. + if (!$modulePath) { + continue; + } + $path = $modulePath.'Resources/lang/'.$locale.'.json'; + if (!$this->files->exists($modulePath.'Resources/lang')) { + $this->files->makeDirectory($modulePath.'Resources/lang', 0755, true); + } + } + + $translations = $groups[$group]; + // Sort translations alphabetically. + ksort($translations); + $output = json_encode($translations, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE); + $this->files->put($path, $output); + + // If it is a module, also export translation into the main langs folder. + if ($moduleAlias && $modulePath) { + $path = $this->app['path.lang'].'/module.'.$moduleAlias.'.'.$locale.'.json'; + $this->files->put($path, $output); + } + // if ( isset( $groups[ self::JSON_GROUP ] ) ) { + // $translations = $groups[ self::JSON_GROUP ]; + // $path = $this->app[ 'path.lang' ] . '/' . $locale . '.json'; + // $output = json_encode( $translations, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE ); + // $this->files->put( $path, $output ); + // } + } + + Translation::ofTranslatedGroup(self::JSON_GROUP)->update(['status' => Translation::STATUS_SAVED]); + } + + $this->events->dispatch(new TranslationsExportedEvent()); + } + + public function exportAllTranslations() + { + $groups = Translation::whereNotNull('value')->selectDistinctGroup()->get('group'); + + foreach ($groups as $group) { + $this->exportTranslations($group->group); + // if ( $group->group == self::JSON_GROUP ) { + // $this->exportTranslations( null, true ); + // } else { + // $this->exportTranslations( $group->group ); + // } + } + + $this->events->dispatch(new TranslationsExportedEvent()); + } + + protected function makeTree($translations, $json = false) + { + $array = []; + foreach ($translations as $translation) { + if ($json) { + $this->jsonSet($array[$translation->locale][$translation->group], $translation->key, + $translation->value); + } else { + array_set($array[$translation->locale][$translation->group], $translation->key, + $translation->value); + } + } + + return $array; + } + + public function jsonSet(&$array, $key, $value) + { + if (is_null($key)) { + return $array = $value; + } + $array[$key] = $value; + + return $array; + } + + public function cleanTranslations() + { + Translation::whereNull('value')->delete(); + } + + public function truncateTranslations() + { + Translation::truncate(); + } + + public function getLocales() + { + if (empty($this->locales)) { + $locales = array_merge([\Helper::getRealAppLocale()], + Translation::groupBy('locale')->pluck('locale')->toArray()); + foreach ($this->files->directories($this->app->langPath()) as $localeDir) { + if (($name = $this->files->name($localeDir)) != 'vendor') { + $locales[] = $name; + } + } + + $this->locales = array_unique($locales); + sort($this->locales); + } + + return array_diff($this->locales, $this->ignoreLocales); + } + + public function addLocale($locale) + { + $localeDir = $this->app->langPath().'/'.basename($locale); + + $this->ignoreLocales = array_diff($this->ignoreLocales, [$locale]); + $this->saveIgnoredLocales(); + $this->ignoreLocales = $this->getIgnoredLocales(); + + if (!$this->files->exists($localeDir) || !$this->files->isDirectory($localeDir)) { + return $this->files->makeDirectory($localeDir); + } + + return true; + } + + protected function saveIgnoredLocales() + { + return $this->files->put($this->ignoreFilePath, json_encode($this->ignoreLocales)); + } + + public function removeLocale($locale) + { + if (!$locale) { + return false; + } + $this->ignoreLocales = array_merge($this->ignoreLocales, [$locale]); + // Only delete from DB. + //$this->saveIgnoredLocales(); + $this->ignoreLocales = $this->getIgnoredLocales(); + + Translation::where('locale', $locale)->delete(); + + // Remove folder. + $localeDir = $this->app->langPath().'/'.$locale; + if ($this->files->exists($localeDir)) { + $this->files->deleteDirectory($localeDir); + } + } + + public function getConfig($key = null) + { + if ($key == null) { + return $this->config; + } else { + return $this->config[$key]; + } + } +} diff --git a/freescout-dist/overrides/chumper/zipper/src/Chumper/Zipper/Repositories/ZipRepository.php b/freescout-dist/overrides/chumper/zipper/src/Chumper/Zipper/Repositories/ZipRepository.php new file mode 100644 index 0000000..619ed06 --- /dev/null +++ b/freescout-dist/overrides/chumper/zipper/src/Chumper/Zipper/Repositories/ZipRepository.php @@ -0,0 +1,190 @@ +archive = $archive ? $archive : new ZipArchive(); + + $res = $this->archive->open($filePath, ($create ? ZipArchive::CREATE : 0)); + if ($res !== true) { + throw new Exception("Error: Failed to open $filePath! Error: ".$this->getErrorMessage($res)); + } + } + + /** + * Add a file to the opened Archive + * + * @param $pathToFile + * @param $pathInArchive + */ + public function addFile($pathToFile, $pathInArchive) + { + $this->archive->addFile($pathToFile, $pathInArchive); + } + + /** + * Add an empty directory + * + * @param $dirName + */ + public function addEmptyDir($dirName) + { + $this->archive->addEmptyDir($dirName); + } + + /** + * Add a file to the opened Archive using its contents + * + * @param $name + * @param $content + */ + public function addFromString($name, $content) + { + $this->archive->addFromString($name, $content); + } + + /** + * Remove a file permanently from the Archive + * + * @param $pathInArchive + */ + public function removeFile($pathInArchive) + { + $this->archive->deleteName($pathInArchive); + } + + /** + * Get the content of a file + * + * @param $pathInArchive + * + * @return string + */ + public function getFileContent($pathInArchive) + { + return $this->archive->getFromName($pathInArchive); + } + + /** + * Get the stream of a file + * + * @param $pathInArchive + * + * @return mixed + */ + public function getFileStream($pathInArchive) + { + return $this->archive->getStream($pathInArchive); + } + + /** + * Will loop over every item in the archive and will execute the callback on them + * Will provide the filename for every item + * + * @param $callback + */ + public function each($callback) + { + for ($i = 0; $i < $this->archive->numFiles; ++$i) { + //skip if folder + $stats = $this->archive->statIndex($i); + if ($stats['size'] === 0 && $stats['crc'] === 0) { + continue; + } + + call_user_func_array($callback, [ + $this->archive->getNameIndex($i), + ]); + } + } + + /** + * Checks whether the file is in the archive + * + * @param $fileInArchive + * + * @return bool + */ + public function fileExists($fileInArchive) + { + return $this->archive->locateName($fileInArchive) !== false; + } + + /** + * Sets the password to be used for decompressing + * function named usePassword for clarity + * + * @param $password + * + * @return bool + */ + public function usePassword($password) + { + return $this->archive->setPassword($password); + } + + /** + * Returns the status of the archive as a string + * + * @return string + */ + public function getStatus() + { + return $this->archive->getStatusString(); + } + + /** + * Closes the archive and saves it + */ + public function close() + { + @$this->archive->close(); + } + + private function getErrorMessage($resultCode) + { + switch ($resultCode) { + case ZipArchive::ER_EXISTS: + return 'ZipArchive::ER_EXISTS - File already exists.'; + case ZipArchive::ER_INCONS: + return 'ZipArchive::ER_INCONS - Zip archive inconsistent.'; + case ZipArchive::ER_MEMORY: + return 'ZipArchive::ER_MEMORY - Malloc failure.'; + case ZipArchive::ER_NOENT: + return 'ZipArchive::ER_NOENT - No such file.'; + case ZipArchive::ER_NOZIP: + return 'ZipArchive::ER_NOZIP - Not a zip archive.'; + case ZipArchive::ER_OPEN: + return 'ZipArchive::ER_OPEN - Can\'t open file.'; + case ZipArchive::ER_READ: + return 'ZipArchive::ER_READ - Read error.'; + case ZipArchive::ER_SEEK: + return 'ZipArchive::ER_SEEK - Seek error.'; + default: + return "An unknown error [$resultCode] has occurred."; + } + } +} diff --git a/freescout-dist/overrides/codedge/laravel-selfupdater/src/AbstractRepositoryType.php b/freescout-dist/overrides/codedge/laravel-selfupdater/src/AbstractRepositoryType.php new file mode 100644 index 0000000..5c0a8cd --- /dev/null +++ b/freescout-dist/overrides/codedge/laravel-selfupdater/src/AbstractRepositoryType.php @@ -0,0 +1,166 @@ + + * @copyright See LICENSE file that was distributed with this source code. + */ +abstract class AbstractRepositoryType +{ + /** + * @var array + */ + protected $config; + + /** + * @var Finder|SplFileInfo[] + */ + protected $pathToUpdate; + + /** + * Unzip an archive. + * + * @param string $file + * @param string $targetDir + * @param bool $deleteZipArchive + * + * @throws \Exception + * + * @return bool + */ + protected function unzipArchive($file = '', $targetDir = '', $deleteZipArchive = true) + { + if (empty($file) || ! File::exists($file)) { + throw new \InvalidArgumentException("Archive [{$file}] cannot be found or is empty."); + } + + $zip = new \ZipArchive(); + $res = $zip->open($file); + + if (! $res) { + throw new \Exception("Cannot open zip archive [{$file}]."); + } + + if (empty($targetDir)) { + $extracted = $zip->extractTo(File::dirname($file)); + } else { + $extracted = $zip->extractTo($targetDir); + } + + $zip->close(); + + if ($extracted && $deleteZipArchive === true) { + File::delete($file); + } + + return true; + } + + /** + * Check a given directory recursively if all files are writeable. + * + * @throws \Exception + * + * @return bool + */ + protected function hasCorrectPermissionForUpdate() + { + if (! $this->pathToUpdate) { + throw new \Exception('No directory set for update. Please set the update with: setPathToUpdate(path).'); + } + + $collection = collect($this->pathToUpdate->files())->each(function ($file) { /* @var \SplFileInfo $file */ + if (! File::isWritable($file->getRealPath())) { + event(new HasWrongPermissions($this)); + + return false; + } + }); + + return true; + } + + /** + * Download a file to a given location. + * + * @param Client $client + * @param string $source + * @param string $storagePath + * + * @return mixed|\Psr\Http\Message\ResponseInterface + */ + protected function downloadRelease(Client $client, $source, $storagePath) + { + return $client->request( + 'GET', $source, [ + 'sink' => $storagePath, + 'timeout' => config('app.curl_timeout'), + 'connect_timeout' => config('app.curl_connect_timeout'), + 'proxy' => config('app.proxy'), + ]); + } + + /** + * Check if the source has already been downloaded. + * + * @param string $version A specific version + * + * @return bool + */ + protected function isSourceAlreadyFetched($version) + { + $storagePath = $this->config['download_path'].'/'.$version; + if (! File::exists($storagePath) || empty(File::directories($storagePath)) + ) { + return false; + } + + return true; + } + + /** + * Set the paths to be updated. + * + * @param string $path Path where the update should be run into + * @param array $exclude List of folder names that shall not be updated + */ + protected function setPathToUpdate($path, array $exclude) + { + $finder = (new Finder())->in($path)->exclude($exclude); + + $this->pathToUpdate = $finder; + } + + /** + * Create a releas sub-folder inside the storage dir. + * + * @param string $storagePath + * @param string $releaseName + */ + public function createReleaseFolder($storagePath, $releaseName) + { + $subDirName = File::directories($storagePath); + $directories = File::directories($subDirName[0]); + + File::makeDirectory($storagePath.'/'.$releaseName, 493, true, true); + + foreach ($directories as $directory) { /* @var string $directory */ + File::moveDirectory($directory, $storagePath.'/'.$releaseName.'/'.File::name($directory)); + } + + $files = File::allFiles($subDirName[0], true); + foreach ($files as $file) { /* @var \SplFileInfo $file */ + File::move($file->getRealPath(), $storagePath.'/'.$releaseName.'/'.$file->getFilename()); + } + + File::deleteDirectory($subDirName[0]); + } +} diff --git a/freescout-dist/overrides/codedge/laravel-selfupdater/src/SourceRepositoryTypes/GithubRepositoryType.php b/freescout-dist/overrides/codedge/laravel-selfupdater/src/SourceRepositoryTypes/GithubRepositoryType.php new file mode 100644 index 0000000..4581a02 --- /dev/null +++ b/freescout-dist/overrides/codedge/laravel-selfupdater/src/SourceRepositoryTypes/GithubRepositoryType.php @@ -0,0 +1,354 @@ + + * @copyright See LICENSE file that was distributed with this source code. + */ +class GithubRepositoryType extends AbstractRepositoryType implements SourceRepositoryTypeContract +{ + const GITHUB_API_URL = 'https://api.github.com'; + const NEW_VERSION_FILE = 'self-updater-new-version'; + + public static $exclude_folders = []; + + /** + * @var Client + */ + protected $client; + + /** + * Github constructor. + * + * @param Client $client + * @param array $config + */ + public function __construct(Client $client, array $config) + { + $this->client = $client; + $this->config = $config; + $this->config['version_installed'] = config('self-update.version_installed'); + $this->config['exclude_folders'] = config('self-update.exclude_folders'); + } + + /** + * Check repository if a newer version than the installed one is available. + * + * @param string $currentVersion + * + * @throws \InvalidArgumentException + * @throws \Exception + * + * @return bool + */ + public function isNewVersionAvailable($currentVersion = '') + { + $version = $currentVersion ?: $this->getVersionInstalled(); + + if (!$version) { + throw new \InvalidArgumentException('No currently installed version specified.'); + } + + if (version_compare($version, $this->getVersionAvailable(), '<')) { + // if (!$this->versionFileExists()) { + // $this->setVersionFile($this->getVersionAvailable()); + // event(new UpdateAvailable($this->getVersionAvailable())); + // } + + return true; + } + + return false; + } + + /** + * Fetches the latest version. If you do not want the latest version, specify one and pass it. + * + * @param string $version + * + * @throws \Exception + * + * @return mixed + */ + public function fetch($version = '') + { + $response = $this->getRepositoryReleases(); + $releaseCollection = collect(\GuzzleHttp\json_decode($response->getBody())); + + if ($releaseCollection->isEmpty()) { + throw new \Exception('Cannot find a release to update. Please check the repository you\'re pulling from'); + } + + $release = $releaseCollection->first(); + + $storagePath = $this->config['download_path']; + + $fs = new \Illuminate\Filesystem\Filesystem; + $fs->cleanDirectory($storagePath); + + if (!File::exists($storagePath)) { + File::makeDirectory($storagePath, 493, true, true); + } + + if (!empty($version)) { + $release = $releaseCollection->where('name', $version)->first(); + } + + $storageFilename = "{$release->name}.zip"; + + //if (!$this->isSourceAlreadyFetched($release->name)) { + if (File::exists($storagePath.DIRECTORY_SEPARATOR.$release->name)) { + File::deleteDirectory($storagePath.DIRECTORY_SEPARATOR.$release->name); + } + + $storageFile = $storagePath.DIRECTORY_SEPARATOR.$storageFilename; + $this->downloadRelease($this->client, $release->zipball_url, $storageFile); + + $this->unzipArchive($storageFile, $storagePath); + + $this->createReleaseFolder($storagePath, $release->name); + } + + /** + * Perform the actual update process. + * + * @param string $version + * + * @return bool + */ + public function update($version = '') + { + $this->setPathToUpdate(base_path(), $this->config['exclude_folders']); + + if ($this->hasCorrectPermissionForUpdate()) { + if (!empty($version)) { + $sourcePath = $this->config['download_path'].DIRECTORY_SEPARATOR.$version; + } else { + $sourcePath = File::directories($this->config['download_path'])[0]; + } + + // Copy Vendor first. + /*if (File::exists(base_path('vendor_backup'))) { + File::deleteDirectory(base_path('vendor_backup')); + }*/ + + // We copy new vendor and rename to avoid error: + // /vendor/composer/../laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php): failed to open stream: No such file or directory in /vendor/composer/ClassLoader.php:444 + /*File::move( + $sourcePath.DIRECTORY_SEPARATOR.'vendor', + base_path('vendor_new') + ); + File::move( + base_path('vendor'), + base_path('vendor_backup') + ); + File::move( + base_path('vendor_new'), + base_path('vendor') + );*/ + + self::$exclude_folders = $this->config['exclude_folders']; + + // Move folders + $folders_to_move = [ + 'vendor', + 'app', + 'config', + 'database', + 'overrides', + 'resources', + 'routes', + 'tests', + ]; + foreach ($folders_to_move as $folder) { + // Move folder to the source directory as _tmp + if (file_exists($sourcePath.DIRECTORY_SEPARATOR.$folder.'_tmp')) { + File::deleteDirectory($sourcePath.DIRECTORY_SEPARATOR.$folder.'_tmp'); + } + File::move( + base_path($folder), + $sourcePath.DIRECTORY_SEPARATOR.$folder.'_tmp' + ); + self::$exclude_folders[] = $folder.'_tmp'; + File::move( + $sourcePath.DIRECTORY_SEPARATOR.$folder, + base_path($folder) + ); + } + + // Move directories + collect((new Finder())->in($sourcePath)->exclude(self::$exclude_folders)->directories()->sort(function ($a, $b) { + return strlen($b->getRealpath()) - strlen($a->getRealpath()); + }))->each(function ($directory) { /** @var \SplFileInfo $directory */ + + if (count(array_intersect(File::directories( + $directory->getRealPath()), GithubRepositoryType::$exclude_folders)) == 0 + ) { + // Copy dir + File::copyDirectory( + $directory->getRealPath(), + base_path($directory->getRelativePath()).'/'.$directory->getBasename() + ); + } + // Delete dir + File::deleteDirectory($directory->getRealPath()); + }); + + // Now move all the files left in the main directory + //collect(File::allFiles($sourcePath, true))->each(function ($file) { /* @var \SplFileInfo $file */ + collect(File::files($sourcePath, true))->each(function ($file) { /* @var \SplFileInfo $file */ + File::copy($file->getRealPath(), base_path($file->getFilename())); + }); + + File::deleteDirectory($sourcePath); + $this->deleteVersionFile(); + event(new UpdateSucceeded($version)); + + return true; + } + + event(new UpdateFailed($this)); + + return false; + } + + /** + * Create a releas sub-folder inside the storage dir. + * + * @param string $storagePath + * @param string $releaseName + */ + public function createReleaseFolder($storagePath, $releaseName) + { + $subDirName = File::directories($storagePath); + $directories = File::directories($subDirName[0]); + + File::makeDirectory($storagePath.'/'.$releaseName, 493, true, true); + + foreach ($directories as $directory) { /* @var string $directory */ + File::moveDirectory($directory, $storagePath.'/'.$releaseName.'/'.File::name($directory)); + } + + $files = File::allFiles($subDirName[0], true); + foreach ($files as $file) { /* @var \SplFileInfo $file */ + File::move($file->getRealPath(), $storagePath.'/'.$releaseName.'/'.$file->getFilename()); + } + + File::deleteDirectory($subDirName[0]); + } + + /** + * Get the version that is currenly installed. + * Example: 1.1.0 or v1.1.0 or "1.1.0 version". + * + * @param string $prepend + * @param string $append + * + * @return string + */ + public function getVersionInstalled($prepend = '', $append = '') + { + return $prepend.$this->config['version_installed'].$append; + } + + /** + * Get the latest version that has been published in a certain repository. + * Example: 2.6.5 or v2.6.5. + * + * @param string $prepend Prepend a string to the latest version + * @param string $append Append a string to the latest version + * + * @return string + */ + public function getVersionAvailable($prepend = '', $append = '') + { + // No need to save version to file. + // if ($this->versionFileExists()) { + // $version = $prepend.$this->getVersionFile().$append; + // } else { + $response = $this->getRepositoryReleases(); + $releaseCollection = collect(\GuzzleHttp\json_decode($response->getBody())); + $version = $prepend.$releaseCollection->first()->name.$append; + //} + + return $version; + } + + /** + * Get all releases for a specific repository. + * + * @throws \Exception + * + * @return mixed|\Psr\Http\Message\ResponseInterface + */ + protected function getRepositoryReleases() + { + if (empty($this->config['repository_vendor']) || empty($this->config['repository_name'])) { + throw new \Exception('No repository specified. Please enter a valid Github repository owner and name in your config.'); + } + + return $this->client->request( + 'GET', + self::GITHUB_API_URL.'/repos/'.$this->config['repository_vendor'].'/'.$this->config['repository_name'].'/tags', [ + 'timeout' => config('app.curl_timeout'), + 'connect_timeout' => config('app.curl_connect_timeout'), + 'proxy' => config('app.proxy'), + ] + ); + } + + /** + * Check if the file with the new version already exists. + * + * @return bool + */ + protected function versionFileExists() + { + return Storage::exists(static::NEW_VERSION_FILE); + } + + /** + * Write the version file. + * + * @param $content + * + * @return bool + */ + protected function setVersionFile($content) + { + return Storage::put(static::NEW_VERSION_FILE, $content); + } + + /** + * Get the content of the version file. + * + * @return string + */ + protected function getVersionFile() + { + return Storage::get(static::NEW_VERSION_FILE); + } + + /** + * Delete the version file. + * + * @return bool + */ + protected function deleteVersionFile() + { + return Storage::delete(static::NEW_VERSION_FILE); + } +} diff --git a/freescout-dist/overrides/devfactory/minify/src/Providers/BaseProvider.php b/freescout-dist/overrides/devfactory/minify/src/Providers/BaseProvider.php new file mode 100644 index 0000000..ed871bf --- /dev/null +++ b/freescout-dist/overrides/devfactory/minify/src/Providers/BaseProvider.php @@ -0,0 +1,360 @@ +file = $file ?: new Filesystem; + + $this->publicPath = $publicPath ?: $_SERVER['DOCUMENT_ROOT']; + + $this->disable_mtime = $config['disable_mtime'] ?: false; + $this->hash_salt = $config['hash_salt'] ?: ''; + + $value = function($key) + { + return isset($_SERVER[$key]) ? $_SERVER[$key] : ''; + }; + + $this->headers = array( + 'User-Agent' => $value('HTTP_USER_AGENT'), + 'Accept' => $value('HTTP_ACCEPT'), + 'Accept-Language' => $value('HTTP_ACCEPT_LANGUAGE'), + 'Accept-Encoding' => 'identity', + 'Connection' => 'close', + ); + } + + /** + * @param $outputDir + * @return bool + */ + public function make($outputDir) + { + $this->outputDir = $this->publicPath . $outputDir; + + $this->checkDirectory(); + + if ($this->checkExistingFiles()) + { + return false; + } + + $this->removeOldFiles(); + $this->appendFiles(); + + return true; + } + + /** + * @param $file + * @return void + * @throws \Devfactory\Minify\Exceptions\FileNotExistException + */ + public function add($file) + { + if (is_array($file)) + { + foreach ($file as $value) $this->add($value); + } + else if ($this->checkExternalFile($file)) + { + $this->files[] = $file; + } + else { + $file = $this->publicPath . $file; + if (!file_exists($file)) + { + throw new FileNotExistException("File '{$file}' does not exist"); + } + + $this->files[] = $file; + } + } + + /** + * @param $baseUrl + * @param $attributes + * + * @return string + */ + public function tags($baseUrl, $attributes) + { + $html = ''; + foreach($this->files as $file) + { + $file = $baseUrl . str_replace($this->publicPath, '', $file); + $html .= $this->tag($file, $attributes); + } + + return $html; + } + + /** + * @return int + */ + public function count(): int + { + return count($this->files); + } + + /** + * @throws \Devfactory\Minify\Exceptions\FileNotExistException + */ + protected function appendFiles() + { + foreach ($this->files as $file) { + if ($this->checkExternalFile($file)) + { + if (strpos($file, '//') === 0) $file = 'http:' . $file; + + $headers = $this->headers; + foreach ($headers as $key => $value) + { + $headers[$key] = $key . ': ' . $value; + } + $context = stream_context_create(array('http' => array( + 'ignore_errors' => true, + 'header' => implode("\r\n", $headers), + ))); + + $http_response_header = array(false); + $contents = file_get_contents($file, false, $context); + + if (strpos($http_response_header[0], '200') === false) + { + throw new FileNotExistException("File '{$file}' does not exist"); + } + } else { + $contents = file_get_contents($file); + } + + $this->appended .= $contents . "\n"; + } + } + + /** + * @return bool + */ + protected function checkExistingFiles() + { + $this->buildMinifiedFilename(); + + return file_exists($this->outputDir . $this->filename); + } + + /** + * @throws \Devfactory\Minify\Exceptions\DirNotWritableException + * @throws \Devfactory\Minify\Exceptions\DirNotExistException + */ + protected function checkDirectory() + { + if (!file_exists($this->outputDir)) + { + // Try to create the directory + if (!$this->file->makeDirectory($this->outputDir, 0775, true)) { + throw new DirNotExistException("Buildpath '{$this->outputDir}' does not exist"); + } + } + + if (!is_writable($this->outputDir)) + { + throw new DirNotWritableException("Buildpath '{$this->outputDir}' is not writable"); + } + } + + /** + * @param string $file + * @return bool + */ + protected function checkExternalFile($file) + { + return preg_match('/^(https?:)?\/\//', $file); + } + + /** + * @return string + */ + protected function buildMinifiedFilename() + { + $this->filename = $this->getHashedFilename() . (($this->disable_mtime) ? '' : $this->countModificationTime()) . static::EXTENSION; + } + + /** + * Build an HTML attribute string from an array. + * + * @param array $attributes + * @return string + */ + protected function attributes($attributes) + { + $html = array(); + foreach ((array) $attributes as $key => $value) + { + $element = $this->attributeElement($key, $value); + + if ( ! is_null($element)) $html[] = $element; + } + + $output = count($html) > 0 ? ' '.implode(' ', $html) : ''; + + return trim($output); + } + + /** + * Build a single attribute element. + * + * @param string|integer $key + * @param string|boolean $value + * @return string|null + */ + protected function attributeElement($key, $value) + { + if (is_numeric($key)) $key = $value; + + if(is_bool($value)) + return $key; + + if ( ! is_null($value)) + return $key.'="'.htmlentities($value, ENT_QUOTES, 'UTF-8', false).'"'; + + return null; + } + + /** + * @return string + */ + protected function getHashedFilename() + { + $publicPath = $this->publicPath; + return md5(implode('-', array_map(function($file) use ($publicPath) { return str_replace($publicPath, '', $file); }, $this->files)) . $this->hash_salt); + } + + /** + * @return int + */ + protected function countModificationTime() + { + $time = 0; + + foreach ($this->files as $file) + { + if ($this->checkExternalFile($file)) + { + $userAgent = isset($this->headers['User-Agent']) ? $this->headers['User-Agent'] : ''; + $time += hexdec(substr(md5($file . $userAgent), 0, 8)); + } + else { + $time += filemtime($file); + } + } + + return $time; + } + + /** + * @throws \Devfactory\Minify\Exceptions\CannotRemoveFileException + */ + protected function removeOldFiles() + { + $pattern = $this->outputDir . $this->getHashedFilename() . '*'; + $find = glob($pattern); + + if( is_array($find) && count($find) ) + { + foreach ($find as $file) + { + if ( ! unlink($file) ) { + throw new CannotRemoveFileException("File '{$file}' cannot be removed"); + } + } + } + } + + /** + * @param $minified + * @return string + * @throws \Devfactory\Minify\Exceptions\CannotSaveFileException + */ + protected function put($minified) + { + if(file_put_contents($this->outputDir . $this->filename, $minified) === false) + { + throw new CannotSaveFileException("File '{$this->outputDir}{$this->filename}' cannot be saved"); + } + + return $this->filename; + } + + /** + * @return string + */ + public function getAppended() + { + return $this->appended; + } + + /** + * @return string + */ + public function getFilename() + { + return $this->filename; + } +} diff --git a/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php new file mode 100644 index 0000000..5147977 --- /dev/null +++ b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php @@ -0,0 +1,142 @@ +setAttribute(PDO::ATTR_STATEMENT_CLASS, [Statement::class, []]); + $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritdoc} + * : int|false + */ + #[\ReturnTypeWillChange] + public function exec($sql) + { + try { + $result = parent::exec($sql); + assert($result !== false); + + return $result; + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritdoc} + */ + public function getServerVersion() + { + return PDO::getAttribute(PDO::ATTR_SERVER_VERSION); + } + + /** + * @param string $sql + * @param array $driverOptions + * + * @return PDOStatement + * : PDOStatement|false + */ + #[\ReturnTypeWillChange] + public function prepare($sql, $driverOptions = []) + { + try { + $statement = parent::prepare($sql, $driverOptions); + assert($statement instanceof PDOStatement); + + return $statement; + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritdoc} + * : string|false + */ + #[\ReturnTypeWillChange] + public function quote($value, $type = ParameterType::STRING) + { + return parent::quote($value ?? '', $type); + } + + /** + * {@inheritdoc} + * : string|false + */ + #[\ReturnTypeWillChange] + public function lastInsertId($name = null) + { + try { + if ($name === null) { + return parent::lastInsertId(); + } + + return parent::lastInsertId($name); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritdoc} + */ + public function requiresQueryForServerVersion() + { + return false; + } + + /** + * @param mixed ...$args + */ + private function doQuery(...$args): PDOStatement + { + try { + $stmt = parent::query(...$args); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + + assert($stmt instanceof PDOStatement); + + return $stmt; + } +} diff --git a/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOQueryImplementation.php b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOQueryImplementation.php new file mode 100644 index 0000000..df92a6c --- /dev/null +++ b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOQueryImplementation.php @@ -0,0 +1,41 @@ += 80000) { + /** + * @internal + */ + trait PDOQueryImplementation + { + /** + * @return PDOStatement + * : PDOStatement|false + */ + #[\ReturnTypeWillChange] + public function query(?string $query = null, ?int $fetchMode = null, mixed ...$fetchModeArgs) + { + return $this->doQuery($query, $fetchMode, ...$fetchModeArgs); + } + } +} else { + /** + * @internal + */ + trait PDOQueryImplementation + { + /** + * @return PDOStatement + */ + public function query() + { + return $this->doQuery(...func_get_args()); + } + } +} diff --git a/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php new file mode 100644 index 0000000..bf9fcac --- /dev/null +++ b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php @@ -0,0 +1,312 @@ + PDO::PARAM_NULL, + ParameterType::INTEGER => PDO::PARAM_INT, + ParameterType::STRING => PDO::PARAM_STR, + ParameterType::ASCII => PDO::PARAM_STR, + ParameterType::BINARY => PDO::PARAM_LOB, + ParameterType::LARGE_OBJECT => PDO::PARAM_LOB, + ParameterType::BOOLEAN => PDO::PARAM_BOOL, + ]; + + private const FETCH_MODE_MAP = [ + FetchMode::ASSOCIATIVE => PDO::FETCH_ASSOC, + FetchMode::NUMERIC => PDO::FETCH_NUM, + FetchMode::MIXED => PDO::FETCH_BOTH, + FetchMode::STANDARD_OBJECT => PDO::FETCH_OBJ, + FetchMode::COLUMN => PDO::FETCH_COLUMN, + FetchMode::CUSTOM_OBJECT => PDO::FETCH_CLASS, + ]; + + /** + * Protected constructor. + * + * @internal The statement can be only instantiated by its driver connection. + */ + protected function __construct() + { + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + $type = $this->convertParamType($type); + + try { + return parent::bindValue($param, $value, $type); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * @param mixed $param + * @param mixed $variable + * @param int $type + * @param int|null $length + * @param mixed $driverOptions + * + * @return bool + */ + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null): bool + { + $type = $this->convertParamType($type); + + try { + return parent::bindParam($param, $variable, $type, ...array_slice(func_get_args(), 3)); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use free() instead. + */ + public function closeCursor(): bool + { + try { + return parent::closeCursor(); + } catch (PDOException $exception) { + // Exceptions not allowed by the interface. + // In case driver implementations do not adhere to the interface, silence exceptions here. + return true; + } + } + + /** + * {@inheritdoc} + */ + public function execute($params = null): bool + { + try { + return parent::execute($params); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchNumeric(), fetchAssociative() or fetchOne() instead. + * : mixed + */ + #[\ReturnTypeWillChange] + public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) + { + $args = func_get_args(); + + if (isset($args[0])) { + $args[0] = $this->convertFetchMode($args[0]); + } + + try { + return parent::fetch(...$args); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritdoc} + * + * @deprecated Use fetchOne() instead. + * : mixed + */ + #[\ReturnTypeWillChange] + public function fetchColumn($columnIndex = 0) + { + try { + return parent::fetchColumn($columnIndex); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritdoc} + */ + public function fetchNumeric() + { + return $this->fetch(PDO::FETCH_NUM); + } + + /** + * {@inheritdoc} + */ + public function fetchAssociative() + { + return $this->fetch(PDO::FETCH_ASSOC); + } + + /** + * {@inheritdoc} + */ + public function fetchOne() + { + return $this->fetch(PDO::FETCH_COLUMN); + } + + /** + * {@inheritdoc} + */ + public function fetchAllNumeric(): array + { + return $this->fetchAll(PDO::FETCH_NUM); + } + + /** + * {@inheritdoc} + */ + public function fetchAllAssociative(): array + { + return $this->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * {@inheritdoc} + */ + public function fetchFirstColumn(): array + { + return $this->fetchAll(PDO::FETCH_COLUMN); + } + + public function free(): void + { + parent::closeCursor(); + } + + /** + * @param mixed ...$args + */ + private function doSetFetchMode(int $fetchMode, ...$args): bool + { + $fetchMode = $this->convertFetchMode($fetchMode); + + // This thin wrapper is necessary to shield against the weird signature + // of PDOStatement::setFetchMode(): even if the second and third + // parameters are optional, PHP will not let us remove it from this + // declaration. + $slice = []; + + foreach ($args as $arg) { + if ($arg === null) { + break; + } + + $slice[] = $arg; + } + + try { + return parent::setFetchMode($fetchMode, ...$slice); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * @param mixed ...$args + * + * @return mixed[] + */ + private function doFetchAll(...$args): array + { + if (isset($args[0])) { + $args[0] = $this->convertFetchMode($args[0]); + } + + $slice = []; + + foreach ($args as $arg) { + if ($arg === null) { + break; + } + + $slice[] = $arg; + } + + try { + $data = parent::fetchAll(...$slice); + } catch (PDOException $exception) { + throw Exception::new($exception); + } + + assert(is_array($data)); + + return $data; + } + + /** + * Converts DBAL parameter type to PDO parameter type + * + * @param int $type Parameter type + */ + private function convertParamType(int $type): int + { + if (! isset(self::PARAM_TYPE_MAP[$type])) { + // TODO: next major: throw an exception + @trigger_error(sprintf( + 'Using a PDO parameter type (%d given) is deprecated and will cause an error in Doctrine DBAL 3.0', + $type + ), E_USER_DEPRECATED); + + return $type; + } + + return self::PARAM_TYPE_MAP[$type]; + } + + /** + * Converts DBAL fetch mode to PDO fetch mode + * + * @param int $fetchMode Fetch mode + */ + private function convertFetchMode(int $fetchMode): int + { + if (! isset(self::FETCH_MODE_MAP[$fetchMode])) { + // TODO: next major: throw an exception + @trigger_error(sprintf( + 'Using a PDO fetch mode or their combination (%d given)' . + ' is deprecated and will cause an error in Doctrine DBAL 3.0', + $fetchMode + ), E_USER_DEPRECATED); + + return $fetchMode; + } + + return self::FETCH_MODE_MAP[$fetchMode]; + } +} diff --git a/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatementImplementations.php b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatementImplementations.php new file mode 100644 index 0000000..c4ab9eb --- /dev/null +++ b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatementImplementations.php @@ -0,0 +1,73 @@ += 80000) { + /** + * @internal + */ + trait PDOStatementImplementations + { + /** + * @deprecated Use one of the fetch- or iterate-related methods. + * + * @param int $mode + * @param mixed ...$args + * + * @return bool + */ + public function setFetchMode($mode, ...$args) + { + return $this->doSetFetchMode($mode, ...$args); + } + + /** + * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. + * + * @param int|null $mode + * @param mixed ...$args + * + * @return mixed[] + */ + public function fetchAll($mode = null, ...$args): array + { + return $this->doFetchAll($mode, ...$args); + } + } +} else { + /** + * @internal + */ + trait PDOStatementImplementations + { + /** + * @deprecated Use one of the fetch- or iterate-related methods. + * + * @param int $fetchMode + * @param mixed $arg2 + * @param mixed $arg3 + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null): bool + { + return $this->doSetFetchMode(...func_get_args()); + } + + /** + * @deprecated Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. + * + * @param int|null $fetchMode + * @param mixed $fetchArgument + * @param mixed $ctorArgs + * + * @return mixed[] + */ + public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) + { + return $this->doFetchAll(...func_get_args()); + } + } +} diff --git a/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php new file mode 100644 index 0000000..63bbff1 --- /dev/null +++ b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php @@ -0,0 +1,1297 @@ + [ + 't', + 'true', + 'y', + 'yes', + 'on', + '1', + ], + 'false' => [ + 'f', + 'false', + 'n', + 'no', + 'off', + '0', + ], + ]; + + /** + * PostgreSQL has different behavior with some drivers + * with regard to how booleans have to be handled. + * + * Enables use of 'true'/'false' or otherwise 1 and 0 instead. + * + * @param bool $flag + * + * @return void + */ + public function setUseBooleanTrueFalseStrings($flag) + { + $this->useBooleanTrueFalseStrings = (bool) $flag; + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($string, $start, $length = null) + { + if ($length === null) { + return 'SUBSTRING(' . $string . ' FROM ' . $start . ')'; + } + + return 'SUBSTRING(' . $string . ' FROM ' . $start . ' FOR ' . $length . ')'; + } + + /** + * {@inheritDoc} + */ + public function getNowExpression() + { + return 'LOCALTIMESTAMP(0)'; + } + + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'SIMILAR TO'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos !== false) { + $str = $this->getSubstringExpression($str, $startPos); + + return 'CASE WHEN (POSITION(' . $substr . ' IN ' . $str . ') = 0) THEN 0' + . ' ELSE (POSITION(' . $substr . ' IN ' . $str . ') + ' . ($startPos - 1) . ') END'; + } + + return 'POSITION(' . $substr . ' IN ' . $str . ')'; + } + + /** + * {@inheritdoc} + */ + protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) + { + if ($unit === DateIntervalUnit::QUARTER) { + $interval *= 3; + $unit = DateIntervalUnit::MONTH; + } + + return '(' . $date . ' ' . $operator . ' (' . $interval . " || ' " . $unit . "')::interval)"; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))'; + } + + /** + * {@inheritDoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsSchemas() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getDefaultSchemaName() + { + return 'public'; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function supportsPartialIndexes() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function usesSequenceEmulatedIdentityColumns() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getIdentitySequenceName($tableName, $columnName) + { + return $tableName . '_' . $columnName . '_seq'; + } + + /** + * {@inheritDoc} + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritDoc} + * + * @deprecated + */ + public function prefersSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function hasNativeGuidType() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return 'SELECT datname FROM pg_database'; + } + + /** + * {@inheritDoc} + */ + public function getListNamespacesSQL() + { + return "SELECT schema_name AS nspname + FROM information_schema.schemata + WHERE schema_name NOT LIKE 'pg\_%' + AND schema_name != 'information_schema'"; + } + + /** + * {@inheritDoc} + */ + public function getListSequencesSQL($database) + { + return "SELECT sequence_name AS relname, + sequence_schema AS schemaname + FROM information_schema.sequences + WHERE sequence_schema NOT LIKE 'pg\_%' + AND sequence_schema != 'information_schema'"; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT quote_ident(table_name) AS table_name, + table_schema AS schema_name + FROM information_schema.tables + WHERE table_schema NOT LIKE 'pg\_%' + AND table_schema != 'information_schema' + AND table_name != 'geometry_columns' + AND table_name != 'spatial_ref_sys' + AND table_type != 'VIEW'"; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return 'SELECT quote_ident(table_name) AS viewname, + table_schema AS schemaname, + view_definition AS definition + FROM information_schema.views + WHERE view_definition IS NOT NULL'; + } + + /** + * @param string $table + * @param string|null $database + * + * @return string + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + return 'SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef + FROM pg_catalog.pg_constraint r + WHERE r.conrelid = + ( + SELECT c.oid + FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n + WHERE ' . $this->getTableWhereClause($table) . " AND n.oid = c.relnamespace + ) + AND r.contype = 'f'"; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + $table = new Identifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return sprintf( + <<<'SQL' +SELECT + quote_ident(relname) as relname +FROM + pg_class +WHERE oid IN ( + SELECT indexrelid + FROM pg_index, pg_class + WHERE pg_class.relname = %s + AND pg_class.oid = pg_index.indrelid + AND (indisunique = 't' OR indisprimary = 't') + ) +SQL + , + $table + ); + } + + /** + * {@inheritDoc} + * + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + public function getListTableIndexesSQL($table, $database = null) + { + return 'SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary, + pg_index.indkey, pg_index.indrelid, + pg_get_expr(indpred, indrelid) AS where + FROM pg_class, pg_index + WHERE oid IN ( + SELECT indexrelid + FROM pg_index si, pg_class sc, pg_namespace sn + WHERE ' . $this->getTableWhereClause($table, 'sc', 'sn') . ' + AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid + ) AND pg_index.indexrelid = oid'; + } + + /** + * @param string $table + * @param string $classAlias + * @param string $namespaceAlias + * + * @return string + */ + private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n') + { + $whereClause = $namespaceAlias . ".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND "; + if (strpos($table, '.') !== false) { + [$schema, $table] = explode('.', $table); + $schema = $this->quoteStringLiteral($schema); + } else { + $schema = 'ANY(current_schemas(false))'; + } + + $table = new Identifier($table); + $table = $this->quoteStringLiteral($table->getName()); + + return $whereClause . sprintf( + '%s.relname = %s AND %s.nspname = %s', + $classAlias, + $table, + $namespaceAlias, + $schema + ); + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + return "SELECT + a.attnum, + quote_ident(a.attname) AS field, + t.typname AS type, + format_type(a.atttypid, a.atttypmod) AS complete_type, + (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, + (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM + pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type, + a.attnotnull AS isnotnull, + (SELECT 't' + FROM pg_index + WHERE c.oid = pg_index.indrelid + AND pg_index.indkey[0] = a.attnum + AND pg_index.indisprimary = 't' + ) AS pri, + (SELECT pg_get_expr(adbin, adrelid) + FROM pg_attrdef + WHERE c.oid = pg_attrdef.adrelid + AND pg_attrdef.adnum=a.attnum + ) AS default, + (SELECT pg_description.description + FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid + ) AS comment + FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n + WHERE " . $this->getTableWhereClause($table, 'c', 'n') . ' + AND a.attnum > 0 + AND a.attrelid = c.oid + AND a.atttypid = t.oid + AND n.oid = c.relnamespace + ORDER BY a.attnum'; + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($name) + { + return 'CREATE DATABASE ' . $name; + } + + /** + * Returns the SQL statement for disallowing new connections on the given database. + * + * This is useful to force DROP DATABASE operations which could fail because of active connections. + * + * @deprecated + * + * @param string $database The name of the database to disallow new connections for. + * + * @return string + */ + public function getDisallowDatabaseConnectionsSQL($database) + { + return "UPDATE pg_database SET datallowconn = 'false' WHERE datname = " . $this->quoteStringLiteral($database); + } + + /** + * Returns the SQL statement for closing currently active connections on the given database. + * + * This is useful to force DROP DATABASE operations which could fail because of active connections. + * + * @deprecated + * + * @param string $database The name of the database to close currently active connections for. + * + * @return string + */ + public function getCloseActiveDatabaseConnectionsSQL($database) + { + return 'SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = ' + . $this->quoteStringLiteral($database); + } + + /** + * {@inheritDoc} + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = ''; + + if ($foreignKey->hasOption('match')) { + $query .= ' MATCH ' . $foreignKey->getOption('match'); + } + + $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + + if ( + ($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false) + || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) + ) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + + return $query; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = []; + $commentsSQL = []; + $columnSql = []; + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + + $comment = $this->getColumnComment($column); + + if ($comment === null || $comment === '') { + continue; + } + + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $comment + ); + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $query = 'DROP ' . $column->getQuotedName($this); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + if ($this->isUnchangedBinaryColumn($columnDiff)) { + continue; + } + + $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($this); + $column = $columnDiff->column; + + if ( + $columnDiff->hasChanged('type') + || $columnDiff->hasChanged('precision') + || $columnDiff->hasChanged('scale') + || $columnDiff->hasChanged('fixed') + ) { + $type = $column->getType(); + + // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type + $columnDefinition = $column->toArray(); + $columnDefinition['autoincrement'] = false; + + // here was a server version check before, but DBAL API does not support this anymore. + $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($columnDefinition, $this); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + if ($columnDiff->hasChanged('default') || $this->typeChangeBreaksDefaultValue($columnDiff)) { + $defaultClause = $column->getDefault() === null + ? ' DROP DEFAULT' + : ' SET' . $this->getDefaultValueDeclarationSQL($column->toArray()); + $query = 'ALTER ' . $oldColumnName . $defaultClause; + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + if ($columnDiff->hasChanged('notnull')) { + $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL'; + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + if ($columnDiff->hasChanged('autoincrement')) { + if ($column->getAutoincrement()) { + // add autoincrement + $seqName = $this->getIdentitySequenceName($diff->name, $oldColumnName); + + $sql[] = 'CREATE SEQUENCE ' . $seqName; + $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ') FROM ' + . $diff->getName($this)->getQuotedName($this) . '))'; + $query = 'ALTER ' . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')"; + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } else { + // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have + $query = 'ALTER ' . $oldColumnName . ' DROP DEFAULT'; + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + } + + $newComment = $this->getColumnComment($column); + $oldComment = $this->getOldColumnComment($columnDiff); + + if ( + $columnDiff->hasChanged('comment') + || ($columnDiff->fromColumn !== null && $oldComment !== $newComment) + ) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->getName($this)->getQuotedName($this), + $column->getQuotedName($this), + $newComment + ); + } + + if (! $columnDiff->hasChanged('length')) { + continue; + } + + $query = 'ALTER ' . $oldColumnName . ' TYPE ' + . $column->getType()->getSQLDeclaration($column->toArray(), $this); + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query; + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = new Identifier($oldColumnName); + + $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . + ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this); + } + + $tableSql = []; + + if (! $this->onSchemaAlterTable($diff, $tableSql)) { + $sql = array_merge($sql, $commentsSQL); + + $newName = $diff->getNewName(); + + if ($newName !== false) { + $sql[] = sprintf( + 'ALTER TABLE %s RENAME TO %s', + $diff->getName($this)->getQuotedName($this), + $newName->getQuotedName($this) + ); + } + + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * Checks whether a given column diff is a logically unchanged binary type column. + * + * Used to determine whether a column alteration for a binary type column can be skipped. + * Doctrine's {@link BinaryType} and {@link BlobType} are mapped to the same database column type on this platform + * as this platform does not have a native VARBINARY/BINARY column type. Therefore the comparator + * might detect differences for binary type columns which do not have to be propagated + * to database as there actually is no difference at database level. + * + * @param ColumnDiff $columnDiff The column diff to check against. + * + * @return bool True if the given column diff is an unchanged binary type column, false otherwise. + */ + private function isUnchangedBinaryColumn(ColumnDiff $columnDiff) + { + $columnType = $columnDiff->column->getType(); + + if (! $columnType instanceof BinaryType && ! $columnType instanceof BlobType) { + return false; + } + + $fromColumn = $columnDiff->fromColumn instanceof Column ? $columnDiff->fromColumn : null; + + if ($fromColumn) { + $fromColumnType = $fromColumn->getType(); + + if (! $fromColumnType instanceof BinaryType && ! $fromColumnType instanceof BlobType) { + return false; + } + + return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0; + } + + if ($columnDiff->hasChanged('type')) { + return false; + } + + return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0; + } + + /** + * {@inheritdoc} + */ + protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) + { + if (strpos($tableName, '.') !== false) { + [$schema] = explode('.', $tableName); + $oldIndexName = $schema . '.' . $oldIndexName; + } + + return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; + } + + /** + * {@inheritdoc} + */ + public function getCommentOnColumnSQL($tableName, $columnName, $comment) + { + $tableName = new Identifier($tableName); + $columnName = new Identifier($columnName); + $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment); + + return sprintf( + 'COMMENT ON COLUMN %s.%s IS %s', + $tableName->getQuotedName($this), + $columnName->getQuotedName($this), + $comment + ); + } + + /** + * {@inheritDoc} + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + ' MINVALUE ' . $sequence->getInitialValue() . + ' START ' . $sequence->getInitialValue() . + $this->getSequenceCacheSQL($sequence); + } + + /** + * {@inheritDoc} + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + $this->getSequenceCacheSQL($sequence); + } + + /** + * Cache definition for sequences + * + * @return string + */ + private function getSequenceCacheSQL(Sequence $sequence) + { + if ($sequence->getCache() > 1) { + return ' CACHE ' . $sequence->getCache(); + } + + return ''; + } + + /** + * {@inheritDoc} + */ + public function getDropSequenceSQL($sequence) + { + if ($sequence instanceof Sequence) { + $sequence = $sequence->getQuotedName($this); + } + + return 'DROP SEQUENCE ' . $sequence . ' CASCADE'; + } + + /** + * {@inheritDoc} + */ + public function getCreateSchemaSQL($schemaName) + { + return 'CREATE SCHEMA ' . $schemaName; + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + return $this->getDropConstraintSQL($foreignKey, $table); + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($name, array $columns, array $options = []) + { + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')'; + + $sql = [$query]; + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $index) { + $sql[] = $this->getCreateIndexSQL($index, $name); + } + } + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $name); + } + } + + return $sql; + } + + /** + * Converts a single boolean value. + * + * First converts the value to its native PHP boolean type + * and passes it to the given callback function to be reconverted + * into any custom representation. + * + * @param mixed $value The value to convert. + * @param callable $callback The callback function to use for converting the real boolean value. + * + * @return mixed + * + * @throws UnexpectedValueException + */ + private function convertSingleBooleanValue($value, $callback) + { + if ($value === null) { + return $callback(null); + } + + if (is_bool($value) || is_numeric($value)) { + return $callback((bool) $value); + } + + if (! is_string($value)) { + return $callback(true); + } + + /** + * Better safe than sorry: http://php.net/in_array#106319 + */ + if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) { + return $callback(false); + } + + if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) { + return $callback(true); + } + + throw new UnexpectedValueException(sprintf("Unrecognized boolean literal '%s'", $value)); + } + + /** + * Converts one or multiple boolean values. + * + * First converts the value(s) to their native PHP boolean type + * and passes them to the given callback function to be reconverted + * into any custom representation. + * + * @param mixed $item The value(s) to convert. + * @param callable $callback The callback function to use for converting the real boolean value(s). + * + * @return mixed + */ + private function doConvertBooleans($item, $callback) + { + if (is_array($item)) { + foreach ($item as $key => $value) { + $item[$key] = $this->convertSingleBooleanValue($value, $callback); + } + + return $item; + } + + return $this->convertSingleBooleanValue($item, $callback); + } + + /** + * {@inheritDoc} + * + * Postgres wants boolean values converted to the strings 'true'/'false'. + */ + public function convertBooleans($item) + { + if (! $this->useBooleanTrueFalseStrings) { + return parent::convertBooleans($item); + } + + return $this->doConvertBooleans( + $item, + static function ($boolean) { + if ($boolean === null) { + return 'NULL'; + } + + return $boolean === true ? 'true' : 'false'; + } + ); + } + + /** + * {@inheritDoc} + */ + public function convertBooleansToDatabaseValue($item) + { + if (! $this->useBooleanTrueFalseStrings) { + return parent::convertBooleansToDatabaseValue($item); + } + + return $this->doConvertBooleans( + $item, + static function ($boolean) { + return $boolean === null ? null : (int) $boolean; + } + ); + } + + /** + * {@inheritDoc} + */ + public function convertFromBoolean($item) + { + if (in_array(strtolower($item), $this->booleanLiterals['false'], true)) { + return false; + } + + return parent::convertFromBoolean($item); + } + + /** + * {@inheritDoc} + */ + public function getSequenceNextValSQL($sequence) + { + return "SELECT NEXTVAL('" . $sequence . "')"; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ' + . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $column) + { + return 'BOOLEAN'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $column) + { + if (! empty($column['autoincrement'])) { + return 'SERIAL'; + } + + return 'INT'; + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $column) + { + if (! empty($column['autoincrement'])) { + return 'BIGSERIAL'; + } + + return 'BIGINT'; + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $column) + { + return 'SMALLINT'; + } + + /** + * {@inheritDoc} + */ + public function getGuidTypeDeclarationSQL(array $column) + { + return 'UUID'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $column) + { + return 'TIMESTAMP(0) WITHOUT TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $column) + { + return 'TIMESTAMP(0) WITH TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $column) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $column) + { + return 'TIME(0) WITHOUT TIME ZONE'; + } + + /** + * {@inheritDoc} + * + * @deprecated Use application-generated UUIDs instead + */ + public function getGuidExpression() + { + return 'UUID_GENERATE_V4()'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $column) + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * {@inheritdoc} + */ + protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) + { + return 'BYTEA'; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $column) + { + return 'TEXT'; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'postgresql'; + } + + /** + * {@inheritDoc} + * + * PostgreSQL returns all column names in SQL result sets in lowercase. + * + * @deprecated + */ + public function getSQLResultCasing($column) + { + return strtolower($column); + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:sO'; + } + + /** + * {@inheritDoc} + */ + public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) + { + return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableIdentifier = new Identifier($tableName); + $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); + + if ($cascade) { + $sql .= ' CASCADE'; + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + public function getReadLockSQL() + { + return 'FOR SHARE'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = [ + 'smallint' => 'smallint', + 'int2' => 'smallint', + 'serial' => 'integer', + 'serial4' => 'integer', + 'int' => 'integer', + 'int4' => 'integer', + 'integer' => 'integer', + 'bigserial' => 'bigint', + 'serial8' => 'bigint', + 'bigint' => 'bigint', + 'int8' => 'bigint', + 'bool' => 'boolean', + 'boolean' => 'boolean', + 'text' => 'text', + 'tsvector' => 'text', + 'varchar' => 'string', + 'interval' => 'string', + '_varchar' => 'string', + 'char' => 'string', + 'bpchar' => 'string', + 'inet' => 'string', + 'date' => 'date', + 'datetime' => 'datetime', + 'timestamp' => 'datetime', + 'timestamptz' => 'datetimetz', + 'time' => 'time', + 'timetz' => 'time', + 'float' => 'float', + 'float4' => 'float', + 'float8' => 'float', + 'double' => 'float', + 'double precision' => 'float', + 'real' => 'float', + 'decimal' => 'decimal', + 'money' => 'decimal', + 'numeric' => 'decimal', + 'year' => 'date', + 'uuid' => 'guid', + 'bytea' => 'blob', + ]; + } + + /** + * {@inheritDoc} + */ + public function getVarcharMaxLength() + { + return 65535; + } + + /** + * {@inheritdoc} + */ + public function getBinaryMaxLength() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function getBinaryDefaultLength() + { + return 0; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return Keywords\PostgreSQLKeywords::class; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $column) + { + return 'BYTEA'; + } + + /** + * {@inheritdoc} + */ + public function getDefaultValueDeclarationSQL($column) + { + if ($this->isSerialColumn($column)) { + return ''; + } + + return parent::getDefaultValueDeclarationSQL($column); + } + + /** + * @param mixed[] $column + */ + private function isSerialColumn(array $column): bool + { + return isset($column['type'], $column['autoincrement']) + && $column['autoincrement'] === true + && $this->isNumericType($column['type']); + } + + /** + * Check whether the type of a column is changed in a way that invalidates the default value for the column + */ + private function typeChangeBreaksDefaultValue(ColumnDiff $columnDiff): bool + { + if (! $columnDiff->fromColumn) { + return $columnDiff->hasChanged('type'); + } + + $oldTypeIsNumeric = $this->isNumericType($columnDiff->fromColumn->getType()); + $newTypeIsNumeric = $this->isNumericType($columnDiff->column->getType()); + + // default should not be changed when switching between numeric types and the default comes from a sequence + return $columnDiff->hasChanged('type') + && ! ($oldTypeIsNumeric && $newTypeIsNumeric && $columnDiff->column->getAutoincrement()); + } + + private function isNumericType(Type $type): bool + { + return $type instanceof IntegerType || $type instanceof BigIntType; + } + + private function getOldColumnComment(ColumnDiff $columnDiff): ?string + { + return $columnDiff->fromColumn ? $this->getColumnComment($columnDiff->fromColumn) : null; + } + + public function getListTableMetadataSQL(string $table, ?string $schema = null): string + { + if ($schema !== null) { + $table = $schema . '.' . $table; + } + + return sprintf( + <<<'SQL' +SELECT obj_description(%s::regclass) AS table_comment; +SQL + , + $this->quoteStringLiteral($table) + ); + } +} diff --git a/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php new file mode 100644 index 0000000..16422f7 --- /dev/null +++ b/freescout-dist/overrides/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php @@ -0,0 +1,538 @@ +_conn->executeQuery( + "SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' AND nspname != 'information_schema'" + ); + + return $statement->fetchAll(FetchMode::COLUMN); + } + + /** + * Returns an array of schema search paths. + * + * This is a PostgreSQL only function. + * + * @return string[] + */ + public function getSchemaSearchPaths() + { + $params = $this->_conn->getParams(); + + $searchPaths = $this->_conn->fetchColumn('SHOW search_path'); + assert($searchPaths !== false); + + $schema = explode(',', $searchPaths); + + if (isset($params['user'])) { + $schema = str_replace('"$user"', $params['user'], $schema); + } + + return array_map('trim', $schema); + } + + /** + * Gets names of all existing schemas in the current users search path. + * + * This is a PostgreSQL only function. + * + * @return string[] + */ + public function getExistingSchemaSearchPaths() + { + if ($this->existingSchemaPaths === null) { + $this->determineExistingSchemaSearchPaths(); + } + + return $this->existingSchemaPaths; + } + + /** + * Sets or resets the order of the existing schemas in the current search path of the user. + * + * This is a PostgreSQL only function. + * + * @return void + */ + public function determineExistingSchemaSearchPaths() + { + $names = $this->getSchemaNames(); + $paths = $this->getSchemaSearchPaths(); + + $this->existingSchemaPaths = array_filter($paths, static function ($v) use ($names) { + return in_array($v, $names); + }); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($database) + { + try { + parent::dropDatabase($database); + } catch (DriverException $exception) { + // If we have a SQLSTATE 55006, the drop database operation failed + // because of active connections on the database. + // To force dropping the database, we first have to close all active connections + // on that database and issue the drop database operation again. + if ($exception->getSQLState() !== '55006') { + throw $exception; + } + + assert($this->_platform instanceof PostgreSqlPlatform); + + $this->_execSql( + [ + $this->_platform->getDisallowDatabaseConnectionsSQL($database), + $this->_platform->getCloseActiveDatabaseConnectionsSQL($database), + ] + ); + + parent::dropDatabase($database); + } + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + $onUpdate = null; + $onDelete = null; + $localColumns = []; + $foreignColumns = []; + $foreignTable = null; + + if (preg_match('(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) { + $onUpdate = $match[1]; + } + + if (preg_match('(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) { + $onDelete = $match[1]; + } + + $result = preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values); + assert($result === 1); + + // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get + // the idea to trim them here. + $localColumns = array_map('trim', explode(',', $values[1])); + $foreignColumns = array_map('trim', explode(',', $values[3])); + $foreignTable = $values[2]; + + return new ForeignKeyConstraint( + $localColumns, + $foreignTable, + $foreignColumns, + $tableForeignKey['conname'], + ['onUpdate' => $onUpdate, 'onDelete' => $onDelete] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTriggerDefinition($trigger) + { + return $trigger['trigger_name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View($view['schemaname'] . '.' . $view['viewname'], $view['definition']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableUserDefinition($user) + { + return [ + 'user' => $user['usename'], + 'password' => $user['passwd'], + ]; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + $schemas = $this->getExistingSchemaSearchPaths(); + $firstSchema = array_shift($schemas); + + if ($table['schema_name'] === $firstSchema) { + return $table['table_name']; + } + + return $table['schema_name'] . '.' . $table['table_name']; + } + + /** + * {@inheritdoc} + * + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) + { + $buffer = []; + foreach ($tableIndexes as $row) { + $colNumbers = array_map('intval', explode(' ', $row['indkey'])); + $columnNameSql = sprintf( + 'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC', + $row['indrelid'], + implode(' ,', $colNumbers) + ); + + $indexColumns = $this->_conn->fetchAllAssociative($columnNameSql); + + // required for getting the order of the columns right. + foreach ($colNumbers as $colNum) { + foreach ($indexColumns as $colRow) { + if ($colNum !== $colRow['attnum']) { + continue; + } + + $buffer[] = [ + 'key_name' => $row['relname'], + 'column_name' => trim($colRow['attname']), + 'non_unique' => ! $row['indisunique'], + 'primary' => $row['indisprimary'], + 'where' => $row['where'], + ]; + } + } + } + + return parent::_getPortableTableIndexesList($buffer, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['datname']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequencesList($sequences) + { + $sequenceDefinitions = []; + + foreach ($sequences as $sequence) { + if ($sequence['schemaname'] !== 'public') { + $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname']; + } else { + $sequenceName = $sequence['relname']; + } + + $sequenceDefinitions[$sequenceName] = $sequence; + } + + $list = []; + + foreach ($this->filterAssetNames(array_keys($sequenceDefinitions)) as $sequenceName) { + $list[] = $this->_getPortableSequenceDefinition($sequenceDefinitions[$sequenceName]); + } + + return $list; + } + + /** + * {@inheritdoc} + */ + protected function getPortableNamespaceDefinition(array $namespace) + { + return $namespace['nspname']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + if ($sequence['schemaname'] !== 'public') { + $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname']; + } else { + $sequenceName = $sequence['relname']; + } + + if (! isset($sequence['increment_by'], $sequence['min_value'])) { + /** @var string[] $data */ + $data = $this->_conn->fetchAssoc( + 'SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName) + ); + + $sequence += $data; + } + + return new Sequence($sequenceName, (int) $sequence['increment_by'], (int) $sequence['min_value']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); + + if (strtolower($tableColumn['type']) === 'varchar' || strtolower($tableColumn['type']) === 'bpchar') { + // get length from varchar definition + $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']); + $tableColumn['length'] = $length; + } + + $matches = []; + + $autoincrement = false; + if ($tableColumn['default'] !== null && preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) { + $tableColumn['sequence'] = $matches[1]; + $tableColumn['default'] = null; + $autoincrement = true; + } + + if ($tableColumn['default'] !== null) { + if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches)) { + $tableColumn['default'] = $matches[1]; + } elseif (preg_match('/^NULL::/', $tableColumn['default'])) { + $tableColumn['default'] = null; + } + } + + $length = $tableColumn['length'] ?? null; + if ($length === '-1' && isset($tableColumn['atttypmod'])) { + $length = $tableColumn['atttypmod'] - 4; + } + + if ((int) $length <= 0) { + $length = null; + } + + $fixed = null; + + if (! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $precision = null; + $scale = null; + $jsonb = null; + + $dbType = strtolower($tableColumn['type']); + if ($tableColumn['domain_type'] !== null + && strlen($tableColumn['domain_type']) + && ! $this->_platform->hasDoctrineTypeMappingFor($tableColumn['type']) + ) { + $dbType = strtolower($tableColumn['domain_type']); + $tableColumn['complete_type'] = $tableColumn['domain_complete_type']; + } + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + + switch ($dbType) { + case 'smallint': + case 'int2': + $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); + $length = null; + break; + + case 'int': + case 'int4': + case 'integer': + $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); + $length = null; + break; + + case 'bigint': + case 'int8': + $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); + $length = null; + break; + + case 'bool': + case 'boolean': + if ($tableColumn['default'] === 'true') { + $tableColumn['default'] = true; + } + + if ($tableColumn['default'] === 'false') { + $tableColumn['default'] = false; + } + + $length = null; + break; + + case 'text': + case '_varchar': + case 'varchar': + $tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']); + $fixed = false; + break; + case 'interval': + $fixed = false; + break; + + case 'char': + case 'bpchar': + $fixed = true; + break; + + case 'float': + case 'float4': + case 'float8': + case 'double': + case 'double precision': + case 'real': + case 'decimal': + case 'money': + case 'numeric': + $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']); + + if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['complete_type'], $match)) { + $precision = $match[1]; + $scale = $match[2]; + $length = null; + } + + break; + + case 'year': + $length = null; + break; + + // PostgreSQL 9.4+ only + case 'jsonb': + $jsonb = true; + break; + } + + if ($tableColumn['default'] !== null && $tableColumn['default'] && preg_match("('([^']+)'::)", $tableColumn['default'], $match)) { + $tableColumn['default'] = $match[1]; + } + + $options = [ + 'length' => $length, + 'notnull' => (bool) $tableColumn['isnotnull'], + 'default' => $tableColumn['default'], + 'precision' => $precision, + 'scale' => $scale, + 'fixed' => $fixed, + 'unsigned' => false, + 'autoincrement' => $autoincrement, + 'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== '' + ? $tableColumn['comment'] + : null, + ]; + + $column = new Column($tableColumn['field'], Type::getType($type), $options); + + if (isset($tableColumn['collation']) && ! empty($tableColumn['collation'])) { + $column->setPlatformOption('collation', $tableColumn['collation']); + } + + if (in_array($column->getType()->getName(), [Types::JSON_ARRAY, Types::JSON], true)) { + $column->setPlatformOption('jsonb', $jsonb); + } + + return $column; + } + + /** + * PostgreSQL 9.4 puts parentheses around negative numeric default values that need to be stripped eventually. + * + * @param mixed $defaultValue + * + * @return mixed + */ + private function fixVersion94NegativeNumericDefaultValue($defaultValue) + { + if ($defaultValue !== null && strpos($defaultValue, '(') === 0) { + return trim($defaultValue, '()'); + } + + return $defaultValue; + } + + /** + * Parses a default value expression as given by PostgreSQL + */ + private function parseDefaultExpression(?string $default): ?string + { + if ($default === null) { + return $default; + } + + return str_replace("''", "'", $default); + } + + /** + * {@inheritdoc} + */ + public function listTableDetails($name): Table + { + $table = parent::listTableDetails($name); + + $platform = $this->_platform; + assert($platform instanceof PostgreSqlPlatform); + $sql = $platform->getListTableMetadataSQL($name); + + $tableOptions = $this->_conn->fetchAssoc($sql); + + if ($tableOptions !== false) { + $table->addOption('comment', $tableOptions['table_comment']); + } + + return $table; + } +} diff --git a/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php b/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php new file mode 100644 index 0000000..922dbf8 --- /dev/null +++ b/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php @@ -0,0 +1,146 @@ +ipv4 = new HTMLPurifier_AttrDef_URI_IPv4(); + $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6(); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $length = strlen($string); + // empty hostname is OK; it's usually semantically equivalent: + // the default host as defined by a URI scheme is used: + // + // If the URI scheme defines a default for host, then that + // default applies when the host subcomponent is undefined + // or when the registered name is empty (zero length). + if ($string === '') { + return ''; + } + if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') { + //IPv6 + $ip = substr($string, 1, $length - 2); + $valid = $this->ipv6->validate($ip, $config, $context); + if ($valid === false) { + return false; + } + return '[' . $valid . ']'; + } + + // need to do checks on unusual encodings too + $ipv4 = $this->ipv4->validate($string, $config, $context); + if ($ipv4 !== false) { + return $ipv4; + } + + // A regular domain name. + + // This doesn't match I18N domain names, but we don't have proper IRI support, + // so force users to insert Punycode. + + // There is not a good sense in which underscores should be + // allowed, since it's technically not! (And if you go as + // far to allow everything as specified by the DNS spec... + // well, that's literally everything, modulo some space limits + // for the components and the overall name (which, by the way, + // we are NOT checking!). So we (arbitrarily) decide this: + // let's allow underscores wherever we would have allowed + // hyphens, if they are enabled. This is a pretty good match + // for browser behavior, for example, a large number of browsers + // cannot handle foo_.example.com, but foo_bar.example.com is + // fairly well supported. + $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : ''; + + // Based off of RFC 1738, but amended so that + // as per RFC 3696, the top label need only not be all numeric. + // The productions describing this are: + $a = '[a-z]'; // alpha + $an = '[a-z0-9]'; // alphanum + $and = "[a-z0-9-$underscore]"; // alphanum | "-" + // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + $domainlabel = "$an(?:$and*$an)?"; + // AMENDED as per RFC 3696 + // toplabel = alphanum | alphanum *( alphanum | "-" ) alphanum + // side condition: not all numeric + $toplabel = "$an(?:$and*$an)?"; + // hostname = *( domainlabel "." ) toplabel [ "." ] + if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) { + if (!ctype_digit($matches[1])) { + return $string; + } + } + + // PHP 5.3 and later support this functionality natively + if (function_exists('idn_to_ascii')) { + try { + if (defined('IDNA_NONTRANSITIONAL_TO_ASCII') && defined('INTL_IDNA_VARIANT_UTS46')) { + $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + } else { + $string = idn_to_ascii($string); + } + } catch (\Exception $e) { + + } + + // If we have Net_IDNA2 support, we can support IRIs by + // punycoding them. (This is the most portable thing to do, + // since otherwise we have to assume browsers support + } elseif ($config->get('Core.EnableIDNA')) { + $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true)); + // we need to encode each period separately + $parts = explode('.', $string); + try { + $new_parts = array(); + foreach ($parts as $part) { + $encodable = false; + for ($i = 0, $c = strlen($part); $i < $c; $i++) { + if (ord($part[$i]) > 0x7a) { + $encodable = true; + break; + } + } + if (!$encodable) { + $new_parts[] = $part; + } else { + $new_parts[] = $idna->encode($part); + } + } + $string = implode('.', $new_parts); + } catch (Exception $e) { + // XXX error reporting + } + } + // Try again + if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) { + return $string; + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php b/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php new file mode 100644 index 0000000..6786e5e --- /dev/null +++ b/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php @@ -0,0 +1,42 @@ +idDef = new HTMLPurifier_AttrDef_HTML_ID(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['name'])) { + return $attr; + } + $name = $attr['name']; + if (isset($attr['id']) && $attr['id'] === $name) { + return $attr; + } + $result = $this->idDef->validate($name, $config, $context); + if ($result === false) { + unset($attr['name']); + } else { + $attr['name'] = $result; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php b/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php new file mode 100644 index 0000000..25265da --- /dev/null +++ b/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php @@ -0,0 +1,205 @@ +getHTMLDefinition(); + $e =& $context->get('ErrorCollector', true); + + // initialize IDAccumulator if necessary + $ok =& $context->get('IDAccumulator', true); + if (!$ok) { + $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); + $context->register('IDAccumulator', $id_accumulator); + } + + // initialize CurrentToken if necessary + $current_token =& $context->get('CurrentToken', true); + if (!$current_token) { + $context->register('CurrentToken', $token); + } + + if (!$token instanceof HTMLPurifier_Token_Start && + !$token instanceof HTMLPurifier_Token_Empty + ) { + return; + } + + // create alias to global definition array, see also $defs + // DEFINITION CALL + $d_defs = $definition->info_global_attr; + + // don't update token until the very end, to ensure an atomic update + $attr = $token->attr; + + // do global transformations (pre) + // nothing currently utilizes this + foreach ($definition->info_attr_transform_pre as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + // do local transformations only applicable to this element (pre) + // ex.

to

+ foreach ($definition->info[$token->name]->attr_transform_pre as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + // create alias to this element's attribute definition array, see + // also $d_defs (global attribute definition array) + // DEFINITION CALL + $defs = $definition->info[$token->name]->attr; + + $attr_key = false; + $context->register('CurrentAttr', $attr_key); + + // Used to make sure width/height are changed proportionally + // when HTML.MaxImgLength is used. + $proportions_change = []; + + // iterate through all the attribute keypairs + // Watch out for name collisions: $key has previously been used + foreach ($attr as $attr_key => $value) { + + // call the definition + if (isset($defs[$attr_key])) { + // there is a local definition defined + if ($defs[$attr_key] === false) { + // We've explicitly been told not to allow this element. + // This is usually when there's a global definition + // that must be overridden. + // Theoretically speaking, we could have a + // AttrDef_DenyAll, but this is faster! + $result = false; + } else { + // validate according to the element's definition + $result = $defs[$attr_key]->validate( + $value, + $config, + $context + ); + if ($token->name == 'img' + && $result + && (int)$value + && (int)$result + && ($attr_key == 'width' || $attr_key == 'height') + ) { + $proportions_change[$attr_key]['original'] = (int)$value; + $proportions_change[$attr_key]['new'] = (int)$result; + } + } + } elseif (isset($d_defs[$attr_key])) { + // there is a global definition defined, validate according + // to the global definition + $result = $d_defs[$attr_key]->validate( + $value, + $config, + $context + ); + } else { + // system never heard of the attribute? DELETE! + $result = false; + } + + // put the results into effect + if ($result === false || $result === null) { + // this is a generic error message that should replaced + // with more specific ones when possible + if ($e) { + $e->send(E_ERROR, 'AttrValidator: Attribute removed'); + } + + // remove the attribute + unset($attr[$attr_key]); + } elseif (is_string($result)) { + // generally, if a substitution is happening, there + // was some sort of implicit correction going on. We'll + // delegate it to the attribute classes to say exactly what. + + // simple substitution + $attr[$attr_key] = $result; + } else { + // nothing happens + } + + // we'd also want slightly more complicated substitution + // involving an array as the return value, + // although we're not sure how colliding attributes would + // resolve (certain ones would be completely overriden, + // others would prepend themselves). + } + + if (!empty($proportions_change['width']) && !empty($proportions_change['height'])) { + if ($proportions_change['width']['new'] < $proportions_change['width']['original'] + && (int)$proportions_change['width']['original'] + ) { + // Adjust height. + $attr['height'] = floor($proportions_change['height']['original'] * $proportions_change['width']['new'] / $proportions_change['width']['original']); + } elseif ($proportions_change['height']['new'] < $proportions_change['height']['original'] + && (int)$proportions_change['height']['original'] + ) { + // Adjust width. + $attr['width'] = floor($proportions_change['height']['original'] * $proportions_change['height']['new'] / $proportions_change['height']['original']); + } + } + + $context->destroy('CurrentAttr'); + + // post transforms + + // global (error reporting untested) + foreach ($definition->info_attr_transform_post as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + // local (error reporting untested) + foreach ($definition->info[$token->name]->attr_transform_post as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + $token->attr = $attr; + + // destroy CurrentToken if we made it ourselves + if (!$current_token) { + $context->destroy('CurrentToken'); + } + + } + + +} + +// vim: et sw=4 sts=4 diff --git a/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php b/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php new file mode 100644 index 0000000..3d584e7 --- /dev/null +++ b/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php @@ -0,0 +1,94 @@ + true, 'ul' => true, 'ol' => true); + + public $whitespace; + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + // Flag for subclasses + $this->whitespace = false; + + // if there are no tokens, delete parent node + if (empty($children)) { + return false; + } + + // if li is not allowed, delete parent node + if (!isset($config->getHTMLDefinition()->info['li'])) { + trigger_error("Cannot allow ul/ol without allowing li", E_USER_WARNING); + return false; + } + + // the new set of children + $result = array(); + + // a little sanity check to make sure it's not ALL whitespace + $all_whitespace = true; + + $current_li = null; + + foreach ($children as $node) { + if (!empty($node->is_whitespace)) { + $result[] = $node; + continue; + } + $all_whitespace = false; // phew, we're not talking about whitespace + + if ($node->name === 'li') { + // good + $current_li = $node; + $result[] = $node; + } else { + // we want to tuck this into the previous li + // Invariant: we expect the node to be ol/ul + // ToDo: Make this more robust in the case of not ol/ul + // by distinguishing between existing li and li created + // to handle non-list elements; non-list elements should + // not be appended to an existing li; only li created + // for non-list. This distinction is not currently made. + if ($current_li === null) { + $current_li = new HTMLPurifier_Node_Element('li'); + $result[] = $current_li; + } + $current_li->children[] = $node; + $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo + } + } + if (empty($result)) { + return false; + } + if ($all_whitespace) { + return false; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php b/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php new file mode 100644 index 0000000..4049cae --- /dev/null +++ b/freescout-dist/overrides/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php @@ -0,0 +1,383 @@ +get('Core.LexerImpl'); + } + + $needs_tracking = + $config->get('Core.MaintainLineNumbers') || + $config->get('Core.CollectErrors'); + + $inst = null; + if (is_object($lexer)) { + $inst = $lexer; + } else { + if (is_null($lexer)) { + do { + // auto-detection algorithm + if ($needs_tracking) { + $lexer = 'DirectLex'; + break; + } + + if (class_exists('DOMDocument', false) && + method_exists('DOMDocument', 'loadHTML') && + !extension_loaded('domxml') + ) { + // check for DOM support, because while it's part of the + // core, it can be disabled compile time. Also, the PECL + // domxml extension overrides the default DOM, and is evil + // and nasty and we shan't bother to support it + $lexer = 'DOMLex'; + } else { + $lexer = 'DirectLex'; + } + } while (0); + } // do..while so we can break + + // instantiate recognized string names + switch ($lexer) { + case 'DOMLex': + $inst = new HTMLPurifier_Lexer_DOMLex(); + break; + case 'DirectLex': + $inst = new HTMLPurifier_Lexer_DirectLex(); + break; + case 'PH5P': + $inst = new HTMLPurifier_Lexer_PH5P(); + break; + default: + throw new HTMLPurifier_Exception( + "Cannot instantiate unrecognized Lexer type " . + htmlspecialchars($lexer) + ); + } + } + + if (!$inst) { + throw new HTMLPurifier_Exception('No lexer was instantiated'); + } + + // once PHP DOM implements native line numbers, or we + // hack out something using XSLT, remove this stipulation + if ($needs_tracking && !$inst->tracksLineNumbers) { + throw new HTMLPurifier_Exception( + 'Cannot use lexer that does not support line numbers with ' . + 'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)' + ); + } + + return $inst; + + } + + // -- CONVENIENCE MEMBERS --------------------------------------------- + + public function __construct() + { + $this->_entity_parser = new HTMLPurifier_EntityParser(); + } + + /** + * Most common entity to raw value conversion table for special entities. + * @type array + */ + protected $_special_entity2str = + array( + '"' => '"', + '&' => '&', + '<' => '<', + '>' => '>', + ''' => "'", + ''' => "'", + ''' => "'" + ); + + public function parseText($string, $config) { + return $this->parseData($string, false, $config); + } + + public function parseAttr($string, $config) { + return $this->parseData($string, true, $config); + } + + /** + * Parses special entities into the proper characters. + * + * This string will translate escaped versions of the special characters + * into the correct ones. + * + * @param string $string String character data to be parsed. + * @return string Parsed character data. + */ + public function parseData($string, $is_attr, $config) + { + // following functions require at least one character + if ($string === '') { + return ''; + } + + // subtracts amps that cannot possibly be escaped + $num_amp = substr_count($string, '&') - substr_count($string, '& ') - + ($string[strlen($string) - 1] === '&' ? 1 : 0); + + if (!$num_amp) { + return $string; + } // abort if no entities + $num_esc_amp = substr_count($string, '&'); + $string = strtr($string, $this->_special_entity2str); + + // code duplication for sake of optimization, see above + $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') - + ($string[strlen($string) - 1] === '&' ? 1 : 0); + + if ($num_amp_2 <= $num_esc_amp) { + return $string; + } + + // hmm... now we have some uncommon entities. Use the callback. + if ($config->get('Core.LegacyEntityDecoder')) { + $string = $this->_entity_parser->substituteSpecialEntities($string); + } else { + if ($is_attr) { + $string = $this->_entity_parser->substituteAttrEntities($string); + } else { + $string = $this->_entity_parser->substituteTextEntities($string); + } + } + return $string; + } + + /** + * Lexes an HTML string into tokens. + * @param $string String HTML. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] array representation of HTML. + */ + public function tokenizeHTML($string, $config, $context) + { + trigger_error('Call to abstract class', E_USER_ERROR); + } + + /** + * Translates CDATA sections into regular sections (through escaping). + * @param string $string HTML string to process. + * @return string HTML with CDATA sections escaped. + */ + protected static function escapeCDATA($string) + { + return preg_replace_callback( + '//s', + array('HTMLPurifier_Lexer', 'CDATACallback'), + $string + ); + } + + /** + * Special CDATA case that is especially convoluted for #i', '', $html); + } + + return $html; + } + + /** + * Takes a string of HTML (fragment or document) and returns the content + * @todo Consider making protected + */ + public function extractBody($html) + { + $matches = array(); + $result = preg_match('|(.*?)]*>(.*)|is', $html ?? '', $matches); + if ($result) { + // Make sure it's not in a comment + $comment_start = strrpos($matches[1], ''); + if ($comment_start === false || + ($comment_end !== false && $comment_end > $comment_start)) { + return $matches[2]; + } + } + return $html; + } +} + +// vim: et sw=4 sts=4 diff --git a/freescout-dist/overrides/fzaninotto/faker/src/Faker/Provider/Base.php b/freescout-dist/overrides/fzaninotto/faker/src/Faker/Provider/Base.php new file mode 100644 index 0000000..ace6548 --- /dev/null +++ b/freescout-dist/overrides/fzaninotto/faker/src/Faker/Provider/Base.php @@ -0,0 +1,616 @@ +generator = $generator; + } + + /** + * Returns a random number between 0 and 9 + * + * @return integer + */ + public static function randomDigit() + { + return mt_rand(0, 9); + } + + /** + * Returns a random number between 1 and 9 + * + * @return integer + */ + public static function randomDigitNotNull() + { + return mt_rand(1, 9); + } + + /** + * Generates a random digit, which cannot be $except + * + * @param int $except + * @return int + */ + public static function randomDigitNot($except) + { + $result = self::numberBetween(0, 8); + if ($result >= $except) { + $result++; + } + return $result; + } + + /** + * Returns a random integer with 0 to $nbDigits digits. + * + * The maximum value returned is mt_getrandmax() + * + * @param integer $nbDigits Defaults to a random number between 1 and 9 + * @param boolean $strict Whether the returned number should have exactly $nbDigits + * @example 79907610 + * + * @return integer + */ + public static function randomNumber($nbDigits = null, $strict = false) + { + if (!is_bool($strict)) { + throw new \InvalidArgumentException('randomNumber() generates numbers of fixed width. To generate numbers between two boundaries, use numberBetween() instead.'); + } + if (null === $nbDigits) { + $nbDigits = static::randomDigitNotNull(); + } + $max = pow(10, $nbDigits) - 1; + if ($max > mt_getrandmax()) { + throw new \InvalidArgumentException('randomNumber() can only generate numbers up to mt_getrandmax()'); + } + if ($strict) { + return mt_rand(pow(10, $nbDigits - 1), $max); + } + + return mt_rand(0, $max); + } + + /** + * Return a random float number + * + * @param int $nbMaxDecimals + * @param int|float $min + * @param int|float $max + * @example 48.8932 + * + * @return float + */ + public static function randomFloat($nbMaxDecimals = null, $min = 0, $max = null) + { + if (null === $nbMaxDecimals) { + $nbMaxDecimals = static::randomDigit(); + } + + if (null === $max) { + $max = static::randomNumber(); + if ($min > $max) { + $max = $min; + } + } + + if ($min > $max) { + $tmp = $min; + $min = $max; + $max = $tmp; + } + + return round($min + mt_rand() / mt_getrandmax() * ($max - $min), $nbMaxDecimals); + } + + /** + * Returns a random number between $int1 and $int2 (any order) + * + * @param integer $int1 default to 0 + * @param integer $int2 defaults to 32 bit max integer, ie 2147483647 + * @example 79907610 + * + * @return integer + */ + public static function numberBetween($int1 = 0, $int2 = 2147483647) + { + $min = $int1 < $int2 ? $int1 : $int2; + $max = $int1 < $int2 ? $int2 : $int1; + return mt_rand($min, $max); + } + + /** + * Returns the passed value + * + * @param mixed $value + * + * @return mixed + */ + public static function passthrough($value) + { + return $value; + } + + /** + * Returns a random letter from a to z + * + * @return string + */ + public static function randomLetter() + { + return chr(mt_rand(97, 122)); + } + + /** + * Returns a random ASCII character (excluding accents and special chars) + */ + public static function randomAscii() + { + return chr(mt_rand(33, 126)); + } + + /** + * Returns randomly ordered subsequence of $count elements from a provided array + * + * @param array $array Array to take elements from. Defaults to a-c + * @param integer $count Number of elements to take. + * @param boolean $allowDuplicates Allow elements to be picked several times. Defaults to false + * @throws \LengthException When requesting more elements than provided + * + * @return array New array with $count elements from $array + */ + public static function randomElements($array = array('a', 'b', 'c'), $count = 1, $allowDuplicates = false) + { + $traversables = array(); + + if ($array instanceof \Traversable) { + foreach ($array as $element) { + $traversables[] = $element; + } + } + + $arr = count($traversables) ? $traversables : $array; + + $allKeys = array_keys($arr); + $numKeys = count($allKeys); + + if (!$allowDuplicates && $numKeys < $count) { + throw new \LengthException(sprintf('Cannot get %d elements, only %d in array', $count, $numKeys)); + } + + $highKey = $numKeys - 1; + $keys = $elements = array(); + $numElements = 0; + + while ($numElements < $count) { + $num = mt_rand(0, $highKey); + + if (!$allowDuplicates) { + if (isset($keys[$num])) { + continue; + } + $keys[$num] = true; + } + + $elements[] = $arr[$allKeys[$num]]; + $numElements++; + } + + return $elements; + } + + /** + * Returns a random element from a passed array + * + * @param array $array + * @return mixed + */ + public static function randomElement($array = array('a', 'b', 'c')) + { + if (!$array || ($array instanceof \Traversable && !count($array))) { + return null; + } + $elements = static::randomElements($array, 1); + + return $elements[0]; + } + + /** + * Returns a random key from a passed associative array + * + * @param array $array + * @return int|string|null + */ + public static function randomKey($array = array()) + { + if (!$array) { + return null; + } + $keys = array_keys($array); + $key = $keys[mt_rand(0, count($keys) - 1)]; + + return $key; + } + + /** + * Returns a shuffled version of the argument. + * + * This function accepts either an array, or a string. + * + * @example $faker->shuffle([1, 2, 3]); // [2, 1, 3] + * @example $faker->shuffle('hello, world'); // 'rlo,h eold!lw' + * + * @see shuffleArray() + * @see shuffleString() + * + * @param array|string $arg The set to shuffle + * @return array|string The shuffled set + */ + public static function shuffle($arg = '') + { + if (is_array($arg)) { + return static::shuffleArray($arg); + } + if (is_string($arg)) { + return static::shuffleString($arg); + } + throw new \InvalidArgumentException('shuffle() only supports strings or arrays'); + } + + /** + * Returns a shuffled version of the array. + * + * This function does not mutate the original array. It uses the + * Fisher–Yates algorithm, which is unbiased, together with a Mersenne + * twister random generator. This function is therefore more random than + * PHP's shuffle() function, and it is seedable. + * + * @link http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + * + * @example $faker->shuffleArray([1, 2, 3]); // [2, 1, 3] + * + * @param array $array The set to shuffle + * @return array The shuffled set + */ + public static function shuffleArray($array = array()) + { + $shuffledArray = array(); + $i = 0; + reset($array); + foreach ($array as $key => $value) { + if ($i == 0) { + $j = 0; + } else { + $j = mt_rand(0, $i); + } + if ($j == $i) { + $shuffledArray[]= $value; + } else { + $shuffledArray[]= $shuffledArray[$j]; + $shuffledArray[$j] = $value; + } + $i++; + } + return $shuffledArray; + } + + /** + * Returns a shuffled version of the string. + * + * This function does not mutate the original string. It uses the + * Fisher–Yates algorithm, which is unbiased, together with a Mersenne + * twister random generator. This function is therefore more random than + * PHP's shuffle() function, and it is seedable. Additionally, it is + * UTF8 safe if the mb extension is available. + * + * @link http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + * + * @example $faker->shuffleString('hello, world'); // 'rlo,h eold!lw' + * + * @param string $string The set to shuffle + * @param string $encoding The string encoding (defaults to UTF-8) + * @return string The shuffled set + */ + public static function shuffleString($string = '', $encoding = 'UTF-8') + { + if (function_exists('mb_strlen')) { + // UTF8-safe str_split() + $array = array(); + $strlen = mb_strlen($string, $encoding); + for ($i = 0; $i < $strlen; $i++) { + $array []= mb_substr($string, $i, 1, $encoding); + } + } else { + $array = str_split($string, 1); + } + return implode('', static::shuffleArray($array)); + } + + private static function replaceWildcard($string, $wildcard = '#', $callback = 'static::randomDigit') + { + if (($pos = strpos($string, $wildcard)) === false) { + return $string; + } + for ($i = $pos, $last = strrpos($string, $wildcard, $pos) + 1; $i < $last; $i++) { + if ($string[$i] === $wildcard) { + if (strspn($callback, 'static::')) { + $string[$i] = call_user_func([static::class, str_replace('static::', '', $callback)]); + } else { + $string[$i] = call_user_func($callback); + } + } + } + return $string; + } + + /** + * Replaces all hash sign ('#') occurrences with a random number + * Replaces all percentage sign ('%') occurrences with a not null number + * + * @param string $string String that needs to bet parsed + * @return string + */ + public static function numerify($string = '###') + { + // instead of using randomDigit() several times, which is slow, + // count the number of hashes and generate once a large number + $toReplace = array(); + if (($pos = strpos($string, '#')) !== false) { + for ($i = $pos, $last = strrpos($string, '#', $pos) + 1; $i < $last; $i++) { + if ($string[$i] === '#') { + $toReplace[] = $i; + } + } + } + if ($nbReplacements = count($toReplace)) { + $maxAtOnce = strlen((string) mt_getrandmax()) - 1; + $numbers = ''; + $i = 0; + while ($i < $nbReplacements) { + $size = min($nbReplacements - $i, $maxAtOnce); + $numbers .= str_pad(static::randomNumber($size), $size, '0', STR_PAD_LEFT); + $i += $size; + } + for ($i = 0; $i < $nbReplacements; $i++) { + $string[$toReplace[$i]] = $numbers[$i]; + } + } + $string = self::replaceWildcard($string, '%', 'static::randomDigitNotNull'); + + return $string; + } + + /** + * Replaces all question mark ('?') occurrences with a random letter + * + * @param string $string String that needs to bet parsed + * @return string + */ + public static function lexify($string = '????') + { + return self::replaceWildcard($string, '?', 'static::randomLetter'); + } + + /** + * Replaces hash signs ('#') and question marks ('?') with random numbers and letters + * An asterisk ('*') is replaced with either a random number or a random letter + * + * @param string $string String that needs to bet parsed + * @return string + */ + public static function bothify($string = '## ??') + { + $string = self::replaceWildcard($string, '*', function () { + return mt_rand(0, 1) ? '#' : '?'; + }); + return static::lexify(static::numerify($string)); + } + + /** + * Replaces * signs with random numbers and letters and special characters + * + * @example $faker->asciify(''********'); // "s5'G!uC3" + * + * @param string $string String that needs to bet parsed + * @return string + */ + public static function asciify($string = '****') + { + return preg_replace_callback('/\*/u', 'static::randomAscii', $string); + } + + /** + * Transforms a basic regular expression into a random string satisfying the expression. + * + * @example $faker->regexify('[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'); // sm0@y8k96a.ej + * + * Regex delimiters '/.../' and begin/end markers '^...$' are ignored. + * + * Only supports a small subset of the regex syntax. For instance, + * unicode, negated classes, unbounded ranges, subpatterns, back references, + * assertions, recursive patterns, and comments are not supported. Escaping + * support is extremely fragile. + * + * This method is also VERY slow. Use it only when no other formatter + * can generate the fake data you want. For instance, prefer calling + * `$faker->email` rather than `regexify` with the previous regular + * expression. + * + * Also note than `bothify` can probably do most of what this method does, + * but much faster. For instance, for a dummy email generation, try + * `$faker->bothify('?????????@???.???')`. + * + * @see https://github.com/icomefromthenet/ReverseRegex for a more robust implementation + * + * @param string $regex A regular expression (delimiters are optional) + * @return string + */ + public static function regexify($regex = '') + { + // ditch the anchors + $regex = preg_replace('/^\/?\^?/', '', $regex); + $regex = preg_replace('/\$?\/?$/', '', $regex); + // All {2} become {2,2} + $regex = preg_replace('/\{(\d+)\}/', '{\1,\1}', $regex); + // Single-letter quantifiers (?, *, +) become bracket quantifiers ({0,1}, {0,rand}, {1, rand}) + $regex = preg_replace('/(? 0 && $weight < 1 && mt_rand() / mt_getrandmax() <= $weight) { + return $this->generator; + } + + // new system with percentage + if (is_int($weight) && mt_rand(1, 100) <= $weight) { + return $this->generator; + } + + return new DefaultGenerator($default); + } + + /** + * Chainable method for making any formatter unique. + * + * + * // will never return twice the same value + * $faker->unique()->randomElement(array(1, 2, 3)); + * + * + * @param boolean $reset If set to true, resets the list of existing values + * @param integer $maxRetries Maximum number of retries to find a unique value, + * After which an OverflowException is thrown. + * @throws \OverflowException When no unique value can be found by iterating $maxRetries times + * + * @return UniqueGenerator A proxy class returning only non-existing values + */ + public function unique($reset = false, $maxRetries = 10000) + { + if ($reset || !$this->unique) { + $this->unique = new UniqueGenerator($this->generator, $maxRetries); + } + + return $this->unique; + } + + /** + * Chainable method for forcing any formatter to return only valid values. + * + * The value validity is determined by a function passed as first argument. + * + * + * $values = array(); + * $evenValidator = function ($digit) { + * return $digit % 2 === 0; + * }; + * for ($i=0; $i < 10; $i++) { + * $values []= $faker->valid($evenValidator)->randomDigit; + * } + * print_r($values); // [0, 4, 8, 4, 2, 6, 0, 8, 8, 6] + * + * + * @param Closure $validator A function returning true for valid values + * @param integer $maxRetries Maximum number of retries to find a unique value, + * After which an OverflowException is thrown. + * @throws \OverflowException When no valid value can be found by iterating $maxRetries times + * + * @return ValidGenerator A proxy class returning only valid values + */ + public function valid($validator = null, $maxRetries = 10000) + { + return new ValidGenerator($this->generator, $validator, $maxRetries); + } +} diff --git a/freescout-dist/overrides/guzzlehttp/guzzle/src/Client.php b/freescout-dist/overrides/guzzlehttp/guzzle/src/Client.php new file mode 100644 index 0000000..bbba32c --- /dev/null +++ b/freescout-dist/overrides/guzzlehttp/guzzle/src/Client.php @@ -0,0 +1,501 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. + * If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + if (!isset($config['handler'])) { + $config['handler'] = HandlerStack::create(); + } elseif (!is_callable($config['handler'])) { + throw new \InvalidArgumentException('handler must be a callable'); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\uri_for($config['base_uri']); + } + + $this->configureDefaults($config); + } + + /** + * @param string $method + * @param array $args + * + * @return Promise\PromiseInterface + */ + public function __call($method, $args) + { + if (count($args) < 1) { + throw new \InvalidArgumentException('Magic request methods require a URI and optional options array'); + } + + $uri = $args[0]; + $opts = isset($args[1]) ? $args[1] : []; + + return substr($method, -5) === 'Async' + ? $this->requestAsync(substr($method, 0, -5), $uri, $opts) + : $this->request($method, $uri, $opts); + } + + /** + * Asynchronously send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @return Promise\PromiseInterface + */ + public function sendAsync(RequestInterface $request, array $options = []) + { + // Merge the base URI into the request URI if needed. + $options = $this->prepareDefaults($options); + + return $this->transfer( + $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), + $options + ); + } + + /** + * Send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @return ResponseInterface + * @throws GuzzleException + */ + public function send(RequestInterface $request, array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->sendAsync($request, $options)->wait(); + } + + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @return Promise\PromiseInterface + */ + public function requestAsync($method, $uri = '', array $options = []) + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = isset($options['headers']) ? $options['headers'] : []; + $body = isset($options['body']) ? $options['body'] : null; + $version = isset($options['version']) ? $options['version'] : '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri($uri, $options); + if (is_array($body)) { + $this->invalidBody(); + } + $request = new Psr7\Request($method, $uri, $headers, $body, $version); + // Remove the option so that they are not doubly-applied. + unset($options['headers'], $options['body'], $options['version']); + + return $this->transfer($request, $options); + } + + /** + * Create and send an HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @return ResponseInterface + * @throws GuzzleException + */ + public function request($method, $uri = '', array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->requestAsync($method, $uri, $options)->wait(); + } + + /** + * Get a client configuration option. + * + * These options include default request options of the client, a "handler" + * (if utilized by the concrete client), and a "base_uri" if utilized by + * the concrete client. + * + * @param string|null $option The config option to retrieve. + * + * @return mixed + */ + public function getConfig($option = null) + { + return $option === null + ? $this->config + : (isset($this->config[$option]) ? $this->config[$option] : null); + } + + /** + * @param string|null $uri + * + * @return UriInterface + */ + private function buildUri($uri, array $config) + { + // for BC we accept null which would otherwise fail in uri_for + $uri = Psr7\uri_for($uri === null ? '' : $uri); + + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri); + } + + if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { + $idnOptions = ($config['idn_conversion'] === true) ? IDNA_DEFAULT : $config['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + + /** + * Configures the default options for a client. + * + * @param array $config + * @return void + */ + private function configureDefaults(array $config) + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false, + 'idn_conversion' => true, + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + + // We can only trust the HTTP_PROXY environment variable in a CLI + // process due to the fact that PHP has no reliable mechanism to + // get environment variables that start with "HTTP_". + if (php_sapi_name() === 'cli' && getenv('HTTP_PROXY')) { + $defaults['proxy']['http'] = getenv('HTTP_PROXY'); + } + + if ($proxy = getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = getenv('NO_PROXY')) { + $cleanedNoProxy = str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = explode(',', $cleanedNoProxy); + } + + $this->config = $config + $defaults; + + if (!empty($config['cookies']) && $config['cookies'] === true) { + $this->config['cookies'] = new CookieJar(); + } + + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => default_user_agent()]; + } else { + // Add the User-Agent header if one was not already set. + foreach (array_keys($this->config['headers']) as $name) { + if (strtolower($name) === 'user-agent') { + return; + } + } + $this->config['headers']['User-Agent'] = default_user_agent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + * + * @return array + */ + private function prepareDefaults(array $options) + { + $defaults = $this->config; + + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = []; + unset($options['headers']); + } elseif (!is_array($options['headers'])) { + throw new \InvalidArgumentException('headers must be an array'); + } + } + + // Shallow merge defaults underneath options. + $result = $options + $defaults; + + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * Transfers the given request and applies request options. + * + * The URI of the request is not modified and the request options are used + * as-is without merging in default options. + * + * @param array $options See \GuzzleHttp\RequestOptions. + * + * @return Promise\PromiseInterface + */ + private function transfer(RequestInterface $request, array $options) + { + // save_to -> sink + if (isset($options['save_to'])) { + $options['sink'] = $options['save_to']; + unset($options['save_to']); + } + + // exceptions -> http_errors + if (isset($options['exceptions'])) { + $options['http_errors'] = $options['exceptions']; + unset($options['exceptions']); + } + + $request = $this->applyOptions($request, $options); + /** @var HandlerStack $handler */ + $handler = $options['handler']; + + try { + return Promise\promise_for($handler($request, $options)); + } catch (\Exception $e) { + return Promise\rejection_for($e); + } + } + + /** + * Applies the array of request options to a request. + * + * @param RequestInterface $request + * @param array $options + * + * @return RequestInterface + */ + private function applyOptions(RequestInterface $request, array &$options) + { + $modify = [ + 'set_headers' => [], + ]; + + if (isset($options['headers'])) { + $modify['set_headers'] = $options['headers']; + unset($options['headers']); + } + + if (isset($options['form_params'])) { + if (isset($options['multipart'])) { + throw new \InvalidArgumentException('You cannot use ' + . 'form_params and multipart at the same time. Use the ' + . 'form_params option if you want to send application/' + . 'x-www-form-urlencoded requests, and the multipart ' + . 'option to send multipart/form-data requests.'); + } + $options['body'] = http_build_query($options['form_params'], '', '&'); + unset($options['form_params']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (isset($options['multipart'])) { + $options['body'] = new Psr7\MultipartStream($options['multipart']); + unset($options['multipart']); + } + + if (isset($options['json'])) { + $options['body'] = \GuzzleHttp\json_encode($options['json']); + unset($options['json']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/json'; + } + + if (!empty($options['decode_content']) + && $options['decode_content'] !== true + ) { + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']); + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['body'])) { + if (is_array($options['body'])) { + $this->invalidBody(); + } + $modify['body'] = Psr7\stream_for($options['body']); + unset($options['body']); + } + + if (!empty($options['auth']) && is_array($options['auth'])) { + $value = $options['auth']; + $type = isset($value[2]) ? strtolower($value[2]) : 'basic'; + switch ($type) { + case 'basic': + // Ensure that we don't have the header in different case and set the new value. + $modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']); + $modify['set_headers']['Authorization'] = 'Basic ' + . base64_encode("$value[0]:$value[1]"); + break; + case 'digest': + // @todo: Do not rely on curl + $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; + $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + case 'ntlm': + $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM; + $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + } + } + + if (isset($options['query'])) { + $value = $options['query']; + if (is_array($value)) { + $value = http_build_query($value, '', '&', PHP_QUERY_RFC3986); + } + if (!is_string($value)) { + throw new \InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + unset($options['query']); + } + + // Ensure that sink is not an invalid value. + if (isset($options['sink'])) { + // TODO: Add more sink validation? + if (is_bool($options['sink'])) { + throw new \InvalidArgumentException('sink must not be a boolean'); + } + } + + $request = Psr7\modify_request($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // Use a multipart/form-data POST if a Content-Type is not set. + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' + . $request->getBody()->getBoundary(); + } + + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // Build up the changes so it's in a single clone of the message. + $modify = []; + foreach ($options['_conditional'] as $k => $v) { + if (!$request->hasHeader($k)) { + $modify['set_headers'][$k] = $v; + } + } + $request = Psr7\modify_request($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + /** + * Throw Exception with pre-set message. + * @return void + * @throws \InvalidArgumentException Invalid body. + */ + private function invalidBody() + { + throw new \InvalidArgumentException('Passing in the "body" request ' + . 'option as an array to send a POST request has been deprecated. ' + . 'Please use the "form_params" request option to send a ' + . 'application/x-www-form-urlencoded request, or the "multipart" ' + . 'request option to send a multipart/form-data request.'); + } +} diff --git a/freescout-dist/overrides/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/freescout-dist/overrides/guzzlehttp/guzzle/src/Cookie/CookieJar.php new file mode 100644 index 0000000..2bf1880 --- /dev/null +++ b/freescout-dist/overrides/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -0,0 +1,321 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + * + * @return self + */ + public static function fromArray(array $cookies, $domain) + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * @deprecated + */ + public static function getCookieValue($value) + { + return $value; + } + + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should persist session cookies + * @return bool + */ + public static function shouldPersist( + SetCookie $cookie, + $allowSessionCookies = false + ) { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return true; + } + } + + return false; + } + + /** + * Finds and returns the cookie based on the name + * + * @param string $name cookie name to search for + * @return SetCookie|null cookie that was found or null if not found + */ + public function getCookieByName($name) + { + // don't allow a non string name + if ($name === null || !is_scalar($name)) { + return null; + } + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + + return null; + } + + public function toArray() + { + return array_map(function (SetCookie $cookie) { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + public function clear($domain = null, $path = null, $name = null) + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($domain) { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + public function clearSessionCookies() + { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + public function setCookie(SetCookie $cookie) + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return false; + } + + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } else { + $this->removeCookieIfEmpty($cookie); + return false; + } + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count(): int + { + return count($this->cookies); + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator(array_values($this->cookies)); + } + + public function extractCookies( + RequestInterface $request, + ResponseInterface $response + ) { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + if (!$sc->matchesDomain($request->getUri()->getHost())) { + continue; + } + // Note: At this point `$sc->getDomain()` being a public suffix should + // be rejected, but we don't want to pull in the full PSL dependency. + $this->setCookie($sc); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 + * + * @param RequestInterface $request + * @return string + */ + private function getCookiePathFromRequest(RequestInterface $request) + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + if (0 === $lastSlashPos = strrpos($uriPath, '/')) { + return '/'; + } + + return substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request) + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme === 'https') + ) { + $values[] = $cookie->getName() . '=' + . $cookie->getValue(); + } + } + + return $values + ? $request->withHeader('Cookie', implode('; ', $values)) + : $request; + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + * + * @param SetCookie $cookie + */ + private function removeCookieIfEmpty(SetCookie $cookie) + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/freescout-dist/overrides/guzzlehttp/psr7/src/LazyOpenStream.php b/freescout-dist/overrides/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 0000000..459f869 --- /dev/null +++ b/freescout-dist/overrides/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,43 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + * + * @return StreamInterface + */ + protected function createStream() + { + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); + } +} diff --git a/freescout-dist/overrides/javoscript/laravel-macroable-models/src/MacroableModels.php b/freescout-dist/overrides/javoscript/laravel-macroable-models/src/MacroableModels.php new file mode 100644 index 0000000..824d076 --- /dev/null +++ b/freescout-dist/overrides/javoscript/laravel-macroable-models/src/MacroableModels.php @@ -0,0 +1,95 @@ +macros; + } + + public function addMacro(String $model, String $name, \Closure $closure) + { + $this->checkModelSubclass($model); + + if (! isset($this->macros[$name])) $this->macros[$name] = []; + $this->macros[$name][$model] = $closure; + $this->syncMacros($name); + } + + public function removeMacro($model, String $name) + { + $this->checkModelSubclass($model); + + if (isset($this->macros[$name]) && isset($this->macros[$name][$model])) { + unset($this->macros[$name][$model]); + if (count($this->macros[$name]) == 0) { + unset($this->macros[$name]); + } else { + $this->syncMacros($name); + } + return true; + } + + return false; + } + + public function modelHasMacro($model, $name) + { + $this->checkModelSubclass($model); + return (isset($this->macros[$name]) && isset($this->macros[$name][$model])); + } + + public function modelsThatImplement($name) + { + if (! isset($this->macros[$name])) return []; + return array_keys($this->macros[$name]); + } + + public function macrosForModel($model) + { + $this->checkModelSubclass($model); + + $macros = []; + + foreach($this->macros as $macro => $models) { + if (in_array($model, array_keys($models))) { + $params = (new \ReflectionFunction($this->macros[$macro][$model]))->getParameters(); + $macros[$macro] = [ + 'name' => $macro, + 'parameters' => $params, + ]; + } + } + + return $macros; + } + + private function syncMacros($name) + { + $models = $this->macros[$name]; + Builder::macro($name, function(...$args) use ($name, $models){ + $class = get_class($this->getModel()); + + if (! isset($models[$class])) { + throw new \BadMethodCallException("Call to undefined method {$class}::{$name}()"); + } + + $closure = \Closure::bind($models[$class], $this->getModel()); + return call_user_func($closure, ...$args); + }); + } + + private function checkModelSubclass(String $model) + { + if (! is_subclass_of($model, Model::class)) { + throw new \InvalidArgumentException('$model must be a subclass of Illuminate\\Database\\Eloquent\\Model'); + } + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/Events/Validated.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/Events/Validated.php new file mode 100644 index 0000000..ebc3b2c --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/Events/Validated.php @@ -0,0 +1,37 @@ +user = $user; + $this->guard = $guard; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php new file mode 100644 index 0000000..dd9b2a6 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php @@ -0,0 +1,70 @@ +auth = $auth; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @param string[] ...$guards + * @return mixed + * + * @throws \Illuminate\Auth\AuthenticationException + */ + public function handle($request, Closure $next, ...$guards) + { + \Eventy::action('auth_middleware.handle', $request, $guards, $next); + + $this->authenticate($guards); + + return $next($request); + } + + /** + * Determine if the user is logged in to any of the given guards. + * + * @param array $guards + * @return void + * + * @throws \Illuminate\Auth\AuthenticationException + */ + protected function authenticate(array $guards) + { + if (empty($guards)) { + return $this->auth->authenticate(); + } + + foreach ($guards as $guard) { + if ($this->auth->guard($guard)->check()) { + return $this->auth->shouldUse($guard); + } + } + + throw new AuthenticationException('Unauthenticated.', $guards); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/SessionGuard.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/SessionGuard.php new file mode 100644 index 0000000..79d0095 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Auth/SessionGuard.php @@ -0,0 +1,775 @@ +name = $name; + $this->session = $session; + $this->request = $request; + $this->provider = $provider; + } + + /** + * Get the currently authenticated user. + * + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function user() + { + if ($this->loggedOut) { + return; + } + + // If we've already retrieved the user for the current request we can just + // return it back immediately. We do not want to fetch the user data on + // every call to this method because that would be tremendously slow. + if (! is_null($this->user)) { + return $this->user; + } + + $id = $this->session->get($this->getName()); + + // First we will try to load the user using the identifier in the session if + // one exists. Otherwise we will check for a "remember me" cookie in this + // request, and if one exists, attempt to retrieve the user using that. + if (! is_null($id)) { + if ($this->user = $this->provider->retrieveById($id)) { + $this->fireAuthenticatedEvent($this->user); + } + } + + // If the user is null, but we decrypt a "recaller" cookie we can attempt to + // pull the user data on that cookie which serves as a remember cookie on + // the application. Once we have a user we can return it to the caller. + $recaller = $this->recaller(); + + if (is_null($this->user) && ! is_null($recaller)) { + $this->user = $this->userFromRecaller($recaller); + + if ($this->user) { + $this->updateSession($this->user->getAuthIdentifier()); + + $this->fireLoginEvent($this->user, true); + } + } + + return $this->user; + } + + /** + * Pull a user from the repository by its "remember me" cookie token. + * + * @param \Illuminate\Auth\Recaller $recaller + * @return mixed + */ + protected function userFromRecaller($recaller) + { + if (! $recaller->valid() || $this->recallAttempted) { + return; + } + + // If the user is null, but we decrypt a "recaller" cookie we can attempt to + // pull the user data on that cookie which serves as a remember cookie on + // the application. Once we have a user we can return it to the caller. + $this->recallAttempted = true; + + $this->viaRemember = ! is_null($user = $this->provider->retrieveByToken( + $recaller->id(), $recaller->token() + )); + + return $user; + } + + /** + * Get the decrypted recaller cookie for the request. + * + * @return \Illuminate\Auth\Recaller|null + */ + protected function recaller() + { + if (is_null($this->request)) { + return; + } + + if ($recaller = $this->request->cookies->get($this->getRecallerName())) { + return new Recaller($recaller); + } + } + + /** + * Get the ID for the currently authenticated user. + * + * @return int|null + */ + public function id() + { + if ($this->loggedOut) { + return; + } + + return $this->user() + ? $this->user()->getAuthIdentifier() + : $this->session->get($this->getName()); + } + + /** + * Log a user into the application without sessions or cookies. + * + * @param array $credentials + * @return bool + */ + public function once(array $credentials = []) + { + $this->fireAttemptEvent($credentials); + + if ($this->validate($credentials)) { + $this->setUser($this->lastAttempted); + + return true; + } + + return false; + } + + /** + * Log the given user ID into the application without sessions or cookies. + * + * @param mixed $id + * @return \Illuminate\Contracts\Auth\Authenticatable|false + */ + public function onceUsingId($id) + { + if (! is_null($user = $this->provider->retrieveById($id))) { + $this->setUser($user); + + return $user; + } + + return false; + } + + /** + * Validate a user's credentials. + * + * @param array $credentials + * @return bool + */ + public function validate(array $credentials = []) + { + $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + + return $this->hasValidCredentials($user, $credentials); + } + + /** + * Attempt to authenticate using HTTP Basic Auth. + * + * @param string $field + * @param array $extraConditions + * @return \Symfony\Component\HttpFoundation\Response|null + */ + public function basic($field = 'email', $extraConditions = []) + { + if ($this->check()) { + return; + } + + // If a username is set on the HTTP basic request, we will return out without + // interrupting the request lifecycle. Otherwise, we'll need to generate a + // request indicating that the given credentials were invalid for login. + if ($this->attemptBasic($this->getRequest(), $field, $extraConditions)) { + return; + } + + return $this->failedBasicResponse(); + } + + /** + * Perform a stateless HTTP Basic login attempt. + * + * @param string $field + * @param array $extraConditions + * @return \Symfony\Component\HttpFoundation\Response|null + */ + public function onceBasic($field = 'email', $extraConditions = []) + { + $credentials = $this->basicCredentials($this->getRequest(), $field); + + if (! $this->once(array_merge($credentials, $extraConditions))) { + return $this->failedBasicResponse(); + } + } + + /** + * Attempt to authenticate using basic authentication. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $field + * @param array $extraConditions + * @return bool + */ + protected function attemptBasic(Request $request, $field, $extraConditions = []) + { + if (! $request->getUser()) { + return false; + } + + return $this->attempt(array_merge( + $this->basicCredentials($request, $field), $extraConditions + )); + } + + /** + * Get the credential array for a HTTP Basic request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $field + * @return array + */ + protected function basicCredentials(Request $request, $field) + { + return [$field => $request->getUser(), 'password' => $request->getPassword()]; + } + + /** + * Get the response for basic authentication. + * + * @return void + * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException + */ + protected function failedBasicResponse() + { + throw new UnauthorizedHttpException('Basic', 'Invalid credentials.'); + } + + /** + * Attempt to authenticate a user using the given credentials. + * + * @param array $credentials + * @param bool $remember + * @return bool + */ + public function attempt(array $credentials = [], $remember = false) + { + $this->fireAttemptEvent($credentials, $remember); + + $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); + + // If an implementation of UserInterface was returned, we'll ask the provider + // to validate the user against the given credentials, and if they are in + // fact valid we'll log the users into the application and return true. + if ($this->hasValidCredentials($user, $credentials)) { + $this->login($user, $remember); + + return true; + } + + // If the authentication attempt fails we will fire an event so that the user + // may be notified of any suspicious attempts to access their account from + // an unrecognized user. A developer may listen to this event as needed. + $this->fireFailedEvent($user, $credentials); + + return false; + } + + /** + * Determine if the user matches the credentials. + * + * @param mixed $user + * @param array $credentials + * @return bool + */ + protected function hasValidCredentials($user, $credentials) + { + // https://github.com/laravel/framework/pull/31357 + $validated = ! is_null($user) && \Eventy::filter('session_guard.validate_credentials', $this->provider->validateCredentials($user, $credentials), $user, $credentials); + + if ($validated) { + $this->fireValidatedEvent($user); + } + + return $validated; + } + + /** + * Fires the retrieved event if the dispatcher is set. + * + * @param $user + */ + protected function fireValidatedEvent($user) + { + if (isset($this->events)) { + $this->events->dispatch(new Validated( + $this->name, $user + )); + } + } + + /** + * Log the given user ID into the application. + * + * @param mixed $id + * @param bool $remember + * @return \Illuminate\Contracts\Auth\Authenticatable|false + */ + public function loginUsingId($id, $remember = false) + { + if (! is_null($user = $this->provider->retrieveById($id))) { + $this->login($user, $remember); + + return $user; + } + + return false; + } + + /** + * Log a user into the application. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param bool $remember + * @return void + */ + public function login(AuthenticatableContract $user, $remember = false) + { + $this->updateSession($user->getAuthIdentifier()); + + // If the user should be permanently "remembered" by the application we will + // queue a permanent cookie that contains the encrypted copy of the user + // identifier. We will then decrypt this later to retrieve the users. + if ($remember) { + $this->ensureRememberTokenIsSet($user); + + $this->queueRecallerCookie($user); + } + + // If we have an event dispatcher instance set we will fire an event so that + // any listeners will hook into the authentication events and run actions + // based on the login and logout events fired from the guard instances. + $this->fireLoginEvent($user, $remember); + + $this->setUser($user); + } + + /** + * Update the session with the given ID. + * + * @param string $id + * @return void + */ + protected function updateSession($id) + { + $this->session->put($this->getName(), $id); + + $this->session->migrate(true); + } + + /** + * Create a new "remember me" token for the user if one doesn't already exist. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @return void + */ + protected function ensureRememberTokenIsSet(AuthenticatableContract $user) + { + if (empty($user->getRememberToken())) { + $this->cycleRememberToken($user); + } + } + + /** + * Queue the recaller cookie into the cookie jar. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @return void + */ + protected function queueRecallerCookie(AuthenticatableContract $user) + { + $this->getCookieJar()->queue($this->createRecaller( + $user->getAuthIdentifier().'|'.$user->getRememberToken().'|'.$user->getAuthPassword() + )); + } + + /** + * Create a "remember me" cookie for a given ID. + * + * @param string $value + * @return \Symfony\Component\HttpFoundation\Cookie + */ + protected function createRecaller($value) + { + return $this->getCookieJar()->forever($this->getRecallerName(), $value); + } + + /** + * Log the user out of the application. + * + * @return void + */ + public function logout() + { + $user = $this->user(); + + // If we have an event dispatcher instance, we can fire off the logout event + // so any further processing can be done. This allows the developer to be + // listening for anytime a user signs out of this application manually. + $this->clearUserDataFromStorage(); + + if (! is_null($this->user)) { + $this->cycleRememberToken($user); + } + + if (isset($this->events)) { + $this->events->dispatch(new Events\Logout($user)); + } + + // Once we have fired the logout event we will clear the users out of memory + // so they are no longer available as the user is no longer considered as + // being signed into this application and should not be available here. + $this->user = null; + + $this->loggedOut = true; + } + + /** + * Remove the user data from the session and cookies. + * + * @return void + */ + protected function clearUserDataFromStorage() + { + $this->session->remove($this->getName()); + + if (! is_null($this->recaller())) { + $this->getCookieJar()->queue($this->getCookieJar() + ->forget($this->getRecallerName())); + } + } + + /** + * Refresh the "remember me" token for the user. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @return void + */ + protected function cycleRememberToken(AuthenticatableContract $user) + { + $user->setRememberToken($token = Str::random(60)); + + $this->provider->updateRememberToken($user, $token); + } + + /** + * Register an authentication attempt event listener. + * + * @param mixed $callback + * @return void + */ + public function attempting($callback) + { + if (isset($this->events)) { + $this->events->listen(Events\Attempting::class, $callback); + } + } + + /** + * Fire the attempt event with the arguments. + * + * @param array $credentials + * @param bool $remember + * @return void + */ + protected function fireAttemptEvent(array $credentials, $remember = false) + { + if (isset($this->events)) { + $this->events->dispatch(new Events\Attempting( + $credentials, $remember + )); + } + } + + /** + * Fire the login event if the dispatcher is set. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param bool $remember + * @return void + */ + protected function fireLoginEvent($user, $remember = false) + { + if (isset($this->events)) { + $this->events->dispatch(new Events\Login($user, $remember)); + } + } + + /** + * Fire the authenticated event if the dispatcher is set. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @return void + */ + protected function fireAuthenticatedEvent($user) + { + if (isset($this->events)) { + $this->events->dispatch(new Events\Authenticated($user)); + } + } + + /** + * Fire the failed authentication attempt event with the given arguments. + * + * @param \Illuminate\Contracts\Auth\Authenticatable|null $user + * @param array $credentials + * @return void + */ + protected function fireFailedEvent($user, array $credentials) + { + if (isset($this->events)) { + $this->events->dispatch(new Events\Failed($user, $credentials)); + } + } + + /** + * Get the last user we attempted to authenticate. + * + * @return \Illuminate\Contracts\Auth\Authenticatable + */ + public function getLastAttempted() + { + return $this->lastAttempted; + } + + /** + * Get a unique identifier for the auth session value. + * + * @return string + */ + public function getName() + { + return 'login_'.$this->name.'_'.sha1(static::class); + } + + /** + * Get the name of the cookie used to store the "recaller". + * + * @return string + */ + public function getRecallerName() + { + return 'remember_'.$this->name.'_'.sha1(static::class); + } + + /** + * Determine if the user was authenticated via "remember me" cookie. + * + * @return bool + */ + public function viaRemember() + { + return $this->viaRemember; + } + + /** + * Get the cookie creator instance used by the guard. + * + * @return \Illuminate\Contracts\Cookie\QueueingFactory + * + * @throws \RuntimeException + */ + public function getCookieJar() + { + if (! isset($this->cookie)) { + throw new RuntimeException('Cookie jar has not been set.'); + } + + return $this->cookie; + } + + /** + * Set the cookie creator instance used by the guard. + * + * @param \Illuminate\Contracts\Cookie\QueueingFactory $cookie + * @return void + */ + public function setCookieJar(CookieJar $cookie) + { + $this->cookie = $cookie; + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Contracts\Events\Dispatcher + */ + public function getDispatcher() + { + return $this->events; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Contracts\Events\Dispatcher $events + * @return void + */ + public function setDispatcher(Dispatcher $events) + { + $this->events = $events; + } + + /** + * Get the session store used by the guard. + * + * @return \Illuminate\Contracts\Session\Session + */ + public function getSession() + { + return $this->session; + } + + /** + * Return the currently cached user. + * + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function getUser() + { + return $this->user; + } + + /** + * Set the current user. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @return $this + */ + public function setUser(AuthenticatableContract $user) + { + $this->user = $user; + + $this->loggedOut = false; + + $this->fireAuthenticatedEvent($user); + + return $this; + } + + /** + * Get the current request instance. + * + * @return \Symfony\Component\HttpFoundation\Request + */ + public function getRequest() + { + return $this->request ?: Request::createFromGlobals(); + } + + /** + * Set the current request instance. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return $this + */ + public function setRequest(Request $request) + { + $this->request = $request; + + return $this; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php new file mode 100644 index 0000000..a73cfd4 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/Broadcaster.php @@ -0,0 +1,204 @@ +channels[$channel] = $callback; + + return $this; + } + + /** + * Authenticate the incoming request for a given channel. + * + * @param \Illuminate\Http\Request $request + * @param string $channel + * @return mixed + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + protected function verifyUserCanAccessChannel($request, $channel) + { + foreach ($this->channels as $pattern => $callback) { + if (! Str::is(preg_replace('/\{(.*?)\}/', '*', $pattern), $channel)) { + continue; + } + + $parameters = $this->extractAuthParameters($pattern, $channel, $callback); + + if ($result = $callback($request->user(), ...$parameters)) { + return $this->validAuthenticationResponse($request, $result); + } + } + + throw new AccessDeniedHttpException; + } + + /** + * Extract the parameters from the given pattern and channel. + * + * @param string $pattern + * @param string $channel + * @param callable $callback + * @return array + */ + protected function extractAuthParameters($pattern, $channel, $callback) + { + $callbackParameters = (new ReflectionFunction($callback))->getParameters(); + + return collect($this->extractChannelKeys($pattern, $channel))->reject(function ($value, $key) { + return is_numeric($key); + })->map(function ($value, $key) use ($callbackParameters) { + return $this->resolveBinding($key, $value, $callbackParameters); + })->values()->all(); + } + + /** + * Extract the channel keys from the incoming channel name. + * + * @param string $pattern + * @param string $channel + * @return array + */ + protected function extractChannelKeys($pattern, $channel) + { + preg_match('/^'.preg_replace('/\{(.*?)\}/', '(?<$1>[^\.]+)', $pattern).'/', $channel, $keys); + + return $keys; + } + + /** + * Resolve the given parameter binding. + * + * @param string $key + * @param string $value + * @param array $callbackParameters + * @return mixed + */ + protected function resolveBinding($key, $value, $callbackParameters) + { + $newValue = $this->resolveExplicitBindingIfPossible($key, $value); + + return $newValue === $value ? $this->resolveImplicitBindingIfPossible( + $key, $value, $callbackParameters + ) : $newValue; + } + + /** + * Resolve an explicit parameter binding if applicable. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function resolveExplicitBindingIfPossible($key, $value) + { + $binder = $this->binder(); + + if ($binder && $binder->getBindingCallback($key)) { + return call_user_func($binder->getBindingCallback($key), $value); + } + + return $value; + } + + /** + * Resolve an implicit parameter binding if applicable. + * + * @param string $key + * @param mixed $value + * @param array $callbackParameters + * @return mixed + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + protected function resolveImplicitBindingIfPossible($key, $value, $callbackParameters) + { + foreach ($callbackParameters as $parameter) { + if (! $this->isImplicitlyBindable($key, $parameter)) { + continue; + } + + $instance = \Helper::getClass($parameter)->newInstance(); + + if (! $model = $instance->resolveRouteBinding($value)) { + throw new AccessDeniedHttpException; + } + + return $model; + } + + return $value; + } + + /** + * Determine if a given key and parameter is implicitly bindable. + * + * @param string $key + * @param \ReflectionParameter $parameter + * @return bool + */ + protected function isImplicitlyBindable($key, $parameter) + { + return $parameter->name === $key && \Helper::getClass($parameter) && + \Helper::getClass($parameter)->isSubclassOf(UrlRoutable::class); + } + + /** + * Format the channel array into an array of strings. + * + * @param array $channels + * @return array + */ + protected function formatChannels(array $channels) + { + return array_map(function ($channel) { + return (string) $channel; + }, $channels); + } + + /** + * Get the model binding registrar instance. + * + * @return \Illuminate\Contracts\Routing\BindingRegistrar + */ + protected function binder() + { + if (! $this->bindingRegistrar) { + $this->bindingRegistrar = Container::getInstance()->bound(BindingRegistrar::class) + ? Container::getInstance()->make(BindingRegistrar::class) : null; + } + + return $this->bindingRegistrar; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php new file mode 100644 index 0000000..3f92d4a --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php @@ -0,0 +1,137 @@ +cache = $cache; + $this->files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function handle() + { + $this->laravel['events']->fire( + 'cache:clearing', [$this->argument('store'), $this->tags()] + ); + + $this->cache()->flush(); + + $this->flushFacades(); + + $this->laravel['events']->fire( + 'cache:cleared', [$this->argument('store'), $this->tags()] + ); + + $this->info('Cache cleared successfully.'); + } + + /** + * Flush the real-time facades stored in the cache directory. + * + * @return void + */ + public function flushFacades() + { + foreach ($this->files->files(storage_path('framework/cache')) as $file) { + if (preg_match('/facade-.*\.php$/', $file)) { + $this->files->delete($file); + } + } + } + + /** + * Get the cache instance for the command. + * + * @return \Illuminate\Cache\Repository + */ + protected function cache() + { + $cache = $this->cache->store($this->argument('store')); + + return empty($this->tags()) ? $cache : $cache->tags($this->tags()); + } + + /** + * Get the tags passed to the command. + * + * @return array + */ + protected function tags() + { + return array_filter(explode(',', $this->option('tags') ?? '')); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return [ + ['store', InputArgument::OPTIONAL, 'The name of the store you would like to clear.'], + ]; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['tags', null, InputOption::VALUE_OPTIONAL, 'The cache tags you would like to clear.', null], + ]; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Cache/Repository.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Cache/Repository.php new file mode 100644 index 0000000..9d6ca8d --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Cache/Repository.php @@ -0,0 +1,588 @@ +store = $store; + } + + /** + * Determine if an item exists in the cache. + * + * @param string $key + * @return bool + */ + public function has($key) + { + return ! is_null($this->get($key)); + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (is_array($key)) { + return $this->many($key); + } + + $value = $this->store->get($this->itemKey($key)); + + // If we could not find the cache value, we will fire the missed event and get + // the default value for this cache value. This default could be a callback + // so we will execute the value function which will resolve it if needed. + if (is_null($value)) { + $this->event(new CacheMissed($key)); + + $value = value($default); + } else { + $this->event(new CacheHit($key, $value)); + } + + return $value; + } + + /** + * Retrieve multiple items from the cache by key. + * + * Items not found in the cache will have a null value. + * + * @param array $keys + * @return array + */ + public function many(array $keys) + { + $values = $this->store->many(collect($keys)->map(function ($value, $key) { + return is_string($key) ? $key : $value; + })->values()->all()); + + return collect($values)->map(function ($value, $key) use ($keys) { + return $this->handleManyResult($keys, $key, $value); + })->all(); + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null) + { + if (is_null($default)) { + return $this->many($keys); + } + + foreach ($keys as $key) { + if (! isset($default[$key])) { + $default[$key] = null; + } + } + + return $this->many($default); + } + + /** + * Handle a result for the "many" method. + * + * @param array $keys + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function handleManyResult($keys, $key, $value) + { + // If we could not find the cache value, we will fire the missed event and get + // the default value for this cache value. This default could be a callback + // so we will execute the value function which will resolve it if needed. + if (is_null($value)) { + $this->event(new CacheMissed($key)); + + return isset($keys[$key]) ? value($keys[$key]) : null; + } + + // If we found a valid value we will fire the "hit" event and return the value + // back from this function. The "hit" event gives developers an opportunity + // to listen for every possible cache "hit" throughout this applications. + $this->event(new CacheHit($key, $value)); + + return $value; + } + + /** + * Retrieve an item from the cache and delete it. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function pull($key, $default = null) + { + return tap($this->get($key, $default), function ($value) use ($key) { + $this->forget($key); + }); + } + + /** + * Store an item in the cache. + * + * @param string $key + * @param mixed $value + * @param \DateTimeInterface|\DateInterval|float|int $minutes + * @return void + */ + public function put($key, $value, $minutes = null) + { + if (is_array($key)) { + return $this->putMany($key, $value); + } + + if (! is_null($minutes = $this->getMinutes($minutes))) { + $this->store->put($this->itemKey($key), $value, $minutes); + + $this->event(new KeyWritten($key, $value, $minutes)); + } + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + $this->put($key, $value, $ttl); + } + + /** + * Store multiple items in the cache for a given number of minutes. + * + * @param array $values + * @param \DateTimeInterface|\DateInterval|float|int $minutes + * @return void + */ + public function putMany(array $values, $minutes) + { + if (! is_null($minutes = $this->getMinutes($minutes))) { + $this->store->putMany($values, $minutes); + + foreach ($values as $key => $value) { + $this->event(new KeyWritten($key, $value, $minutes)); + } + } + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + $this->putMany($values, $ttl); + } + + /** + * Store an item in the cache if the key does not exist. + * + * @param string $key + * @param mixed $value + * @param \DateTimeInterface|\DateInterval|float|int $minutes + * @return bool + */ + public function add($key, $value, $minutes) + { + if (is_null($minutes = $this->getMinutes($minutes))) { + return false; + } + + // If the store has an "add" method we will call the method on the store so it + // has a chance to override this logic. Some drivers better support the way + // this operation should work with a total "atomic" implementation of it. + if (method_exists($this->store, 'add')) { + return $this->store->add( + $this->itemKey($key), $value, $minutes + ); + } + + // If the value did not exist in the cache, we will put the value in the cache + // so it exists for subsequent requests. Then, we will return true so it is + // easy to know if the value gets added. Otherwise, we will return false. + if (is_null($this->get($key))) { + $this->put($key, $value, $minutes); + + return true; + } + + return false; + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function increment($key, $value = 1) + { + return $this->store->increment($key, $value); + } + + /** + * Decrement the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return int|bool + */ + public function decrement($key, $value = 1) + { + return $this->store->decrement($key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $this->store->forever($this->itemKey($key), $value); + + $this->event(new KeyWritten($key, $value, 0)); + } + + /** + * Get an item from the cache, or store the default value. + * + * @param string $key + * @param \DateTimeInterface|\DateInterval|float|int $minutes + * @param \Closure $callback + * @return mixed + */ + public function remember($key, $minutes, Closure $callback) + { + $value = $this->get($key); + + // If the item exists in the cache we will just return this immediately and if + // not we will execute the given Closure and cache the result of that for a + // given number of minutes so it's available for all subsequent requests. + if (! is_null($value)) { + return $value; + } + + $this->put($key, $value = $callback(), $minutes); + + return $value; + } + + /** + * Get an item from the cache, or store the default value forever. + * + * @param string $key + * @param \Closure $callback + * @return mixed + */ + public function sear($key, Closure $callback) + { + return $this->rememberForever($key, $callback); + } + + /** + * Get an item from the cache, or store the default value forever. + * + * @param string $key + * @param \Closure $callback + * @return mixed + */ + public function rememberForever($key, Closure $callback) + { + $value = $this->get($key); + + // If the item exists in the cache we will just return this immediately and if + // not we will execute the given Closure and cache the result of that for a + // given number of minutes so it's available for all subsequent requests. + if (! is_null($value)) { + return $value; + } + + $this->forever($key, $value = $callback()); + + return $value; + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return bool + */ + public function forget($key) + { + return tap($this->store->forget($this->itemKey($key)), function () use ($key) { + $this->event(new KeyForgotten($key)); + }); + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + return $this->forget($key); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple($keys) + { + foreach ($keys as $key) { + $this->forget($key); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->store->flush(); + } + + /** + * Begin executing a new tags operation if the store supports it. + * + * @param array|mixed $names + * @return \Illuminate\Cache\TaggedCache + * + * @throws \BadMethodCallException + */ + public function tags($names) + { + if (! method_exists($this->store, 'tags')) { + throw new BadMethodCallException('This cache store does not support tagging.'); + } + + $cache = $this->store->tags($names); + + if (! is_null($this->events)) { + $cache->setEventDispatcher($this->events); + } + + return $cache->setDefaultCacheTime($this->default); + } + + /** + * Format the key for a cache item. + * + * @param string $key + * @return string + */ + protected function itemKey($key) + { + return $key; + } + + /** + * Get the default cache time. + * + * @return float|int + */ + public function getDefaultCacheTime() + { + return $this->default; + } + + /** + * Set the default cache time in minutes. + * + * @param float|int $minutes + * @return $this + */ + public function setDefaultCacheTime($minutes) + { + $this->default = $minutes; + + return $this; + } + + /** + * Get the cache store implementation. + * + * @return \Illuminate\Contracts\Cache\Store + */ + public function getStore() + { + return $this->store; + } + + /** + * Fire an event for this cache instance. + * + * @param string $event + * @return void + */ + protected function event($event) + { + if (isset($this->events)) { + $this->events->dispatch($event); + } + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Contracts\Events\Dispatcher $events + * @return void + */ + public function setEventDispatcher(Dispatcher $events) + { + $this->events = $events; + } + + /** + * Determine if a cached value exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key): bool + { + return $this->has($key); + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + * : mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Store an item in the cache for the default time. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value): void + { + $this->put($key, $value, $this->default); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ + public function offsetUnset($key): void + { + $this->forget($key); + } + + /** + * Calculate the number of minutes with the given duration. + * + * @param \DateTimeInterface|\DateInterval|float|int $duration + * @return float|int|null + */ + protected function getMinutes($duration) + { + $duration = $this->parseDateInterval($duration); + + if ($duration instanceof DateTimeInterface) { + $duration = Carbon::now()->diffInSeconds(Carbon::createFromTimestamp($duration->getTimestamp()), false) / 60; + } + + return (int) ($duration * 60) > 0 ? $duration : null; + } + + /** + * Handle dynamic calls into macros or pass missing methods to the store. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + return $this->store->$method(...$parameters); + } + + /** + * Clone cache repository instance. + * + * @return void + */ + public function __clone() + { + $this->store = clone $this->store; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Config/Repository.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Config/Repository.php new file mode 100644 index 0000000..01e9486 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Config/Repository.php @@ -0,0 +1,180 @@ +items = $items; + } + + /** + * Determine if the given configuration value exists. + * + * @param string $key + * @return bool + */ + public function has($key) + { + return Arr::has($this->items, $key); + } + + /** + * Get the specified configuration value. + * + * @param array|string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (is_array($key)) { + return $this->getMany($key); + } + + return Arr::get($this->items, $key, $default); + } + + /** + * Get many configuration values. + * + * @param array $keys + * @return array + */ + public function getMany($keys) + { + $config = []; + + foreach ($keys as $key => $default) { + if (is_numeric($key)) { + list($key, $default) = [$default, null]; + } + + $config[$key] = Arr::get($this->items, $key, $default); + } + + return $config; + } + + /** + * Set a given configuration value. + * + * @param array|string $key + * @param mixed $value + * @return void + */ + public function set($key, $value = null) + { + $keys = is_array($key) ? $key : [$key => $value]; + + foreach ($keys as $key => $value) { + Arr::set($this->items, $key, $value); + } + } + + /** + * Prepend a value onto an array configuration value. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function prepend($key, $value) + { + $array = $this->get($key); + + array_unshift($array, $value); + + $this->set($key, $array); + } + + /** + * Push a value onto an array configuration value. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function push($key, $value) + { + $array = $this->get($key); + + $array[] = $value; + + $this->set($key, $array); + } + + /** + * Get all of the configuration items for the application. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Determine if the given configuration option exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key): bool + { + return $this->has($key); + } + + /** + * Get a configuration option. + * + * @param string $key + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Set a configuration option. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value): void + { + $this->set($key, $value); + } + + /** + * Unset a configuration option. + * + * @param string $key + * @return void + */ + public function offsetUnset($key): void + { + $this->set($key, null); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Container/BoundMethod.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Container/BoundMethod.php new file mode 100644 index 0000000..0f8f8ee --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Container/BoundMethod.php @@ -0,0 +1,174 @@ +make($segments[0]), $method], $parameters + ); + } + + /** + * Call a method that has been bound to the container. + * + * @param \Illuminate\Container\Container $container + * @param callable $callback + * @param mixed $default + * @return mixed + */ + protected static function callBoundMethod($container, $callback, $default) + { + if (! is_array($callback)) { + return $default instanceof Closure ? $default() : $default; + } + + // Here we need to turn the array callable into a Class@method string we can use to + // examine the container and see if there are any method bindings for this given + // method. If there are, we can call this method binding callback immediately. + $method = static::normalizeMethod($callback); + + if ($container->hasMethodBinding($method)) { + return $container->callMethodBinding($method, $callback[0]); + } + + return $default instanceof Closure ? $default() : $default; + } + + /** + * Normalize the given callback into a Class@method string. + * + * @param callable $callback + * @return string + */ + protected static function normalizeMethod($callback) + { + $class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]); + + return "{$class}@{$callback[1]}"; + } + + /** + * Get all dependencies for a given method. + * + * @param \Illuminate\Container\Container $container + * @param callable|string $callback + * @param array $parameters + * @return array + */ + protected static function getMethodDependencies($container, $callback, array $parameters = []) + { + $dependencies = []; + + foreach (static::getCallReflector($callback)->getParameters() as $parameter) { + static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies); + } + + return array_merge($dependencies, $parameters); + } + + /** + * Get the proper reflection instance for the given callback. + * + * @param callable|string $callback + * @return \ReflectionFunctionAbstract + */ + protected static function getCallReflector($callback) + { + if (is_string($callback) && strpos($callback, '::') !== false) { + $callback = explode('::', $callback); + } + + return is_array($callback) + ? new ReflectionMethod($callback[0], $callback[1]) + : new ReflectionFunction($callback); + } + + /** + * Get the dependency for the given call parameter. + * + * @param \Illuminate\Container\Container $container + * @param \ReflectionParameter $parameter + * @param array $parameters + * @param array $dependencies + * @return mixed + */ + protected static function addDependencyForCallParameter($container, $parameter, + array &$parameters, &$dependencies) + { + if (array_key_exists($parameter->name, $parameters)) { + $dependencies[] = $parameters[$parameter->name]; + + unset($parameters[$parameter->name]); + //} elseif ($parameter->getClass()) { + } elseif (\Helper::getClass($parameter)) { + //$dependencies[] = $container->make($parameter->getClass()->name); + $dependencies[] = $container->make(\Helper::getClassName($parameter)); + } elseif ($parameter->isDefaultValueAvailable()) { + $dependencies[] = $parameter->getDefaultValue(); + } + } + + /** + * Determine if the given string is in Class@method syntax. + * + * @param mixed $callback + * @return bool + */ + protected static function isCallableWithAtSign($callback) + { + return is_string($callback) && strpos($callback, '@') !== false; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Container/Container.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Container/Container.php new file mode 100644 index 0000000..785695b --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Container/Container.php @@ -0,0 +1,1248 @@ +getAlias($concrete)); + } + + /** + * Determine if the given abstract type has been bound. + * + * @param string $abstract + * @return bool + */ + public function bound($abstract) + { + return isset($this->bindings[$abstract]) || + isset($this->instances[$abstract]) || + $this->isAlias($abstract); + } + + /** + * {@inheritdoc} + */ + public function has($id) + { + return $this->bound($id); + } + + /** + * Determine if the given abstract type has been resolved. + * + * @param string $abstract + * @return bool + */ + public function resolved($abstract) + { + if ($this->isAlias($abstract)) { + $abstract = $this->getAlias($abstract); + } + + return isset($this->resolved[$abstract]) || + isset($this->instances[$abstract]); + } + + /** + * Determine if a given type is shared. + * + * @param string $abstract + * @return bool + */ + public function isShared($abstract) + { + return isset($this->instances[$abstract]) || + (isset($this->bindings[$abstract]['shared']) && + $this->bindings[$abstract]['shared'] === true); + } + + /** + * Determine if a given string is an alias. + * + * @param string $name + * @return bool + */ + public function isAlias($name) + { + return isset($this->aliases[$name]); + } + + /** + * Register a binding with the container. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @param bool $shared + * @return void + */ + public function bind($abstract, $concrete = null, $shared = false) + { + // If no concrete type was given, we will simply set the concrete type to the + // abstract type. After that, the concrete type to be registered as shared + // without being forced to state their classes in both of the parameters. + $this->dropStaleInstances($abstract); + + if (is_null($concrete)) { + $concrete = $abstract; + } + + // If the factory is not a Closure, it means it is just a class name which is + // bound into this container to the abstract type and we will just wrap it + // up inside its own Closure to give us more convenience when extending. + if (! $concrete instanceof Closure) { + $concrete = $this->getClosure($abstract, $concrete); + } + + $this->bindings[$abstract] = compact('concrete', 'shared'); + + // If the abstract type was already resolved in this container we'll fire the + // rebound listener so that any objects which have already gotten resolved + // can have their copy of the object updated via the listener callbacks. + if ($this->resolved($abstract)) { + $this->rebound($abstract); + } + } + + /** + * Get the Closure to be used when building a type. + * + * @param string $abstract + * @param string $concrete + * @return \Closure + */ + protected function getClosure($abstract, $concrete) + { + return function ($container, $parameters = []) use ($abstract, $concrete) { + if ($abstract == $concrete) { + return $container->build($concrete); + } + + return $container->make($concrete, $parameters); + }; + } + + /** + * Determine if the container has a method binding. + * + * @param string $method + * @return bool + */ + public function hasMethodBinding($method) + { + return isset($this->methodBindings[$method]); + } + + /** + * Bind a callback to resolve with Container::call. + * + * @param string $method + * @param \Closure $callback + * @return void + */ + public function bindMethod($method, $callback) + { + $this->methodBindings[$method] = $callback; + } + + /** + * Get the method binding for the given method. + * + * @param string $method + * @param mixed $instance + * @return mixed + */ + public function callMethodBinding($method, $instance) + { + return call_user_func($this->methodBindings[$method], $instance, $this); + } + + /** + * Add a contextual binding to the container. + * + * @param string $concrete + * @param string $abstract + * @param \Closure|string $implementation + * @return void + */ + public function addContextualBinding($concrete, $abstract, $implementation) + { + $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation; + } + + /** + * Register a binding if it hasn't already been registered. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @param bool $shared + * @return void + */ + public function bindIf($abstract, $concrete = null, $shared = false) + { + if (! $this->bound($abstract)) { + $this->bind($abstract, $concrete, $shared); + } + } + + /** + * Register a shared binding in the container. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @return void + */ + public function singleton($abstract, $concrete = null) + { + $this->bind($abstract, $concrete, true); + } + + /** + * "Extend" an abstract type in the container. + * + * @param string $abstract + * @param \Closure $closure + * @return void + * + * @throws \InvalidArgumentException + */ + public function extend($abstract, Closure $closure) + { + $abstract = $this->getAlias($abstract); + + if (isset($this->instances[$abstract])) { + $this->instances[$abstract] = $closure($this->instances[$abstract], $this); + + $this->rebound($abstract); + } else { + $this->extenders[$abstract][] = $closure; + + if ($this->resolved($abstract)) { + $this->rebound($abstract); + } + } + } + + /** + * Register an existing instance as shared in the container. + * + * @param string $abstract + * @param mixed $instance + * @return mixed + */ + public function instance($abstract, $instance) + { + $this->removeAbstractAlias($abstract); + + $isBound = $this->bound($abstract); + + unset($this->aliases[$abstract]); + + // We'll check to determine if this type has been bound before, and if it has + // we will fire the rebound callbacks registered with the container and it + // can be updated with consuming classes that have gotten resolved here. + $this->instances[$abstract] = $instance; + + if ($isBound) { + $this->rebound($abstract); + } + + return $instance; + } + + /** + * Remove an alias from the contextual binding alias cache. + * + * @param string $searched + * @return void + */ + protected function removeAbstractAlias($searched) + { + if (! isset($this->aliases[$searched])) { + return; + } + + foreach ($this->abstractAliases as $abstract => $aliases) { + foreach ($aliases as $index => $alias) { + if ($alias == $searched) { + unset($this->abstractAliases[$abstract][$index]); + } + } + } + } + + /** + * Assign a set of tags to a given binding. + * + * @param array|string $abstracts + * @param array|mixed ...$tags + * @return void + */ + public function tag($abstracts, $tags) + { + $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1); + + foreach ($tags as $tag) { + if (! isset($this->tags[$tag])) { + $this->tags[$tag] = []; + } + + foreach ((array) $abstracts as $abstract) { + $this->tags[$tag][] = $abstract; + } + } + } + + /** + * Resolve all of the bindings for a given tag. + * + * @param string $tag + * @return array + */ + public function tagged($tag) + { + $results = []; + + if (isset($this->tags[$tag])) { + foreach ($this->tags[$tag] as $abstract) { + $results[] = $this->make($abstract); + } + } + + return $results; + } + + /** + * Alias a type to a different name. + * + * @param string $abstract + * @param string $alias + * @return void + */ + public function alias($abstract, $alias) + { + $this->aliases[$alias] = $abstract; + + $this->abstractAliases[$abstract][] = $alias; + } + + /** + * Bind a new callback to an abstract's rebind event. + * + * @param string $abstract + * @param \Closure $callback + * @return mixed + */ + public function rebinding($abstract, Closure $callback) + { + $this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback; + + if ($this->bound($abstract)) { + return $this->make($abstract); + } + } + + /** + * Refresh an instance on the given target and method. + * + * @param string $abstract + * @param mixed $target + * @param string $method + * @return mixed + */ + public function refresh($abstract, $target, $method) + { + return $this->rebinding($abstract, function ($app, $instance) use ($target, $method) { + $target->{$method}($instance); + }); + } + + /** + * Fire the "rebound" callbacks for the given abstract type. + * + * @param string $abstract + * @return void + */ + protected function rebound($abstract) + { + $instance = $this->make($abstract); + + foreach ($this->getReboundCallbacks($abstract) as $callback) { + call_user_func($callback, $this, $instance); + } + } + + /** + * Get the rebound callbacks for a given type. + * + * @param string $abstract + * @return array + */ + protected function getReboundCallbacks($abstract) + { + if (isset($this->reboundCallbacks[$abstract])) { + return $this->reboundCallbacks[$abstract]; + } + + return []; + } + + /** + * Wrap the given closure such that its dependencies will be injected when executed. + * + * @param \Closure $callback + * @param array $parameters + * @return \Closure + */ + public function wrap(Closure $callback, array $parameters = []) + { + return function () use ($callback, $parameters) { + return $this->call($callback, $parameters); + }; + } + + /** + * Call the given Closure / class@method and inject its dependencies. + * + * @param callable|string $callback + * @param array $parameters + * @param string|null $defaultMethod + * @return mixed + */ + public function call($callback, array $parameters = [], $defaultMethod = null) + { + return BoundMethod::call($this, $callback, $parameters, $defaultMethod); + } + + /** + * Get a closure to resolve the given type from the container. + * + * @param string $abstract + * @return \Closure + */ + public function factory($abstract) + { + return function () use ($abstract) { + return $this->make($abstract); + }; + } + + /** + * An alias function name for make(). + * + * @param string $abstract + * @param array $parameters + * @return mixed + */ + public function makeWith($abstract, array $parameters = []) + { + return $this->make($abstract, $parameters); + } + + /** + * Resolve the given type from the container. + * + * @param string $abstract + * @param array $parameters + * @return mixed + */ + public function make($abstract, array $parameters = []) + { + return $this->resolve($abstract, $parameters); + } + + /** + * {@inheritdoc} + */ + public function get($id) + { + if ($this->has($id)) { + return $this->resolve($id); + } + + throw new EntryNotFoundException; + } + + /** + * Resolve the given type from the container. + * + * @param string $abstract + * @param array $parameters + * @return mixed + */ + protected function resolve($abstract, $parameters = []) + { + $abstract = $this->getAlias($abstract); + + $needsContextualBuild = ! empty($parameters) || ! is_null( + $this->getContextualConcrete($abstract) + ); + + // If an instance of the type is currently being managed as a singleton we'll + // just return an existing instance instead of instantiating new instances + // so the developer can keep using the same objects instance every time. + if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { + return $this->instances[$abstract]; + } + + $this->with[] = $parameters; + + $concrete = $this->getConcrete($abstract); + + // We're ready to instantiate an instance of the concrete type registered for + // the binding. This will instantiate the types, as well as resolve any of + // its "nested" dependencies recursively until all have gotten resolved. + if ($this->isBuildable($concrete, $abstract)) { + $object = $this->build($concrete); + } else { + $object = $this->make($concrete); + } + + // If we defined any extenders for this type, we'll need to spin through them + // and apply them to the object being built. This allows for the extension + // of services, such as changing configuration or decorating the object. + foreach ($this->getExtenders($abstract) as $extender) { + $object = $extender($object, $this); + } + + // If the requested type is registered as a singleton we'll want to cache off + // the instances in "memory" so we can return it later without creating an + // entirely new instance of an object on each subsequent request for it. + if ($this->isShared($abstract) && ! $needsContextualBuild) { + $this->instances[$abstract] = $object; + } + + $this->fireResolvingCallbacks($abstract, $object); + + // Before returning, we will also set the resolved flag to "true" and pop off + // the parameter overrides for this build. After those two things are done + // we will be ready to return back the fully constructed class instance. + $this->resolved[$abstract] = true; + + array_pop($this->with); + + return $object; + } + + /** + * Get the concrete type for a given abstract. + * + * @param string $abstract + * @return mixed $concrete + */ + protected function getConcrete($abstract) + { + if (! is_null($concrete = $this->getContextualConcrete($abstract))) { + return $concrete; + } + + // If we don't have a registered resolver or concrete for the type, we'll just + // assume each type is a concrete name and will attempt to resolve it as is + // since the container should be able to resolve concretes automatically. + if (isset($this->bindings[$abstract])) { + return $this->bindings[$abstract]['concrete']; + } + + return $abstract; + } + + /** + * Get the contextual concrete binding for the given abstract. + * + * @param string $abstract + * @return string|null + */ + protected function getContextualConcrete($abstract) + { + if (! is_null($binding = $this->findInContextualBindings($abstract))) { + return $binding; + } + + // Next we need to see if a contextual binding might be bound under an alias of the + // given abstract type. So, we will need to check if any aliases exist with this + // type and then spin through them and check for contextual bindings on these. + if (empty($this->abstractAliases[$abstract])) { + return; + } + + foreach ($this->abstractAliases[$abstract] as $alias) { + if (! is_null($binding = $this->findInContextualBindings($alias))) { + return $binding; + } + } + } + + /** + * Find the concrete binding for the given abstract in the contextual binding array. + * + * @param string $abstract + * @return string|null + */ + protected function findInContextualBindings($abstract) + { + if (isset($this->contextual[end($this->buildStack)][$abstract])) { + return $this->contextual[end($this->buildStack)][$abstract]; + } + } + + /** + * Determine if the given concrete is buildable. + * + * @param mixed $concrete + * @param string $abstract + * @return bool + */ + protected function isBuildable($concrete, $abstract) + { + return $concrete === $abstract || $concrete instanceof Closure; + } + + /** + * Instantiate a concrete instance of the given type. + * + * @param string $concrete + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + public function build($concrete) + { + // If the concrete type is actually a Closure, we will just execute it and + // hand back the results of the functions, which allows functions to be + // used as resolvers for more fine-tuned resolution of these objects. + if ($concrete instanceof Closure) { + return $concrete($this, $this->getLastParameterOverride()); + } + + $reflector = new ReflectionClass($concrete); + + // If the type is not instantiable, the developer is attempting to resolve + // an abstract type such as an Interface of Abstract Class and there is + // no binding registered for the abstractions so we need to bail out. + if (! $reflector->isInstantiable()) { + return $this->notInstantiable($concrete); + } + + $this->buildStack[] = $concrete; + + $constructor = $reflector->getConstructor(); + + // If there are no constructors, that means there are no dependencies then + // we can just resolve the instances of the objects right away, without + // resolving any other types or dependencies out of these containers. + if (is_null($constructor)) { + array_pop($this->buildStack); + + return new $concrete; + } + + $dependencies = $constructor->getParameters(); + + // Once we have all the constructor's parameters we can create each of the + // dependency instances and then use the reflection instances to make a + // new instance of this class, injecting the created dependencies in. + $instances = $this->resolveDependencies( + $dependencies + ); + + array_pop($this->buildStack); + + return $reflector->newInstanceArgs($instances); + } + + /** + * Resolve all of the dependencies from the ReflectionParameters. + * + * @param array $dependencies + * @return array + */ + protected function resolveDependencies(array $dependencies) + { + $results = []; + + foreach ($dependencies as $dependency) { + // If this dependency has a override for this particular build we will use + // that instead as the value. Otherwise, we will continue with this run + // of resolutions and let reflection attempt to determine the result. + if ($this->hasParameterOverride($dependency)) { + $results[] = $this->getParameterOverride($dependency); + + continue; + } + + // If the class is null, it means the dependency is a string or some other + // primitive type which we can not resolve since it is not a class and + // we will just bomb out with an error since we have no-where to go. + $results[] = is_null($dependency->getType() && !$dependency->getType()->isBuiltin() ? new ReflectionClass(method_exists($dependency->getType(), 'getType') ? $dependency->getType()->getName() : $dependency->getType()) : null) + ? $this->resolvePrimitive($dependency) + : $this->resolveClass($dependency); + } + + return $results; + } + + /** + * Determine if the given dependency has a parameter override. + * + * @param \ReflectionParameter $dependency + * @return bool + */ + protected function hasParameterOverride($dependency) + { + return array_key_exists( + $dependency->name, $this->getLastParameterOverride() + ); + } + + /** + * Get a parameter override for a dependency. + * + * @param \ReflectionParameter $dependency + * @return mixed + */ + protected function getParameterOverride($dependency) + { + return $this->getLastParameterOverride()[$dependency->name]; + } + + /** + * Get the last parameter override. + * + * @return array + */ + protected function getLastParameterOverride() + { + return count($this->with) ? end($this->with) : []; + } + + /** + * Resolve a non-class hinted primitive dependency. + * + * @param \ReflectionParameter $parameter + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function resolvePrimitive(ReflectionParameter $parameter) + { + if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) { + return $concrete instanceof Closure ? $concrete($this) : $concrete; + } + + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + + $this->unresolvablePrimitive($parameter); + } + + /** + * Resolve a class based dependency from the container. + * + * @param \ReflectionParameter $parameter + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function resolveClass(ReflectionParameter $parameter) + { + try { + return $this->make($parameter->getType() && !$parameter->getType()->isBuiltin() ? (method_exists($parameter->getType(), 'getName') ? $parameter->getType()->getName() : $parameter->getClass()->name) : null); + } + + // If we can not resolve the class instance, we will check to see if the value + // is optional, and if it is we will return the optional parameter value as + // the value of the dependency, similarly to how we do this with scalars. + catch (BindingResolutionException $e) { + if ($parameter->isOptional()) { + return $parameter->getDefaultValue(); + } + + throw $e; + } + } + + /** + * Throw an exception that the concrete is not instantiable. + * + * @param string $concrete + * @return void + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function notInstantiable($concrete) + { + if (! empty($this->buildStack)) { + $previous = implode(', ', $this->buildStack); + + $message = "Target [$concrete] is not instantiable while building [$previous]."; + } else { + $message = "Target [$concrete] is not instantiable."; + } + + throw new BindingResolutionException($message); + } + + /** + * Throw an exception for an unresolvable primitive. + * + * @param \ReflectionParameter $parameter + * @return void + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function unresolvablePrimitive(ReflectionParameter $parameter) + { + $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}"; + + throw new BindingResolutionException($message); + } + + /** + * Register a new resolving callback. + * + * @param \Closure|string $abstract + * @param \Closure|null $callback + * @return void + */ + public function resolving($abstract, Closure $callback = null) + { + if (is_string($abstract)) { + $abstract = $this->getAlias($abstract); + } + + if (is_null($callback) && $abstract instanceof Closure) { + $this->globalResolvingCallbacks[] = $abstract; + } else { + $this->resolvingCallbacks[$abstract][] = $callback; + } + } + + /** + * Register a new after resolving callback for all types. + * + * @param \Closure|string $abstract + * @param \Closure|null $callback + * @return void + */ + public function afterResolving($abstract, Closure $callback = null) + { + if (is_string($abstract)) { + $abstract = $this->getAlias($abstract); + } + + if ($abstract instanceof Closure && is_null($callback)) { + $this->globalAfterResolvingCallbacks[] = $abstract; + } else { + $this->afterResolvingCallbacks[$abstract][] = $callback; + } + } + + /** + * Fire all of the resolving callbacks. + * + * @param string $abstract + * @param mixed $object + * @return void + */ + protected function fireResolvingCallbacks($abstract, $object) + { + $this->fireCallbackArray($object, $this->globalResolvingCallbacks); + + $this->fireCallbackArray( + $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks) + ); + + $this->fireAfterResolvingCallbacks($abstract, $object); + } + + /** + * Fire all of the after resolving callbacks. + * + * @param string $abstract + * @param mixed $object + * @return void + */ + protected function fireAfterResolvingCallbacks($abstract, $object) + { + $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks); + + $this->fireCallbackArray( + $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks) + ); + } + + /** + * Get all callbacks for a given type. + * + * @param string $abstract + * @param object $object + * @param array $callbacksPerType + * + * @return array + */ + protected function getCallbacksForType($abstract, $object, array $callbacksPerType) + { + $results = []; + + foreach ($callbacksPerType as $type => $callbacks) { + if ($type === $abstract || $object instanceof $type) { + $results = array_merge($results, $callbacks); + } + } + + return $results; + } + + /** + * Fire an array of callbacks with an object. + * + * @param mixed $object + * @param array $callbacks + * @return void + */ + protected function fireCallbackArray($object, array $callbacks) + { + foreach ($callbacks as $callback) { + $callback($object, $this); + } + } + + /** + * Get the container's bindings. + * + * @return array + */ + public function getBindings() + { + return $this->bindings; + } + + /** + * Get the alias for an abstract if available. + * + * @param string $abstract + * @return string + * + * @throws \LogicException + */ + public function getAlias($abstract) + { + if (! isset($this->aliases[$abstract])) { + return $abstract; + } + + if ($this->aliases[$abstract] === $abstract) { + throw new LogicException("[{$abstract}] is aliased to itself."); + } + + return $this->getAlias($this->aliases[$abstract]); + } + + /** + * Get the extender callbacks for a given type. + * + * @param string $abstract + * @return array + */ + protected function getExtenders($abstract) + { + $abstract = $this->getAlias($abstract); + + if (isset($this->extenders[$abstract])) { + return $this->extenders[$abstract]; + } + + return []; + } + + /** + * Remove all of the extender callbacks for a given type. + * + * @param string $abstract + * @return void + */ + public function forgetExtenders($abstract) + { + unset($this->extenders[$this->getAlias($abstract)]); + } + + /** + * Drop all of the stale instances and aliases. + * + * @param string $abstract + * @return void + */ + protected function dropStaleInstances($abstract) + { + unset($this->instances[$abstract], $this->aliases[$abstract]); + } + + /** + * Remove a resolved instance from the instance cache. + * + * @param string $abstract + * @return void + */ + public function forgetInstance($abstract) + { + unset($this->instances[$abstract]); + } + + /** + * Clear all of the instances from the container. + * + * @return void + */ + public function forgetInstances() + { + $this->instances = []; + } + + /** + * Flush the container of all bindings and resolved instances. + * + * @return void + */ + public function flush() + { + $this->aliases = []; + $this->resolved = []; + $this->bindings = []; + $this->instances = []; + $this->abstractAliases = []; + } + + /** + * Set the globally available instance of the container. + * + * @return static + */ + public static function getInstance() + { + if (is_null(static::$instance)) { + static::$instance = new static; + } + + return static::$instance; + } + + /** + * Set the shared instance of the container. + * + * @param \Illuminate\Contracts\Container\Container|null $container + * @return static + */ + public static function setInstance(ContainerContract $container = null) + { + return static::$instance = $container; + } + + /** + * Determine if a given offset exists. + * + * @param string $key + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return $this->bound($key); + } + + /** + * Get the value at a given offset. + * + * @param string $key + * @return mixed + * : mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->make($key); + } + + /** + * Set the value at a given offset. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value): void + { + $this->bind($key, $value instanceof Closure ? $value : function () use ($value) { + return $value; + }); + } + + /** + * Unset the value at a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key): void + { + unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]); + } + + /** + * Dynamically access container services. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this[$key]; + } + + /** + * Dynamically set container services. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this[$key] = $value; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Cookie/CookieValuePrefix.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Cookie/CookieValuePrefix.php new file mode 100644 index 0000000..159ae4a --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Cookie/CookieValuePrefix.php @@ -0,0 +1,50 @@ +encrypter = $encrypter; + } + + /** + * Disable encryption for the given cookie name(s). + * + * @param string|array $cookieName + * @return void + */ + public function disableFor($cookieName) + { + $this->except = array_merge($this->except, (array) $cookieName); + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + return $this->encrypt($next($this->decrypt($request))); + } + + /** + * Decrypt the cookies on the request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\Request + */ + protected function decrypt(Request $request) + { + foreach ($request->cookies as $key => $cookie) { + if ($this->isDisabled($key)) { + continue; + } + + try { + //$request->cookies->set($key, $this->decryptCookie($c)); + $decryptedValue = $this->decryptCookie($cookie); + + $value = CookieValuePrefix::getVerifiedValue($key, $decryptedValue, $this->encrypter->getKey()); + + if (empty($value) && $key === config('session.cookie') && Session::isValidId($decryptedValue)) { + $value = $decryptedValue; + } + + $request->cookies->set($key, $value); + } catch (DecryptException $e) { + $request->cookies->set($key, null); + } + } + + return $request; + } + + /** + * Decrypt the given cookie and return the value. + * + * @param string|array $cookie + * @return string|array + */ + protected function decryptCookie($cookie) + { + return is_array($cookie) + ? $this->decryptArray($cookie) + : $this->encrypter->decrypt($cookie, false); + } + + /** + * Decrypt an array based cookie. + * + * @param array $cookie + * @return array + */ + protected function decryptArray(array $cookie) + { + $decrypted = []; + + foreach ($cookie as $key => $value) { + if (is_string($value)) { + $decrypted[$key] = $this->encrypter->decrypt($value, false); + } + } + + return $decrypted; + } + + /** + * Encrypt the cookies on an outgoing response. + * + * @param \Symfony\Component\HttpFoundation\Response $response + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function encrypt(Response $response) + { + foreach ($response->headers->getCookies() as $cookie) { + if ($this->isDisabled($cookie->getName())) { + continue; + } + + $prefix = ''; + + if ($cookie->getName() !== 'XSRF-TOKEN') { + $prefix = CookieValuePrefix::create($cookie->getName(), $this->encrypter->getKey()); + } + + $response->headers->setCookie($this->duplicate( + $cookie, $this->encrypter->encrypt($prefix.$cookie->getValue(), false) + )); + } + + return $response; + } + + /** + * Duplicate a cookie with a new value. + * + * @param \Symfony\Component\HttpFoundation\Cookie $c + * @param mixed $value + * @return \Symfony\Component\HttpFoundation\Cookie + */ + protected function duplicate(Cookie $c, $value) + { + return new Cookie( + $c->getName(), $value, $c->getExpiresTime(), $c->getPath(), + $c->getDomain(), $c->isSecure(), $c->isHttpOnly(), $c->isRaw(), + $c->getSameSite() + ); + } + + /** + * Determine whether encryption has been disabled for the given cookie. + * + * @param string $name + * @return bool + */ + public function isDisabled($name) + { + return in_array($name, $this->except); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php new file mode 100644 index 0000000..e1f2ced --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php @@ -0,0 +1,226 @@ +fillable; + } + + /** + * Set the fillable attributes for the model. + * + * @param array $fillable + * @return $this + */ + public function fillable(array $fillable) + { + $this->fillable = $fillable; + + return $this; + } + + /** + * Get the guarded attributes for the model. + * + * @return array + */ + public function getGuarded() + { + return $this->guarded; + } + + /** + * Set the guarded attributes for the model. + * + * @param array $guarded + * @return $this + */ + public function guard(array $guarded) + { + $this->guarded = $guarded; + + return $this; + } + + /** + * Disable all mass assignable restrictions. + * + * @param bool $state + * @return void + */ + public static function unguard($state = true) + { + static::$unguarded = $state; + } + + /** + * Enable the mass assignment restrictions. + * + * @return void + */ + public static function reguard() + { + static::$unguarded = false; + } + + /** + * Determine if current state is "unguarded". + * + * @return bool + */ + public static function isUnguarded() + { + return static::$unguarded; + } + + /** + * Run the given callable while being unguarded. + * + * @param callable $callback + * @return mixed + */ + public static function unguarded(callable $callback) + { + if (static::$unguarded) { + return $callback(); + } + + static::unguard(); + + try { + return $callback(); + } finally { + static::reguard(); + } + } + + /** + * Determine if the given attribute may be mass assigned. + * + * @param string $key + * @return bool + */ + public function isFillable($key) + { + if (static::$unguarded) { + return true; + } + + // If the key is in the "fillable" array, we can of course assume that it's + // a fillable attribute. Otherwise, we will check the guarded array when + // we need to determine if the attribute is black-listed on the model. + if (in_array($key, $this->getFillable())) { + return true; + } + + // If the attribute is explicitly listed in the "guarded" array then we can + // return false immediately. This means this attribute is definitely not + // fillable and there is no point in going any further in this method. + if ($this->isGuarded($key)) { + return false; + } + + return empty($this->getFillable()) && + strpos($key, '.') === false && + ! Str::startsWith($key, '_'); + } + + /** + * Determine if the given key is guarded. + * + * @param string $key + * @return bool + */ + public function isGuarded($key) + { + //return in_array($key, $this->getGuarded()) || $this->getGuarded() == ['*']; + + if (empty($this->getGuarded())) { + return false; + } + + return $this->getGuarded() == ['*'] || + ! empty(preg_grep('/^'.preg_quote($key).'$/i', $this->getGuarded())) || + ! $this->isGuardableColumn($key); + } + + /** + * Determine if the given column is a valid, guardable column. + * + * @param string $key + * @return bool + */ + protected function isGuardableColumn($key) + { + if (! isset(static::$guardableColumns[get_class($this)])) { + static::$guardableColumns[get_class($this)] = $this->getConnection() + ->getSchemaBuilder() + ->getColumnListing($this->getTable()); + } + + return in_array($key, static::$guardableColumns[get_class($this)]); + } + + /** + * Determine if the model is totally guarded. + * + * @return bool + */ + public function totallyGuarded() + { + return count($this->getFillable()) == 0 && $this->getGuarded() == ['*']; + } + + /** + * Get the fillable attributes of a given array. + * + * @param array $attributes + * @return array + */ + protected function fillableFromArray(array $attributes) + { + if (count($this->getFillable()) > 0 && ! static::$unguarded) { + return array_intersect_key($attributes, array_flip($this->getFillable())); + } + + return $attributes; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Factory.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Factory.php new file mode 100644 index 0000000..9975a7f --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Factory.php @@ -0,0 +1,256 @@ +faker = $faker; + } + + /** + * Create a new factory container. + * + * @param \Faker\Generator $faker + * @param string|null $pathToFactories + * @return static + */ + public static function construct(Faker $faker, $pathToFactories = null) + { + $pathToFactories = $pathToFactories ?: database_path('factories'); + + return (new static($faker))->load($pathToFactories); + } + + /** + * Define a class with a given short-name. + * + * @param string $class + * @param string $name + * @param callable $attributes + * @return $this + */ + public function defineAs($class, $name, callable $attributes) + { + return $this->define($class, $attributes, $name); + } + + /** + * Define a class with a given set of attributes. + * + * @param string $class + * @param callable $attributes + * @param string $name + * @return $this + */ + public function define($class, callable $attributes, $name = 'default') + { + $this->definitions[$class][$name] = $attributes; + + return $this; + } + + /** + * Define a state with a given set of attributes. + * + * @param string $class + * @param string $state + * @param callable|array $attributes + * @return $this + */ + public function state($class, $state, $attributes) + { + $this->states[$class][$state] = $attributes; + + return $this; + } + + /** + * Create an instance of the given model and persist it to the database. + * + * @param string $class + * @param array $attributes + * @return mixed + */ + public function create($class, array $attributes = []) + { + return $this->of($class)->create($attributes); + } + + /** + * Create an instance of the given model and type and persist it to the database. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return mixed + */ + public function createAs($class, $name, array $attributes = []) + { + return $this->of($class, $name)->create($attributes); + } + + /** + * Create an instance of the given model. + * + * @param string $class + * @param array $attributes + * @return mixed + */ + public function make($class, array $attributes = []) + { + return $this->of($class)->make($attributes); + } + + /** + * Create an instance of the given model and type. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return mixed + */ + public function makeAs($class, $name, array $attributes = []) + { + return $this->of($class, $name)->make($attributes); + } + + /** + * Get the raw attribute array for a given named model. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return array + */ + public function rawOf($class, $name, array $attributes = []) + { + return $this->raw($class, $attributes, $name); + } + + /** + * Get the raw attribute array for a given model. + * + * @param string $class + * @param array $attributes + * @param string $name + * @return array + */ + public function raw($class, array $attributes = [], $name = 'default') + { + return array_merge( + call_user_func($this->definitions[$class][$name], $this->faker), $attributes + ); + } + + /** + * Create a builder for the given model. + * + * @param string $class + * @param string $name + * @return \Illuminate\Database\Eloquent\FactoryBuilder + */ + public function of($class, $name = 'default') + { + return new FactoryBuilder($class, $name, $this->definitions, $this->states, $this->faker); + } + + /** + * Load factories from path. + * + * @param string $path + * @return $this + */ + public function load($path) + { + $factory = $this; + + if (is_dir($path)) { + foreach (Finder::create()->files()->name('*.php')->in($path) as $file) { + require $file->getRealPath(); + } + } + + return $factory; + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * @return bool + */ + public function offsetExists($offset): bool + { + return isset($this->definitions[$offset]); + } + + /** + * Get the value of the given offset. + * + * @param string $offset + * @return mixed + * : mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->make($offset); + } + + /** + * Set the given offset to the given value. + * + * @param string $offset + * @param callable $value + * @return void + */ + public function offsetSet($offset, $value): void + { + //return $this->define($offset, $value); + $this->define($offset, $value); + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset): void + { + unset($this->definitions[$offset]); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Model.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Model.php new file mode 100644 index 0000000..4cb4995 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Database/Eloquent/Model.php @@ -0,0 +1,1509 @@ +bootIfNotBooted(); + + $this->syncOriginal(); + + $this->fill($attributes); + } + + /** + * Check if the model needs to be booted and if so, do it. + * + * @return void + */ + protected function bootIfNotBooted() + { + if (! isset(static::$booted[static::class])) { + static::$booted[static::class] = true; + + $this->fireModelEvent('booting', false); + + static::boot(); + + $this->fireModelEvent('booted', false); + } + } + + /** + * The "booting" method of the model. + * + * @return void + */ + protected static function boot() + { + static::bootTraits(); + } + + /** + * Boot all of the bootable traits on the model. + * + * @return void + */ + protected static function bootTraits() + { + $class = static::class; + + foreach (class_uses_recursive($class) as $trait) { + if (method_exists($class, $method = 'boot'.class_basename($trait))) { + forward_static_call([$class, $method]); + } + } + } + + /** + * Clear the list of booted models so they will be re-booted. + * + * @return void + */ + public static function clearBootedModels() + { + static::$booted = []; + + static::$globalScopes = []; + } + + /** + * Fill the model with an array of attributes. + * + * @param array $attributes + * @return $this + * + * @throws \Illuminate\Database\Eloquent\MassAssignmentException + */ + public function fill(array $attributes) + { + $totallyGuarded = $this->totallyGuarded(); + + foreach ($this->fillableFromArray($attributes) as $key => $value) { + $key = $this->removeTableFromKey($key); + + // The developers may choose to place some attributes in the "fillable" array + // which means only those attributes may be set through mass assignment to + // the model, and all others will just get ignored for security reasons. + if ($this->isFillable($key)) { + $this->setAttribute($key, $value); + } elseif ($totallyGuarded) { + throw new MassAssignmentException($key); + } + } + + return $this; + } + + /** + * Fill the model with an array of attributes. Force mass assignment. + * + * @param array $attributes + * @return $this + */ + public function forceFill(array $attributes) + { + return static::unguarded(function () use ($attributes) { + return $this->fill($attributes); + }); + } + + /** + * Qualify the given column name by the model's table. + * + * @param string $column + * @return string + */ + public function qualifyColumn($column) + { + if (Str::contains($column, '.')) { + return $column; + } + + return $this->getTable().'.'.$column; + } + + /** + * Remove the table name from a given key. + * + * @param string $key + * @return string + */ + protected function removeTableFromKey($key) + { + //return Str::contains($key, '.') ? last(explode('.', $key)) : $key; + return $key; + } + + /** + * Create a new instance of the given model. + * + * @param array $attributes + * @param bool $exists + * @return static + */ + public function newInstance($attributes = [], $exists = false) + { + // This method just provides a convenient way for us to generate fresh model + // instances of this current model. It is particularly useful during the + // hydration of new objects via the Eloquent query builder instances. + $model = new static((array) $attributes); + + $model->exists = $exists; + + $model->setConnection( + $this->getConnectionName() + ); + + return $model; + } + + /** + * Create a new model instance that is existing. + * + * @param array $attributes + * @param string|null $connection + * @return static + */ + public function newFromBuilder($attributes = [], $connection = null) + { + $model = $this->newInstance([], true); + + $model->setRawAttributes((array) $attributes, true); + + $model->setConnection($connection ?: $this->getConnectionName()); + + $model->fireModelEvent('retrieved', false); + + return $model; + } + + /** + * Begin querying the model on a given connection. + * + * @param string|null $connection + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function on($connection = null) + { + // First we will just create a fresh instance of this model, and then we can + // set the connection on the model so that it is be used for the queries + // we execute, as well as being set on each relationship we retrieve. + $instance = new static; + + $instance->setConnection($connection); + + return $instance->newQuery(); + } + + /** + * Begin querying the model on the write connection. + * + * @return \Illuminate\Database\Query\Builder + */ + public static function onWriteConnection() + { + $instance = new static; + + return $instance->newQuery()->useWritePdo(); + } + + /** + * Get all of the models from the database. + * + * @param array|mixed $columns + * @return \Illuminate\Database\Eloquent\Collection|static[] + */ + public static function all($columns = ['*']) + { + return (new static)->newQuery()->get( + is_array($columns) ? $columns : func_get_args() + ); + } + + /** + * Begin querying a model with eager loading. + * + * @param array|string $relations + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public static function with($relations) + { + return (new static)->newQuery()->with( + is_string($relations) ? func_get_args() : $relations + ); + } + + /** + * Eager load relations on the model. + * + * @param array|string $relations + * @return $this + */ + public function load($relations) + { + $query = $this->newQueryWithoutRelationships()->with( + is_string($relations) ? func_get_args() : $relations + ); + + $query->eagerLoadRelations([$this]); + + return $this; + } + + /** + * Eager load relations on the model if they are not already eager loaded. + * + * @param array|string $relations + * @return $this + */ + public function loadMissing($relations) + { + $relations = is_string($relations) ? func_get_args() : $relations; + + return $this->load(array_filter($relations, function ($relation) { + return ! $this->relationLoaded($relation); + })); + } + + /** + * Increment a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @param array $extra + * @return int + */ + protected function increment($column, $amount = 1, array $extra = []) + { + return $this->incrementOrDecrement($column, $amount, $extra, 'increment'); + } + + /** + * Decrement a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @param array $extra + * @return int + */ + protected function decrement($column, $amount = 1, array $extra = []) + { + return $this->incrementOrDecrement($column, $amount, $extra, 'decrement'); + } + + /** + * Run the increment or decrement method on the model. + * + * @param string $column + * @param int $amount + * @param array $extra + * @param string $method + * @return int + */ + protected function incrementOrDecrement($column, $amount, $extra, $method) + { + $query = $this->newQuery(); + + if (! $this->exists) { + return $query->{$method}($column, $amount, $extra); + } + + $this->incrementOrDecrementAttributeValue($column, $amount, $extra, $method); + + return $query->where( + $this->getKeyName(), $this->getKey() + )->{$method}($column, $amount, $extra); + } + + /** + * Increment the underlying attribute value and sync with original. + * + * @param string $column + * @param int $amount + * @param array $extra + * @param string $method + * @return void + */ + protected function incrementOrDecrementAttributeValue($column, $amount, $extra, $method) + { + $this->{$column} = $this->{$column} + ($method == 'increment' ? $amount : $amount * -1); + + $this->forceFill($extra); + + $this->syncOriginalAttribute($column); + } + + /** + * Update the model in the database. + * + * @param array $attributes + * @param array $options + * @return bool + */ + public function update(array $attributes = [], array $options = []) + { + if (! $this->exists) { + return false; + } + + return $this->fill($attributes)->save($options); + } + + /** + * Save the model and all of its relationships. + * + * @return bool + */ + public function push() + { + if (! $this->save()) { + return false; + } + + // To sync all of the relationships to the database, we will simply spin through + // the relationships and save each model via this "push" method, which allows + // us to recurse into all of these nested relations for the model instance. + foreach ($this->relations as $models) { + $models = $models instanceof Collection + ? $models->all() : [$models]; + + foreach (array_filter($models) as $model) { + if (! $model->push()) { + return false; + } + } + } + + return true; + } + + /** + * Save the model to the database. + * + * @param array $options + * @return bool + */ + public function save(array $options = []) + { + $query = $this->newQueryWithoutScopes(); + + // If the "saving" event returns false we'll bail out of the save and return + // false, indicating that the save failed. This provides a chance for any + // listeners to cancel save operations if validations fail or whatever. + if ($this->fireModelEvent('saving') === false) { + return false; + } + + // If the model already exists in the database we can just update our record + // that is already in this database using the current IDs in this "where" + // clause to only update this model. Otherwise, we'll just insert them. + if ($this->exists) { + $saved = $this->isDirty() ? + $this->performUpdate($query) : true; + } + + // If the model is brand new, we'll insert it into our database and set the + // ID attribute on the model to the value of the newly inserted row's ID + // which is typically an auto-increment value managed by the database. + else { + $saved = $this->performInsert($query); + + if (! $this->getConnectionName() && + $connection = $query->getConnection()) { + $this->setConnection($connection->getName()); + } + } + + // If the model is successfully saved, we need to do a few more things once + // that is done. We will call the "saved" method here to run any actions + // we need to happen after a model gets successfully saved right here. + if ($saved) { + $this->finishSave($options); + } + + return $saved; + } + + /** + * Save the model to the database using transaction. + * + * @param array $options + * @return bool + * + * @throws \Throwable + */ + public function saveOrFail(array $options = []) + { + return $this->getConnection()->transaction(function () use ($options) { + return $this->save($options); + }); + } + + /** + * Perform any actions that are necessary after the model is saved. + * + * @param array $options + * @return void + */ + protected function finishSave(array $options) + { + $this->fireModelEvent('saved', false); + + if ($this->isDirty() && ($options['touch'] ?? true)) { + $this->touchOwners(); + } + + $this->syncOriginal(); + } + + /** + * Perform a model update operation. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return bool + */ + protected function performUpdate(Builder $query) + { + // If the updating event returns false, we will cancel the update operation so + // developers can hook Validation systems into their models and cancel this + // operation if the model does not pass validation. Otherwise, we update. + if ($this->fireModelEvent('updating') === false) { + return false; + } + + // First we need to create a fresh query instance and touch the creation and + // update timestamp on the model which are maintained by us for developer + // convenience. Then we will just continue saving the model instances. + if ($this->usesTimestamps()) { + $this->updateTimestamps(); + } + + // Once we have run the update operation, we will fire the "updated" event for + // this model instance. This will allow developers to hook into these after + // models are updated, giving them a chance to do any special processing. + $dirty = $this->getDirty(); + + if (count($dirty) > 0) { + $this->setKeysForSaveQuery($query)->update($dirty); + + $this->fireModelEvent('updated', false); + + $this->syncChanges(); + } + + return true; + } + + /** + * Set the keys for a save update query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSaveQuery(Builder $query) + { + $query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery()); + + return $query; + } + + /** + * Get the primary key value for a save query. + * + * @return mixed + */ + protected function getKeyForSaveQuery() + { + return $this->original[$this->getKeyName()] + ?? $this->getKey(); + } + + /** + * Perform a model insert operation. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return bool + */ + protected function performInsert(Builder $query) + { + if ($this->fireModelEvent('creating') === false) { + return false; + } + + // First we'll need to create a fresh query instance and touch the creation and + // update timestamps on this model, which are maintained by us for developer + // convenience. After, we will just continue saving these model instances. + if ($this->usesTimestamps()) { + $this->updateTimestamps(); + } + + // If the model has an incrementing key, we can use the "insertGetId" method on + // the query builder, which will give us back the final inserted ID for this + // table from the database. Not all tables have to be incrementing though. + $attributes = $this->attributes; + + if ($this->getIncrementing()) { + $this->insertAndSetId($query, $attributes); + } + + // If the table isn't incrementing we'll simply insert these attributes as they + // are. These attribute arrays must contain an "id" column previously placed + // there by the developer as the manually determined key for these models. + else { + if (empty($attributes)) { + return true; + } + + $query->insert($attributes); + } + + // We will go ahead and set the exists property to true, so that it is set when + // the created event is fired, just in case the developer tries to update it + // during the event. This will allow them to do so and run an update here. + $this->exists = true; + + $this->wasRecentlyCreated = true; + + $this->fireModelEvent('created', false); + + return true; + } + + /** + * Insert the given attributes and set the ID on the model. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param array $attributes + * @return void + */ + protected function insertAndSetId(Builder $query, $attributes) + { + $id = $query->insertGetId($attributes, $keyName = $this->getKeyName()); + + $this->setAttribute($keyName, $id); + } + + /** + * Destroy the models for the given IDs. + * + * @param array|int $ids + * @return int + */ + public static function destroy($ids) + { + // We'll initialize a count here so we will return the total number of deletes + // for the operation. The developers can then check this number as a boolean + // type value or get this total count of records deleted for logging, etc. + $count = 0; + + $ids = is_array($ids) ? $ids : func_get_args(); + + // We will actually pull the models from the database table and call delete on + // each of them individually so that their events get fired properly with a + // correct set of attributes in case the developers wants to check these. + $key = ($instance = new static)->getKeyName(); + + foreach ($instance->whereIn($key, $ids)->get() as $model) { + if ($model->delete()) { + $count++; + } + } + + return $count; + } + + /** + * Delete the model from the database. + * + * @return bool|null + * + * @throws \Exception + */ + public function delete() + { + if (is_null($this->getKeyName())) { + throw new Exception('No primary key defined on model.'); + } + + // If the model doesn't exist, there is nothing to delete so we'll just return + // immediately and not do anything else. Otherwise, we will continue with a + // deletion process on the model, firing the proper events, and so forth. + if (! $this->exists) { + return; + } + + if ($this->fireModelEvent('deleting') === false) { + return false; + } + + // Here, we'll touch the owning models, verifying these timestamps get updated + // for the models. This will allow any caching to get broken on the parents + // by the timestamp. Then we will go ahead and delete the model instance. + $this->touchOwners(); + + $this->performDeleteOnModel(); + + // Once the model has been deleted, we will fire off the deleted event so that + // the developers may hook into post-delete operations. We will then return + // a boolean true as the delete is presumably successful on the database. + $this->fireModelEvent('deleted', false); + + return true; + } + + /** + * Force a hard delete on a soft deleted model. + * + * This method protects developers from running forceDelete when trait is missing. + * + * @return bool|null + */ + public function forceDelete() + { + return $this->delete(); + } + + /** + * Perform the actual delete query on this model instance. + * + * @return void + */ + protected function performDeleteOnModel() + { + $this->setKeysForSaveQuery($this->newQueryWithoutScopes())->delete(); + + $this->exists = false; + } + + /** + * Begin querying the model. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function query() + { + return (new static)->newQuery(); + } + + /** + * Get a new query builder for the model's table. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQuery() + { + return $this->registerGlobalScopes($this->newQueryWithoutScopes()); + } + + /** + * Get a new query builder with no relationships loaded. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryWithoutRelationships() + { + return $this->registerGlobalScopes( + $this->newEloquentBuilder($this->newBaseQueryBuilder())->setModel($this) + ); + } + + /** + * Register the global scopes for this builder instance. + * + * @param \Illuminate\Database\Eloquent\Builder $builder + * @return \Illuminate\Database\Eloquent\Builder + */ + public function registerGlobalScopes($builder) + { + foreach ($this->getGlobalScopes() as $identifier => $scope) { + $builder->withGlobalScope($identifier, $scope); + } + + return $builder; + } + + /** + * Get a new query builder that doesn't have any global scopes. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function newQueryWithoutScopes() + { + $builder = $this->newEloquentBuilder($this->newBaseQueryBuilder()); + + // Once we have the query builders, we will set the model instances so the + // builder can easily access any information it may need from the model + // while it is constructing and executing various queries against it. + return $builder->setModel($this) + ->with($this->with) + ->withCount($this->withCount); + } + + /** + * Get a new query instance without a given scope. + * + * @param \Illuminate\Database\Eloquent\Scope|string $scope + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryWithoutScope($scope) + { + $builder = $this->newQuery(); + + return $builder->withoutGlobalScope($scope); + } + + /** + * Get a new query to restore one or more models by their queueable IDs. + * + * @param array|int $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryForRestoration($ids) + { + if (is_array($ids)) { + return $this->newQueryWithoutScopes()->whereIn($this->getQualifiedKeyName(), $ids); + } + + return $this->newQueryWithoutScopes()->whereKey($ids); + } + + /** + * Create a new Eloquent query builder for the model. + * + * @param \Illuminate\Database\Query\Builder $query + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function newEloquentBuilder($query) + { + return new Builder($query); + } + + /** + * Get a new query builder instance for the connection. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function newBaseQueryBuilder() + { + $connection = $this->getConnection(); + + return new QueryBuilder( + $connection, $connection->getQueryGrammar(), $connection->getPostProcessor() + ); + } + + /** + * Create a new Eloquent Collection instance. + * + * @param array $models + * @return \Illuminate\Database\Eloquent\Collection + */ + public function newCollection(array $models = []) + { + return new Collection($models); + } + + /** + * Create a new pivot model instance. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @param array $attributes + * @param string $table + * @param bool $exists + * @param string|null $using + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newPivot(self $parent, array $attributes, $table, $exists, $using = null) + { + return $using ? $using::fromRawAttributes($parent, $attributes, $table, $exists) + : Pivot::fromAttributes($parent, $attributes, $table, $exists); + } + + /** + * Convert the model instance to an array. + * + * @return array + */ + public function toArray() + { + return array_merge($this->attributesToArray(), $this->relationsToArray()); + } + + /** + * Convert the model instance to JSON. + * + * @param int $options + * @return string + * + * @throws \Illuminate\Database\Eloquent\JsonEncodingException + */ + public function toJson($options = 0) + { + $json = json_encode($this->jsonSerialize(), $options); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw JsonEncodingException::forModel($this, json_last_error_msg()); + } + + return $json; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + * : mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Reload a fresh model instance from the database. + * + * @param array|string $with + * @return static|null + */ + public function fresh($with = []) + { + if (! $this->exists) { + return; + } + + return static::newQueryWithoutScopes() + ->with(is_string($with) ? func_get_args() : $with) + ->where($this->getKeyName(), $this->getKey()) + ->first(); + } + + /** + * Reload the current model instance with fresh attributes from the database. + * + * @return $this + */ + public function refresh() + { + if (! $this->exists) { + return $this; + } + + $this->setRawAttributes( + static::newQueryWithoutScopes()->findOrFail($this->getKey())->attributes + ); + + $this->load(collect($this->relations)->except('pivot')->keys()->toArray()); + + return $this; + } + + /** + * Clone the model into a new, non-existing instance. + * + * @param array|null $except + * @return \Illuminate\Database\Eloquent\Model + */ + public function replicate(array $except = null) + { + $defaults = [ + $this->getKeyName(), + $this->getCreatedAtColumn(), + $this->getUpdatedAtColumn(), + ]; + + $attributes = Arr::except( + $this->attributes, $except ? array_unique(array_merge($except, $defaults)) : $defaults + ); + + return tap(new static, function ($instance) use ($attributes) { + $instance->setRawAttributes($attributes); + + $instance->setRelations($this->relations); + }); + } + + /** + * Determine if two models have the same ID and belong to the same table. + * + * @param \Illuminate\Database\Eloquent\Model|null $model + * @return bool + */ + public function is($model) + { + return ! is_null($model) && + $this->getKey() === $model->getKey() && + $this->getTable() === $model->getTable() && + $this->getConnectionName() === $model->getConnectionName(); + } + + /** + * Determine if two models are not the same. + * + * @param \Illuminate\Database\Eloquent\Model|null $model + * @return bool + */ + public function isNot($model) + { + return ! $this->is($model); + } + + /** + * Get the database connection for the model. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return static::resolveConnection($this->getConnectionName()); + } + + /** + * Get the current connection name for the model. + * + * @return string + */ + public function getConnectionName() + { + return $this->connection; + } + + /** + * Set the connection associated with the model. + * + * @param string $name + * @return $this + */ + public function setConnection($name) + { + $this->connection = $name; + + return $this; + } + + /** + * Resolve a connection instance. + * + * @param string|null $connection + * @return \Illuminate\Database\Connection + */ + public static function resolveConnection($connection = null) + { + return static::$resolver->connection($connection); + } + + /** + * Get the connection resolver instance. + * + * @return \Illuminate\Database\ConnectionResolverInterface + */ + public static function getConnectionResolver() + { + return static::$resolver; + } + + /** + * Set the connection resolver instance. + * + * @param \Illuminate\Database\ConnectionResolverInterface $resolver + * @return void + */ + public static function setConnectionResolver(Resolver $resolver) + { + static::$resolver = $resolver; + } + + /** + * Unset the connection resolver for models. + * + * @return void + */ + public static function unsetConnectionResolver() + { + static::$resolver = null; + } + + /** + * Get the table associated with the model. + * + * @return string + */ + public function getTable() + { + if (! isset($this->table)) { + return str_replace( + '\\', '', Str::snake(Str::plural(class_basename($this))) + ); + } + + return $this->table; + } + + /** + * Set the table associated with the model. + * + * @param string $table + * @return $this + */ + public function setTable($table) + { + $this->table = $table; + + return $this; + } + + /** + * Get the primary key for the model. + * + * @return string + */ + public function getKeyName() + { + return $this->primaryKey; + } + + /** + * Set the primary key for the model. + * + * @param string $key + * @return $this + */ + public function setKeyName($key) + { + $this->primaryKey = $key; + + return $this; + } + + /** + * Get the table qualified key name. + * + * @return string + */ + public function getQualifiedKeyName() + { + return $this->qualifyColumn($this->getKeyName()); + } + + /** + * Get the auto-incrementing key type. + * + * @return string + */ + public function getKeyType() + { + return $this->keyType; + } + + /** + * Set the data type for the primary key. + * + * @param string $type + * @return $this + */ + public function setKeyType($type) + { + $this->keyType = $type; + + return $this; + } + + /** + * Get the value indicating whether the IDs are incrementing. + * + * @return bool + */ + public function getIncrementing() + { + return $this->incrementing; + } + + /** + * Set whether IDs are incrementing. + * + * @param bool $value + * @return $this + */ + public function setIncrementing($value) + { + $this->incrementing = $value; + + return $this; + } + + /** + * Get the value of the model's primary key. + * + * @return mixed + */ + public function getKey() + { + return $this->getAttribute($this->getKeyName()); + } + + /** + * Get the queueable identity for the entity. + * + * @return mixed + */ + public function getQueueableId() + { + return $this->getKey(); + } + + /** + * Get the queueable connection for the entity. + * + * @return mixed + */ + public function getQueueableConnection() + { + return $this->getConnectionName(); + } + + /** + * Get the value of the model's route key. + * + * @return mixed + */ + public function getRouteKey() + { + return $this->getAttribute($this->getRouteKeyName()); + } + + /** + * Get the route key for the model. + * + * @return string + */ + public function getRouteKeyName() + { + return $this->getKeyName(); + } + + /** + * Retrieve the model for a bound value. + * + * @param mixed $value + * @return \Illuminate\Database\Eloquent\Model|null + */ + public function resolveRouteBinding($value) + { + return $this->where($this->getRouteKeyName(), $value)->first(); + } + + /** + * Get the default foreign key name for the model. + * + * @return string + */ + public function getForeignKey() + { + return Str::snake(class_basename($this)).'_'.$this->primaryKey; + } + + /** + * Get the number of models to return per page. + * + * @return int + */ + public function getPerPage() + { + return $this->perPage; + } + + /** + * Set the number of models to return per page. + * + * @param int $perPage + * @return $this + */ + public function setPerPage($perPage) + { + $this->perPage = $perPage; + + return $this; + } + + /** + * Dynamically retrieve attributes on the model. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->getAttribute($key); + } + + /** + * Dynamically set attributes on the model. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->setAttribute($key, $value); + } + + /** + * Determine if the given attribute exists. + * + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset): bool + { + return ! is_null($this->getAttribute($offset)); + } + + /** + * Get the value for a given offset. + * + * @param mixed $offset + * @return mixed + * : mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->getAttribute($offset); + } + + /** + * Set the value for a given offset. + * + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value): void + { + $this->setAttribute($offset, $value); + } + + /** + * Unset the value for a given offset. + * + * @param mixed $offset + * @return void + */ + public function offsetUnset($offset): void + { + unset($this->attributes[$offset], $this->relations[$offset]); + } + + /** + * Determine if an attribute or relation exists on the model. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return $this->offsetExists($key); + } + + /** + * Unset an attribute on the model. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + $this->offsetUnset($key); + } + + /** + * Handle dynamic method calls into the model. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (in_array($method, ['increment', 'decrement'])) { + return $this->$method(...$parameters); + } + + return $this->newQuery()->$method(...$parameters); + } + + /** + * Handle dynamic static method calls into the method. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + return (new static)->$method(...$parameters); + } + + /** + * Convert the model to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * When a model is being unserialized, check if it needs to be booted. + * + * @return void + */ + public function __wakeup() + { + $this->bootIfNotBooted(); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Filesystem/Filesystem.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Filesystem/Filesystem.php new file mode 100644 index 0000000..2798213 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Filesystem/Filesystem.php @@ -0,0 +1,567 @@ +isFile($path)) { + return $lock ? $this->sharedGet($path) : file_get_contents($path); + } + + throw new FileNotFoundException("File does not exist at path {$path}"); + } + + /** + * Get contents of a file with shared access. + * + * @param string $path + * @return string + */ + public function sharedGet($path) + { + $contents = ''; + + $handle = fopen($path, 'rb'); + + if ($handle) { + try { + if (flock($handle, LOCK_SH)) { + clearstatcache(true, $path); + + $contents = fread($handle, $this->size($path) ?: 1); + + flock($handle, LOCK_UN); + } + } finally { + fclose($handle); + } + } + + return $contents; + } + + /** + * Get the returned value of a file. + * + * @param string $path + * @return mixed + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + public function getRequire($path) + { + if ($this->isFile($path)) { + return require $path; + } + + throw new FileNotFoundException("File does not exist at path {$path}"); + } + + /** + * Require the given file once. + * + * @param string $file + * @return mixed + */ + public function requireOnce($file) + { + require_once $file; + } + + /** + * Get the MD5 hash of the file at the given path. + * + * @param string $path + * @return string + */ + public function hash($path) + { + return md5_file($path); + } + + /** + * Write the contents of a file. + * + * @param string $path + * @param string $contents + * @param bool $lock + * @return int + */ + public function put($path, $contents, $lock = false) + { + return file_put_contents($path, $contents, $lock ? LOCK_EX : 0); + } + + /** + * Prepend to a file. + * + * @param string $path + * @param string $data + * @return int + */ + public function prepend($path, $data) + { + if ($this->exists($path)) { + return $this->put($path, $data.$this->get($path)); + } + + return $this->put($path, $data); + } + + /** + * Append to a file. + * + * @param string $path + * @param string $data + * @return int + */ + public function append($path, $data) + { + return file_put_contents($path, $data, FILE_APPEND); + } + + /** + * Get or set UNIX mode of a file or directory. + * + * @param string $path + * @param int $mode + * @return mixed + */ + public function chmod($path, $mode = null) + { + if ($mode) { + return chmod($path, $mode); + } + + return substr(sprintf('%o', fileperms($path)), -4); + } + + /** + * Delete the file at a given path. + * + * @param string|array $paths + * @return bool + */ + public function delete($paths) + { + $paths = is_array($paths) ? $paths : func_get_args(); + + $success = true; + + foreach ($paths as $path) { + try { + if (! @unlink($path)) { + $success = false; + } + } catch (ErrorException $e) { + $success = false; + } + } + + return $success; + } + + /** + * Move a file to a new location. + * + * @param string $path + * @param string $target + * @return bool + */ + public function move($path, $target) + { + return rename($path, $target); + } + + /** + * Copy a file to a new location. + * + * @param string $path + * @param string $target + * @return bool + */ + public function copy($path, $target) + { + return copy($path, $target); + } + + /** + * Create a hard link to the target file or directory. + * + * @param string $target + * @param string $link + * @return void + */ + public function link($target, $link) + { + if (! windows_os()) { + return symlink($target, $link); + } + + $mode = $this->isDirectory($target) ? 'J' : 'H'; + + // OS Command Injection issue is already fixed. + exec("mklink /{$mode} ".escapeshellarg($link)." ".escapeshellarg($target)); + } + + /** + * Extract the file name from a file path. + * + * @param string $path + * @return string + */ + public function name($path) + { + return pathinfo($path, PATHINFO_FILENAME); + } + + /** + * Extract the trailing name component from a file path. + * + * @param string $path + * @return string + */ + public function basename($path) + { + return pathinfo($path, PATHINFO_BASENAME); + } + + /** + * Extract the parent directory from a file path. + * + * @param string $path + * @return string + */ + public function dirname($path) + { + return pathinfo($path, PATHINFO_DIRNAME); + } + + /** + * Extract the file extension from a file path. + * + * @param string $path + * @return string + */ + public function extension($path) + { + return pathinfo($path, PATHINFO_EXTENSION); + } + + /** + * Get the file type of a given file. + * + * @param string $path + * @return string + */ + public function type($path) + { + return filetype($path); + } + + /** + * Get the mime-type of a given file. + * + * @param string $path + * @return string|false + */ + public function mimeType($path) + { + return finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path); + } + + /** + * Get the file size of a given file. + * + * @param string $path + * @return int + */ + public function size($path) + { + return filesize($path); + } + + /** + * Get the file's last modification time. + * + * @param string $path + * @return int + */ + public function lastModified($path) + { + return filemtime($path); + } + + /** + * Determine if the given path is a directory. + * + * @param string $directory + * @return bool + */ + public function isDirectory($directory) + { + return is_dir($directory); + } + + /** + * Determine if the given path is readable. + * + * @param string $path + * @return bool + */ + public function isReadable($path) + { + return is_readable($path); + } + + /** + * Determine if the given path is writable. + * + * @param string $path + * @return bool + */ + public function isWritable($path) + { + return is_writable($path); + } + + /** + * Determine if the given path is a file. + * + * @param string $file + * @return bool + */ + public function isFile($file) + { + return is_file($file); + } + + /** + * Find path names matching a given pattern. + * + * @param string $pattern + * @param int $flags + * @return array + */ + public function glob($pattern, $flags = 0) + { + return glob($pattern, $flags); + } + + /** + * Get an array of all files in a directory. + * + * @param string $directory + * @param bool $hidden + * @return \Symfony\Component\Finder\SplFileInfo[] + */ + public function files($directory, $hidden = false) + { + return iterator_to_array( + Finder::create()->files()->ignoreDotFiles(! $hidden)->in($directory)->depth(0), + false + ); + } + + /** + * Get all of the files from the given directory (recursive). + * + * @param string $directory + * @param bool $hidden + * @return \Symfony\Component\Finder\SplFileInfo[] + */ + public function allFiles($directory, $hidden = false) + { + return iterator_to_array( + Finder::create()->files()->ignoreDotFiles(! $hidden)->in($directory), + false + ); + } + + /** + * Get all of the directories within a given directory. + * + * @param string $directory + * @return array + */ + public function directories($directory) + { + $directories = []; + + foreach (Finder::create()->in($directory)->directories()->depth(0) as $dir) { + $directories[] = $dir->getPathname(); + } + + return $directories; + } + + /** + * Create a directory. + * + * @param string $path + * @param int $mode + * @param bool $recursive + * @param bool $force + * @return bool + */ + public function makeDirectory($path, $mode = 0755, $recursive = false, $force = false) + { + if ($force) { + return @mkdir($path, $mode, $recursive); + } + + return mkdir($path, $mode, $recursive); + } + + /** + * Move a directory. + * + * @param string $from + * @param string $to + * @param bool $overwrite + * @return bool + */ + public function moveDirectory($from, $to, $overwrite = false) + { + if ($overwrite && $this->isDirectory($to)) { + if (! $this->deleteDirectory($to)) { + return false; + } + } + + return @rename($from, $to) === true; + } + + /** + * Copy a directory from one location to another. + * + * @param string $directory + * @param string $destination + * @param int $options + * @return bool + */ + public function copyDirectory($directory, $destination, $options = null) + { + if (! $this->isDirectory($directory)) { + return false; + } + + $options = $options ?: FilesystemIterator::SKIP_DOTS; + + // If the destination directory does not actually exist, we will go ahead and + // create it recursively, which just gets the destination prepared to copy + // the files over. Once we make the directory we'll proceed the copying. + if (! $this->isDirectory($destination)) { + $this->makeDirectory($destination, 0777, true); + } + + $items = new FilesystemIterator($directory, $options); + + foreach ($items as $item) { + // As we spin through items, we will check to see if the current file is actually + // a directory or a file. When it is actually a directory we will need to call + // back into this function recursively to keep copying these nested folders. + $target = $destination.'/'.$item->getBasename(); + + if ($item->isDir()) { + $path = $item->getPathname(); + + if (! $this->copyDirectory($path, $target, $options)) { + return false; + } + } + + // If the current items is just a regular file, we will just copy this to the new + // location and keep looping. If for some reason the copy fails we'll bail out + // and return false, so the developer is aware that the copy process failed. + else { + if (! $this->copy($item->getPathname(), $target)) { + return false; + } + } + } + + return true; + } + + /** + * Recursively delete a directory. + * + * The directory itself may be optionally preserved. + * + * @param string $directory + * @param bool $preserve + * @return bool + */ + public function deleteDirectory($directory, $preserve = false) + { + if (! $this->isDirectory($directory)) { + return false; + } + + $items = new FilesystemIterator($directory); + + foreach ($items as $item) { + // If the item is a directory, we can just recurse into the function and + // delete that sub-directory otherwise we'll just delete the file and + // keep iterating through each file until the directory is cleaned. + if ($item->isDir() && ! $item->isLink()) { + $this->deleteDirectory($item->getPathname()); + } + + // If the item is just a file, we can go ahead and delete it since we're + // just looping through and waxing all of the files in this directory + // and calling directories recursively, so we delete the real path. + else { + $this->delete($item->getPathname()); + } + } + + if (! $preserve) { + @rmdir($directory); + } + + return true; + } + + /** + * Empty the specified directory of all files and folders. + * + * @param string $directory + * @return bool + */ + public function cleanDirectory($directory) + { + return $this->deleteDirectory($directory, true); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php new file mode 100644 index 0000000..89a5501 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php @@ -0,0 +1,167 @@ +app = $app; + $this->encrypter = $encrypter; + } + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + * + * @throws \Illuminate\Session\TokenMismatchException + */ + public function handle($request, Closure $next) + { + if ( + $this->isReading($request) || + $this->runningUnitTests() || + $this->inExceptArray($request) || + $this->tokensMatch($request) + ) { + return $this->addCookieToResponse($request, $next($request)); + } + + throw new TokenMismatchException; + } + + /** + * Determine if the HTTP request uses a ‘read’ verb. + * + * @param \Illuminate\Http\Request $request + * @return bool + */ + protected function isReading($request) + { + return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']); + } + + /** + * Determine if the application is running unit tests. + * + * @return bool + */ + protected function runningUnitTests() + { + return $this->app->runningInConsole() && $this->app->runningUnitTests(); + } + + /** + * Determine if the request has a URI that should pass through CSRF verification. + * + * @param \Illuminate\Http\Request $request + * @return bool + */ + protected function inExceptArray($request) + { + foreach ($this->except as $except) { + if ($except !== '/') { + $except = trim($except, '/'); + } + + if ($request->fullUrlIs($except) || $request->is($except)) { + return true; + } + } + + return false; + } + + /** + * Determine if the session and input CSRF tokens match. + * + * @param \Illuminate\Http\Request $request + * @return bool + */ + protected function tokensMatch($request) + { + $token = $this->getTokenFromRequest($request); + + return is_string($request->session()->token()) && + is_string($token) && + hash_equals($request->session()->token(), $token); + } + + /** + * Get the CSRF token from the request. + * + * @param \Illuminate\Http\Request $request + * @return string + */ + protected function getTokenFromRequest($request) + { + $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN'); + + if (! $token && $header = $request->header('X-XSRF-TOKEN')) { + $token = $this->encrypter->decrypt($header, false); + } + + return $token; + } + + /** + * Add the CSRF token to the response cookies. + * + * @param \Illuminate\Http\Request $request + * @param \Symfony\Component\HttpFoundation\Response $response + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function addCookieToResponse($request, $response) + { + $config = config('session'); + + $response->headers->setCookie( + new Cookie( + 'XSRF-TOKEN', $request->session()->token(), $this->availableAt(60 * $config['lifetime']), + $config['path'], $config['domain'], $config['secure'], false, false, $config['same_site'] ?? null + ) + ); + + return $response; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/PackageManifest.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/PackageManifest.php new file mode 100644 index 0000000..2cde235 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/PackageManifest.php @@ -0,0 +1,174 @@ +files = $files; + $this->basePath = $basePath; + $this->manifestPath = $manifestPath; + $this->vendorPath = $basePath.'/vendor'; + } + + /** + * Get all of the service provider class names for all packages. + * + * @return array + */ + public function providers() + { + return collect($this->getManifest())->flatMap(function ($configuration) { + return (array) ($configuration['providers'] ?? []); + })->filter()->all(); + } + + /** + * Get all of the aliases for all packages. + * + * @return array + */ + public function aliases() + { + return collect($this->getManifest())->flatMap(function ($configuration) { + return (array) ($configuration['aliases'] ?? []); + })->filter()->all(); + } + + /** + * Get the current package manifest. + * + * @return array + */ + protected function getManifest() + { + if (! is_null($this->manifest)) { + return $this->manifest; + } + + if (! file_exists($this->manifestPath)) { + $this->build(); + } + + return $this->manifest = file_exists($this->manifestPath) ? + $this->files->getRequire($this->manifestPath) : []; + } + + /** + * Build the manifest and write it to disk. + * + * @return void + */ + public function build() + { + $packages = []; + + if ($this->files->exists($path = $this->vendorPath.'/composer/installed.json')) { + $packages = json_decode($this->files->get($path), true); + } + + $ignoreAll = in_array('*', $ignore = $this->packagesToIgnore()); + if (!empty($packages['packages'])) { + $packages = $packages['packages']; + } + $this->write(collect($packages)->mapWithKeys(function ($package) { + return [$this->format($package['name']) => $package['extra']['laravel'] ?? []]; + })->each(function ($configuration) use (&$ignore) { + $ignore = array_merge($ignore, $configuration['dont-discover'] ?? []); + })->reject(function ($configuration, $package) use ($ignore, $ignoreAll) { + return $ignoreAll || in_array($package, $ignore); + })->filter()->all()); + } + + /** + * Format the given package name. + * + * @param string $package + * @return string + */ + protected function format($package) + { + return str_replace($this->vendorPath.'/', '', $package); + } + + /** + * Get all of the package names that should be ignored. + * + * @return array + */ + protected function packagesToIgnore() + { + if (! file_exists($this->basePath.'/composer.json')) { + return []; + } + + return json_decode(file_get_contents( + $this->basePath.'/composer.json' + ), true)['extra']['laravel']['dont-discover'] ?? []; + } + + /** + * Write the given manifest array to disk. + * + * @param array $manifest + * @return void + * @throws \Exception + */ + protected function write(array $manifest) + { + if (! is_writable(dirname($this->manifestPath))) { + throw new Exception('The '.dirname($this->manifestPath).' directory must be present and writable.'); + } + + $this->files->put( + $this->manifestPath, 'app = $app; + $this->files = $files; + $this->manifestPath = $manifestPath; + } + + /** + * Register the application service providers. + * + * @param array $providers + * + * @return void + */ + public function load(array $providers) + { + $manifest = $this->loadManifest(); + + // First we will load the service manifest, which contains information on all + // service providers registered with the application and which services it + // provides. This is used to know which services are "deferred" loaders. + if ($this->shouldRecompile($manifest, $providers)) { + $manifest = $this->compileManifest($providers); + } + + // Next, we will register events to load the providers for each of the events + // that it has requested. This allows the service provider to defer itself + // while still getting automatically loaded when a certain event occurs. + foreach ($manifest['when'] as $provider => $events) { + $this->registerLoadEvents($provider, $events); + } + + // We will go ahead and register all of the eagerly loaded providers with the + // application so their services can be registered with the application as + // a provided service. Then we will set the deferred service list on it. + foreach ($manifest['eager'] as $i => $provider) { + // Application config is cached with `config:cache`. + // If we remove some service provider file from app.php and from disk, + // when updating the app, users will receive "Class '...' not found" error, + // because their cached config still has this service provider listed. + try { + $this->app->register($provider); + } catch (\Throwable $e) { + preg_match("/Class '([^']+)' not found/", $e->getMessage(), $matches); + + if (empty($matches[1])) { + throw $e; + } + $provider_name = $matches[1]; + // Read app.php and check if service provider is listed there, + // if not listed, we can ignore the exception. + if (!$this->appConfig) { + $this->appConfig = include base_path().DIRECTORY_SEPARATOR.'config/app.php'; + } + + if (!in_array($provider_name, $this->appConfig['providers'])) { + // Just log the error + // After cache will be cleared, problem will go away + \Log::error($e->getMessage()); + unset($manifest['eager'][$i]); + continue; + } else { + throw $e; + } + } + } + + $this->app->addDeferredServices($manifest['deferred']); + } + + /** + * Load the service provider manifest JSON file. + * + * @return array|null + */ + public function loadManifest() + { + // The service manifest is a file containing a JSON representation of every + // service provided by the application and whether its provider is using + // deferred loading or should be eagerly loaded on each request to us. + if ($this->files->exists($this->manifestPath)) { + $manifest = $this->files->getRequire($this->manifestPath); + + if ($manifest) { + return array_merge(['when' => []], $manifest); + } + } + } + + /** + * Determine if the manifest should be compiled. + * + * @param array $manifest + * @param array $providers + * + * @return bool + */ + public function shouldRecompile($manifest, $providers) + { + return is_null($manifest) || $manifest['providers'] != $providers; + } + + /** + * Register the load events for the given provider. + * + * @param string $provider + * @param array $events + * + * @return void + */ + protected function registerLoadEvents($provider, array $events) + { + if (count($events) < 1) { + return; + } + + $this->app->make('events')->listen($events, function () use ($provider) { + $this->app->register($provider); + }); + } + + /** + * Compile the application service manifest file. + * + * @param array $providers + * + * @return array + */ + protected function compileManifest($providers) + { + // The service manifest should contain a list of all of the providers for + // the application so we can compare it on each request to the service + // and determine if the manifest should be recompiled or is current. + $manifest = $this->freshManifest($providers); + + foreach ($providers as $i => $provider) { + // Application config is cached with `config:cache`. + // If we remove some service provider file from app.php and from disk, + // when updating the app, users will receive "Class '...' not found" error, + // because their cached config still has this service provider listed. + try { + $instance = $this->createProvider($provider); + } catch (\Throwable $e) { + preg_match("/Class '([^']+)' not found/", $e->getMessage(), $matches); + + if (empty($matches[1])) { + throw $e; + } + $provider_name = $matches[1]; + // Read app.php and check if service provider is listed there, + // if not listed, we can ignore the exception. + if (!$this->appConfig) { + $this->appConfig = include base_path().DIRECTORY_SEPARATOR.'config/app.php'; + } + + if (!in_array($provider_name, $this->appConfig['providers'])) { + // Just log the error + // After cache will be cleared, problem will go away + \Log::error($e->getMessage()); + unset($providers[$i]); + continue; + } else { + throw $e; + } + } + + // When recompiling the service manifest, we will spin through each of the + // providers and check if it's a deferred provider or not. If so we'll + // add it's provided services to the manifest and note the provider. + if ($instance->isDeferred()) { + foreach ($instance->provides() as $service) { + $manifest['deferred'][$service] = $provider; + } + + $manifest['when'][$provider] = $instance->when(); + } + + // If the service providers are not deferred, we will simply add it to an + // array of eagerly loaded providers that will get registered on every + // request to this application instead of "lazy" loading every time. + else { + $manifest['eager'][] = $provider; + } + } + + return $this->writeManifest($manifest); + } + + /** + * Create a fresh service manifest data structure. + * + * @param array $providers + * + * @return array + */ + protected function freshManifest(array $providers) + { + return ['providers' => $providers, 'eager' => [], 'deferred' => []]; + } + + /** + * Write the service manifest file to disk. + * + * @param array $manifest + * + * @throws \Exception + * + * @return array + */ + public function writeManifest($manifest) + { + if (!is_writable(dirname($this->manifestPath))) { + throw new Exception('The bootstrap/cache directory must be present and writable: '.$this->manifestPath); + } + + $this->files->put( + $this->manifestPath, ' []], $manifest); + } + + /** + * Create a new provider instance. + * + * @param string $provider + * + * @return \Illuminate\Support\ServiceProvider + */ + public function createProvider($provider) + { + return new $provider($this->app); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php new file mode 100644 index 0000000..d7d8248 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php @@ -0,0 +1,200 @@ +app) { + $this->refreshApplication(); + } + + $this->setUpTraits(); + + foreach ($this->afterApplicationCreatedCallbacks as $callback) { + call_user_func($callback); + } + + Facade::clearResolvedInstances(); + + Model::setEventDispatcher($this->app['events']); + + $this->setUpHasRun = true; + } + + /** + * Refresh the application instance. + * + * @return void + */ + protected function refreshApplication() + { + $this->app = $this->createApplication(); + } + + /** + * Boot the testing helper traits. + * + * @return array + */ + protected function setUpTraits() + { + $uses = array_flip(class_uses_recursive(static::class)); + + if (isset($uses[RefreshDatabase::class])) { + $this->refreshDatabase(); + } + + if (isset($uses[DatabaseMigrations::class])) { + $this->runDatabaseMigrations(); + } + + if (isset($uses[DatabaseTransactions::class])) { + $this->beginDatabaseTransaction(); + } + + if (isset($uses[WithoutMiddleware::class])) { + $this->disableMiddlewareForAllTests(); + } + + if (isset($uses[WithoutEvents::class])) { + $this->disableEventsForAllTests(); + } + + if (isset($uses[WithFaker::class])) { + $this->setUpFaker(); + } + + return $uses; + } + + /** + * Clean up the testing environment before the next test. + * + * @return void + */ + protected function tearDown() : void + { + if ($this->app) { + foreach ($this->beforeApplicationDestroyedCallbacks as $callback) { + call_user_func($callback); + } + + $this->app->flush(); + + $this->app = null; + } + + $this->setUpHasRun = false; + + if (property_exists($this, 'serverVariables')) { + $this->serverVariables = []; + } + + if (property_exists($this, 'defaultHeaders')) { + $this->defaultHeaders = []; + } + + if (class_exists('Mockery')) { + if ($container = Mockery::getContainer()) { + $this->addToAssertionCount($container->mockery_getExpectationCount()); + } + + Mockery::close(); + } + + if (class_exists(Carbon::class)) { + Carbon::setTestNow(); + } + + $this->afterApplicationCreatedCallbacks = []; + $this->beforeApplicationDestroyedCallbacks = []; + + Artisan::forgetBootstrappers(); + } + + /** + * Register a callback to be run after the application is created. + * + * @param callable $callback + * @return void + */ + public function afterApplicationCreated(callable $callback) + { + $this->afterApplicationCreatedCallbacks[] = $callback; + + if ($this->setUpHasRun) { + call_user_func($callback); + } + } + + /** + * Register a callback to be run before the application is destroyed. + * + * @param callable $callback + * @return void + */ + protected function beforeApplicationDestroyed(callable $callback) + { + $this->beforeApplicationDestroyedCallbacks[] = $callback; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Http/Request.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Http/Request.php new file mode 100644 index 0000000..316a485 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Http/Request.php @@ -0,0 +1,626 @@ +getMethod(); + } + + /** + * Get the root URL for the application. + * + * @return string + */ + public function root() + { + return rtrim($this->getSchemeAndHttpHost().$this->getBaseUrl(), '/'); + } + + /** + * Get the URL (no query string) for the request. + * + * @return string + */ + public function url() + { + return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/'); + } + + /** + * Get the full URL for the request. + * + * @return string + */ + public function fullUrl() + { + $query = $this->getQueryString(); + + $question = $this->getBaseUrl().$this->getPathInfo() == '/' ? '/?' : '?'; + + return $query ? $this->url().$question.$query : $this->url(); + } + + /** + * Get the full URL for the request with the added query string parameters. + * + * @param array $query + * @return string + */ + public function fullUrlWithQuery(array $query) + { + $question = $this->getBaseUrl().$this->getPathInfo() == '/' ? '/?' : '?'; + + return count($this->query()) > 0 + ? $this->url().$question.http_build_query(array_merge($this->query(), $query)) + : $this->fullUrl().$question.http_build_query($query); + } + + /** + * Get the current path info for the request. + * + * @return string + */ + public function path() + { + $pattern = trim($this->getPathInfo(), '/'); + + return $pattern == '' ? '/' : $pattern; + } + + /** + * Get the current decoded path info for the request. + * + * @return string + */ + public function decodedPath() + { + return rawurldecode($this->path()); + } + + /** + * Get a segment from the URI (1 based index). + * + * @param int $index + * @param string|null $default + * @return string|null + */ + public function segment($index, $default = null) + { + return Arr::get($this->segments(), $index - 1, $default); + } + + /** + * Get all of the segments for the request path. + * + * @return array + */ + public function segments() + { + $segments = explode('/', $this->decodedPath()); + + return array_values(array_filter($segments, function ($value) { + return $value !== ''; + })); + } + + /** + * Determine if the current request URI matches a pattern. + * + * @param dynamic $patterns + * @return bool + */ + public function is(...$patterns) + { + foreach ($patterns as $pattern) { + if (Str::is($pattern, $this->decodedPath())) { + return true; + } + } + + return false; + } + + /** + * Determine if the route name matches a given pattern. + * + * @param dynamic $patterns + * @return bool + */ + public function routeIs(...$patterns) + { + return $this->route() && $this->route()->named(...$patterns); + } + + /** + * Determine if the current request URL and query string matches a pattern. + * + * @param dynamic $patterns + * @return bool + */ + public function fullUrlIs(...$patterns) + { + $url = $this->fullUrl(); + + foreach ($patterns as $pattern) { + if (Str::is($pattern, $url)) { + return true; + } + } + + return false; + } + + /** + * Determine if the request is the result of an AJAX call. + * + * @return bool + */ + public function ajax() + { + return $this->isXmlHttpRequest(); + } + + /** + * Determine if the request is the result of an PJAX call. + * + * @return bool + */ + public function pjax() + { + return $this->headers->get('X-PJAX') == true; + } + + /** + * Determine if the request is over HTTPS. + * + * @return bool + */ + public function secure() + { + return $this->isSecure(); + } + + /** + * Get the client IP address. + * + * @return string + */ + public function ip() + { + return $this->getClientIp(); + } + + /** + * Get the client IP addresses. + * + * @return array + */ + public function ips() + { + return $this->getClientIps(); + } + + /** + * Get the client user agent. + * + * @return string + */ + public function userAgent() + { + return $this->headers->get('User-Agent'); + } + + /** + * Merge new input into the current request's input array. + * + * @param array $input + * @return \Illuminate\Http\Request + */ + public function merge(array $input) + { + $this->getInputSource()->add($input); + + return $this; + } + + /** + * Replace the input for the current request. + * + * @param array $input + * @return \Illuminate\Http\Request + */ + public function replace(array $input) + { + $this->getInputSource()->replace($input); + + return $this; + } + + /** + * Get the JSON payload for the request. + * + * @param string $key + * @param mixed $default + * @return \Symfony\Component\HttpFoundation\ParameterBag|mixed + */ + public function json($key = null, $default = null) + { + if (! isset($this->json)) { + $this->json = new ParameterBag((array) json_decode($this->getContent(), true)); + } + + if (is_null($key)) { + return $this->json; + } + + return data_get($this->json->all(), $key, $default); + } + + /** + * Get the input source for the request. + * + * @return \Symfony\Component\HttpFoundation\ParameterBag + */ + protected function getInputSource() + { + if ($this->isJson()) { + return $this->json(); + } + + return $this->getRealMethod() == 'GET' ? $this->query : $this->request; + } + + /** + * Create an Illuminate request from a Symfony instance. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Illuminate\Http\Request + */ + public static function createFromBase(SymfonyRequest $request) + { + if ($request instanceof static) { + return $request; + } + + $content = $request->content; + + $request = (new static)->duplicate( + $request->query->all(), $request->request->all(), $request->attributes->all(), + $request->cookies->all(), $request->files->all(), $request->server->all() + ); + + $request->content = $content; + + $request->request = $request->getInputSource(); + + return $request; + } + + /** + * {@inheritdoc} + */ + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + return parent::duplicate($query, $request, $attributes, $cookies, $this->filterFiles($files), $server); + } + + /** + * Filter the given array of files, removing any empty values. + * + * @param mixed $files + * @return mixed + */ + protected function filterFiles($files) + { + if (! $files) { + return; + } + + foreach ($files as $key => $file) { + if (is_array($file)) { + $files[$key] = $this->filterFiles($files[$key]); + } + + if (empty($files[$key])) { + unset($files[$key]); + } + } + + return $files; + } + + /** + * Get the session associated with the request. + * + * @return \Illuminate\Session\Store + * + * @throws \RuntimeException + */ + public function session() + { + if (! $this->hasSession()) { + throw new RuntimeException('Session store not set on request.'); + } + + return $this->getSession(); + } + + /** + * Set the session instance on the request. + * + * @param \Illuminate\Contracts\Session\Session $session + * @return void + */ + public function setLaravelSession($session) + { + $this->session = $session; + } + + /** + * Get the user making the request. + * + * @param string|null $guard + * @return mixed + */ + public function user($guard = null) + { + return call_user_func($this->getUserResolver(), $guard); + } + + /** + * Get the route handling the request. + * + * @param string|null $param + * + * @return \Illuminate\Routing\Route|object|string + */ + public function route($param = null) + { + $route = call_user_func($this->getRouteResolver()); + + if (is_null($route) || is_null($param)) { + return $route; + } + + return $route->parameter($param); + } + + /** + * Get a unique fingerprint for the request / route / IP address. + * + * @return string + * + * @throws \RuntimeException + */ + public function fingerprint() + { + if (! $route = $this->route()) { + throw new RuntimeException('Unable to generate fingerprint. Route unavailable.'); + } + + return sha1(implode('|', array_merge( + $route->methods(), [$route->getDomain(), $route->uri(), $this->ip()] + ))); + } + + /** + * Set the JSON payload for the request. + * + * @param \Symfony\Component\HttpFoundation\ParameterBag $json + * @return $this + */ + public function setJson($json) + { + $this->json = $json; + + return $this; + } + + /** + * Get the user resolver callback. + * + * @return \Closure + */ + public function getUserResolver() + { + return $this->userResolver ?: function () { + // + }; + } + + /** + * Set the user resolver callback. + * + * @param \Closure $callback + * @return $this + */ + public function setUserResolver(Closure $callback) + { + $this->userResolver = $callback; + + return $this; + } + + /** + * Get the route resolver callback. + * + * @return \Closure + */ + public function getRouteResolver() + { + return $this->routeResolver ?: function () { + // + }; + } + + /** + * Set the route resolver callback. + * + * @param \Closure $callback + * @return $this + */ + public function setRouteResolver(Closure $callback) + { + $this->routeResolver = $callback; + + return $this; + } + + /** + * Get all of the input and files for the request. + * + * @return array + */ + public function toArray() + { + return $this->all(); + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * @return bool + */ + public function offsetExists($offset): bool + { + return array_key_exists( + $offset, $this->all() + $this->route()->parameters() + ); + } + + /** + * Get the value at the given offset. + * + * @param string $offset + * @return mixed + * : mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->__get($offset); + } + + /** + * Set the value at the given offset. + * + * @param string $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value): void + { + $this->getInputSource()->set($offset, $value); + } + + /** + * Remove the value at the given offset. + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset): void + { + $this->getInputSource()->remove($offset); + } + + /** + * Check if an input element is set on the request. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return ! is_null($this->__get($key)); + } + + /** + * Get an input element from the request. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + if (array_key_exists($key, $this->all())) { + return data_get($this->all(), $key); + } + + return $this->route($key); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Mail/TransportManager.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Mail/TransportManager.php new file mode 100644 index 0000000..25d44a1 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Mail/TransportManager.php @@ -0,0 +1,209 @@ +app->make('config')->get('mail'); + + // The Swift SMTP transport instance will allow us to use any SMTP backend + // for delivering mail such as Sendgrid, Amazon SES, or a custom server + // a developer has available. We will just pass this configured host. + $transport = new SmtpTransport($config['host'], $config['port']); + + if (isset($config['encryption'])) { + $transport->setEncryption($config['encryption']); + } + + // Once we have the transport we will check for the presence of a username + // and password. If we have it we will set the credentials on the Swift + // transporter instance so that we'll properly authenticate delivery. + if (isset($config['username'])) { + $transport->setUsername($config['username']); + + $transport->setPassword($config['password']); + } + + // Next we will set any stream context options specified for the transport + // and then return it. The option is not required any may not be inside + // the configuration array at all so we'll verify that before adding. + if (isset($config['stream'])) { + $transport->setStreamOptions($config['stream']); + } + + // SMTP Timeout. + $transport->setTimeout(config('mail.smtp_timeout')); + + return $transport; + } + + /** + * Create an instance of the Sendmail Swift Transport driver. + * + * @return \Swift_SendmailTransport + */ + protected function createSendmailDriver() + { + return new SendmailTransport($this->app['config']['mail']['sendmail']); + } + + /** + * Create an instance of the Amazon SES Swift Transport driver. + * + * @return \Swift_SendmailTransport + */ + protected function createSesDriver() + { + $config = array_merge($this->app['config']->get('services.ses', []), [ + 'version' => 'latest', 'service' => 'email', + ]); + + return new SesTransport(new SesClient( + $this->addSesCredentials($config) + )); + } + + /** + * Add the SES credentials to the configuration array. + * + * @param array $config + * @return array + */ + protected function addSesCredentials(array $config) + { + if ($config['key'] && $config['secret']) { + $config['credentials'] = Arr::only($config, ['key', 'secret']); + } + + return $config; + } + + /** + * Create an instance of the Mail Swift Transport driver. + * + * @return \Swift_SendmailTransport + */ + protected function createMailDriver() + { + return new MailTransport; + } + + /** + * Create an instance of the Mailgun Swift Transport driver. + * + * @return \Illuminate\Mail\Transport\MailgunTransport + */ + protected function createMailgunDriver() + { + $config = $this->app['config']->get('services.mailgun', []); + + return new MailgunTransport( + $this->guzzle($config), + $config['secret'], $config['domain'] + ); + } + + /** + * Create an instance of the Mandrill Swift Transport driver. + * + * @return \Illuminate\Mail\Transport\MandrillTransport + */ + protected function createMandrillDriver() + { + $config = $this->app['config']->get('services.mandrill', []); + + return new MandrillTransport( + $this->guzzle($config), $config['secret'] + ); + } + + /** + * Create an instance of the SparkPost Swift Transport driver. + * + * @return \Illuminate\Mail\Transport\SparkPostTransport + */ + protected function createSparkPostDriver() + { + $config = $this->app['config']->get('services.sparkpost', []); + + return new SparkPostTransport( + $this->guzzle($config), $config['secret'], $config['options'] ?? [] + ); + } + + /** + * Create an instance of the Log Swift Transport driver. + * + * @return \Illuminate\Mail\Transport\LogTransport + */ + protected function createLogDriver() + { + return new LogTransport($this->app->make(LoggerInterface::class)); + } + + /** + * Create an instance of the Array Swift Transport Driver. + * + * @return \Illuminate\Mail\Transport\ArrayTransport + */ + protected function createArrayDriver() + { + return new ArrayTransport; + } + + /** + * Get a fresh Guzzle HTTP client instance. + * + * @param array $config + * @return \GuzzleHttp\Client + */ + protected function guzzle($config) + { + return new HttpClient(Arr::add( + $config['guzzle'] ?? [], 'connect_timeout', 60 + )); + } + + /** + * Get the default mail driver name. + * + * @return string + */ + public function getDefaultDriver() + { + return $this->app['config']['mail.driver']; + } + + /** + * Set the default mail driver name. + * + * @param string $name + * @return void + */ + public function setDefaultDriver($name) + { + $this->app['config']['mail.driver'] = $name; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php new file mode 100644 index 0000000..5530da9 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/AbstractPaginator.php @@ -0,0 +1,591 @@ += 1 && filter_var($page, FILTER_VALIDATE_INT) !== false; + } + + /** + * Get the URL for the previous page. + * + * @return string|null + */ + public function previousPageUrl() + { + if ($this->currentPage() > 1) { + return $this->url($this->currentPage() - 1); + } + } + + /** + * Create a range of pagination URLs. + * + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange($start, $end) + { + return collect(range($start, $end))->mapWithKeys(function ($page) { + return [$page => $this->url($page)]; + })->all(); + } + + /** + * Get the URL for a given page number. + * + * @param int $page + * @return string + */ + public function url($page) + { + if ($page <= 0) { + $page = 1; + } + + // If we have any extra query string key / value pairs that need to be added + // onto the URL, we will put them in query string form and then attach it + // to the URL. This allows for extra information like sortings storage. + $parameters = [$this->pageName => $page]; + + if (count($this->query) > 0) { + $parameters = array_merge($this->query, $parameters); + } + + return $this->path + .(Str::contains($this->path, '?') ? '&' : '?') + .http_build_query($parameters, '', '&') + .$this->buildFragment(); + } + + /** + * Get / set the URL fragment to be appended to URLs. + * + * @param string|null $fragment + * @return $this|string|null + */ + public function fragment($fragment = null) + { + if (is_null($fragment)) { + return $this->fragment; + } + + $this->fragment = $fragment; + + return $this; + } + + /** + * Add a set of query string values to the paginator. + * + * @param array|string $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (is_array($key)) { + return $this->appendArray($key); + } + + return $this->addQuery($key, $value); + } + + /** + * Add an array of query string values. + * + * @param array $keys + * @return $this + */ + protected function appendArray(array $keys) + { + foreach ($keys as $key => $value) { + $this->addQuery($key, $value); + } + + return $this; + } + + /** + * Add a query string value to the paginator. + * + * @param string $key + * @param string $value + * @return $this + */ + protected function addQuery($key, $value) + { + if ($key !== $this->pageName) { + $this->query[$key] = $value; + } + + return $this; + } + + /** + * Build the full fragment portion of a URL. + * + * @return string + */ + protected function buildFragment() + { + return $this->fragment ? '#'.$this->fragment : ''; + } + + /** + * Get the slice of items being paginated. + * + * @return array + */ + public function items() + { + return $this->items->all(); + } + + /** + * Get the number of the first item in the slice. + * + * @return int + */ + public function firstItem() + { + return count($this->items) > 0 ? ($this->currentPage - 1) * $this->perPage + 1 : null; + } + + /** + * Get the number of the last item in the slice. + * + * @return int + */ + public function lastItem() + { + return count($this->items) > 0 ? $this->firstItem() + $this->count() - 1 : null; + } + + /** + * Get the number of items shown per page. + * + * @return int + */ + public function perPage() + { + return $this->perPage; + } + + /** + * Determine if there are enough items to split into multiple pages. + * + * @return bool + */ + public function hasPages() + { + return $this->currentPage() != 1 || $this->hasMorePages(); + } + + /** + * Determine if the paginator is on the first page. + * + * @return bool + */ + public function onFirstPage() + { + return $this->currentPage() <= 1; + } + + /** + * Get the current page. + * + * @return int + */ + public function currentPage() + { + return $this->currentPage; + } + + /** + * Get the query string variable used to store the page. + * + * @return string + */ + public function getPageName() + { + return $this->pageName; + } + + /** + * Set the query string variable used to store the page. + * + * @param string $name + * @return $this + */ + public function setPageName($name) + { + $this->pageName = $name; + + return $this; + } + + /** + * Set the base path to assign to all URLs. + * + * @param string $path + * @return $this + */ + public function withPath($path) + { + return $this->setPath($path); + } + + /** + * Set the base path to assign to all URLs. + * + * @param string $path + * @return $this + */ + public function setPath($path) + { + $this->path = $path; + + return $this; + } + + /** + * Resolve the current request path or return the default value. + * + * @param string $default + * @return string + */ + public static function resolveCurrentPath($default = '/') + { + if (isset(static::$currentPathResolver)) { + return call_user_func(static::$currentPathResolver); + } + + return $default; + } + + /** + * Set the current request path resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public static function currentPathResolver(Closure $resolver) + { + static::$currentPathResolver = $resolver; + } + + /** + * Resolve the current page or return the default value. + * + * @param string $pageName + * @param int $default + * @return int + */ + public static function resolveCurrentPage($pageName = 'page', $default = 1) + { + if (isset(static::$currentPageResolver)) { + return call_user_func(static::$currentPageResolver, $pageName); + } + + return $default; + } + + /** + * Set the current page resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public static function currentPageResolver(Closure $resolver) + { + static::$currentPageResolver = $resolver; + } + + /** + * Get an instance of the view factory from the resolver. + * + * @return \Illuminate\Contracts\View\Factory + */ + public static function viewFactory() + { + return call_user_func(static::$viewFactoryResolver); + } + + /** + * Set the view factory resolver callback. + * + * @param \Closure $resolver + * @return void + */ + public static function viewFactoryResolver(Closure $resolver) + { + static::$viewFactoryResolver = $resolver; + } + + /** + * Set the default pagination view. + * + * @param string $view + * @return void + */ + public static function defaultView($view) + { + static::$defaultView = $view; + } + + /** + * Set the default "simple" pagination view. + * + * @param string $view + * @return void + */ + public static function defaultSimpleView($view) + { + static::$defaultSimpleView = $view; + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + public function getIterator(): \Traversable + { + return $this->items->getIterator(); + } + + /** + * Determine if the list of items is empty or not. + * + * @return bool + */ + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * Determine if the list of items is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return $this->items->isNotEmpty(); + } + + /** + * Get the number of items for the current page. + * + * @return int + */ + public function count(): int + { + return $this->items->count(); + } + + /** + * Get the paginator's underlying collection. + * + * @return \Illuminate\Support\Collection + */ + public function getCollection() + { + return $this->items; + } + + /** + * Set the paginator's underlying collection. + * + * @param \Illuminate\Support\Collection $collection + * @return $this + */ + public function setCollection(Collection $collection) + { + $this->items = $collection; + + return $this; + } + + /** + * Determine if the given item exists. + * + * @param mixed $key + * @return bool + */ + public function offsetExists($key): bool + { + return $this->items->has($key); + } + + /** + * Get the item at the given offset. + * + * @param mixed $key + * @return mixed + * : mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->items->get($key); + } + + /** + * Set the item at the given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value): void + { + $this->items->put($key, $value); + } + + /** + * Unset the item at the given key. + * + * @param mixed $key + * @return void + */ + public function offsetUnset($key): void + { + $this->items->forget($key); + } + + /** + * Render the contents of the paginator to HTML. + * + * @return string + */ + public function toHtml() + { + return (string) $this->render(); + } + + /** + * Make dynamic calls into the collection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->getCollection()->$method(...$parameters); + } + + /** + * Render the contents of the paginator when casting to string. + * + * @return string + */ + public function __toString() + { + return (string) $this->render(); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php new file mode 100644 index 0000000..1eb5cf5 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/LengthAwarePaginator.php @@ -0,0 +1,201 @@ + $value) { + $this->{$key} = $value; + } + + $this->total = $total; + $this->perPage = $perPage; + $this->lastPage = max((int) ceil($total / $perPage), 1); + $this->path = $this->path !== '/' ? rtrim($this->path, '/') : $this->path; + $this->currentPage = $this->setCurrentPage($currentPage, $this->pageName); + $this->items = $items instanceof Collection ? $items : Collection::make($items); + } + + /** + * Get the current page for the request. + * + * @param int $currentPage + * @param string $pageName + * @return int + */ + protected function setCurrentPage($currentPage, $pageName) + { + $currentPage = $currentPage ?: static::resolveCurrentPage($pageName); + + return $this->isValidPageNumber($currentPage) ? (int) $currentPage : 1; + } + + /** + * Render the paginator using the given view. + * + * @param string|null $view + * @param array $data + * @return \Illuminate\Support\HtmlString + */ + public function links($view = null, $data = []) + { + return $this->render($view, $data); + } + + /** + * Render the paginator using the given view. + * + * @param string|null $view + * @param array $data + * @return \Illuminate\Support\HtmlString + */ + public function render($view = null, $data = []) + { + return new HtmlString(static::viewFactory()->make($view ?: static::$defaultView, array_merge($data, [ + 'paginator' => $this, + 'elements' => $this->elements(), + ]))->render()); + } + + /** + * Get the array of elements to pass to the view. + * + * @return array + */ + protected function elements() + { + $window = UrlWindow::make($this); + + return array_filter([ + $window['first'], + is_array($window['slider']) ? '...' : null, + $window['slider'], + is_array($window['last']) ? '...' : null, + $window['last'], + ]); + } + + /** + * Get the total number of items being paginated. + * + * @return int + */ + public function total() + { + return $this->total; + } + + /** + * Determine if there are more items in the data source. + * + * @return bool + */ + public function hasMorePages() + { + return $this->currentPage() < $this->lastPage(); + } + + /** + * Get the URL for the next page. + * + * @return string|null + */ + public function nextPageUrl() + { + if ($this->lastPage() > $this->currentPage()) { + return $this->url($this->currentPage() + 1); + } + } + + /** + * Get the last page. + * + * @return int + */ + public function lastPage() + { + return $this->lastPage; + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return [ + 'current_page' => $this->currentPage(), + 'data' => $this->items->toArray(), + 'first_page_url' => $this->url(1), + 'from' => $this->firstItem(), + 'last_page' => $this->lastPage(), + 'last_page_url' => $this->url($this->lastPage()), + 'next_page_url' => $this->nextPageUrl(), + 'path' => $this->path, + 'per_page' => $this->perPage(), + 'prev_page_url' => $this->previousPageUrl(), + 'to' => $this->lastItem(), + 'total' => $this->total(), + ]; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + * : mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/Paginator.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/Paginator.php new file mode 100644 index 0000000..43f9005 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Pagination/Paginator.php @@ -0,0 +1,179 @@ + $value) { + $this->{$key} = $value; + } + + $this->perPage = $perPage; + $this->currentPage = $this->setCurrentPage($currentPage); + $this->path = $this->path !== '/' ? rtrim($this->path, '/') : $this->path; + + $this->setItems($items); + } + + /** + * Get the current page for the request. + * + * @param int $currentPage + * @return int + */ + protected function setCurrentPage($currentPage) + { + $currentPage = $currentPage ?: static::resolveCurrentPage(); + + return $this->isValidPageNumber($currentPage) ? (int) $currentPage : 1; + } + + /** + * Set the items for the paginator. + * + * @param mixed $items + * @return void + */ + protected function setItems($items) + { + $this->items = $items instanceof Collection ? $items : Collection::make($items); + + $this->hasMore = $this->items->count() > $this->perPage; + + $this->items = $this->items->slice(0, $this->perPage); + } + + /** + * Get the URL for the next page. + * + * @return string|null + */ + public function nextPageUrl() + { + if ($this->hasMorePages()) { + return $this->url($this->currentPage() + 1); + } + } + + /** + * Render the paginator using the given view. + * + * @param string|null $view + * @param array $data + * @return string + */ + public function links($view = null, $data = []) + { + return $this->render($view, $data); + } + + /** + * Render the paginator using the given view. + * + * @param string|null $view + * @param array $data + * @return string + */ + public function render($view = null, $data = []) + { + return new HtmlString( + static::viewFactory()->make($view ?: static::$defaultSimpleView, array_merge($data, [ + 'paginator' => $this, + ]))->render() + ); + } + + /** + * Manually indicate that the paginator does have more pages. + * + * @param bool $hasMore + * @return $this + */ + public function hasMorePagesWhen($hasMore = true) + { + $this->hasMore = $hasMore; + + return $this; + } + + /** + * Determine if there are more items in the data source. + * + * @return bool + */ + public function hasMorePages() + { + return $this->hasMore; + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return [ + 'current_page' => $this->currentPage(), + 'data' => $this->items->toArray(), + 'first_page_url' => $this->url(1), + 'from' => $this->firstItem(), + 'next_page_url' => $this->nextPageUrl(), + 'path' => $this->path, + 'per_page' => $this->perPage(), + 'prev_page_url' => $this->previousPageUrl(), + 'to' => $this->lastItem(), + ]; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + * : mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Queue/Listener.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Queue/Listener.php new file mode 100644 index 0000000..2e55ea9 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Queue/Listener.php @@ -0,0 +1,248 @@ +commandPath = $commandPath; + $this->workerCommand = $this->buildCommandTemplate(); + } + + /** + * Build the environment specific worker command. + * + * @return string + */ + protected function buildCommandTemplate() + { + $command = 'queue:work %s --once --queue=%s --delay=%s --memory=%s --sleep=%s --tries=%s'; + + return "{$this->phpBinary()} {$this->artisanBinary()} {$command}"; + } + + /** + * Get the PHP binary. + * + * @return string + */ + protected function phpBinary() + { + return ProcessUtils::escapeArgument( + (new PhpExecutableFinder)->find(false) + ); + } + + /** + * Get the Artisan binary. + * + * @return string + */ + protected function artisanBinary() + { + return defined('ARTISAN_BINARY') + ? ProcessUtils::escapeArgument(ARTISAN_BINARY) + : 'artisan'; + } + + /** + * Listen to the given queue connection. + * + * @param string $connection + * @param string $queue + * @param \Illuminate\Queue\ListenerOptions $options + * @return void + */ + public function listen($connection, $queue, ListenerOptions $options) + { + $process = $this->makeProcess($connection, $queue, $options); + + while (true) { + $this->runProcess($process, $options->memory); + } + } + + /** + * Create a new Symfony process for the worker. + * + * @param string $connection + * @param string $queue + * @param \Illuminate\Queue\ListenerOptions $options + * @return \Symfony\Component\Process\Process + */ + public function makeProcess($connection, $queue, ListenerOptions $options) + { + $command = $this->workerCommand; + + // If the environment is set, we will append it to the command string so the + // workers will run under the specified environment. Otherwise, they will + // just run under the production environment which is not always right. + if (isset($options->environment)) { + $command = $this->addEnvironment($command, $options); + } + + // Next, we will just format out the worker commands with all of the various + // options available for the command. This will produce the final command + // line that we will pass into a Symfony process object for processing. + $command = $this->formatCommand( + $command, $connection, $queue, $options + ); + + return new Process( + $command, $this->commandPath, null, null, $options->timeout + ); + } + + /** + * Add the environment option to the given command. + * + * @param string $command + * @param \Illuminate\Queue\ListenerOptions $options + * @return string + */ + protected function addEnvironment($command, ListenerOptions $options) + { + return $command.' --env='.ProcessUtils::escapeArgument($options->environment); + } + + /** + * Format the given command with the listener options. + * + * @param string $command + * @param string $connection + * @param string $queue + * @param \Illuminate\Queue\ListenerOptions $options + * @return string + */ + protected function formatCommand($command, $connection, $queue, ListenerOptions $options) + { + return sprintf( + $command, + ProcessUtils::escapeArgument($connection), + ProcessUtils::escapeArgument($queue), + $options->delay, $options->memory, + $options->sleep, $options->maxTries + ); + } + + /** + * Run the given process. + * + * @param \Symfony\Component\Process\Process $process + * @param int $memory + * @return void + */ + public function runProcess(Process $process, $memory) + { + $process->run(function ($type, $line) { + $this->handleWorkerOutput($type, $line); + }); + + // Once we have run the job we'll go check if the memory limit has been exceeded + // for the script. If it has, we will kill this script so the process manager + // will restart this with a clean slate of memory automatically on exiting. + if ($this->memoryExceeded($memory)) { + $this->stop(); + } + } + + /** + * Handle output from the worker process. + * + * @param int $type + * @param string $line + * @return void + */ + protected function handleWorkerOutput($type, $line) + { + if (isset($this->outputHandler)) { + call_user_func($this->outputHandler, $type, $line); + } + } + + /** + * Determine if the memory limit has been exceeded. + * + * @param int $memoryLimit + * @return bool + */ + public function memoryExceeded($memoryLimit) + { + return (memory_get_usage() / 1024 / 1024) >= $memoryLimit; + } + + /** + * Stop listening and bail out of the script. + * + * @return void + */ + public function stop() + { + exit; + } + + /** + * Set the output handler callback. + * + * @param \Closure $outputHandler + * @return void + */ + public function setOutputHandler(Closure $outputHandler) + { + $this->outputHandler = $outputHandler; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/Controller.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/Controller.php new file mode 100644 index 0000000..45c957a --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/Controller.php @@ -0,0 +1,70 @@ +middleware[] = [ + 'middleware' => $m, + 'options' => &$options, + ]; + } + + return new ControllerMiddlewareOptions($options); + } + + /** + * Get the middleware assigned to the controller. + * + * @return array + */ + public function getMiddleware() + { + return $this->middleware; + } + + /** + * Execute an action on the controller. + * + * @param string $method + * @param array $parameters + * @return \Symfony\Component\HttpFoundation\Response + */ + public function callAction($method, $parameters) + { + return call_user_func_array([$this, $method], array_values($parameters)); + } + + /** + * Handle calls to missing methods on the controller. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + throw new BadMethodCallException("Method [{$method}] does not exist on [".get_class($this).'].'); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteCollection.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteCollection.php new file mode 100644 index 0000000..9028285 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteCollection.php @@ -0,0 +1,351 @@ +addToCollections($route); + + $this->addLookups($route); + + return $route; + } + + /** + * Add the given route to the arrays of routes. + * + * @param \Illuminate\Routing\Route $route + * @return void + */ + protected function addToCollections($route) + { + $domainAndUri = $route->getDomain().$route->uri(); + + foreach ($route->methods() as $method) { + $this->routes[$method][$domainAndUri] = $route; + } + + $this->allRoutes[$method.$domainAndUri] = $route; + } + + /** + * Add the route to any look-up tables if necessary. + * + * @param \Illuminate\Routing\Route $route + * @return void + */ + protected function addLookups($route) + { + // If the route has a name, we will add it to the name look-up table so that we + // will quickly be able to find any route associate with a name and not have + // to iterate through every route every time we need to perform a look-up. + $action = $route->getAction(); + + if (isset($action['as'])) { + $this->nameList[$action['as']] = $route; + } + + // When the route is routing to a controller we will also store the action that + // is used by the route. This will let us reverse route to controllers while + // processing a request and easily generate URLs to the given controllers. + if (isset($action['controller'])) { + $this->addToActionList($action, $route); + } + } + + /** + * Add a route to the controller action dictionary. + * + * @param array $action + * @param \Illuminate\Routing\Route $route + * @return void + */ + protected function addToActionList($action, $route) + { + $this->actionList[trim($action['controller'], '\\')] = $route; + } + + /** + * Refresh the name look-up table. + * + * This is done in case any names are fluently defined or if routes are overwritten. + * + * @return void + */ + public function refreshNameLookups() + { + $this->nameList = []; + + foreach ($this->allRoutes as $route) { + if ($route->getName()) { + $this->nameList[$route->getName()] = $route; + } + } + } + + /** + * Refresh the action look-up table. + * + * This is done in case any actions are overwritten with new controllers. + * + * @return void + */ + public function refreshActionLookups() + { + $this->actionList = []; + + foreach ($this->allRoutes as $route) { + if (isset($route->getAction()['controller'])) { + $this->addToActionList($route->getAction(), $route); + } + } + } + + /** + * Find the first route matching a given request. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Routing\Route + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function match(Request $request) + { + $routes = $this->get($request->getMethod()); + + // First, we will see if we can find a matching route for this current request + // method. If we can, great, we can just return it so that it can be called + // by the consumer. Otherwise we will check for routes with another verb. + $route = $this->matchAgainstRoutes($routes, $request); + + if (! is_null($route)) { + return $route->bind($request); + } + + // If no route was found we will now check if a matching route is specified by + // another HTTP verb. If it is we will need to throw a MethodNotAllowed and + // inform the user agent of which HTTP verb it should use for this route. + $others = $this->checkForAlternateVerbs($request); + + if (count($others) > 0) { + return $this->getRouteForMethods($request, $others); + } + + throw new NotFoundHttpException; + } + + /** + * Determine if a route in the array matches the request. + * + * @param array $routes + * @param \Illuminate\http\Request $request + * @param bool $includingMethod + * @return \Illuminate\Routing\Route|null + */ + protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true) + { + list($fallbacks, $routes) = collect($routes)->partition(function ($route) { + return $route->isFallback; + }); + + return $routes->merge($fallbacks)->first(function ($value) use ($request, $includingMethod) { + return $value->matches($request, $includingMethod); + }); + } + + /** + * Determine if any routes match on another HTTP verb. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function checkForAlternateVerbs($request) + { + $methods = array_diff(Router::$verbs, [$request->getMethod()]); + + // Here we will spin through all verbs except for the current request verb and + // check to see if any routes respond to them. If they do, we will return a + // proper error response with the correct headers on the response string. + $others = []; + + foreach ($methods as $method) { + if (! is_null($this->matchAgainstRoutes($this->get($method), $request, false))) { + $others[] = $method; + } + } + + return $others; + } + + /** + * Get a route (if necessary) that responds when other available methods are present. + * + * @param \Illuminate\Http\Request $request + * @param array $methods + * @return \Illuminate\Routing\Route + * + * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException + */ + protected function getRouteForMethods($request, array $methods) + { + if ($request->method() == 'OPTIONS') { + return (new Route('OPTIONS', $request->path(), function () use ($methods) { + return new Response('', 200, ['Allow' => implode(',', $methods)]); + }))->bind($request); + } + + $this->methodNotAllowed($methods); + } + + /** + * Throw a method not allowed HTTP exception. + * + * @param array $others + * @return void + * + * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException + */ + protected function methodNotAllowed(array $others) + { + throw new MethodNotAllowedHttpException($others); + } + + /** + * Get routes from the collection by method. + * + * @param string|null $method + * @return array + */ + public function get($method = null) + { + return is_null($method) ? $this->getRoutes() : Arr::get($this->routes, $method, []); + } + + /** + * Determine if the route collection contains a given named route. + * + * @param string $name + * @return bool + */ + public function hasNamedRoute($name) + { + return ! is_null($this->getByName($name)); + } + + /** + * Get a route instance by its name. + * + * @param string $name + * @return \Illuminate\Routing\Route|null + */ + public function getByName($name) + { + return $this->nameList[$name] ?? null; + } + + /** + * Get a route instance by its controller action. + * + * @param string $action + * @return \Illuminate\Routing\Route|null + */ + public function getByAction($action) + { + return $this->actionList[$action] ?? null; + } + + /** + * Get all of the routes in the collection. + * + * @return array + */ + public function getRoutes() + { + return array_values($this->allRoutes); + } + + /** + * Get all of the routes keyed by their HTTP verb / method. + * + * @return array + */ + public function getRoutesByMethod() + { + return $this->routes; + } + + /** + * Get all of the routes keyed by their name. + * + * @return array + */ + public function getRoutesByName() + { + return $this->nameList; + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + public function getIterator(): \Traversable + { + return new ArrayIterator($this->getRoutes()); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + public function count(): int + { + return count($this->getRoutes()); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php new file mode 100644 index 0000000..b411b43 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php @@ -0,0 +1,111 @@ +resolveMethodDependencies( + $parameters, new ReflectionMethod($instance, $method) + ); + } + + /** + * Resolve the given method's type-hinted dependencies. + * + * @param array $parameters + * @param \ReflectionFunctionAbstract $reflector + * @return array + */ + public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector) + { + $instanceCount = 0; + + $values = array_values($parameters); + + foreach ($reflector->getParameters() as $key => $parameter) { + $instance = $this->transformDependency( + $parameter, $parameters + ); + + if (! is_null($instance)) { + $instanceCount++; + + $this->spliceIntoParameters($parameters, $key, $instance); + } elseif (! isset($values[$key - $instanceCount]) && + $parameter->isDefaultValueAvailable()) { + $this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue()); + } + } + + return $parameters; + } + + /** + * Attempt to transform the given parameter into a class instance. + * + * @param \ReflectionParameter $parameter + * @param array $parameters + * @return mixed + */ + protected function transformDependency(ReflectionParameter $parameter, $parameters) + { + $class = \Helper::getClass($parameter); + + // If the parameter has a type-hinted class, we will check to see if it is already in + // the list of parameters. If it is we will just skip it as it is probably a model + // binding and we do not want to mess with those; otherwise, we resolve it here. + if ($class && ! $this->alreadyInParameters($class->name, $parameters)) { + return $parameter->isDefaultValueAvailable() + ? $parameter->getDefaultValue() + : $this->container->make($class->name); + } + } + + /** + * Determine if an object of the given class is in a list of parameters. + * + * @param string $class + * @param array $parameters + * @return bool + */ + protected function alreadyInParameters($class, array $parameters) + { + return ! is_null(Arr::first($parameters, function ($value) use ($class) { + return $value instanceof $class; + })); + } + + /** + * Splice the given value into the parameter list. + * + * @param array $parameters + * @param string $offset + * @param mixed $value + * @return void + */ + protected function spliceIntoParameters(array &$parameters, $offset, $value) + { + array_splice( + $parameters, $offset, 0, [$value] + ); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteSignatureParameters.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteSignatureParameters.php new file mode 100644 index 0000000..eff62a0 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/RouteSignatureParameters.php @@ -0,0 +1,45 @@ +getParameters(); + + return is_null($subClass) ? $parameters : array_filter($parameters, function ($p) use ($subClass) { + return \Helper::getClass($p) && \Helper::getClass($p)->isSubclassOf($subClass); + }); + } + + /** + * Get the parameters for the given class / method by string. + * + * @param string $uses + * @return array + */ + protected static function fromClassMethodString($uses) + { + list($class, $method) = Str::parseCallback($uses); + + if (! method_exists($class, $method) && is_callable($class, $method)) { + return []; + } + + return (new ReflectionMethod($class, $method))->getParameters(); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/Router.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/Router.php new file mode 100644 index 0000000..64c2586 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/Router.php @@ -0,0 +1,1218 @@ +events = $events; + $this->routes = new RouteCollection; + $this->container = $container ?: new Container; + } + + /** + * Register a new GET route with the router. + * + * @param string $uri + * @param \Closure|array|string|null $action + * @return \Illuminate\Routing\Route + */ + public function get($uri, $action = null) + { + return $this->addRoute(['GET', 'HEAD'], $uri, $action); + } + + /** + * Register a new POST route with the router. + * + * @param string $uri + * @param \Closure|array|string|null $action + * @return \Illuminate\Routing\Route + */ + public function post($uri, $action = null) + { + return $this->addRoute('POST', $uri, $action); + } + + /** + * Register a new PUT route with the router. + * + * @param string $uri + * @param \Closure|array|string|null $action + * @return \Illuminate\Routing\Route + */ + public function put($uri, $action = null) + { + return $this->addRoute('PUT', $uri, $action); + } + + /** + * Register a new PATCH route with the router. + * + * @param string $uri + * @param \Closure|array|string|null $action + * @return \Illuminate\Routing\Route + */ + public function patch($uri, $action = null) + { + return $this->addRoute('PATCH', $uri, $action); + } + + /** + * Register a new DELETE route with the router. + * + * @param string $uri + * @param \Closure|array|string|null $action + * @return \Illuminate\Routing\Route + */ + public function delete($uri, $action = null) + { + return $this->addRoute('DELETE', $uri, $action); + } + + /** + * Register a new OPTIONS route with the router. + * + * @param string $uri + * @param \Closure|array|string|null $action + * @return \Illuminate\Routing\Route + */ + public function options($uri, $action = null) + { + return $this->addRoute('OPTIONS', $uri, $action); + } + + /** + * Register a new route responding to all verbs. + * + * @param string $uri + * @param \Closure|array|string|null $action + * @return \Illuminate\Routing\Route + */ + public function any($uri, $action = null) + { + return $this->addRoute(self::$verbs, $uri, $action); + } + + /** + * Register a new Fallback route with the router. + * + * @param \Closure|array|string|null $action + * @return \Illuminate\Routing\Route + */ + public function fallback($action) + { + $placeholder = 'fallbackPlaceholder'; + + return $this->addRoute( + 'GET', "{{$placeholder}}", $action + )->where($placeholder, '.*')->fallback(); + } + + /** + * Create a redirect from one URI to another. + * + * @param string $uri + * @param string $destination + * @param int $status + * @return \Illuminate\Routing\Route + */ + public function redirect($uri, $destination, $status = 301) + { + return $this->any($uri, '\Illuminate\Routing\RedirectController') + ->defaults('destination', $destination) + ->defaults('status', $status); + } + + /** + * Register a new route that returns a view. + * + * @param string $uri + * @param string $view + * @param array $data + * @return \Illuminate\Routing\Route + */ + public function view($uri, $view, $data = []) + { + return $this->match(['GET', 'HEAD'], $uri, '\Illuminate\Routing\ViewController') + ->defaults('view', $view) + ->defaults('data', $data); + } + + /** + * Register a new route with the given verbs. + * + * @param array|string $methods + * @param string $uri + * @param \Closure|array|string|null $action + * @return \Illuminate\Routing\Route + */ + public function match($methods, $uri, $action = null) + { + return $this->addRoute(array_map('strtoupper', (array) $methods), $uri, $action); + } + + /** + * Register an array of resource controllers. + * + * @param array $resources + * @return void + */ + public function resources(array $resources) + { + foreach ($resources as $name => $controller) { + $this->resource($name, $controller); + } + } + + /** + * Route a resource to a controller. + * + * @param string $name + * @param string $controller + * @param array $options + * @return \Illuminate\Routing\PendingResourceRegistration + */ + public function resource($name, $controller, array $options = []) + { + if ($this->container && $this->container->bound(ResourceRegistrar::class)) { + $registrar = $this->container->make(ResourceRegistrar::class); + } else { + $registrar = new ResourceRegistrar($this); + } + + return new PendingResourceRegistration( + $registrar, $name, $controller, $options + ); + } + + /** + * Register an array of API resource controllers. + * + * @param array $resources + * @return void + */ + public function apiResources(array $resources) + { + foreach ($resources as $name => $controller) { + $this->apiResource($name, $controller); + } + } + + /** + * Route an API resource to a controller. + * + * @param string $name + * @param string $controller + * @param array $options + * @return \Illuminate\Routing\PendingResourceRegistration + */ + public function apiResource($name, $controller, array $options = []) + { + return $this->resource($name, $controller, array_merge([ + 'only' => ['index', 'show', 'store', 'update', 'destroy'], + ], $options)); + } + + /** + * Create a route group with shared attributes. + * + * @param array $attributes + * @param \Closure|string $routes + * @return void + */ + public function group(array $attributes, $routes) + { + $this->updateGroupStack($attributes); + + // Once we have updated the group stack, we'll load the provided routes and + // merge in the group's attributes when the routes are created. After we + // have created the routes, we will pop the attributes off the stack. + $this->loadRoutes($routes); + + array_pop($this->groupStack); + } + + /** + * Update the group stack with the given attributes. + * + * @param array $attributes + * @return void + */ + protected function updateGroupStack(array $attributes) + { + if (! empty($this->groupStack)) { + $attributes = RouteGroup::merge($attributes, end($this->groupStack)); + } + + $this->groupStack[] = $attributes; + } + + /** + * Merge the given array with the last group stack. + * + * @param array $new + * @return array + */ + public function mergeWithLastGroup($new) + { + return RouteGroup::merge($new, end($this->groupStack)); + } + + /** + * Load the provided routes. + * + * @param \Closure|string $routes + * @return void + */ + protected function loadRoutes($routes) + { + if ($routes instanceof Closure) { + $routes($this); + } else { + $router = $this; + + require $routes; + } + } + + /** + * Get the prefix from the last group on the stack. + * + * @return string + */ + public function getLastGroupPrefix() + { + if (! empty($this->groupStack)) { + $last = end($this->groupStack); + + return $last['prefix'] ?? ''; + } + + return ''; + } + + /** + * Add a route to the underlying route collection. + * + * @param array|string $methods + * @param string $uri + * @param \Closure|array|string|null $action + * @return \Illuminate\Routing\Route + */ + protected function addRoute($methods, $uri, $action) + { + return $this->routes->add($this->createRoute($methods, $uri, $action)); + } + + /** + * Create a new route instance. + * + * @param array|string $methods + * @param string $uri + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + protected function createRoute($methods, $uri, $action) + { + // If the route is routing to a controller we will parse the route action into + // an acceptable array format before registering it and creating this route + // instance itself. We need to build the Closure that will call this out. + if ($this->actionReferencesController($action)) { + $action = $this->convertToControllerAction($action); + } + + $route = $this->newRoute( + $methods, $this->prefix($uri), $action + ); + + // If we have groups that need to be merged, we will merge them now after this + // route has already been created and is ready to go. After we're done with + // the merge we will be ready to return the route back out to the caller. + if ($this->hasGroupStack()) { + $this->mergeGroupAttributesIntoRoute($route); + } + + $this->addWhereClausesToRoute($route); + + return $route; + } + + /** + * Determine if the action is routing to a controller. + * + * @param array $action + * @return bool + */ + protected function actionReferencesController($action) + { + if (! $action instanceof Closure) { + return is_string($action) || (isset($action['uses']) && is_string($action['uses'])); + } + + return false; + } + + /** + * Add a controller based route action to the action array. + * + * @param array|string $action + * @return array + */ + protected function convertToControllerAction($action) + { + if (is_string($action)) { + $action = ['uses' => $action]; + } + + // Here we'll merge any group "uses" statement if necessary so that the action + // has the proper clause for this property. Then we can simply set the name + // of the controller on the action and return the action array for usage. + if (! empty($this->groupStack)) { + $action['uses'] = $this->prependGroupNamespace($action['uses']); + } + + // Here we will set this controller name on the action array just so we always + // have a copy of it for reference if we need it. This can be used while we + // search for a controller name or do some other type of fetch operation. + $action['controller'] = $action['uses']; + + return $action; + } + + /** + * Prepend the last group namespace onto the use clause. + * + * @param string $class + * @return string + */ + protected function prependGroupNamespace($class) + { + $group = end($this->groupStack); + + return isset($group['namespace']) && strpos($class, '\\') !== 0 + ? $group['namespace'].'\\'.$class : $class; + } + + /** + * Create a new Route object. + * + * @param array|string $methods + * @param string $uri + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + protected function newRoute($methods, $uri, $action) + { + return (new Route($methods, $uri, $action)) + ->setRouter($this) + ->setContainer($this->container); + } + + /** + * Prefix the given URI with the last prefix. + * + * @param string $uri + * @return string + */ + protected function prefix($uri) + { + return trim(trim($this->getLastGroupPrefix() ?? '', '/').'/'.trim($uri ?? '', '/'), '/') ?: '/'; + } + + /** + * Add the necessary where clauses to the route based on its initial registration. + * + * @param \Illuminate\Routing\Route $route + * @return \Illuminate\Routing\Route + */ + protected function addWhereClausesToRoute($route) + { + $route->where(array_merge( + $this->patterns, $route->getAction()['where'] ?? [] + )); + + return $route; + } + + /** + * Merge the group stack with the controller action. + * + * @param \Illuminate\Routing\Route $route + * @return void + */ + protected function mergeGroupAttributesIntoRoute($route) + { + $route->setAction($this->mergeWithLastGroup($route->getAction())); + } + + /** + * Return the response returned by the given route. + * + * @param string $name + * @return mixed + */ + public function respondWithRoute($name) + { + $route = tap($this->routes->getByName($name))->bind($this->currentRequest); + + return $this->runRoute($this->currentRequest, $route); + } + + /** + * Dispatch the request to the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function dispatch(Request $request) + { + $this->currentRequest = $request; + + return $this->dispatchToRoute($request); + } + + /** + * Dispatch the request to a route and return the response. + * + * @param \Illuminate\Http\Request $request + * @return mixed + */ + public function dispatchToRoute(Request $request) + { + return $this->runRoute($request, $this->findRoute($request)); + } + + /** + * Find the route matching a given request. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Routing\Route + */ + protected function findRoute($request) + { + $this->current = $route = $this->routes->match($request); + + $this->container->instance(Route::class, $route); + + return $route; + } + + /** + * Return the response for the given route. + * + * @param Route $route + * @param Request $request + * @return mixed + */ + protected function runRoute(Request $request, Route $route) + { + $request->setRouteResolver(function () use ($route) { + return $route; + }); + + $this->events->dispatch(new Events\RouteMatched($route, $request)); + + return $this->prepareResponse($request, + $this->runRouteWithinStack($route, $request) + ); + } + + /** + * Run the given route within a Stack "onion" instance. + * + * @param \Illuminate\Routing\Route $route + * @param \Illuminate\Http\Request $request + * @return mixed + */ + protected function runRouteWithinStack(Route $route, Request $request) + { + $shouldSkipMiddleware = $this->container->bound('middleware.disable') && + $this->container->make('middleware.disable') === true; + + $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route); + + return (new Pipeline($this->container)) + ->send($request) + ->through($middleware) + ->then(function ($request) use ($route) { + return $this->prepareResponse( + $request, $route->run() + ); + }); + } + + /** + * Gather the middleware for the given route with resolved class names. + * + * @param \Illuminate\Routing\Route $route + * @return array + */ + public function gatherRouteMiddleware(Route $route) + { + $middleware = collect($route->gatherMiddleware())->map(function ($name) { + return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups); + })->flatten(); + + return $this->sortMiddleware($middleware); + } + + /** + * Sort the given middleware by priority. + * + * @param \Illuminate\Support\Collection $middlewares + * @return array + */ + protected function sortMiddleware(Collection $middlewares) + { + return (new SortedMiddleware($this->middlewarePriority, $middlewares))->all(); + } + + /** + * Create a response instance from the given value. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param mixed $response + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public function prepareResponse($request, $response) + { + return static::toResponse($request, $response); + } + + /** + * Static version of prepareResponse. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param mixed $response + * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + */ + public static function toResponse($request, $response) + { + if ($response instanceof Responsable) { + $response = $response->toResponse($request); + } + + if ($response instanceof PsrResponseInterface) { + $response = (new HttpFoundationFactory)->createResponse($response); + } elseif (! $response instanceof SymfonyResponse && + ($response instanceof Arrayable || + $response instanceof Jsonable || + $response instanceof ArrayObject || + $response instanceof JsonSerializable || + is_array($response))) { + $response = new JsonResponse($response); + } elseif (! $response instanceof SymfonyResponse) { + $response = new Response($response); + } + + if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) { + $response->setNotModified(); + } + + return $response->prepare($request); + } + + /** + * Substitute the route bindings onto the route. + * + * @param \Illuminate\Routing\Route $route + * @return \Illuminate\Routing\Route + */ + public function substituteBindings($route) + { + foreach ($route->parameters() as $key => $value) { + if (isset($this->binders[$key])) { + $route->setParameter($key, $this->performBinding($key, $value, $route)); + } + } + + return $route; + } + + /** + * Substitute the implicit Eloquent model bindings for the route. + * + * @param \Illuminate\Routing\Route $route + * @return void + */ + public function substituteImplicitBindings($route) + { + ImplicitRouteBinding::resolveForRoute($this->container, $route); + } + + /** + * Call the binding callback for the given key. + * + * @param string $key + * @param string $value + * @param \Illuminate\Routing\Route $route + * @return mixed + */ + protected function performBinding($key, $value, $route) + { + return call_user_func($this->binders[$key], $value, $route); + } + + /** + * Register a route matched event listener. + * + * @param string|callable $callback + * @return void + */ + public function matched($callback) + { + $this->events->listen(Events\RouteMatched::class, $callback); + } + + /** + * Get all of the defined middleware short-hand names. + * + * @return array + */ + public function getMiddleware() + { + return $this->middleware; + } + + /** + * Register a short-hand name for a middleware. + * + * @param string $name + * @param string $class + * @return $this + */ + public function aliasMiddleware($name, $class) + { + $this->middleware[$name] = $class; + + return $this; + } + + /** + * Check if a middlewareGroup with the given name exists. + * + * @param string $name + * @return bool + */ + public function hasMiddlewareGroup($name) + { + return array_key_exists($name, $this->middlewareGroups); + } + + /** + * Get all of the defined middleware groups. + * + * @return array + */ + public function getMiddlewareGroups() + { + return $this->middlewareGroups; + } + + /** + * Register a group of middleware. + * + * @param string $name + * @param array $middleware + * @return $this + */ + public function middlewareGroup($name, array $middleware) + { + $this->middlewareGroups[$name] = $middleware; + + return $this; + } + + /** + * Add a middleware to the beginning of a middleware group. + * + * If the middleware is already in the group, it will not be added again. + * + * @param string $group + * @param string $middleware + * @return $this + */ + public function prependMiddlewareToGroup($group, $middleware) + { + if (isset($this->middlewareGroups[$group]) && ! in_array($middleware, $this->middlewareGroups[$group])) { + array_unshift($this->middlewareGroups[$group], $middleware); + } + + return $this; + } + + /** + * Add a middleware to the end of a middleware group. + * + * If the middleware is already in the group, it will not be added again. + * + * @param string $group + * @param string $middleware + * @return $this + */ + public function pushMiddlewareToGroup($group, $middleware) + { + if (! array_key_exists($group, $this->middlewareGroups)) { + $this->middlewareGroups[$group] = []; + } + + if (! in_array($middleware, $this->middlewareGroups[$group])) { + $this->middlewareGroups[$group][] = $middleware; + } + + return $this; + } + + /** + * Add a new route parameter binder. + * + * @param string $key + * @param string|callable $binder + * @return void + */ + public function bind($key, $binder) + { + $this->binders[str_replace('-', '_', $key)] = RouteBinding::forCallback( + $this->container, $binder + ); + } + + /** + * Register a model binder for a wildcard. + * + * @param string $key + * @param string $class + * @param \Closure|null $callback + * @return void + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function model($key, $class, Closure $callback = null) + { + $this->bind($key, RouteBinding::forModel($this->container, $class, $callback)); + } + + /** + * Get the binding callback for a given binding. + * + * @param string $key + * @return \Closure|null + */ + public function getBindingCallback($key) + { + if (isset($this->binders[$key = str_replace('-', '_', $key)])) { + return $this->binders[$key]; + } + } + + /** + * Get the global "where" patterns. + * + * @return array + */ + public function getPatterns() + { + return $this->patterns; + } + + /** + * Set a global where pattern on all routes. + * + * @param string $key + * @param string $pattern + * @return void + */ + public function pattern($key, $pattern) + { + $this->patterns[$key] = $pattern; + } + + /** + * Set a group of global where patterns on all routes. + * + * @param array $patterns + * @return void + */ + public function patterns($patterns) + { + foreach ($patterns as $key => $pattern) { + $this->pattern($key, $pattern); + } + } + + /** + * Determine if the router currently has a group stack. + * + * @return bool + */ + public function hasGroupStack() + { + return ! empty($this->groupStack); + } + + /** + * Get the current group stack for the router. + * + * @return array + */ + public function getGroupStack() + { + return $this->groupStack; + } + + /** + * Get a route parameter for the current route. + * + * @param string $key + * @param string $default + * @return mixed + */ + public function input($key, $default = null) + { + return $this->current()->parameter($key, $default); + } + + /** + * Get the request currently being dispatched. + * + * @return \Illuminate\Http\Request + */ + public function getCurrentRequest() + { + return $this->currentRequest; + } + + /** + * Get the currently dispatched route instance. + * + * @return \Illuminate\Routing\Route + */ + public function getCurrentRoute() + { + return $this->current(); + } + + /** + * Get the currently dispatched route instance. + * + * @return \Illuminate\Routing\Route + */ + public function current() + { + return $this->current; + } + + /** + * Check if a route with the given name exists. + * + * @param string $name + * @return bool + */ + public function has($name) + { + $names = is_array($name) ? $name : func_get_args(); + + foreach ($names as $value) { + if (! $this->routes->hasNamedRoute($value)) { + return false; + } + } + + return true; + } + + /** + * Get the current route name. + * + * @return string|null + */ + public function currentRouteName() + { + return $this->current() ? $this->current()->getName() : null; + } + + /** + * Alias for the "currentRouteNamed" method. + * + * @param dynamic $patterns + * @return bool + */ + public function is(...$patterns) + { + return $this->currentRouteNamed(...$patterns); + } + + /** + * Determine if the current route matches a pattern. + * + * @param dynamic $patterns + * @return bool + */ + public function currentRouteNamed(...$patterns) + { + return $this->current() && $this->current()->named(...$patterns); + } + + /** + * Get the current route action. + * + * @return string|null + */ + public function currentRouteAction() + { + if ($this->current()) { + return $this->current()->getAction()['controller'] ?? null; + } + } + + /** + * Alias for the "currentRouteUses" method. + * + * @param array ...$patterns + * @return bool + */ + public function uses(...$patterns) + { + foreach ($patterns as $pattern) { + if (Str::is($pattern, $this->currentRouteAction())) { + return true; + } + } + + return false; + } + + /** + * Determine if the current route action matches a given action. + * + * @param string $action + * @return bool + */ + public function currentRouteUses($action) + { + return $this->currentRouteAction() == $action; + } + + /** + * Register the typical authentication routes for an application. + * + * @return void + */ + public function auth() + { + // Authentication Routes... + // login routes moved to web.php + //$this->get('login', 'Auth\LoginController@showLoginForm')->name('login'); + //$this->post('login', 'Auth\LoginController@login'); + $this->post('logout', 'Auth\LoginController@logout')->name('logout'); + + // Registration Routes... + $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register'); + $this->post('register', 'Auth\RegisterController@register'); + + // Password Reset Routes... + $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request'); + $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email'); + $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset'); + $this->post('password/reset', 'Auth\ResetPasswordController@reset'); + } + + /** + * Set the unmapped global resource parameters to singular. + * + * @param bool $singular + * @return void + */ + public function singularResourceParameters($singular = true) + { + ResourceRegistrar::singularParameters($singular); + } + + /** + * Set the global resource parameter mapping. + * + * @param array $parameters + * @return void + */ + public function resourceParameters(array $parameters = []) + { + ResourceRegistrar::setParameters($parameters); + } + + /** + * Get or set the verbs used in the resource URIs. + * + * @param array $verbs + * @return array|null + */ + public function resourceVerbs(array $verbs = []) + { + return ResourceRegistrar::verbs($verbs); + } + + /** + * Get the underlying route collection. + * + * @return \Illuminate\Routing\RouteCollection + */ + public function getRoutes() + { + return $this->routes; + } + + /** + * Set the route collection instance. + * + * @param \Illuminate\Routing\RouteCollection $routes + * @return void + */ + public function setRoutes(RouteCollection $routes) + { + foreach ($routes as $route) { + $route->setRouter($this)->setContainer($this->container); + } + + $this->routes = $routes; + + $this->container->instance('routes', $this->routes); + } + + /** + * Dynamically handle calls into the router instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + if ($method == 'middleware') { + return (new RouteRegistrar($this))->attribute($method, is_array($parameters[0]) ? $parameters[0] : $parameters); + } + + return (new RouteRegistrar($this))->attribute($method, $parameters[0]); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/UrlGenerator.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/UrlGenerator.php new file mode 100755 index 0000000..0f6ef25 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Routing/UrlGenerator.php @@ -0,0 +1,657 @@ +routes = $routes; + + $this->setRequest($request); + } + + /** + * Get the full URL for the current request. + * + * @return string + */ + public function full() + { + return $this->request->fullUrl(); + } + + /** + * Get the current URL for the request. + * + * @return string + */ + public function current() + { + return $this->to($this->request->getPathInfo()); + } + + /** + * Get the URL for the previous request. + * + * @param mixed $fallback + * @return string + */ + public function previous($fallback = false) + { + $referrer = $this->request->headers->get('referer'); + + $url = $referrer ? $this->to($referrer) : $this->getPreviousUrlFromSession(); + + if ($url) { + return $url; + } elseif ($fallback) { + return $this->to($fallback); + } + + return $this->to('/'); + } + + /** + * Get the previous URL from the session if possible. + * + * @return string|null + */ + protected function getPreviousUrlFromSession() + { + $session = $this->getSession(); + + return $session ? $session->previousUrl() : null; + } + + /** + * Generate an absolute URL to the given path. + * + * @param string $path + * @param mixed $extra + * @param bool|null $secure + * @return string + */ + public function to($path, $extra = [], $secure = null) + { + // First we will check if the URL is already a valid URL. If it is we will not + // try to generate a new one but will simply return the URL as is, which is + // convenient since developers do not always have to check if it's valid. + if ($this->isValidUrl($path)) { + return $path; + } + + $tail = implode('/', array_map( + 'rawurlencode', (array) $this->formatParameters($extra)) + ); + + // Once we have the scheme we will compile the "tail" by collapsing the values + // into a single string delimited by slashes. This just makes it convenient + // for passing the array of parameters to this URL as a list of segments. + $root = $this->formatRoot($this->formatScheme($secure)); + + list($path, $query) = $this->extractQueryString($path); + + return $this->format( + $root, '/'.trim($path.'/'.$tail, '/') + ).$query; + } + + /** + * Generate a secure, absolute URL to the given path. + * + * @param string $path + * @param array $parameters + * @return string + */ + public function secure($path, $parameters = []) + { + return $this->to($path, $parameters, true); + } + + /** + * Generate the URL to an application asset. + * + * @param string $path + * @param bool|null $secure + * @return string + */ + public function asset($path, $secure = null) + { + if ($this->isValidUrl($path)) { + return $path; + } + + // Once we get the root URL, we will check to see if it contains an index.php + // file in the paths. If it does, we will remove it since it is not needed + // for asset paths, but only for routes to endpoints in the application. + $root = $this->formatRoot($this->formatScheme($secure)); + + return $this->removeIndex($root).'/'.trim($path, '/'); + } + + /** + * Generate the URL to a secure asset. + * + * @param string $path + * @return string + */ + public function secureAsset($path) + { + return $this->asset($path, true); + } + + /** + * Generate the URL to an asset from a custom root domain such as CDN, etc. + * + * @param string $root + * @param string $path + * @param bool|null $secure + * @return string + */ + public function assetFrom($root, $path, $secure = null) + { + // Once we get the root URL, we will check to see if it contains an index.php + // file in the paths. If it does, we will remove it since it is not needed + // for asset paths, but only for routes to endpoints in the application. + $root = $this->formatRoot($this->formatScheme($secure), $root); + + return $this->removeIndex($root).'/'.trim($path, '/'); + } + + /** + * Remove the index.php file from a path. + * + * @param string $root + * @return string + */ + protected function removeIndex($root) + { + $i = 'index.php'; + + return Str::contains($root, $i) ? str_replace('/'.$i, '', $root) : $root; + } + + /** + * Get the default scheme for a raw URL. + * + * @param bool|null $secure + * @return string + */ + public function formatScheme($secure) + { + if (! is_null($secure)) { + return $secure ? 'https://' : 'http://'; + } + + if (is_null($this->cachedSchema)) { + $this->cachedSchema = $this->forceScheme ?: $this->request->getScheme().'://'; + } + + return $this->cachedSchema; + } + + /** + * Get the URL to a named route. + * + * @param string $name + * @param mixed $parameters + * @param bool $absolute + * @return string + * + * @throws \InvalidArgumentException + */ + public function route($name, $parameters = [], $absolute = true) + { + if (! is_null($route = $this->routes->getByName($name))) { + // Pass x_ parameters globally. + foreach (request()->query() as $param => $value) { + if (preg_match("/^x_/", $param)) { + $parameters[$param] = $value; + } + } + // Pass xs_ parameters only on the same page. + if ($name == \Route::currentRouteName()) { + foreach (request()->query() as $param => $value) { + if (preg_match("/^xs_/", $param)) { + $parameters[$param] = $value; + } + } + } + return $this->toRoute($route, $parameters, $absolute); + } + + throw new InvalidArgumentException("Route [{$name}] not defined."); + } + + /** + * Get the URL for a given route instance. + * + * @param \Illuminate\Routing\Route $route + * @param mixed $parameters + * @param bool $absolute + * @return string + * + * @throws \Illuminate\Routing\Exceptions\UrlGenerationException + */ + protected function toRoute($route, $parameters, $absolute) + { + return $this->routeUrl()->to( + $route, $this->formatParameters($parameters), $absolute + ); + } + + /** + * Get the URL to a controller action. + * + * @param string $action + * @param mixed $parameters + * @param bool $absolute + * @return string + * + * @throws \InvalidArgumentException + */ + public function action($action, $parameters = [], $absolute = true) + { + if (is_null($route = $this->routes->getByAction($action = $this->formatAction($action)))) { + throw new InvalidArgumentException("Action {$action} not defined."); + } + + return $this->toRoute($route, $parameters, $absolute); + } + + /** + * Format the given controller action. + * + * @param string $action + * @return string + */ + protected function formatAction($action) + { + if ($this->rootNamespace && ! (strpos($action, '\\') === 0)) { + return $this->rootNamespace.'\\'.$action; + } else { + return trim($action, '\\'); + } + } + + /** + * Format the array of URL parameters. + * + * @param mixed|array $parameters + * @return array + */ + public function formatParameters($parameters) + { + $parameters = Arr::wrap($parameters); + + foreach ($parameters as $key => $parameter) { + if ($parameter instanceof UrlRoutable) { + $parameters[$key] = $parameter->getRouteKey(); + } + } + + return $parameters; + } + + /** + * Extract the query string from the given path. + * + * @param string $path + * @return array + */ + protected function extractQueryString($path) + { + if (($queryPosition = strpos($path, '?')) !== false) { + return [ + substr($path, 0, $queryPosition), + substr($path, $queryPosition), + ]; + } + + return [$path, '']; + } + + /** + * Get the base URL for the request. + * + * @param string $scheme + * @param string $root + * @return string + */ + public function formatRoot($scheme, $root = null) + { + if (is_null($root)) { + if (is_null($this->cachedRoot)) { + // $this->request->root() does not determine subdirectory properly. + $this->cachedRoot = \Eventy::filter('url_generator.app_url', $this->forcedRoot ?: config('app.url')); + + if (!$this->cachedRoot || \Helper::isDefaultAppUrl($this->cachedRoot)) { + $this->cachedRoot = $this->request->root(); + } + + // Remove the slash at the end as on some systems there is a slash at the end. + $this->cachedRoot = rtrim($this->cachedRoot, "/"); + } + + $root = $this->cachedRoot; + } + + $start = Str::startsWith($root, 'http://') ? 'http://' : 'https://'; + + return preg_replace('~'.$start.'~', $scheme, $root, 1); + } + + /** + * Format the given URL segments into a single URL. + * + * @param string $root + * @param string $path + * @return string + */ + public function format($root, $path) + { + $path = '/'.trim($path, '/'); + + if ($this->formatHostUsing) { + $root = call_user_func($this->formatHostUsing, $root); + } + + if ($this->formatPathUsing) { + $path = call_user_func($this->formatPathUsing, $path); + } + + // Cut subdirectory from path. + $subdirectory = \Helper::getSubdirectory(false, true); + if ($subdirectory && preg_match("#".preg_quote($subdirectory)."$#", trim($root, '/'))) { + $path = preg_replace("#^".preg_quote($subdirectory)."#", '', $path); + } + + return trim($root.$path, '/'); + } + + /** + * Determine if the given path is a valid URL. + * + * @param string $path + * @return bool + */ + public function isValidUrl($path) + { + if (! preg_match('~^(#|//|https?://|mailto:|tel:)~', $path)) { + return filter_var($path, FILTER_VALIDATE_URL) !== false; + } + + return true; + } + + /** + * Get the Route URL generator instance. + * + * @return \Illuminate\Routing\RouteUrlGenerator + */ + protected function routeUrl() + { + if (! $this->routeGenerator) { + $this->routeGenerator = new RouteUrlGenerator($this, $this->request); + } + + return $this->routeGenerator; + } + + /** + * Set the default named parameters used by the URL generator. + * + * @param array $defaults + * @return void + */ + public function defaults(array $defaults) + { + $this->routeUrl()->defaults($defaults); + } + + /** + * Get the default named parameters used by the URL generator. + * + * @return array + */ + public function getDefaultParameters() + { + return $this->routeUrl()->defaultParameters; + } + + /** + * Force the scheme for URLs. + * + * @param string $schema + * @return void + */ + public function forceScheme($schema) + { + $this->cachedSchema = null; + + $this->forceScheme = $schema.'://'; + } + + /** + * Set the forced root URL. + * + * @param string $root + * @return void + */ + public function forceRootUrl($root) + { + $this->forcedRoot = rtrim($root, '/'); + + $this->cachedRoot = null; + } + + /** + * Set a callback to be used to format the host of generated URLs. + * + * @param \Closure $callback + * @return $this + */ + public function formatHostUsing(Closure $callback) + { + $this->formatHostUsing = $callback; + + return $this; + } + + /** + * Set a callback to be used to format the path of generated URLs. + * + * @param \Closure $callback + * @return $this + */ + public function formatPathUsing(Closure $callback) + { + $this->formatPathUsing = $callback; + + return $this; + } + + /** + * Get the path formatter being used by the URL generator. + * + * @return \Closure + */ + public function pathFormatter() + { + return $this->formatPathUsing ?: function ($path) { + return $path; + }; + } + + /** + * Get the request instance. + * + * @return \Illuminate\Http\Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Set the current request instance. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + + $this->cachedRoot = null; + $this->cachedSchema = null; + $this->routeGenerator = null; + } + + /** + * Set the route collection. + * + * @param \Illuminate\Routing\RouteCollection $routes + * @return $this + */ + public function setRoutes(RouteCollection $routes) + { + $this->routes = $routes; + + return $this; + } + + /** + * Get the session implementation from the resolver. + * + * @return \Illuminate\Session\Store|null + */ + protected function getSession() + { + if ($this->sessionResolver) { + return call_user_func($this->sessionResolver); + } + } + + /** + * Set the session resolver for the generator. + * + * @param callable $sessionResolver + * @return $this + */ + public function setSessionResolver(callable $sessionResolver) + { + $this->sessionResolver = $sessionResolver; + + return $this; + } + + /** + * Set the root controller namespace. + * + * @param string $rootNamespace + * @return $this + */ + public function setRootControllerNamespace($rootNamespace) + { + $this->rootNamespace = $rootNamespace; + + return $this; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Session/FileSessionHandler.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Session/FileSessionHandler.php new file mode 100644 index 0000000..c9e2ec1 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Session/FileSessionHandler.php @@ -0,0 +1,119 @@ +path = $path; + $this->files = $files; + $this->minutes = $minutes; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName): bool + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close(): bool + { + return true; + } + + /** + * {@inheritdoc} + * : string|false + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + if ($this->files->exists($path = $this->path.'/'.$sessionId)) { + if (filemtime($path) >= Carbon::now()->subMinutes($this->minutes)->getTimestamp()) { + return $this->files->get($path, true); + } + } + + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data): bool + { + $this->files->put($this->path.'/'.$sessionId, $data, true); + + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId): bool + { + $this->files->delete($this->path.'/'.$sessionId); + + return true; + } + + /** + * {@inheritdoc} + * : int|false + */ + #[\ReturnTypeWillChange] + public function gc($lifetime) + { + $files = Finder::create() + ->in($this->path) + ->files() + ->ignoreDotFiles(true) + ->date('<= now - '.$lifetime.' seconds'); + + foreach ($files as $file) { + $this->files->delete($file->getRealPath()); + } + + return false; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Carbon.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Carbon.php new file mode 100644 index 0000000..a3dd8c9 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Carbon.php @@ -0,0 +1,50 @@ +items = $this->getArrayableItems($items); + } + + /** + * Create a new collection instance if the value isn't one already. + * + * @param mixed $items + * @return static + */ + public static function make($items = []) + { + return new static($items); + } + + /** + * Wrap the given value in a collection if applicable. + * + * @param mixed $value + * @return static + */ + public static function wrap($value) + { + return $value instanceof self + ? new static($value) + : new static(Arr::wrap($value)); + } + + /** + * Get the underlying items from the given collection if applicable. + * + * @param array|static $value + * @return array + */ + public static function unwrap($value) + { + return $value instanceof self ? $value->all() : $value; + } + + /** + * Create a new collection by invoking the callback a given amount of times. + * + * @param int $number + * @param callable $callback + * @return static + */ + public static function times($number, callable $callback = null) + { + if ($number < 1) { + return new static; + } + + if (is_null($callback)) { + return new static(range(1, $number)); + } + + return (new static(range(1, $number)))->map($callback); + } + + /** + * Get all of the items in the collection. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Get the average value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function avg($callback = null) + { + if ($count = $this->count()) { + return $this->sum($callback) / $count; + } + } + + /** + * Alias for the "avg" method. + * + * @param callable|string|null $callback + * @return mixed + */ + public function average($callback = null) + { + return $this->avg($callback); + } + + /** + * Get the median of a given key. + * + * @param null $key + * @return mixed + */ + public function median($key = null) + { + $count = $this->count(); + + if ($count == 0) { + return; + } + + $values = (isset($key) ? $this->pluck($key) : $this) + ->sort()->values(); + + $middle = (int) ($count / 2); + + if ($count % 2) { + return $values->get($middle); + } + + return (new static([ + $values->get($middle - 1), $values->get($middle), + ]))->average(); + } + + /** + * Get the mode of a given key. + * + * @param mixed $key + * @return array|null + */ + public function mode($key = null) + { + $count = $this->count(); + + if ($count == 0) { + return; + } + + $collection = isset($key) ? $this->pluck($key) : $this; + + $counts = new self; + + $collection->each(function ($value) use ($counts) { + $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1; + }); + + $sorted = $counts->sort(); + + $highestValue = $sorted->last(); + + return $sorted->filter(function ($value) use ($highestValue) { + return $value == $highestValue; + })->sort()->keys()->all(); + } + + /** + * Collapse the collection of items into a single array. + * + * @return static + */ + public function collapse() + { + return new static(Arr::collapse($this->items)); + } + + /** + * Determine if an item exists in the collection. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function contains($key, $operator = null, $value = null) + { + if (func_num_args() == 1) { + if ($this->useAsCallable($key)) { + $placeholder = new stdClass; + + return $this->first($key, $placeholder) !== $placeholder; + } + + return in_array($key, $this->items); + } + + return $this->contains($this->operatorForWhere(...func_get_args())); + } + + /** + * Determine if an item exists in the collection using strict comparison. + * + * @param mixed $key + * @param mixed $value + * @return bool + */ + public function containsStrict($key, $value = null) + { + if (func_num_args() == 2) { + return $this->contains(function ($item) use ($key, $value) { + return data_get($item, $key) === $value; + }); + } + + if ($this->useAsCallable($key)) { + return ! is_null($this->first($key)); + } + + return in_array($key, $this->items, true); + } + + /** + * Cross join with the given lists, returning all possible permutations. + * + * @param mixed ...$lists + * @return static + */ + public function crossJoin(...$lists) + { + return new static(Arr::crossJoin( + $this->items, ...array_map([$this, 'getArrayableItems'], $lists) + )); + } + + /** + * Dump the collection and end the script. + * + * @return void + */ + public function dd(...$args) + { + http_response_code(500); + + call_user_func_array([$this, 'dump'], $args); + + exit(1); + } + + /** + * Dump the collection. + * + * @return $this + */ + public function dump() + { + (new static(func_get_args())) + ->push($this) + ->each(function ($item) { + (new Dumper)->dump($item); + }); + + return $this; + } + + /** + * Get the items in the collection that are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diff($items) + { + return new static(array_diff($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection whose keys and values are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diffAssoc($items) + { + return new static(array_diff_assoc($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection whose keys are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diffKeys($items) + { + return new static(array_diff_key($this->items, $this->getArrayableItems($items))); + } + + /** + * Execute a callback over each item. + * + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + if ($callback($item, $key) === false) { + break; + } + } + + return $this; + } + + /** + * Execute a callback over each nested chunk of items. + * + * @param callable $callback + * @return static + */ + public function eachSpread(callable $callback) + { + return $this->each(function ($chunk, $key) use ($callback) { + $chunk[] = $key; + + return $callback(...$chunk); + }); + } + + /** + * Determine if all items in the collection pass the given test. + * + * @param string|callable $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function every($key, $operator = null, $value = null) + { + if (func_num_args() == 1) { + $callback = $this->valueRetriever($key); + + foreach ($this->items as $k => $v) { + if (! $callback($v, $k)) { + return false; + } + } + + return true; + } + + return $this->every($this->operatorForWhere(...func_get_args())); + } + + /** + * Get all items except for those with the specified keys. + * + * @param \Illuminate\Support\Collection|mixed $keys + * @return static + */ + public function except($keys) + { + if ($keys instanceof self) { + $keys = $keys->all(); + } elseif (! is_array($keys)) { + $keys = func_get_args(); + } + + return new static(Arr::except($this->items, $keys)); + } + + /** + * Run a filter over each of the items. + * + * @param callable|null $callback + * @return static + */ + public function filter(callable $callback = null) + { + if ($callback) { + return new static(Arr::where($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * Apply the callback if the value is truthy. + * + * @param bool $value + * @param callable $callback + * @param callable $default + * @return mixed + */ + public function when($value, callable $callback, callable $default = null) + { + if ($value) { + return $callback($this, $value); + } elseif ($default) { + return $default($this, $value); + } + + return $this; + } + + /** + * Apply the callback if the value is falsy. + * + * @param bool $value + * @param callable $callback + * @param callable $default + * @return mixed + */ + public function unless($value, callable $callback, callable $default = null) + { + return $this->when(! $value, $callback, $default); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $operator + * @param mixed $value + * @return static + */ + public function where($key, $operator, $value = null) + { + return $this->filter($this->operatorForWhere(...func_get_args())); + } + + /** + * Get an operator checker callback. + * + * @param string $key + * @param string $operator + * @param mixed $value + * @return \Closure + */ + protected function operatorForWhere($key, $operator, $value = null) + { + if (func_num_args() == 2) { + $value = $operator; + + $operator = '='; + } + + return function ($item) use ($key, $operator, $value) { + $retrieved = data_get($item, $key); + + $strings = array_filter([$retrieved, $value], function ($value) { + return is_string($value) || (is_object($value) && method_exists($value, '__toString')); + }); + + if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) { + return in_array($operator, ['!=', '<>', '!==']); + } + + switch ($operator) { + default: + case '=': + case '==': return $retrieved == $value; + case '!=': + case '<>': return $retrieved != $value; + case '<': return $retrieved < $value; + case '>': return $retrieved > $value; + case '<=': return $retrieved <= $value; + case '>=': return $retrieved >= $value; + case '===': return $retrieved === $value; + case '!==': return $retrieved !== $value; + } + }; + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $value + * @return static + */ + public function whereStrict($key, $value) + { + return $this->where($key, '===', $value); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $values + * @param bool $strict + * @return static + */ + public function whereIn($key, $values, $strict = false) + { + $values = $this->getArrayableItems($values); + + return $this->filter(function ($item) use ($key, $values, $strict) { + return in_array(data_get($item, $key), $values, $strict); + }); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $values + * @return static + */ + public function whereInStrict($key, $values) + { + return $this->whereIn($key, $values, true); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $values + * @param bool $strict + * @return static + */ + public function whereNotIn($key, $values, $strict = false) + { + $values = $this->getArrayableItems($values); + + return $this->reject(function ($item) use ($key, $values, $strict) { + return in_array(data_get($item, $key), $values, $strict); + }); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $values + * @return static + */ + public function whereNotInStrict($key, $values) + { + return $this->whereNotIn($key, $values, true); + } + + /** + * Get the first item from the collection. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function first(callable $callback = null, $default = null) + { + return Arr::first($this->items, $callback, $default); + } + + /** + * Get the first item by the given key value pair. + * + * @param string $key + * @param mixed $operator + * @param mixed $value + * @return static + */ + public function firstWhere($key, $operator, $value = null) + { + return $this->first($this->operatorForWhere(...func_get_args())); + } + + /** + * Get a flattened array of the items in the collection. + * + * @param int $depth + * @return static + */ + public function flatten($depth = INF) + { + return new static(Arr::flatten($this->items, $depth)); + } + + /** + * Flip the items in the collection. + * + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * Remove an item from the collection by key. + * + * @param string|array $keys + * @return $this + */ + public function forget($keys) + { + foreach ((array) $keys as $key) { + $this->offsetUnset($key); + } + + return $this; + } + + /** + * Get an item from the collection by key. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if ($this->offsetExists($key)) { + return $this->items[$key]; + } + + return value($default); + } + + /** + * Group an associative array by a field or using a callback. + * + * @param callable|string $groupBy + * @param bool $preserveKeys + * @return static + */ + public function groupBy($groupBy, $preserveKeys = false) + { + if (is_array($groupBy)) { + $nextGroups = $groupBy; + + $groupBy = array_shift($nextGroups); + } + + $groupBy = $this->valueRetriever($groupBy); + + $results = []; + + foreach ($this->items as $key => $value) { + $groupKeys = $groupBy($value, $key); + + if (! is_array($groupKeys)) { + $groupKeys = [$groupKeys]; + } + + foreach ($groupKeys as $groupKey) { + $groupKey = is_bool($groupKey) ? (int) $groupKey : $groupKey; + + if (! array_key_exists($groupKey, $results)) { + $results[$groupKey] = new static; + } + + $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value); + } + } + + $result = new static($results); + + if (! empty($nextGroups)) { + return $result->map->groupBy($nextGroups, $preserveKeys); + } + + return $result; + } + + /** + * Key an associative array by a field or using a callback. + * + * @param callable|string $keyBy + * @return static + */ + public function keyBy($keyBy) + { + $keyBy = $this->valueRetriever($keyBy); + + $results = []; + + foreach ($this->items as $key => $item) { + $resolvedKey = $keyBy($item, $key); + + if (is_object($resolvedKey)) { + $resolvedKey = (string) $resolvedKey; + } + + $results[$resolvedKey] = $item; + } + + return new static($results); + } + + /** + * Determine if an item exists in the collection by key. + * + * @param mixed $key + * @return bool + */ + public function has($key) + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if (! $this->offsetExists($value)) { + return false; + } + } + + return true; + } + + /** + * Concatenate values of a given key as a string. + * + * @param string $value + * @param string $glue + * @return string + */ + public function implode($value, $glue = null) + { + $first = $this->first(); + + if (is_array($first) || is_object($first)) { + return implode($glue, $this->pluck($value)->all()); + } + + return implode($value, $this->items); + } + + /** + * Intersect the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function intersect($items) + { + return new static(array_intersect($this->items, $this->getArrayableItems($items))); + } + + /** + * Intersect the collection with the given items by key. + * + * @param mixed $items + * @return static + */ + public function intersectByKeys($items) + { + return new static(array_intersect_key( + $this->items, $this->getArrayableItems($items) + )); + } + + /** + * Determine if the collection is empty or not. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * Determine if the collection is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return ! $this->isEmpty(); + } + + /** + * Determine if the given value is callable, but not a string. + * + * @param mixed $value + * @return bool + */ + protected function useAsCallable($value) + { + return ! is_string($value) && is_callable($value); + } + + /** + * Get the keys of the collection items. + * + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * Get the last item from the collection. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function last(callable $callback = null, $default = null) + { + return Arr::last($this->items, $callback, $default); + } + + /** + * Get the values of a given key. + * + * @param string|array $value + * @param string|null $key + * @return static + */ + public function pluck($value, $key = null) + { + return new static(Arr::pluck($this->items, $value, $key)); + } + + /** + * Run a map over each of the items. + * + * @param callable $callback + * @return static + */ + public function map(callable $callback) + { + $keys = array_keys($this->items); + + $items = array_map($callback, $this->items, $keys); + + return new static(array_combine($keys, $items)); + } + + /** + * Run a map over each nested chunk of items. + * + * @param callable $callback + * @return static + */ + public function mapSpread(callable $callback) + { + return $this->map(function ($chunk, $key) use ($callback) { + $chunk[] = $key; + + return $callback(...$chunk); + }); + } + + /** + * Run a dictionary map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapToDictionary(callable $callback) + { + $dictionary = $this->map($callback)->reduce(function ($groups, $pair) { + $groups[key($pair)][] = reset($pair); + + return $groups; + }, []); + + return new static($dictionary); + } + + /** + * Run a grouping map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapToGroups(callable $callback) + { + $groups = $this->mapToDictionary($callback); + + return $groups->map([$this, 'make']); + } + + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapWithKeys(callable $callback) + { + $result = []; + + foreach ($this->items as $key => $value) { + $assoc = $callback($value, $key); + + foreach ($assoc as $mapKey => $mapValue) { + $result[$mapKey] = $mapValue; + } + } + + return new static($result); + } + + /** + * Map a collection and flatten the result by a single level. + * + * @param callable $callback + * @return static + */ + public function flatMap(callable $callback) + { + return $this->map($callback)->collapse(); + } + + /** + * Map the values into a new class. + * + * @param string $class + * @return static + */ + public function mapInto($class) + { + return $this->map(function ($value, $key) use ($class) { + return new $class($value, $key); + }); + } + + /** + * Get the max value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function max($callback = null) + { + $callback = $this->valueRetriever($callback); + + return $this->filter(function ($value) { + return ! is_null($value); + })->reduce(function ($result, $item) use ($callback) { + $value = $callback($item); + + return is_null($result) || $value > $result ? $value : $result; + }); + } + + /** + * Merge the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->getArrayableItems($items))); + } + + /** + * Create a collection by using this collection for keys and another for its values. + * + * @param mixed $values + * @return static + */ + public function combine($values) + { + return new static(array_combine($this->all(), $this->getArrayableItems($values))); + } + + /** + * Union the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function union($items) + { + return new static($this->items + $this->getArrayableItems($items)); + } + + /** + * Get the min value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function min($callback = null) + { + $callback = $this->valueRetriever($callback); + + return $this->filter(function ($value) { + return ! is_null($value); + })->reduce(function ($result, $item) use ($callback) { + $value = $callback($item); + + return is_null($result) || $value < $result ? $value : $result; + }); + } + + /** + * Create a new collection consisting of every n-th element. + * + * @param int $step + * @param int $offset + * @return static + */ + public function nth($step, $offset = 0) + { + $new = []; + + $position = 0; + + foreach ($this->items as $item) { + if ($position % $step === $offset) { + $new[] = $item; + } + + $position++; + } + + return new static($new); + } + + /** + * Get the items with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function only($keys) + { + if (is_null($keys)) { + return new static($this->items); + } + + if ($keys instanceof self) { + $keys = $keys->all(); + } + + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::only($this->items, $keys)); + } + + /** + * "Paginate" the collection by slicing it into a smaller collection. + * + * @param int $page + * @param int $perPage + * @return static + */ + public function forPage($page, $perPage) + { + $offset = max(0, ($page - 1) * $perPage); + + return $this->slice($offset, $perPage); + } + + /** + * Partition the collection into two arrays using the given callback or key. + * + * @param callable|string $callback + * @return static + */ + public function partition($callback) + { + $partitions = [new static, new static]; + + $callback = $this->valueRetriever($callback); + + foreach ($this->items as $key => $item) { + $partitions[(int) ! $callback($item, $key)][$key] = $item; + } + + return new static($partitions); + } + + /** + * Pass the collection to the given callback and return the result. + * + * @param callable $callback + * @return mixed + */ + public function pipe(callable $callback) + { + return $callback($this); + } + + /** + * Get and remove the last item from the collection. + * + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * Push an item onto the beginning of the collection. + * + * @param mixed $value + * @param mixed $key + * @return $this + */ + public function prepend($value, $key = null) + { + $this->items = Arr::prepend($this->items, $value, $key); + + return $this; + } + + /** + * Push an item onto the end of the collection. + * + * @param mixed $value + * @return $this + */ + public function push($value) + { + $this->offsetSet(null, $value); + + return $this; + } + + /** + * Push all of the given items onto the collection. + * + * @param \Traversable $source + * @return $this + */ + public function concat($source) + { + $result = new static($this); + + foreach ($source as $item) { + $result->push($item); + } + + return $result; + } + + /** + * Get and remove an item from the collection. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function pull($key, $default = null) + { + return Arr::pull($this->items, $key, $default); + } + + /** + * Put an item in the collection by key. + * + * @param mixed $key + * @param mixed $value + * @return $this + */ + public function put($key, $value) + { + $this->offsetSet($key, $value); + + return $this; + } + + /** + * Get one or a specified number of items randomly from the collection. + * + * @param int|null $number + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function random($number = null) + { + if (is_null($number)) { + return Arr::random($this->items); + } + + return new static(Arr::random($this->items, $number)); + } + + /** + * Reduce the collection to a single value. + * + * @param callable $callback + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * Create a collection of all elements that do not pass a given truth test. + * + * @param callable|mixed $callback + * @return static + */ + public function reject($callback) + { + if ($this->useAsCallable($callback)) { + return $this->filter(function ($value, $key) use ($callback) { + return ! $callback($value, $key); + }); + } + + return $this->filter(function ($item) use ($callback) { + return $item != $callback; + }); + } + + /** + * Reverse items order. + * + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items, true)); + } + + /** + * Search the collection for a given value and return the corresponding key if successful. + * + * @param mixed $value + * @param bool $strict + * @return mixed + */ + public function search($value, $strict = false) + { + if (! $this->useAsCallable($value)) { + return array_search($value, $this->items, $strict); + } + + foreach ($this->items as $key => $item) { + if (call_user_func($value, $item, $key)) { + return $key; + } + } + + return false; + } + + /** + * Get and remove the first item from the collection. + * + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * Shuffle the items in the collection. + * + * @param int $seed + * @return static + */ + public function shuffle($seed = null) + { + $items = $this->items; + + if (is_null($seed)) { + shuffle($items); + } else { + srand($seed); + + usort($items, function () { + return rand(-1, 1); + }); + } + + return new static($items); + } + + /** + * Slice the underlying collection array. + * + * @param int $offset + * @param int $length + * @return static + */ + public function slice($offset, $length = null) + { + return new static(array_slice($this->items, $offset, $length, true)); + } + + /** + * Split a collection into a certain number of groups. + * + * @param int $numberOfGroups + * @return static + */ + public function split($numberOfGroups) + { + if ($this->isEmpty()) { + return new static; + } + + $groupSize = ceil($this->count() / $numberOfGroups); + + return $this->chunk($groupSize); + } + + /** + * Chunk the underlying collection array. + * + * @param int $size + * @return static + */ + public function chunk($size) + { + if ($size <= 0) { + return new static; + } + + $chunks = []; + + foreach (array_chunk($this->items, $size, true) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * Sort through each item with a callback. + * + * @param callable|null $callback + * @return static + */ + public function sort(callable $callback = null) + { + $items = $this->items; + + $callback + ? uasort($items, $callback) + : asort($items); + + return new static($items); + } + + /** + * Sort the collection using the given callback. + * + * @param callable|string $callback + * @param int $options + * @param bool $descending + * @return static + */ + public function sortBy($callback, $options = SORT_REGULAR, $descending = false) + { + $results = []; + + $callback = $this->valueRetriever($callback); + + // First we will loop through the items and get the comparator from a callback + // function which we were given. Then, we will sort the returned values and + // and grab the corresponding values for the sorted keys from this array. + foreach ($this->items as $key => $value) { + $results[$key] = $callback($value, $key); + } + + $descending ? arsort($results, $options ?: SORT_REGULAR) + : asort($results, $options ?: SORT_REGULAR); + + // Once we have sorted all of the keys in the array, we will loop through them + // and grab the corresponding model so we can set the underlying items list + // to the sorted version. Then we'll just return the collection instance. + foreach (array_keys($results) as $key) { + $results[$key] = $this->items[$key]; + } + + return new static($results); + } + + /** + * Sort the collection in descending order using the given callback. + * + * @param callable|string $callback + * @param int $options + * @return static + */ + public function sortByDesc($callback, $options = SORT_REGULAR) + { + return $this->sortBy($callback, $options, true); + } + + /** + * Splice a portion of the underlying collection array. + * + * @param int $offset + * @param int|null $length + * @param mixed $replacement + * @return static + */ + public function splice($offset, $length = null, $replacement = []) + { + if (func_num_args() == 1) { + return new static(array_splice($this->items, $offset)); + } + + return new static(array_splice($this->items, $offset, $length, $replacement)); + } + + /** + * Get the sum of the given values. + * + * @param callable|string|null $callback + * @return mixed + */ + public function sum($callback = null) + { + if (is_null($callback)) { + return array_sum($this->items); + } + + $callback = $this->valueRetriever($callback); + + return $this->reduce(function ($result, $item) use ($callback) { + return $result + $callback($item); + }, 0); + } + + /** + * Take the first or last {$limit} items. + * + * @param int $limit + * @return static + */ + public function take($limit) + { + if ($limit < 0) { + return $this->slice($limit, abs($limit)); + } + + return $this->slice(0, $limit); + } + + /** + * Pass the collection to the given callback and then return it. + * + * @param callable $callback + * @return $this + */ + public function tap(callable $callback) + { + $callback(new static($this->items)); + + return $this; + } + + /** + * Transform each item in the collection using a callback. + * + * @param callable $callback + * @return $this + */ + public function transform(callable $callback) + { + $this->items = $this->map($callback)->all(); + + return $this; + } + + /** + * Return only unique items from the collection array. + * + * @param string|callable|null $key + * @param bool $strict + * @return static + */ + public function unique($key = null, $strict = false) + { + if (is_null($key)) { + return new static(array_unique($this->items, SORT_REGULAR)); + } + + $callback = $this->valueRetriever($key); + + $exists = []; + + return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) { + if (in_array($id = $callback($item, $key), $exists, $strict)) { + return true; + } + + $exists[] = $id; + }); + } + + /** + * Return only unique items from the collection array using strict comparison. + * + * @param string|callable|null $key + * @return static + */ + public function uniqueStrict($key = null) + { + return $this->unique($key, true); + } + + /** + * Reset the keys on the underlying array. + * + * @return static + */ + public function values() + { + return new static(array_values($this->items)); + } + + /** + * Get a value retrieving callback. + * + * @param string $value + * @return callable + */ + protected function valueRetriever($value) + { + if ($this->useAsCallable($value)) { + return $value; + } + + return function ($item) use ($value) { + return data_get($item, $value); + }; + } + + /** + * Zip the collection together with one or more arrays. + * + * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @param mixed ...$items + * @return static + */ + public function zip($items) + { + $arrayableItems = array_map(function ($items) { + return $this->getArrayableItems($items); + }, func_get_args()); + + $params = array_merge([function () { + return new static(func_get_args()); + }, $this->items], $arrayableItems); + + return new static(call_user_func_array('array_map', $params)); + } + + /** + * Pad collection to the specified length with a value. + * + * @param int $size + * @param mixed $value + * @return static + */ + public function pad($size, $value) + { + return new static(array_pad($this->items, $size, $value)); + } + + /** + * Get the collection of items as a plain array. + * + * @return array + */ + public function toArray() + { + return array_map(function ($value) { + return $value instanceof Arrayable ? $value->toArray() : $value; + }, $this->items); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + * : mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return array_map(function ($value) { + if ($value instanceof JsonSerializable) { + return $value->jsonSerialize(); + } elseif ($value instanceof Jsonable) { + return json_decode($value->toJson(), true); + } elseif ($value instanceof Arrayable) { + return $value->toArray(); + } + + return $value; + }, $this->items); + } + + /** + * Get the collection of items as JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->items); + } + + /** + * Get a CachingIterator instance. + * + * @param int $flags + * @return \CachingIterator + */ + public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING) + { + return new CachingIterator($this->getIterator(), $flags); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + public function count(): int + { + return count($this->items); + } + + /** + * Get a base Support collection instance from this collection. + * + * @return \Illuminate\Support\Collection + */ + public function toBase() + { + return new self($this); + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * @return bool + */ + public function offsetExists($key): bool + { + return array_key_exists($key, $this->items); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * @return mixed + * : mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->items[$key]; + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value): void + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key): void + { + unset($this->items[$key]); + } + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * Results array of items from Collection or Arrayable. + * + * @param mixed $items + * @return array + */ + protected function getArrayableItems($items) + { + if (is_array($items)) { + return $items; + } elseif ($items instanceof self) { + return $items->all(); + } elseif ($items instanceof Arrayable) { + return $items->toArray(); + } elseif ($items instanceof Jsonable) { + return json_decode($items->toJson(), true); + } elseif ($items instanceof JsonSerializable) { + return $items->jsonSerialize(); + } elseif ($items instanceof Traversable) { + return iterator_to_array($items); + } + + return (array) $items; + } + + /** + * Add a method to the list of proxied methods. + * + * @param string $method + * @return void + */ + public static function proxy($method) + { + static::$proxies[] = $method; + } + + /** + * Dynamically access collection proxies. + * + * @param string $key + * @return mixed + * + * @throws \Exception + */ + public function __get($key) + { + if (! in_array($key, static::$proxies)) { + throw new Exception("Property [{$key}] does not exist on this collection instance."); + } + + return new HigherOrderCollectionProxy($this, $key); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Fluent.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Fluent.php new file mode 100644 index 0000000..3e51ffa --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Fluent.php @@ -0,0 +1,196 @@ + $value) { + $this->attributes[$key] = $value; + } + } + + /** + * Get an attribute from the container. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + + return value($default); + } + + /** + * Get the attributes from the container. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Convert the Fluent instance to an array. + * + * @return array + */ + public function toArray() + { + return $this->attributes; + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + * : mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the Fluent instance to JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * @return bool + */ + public function offsetExists($offset): bool + { + return isset($this->attributes[$offset]); + } + + /** + * Get the value for a given offset. + * + * @param string $offset + * @return mixed + * : mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Set the value at the given offset. + * + * @param string $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value): void + { + $this->attributes[$offset] = $value; + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset): void + { + unset($this->attributes[$offset]); + } + + /** + * Handle dynamic calls to the container to set attributes. + * + * @param string $method + * @param array $parameters + * @return $this + */ + public function __call($method, $parameters) + { + $this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true; + + return $this; + } + + /** + * Dynamically retrieve the value of an attribute. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Dynamically set the value of an attribute. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->offsetSet($key, $value); + } + + /** + * Dynamically check if an attribute is set. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return $this->offsetExists($key); + } + + /** + * Dynamically unset an attribute. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + $this->offsetUnset($key); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/MessageBag.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/MessageBag.php new file mode 100644 index 0000000..3838f02 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/MessageBag.php @@ -0,0 +1,397 @@ + $value) { + $this->messages[$key] = $value instanceof Arrayable + ? $value->toArray() : (array) $value; + } + } + + /** + * Get the keys present in the message bag. + * + * @return array + */ + public function keys() + { + return array_keys($this->messages); + } + + /** + * Add a message to the bag. + * + * @param string $key + * @param string $message + * @return $this + */ + public function add($key, $message) + { + if ($this->isUnique($key, $message)) { + $this->messages[$key][] = $message; + } + + return $this; + } + + /** + * Determine if a key and message combination already exists. + * + * @param string $key + * @param string $message + * @return bool + */ + protected function isUnique($key, $message) + { + $messages = (array) $this->messages; + + return ! isset($messages[$key]) || ! in_array($message, $messages[$key]); + } + + /** + * Merge a new array of messages into the bag. + * + * @param \Illuminate\Contracts\Support\MessageProvider|array $messages + * @return $this + */ + public function merge($messages) + { + if ($messages instanceof MessageProvider) { + $messages = $messages->getMessageBag()->getMessages(); + } + + $this->messages = array_merge_recursive($this->messages, $messages); + + return $this; + } + + /** + * Determine if messages exist for all of the given keys. + * + * @param array|string $key + * @return bool + */ + public function has($key) + { + if (is_null($key)) { + return $this->any(); + } + + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $key) { + if ($this->first($key) === '') { + return false; + } + } + + return true; + } + + /** + * Determine if messages exist for any of the given keys. + * + * @param array|string $keys + * @return bool + */ + public function hasAny($keys = []) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + foreach ($keys as $key) { + if ($this->has($key)) { + return true; + } + } + + return false; + } + + /** + * Get the first message from the bag for a given key. + * + * @param string $key + * @param string $format + * @return string + */ + public function first($key = null, $format = null) + { + $messages = is_null($key) ? $this->all($format) : $this->get($key, $format); + + $firstMessage = Arr::first($messages, null, ''); + + return is_array($firstMessage) ? Arr::first($firstMessage) : $firstMessage; + } + + /** + * Get all of the messages from the bag for a given key. + * + * @param string $key + * @param string $format + * @return array + */ + public function get($key, $format = null) + { + // If the message exists in the container, we will transform it and return + // the message. Otherwise, we'll check if the key is implicit & collect + // all the messages that match a given key and output it as an array. + if (array_key_exists($key, $this->messages)) { + return $this->transform( + $this->messages[$key], $this->checkFormat($format), $key + ); + } + + if (Str::contains($key, '*')) { + return $this->getMessagesForWildcardKey($key, $format); + } + + return []; + } + + /** + * Get the messages for a wildcard key. + * + * @param string $key + * @param string|null $format + * @return array + */ + protected function getMessagesForWildcardKey($key, $format) + { + return collect($this->messages) + ->filter(function ($messages, $messageKey) use ($key) { + return Str::is($key, $messageKey); + }) + ->map(function ($messages, $messageKey) use ($format) { + return $this->transform( + $messages, $this->checkFormat($format), $messageKey + ); + })->all(); + } + + /** + * Get all of the messages for every key in the bag. + * + * @param string $format + * @return array + */ + public function all($format = null) + { + $format = $this->checkFormat($format); + + $all = []; + + foreach ($this->messages as $key => $messages) { + $all = array_merge($all, $this->transform($messages, $format, $key)); + } + + return $all; + } + + /** + * Get all of the unique messages for every key in the bag. + * + * @param string $format + * @return array + */ + public function unique($format = null) + { + return array_unique($this->all($format)); + } + + /** + * Format an array of messages. + * + * @param array $messages + * @param string $format + * @param string $messageKey + * @return array + */ + protected function transform($messages, $format, $messageKey) + { + return collect((array) $messages) + ->map(function ($message) use ($format, $messageKey) { + // We will simply spin through the given messages and transform each one + // replacing the :message place holder with the real message allowing + // the messages to be easily formatted to each developer's desires. + return str_replace([':message', ':key'], [$message, $messageKey], $format); + })->all(); + } + + /** + * Get the appropriate format based on the given format. + * + * @param string $format + * @return string + */ + protected function checkFormat($format) + { + return $format ?: $this->format; + } + + /** + * Get the raw messages in the container. + * + * @return array + */ + public function messages() + { + return $this->messages; + } + + /** + * Get the raw messages in the container. + * + * @return array + */ + public function getMessages() + { + return $this->messages(); + } + + /** + * Get the messages for the instance. + * + * @return \Illuminate\Support\MessageBag + */ + public function getMessageBag() + { + return $this; + } + + /** + * Get the default message format. + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Set the default message format. + * + * @param string $format + * @return \Illuminate\Support\MessageBag + */ + public function setFormat($format = ':message') + { + $this->format = $format; + + return $this; + } + + /** + * Determine if the message bag has any messages. + * + * @return bool + */ + public function isEmpty() + { + return ! $this->any(); + } + + /** + * Determine if the message bag has any messages. + * + * @return bool + */ + public function isNotEmpty() + { + return $this->any(); + } + + /** + * Determine if the message bag has any messages. + * + * @return bool + */ + public function any() + { + return $this->count() > 0; + } + + /** + * Get the number of messages in the container. + * + * @return int + */ + public function count(): int + { + return count($this->messages, COUNT_RECURSIVE) - count($this->messages); + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return $this->getMessages(); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + * : mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Convert the message bag to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Optional.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Optional.php new file mode 100644 index 0000000..9736dcb --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Optional.php @@ -0,0 +1,112 @@ +value = $value; + } + + /** + * Dynamically access a property on the underlying object. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + if (is_object($this->value)) { + return $this->value->{$key}; + } + } + + /** + * Dynamically pass a method to the underlying object. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (static::hasMacro($method)) { + return $this->macroCall($method, $parameters); + } + + if (is_object($this->value)) { + return $this->value->{$method}(...$parameters); + } + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * @return bool + */ + public function offsetExists($key): bool + { + return Arr::accessible($this->value) && Arr::exists($this->value, $key); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * @return mixed + * : mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return Arr::get($this->value, $key); + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value): void + { + if (Arr::accessible($this->value)) { + $this->value[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key): void + { + if (Arr::accessible($this->value)) { + unset($this->value[$key]); + } + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Str.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Str.php new file mode 100644 index 0000000..7a1835e --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/Str.php @@ -0,0 +1,683 @@ + $val) { + $value = str_replace($val, $key, $value); + } + + return preg_replace('/[^\x20-\x7E]/u', '', $value); + } + + /** + * Get the portion of a string before a given value. + * + * @param string $subject + * @param string $search + * @return string + */ + public static function before($subject, $search) + { + return $search === '' ? $subject : explode($search, $subject)[0]; + } + + /** + * Convert a value to camel case. + * + * @param string $value + * @return string + */ + public static function camel($value) + { + if (isset(static::$camelCache[$value])) { + return static::$camelCache[$value]; + } + + return static::$camelCache[$value] = lcfirst(static::studly($value)); + } + + /** + * Determine if a given string contains a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function contains($haystack, $needles) + { + foreach ((array) $needles as $needle) { + if ($needle !== '' && mb_strpos($haystack ?? '', $needle) !== false) { + return true; + } + } + + return false; + } + + /** + * Determine if a given string ends with a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function endsWith($haystack, $needles) + { + foreach ((array) $needles as $needle) { + if (substr($haystack, -strlen($needle)) === (string) $needle) { + return true; + } + } + + return false; + } + + /** + * Cap a string with a single instance of a given value. + * + * @param string $value + * @param string $cap + * @return string + */ + public static function finish($value, $cap) + { + $quoted = preg_quote($cap, '/'); + + return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap; + } + + /** + * Determine if a given string matches a given pattern. + * + * @param string|array $pattern + * @param string $value + * @return bool + */ + public static function is($pattern, $value) + { + $patterns = is_array($pattern) ? $pattern : (array) $pattern; + + if (empty($patterns)) { + return false; + } + + foreach ($patterns as $pattern) { + // If the given value is an exact match we can of course return true right + // from the beginning. Otherwise, we will translate asterisks and do an + // actual pattern match against the two strings to see if they match. + if ($pattern == $value) { + return true; + } + + $pattern = preg_quote($pattern, '#'); + + // Asterisks are translated into zero-or-more regular expression wildcards + // to make it convenient to check if the strings starts with the given + // pattern such as "library/*", making any string check convenient. + $pattern = str_replace('\*', '.*', $pattern); + + if (preg_match('#^'.$pattern.'\z#u', $value ?? '') === 1) { + return true; + } + } + + return false; + } + + /** + * Convert a string to kebab case. + * + * @param string $value + * @return string + */ + public static function kebab($value) + { + return static::snake($value, '-'); + } + + /** + * Return the length of the given string. + * + * @param string $value + * @param string $encoding + * @return int + */ + public static function length($value, $encoding = null) + { + if ($encoding) { + return mb_strlen($value, $encoding); + } + + return mb_strlen($value); + } + + /** + * Limit the number of characters in a string. + * + * @param string $value + * @param int $limit + * @param string $end + * @return string + */ + public static function limit($value, $limit = 100, $end = '...') + { + if (mb_strwidth($value, 'UTF-8') <= $limit) { + return $value; + } + + return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end; + } + + /** + * Convert the given string to lower-case. + * + * @param string $value + * @return string + */ + public static function lower($value) + { + return mb_strtolower($value, 'UTF-8'); + } + + /** + * Limit the number of words in a string. + * + * @param string $value + * @param int $words + * @param string $end + * @return string + */ + public static function words($value, $words = 100, $end = '...') + { + preg_match('/^\s*+(?:\S++\s*+){1,'.$words.'}/u', $value, $matches); + + if (! isset($matches[0]) || static::length($value) === static::length($matches[0])) { + return $value; + } + + return rtrim($matches[0]).$end; + } + + /** + * Parse a Class@method style callback into class and method. + * + * @param string $callback + * @param string|null $default + * @return array + */ + public static function parseCallback($callback, $default = null) + { + return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default]; + } + + /** + * Get the plural form of an English word. + * + * @param string $value + * @param int $count + * @return string + */ + public static function plural($value, $count = 2) + { + return Pluralizer::plural($value, $count); + } + + /** + * Generate a more truly "random" alpha-numeric string. + * + * @param int $length + * @return string + */ + public static function random($length = 16) + { + $string = ''; + + while (($len = strlen($string)) < $length) { + $size = $length - $len; + + $bytes = random_bytes($size); + + $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); + } + + return $string; + } + + /** + * Replace a given value in the string sequentially with an array. + * + * @param string $search + * @param array $replace + * @param string $subject + * @return string + */ + public static function replaceArray($search, array $replace, $subject) + { + foreach ($replace as $value) { + $subject = static::replaceFirst($search, $value, $subject); + } + + return $subject; + } + + /** + * Replace the first occurrence of a given value in the string. + * + * @param string $search + * @param string $replace + * @param string $subject + * @return string + */ + public static function replaceFirst($search, $replace, $subject) + { + if ($search == '') { + return $subject ?? ''; + } + + $position = strpos($subject, $search); + + if ($position !== false) { + return substr_replace($subject, $replace ?? '', $position, strlen($search)) ?? ''; + } + + return $subject ?? ''; + } + + /** + * Replace the last occurrence of a given value in the string. + * + * @param string $search + * @param string $replace + * @param string $subject + * @return string + */ + public static function replaceLast($search, $replace, $subject) + { + $position = strrpos($subject, $search); + + if ($position !== false) { + return substr_replace($subject, $replace ?? '', $position, strlen($search)); + } + + return $subject; + } + + /** + * Begin a string with a single instance of a given value. + * + * @param string $value + * @param string $prefix + * @return string + */ + public static function start($value, $prefix) + { + $quoted = preg_quote($prefix, '/'); + + return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value); + } + + /** + * Convert the given string to upper-case. + * + * @param string $value + * @return string + */ + public static function upper($value) + { + return mb_strtoupper($value, 'UTF-8'); + } + + /** + * Convert the given string to title case. + * + * @param string $value + * @return string + */ + public static function title($value) + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } + + /** + * Get the singular form of an English word. + * + * @param string $value + * @return string + */ + public static function singular($value) + { + return Pluralizer::singular($value); + } + + /** + * Generate a URL friendly "slug" from a given string. + * + * @param string $title + * @param string $separator + * @param string $language + * @return string + */ + public static function slug($title, $separator = '-', $language = 'en') + { + $title = static::ascii($title, $language); + + // Convert all dashes/underscores into separator + $flip = $separator == '-' ? '_' : '-'; + + $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title); + + // Replace @ with the word 'at' + $title = str_replace('@', $separator.'at'.$separator, $title); + + // Remove all characters that are not the separator, letters, numbers, or whitespace. + $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', mb_strtolower($title)); + + // Replace all separator characters and whitespace by a single separator + $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title); + + return trim($title, $separator); + } + + /** + * Convert a string to snake case. + * + * @param string $value + * @param string $delimiter + * @return string + */ + public static function snake($value, $delimiter = '_') + { + $key = $value; + + if (isset(static::$snakeCache[$key][$delimiter])) { + return static::$snakeCache[$key][$delimiter]; + } + + if (! ctype_lower($value)) { + $value = preg_replace('/\s+/u', '', ucwords($value)); + + $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value)); + } + + return static::$snakeCache[$key][$delimiter] = $value; + } + + /** + * Determine if a given string starts with a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function startsWith($haystack, $needles) + { + foreach ((array) $needles as $needle) { + if ($needle !== '' && substr($haystack, 0, strlen($needle)) === (string) $needle) { + return true; + } + } + + return false; + } + + /** + * Convert a value to studly caps case. + * + * @param string $value + * @return string + */ + public static function studly($value) + { + $key = $value; + + if (isset(static::$studlyCache[$key])) { + return static::$studlyCache[$key]; + } + + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return static::$studlyCache[$key] = str_replace(' ', '', $value); + } + + /** + * Returns the portion of string specified by the start and length parameters. + * + * @param string $string + * @param int $start + * @param int|null $length + * @return string + */ + public static function substr($string, $start, $length = null) + { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + /** + * Make a string's first character uppercase. + * + * @param string $string + * @return string + */ + public static function ucfirst($string) + { + return static::upper(static::substr($string, 0, 1)).static::substr($string, 1); + } + + /** + * Returns the replacements for the ascii method. + * + * Note: Adapted from Stringy\Stringy. + * + * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt + * + * @return array + */ + protected static function charsArray() + { + static $charsArray; + + if (isset($charsArray)) { + return $charsArray; + } + + return $charsArray = [ + '0' => ['°', '₀', '۰', '0'], + '1' => ['¹', '₁', '۱', '1'], + '2' => ['²', '₂', '۲', '2'], + '3' => ['³', '₃', '۳', '3'], + '4' => ['⁴', '₄', '۴', '٤', '4'], + '5' => ['⁵', '₅', '۵', '٥', '5'], + '6' => ['⁶', '₆', '۶', '٦', '6'], + '7' => ['⁷', '₇', '۷', '7'], + '8' => ['⁸', '₈', '۸', '8'], + '9' => ['⁹', '₉', '۹', '9'], + 'a' => ['à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', 'a', 'ä'], + 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'], + 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'], + 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'], + 'e' => ['é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', 'e'], + 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'], + 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', 'g'], + 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'], + 'i' => ['í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', 'इ', 'ی', 'i'], + 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'], + 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک', 'k'], + 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', 'l'], + 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'], + 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ', 'n'], + 'o' => ['ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', 'o', 'ö'], + 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'], + 'q' => ['ყ', 'q'], + 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'], + 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს', 's'], + 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ', 't'], + 'u' => ['ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', 'u', 'ў', 'ü'], + 'v' => ['в', 'ვ', 'ϐ', 'v'], + 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'], + 'x' => ['χ', 'ξ', 'x'], + 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'], + 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'], + 'aa' => ['ع', 'आ', 'آ'], + 'ae' => ['æ', 'ǽ'], + 'ai' => ['ऐ'], + 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'], + 'dj' => ['ђ', 'đ'], + 'dz' => ['џ', 'ძ'], + 'ei' => ['ऍ'], + 'gh' => ['غ', 'ღ'], + 'ii' => ['ई'], + 'ij' => ['ij'], + 'kh' => ['х', 'خ', 'ხ'], + 'lj' => ['љ'], + 'nj' => ['њ'], + 'oe' => ['ö', 'œ', 'ؤ'], + 'oi' => ['ऑ'], + 'oii' => ['ऒ'], + 'ps' => ['ψ'], + 'sh' => ['ш', 'შ', 'ش'], + 'shch' => ['щ'], + 'ss' => ['ß'], + 'sx' => ['ŝ'], + 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'], + 'ts' => ['ц', 'ც', 'წ'], + 'ue' => ['ü'], + 'uu' => ['ऊ'], + 'ya' => ['я'], + 'yu' => ['ю'], + 'zh' => ['ж', 'ჟ', 'ژ'], + '(c)' => ['©'], + 'A' => ['Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', 'A', 'Ä'], + 'B' => ['Б', 'Β', 'ब', 'B'], + 'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ', 'C'], + 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', 'D'], + 'E' => ['É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', 'Є', 'Ə', 'E'], + 'F' => ['Ф', 'Φ', 'F'], + 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'], + 'H' => ['Η', 'Ή', 'Ħ', 'H'], + 'I' => ['Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', 'I'], + 'J' => ['J'], + 'K' => ['К', 'Κ', 'K'], + 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'], + 'M' => ['М', 'Μ', 'M'], + 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'], + 'O' => ['Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', 'O', 'Ö'], + 'P' => ['П', 'Π', 'P'], + 'Q' => ['Q'], + 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'], + 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'], + 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'], + 'U' => ['Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', 'Ǘ', 'Ǚ', 'Ǜ', 'U', 'Ў', 'Ü'], + 'V' => ['В', 'V'], + 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'], + 'X' => ['Χ', 'Ξ', 'X'], + 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'], + 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'], + 'AE' => ['Æ', 'Ǽ'], + 'Ch' => ['Ч'], + 'Dj' => ['Ђ'], + 'Dz' => ['Џ'], + 'Gx' => ['Ĝ'], + 'Hx' => ['Ĥ'], + 'Ij' => ['IJ'], + 'Jx' => ['Ĵ'], + 'Kh' => ['Х'], + 'Lj' => ['Љ'], + 'Nj' => ['Њ'], + 'Oe' => ['Œ'], + 'Ps' => ['Ψ'], + 'Sh' => ['Ш'], + 'Shch' => ['Щ'], + 'Ss' => ['ẞ'], + 'Th' => ['Þ'], + 'Ts' => ['Ц'], + 'Ya' => ['Я'], + 'Yu' => ['Ю'], + 'Zh' => ['Ж'], + ' ' => ["\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", "\xEF\xBE\xA0"], + ]; + } + + /** + * Returns the language specific replacements for the ascii method. + * + * Note: Adapted from Stringy\Stringy. + * + * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt + * + * @param string $language + * @return array|null + */ + protected static function languageSpecificCharsArray($language) + { + static $languageSpecific; + + if (! isset($languageSpecific)) { + $languageSpecific = [ + 'bg' => [ + ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'], + ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'], + ], + 'de' => [ + ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'], + ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], + ], + ]; + } + + return $languageSpecific[$language] ?? null; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/ViewErrorBag.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/ViewErrorBag.php new file mode 100644 index 0000000..ff9da4f --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Support/ViewErrorBag.php @@ -0,0 +1,130 @@ +bags[$key]); + } + + /** + * Get a MessageBag instance from the bags. + * + * @param string $key + * @return \Illuminate\Contracts\Support\MessageBag + */ + public function getBag($key) + { + return Arr::get($this->bags, $key) ?: new MessageBag; + } + + /** + * Get all the bags. + * + * @return array + */ + public function getBags() + { + return $this->bags; + } + + /** + * Add a new MessageBag instance to the bags. + * + * @param string $key + * @param \Illuminate\Contracts\Support\MessageBag $bag + * @return $this + */ + public function put($key, MessageBagContract $bag) + { + $this->bags[$key] = $bag; + + return $this; + } + + /** + * Determine if the default message bag has any messages. + * + * @return bool + */ + public function any() + { + return $this->count() > 0; + } + + /** + * Get the number of messages in the default bag. + * + * @return int + */ + public function count(): int + { + return $this->getBag('default')->count(); + } + + /** + * Dynamically call methods on the default bag. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->getBag('default')->$method(...$parameters); + } + + /** + * Dynamically access a view error bag. + * + * @param string $key + * @return \Illuminate\Contracts\Support\MessageBag + */ + public function __get($key) + { + return $this->getBag($key); + } + + /** + * Dynamically set a view error bag. + * + * @param string $key + * @param \Illuminate\Contracts\Support\MessageBag $value + * @return void + */ + public function __set($key, $value) + { + $this->put($key, $value); + } + + /** + * Convert the default bag to its string representation. + * + * @return string + */ + public function __toString() + { + return (string) $this->getBag('default'); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Validation/Concerns/FormatsMessages.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Validation/Concerns/FormatsMessages.php new file mode 100644 index 0000000..87a6683 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Validation/Concerns/FormatsMessages.php @@ -0,0 +1,384 @@ +getInlineMessage($attribute, $rule); + + // First we will retrieve the custom message for the validation rule if one + // exists. If a custom validation message is being used we'll return the + // custom message, otherwise we'll keep searching for a valid message. + if (! is_null($inlineMessage)) { + return $inlineMessage; + } + + $lowerRule = Str::snake($rule); + + $customMessage = $this->getCustomMessageFromTranslator( + $customKey = "validation.custom.{$attribute}.{$lowerRule}" + ); + + // First we check for a custom defined validation message for the attribute + // and rule. This allows the developer to specify specific messages for + // only some attributes and rules that need to get specially formed. + if ($customMessage !== $customKey) { + return $customMessage; + } + + // If the rule being validated is a "size" rule, we will need to gather the + // specific error message for the type of attribute being validated such + // as a number, file or string which all have different message types. + elseif (in_array($rule, $this->sizeRules)) { + return $this->getSizeMessage($attribute, $rule); + } + + // Finally, if no developer specified messages have been set, and no other + // special messages apply for this rule, we will just pull the default + // messages out of the translator service for this validation rule. + $key = "validation.{$lowerRule}"; + + if ($key != ($value = $this->translator->trans($key))) { + return $value; + } + + return $this->getFromLocalArray( + $attribute, $lowerRule, $this->fallbackMessages + ) ?: $key; + } + + /** + * Get the proper inline error message for standard and size rules. + * + * @param string $attribute + * @param string $rule + * @return string|null + */ + protected function getInlineMessage($attribute, $rule) + { + $inlineEntry = $this->getFromLocalArray($attribute, Str::snake($rule)); + + return is_array($inlineEntry) && in_array($rule, $this->sizeRules) + ? $inlineEntry[$this->getAttributeType($attribute)] + : $inlineEntry; + } + + /** + * Get the inline message for a rule if it exists. + * + * @param string $attribute + * @param string $lowerRule + * @param array|null $source + * @return string|null + */ + protected function getFromLocalArray($attribute, $lowerRule, $source = null) + { + $source = $source ?: $this->customMessages; + + $keys = ["{$attribute}.{$lowerRule}", $lowerRule]; + + // First we will check for a custom message for an attribute specific rule + // message for the fields, then we will check for a general custom line + // that is not attribute specific. If we find either we'll return it. + foreach ($keys as $key) { + foreach (array_keys($source) as $sourceKey) { + if (Str::is($sourceKey, $key)) { + return $source[$sourceKey]; + } + } + } + } + + /** + * Get the custom error message from translator. + * + * @param string $key + * @return string + */ + protected function getCustomMessageFromTranslator($key) + { + if (($message = $this->translator->trans($key)) !== $key) { + return $message; + } + + // If an exact match was not found for the key, we will collapse all of these + // messages and loop through them and try to find a wildcard match for the + // given key. Otherwise, we will simply return the key's value back out. + $shortKey = preg_replace( + '/^validation\.custom\./', '', $key + ); + + return $this->getWildcardCustomMessages(Arr::dot( + (array) $this->translator->trans('validation.custom') + ), $shortKey, $key); + } + + /** + * Check the given messages for a wildcard key. + * + * @param array $messages + * @param string $search + * @param string $default + * @return string + */ + protected function getWildcardCustomMessages($messages, $search, $default) + { + foreach ($messages as $key => $message) { + if ($search === $key || (Str::contains($key, ['*']) && Str::is($key, $search))) { + return $message; + } + } + + return $default; + } + + /** + * Get the proper error message for an attribute and size rule. + * + * @param string $attribute + * @param string $rule + * @return string + */ + protected function getSizeMessage($attribute, $rule) + { + $lowerRule = Str::snake($rule); + + // There are three different types of size validations. The attribute may be + // either a number, file, or string so we will check a few things to know + // which type of value it is and return the correct line for that type. + $type = $this->getAttributeType($attribute); + + $key = "validation.{$lowerRule}.{$type}"; + + return $this->translator->trans($key); + } + + /** + * Get the data type of the given attribute. + * + * @param string $attribute + * @return string + */ + protected function getAttributeType($attribute) + { + // We assume that the attributes present in the file array are files so that + // means that if the attribute does not have a numeric rule and the files + // list doesn't have it we'll just consider it a string by elimination. + if ($this->hasRule($attribute, $this->numericRules)) { + return 'numeric'; + } elseif ($this->hasRule($attribute, ['Array'])) { + return 'array'; + } elseif ($this->getValue($attribute) instanceof UploadedFile) { + return 'file'; + } + + return 'string'; + } + + /** + * Replace all error message place-holders with actual values. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + public function makeReplacements($message, $attribute, $rule, $parameters) + { + $message = $this->replaceAttributePlaceholder( + $message, $this->getDisplayableAttribute($attribute) + ); + + $message = $this->replaceInputPlaceholder($message, $attribute); + + if (isset($this->replacers[Str::snake($rule)])) { + return $this->callReplacer($message, $attribute, Str::snake($rule), $parameters, $this); + } elseif (method_exists($this, $replacer = "replace{$rule}")) { + return $this->$replacer($message, $attribute, $rule, $parameters); + } + + return $message; + } + + /** + * Get the displayable name of the attribute. + * + * @param string $attribute + * @return string + */ + public function getDisplayableAttribute($attribute) + { + $primaryAttribute = $this->getPrimaryAttribute($attribute); + + $expectedAttributes = $attribute != $primaryAttribute + ? [$attribute, $primaryAttribute] : [$attribute]; + + foreach ($expectedAttributes as $name) { + // The developer may dynamically specify the array of custom attributes on this + // validator instance. If the attribute exists in this array it is used over + // the other ways of pulling the attribute name for this given attributes. + if (isset($this->customAttributes[$name])) { + return $this->customAttributes[$name]; + } + + // We allow for a developer to specify language lines for any attribute in this + // application, which allows flexibility for displaying a unique displayable + // version of the attribute name instead of the name used in an HTTP POST. + if ($line = $this->getAttributeFromTranslations($name)) { + return $line; + } + } + + // When no language line has been specified for the attribute and it is also + // an implicit attribute we will display the raw attribute's name and not + // modify it with any of these replacements before we display the name. + if (isset($this->implicitAttributes[$primaryAttribute])) { + return $attribute; + } + + return str_replace('_', ' ', Str::snake($attribute)); + } + + /** + * Get the given attribute from the attribute translations. + * + * @param string $name + * @return string + */ + protected function getAttributeFromTranslations($name) + { + return Arr::get($this->translator->trans('validation.attributes'), $name); + } + + /** + * Replace the :attribute placeholder in the given message. + * + * @param string $message + * @param string $value + * @return string + */ + protected function replaceAttributePlaceholder($message, $value) + { + return str_replace( + [':attribute', ':ATTRIBUTE', ':Attribute'], + [$value, Str::upper($value), Str::ucfirst($value)], + $message + ); + } + + /** + * Replace the :input placeholder in the given message. + * + * @param string $message + * @param string $attribute + * @return string + */ + protected function replaceInputPlaceholder($message, $attribute) + { + $actualValue = $this->getValue($attribute); + + if (is_scalar($actualValue) || is_null($actualValue)) { + $message = str_replace(':input', $actualValue ?? '', $message); + } + + return $message; + } + + /** + * Get the displayable name of the value. + * + * @param string $attribute + * @param mixed $value + * @return string + */ + public function getDisplayableValue($attribute, $value) + { + if (isset($this->customValues[$attribute][$value])) { + return $this->customValues[$attribute][$value]; + } + + $key = "validation.values.{$attribute}.{$value}"; + + if (($line = $this->translator->trans($key)) !== $key) { + return $line; + } + + return $value; + } + + /** + * Transform an array of attributes to their displayable form. + * + * @param array $values + * @return array + */ + protected function getAttributeList(array $values) + { + $attributes = []; + + // For each attribute in the list we will simply get its displayable form as + // this is convenient when replacing lists of parameters like some of the + // replacement functions do when formatting out the validation message. + foreach ($values as $key => $value) { + $attributes[$key] = $this->getDisplayableAttribute($value); + } + + return $attributes; + } + + /** + * Call a custom validator message replacer. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @param \Illuminate\Validation\Validator $validator + * @return string|null + */ + protected function callReplacer($message, $attribute, $rule, $parameters, $validator) + { + $callback = $this->replacers[$rule]; + + if ($callback instanceof Closure) { + return call_user_func_array($callback, func_get_args()); + } elseif (is_string($callback)) { + return $this->callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator); + } + } + + /** + * Call a class based validator message replacer. + * + * @param string $callback + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @param \Illuminate\Validation\Validator $validator + * @return string + */ + protected function callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator) + { + list($class, $method) = Str::parseCallback($callback, 'replace'); + + return call_user_func_array([$this->container->make($class), $method], array_slice(func_get_args(), 1)); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php new file mode 100644 index 0000000..acd3abe --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -0,0 +1,1478 @@ +validateRequired($attribute, $value) && in_array($value, $acceptable, true); + } + + /** + * Validate that an attribute is an active URL. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateActiveUrl($attribute, $value) + { + if (! is_string($value)) { + return false; + } + + if ($url = parse_url($value, PHP_URL_HOST)) { + try { + return count(dns_get_record($url, DNS_A | DNS_AAAA)) > 0; + } catch (Exception $e) { + return false; + } + } + + return false; + } + + /** + * "Break" on first validation fail. + * + * Always returns true, just lets us put "bail" in rules. + * + * @return bool + */ + public function validateBail() + { + return true; + } + + /** + * Validate the date is before a given date. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateBefore($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'before'); + + return $this->compareDates($attribute, $value, $parameters, '<'); + } + + /** + * Validate the date is before or equal a given date. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateBeforeOrEqual($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'before_or_equal'); + + return $this->compareDates($attribute, $value, $parameters, '<='); + } + + /** + * Validate the date is after a given date. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateAfter($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'after'); + + return $this->compareDates($attribute, $value, $parameters, '>'); + } + + /** + * Validate the date is equal or after a given date. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateAfterOrEqual($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'after_or_equal'); + + return $this->compareDates($attribute, $value, $parameters, '>='); + } + + /** + * Compare a given date against another using an operator. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @param string $operator + * @return bool + */ + protected function compareDates($attribute, $value, $parameters, $operator) + { + if (! is_string($value) && ! is_numeric($value) && ! $value instanceof DateTimeInterface) { + return false; + } + + if ($format = $this->getDateFormat($attribute)) { + return $this->checkDateTimeOrder( + $format, $value, $this->getValue($parameters[0]) ?: $parameters[0], $operator + ); + } + + if (! $date = $this->getDateTimestamp($parameters[0])) { + $date = $this->getDateTimestamp($this->getValue($parameters[0])); + } + + return $this->compare($this->getDateTimestamp($value), $date, $operator); + } + + /** + * Get the date format for an attribute if it has one. + * + * @param string $attribute + * @return string|null + */ + protected function getDateFormat($attribute) + { + if ($result = $this->getRule($attribute, 'DateFormat')) { + return $result[1][0]; + } + } + + /** + * Get the date timestamp. + * + * @param mixed $value + * @return int + */ + protected function getDateTimestamp($value) + { + return $value instanceof DateTimeInterface ? $value->getTimestamp() : strtotime($value); + } + + /** + * Given two date/time strings, check that one is after the other. + * + * @param string $format + * @param string $first + * @param string $second + * @param string $operator + * @return bool + */ + protected function checkDateTimeOrder($format, $first, $second, $operator) + { + $first = $this->getDateTimeWithOptionalFormat($format, $first); + + $second = $this->getDateTimeWithOptionalFormat($format, $second); + + return ($first && $second) && ($this->compare($first, $second, $operator)); + } + + /** + * Get a DateTime instance from a string. + * + * @param string $format + * @param string $value + * @return \DateTime|null + */ + protected function getDateTimeWithOptionalFormat($format, $value) + { + if ($date = DateTime::createFromFormat('!'.$format, $value)) { + return $date; + } + + try { + return new DateTime($value); + } catch (Exception $e) { + // + } + } + + /** + * Validate that an attribute contains only alphabetic characters. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateAlpha($attribute, $value) + { + return is_string($value) && preg_match('/^[\pL\pM]+$/u', $value); + } + + /** + * Validate that an attribute contains only alpha-numeric characters, dashes, and underscores. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateAlphaDash($attribute, $value) + { + if (! is_string($value) && ! is_numeric($value)) { + return false; + } + + return preg_match('/^[\pL\pM\pN_-]+$/u', $value) > 0; + } + + /** + * Validate that an attribute contains only alpha-numeric characters. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateAlphaNum($attribute, $value) + { + if (! is_string($value) && ! is_numeric($value)) { + return false; + } + + return preg_match('/^[\pL\pM\pN]+$/u', $value) > 0; + } + + /** + * Validate that an attribute is an array. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateArray($attribute, $value) + { + return is_array($value); + } + + /** + * Validate the size of an attribute is between a set of values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateBetween($attribute, $value, $parameters) + { + $this->requireParameterCount(2, $parameters, 'between'); + + $size = $this->getSize($attribute, $value); + + return $size >= $parameters[0] && $size <= $parameters[1]; + } + + /** + * Validate that an attribute is a boolean. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateBoolean($attribute, $value) + { + $acceptable = [true, false, 0, 1, '0', '1']; + + return in_array($value, $acceptable, true); + } + + /** + * Validate that an attribute has a matching confirmation. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateConfirmed($attribute, $value) + { + return $this->validateSame($attribute, $value, [$attribute.'_confirmation']); + } + + /** + * Validate that an attribute is a valid date. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateDate($attribute, $value) + { + if ($value instanceof DateTimeInterface) { + return true; + } + + if ((! is_string($value) && ! is_numeric($value)) || strtotime($value) === false) { + return false; + } + + $date = date_parse($value); + + return checkdate($date['month'], $date['day'], $date['year']); + } + + /** + * Validate that an attribute matches a date format. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateDateFormat($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'date_format'); + + if (! is_string($value) && ! is_numeric($value)) { + return false; + } + + $format = $parameters[0]; + + $date = DateTime::createFromFormat('!'.$format, $value); + + return $date && $date->format($format) == $value; + } + + /** + * Validate that an attribute is equal to another date. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateDateEquals($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'date_equals'); + + return $this->compareDates($attribute, $value, $parameters, '='); + } + + /** + * Validate that an attribute is different from another attribute. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateDifferent($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'different'); + + foreach ($parameters as $parameter) { + $other = Arr::get($this->data, $parameter); + + if (is_null($other) || $value === $other) { + return false; + } + } + + return true; + } + + /** + * Validate that an attribute has a given number of digits. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateDigits($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'digits'); + + return ! preg_match('/[^0-9]/', $value) + && strlen((string) $value) == $parameters[0]; + } + + /** + * Validate that an attribute is between a given number of digits. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateDigitsBetween($attribute, $value, $parameters) + { + $this->requireParameterCount(2, $parameters, 'digits_between'); + + $length = strlen((string) $value); + + return ! preg_match('/[^0-9]/', $value) + && $length >= $parameters[0] && $length <= $parameters[1]; + } + + /** + * Validate the dimensions of an image matches the given values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateDimensions($attribute, $value, $parameters) + { + if (! $this->isValidFileInstance($value) || ! $sizeDetails = @getimagesize($value->getRealPath())) { + return false; + } + + $this->requireParameterCount(1, $parameters, 'dimensions'); + + list($width, $height) = $sizeDetails; + + $parameters = $this->parseNamedParameters($parameters); + + if ($this->failsBasicDimensionChecks($parameters, $width, $height) || + $this->failsRatioCheck($parameters, $width, $height)) { + return false; + } + + return true; + } + + /** + * Test if the given width and height fail any conditions. + * + * @param array $parameters + * @param int $width + * @param int $height + * @return bool + */ + protected function failsBasicDimensionChecks($parameters, $width, $height) + { + return (isset($parameters['width']) && $parameters['width'] != $width) || + (isset($parameters['min_width']) && $parameters['min_width'] > $width) || + (isset($parameters['max_width']) && $parameters['max_width'] < $width) || + (isset($parameters['height']) && $parameters['height'] != $height) || + (isset($parameters['min_height']) && $parameters['min_height'] > $height) || + (isset($parameters['max_height']) && $parameters['max_height'] < $height); + } + + /** + * Determine if the given parameters fail a dimension ratio check. + * + * @param array $parameters + * @param int $width + * @param int $height + * @return bool + */ + protected function failsRatioCheck($parameters, $width, $height) + { + if (! isset($parameters['ratio'])) { + return false; + } + + list($numerator, $denominator) = array_replace( + [1, 1], array_filter(sscanf($parameters['ratio'], '%f/%d')) + ); + + $precision = 1 / max($width, $height); + + return abs($numerator / $denominator - $width / $height) > $precision; + } + + /** + * Validate an attribute is unique among other values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateDistinct($attribute, $value, $parameters) + { + $attributeName = $this->getPrimaryAttribute($attribute); + + $attributeData = ValidationData::extractDataFromPath( + ValidationData::getLeadingExplicitAttributePath($attributeName), $this->data + ); + + $pattern = str_replace('\*', '[^.]+', preg_quote($attributeName, '#')); + + $data = Arr::where(Arr::dot($attributeData), function ($value, $key) use ($attribute, $pattern) { + return $key != $attribute && (bool) preg_match('#^'.$pattern.'\z#u', $key); + }); + + if (in_array('ignore_case', $parameters)) { + return empty(preg_grep('/^'.preg_quote($value, '/').'$/iu', $data)); + } + + return ! in_array($value, array_values($data)); + } + + /** + * Validate that an attribute is a valid e-mail address. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateEmail($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; + } + + /** + * Validate the existence of an attribute value in a database table. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateExists($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'exists'); + + list($connection, $table) = $this->parseTable($parameters[0]); + + // The second parameter position holds the name of the column that should be + // verified as existing. If this parameter is not specified we will guess + // that the columns being "verified" shares the given attribute's name. + $column = $this->getQueryColumn($parameters, $attribute); + + $expected = (is_array($value)) ? count($value) : 1; + + return $this->getExistCount( + $connection, $table, $column, $value, $parameters + ) >= $expected; + } + + /** + * Get the number of records that exist in storage. + * + * @param mixed $connection + * @param string $table + * @param string $column + * @param mixed $value + * @param array $parameters + * @return int + */ + protected function getExistCount($connection, $table, $column, $value, $parameters) + { + $verifier = $this->getPresenceVerifierFor($connection); + + $extra = $this->getExtraConditions( + array_values(array_slice($parameters, 2)) + ); + + if ($this->currentRule instanceof Exists) { + $extra = array_merge($extra, $this->currentRule->queryCallbacks()); + } + + return is_array($value) + ? $verifier->getMultiCount($table, $column, $value, $extra) + : $verifier->getCount($table, $column, $value, null, null, $extra); + } + + /** + * Validate the uniqueness of an attribute value on a given database table. + * + * If a database column is not specified, the attribute will be used. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateUnique($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'unique'); + + list($connection, $table) = $this->parseTable($parameters[0]); + + // The second parameter position holds the name of the column that needs to + // be verified as unique. If this parameter isn't specified we will just + // assume that this column to be verified shares the attribute's name. + $column = $this->getQueryColumn($parameters, $attribute); + + list($idColumn, $id) = [null, null]; + + if (isset($parameters[2])) { + list($idColumn, $id) = $this->getUniqueIds($parameters); + } + + // The presence verifier is responsible for counting rows within this store + // mechanism which might be a relational database or any other permanent + // data store like Redis, etc. We will use it to determine uniqueness. + $verifier = $this->getPresenceVerifierFor($connection); + + $extra = $this->getUniqueExtra($parameters); + + if ($this->currentRule instanceof Unique) { + $extra = array_merge($extra, $this->currentRule->queryCallbacks()); + } + + return $verifier->getCount( + $table, $column, $value, $id, $idColumn, $extra + ) == 0; + } + + /** + * Get the excluded ID column and value for the unique rule. + * + * @param array $parameters + * @return array + */ + protected function getUniqueIds($parameters) + { + $idColumn = $parameters[3] ?? 'id'; + + return [$idColumn, $this->prepareUniqueId($parameters[2])]; + } + + /** + * Prepare the given ID for querying. + * + * @param mixed $id + * @return int + */ + protected function prepareUniqueId($id) + { + if (preg_match('/\[(.*)\]/', $id, $matches)) { + $id = $this->getValue($matches[1]); + } + + if (strtolower($id) == 'null') { + $id = null; + } + + if (filter_var($id, FILTER_VALIDATE_INT) !== false) { + $id = (int) $id; + } + + return $id; + } + + /** + * Get the extra conditions for a unique rule. + * + * @param array $parameters + * @return array + */ + protected function getUniqueExtra($parameters) + { + if (isset($parameters[4])) { + return $this->getExtraConditions(array_slice($parameters, 4)); + } + + return []; + } + + /** + * Parse the connection / table for the unique / exists rules. + * + * @param string $table + * @return array + */ + protected function parseTable($table) + { + return Str::contains($table, '.') ? explode('.', $table, 2) : [null, $table]; + } + + /** + * Get the column name for an exists / unique query. + * + * @param array $parameters + * @param string $attribute + * @return bool + */ + protected function getQueryColumn($parameters, $attribute) + { + return isset($parameters[1]) && $parameters[1] !== 'NULL' + ? $parameters[1] : $this->guessColumnForQuery($attribute); + } + + /** + * Guess the database column from the given attribute name. + * + * @param string $attribute + * @return string + */ + public function guessColumnForQuery($attribute) + { + if (in_array($attribute, Arr::collapse($this->implicitAttributes)) + && ! is_numeric($last = last(explode('.', $attribute)))) { + return $last; + } + + return $attribute; + } + + /** + * Get the extra conditions for a unique / exists rule. + * + * @param array $segments + * @return array + */ + protected function getExtraConditions(array $segments) + { + $extra = []; + + $count = count($segments); + + for ($i = 0; $i < $count; $i += 2) { + $extra[$segments[$i]] = $segments[$i + 1]; + } + + return $extra; + } + + /** + * Validate the given value is a valid file. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateFile($attribute, $value) + { + return $this->isValidFileInstance($value); + } + + /** + * Validate the given attribute is filled if it is present. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateFilled($attribute, $value) + { + if (Arr::has($this->data, $attribute)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate the MIME type of a file is an image MIME type. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateImage($attribute, $value) + { + return $this->validateMimes($attribute, $value, ['jpeg', 'png', 'gif', 'bmp', 'svg']); + } + + /** + * Validate an attribute is contained within a list of values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateIn($attribute, $value, $parameters) + { + if (is_array($value) && $this->hasRule($attribute, 'Array')) { + foreach ($value as $element) { + if (is_array($element)) { + return false; + } + } + + return count(array_diff($value, $parameters)) == 0; + } + + return ! is_array($value) && in_array((string) $value, $parameters); + } + + /** + * Validate that the values of an attribute is in another attribute. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateInArray($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'in_array'); + + $explicitPath = ValidationData::getLeadingExplicitAttributePath($parameters[0]); + + $attributeData = ValidationData::extractDataFromPath($explicitPath, $this->data); + + $otherValues = Arr::where(Arr::dot($attributeData), function ($value, $key) use ($parameters) { + return Str::is($parameters[0], $key); + }); + + return in_array($value, $otherValues); + } + + /** + * Validate that an attribute is an integer. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateInteger($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_INT) !== false; + } + + /** + * Validate that an attribute is a valid IP. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateIp($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_IP) !== false; + } + + /** + * Validate that an attribute is a valid IPv4. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateIpv4($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false; + } + + /** + * Validate that an attribute is a valid IPv6. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateIpv6($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false; + } + + /** + * Validate the attribute is a valid JSON string. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateJson($attribute, $value) + { + if (! is_scalar($value) && ! method_exists($value, '__toString')) { + return false; + } + + json_decode($value); + + return json_last_error() === JSON_ERROR_NONE; + } + + /** + * Validate the size of an attribute is less than a maximum value. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateMax($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'max'); + + if ($value instanceof UploadedFile && ! $value->isValid()) { + return false; + } + + return $this->getSize($attribute, $value) <= $parameters[0]; + } + + /** + * Validate the guessed extension of a file upload is in a set of file extensions. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateMimes($attribute, $value, $parameters) + { + if (! $this->isValidFileInstance($value)) { + return false; + } + + if ($this->shouldBlockPhpUpload($value, $parameters)) { + return false; + } + + return $value->getPath() !== '' && in_array($value->guessExtension(), $parameters); + } + + /** + * Validate the MIME type of a file upload attribute is in a set of MIME types. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateMimetypes($attribute, $value, $parameters) + { + if (! $this->isValidFileInstance($value)) { + return false; + } + + if ($this->shouldBlockPhpUpload($value, $parameters)) { + return false; + } + + return $value->getPath() !== '' && + (in_array($value->getMimeType(), $parameters) || + in_array(explode('/', $value->getMimeType())[0].'/*', $parameters)); + } + + /** + * Check if PHP uploads are explicitly allowed. + * + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function shouldBlockPhpUpload($value, $parameters) + { + if (in_array('php', $parameters)) { + return false; + } + + return ($value instanceof UploadedFile) + ? trim(strtolower($value->getClientOriginalExtension())) === 'php' + : trim(strtolower($value->getExtension())) === 'php'; + } + + /** + * Validate the size of an attribute is greater than a minimum value. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateMin($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'min'); + + return $this->getSize($attribute, $value) >= $parameters[0]; + } + + /** + * "Indicate" validation should pass if value is null. + * + * Always returns true, just lets us put "nullable" in rules. + * + * @return bool + */ + public function validateNullable() + { + return true; + } + + /** + * Validate an attribute is not contained within a list of values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateNotIn($attribute, $value, $parameters) + { + return ! $this->validateIn($attribute, $value, $parameters); + } + + /** + * Validate that an attribute is numeric. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateNumeric($attribute, $value) + { + return is_numeric($value); + } + + /** + * Validate that an attribute exists even if not filled. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validatePresent($attribute, $value) + { + return Arr::has($this->data, $attribute); + } + + /** + * Validate that an attribute passes a regular expression check. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateRegex($attribute, $value, $parameters) + { + if (! is_string($value) && ! is_numeric($value)) { + return false; + } + + $this->requireParameterCount(1, $parameters, 'regex'); + + return preg_match($parameters[0], $value) > 0; + } + + /** + * Validate that a required attribute exists. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateRequired($attribute, $value) + { + if (is_null($value)) { + return false; + } elseif (is_string($value) && trim($value) === '') { + return false; + } elseif ((is_array($value) || $value instanceof Countable) && count($value) < 1) { + return false; + } elseif ($value instanceof File) { + return (string) $value->getPath() !== ''; + } + + return true; + } + + /** + * Validate that an attribute exists when another attribute has a given value. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + public function validateRequiredIf($attribute, $value, $parameters) + { + $this->requireParameterCount(2, $parameters, 'required_if'); + + $other = Arr::get($this->data, $parameters[0]); + + $values = array_slice($parameters, 1); + + if (is_bool($other)) { + $values = $this->convertValuesToBoolean($values); + } + + if (in_array($other, $values)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Convert the given values to boolean if they are string "true" / "false". + * + * @param array $values + * @return array + */ + protected function convertValuesToBoolean($values) + { + return array_map(function ($value) { + if ($value === 'true') { + return true; + } elseif ($value === 'false') { + return false; + } + + return $value; + }, $values); + } + + /** + * Validate that an attribute exists when another attribute does not have a given value. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + public function validateRequiredUnless($attribute, $value, $parameters) + { + $this->requireParameterCount(2, $parameters, 'required_unless'); + + $data = Arr::get($this->data, $parameters[0]); + + $values = array_slice($parameters, 1); + + if (! in_array($data, $values)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate that an attribute exists when any other attribute exists. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + public function validateRequiredWith($attribute, $value, $parameters) + { + if (! $this->allFailingRequired($parameters)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate that an attribute exists when all other attributes exists. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + public function validateRequiredWithAll($attribute, $value, $parameters) + { + if (! $this->anyFailingRequired($parameters)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate that an attribute exists when another attribute does not. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + public function validateRequiredWithout($attribute, $value, $parameters) + { + if ($this->anyFailingRequired($parameters)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate that an attribute exists when all other attributes do not. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + public function validateRequiredWithoutAll($attribute, $value, $parameters) + { + if ($this->allFailingRequired($parameters)) { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Determine if any of the given attributes fail the required test. + * + * @param array $attributes + * @return bool + */ + protected function anyFailingRequired(array $attributes) + { + foreach ($attributes as $key) { + if (! $this->validateRequired($key, $this->getValue($key))) { + return true; + } + } + + return false; + } + + /** + * Determine if all of the given attributes fail the required test. + * + * @param array $attributes + * @return bool + */ + protected function allFailingRequired(array $attributes) + { + foreach ($attributes as $key) { + if ($this->validateRequired($key, $this->getValue($key))) { + return false; + } + } + + return true; + } + + /** + * Validate that two attributes match. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateSame($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'same'); + + $other = Arr::get($this->data, $parameters[0]); + + return $value === $other; + } + + /** + * Validate the size of an attribute. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateSize($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'size'); + + return $this->getSize($attribute, $value) == $parameters[0]; + } + + /** + * "Validate" optional attributes. + * + * Always returns true, just lets us put sometimes in rules. + * + * @return bool + */ + public function validateSometimes() + { + return true; + } + + /** + * Validate that an attribute is a string. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateString($attribute, $value) + { + return is_string($value); + } + + /** + * Validate that an attribute is a valid timezone. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateTimezone($attribute, $value) + { + try { + new DateTimeZone($value); + } catch (Exception $e) { + return false; + } catch (Throwable $e) { + return false; + } + + return true; + } + + /** + * Validate that an attribute is a valid URL. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + public function validateUrl($attribute, $value) + { + if (! is_string($value)) { + return false; + } + + /* + * This pattern is derived from Symfony\Component\Validator\Constraints\UrlValidator (2.7.4). + * + * (c) Fabien Potencier http://symfony.com + */ + $pattern = '~^ + ((aaa|aaas|about|acap|acct|acr|adiumxtra|afp|afs|aim|apt|attachment|aw|barion|beshare|bitcoin|blob|bolo|callto|cap|chrome|chrome-extension|cid|coap|coaps|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-playcontainer|dlna-playsingle|dns|dntp|dtn|dvb|ed2k|example|facetime|fax|feed|feedready|file|filesystem|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|ham|hcp|http|https|iax|icap|icon|im|imap|info|iotdisco|ipn|ipp|ipps|irc|irc6|ircs|iris|iris.beep|iris.lwz|iris.xpc|iris.xpcs|itms|jabber|jar|jms|keyparc|lastfm|ldap|ldaps|magnet|mailserver|mailto|maps|market|message|mid|mms|modem|ms-help|ms-settings|ms-settings-airplanemode|ms-settings-bluetooth|ms-settings-camera|ms-settings-cellular|ms-settings-cloudstorage|ms-settings-emailandaccounts|ms-settings-language|ms-settings-location|ms-settings-lock|ms-settings-nfctransactions|ms-settings-notifications|ms-settings-power|ms-settings-privacy|ms-settings-proximity|ms-settings-screenrotation|ms-settings-wifi|ms-settings-workplace|msnim|msrp|msrps|mtqp|mumble|mupdate|mvn|news|nfs|ni|nih|nntp|notes|oid|opaquelocktoken|pack|palm|paparazzi|pkcs11|platform|pop|pres|prospero|proxy|psyc|query|redis|rediss|reload|res|resource|rmi|rsync|rtmfp|rtmp|rtsp|rtsps|rtspu|secondlife|s3|service|session|sftp|sgn|shttp|sieve|sip|sips|skype|smb|sms|smtp|snews|snmp|soap.beep|soap.beeps|soldat|spotify|ssh|steam|stun|stuns|submit|svn|tag|teamspeak|tel|teliaeid|telnet|tftp|things|thismessage|tip|tn3270|turn|turns|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|videotex|view-source|wais|webcal|ws|wss|wtai|wyciwyg|xcon|xcon-userid|xfire|xmlrpc\.beep|xmlrpc.beeps|xmpp|xri|ymsgr|z39\.50|z39\.50r|z39\.50s)):// # protocol + (([\pL\pN-]+:)?([\pL\pN-]+)@)? # basic auth + ( + ([\pL\pN\pS\-\.])+(\.?([\pL]|xn\-\-[\pL\pN-]+)+\.?) # a domain name + | # or + \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address + | # or + \[ + (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) + \] # an IPv6 address + ) + (:[0-9]+)? # a port (optional) + (/?|/\S+|\?\S*|\#\S*) # a /, nothing, a / with something, a query or a fragment + $~ixu'; + + return preg_match($pattern, $value) > 0; + } + + /** + * Get the size of an attribute. + * + * @param string $attribute + * @param mixed $value + * @return mixed + */ + protected function getSize($attribute, $value) + { + $hasNumeric = $this->hasRule($attribute, $this->numericRules); + + // This method will determine if the attribute is a number, string, or file and + // return the proper size accordingly. If it is a number, then number itself + // is the size. If it is a file, we take kilobytes, and for a string the + // entire length of the string will be considered the attribute size. + if (is_numeric($value) && $hasNumeric) { + return $value; + } elseif (is_array($value)) { + return count($value); + } elseif ($value instanceof File) { + return $value->getSize() / 1024; + } + + return mb_strlen($value ?? ''); + } + + /** + * Check that the given value is a valid file instance. + * + * @param mixed $value + * @return bool + */ + public function isValidFileInstance($value) + { + if ($value instanceof UploadedFile && ! $value->isValid()) { + return false; + } + + return $value instanceof File; + } + + /** + * Determine if a comparison passes between the given values. + * + * @param mixed $first + * @param mixed $second + * @param string $operator + * @return bool + */ + protected function compare($first, $second, $operator) + { + switch ($operator) { + case '<': + return $first < $second; + case '>': + return $first > $second; + case '<=': + return $first <= $second; + case '>=': + return $first >= $second; + case '=': + return $first == $second; + default: + throw new InvalidArgumentException; + } + } + + /** + * Parse named parameters to $key => $value items. + * + * @param array $parameters + * @return array + */ + protected function parseNamedParameters($parameters) + { + return array_reduce($parameters, function ($result, $item) { + list($key, $value) = array_pad(explode('=', $item, 2), 2, null); + + $result[$key] = $value; + + return $result; + }); + } + + /** + * Require a certain number of parameters to be present. + * + * @param int $count + * @param array $parameters + * @param string $rule + * @return void + * + * @throws \InvalidArgumentException + */ + protected function requireParameterCount($count, $parameters, $rule) + { + if (count($parameters) < $count) { + throw new InvalidArgumentException("Validation rule $rule requires at least $count parameters."); + } + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/View/Compilers/Compiler.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/View/Compilers/Compiler.php new file mode 100644 index 0000000..b15c9df --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/View/Compilers/Compiler.php @@ -0,0 +1,74 @@ +files = $files; + $this->cachePath = $cachePath; + } + + /** + * Get the path to the compiled version of a view. + * + * @param string $path + * @return string + */ + public function getCompiledPath($path) + { + return $this->cachePath.'/'.sha1('v2'.$path).'.php'; + } + + /** + * Determine if the view at the given path is expired. + * + * @param string $path + * @return bool + */ + public function isExpired($path) + { + $compiled = $this->getCompiledPath($path); + + // If the compiled file doesn't exist we will indicate that the view is expired + // so that it can be re-compiled. Else, we will verify the last modification + // of the views is less than the modification times of the compiled views. + if (! $this->files->exists($compiled)) { + return true; + } + + return $this->files->lastModified($path) >= + $this->files->lastModified($compiled); + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesLayouts.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesLayouts.php new file mode 100644 index 0000000..f828210 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/View/Compilers/Concerns/CompilesLayouts.php @@ -0,0 +1,118 @@ +stripParentheses($expression); + + $echo = "make({$expression}, array_except(get_defined_vars(), array('__data', '__path')))->render(); ?>"; + + $this->footer[] = $echo; + + return ''; + } + + /** + * Compile the section statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileSection($expression) + { + $this->lastSection = trim($expression, "()'\" "); + + return "startSection{$expression}; ?>"; + } + + /** + * Replace the @parent directive to a placeholder. + * + * @return string + */ + protected function compileParent() + { + //return ViewFactory::parentPlaceholder($this->lastSection ?: ''); + + $escapedLastSection = strtr($this->lastSection, ['\\' => '\\\\', "'" => "\\'"]); + + return ""; + } + + /** + * Compile the yield statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileYield($expression) + { + return "yieldContent{$expression}; ?>"; + } + + /** + * Compile the show statements into valid PHP. + * + * @return string + */ + protected function compileShow() + { + return 'yieldSection(); ?>'; + } + + /** + * Compile the append statements into valid PHP. + * + * @return string + */ + protected function compileAppend() + { + return 'appendSection(); ?>'; + } + + /** + * Compile the overwrite statements into valid PHP. + * + * @return string + */ + protected function compileOverwrite() + { + return 'stopSection(true); ?>'; + } + + /** + * Compile the stop statements into valid PHP. + * + * @return string + */ + protected function compileStop() + { + return 'stopSection(); ?>'; + } + + /** + * Compile the end-section statements into valid PHP. + * + * @return string + */ + protected function compileEndsection() + { + return 'stopSection(); ?>'; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/View/Concerns/ManagesLayouts.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/View/Concerns/ManagesLayouts.php new file mode 100644 index 0000000..b96049c --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/View/Concerns/ManagesLayouts.php @@ -0,0 +1,243 @@ +sectionStack[] = $section; + } + } else { + $this->extendSection($section, $content instanceof View ? $content : e($content)); + } + } + + /** + * Inject inline content into a section. + * + * @param string $section + * @param string $content + * @return void + */ + public function inject($section, $content) + { + $this->startSection($section, $content); + } + + /** + * Stop injecting content into a section and return its contents. + * + * @return string + */ + public function yieldSection() + { + if (empty($this->sectionStack)) { + return ''; + } + + return $this->yieldContent($this->stopSection()); + } + + /** + * Stop injecting content into a section. + * + * @param bool $overwrite + * @return string + * @throws \InvalidArgumentException + */ + public function stopSection($overwrite = false) + { + if (empty($this->sectionStack)) { + throw new InvalidArgumentException('Cannot end a section without first starting one.'); + } + + $last = array_pop($this->sectionStack); + + if ($overwrite) { + $this->sections[$last] = ob_get_clean(); + } else { + $this->extendSection($last, ob_get_clean()); + } + + return $last; + } + + /** + * Stop injecting content into a section and append it. + * + * @return string + * @throws \InvalidArgumentException + */ + public function appendSection() + { + if (empty($this->sectionStack)) { + throw new InvalidArgumentException('Cannot end a section without first starting one.'); + } + + $last = array_pop($this->sectionStack); + + if (isset($this->sections[$last])) { + $this->sections[$last] .= ob_get_clean(); + } else { + $this->sections[$last] = ob_get_clean(); + } + + return $last; + } + + /** + * Append content to a given section. + * + * @param string $section + * @param string $content + * @return void + */ + protected function extendSection($section, $content) + { + if (isset($this->sections[$section])) { + $content = str_replace(static::parentPlaceholder($section), $content, $this->sections[$section]); + } + + $this->sections[$section] = $content; + } + + /** + * Get the string contents of a section. + * + * @param string $section + * @param string $default + * @return string + */ + public function yieldContent($section, $default = '') + { + $sectionContent = $default instanceof View ? $default : e($default); + + if (isset($this->sections[$section])) { + $sectionContent = $this->sections[$section]; + } + + $sectionContent = str_replace('@@parent', '--parent--holder--', $sectionContent); + + return str_replace( + '--parent--holder--', '@parent', str_replace(static::parentPlaceholder($section), '', $sectionContent) + ); + } + + /** + * Get the parent placeholder for the current request. + * + * @param string $section + * @return string + */ + public static function parentPlaceholder($section = '') + { + if (! isset(static::$parentPlaceholder[$section])) { + //static::$parentPlaceholder[$section] = '##parent-placeholder-'.sha1($section).'##'; + $salt = static::parentPlaceholderSalt(); + + static::$parentPlaceholder[$section] = '##parent-placeholder-'.sha1($salt.$section).'##'; + } + + return static::$parentPlaceholder[$section]; + } + + /** + * Get the parent placeholder salt. + * + * @return string + */ + protected static function parentPlaceholderSalt() + { + if (! static::$parentPlaceholderSalt) { + return static::$parentPlaceholderSalt = Str::random(40); + } + + return static::$parentPlaceholderSalt; + } + + /** + * Check if section exists. + * + * @param string $name + * @return bool + */ + public function hasSection($name) + { + return array_key_exists($name, $this->sections); + } + + /** + * Get the contents of a section. + * + * @param string $name + * @param string $default + * @return mixed + */ + public function getSection($name, $default = null) + { + return $this->getSections()[$name] ?? $default; + } + + /** + * Get the entire array of sections. + * + * @return array + */ + public function getSections() + { + return $this->sections; + } + + /** + * Flush all of the sections. + * + * @return void + */ + public function flushSections() + { + $this->sections = []; + $this->sectionStack = []; + } +} diff --git a/freescout-dist/overrides/laravel/framework/src/Illuminate/View/View.php b/freescout-dist/overrides/laravel/framework/src/Illuminate/View/View.php new file mode 100644 index 0000000..7125e32 --- /dev/null +++ b/freescout-dist/overrides/laravel/framework/src/Illuminate/View/View.php @@ -0,0 +1,416 @@ +view = $view; + $this->path = $path; + $this->engine = $engine; + $this->factory = $factory; + + $this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data; + } + + /** + * Get the string contents of the view. + * + * @param callable|null $callback + * @return string + * + * @throws \Throwable + */ + public function render(callable $callback = null) + { + try { + $contents = $this->renderContents(); + + $response = isset($callback) ? call_user_func($callback, $this, $contents) : null; + + // Once we have the contents of the view, we will flush the sections if we are + // done rendering all views so that there is nothing left hanging over when + // another view gets rendered in the future by the application developer. + $this->factory->flushStateIfDoneRendering(); + + return ! is_null($response) ? $response : $contents; + } catch (Exception $e) { + $this->factory->flushState(); + + throw $e; + } catch (Throwable $e) { + $this->factory->flushState(); + + throw $e; + } + } + + /** + * Get the contents of the view instance. + * + * @return string + */ + protected function renderContents() + { + // We will keep track of the amount of views being rendered so we can flush + // the section after the complete rendering operation is done. This will + // clear out the sections for any separate views that may be rendered. + $this->factory->incrementRender(); + + $this->factory->callComposer($this); + + $contents = $this->getContents(); + + // Once we've finished rendering the view, we'll decrement the render count + // so that each sections get flushed out next time a view is created and + // no old sections are staying around in the memory of an environment. + $this->factory->decrementRender(); + + return $contents; + } + + /** + * Get the evaluated contents of the view. + * + * @return string + */ + protected function getContents() + { + return $this->engine->get($this->path, $this->gatherData()); + } + + /** + * Get the data bound to the view instance. + * + * @return array + */ + protected function gatherData() + { + $data = array_merge($this->factory->getShared(), $this->data); + + foreach ($data as $key => $value) { + if ($value instanceof Renderable) { + $data[$key] = $value->render(); + } + } + + return $data; + } + + /** + * Get the sections of the rendered view. + * + * @return string + */ + public function renderSections() + { + return $this->render(function () { + return $this->factory->getSections(); + }); + } + + /** + * Add a piece of data to the view. + * + * @param string|array $key + * @param mixed $value + * @return $this + */ + public function with($key, $value = null) + { + if (is_array($key)) { + $this->data = array_merge($this->data, $key); + } else { + $this->data[$key] = $value; + } + + return $this; + } + + /** + * Add a view instance to the view data. + * + * @param string $key + * @param string $view + * @param array $data + * @return $this + */ + public function nest($key, $view, array $data = []) + { + return $this->with($key, $this->factory->make($view, $data)); + } + + /** + * Add validation errors to the view. + * + * @param \Illuminate\Contracts\Support\MessageProvider|array $provider + * @return $this + */ + public function withErrors($provider) + { + $this->with('errors', $this->formatErrors($provider)); + + return $this; + } + + /** + * Format the given message provider into a MessageBag. + * + * @param \Illuminate\Contracts\Support\MessageProvider|array $provider + * @return \Illuminate\Support\MessageBag + */ + protected function formatErrors($provider) + { + return $provider instanceof MessageProvider + ? $provider->getMessageBag() : new MessageBag((array) $provider); + } + + /** + * Get the name of the view. + * + * @return string + */ + public function name() + { + return $this->getName(); + } + + /** + * Get the name of the view. + * + * @return string + */ + public function getName() + { + return $this->view; + } + + /** + * Get the array of view data. + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Get the path to the view file. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Set the path to the view. + * + * @param string $path + * @return void + */ + public function setPath($path) + { + $this->path = $path; + } + + /** + * Get the view factory instance. + * + * @return \Illuminate\View\Factory + */ + public function getFactory() + { + return $this->factory; + } + + /** + * Get the view's rendering engine. + * + * @return \Illuminate\Contracts\View\Engine + */ + public function getEngine() + { + return $this->engine; + } + + /** + * Determine if a piece of data is bound. + * + * @param string $key + * @return bool + */ + public function offsetExists($key): bool + { + return array_key_exists($key, $this->data); + } + + /** + * Get a piece of bound data to the view. + * + * @param string $key + * @return mixed + * : mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->data[$key]; + } + + /** + * Set a piece of data on the view. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value): void + { + $this->with($key, $value); + } + + /** + * Unset a piece of data from the view. + * + * @param string $key + * @return void + */ + public function offsetUnset($key): void + { + unset($this->data[$key]); + } + + /** + * Get a piece of data from the view. + * + * @param string $key + * @return mixed + */ + public function &__get($key) + { + return $this->data[$key]; + } + + /** + * Set a piece of data on the view. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->with($key, $value); + } + + /** + * Check if a piece of data is bound to the view. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return isset($this->data[$key]); + } + + /** + * Remove a piece of bound data from the view. + * + * @param string $key + * @return bool + */ + public function __unset($key) + { + unset($this->data[$key]); + } + + /** + * Dynamically bind parameters to the view. + * + * @param string $method + * @param array $parameters + * @return \Illuminate\View\View + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if (! Str::startsWith($method, 'with')) { + throw new BadMethodCallException("Method [$method] does not exist on view."); + } + + return $this->with(Str::camel(substr($method, 4)), $parameters[0]); + } + + /** + * Get the string contents of the view. + * + * @return string + */ + public function __toString() + { + return $this->render(); + } +} diff --git a/freescout-dist/overrides/lord/laroute/src/Routes/Collection.php b/freescout-dist/overrides/lord/laroute/src/Routes/Collection.php new file mode 100644 index 0000000..13640c8 --- /dev/null +++ b/freescout-dist/overrides/lord/laroute/src/Routes/Collection.php @@ -0,0 +1,99 @@ +items = $this->parseRoutes($routes, $filter, $namespace); + } + + /** + * Parse the routes into a jsonable output. + * + * @param RouteCollection $routes + * @param string $filter + * @param string $namespace + * + * @return array + * @throws ZeroRoutesException + */ + protected function parseRoutes(RouteCollection $routes, $filter, $namespace) + { + $this->guardAgainstZeroRoutes($routes); + + $results = []; + + foreach ($routes as $route) { + $results[] = $this->getRouteInformation($route, $filter, $namespace); + } + + return array_values(array_filter($results)); + } + + /** + * Throw an exception if there aren't any routes to process + * + * @param RouteCollection $routes + * + * @throws ZeroRoutesException + */ + protected function guardAgainstZeroRoutes(RouteCollection $routes) + { + if (count($routes) < 1) { + throw new ZeroRoutesException("You don't have any routes!"); + } + } + + /** + * Get the route information for a given route. + * + * @param $route \Illuminate\Routing\Route + * @param $filter string + * @param $namespace string + * + * @return array + */ + protected function getRouteInformation(Route $route, $filter, $namespace) + { + $uri = $route->uri(); + + // Cut subdirectory from URI. + $subdirectory = \Helper::getSubdirectory(true); + if ($subdirectory) { + $uri = preg_replace("#^".preg_quote($subdirectory)."#", '', $uri); + } + + $host = $route->domain(); + $methods = $route->methods(); + $uri = $uri; + $name = $route->getName(); + $action = $route->getActionName(); + $laroute = array_get($route->getAction(), 'laroute', null); + + if(!empty($namespace)) { + $a = $route->getAction(); + + if(isset($a['controller'])) { + $action = str_replace($namespace.'\\', '', $action); + } + } + + switch ($filter) { + case 'all': + if($laroute === false) return null; + break; + case 'only': + if($laroute !== true) return null; + break; + } + + return compact('host', 'methods', 'uri', 'name', 'action'); + } + +} diff --git a/freescout-dist/overrides/maximebf/debugbar/src/DebugBar/DataFormatter/DataFormatter.php b/freescout-dist/overrides/maximebf/debugbar/src/DebugBar/DataFormatter/DataFormatter.php new file mode 100644 index 0000000..54e2641 --- /dev/null +++ b/freescout-dist/overrides/maximebf/debugbar/src/DebugBar/DataFormatter/DataFormatter.php @@ -0,0 +1,84 @@ +cloner = new VarCloner(); + $this->dumper = new CliDumper(); + } + + /** + * @param $data + * @return string + */ + public function formatVar($data) + { + $output = ''; + + $this->dumper->dump( + $this->cloner->cloneVar($data), + function ($line, $depth) use (&$output) { + // A negative depth means "end of dump" + if ($depth >= 0) { + // Adds a two spaces indentation to the line + $output .= str_repeat(' ', $depth).$line."\n"; + } + } + ); + + return trim($output); + } + + /** + * @param float $seconds + * @return string + */ + public function formatDuration($seconds) + { + if ($seconds < 0.001) { + return round($seconds * 1000000) . 'μs'; + } elseif ($seconds < 1) { + return round($seconds * 1000, 2) . 'ms'; + } + return round($seconds, 2) . 's'; + } + + /** + * @param string $size + * @param int $precision + * @return string + */ + public function formatBytes($size, $precision = 2) + { + if ($size === 0 || $size === null) { + return "0B"; + } + + $sign = $size < 0 ? '-' : ''; + $size = abs($size); + + $base = log($size) / log(1024); + $suffixes = array('B', 'KB', 'MB', 'GB', 'TB'); + return $sign . round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; + } +} diff --git a/freescout-dist/overrides/maximebf/debugbar/src/DebugBar/DebugBar.php b/freescout-dist/overrides/maximebf/debugbar/src/DebugBar/DebugBar.php new file mode 100644 index 0000000..10f3fa6 --- /dev/null +++ b/freescout-dist/overrides/maximebf/debugbar/src/DebugBar/DebugBar.php @@ -0,0 +1,495 @@ + + * $debugbar = new DebugBar(); + * $debugbar->addCollector(new DataCollector\MessagesCollector()); + * $debugbar['messages']->addMessage("foobar"); + * + */ +class DebugBar implements ArrayAccess +{ + public static $useOpenHandlerWhenSendingDataHeaders = false; + + protected $collectors = array(); + + protected $data; + + protected $jsRenderer; + + protected $requestIdGenerator; + + protected $requestId; + + protected $storage; + + protected $httpDriver; + + protected $stackSessionNamespace = 'PHPDEBUGBAR_STACK_DATA'; + + protected $stackAlwaysUseSessionStorage = false; + + /** + * Adds a data collector + * + * @param DataCollectorInterface $collector + * + * @throws DebugBarException + * @return $this + */ + public function addCollector(DataCollectorInterface $collector) + { + if ($collector->getName() === '__meta') { + throw new DebugBarException("'__meta' is a reserved name and cannot be used as a collector name"); + } + if (isset($this->collectors[$collector->getName()])) { + throw new DebugBarException("'{$collector->getName()}' is already a registered collector"); + } + $this->collectors[$collector->getName()] = $collector; + return $this; + } + + /** + * Checks if a data collector has been added + * + * @param string $name + * @return boolean + */ + public function hasCollector($name) + { + return isset($this->collectors[$name]); + } + + /** + * Returns a data collector + * + * @param string $name + * @return DataCollectorInterface + * @throws DebugBarException + */ + public function getCollector($name) + { + if (!isset($this->collectors[$name])) { + throw new DebugBarException("'$name' is not a registered collector"); + } + return $this->collectors[$name]; + } + + /** + * Returns an array of all data collectors + * + * @return array[DataCollectorInterface] + */ + public function getCollectors() + { + return $this->collectors; + } + + /** + * Sets the request id generator + * + * @param RequestIdGeneratorInterface $generator + * @return $this + */ + public function setRequestIdGenerator(RequestIdGeneratorInterface $generator) + { + $this->requestIdGenerator = $generator; + return $this; + } + + /** + * @return RequestIdGeneratorInterface + */ + public function getRequestIdGenerator() + { + if ($this->requestIdGenerator === null) { + $this->requestIdGenerator = new RequestIdGenerator(); + } + return $this->requestIdGenerator; + } + + /** + * Returns the id of the current request + * + * @return string + */ + public function getCurrentRequestId() + { + if ($this->requestId === null) { + $this->requestId = $this->getRequestIdGenerator()->generate(); + } + return $this->requestId; + } + + /** + * Sets the storage backend to use to store the collected data + * + * @param StorageInterface $storage + * @return $this + */ + public function setStorage(StorageInterface $storage = null) + { + $this->storage = $storage; + return $this; + } + + /** + * @return StorageInterface + */ + public function getStorage() + { + return $this->storage; + } + + /** + * Checks if the data will be persisted + * + * @return boolean + */ + public function isDataPersisted() + { + return $this->storage !== null; + } + + /** + * Sets the HTTP driver + * + * @param HttpDriverInterface $driver + * @return $this + */ + public function setHttpDriver(HttpDriverInterface $driver) + { + $this->httpDriver = $driver; + return $this; + } + + /** + * Returns the HTTP driver + * + * If no http driver where defined, a PhpHttpDriver is automatically created + * + * @return HttpDriverInterface + */ + public function getHttpDriver() + { + if ($this->httpDriver === null) { + $this->httpDriver = new PhpHttpDriver(); + } + return $this->httpDriver; + } + + /** + * Collects the data from the collectors + * + * @return array + */ + public function collect() + { + if (php_sapi_name() === 'cli') { + $ip = gethostname(); + if ($ip) { + $ip = gethostbyname($ip); + } else { + $ip = '127.0.0.1'; + } + $request_variables = array( + 'method' => 'CLI', + 'uri' => isset($_SERVER['SCRIPT_FILENAME']) ? realpath($_SERVER['SCRIPT_FILENAME']) : null, + 'ip' => $ip + ); + } else { + $request_variables = array( + 'method' => isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null, + 'uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null, + 'ip' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null + ); + } + $this->data = array( + '__meta' => array_merge( + array( + 'id' => $this->getCurrentRequestId(), + 'datetime' => date('Y-m-d H:i:s'), + 'utime' => microtime(true) + ), + $request_variables + ) + ); + + foreach ($this->collectors as $name => $collector) { + $this->data[$name] = $collector->collect(); + } + + // Remove all invalid (non UTF-8) characters + array_walk_recursive($this->data, function (&$item) { + if (is_string($item) && !mb_check_encoding($item, 'UTF-8')) { + $item = mb_convert_encoding($item, 'UTF-8', 'UTF-8'); + } + }); + + if ($this->storage !== null) { + $this->storage->save($this->getCurrentRequestId(), $this->data); + } + + return $this->data; + } + + /** + * Returns collected data + * + * Will collect the data if none have been collected yet + * + * @return array + */ + public function getData() + { + if ($this->data === null) { + $this->collect(); + } + return $this->data; + } + + /** + * Returns an array of HTTP headers containing the data + * + * @param string $headerName + * @param integer $maxHeaderLength + * @return array + */ + public function getDataAsHeaders($headerName = 'phpdebugbar', $maxHeaderLength = 4096, $maxTotalHeaderLength = 250000) + { + $data = rawurlencode(json_encode(array( + 'id' => $this->getCurrentRequestId(), + 'data' => $this->getData() + ))); + + if (strlen($data) > $maxTotalHeaderLength) { + $data = rawurlencode(json_encode(array( + 'error' => 'Maximum header size exceeded' + ))); + } + + $chunks = array(); + + while (strlen($data) > $maxHeaderLength) { + $chunks[] = substr($data, 0, $maxHeaderLength); + $data = substr($data, $maxHeaderLength); + } + $chunks[] = $data; + + $headers = array(); + for ($i = 0, $c = count($chunks); $i < $c; $i++) { + $name = $headerName . ($i > 0 ? "-$i" : ''); + $headers[$name] = $chunks[$i]; + } + + return $headers; + } + + /** + * Sends the data through the HTTP headers + * + * @param bool $useOpenHandler + * @param string $headerName + * @param integer $maxHeaderLength + * @return $this + */ + public function sendDataInHeaders($useOpenHandler = null, $headerName = 'phpdebugbar', $maxHeaderLength = 4096) + { + if ($useOpenHandler === null) { + $useOpenHandler = self::$useOpenHandlerWhenSendingDataHeaders; + } + if ($useOpenHandler && $this->storage !== null) { + $this->getData(); + $headerName .= '-id'; + $headers = array($headerName => $this->getCurrentRequestId()); + } else { + $headers = $this->getDataAsHeaders($headerName, $maxHeaderLength); + } + $this->getHttpDriver()->setHeaders($headers); + return $this; + } + + /** + * Stacks the data in the session for later rendering + */ + public function stackData() + { + $http = $this->initStackSession(); + + $data = null; + if (!$this->isDataPersisted() || $this->stackAlwaysUseSessionStorage) { + $data = $this->getData(); + } elseif ($this->data === null) { + $this->collect(); + } + + $stack = $http->getSessionValue($this->stackSessionNamespace); + $stack[$this->getCurrentRequestId()] = $data; + $http->setSessionValue($this->stackSessionNamespace, $stack); + return $this; + } + + /** + * Checks if there is stacked data in the session + * + * @return boolean + */ + public function hasStackedData() + { + try { + $http = $this->initStackSession(); + } catch (DebugBarException $e) { + return false; + } + return count($http->getSessionValue($this->stackSessionNamespace)) > 0; + } + + /** + * Returns the data stacked in the session + * + * @param boolean $delete Whether to delete the data in the session + * @return array + */ + public function getStackedData($delete = true) + { + $http = $this->initStackSession(); + $stackedData = $http->getSessionValue($this->stackSessionNamespace); + if ($delete) { + $http->deleteSessionValue($this->stackSessionNamespace); + } + + $datasets = array(); + if ($this->isDataPersisted() && !$this->stackAlwaysUseSessionStorage) { + foreach ($stackedData as $id => $data) { + $datasets[$id] = $this->getStorage()->get($id); + } + } else { + $datasets = $stackedData; + } + + return $datasets; + } + + /** + * Sets the key to use in the $_SESSION array + * + * @param string $ns + * @return $this + */ + public function setStackDataSessionNamespace($ns) + { + $this->stackSessionNamespace = $ns; + return $this; + } + + /** + * Returns the key used in the $_SESSION array + * + * @return string + */ + public function getStackDataSessionNamespace() + { + return $this->stackSessionNamespace; + } + + /** + * Sets whether to only use the session to store stacked data even + * if a storage is enabled + * + * @param boolean $enabled + * @return $this + */ + public function setStackAlwaysUseSessionStorage($enabled = true) + { + $this->stackAlwaysUseSessionStorage = $enabled; + return $this; + } + + /** + * Checks if the session is always used to store stacked data + * even if a storage is enabled + * + * @return boolean + */ + public function isStackAlwaysUseSessionStorage() + { + return $this->stackAlwaysUseSessionStorage; + } + + /** + * Initializes the session for stacked data + * @return HttpDriverInterface + * @throws DebugBarException + */ + protected function initStackSession() + { + $http = $this->getHttpDriver(); + if (!$http->isSessionStarted()) { + throw new DebugBarException("Session must be started before using stack data in the debug bar"); + } + + if (!$http->hasSessionValue($this->stackSessionNamespace)) { + $http->setSessionValue($this->stackSessionNamespace, array()); + } + + return $http; + } + + /** + * Returns a JavascriptRenderer for this instance + * @param string $baseUrl + * @param string $basePath + * @return JavascriptRenderer + */ + public function getJavascriptRenderer($baseUrl = null, $basePath = null) + { + if ($this->jsRenderer === null) { + $this->jsRenderer = new JavascriptRenderer($this, $baseUrl, $basePath); + } + return $this->jsRenderer; + } + + // -------------------------------------------- + // ArrayAccess implementation + + public function offsetSet($key, $value): void + { + throw new DebugBarException("DebugBar[] is read-only"); + } + + // : mixed + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->getCollector($key); + } + + public function offsetExists($key): bool + { + return $this->hasCollector($key); + } + + public function offsetUnset($key): void + { + throw new DebugBarException("DebugBar[] is read-only"); + } +} diff --git a/freescout-dist/overrides/maximebf/debugbar/src/DebugBar/JavascriptRenderer.php b/freescout-dist/overrides/maximebf/debugbar/src/DebugBar/JavascriptRenderer.php new file mode 100644 index 0000000..1311908 --- /dev/null +++ b/freescout-dist/overrides/maximebf/debugbar/src/DebugBar/JavascriptRenderer.php @@ -0,0 +1,1152 @@ + 'vendor/font-awesome/css/font-awesome.min.css', + 'highlightjs' => 'vendor/highlightjs/styles/github.css' + ); + + protected $jsVendors = array( + 'jquery' => 'vendor/jquery/dist/jquery.min.js', + 'highlightjs' => 'vendor/highlightjs/highlight.pack.js' + ); + + protected $includeVendors = true; + + protected $cssFiles = array('debugbar.css', 'widgets.css', 'openhandler.css'); + + protected $jsFiles = array('debugbar.js', 'widgets.js', 'openhandler.js'); + + protected $additionalAssets = array(); + + protected $javascriptClass = 'PhpDebugBar.DebugBar'; + + protected $variableName = 'phpdebugbar'; + + protected $enableJqueryNoConflict = true; + + protected $useRequireJs = false; + + protected $initialization; + + protected $controls = array(); + + protected $ignoredCollectors = array(); + + protected $ajaxHandlerClass = 'PhpDebugBar.AjaxHandler'; + + protected $ajaxHandlerBindToFetch = false; + + protected $ajaxHandlerBindToJquery = true; + + protected $ajaxHandlerBindToXHR = false; + + protected $ajaxHandlerAutoShow = true; + + protected $openHandlerClass = 'PhpDebugBar.OpenHandler'; + + protected $openHandlerUrl; + + /** + * @param \DebugBar\DebugBar $debugBar + * @param string $baseUrl + * @param string $basePath + */ + public function __construct(DebugBar $debugBar, $baseUrl = null, $basePath = null) + { + $this->debugBar = $debugBar; + + if ($baseUrl === null) { + $baseUrl = '/vendor/maximebf/debugbar/src/DebugBar/Resources'; + } + $this->baseUrl = $baseUrl; + + if ($basePath === null) { + $basePath = str_replace('/overrides/', '/vendor/', __DIR__) . DIRECTORY_SEPARATOR . 'Resources'; + } + $this->basePath = $basePath; + + // bitwise operations cannot be done in class definition :( + $this->initialization = self::INITIALIZE_CONSTRUCTOR | self::INITIALIZE_CONTROLS; + } + + /** + * Sets options from an array + * + * Options: + * - base_path + * - base_url + * - include_vendors + * - javascript_class + * - variable_name + * - initialization + * - enable_jquery_noconflict + * - controls + * - disable_controls + * - ignore_collectors + * - ajax_handler_classname + * - ajax_handler_bind_to_jquery + * - ajax_handler_auto_show + * - open_handler_classname + * - open_handler_url + * + * @param array $options [description] + */ + public function setOptions(array $options) + { + if (array_key_exists('base_path', $options)) { + $this->setBasePath($options['base_path']); + } + if (array_key_exists('base_url', $options)) { + $this->setBaseUrl($options['base_url']); + } + if (array_key_exists('include_vendors', $options)) { + $this->setIncludeVendors($options['include_vendors']); + } + if (array_key_exists('javascript_class', $options)) { + $this->setJavascriptClass($options['javascript_class']); + } + if (array_key_exists('variable_name', $options)) { + $this->setVariableName($options['variable_name']); + } + if (array_key_exists('initialization', $options)) { + $this->setInitialization($options['initialization']); + } + if (array_key_exists('enable_jquery_noconflict', $options)) { + $this->setEnableJqueryNoConflict($options['enable_jquery_noconflict']); + } + if (array_key_exists('use_requirejs', $options)) { + $this->setUseRequireJs($options['use_requirejs']); + } + if (array_key_exists('controls', $options)) { + foreach ($options['controls'] as $name => $control) { + $this->addControl($name, $control); + } + } + if (array_key_exists('disable_controls', $options)) { + foreach ((array) $options['disable_controls'] as $name) { + $this->disableControl($name); + } + } + if (array_key_exists('ignore_collectors', $options)) { + foreach ((array) $options['ignore_collectors'] as $name) { + $this->ignoreCollector($name); + } + } + if (array_key_exists('ajax_handler_classname', $options)) { + $this->setAjaxHandlerClass($options['ajax_handler_classname']); + } + if (array_key_exists('ajax_handler_bind_to_jquery', $options)) { + $this->setBindAjaxHandlerToJquery($options['ajax_handler_bind_to_jquery']); + } + if (array_key_exists('ajax_handler_auto_show', $options)) { + $this->setAjaxHandlerAutoShow($options['ajax_handler_auto_show']); + } + if (array_key_exists('open_handler_classname', $options)) { + $this->setOpenHandlerClass($options['open_handler_classname']); + } + if (array_key_exists('open_handler_url', $options)) { + $this->setOpenHandlerUrl($options['open_handler_url']); + } + } + + /** + * Sets the path which assets are relative to + * + * @param string $path + */ + public function setBasePath($path) + { + $this->basePath = $path; + return $this; + } + + /** + * Returns the path which assets are relative to + * + * @return string + */ + public function getBasePath() + { + return $this->basePath; + } + + /** + * Sets the base URL from which assets will be served + * + * @param string $url + */ + public function setBaseUrl($url) + { + $this->baseUrl = $url; + return $this; + } + + /** + * Returns the base URL from which assets will be served + * + * @return string + */ + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * Whether to include vendor assets + * + * You can only include js or css vendors using + * setIncludeVendors('css') or setIncludeVendors('js') + * + * @param boolean $enabled + */ + public function setIncludeVendors($enabled = true) + { + if (is_string($enabled)) { + $enabled = array($enabled); + } + $this->includeVendors = $enabled; + + if (!$enabled || (is_array($enabled) && !in_array('js', $enabled))) { + // no need to call jQuery.noConflict() if we do not include our own version + $this->enableJqueryNoConflict = false; + } + + return $this; + } + + /** + * Checks if vendors assets are included + * + * @return boolean + */ + public function areVendorsIncluded() + { + return $this->includeVendors !== false; + } + + /** + * Disable a specific vendor's assets. + * + * @param string $name "jquery", "fontawesome", "highlightjs" + * + * @return void + */ + public function disableVendor($name) + { + if (array_key_exists($name, $this->cssVendors)) { + unset($this->cssVendors[$name]); + } + if (array_key_exists($name, $this->jsVendors)) { + unset($this->jsVendors[$name]); + } + } + + /** + * Sets the javascript class name + * + * @param string $className + */ + public function setJavascriptClass($className) + { + $this->javascriptClass = $className; + return $this; + } + + /** + * Returns the javascript class name + * + * @return string + */ + public function getJavascriptClass() + { + return $this->javascriptClass; + } + + /** + * Sets the variable name of the class instance + * + * @param string $name + */ + public function setVariableName($name) + { + $this->variableName = $name; + return $this; + } + + /** + * Returns the variable name of the class instance + * + * @return string + */ + public function getVariableName() + { + return $this->variableName; + } + + /** + * Sets what should be initialized + * + * - INITIALIZE_CONSTRUCTOR: only initializes the instance + * - INITIALIZE_CONTROLS: initializes the controls and data mapping + * - INITIALIZE_CONSTRUCTOR | INITIALIZE_CONTROLS: initialize everything (default) + * + * @param integer $init + */ + public function setInitialization($init) + { + $this->initialization = $init; + return $this; + } + + /** + * Returns what should be initialized + * + * @return integer + */ + public function getInitialization() + { + return $this->initialization; + } + + /** + * Sets whether to call jQuery.noConflict() + * + * @param boolean $enabled + */ + public function setEnableJqueryNoConflict($enabled = true) + { + $this->enableJqueryNoConflict = $enabled; + return $this; + } + + /** + * Checks if jQuery.noConflict() will be called + * + * @return boolean + */ + public function isJqueryNoConflictEnabled() + { + return $this->enableJqueryNoConflict; + } + + /** + * Sets whether to use RequireJS or not + * + * @param boolean $enabled + * @return $this + */ + public function setUseRequireJs($enabled = true) + { + $this->useRequireJs = $enabled; + return $this; + } + + /** + * Checks if RequireJS is used + * + * @return boolean + */ + public function isRequireJsUsed() + { + return $this->useRequireJs; + } + + /** + * Adds a control to initialize + * + * Possible options: + * - icon: icon name + * - tooltip: string + * - widget: widget class name + * - title: tab title + * - map: a property name from the data to map the control to + * - default: a js string, default value of the data map + * + * "icon" or "widget" are at least needed + * + * @param string $name + * @param array $options + */ + public function addControl($name, array $options) + { + if (count(array_intersect(array_keys($options), array('icon', 'widget', 'tab', 'indicator'))) === 0) { + throw new DebugBarException("Not enough options for control '$name'"); + } + $this->controls[$name] = $options; + return $this; + } + + /** + * Disables a control + * + * @param string $name + */ + public function disableControl($name) + { + $this->controls[$name] = null; + return $this; + } + + /** + * Returns the list of controls + * + * This does not include controls provided by collectors + * + * @return array + */ + public function getControls() + { + return $this->controls; + } + + /** + * Ignores widgets provided by a collector + * + * @param string $name + */ + public function ignoreCollector($name) + { + $this->ignoredCollectors[] = $name; + return $this; + } + + /** + * Returns the list of ignored collectors + * + * @return array + */ + public function getIgnoredCollectors() + { + return $this->ignoredCollectors; + } + + /** + * Sets the class name of the ajax handler + * + * Set to false to disable + * + * @param string $className + */ + public function setAjaxHandlerClass($className) + { + $this->ajaxHandlerClass = $className; + return $this; + } + + /** + * Returns the class name of the ajax handler + * + * @return string + */ + public function getAjaxHandlerClass() + { + return $this->ajaxHandlerClass; + } + + /** + * Sets whether to call bindToFetch() on the ajax handler + * + * @param boolean $bind + */ + public function setBindAjaxHandlerToFetch($bind = true) + { + $this->ajaxHandlerBindToFetch = $bind; + return $this; + } + + /** + * Checks whether bindToFetch() will be called on the ajax handler + * + * @return boolean + */ + public function isAjaxHandlerBoundToFetch() + { + return $this->ajaxHandlerBindToFetch; + } + + /** + * Sets whether to call bindToJquery() on the ajax handler + * + * @param boolean $bind + */ + public function setBindAjaxHandlerToJquery($bind = true) + { + $this->ajaxHandlerBindToJquery = $bind; + return $this; + } + + /** + * Checks whether bindToJquery() will be called on the ajax handler + * + * @return boolean + */ + public function isAjaxHandlerBoundToJquery() + { + return $this->ajaxHandlerBindToJquery; + } + + /** + * Sets whether to call bindToXHR() on the ajax handler + * + * @param boolean $bind + */ + public function setBindAjaxHandlerToXHR($bind = true) + { + $this->ajaxHandlerBindToXHR = $bind; + return $this; + } + + /** + * Checks whether bindToXHR() will be called on the ajax handler + * + * @return boolean + */ + public function isAjaxHandlerBoundToXHR() + { + return $this->ajaxHandlerBindToXHR; + } + + /** + * Sets whether new ajax debug data will be immediately shown. Setting to false could be useful + * if there are a lot of tracking events cluttering things. + * + * @param boolean $autoShow + */ + public function setAjaxHandlerAutoShow($autoShow = true) + { + $this->ajaxHandlerAutoShow = $autoShow; + return $this; + } + + /** + * Checks whether the ajax handler will immediately show new ajax requests. + * + * @return boolean + */ + public function isAjaxHandlerAutoShow() + { + return $this->ajaxHandlerAutoShow; + } + + /** + * Sets the class name of the js open handler + * + * @param string $className + */ + public function setOpenHandlerClass($className) + { + $this->openHandlerClass = $className; + return $this; + } + + /** + * Returns the class name of the js open handler + * + * @return string + */ + public function getOpenHandlerClass() + { + return $this->openHandlerClass; + } + + /** + * Sets the url of the open handler + * + * @param string $url + */ + public function setOpenHandlerUrl($url) + { + $this->openHandlerUrl = $url; + return $this; + } + + /** + * Returns the url for the open handler + * + * @return string + */ + public function getOpenHandlerUrl() + { + return $this->openHandlerUrl; + } + + /** + * Add assets stored in files to render in the head + * + * @param array $cssFiles An array of filenames + * @param array $jsFiles An array of filenames + * @param string $basePath Base path of those files + * @param string $baseUrl Base url of those files + * @return $this + */ + public function addAssets($cssFiles, $jsFiles, $basePath = null, $baseUrl = null) + { + $this->additionalAssets[] = array( + 'base_path' => $basePath, + 'base_url' => $baseUrl, + 'css' => (array) $cssFiles, + 'js' => (array) $jsFiles + ); + return $this; + } + + /** + * Add inline assets to render inline in the head. Ideally, you should store static assets in + * files that you add with the addAssets function. However, adding inline assets is useful when + * integrating with 3rd-party libraries that require static assets that are only available in an + * inline format. + * + * The inline content arrays require special string array keys: they are used to deduplicate + * content. This is particularly useful if multiple instances of the same asset end up being + * added. Inline assets from all collectors are merged together into the same array, so these + * content IDs effectively deduplicate the inline assets. + * + * @param array $inlineCss An array map of content ID to inline CSS content (not including ' . "\n", $content); + } + + foreach ($jsFiles as $file) { + $html .= sprintf('' . "\n", $file); + } + + foreach ($inlineJs as $content) { + $html .= sprintf('' . "\n", $content); + } + + foreach ($inlineHead as $content) { + $html .= $content . "\n"; + } + + if ($this->enableJqueryNoConflict && !$this->useRequireJs) { + $html .= '' . "\n"; + } + + return $html; + } + + /** + * Register shutdown to display the debug bar + * + * @param boolean $here Set position of HTML. True if is to current position or false for end file + * @param boolean $initialize Whether to render the de bug bar initialization code + * @param bool $renderStackedData + * @param bool $head + * @return string Return "{--DEBUGBAR_OB_START_REPLACE_ME--}" or return an empty string if $here == false + */ + public function renderOnShutdown($here = true, $initialize = true, $renderStackedData = true, $head = false) + { + register_shutdown_function(array($this, "replaceTagInBuffer"), $here, $initialize, $renderStackedData, $head); + + if (ob_get_level() === 0) { + ob_start(); + } + + return ($here) ? self::REPLACEABLE_TAG : ""; + } + + /** + * Same as renderOnShutdown() with $head = true + * + * @param boolean $here + * @param boolean $initialize + * @param boolean $renderStackedData + * @return string + */ + public function renderOnShutdownWithHead($here = true, $initialize = true, $renderStackedData = true) + { + return $this->renderOnShutdown($here, $initialize, $renderStackedData, true); + } + + /** + * Is callback function for register_shutdown_function(...) + * + * @param boolean $here Set position of HTML. True if is to current position or false for end file + * @param boolean $initialize Whether to render the de bug bar initialization code + * @param bool $renderStackedData + * @param bool $head + */ + public function replaceTagInBuffer($here = true, $initialize = true, $renderStackedData = true, $head = false) + { + $render = ($head ? $this->renderHead() : "") + . $this->render($initialize, $renderStackedData); + + $current = ($here && ob_get_level() > 0) ? ob_get_clean() : self::REPLACEABLE_TAG; + + echo str_replace(self::REPLACEABLE_TAG, $render, $current, $count); + + if ($count === 0) { + echo $render; + } + } + + /** + * Returns the code needed to display the debug bar + * + * AJAX request should not render the initialization code. + * + * @param boolean $initialize Whether or not to render the debug bar initialization code + * @param boolean $renderStackedData Whether or not to render the stacked data + * @return string + */ + public function render($initialize = true, $renderStackedData = true) + { + $js = ''; + + if ($initialize) { + $js = $this->getJsInitializationCode(); + } + + if ($renderStackedData && $this->debugBar->hasStackedData()) { + foreach ($this->debugBar->getStackedData() as $id => $data) { + $js .= $this->getAddDatasetCode($id, $data, '(stacked)'); + } + } + + $suffix = !$initialize ? '(ajax)' : null; + $js .= $this->getAddDatasetCode($this->debugBar->getCurrentRequestId(), $this->debugBar->getData(), $suffix); + + if ($this->useRequireJs){ + return "\n"; + } else { + return "\n"; + } + + } + + /** + * Returns the js code needed to initialize the debug bar + * + * @return string + */ + protected function getJsInitializationCode() + { + $js = ''; + + if (($this->initialization & self::INITIALIZE_CONSTRUCTOR) === self::INITIALIZE_CONSTRUCTOR) { + $js .= sprintf("var %s = new %s();\n", $this->variableName, $this->javascriptClass); + } + + if (($this->initialization & self::INITIALIZE_CONTROLS) === self::INITIALIZE_CONTROLS) { + $js .= $this->getJsControlsDefinitionCode($this->variableName); + } + + if ($this->ajaxHandlerClass) { + $js .= sprintf("%s.ajaxHandler = new %s(%s, undefined, %s);\n", + $this->variableName, + $this->ajaxHandlerClass, + $this->variableName, + $this->ajaxHandlerAutoShow ? 'true' : 'false' + ); + if ($this->ajaxHandlerBindToFetch) { + $js .= sprintf("%s.ajaxHandler.bindToFetch();\n", $this->variableName); + } + if ($this->ajaxHandlerBindToXHR) { + $js .= sprintf("%s.ajaxHandler.bindToXHR();\n", $this->variableName); + } elseif ($this->ajaxHandlerBindToJquery) { + $js .= sprintf("if (jQuery) %s.ajaxHandler.bindToJquery(jQuery);\n", $this->variableName); + } + } + + if ($this->openHandlerUrl !== null) { + $js .= sprintf("%s.setOpenHandler(new %s(%s));\n", $this->variableName, + $this->openHandlerClass, + json_encode(array("url" => $this->openHandlerUrl))); + } + + return $js; + } + + /** + * Returns the js code needed to initialized the controls and data mapping of the debug bar + * + * Controls can be defined by collectors themselves or using {@see addControl()} + * + * @param string $varname Debug bar's variable name + * @return string + */ + protected function getJsControlsDefinitionCode($varname) + { + $js = ''; + $dataMap = array(); + $excludedOptions = array('indicator', 'tab', 'map', 'default', 'widget', 'position'); + + // finds controls provided by collectors + $widgets = array(); + foreach ($this->debugBar->getCollectors() as $collector) { + if (($collector instanceof Renderable) && !in_array($collector->getName(), $this->ignoredCollectors)) { + if ($w = $collector->getWidgets()) { + $widgets = array_merge($widgets, $w); + } + } + } + $controls = array_merge($widgets, $this->controls); + + foreach (array_filter($controls) as $name => $options) { + $opts = array_diff_key($options, array_flip($excludedOptions)); + + if (isset($options['tab']) || isset($options['widget'])) { + if (!isset($opts['title'])) { + $opts['title'] = ucfirst(str_replace('_', ' ', $name)); + } + $js .= sprintf("%s.addTab(\"%s\", new %s({%s%s}));\n", + $varname, + $name, + isset($options['tab']) ? $options['tab'] : 'PhpDebugBar.DebugBar.Tab', + substr(json_encode($opts, JSON_FORCE_OBJECT), 1, -1), + isset($options['widget']) ? sprintf('%s"widget": new %s()', count($opts) ? ', ' : '', $options['widget']) : '' + ); + } elseif (isset($options['indicator']) || isset($options['icon'])) { + $js .= sprintf("%s.addIndicator(\"%s\", new %s(%s), \"%s\");\n", + $varname, + $name, + isset($options['indicator']) ? $options['indicator'] : 'PhpDebugBar.DebugBar.Indicator', + json_encode($opts, JSON_FORCE_OBJECT), + isset($options['position']) ? $options['position'] : 'right' + ); + } + + if (isset($options['map']) && isset($options['default'])) { + $dataMap[$name] = array($options['map'], $options['default']); + } + } + + // creates the data mapping object + $mapJson = array(); + foreach ($dataMap as $name => $values) { + $mapJson[] = sprintf('"%s": ["%s", %s]', $name, $values[0], $values[1]); + } + $js .= sprintf("%s.setDataMap({\n%s\n});\n", $varname, implode(",\n", $mapJson)); + + // activate state restoration + $js .= sprintf("%s.restoreState();\n", $varname); + + return $js; + } + + /** + * Returns the js code needed to add a dataset + * + * @param string $requestId + * @param array $data + * @param mixed $suffix + * @return string + */ + protected function getAddDatasetCode($requestId, $data, $suffix = null) + { + $js = sprintf("%s.addDataSet(%s, \"%s\"%s);\n", + $this->variableName, + json_encode($data), + $requestId, + $suffix ? ", " . json_encode($suffix) : '' + ); + return $js; + } +} diff --git a/freescout-dist/overrides/natxet/cssmin/src/CssMin.php b/freescout-dist/overrides/natxet/cssmin/src/CssMin.php new file mode 100644 index 0000000..98acfaf --- /dev/null +++ b/freescout-dist/overrides/natxet/cssmin/src/CssMin.php @@ -0,0 +1,5157 @@ + + * + * 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. + * -- + * + * @package CssMin + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +/** + * Abstract definition of a CSS token class. + * + * Every token has to extend this class. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +abstract class aCssToken +{ + /** + * Returns the token as string. + * + * @return string + */ + abstract public function __toString(); +} + +/** + * Abstract definition of a for a ruleset start token. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +abstract class aCssRulesetStartToken extends aCssToken +{ + +} + +/** + * Abstract definition of a for ruleset end token. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +abstract class aCssRulesetEndToken extends aCssToken +{ + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return "}"; + } +} + +/** + * Abstract definition of a parser plugin. + * + * Every parser plugin have to extend this class. A parser plugin contains the logic to parse one or aspects of a + * stylesheet. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +abstract class aCssParserPlugin +{ + /** + * Plugin configuration. + * + * @var array + */ + protected $configuration = array(); + /** + * The CssParser of the plugin. + * + * @var CssParser + */ + protected $parser = null; + /** + * Plugin buffer. + * + * @var string + */ + protected $buffer = ""; + /** + * Constructor. + * + * @param CssParser $parser The CssParser object of this plugin. + * @param array $configuration Plugin configuration [optional] + * @return void + */ + public function __construct(CssParser $parser, array $configuration = null) + { + $this->configuration = $configuration; + $this->parser = $parser; + } + /** + * Returns the array of chars triggering the parser plugin. + * + * @return array + */ + abstract public function getTriggerChars(); + /** + * Returns the array of states triggering the parser plugin or FALSE if every state will trigger the parser plugin. + * + * @return array + */ + abstract public function getTriggerStates(); + /** + * Parser routine of the plugin. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + abstract public function parse($index, $char, $previousChar, $state); +} + +/** + * Abstract definition of a minifier plugin class. + * + * Minifier plugin process the parsed tokens one by one to apply changes to the token. Every minifier plugin has to + * extend this class. + * + * @package CssMin/Minifier/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +abstract class aCssMinifierPlugin +{ + /** + * Plugin configuration. + * + * @var array + */ + protected $configuration = array(); + /** + * The CssMinifier of the plugin. + * + * @var CssMinifier + */ + protected $minifier = null; + /** + * Constructor. + * + * @param CssMinifier $minifier The CssMinifier object of this plugin. + * @param array $configuration Plugin configuration [optional] + * @return void + */ + public function __construct(CssMinifier $minifier, array $configuration = array()) + { + $this->configuration = $configuration; + $this->minifier = $minifier; + } + /** + * Apply the plugin to the token. + * + * @param aCssToken $token Token to process + * @return boolean Return TRUE to break the processing of this token; FALSE to continue + */ + abstract public function apply(aCssToken &$token); + /** + * -- + * + * @return array + */ + abstract public function getTriggerTokens(); +} + +/** + * Abstract definition of a minifier filter class. + * + * Minifier filters allows a pre-processing of the parsed token to add, edit or delete tokens. Every minifier filter + * has to extend this class. + * + * @package CssMin/Minifier/Filters + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +abstract class aCssMinifierFilter +{ + /** + * Filter configuration. + * + * @var array + */ + protected $configuration = array(); + /** + * The CssMinifier of the filter. + * + * @var CssMinifier + */ + protected $minifier = null; + /** + * Constructor. + * + * @param CssMinifier $minifier The CssMinifier object of this plugin. + * @param array $configuration Filter configuration [optional] + * @return void + */ + public function __construct(CssMinifier $minifier, array $configuration = array()) + { + $this->configuration = $configuration; + $this->minifier = $minifier; + } + /** + * Filter the tokens. + * + * @param array $tokens Array of objects of type aCssToken + * @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array + */ + abstract public function apply(array &$tokens); +} + +/** + * Abstract formatter definition. + * + * Every formatter have to extend this class. + * + * @package CssMin/Formatter + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +abstract class aCssFormatter +{ + /** + * Indent string. + * + * @var string + */ + protected $indent = " "; + /** + * Declaration padding. + * + * @var integer + */ + protected $padding = 0; + /** + * Tokens. + * + * @var array + */ + protected $tokens = array(); + /** + * Constructor. + * + * @param array $tokens Array of CssToken + * @param string $indent Indent string [optional] + * @param integer $padding Declaration value padding [optional] + */ + public function __construct(array $tokens, $indent = null, $padding = null) + { + $this->tokens = $tokens; + $this->indent = !is_null($indent) ? $indent : $this->indent; + $this->padding = !is_null($padding) ? $padding : $this->padding; + } + /** + * Returns the array of aCssToken as formatted string. + * + * @return string + */ + abstract public function __toString(); +} + +/** + * Abstract definition of a ruleset declaration token. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +abstract class aCssDeclarationToken extends aCssToken +{ + /** + * Is the declaration flagged as important? + * + * @var boolean + */ + public $IsImportant = false; + /** + * Is the declaration flagged as last one of the ruleset? + * + * @var boolean + */ + public $IsLast = false; + /** + * Property name of the declaration. + * + * @var string + */ + public $Property = ""; + /** + * Value of the declaration. + * + * @var string + */ + public $Value = ""; + /** + * Set the properties of the @font-face declaration. + * + * @param string $property Property of the declaration + * @param string $value Value of the declaration + * @param boolean $isImportant Is the !important flag is set? + * @param boolean $IsLast Is the declaration the last one of the block? + * @return void + */ + public function __construct($property, $value, $isImportant = false, $isLast = false) + { + $this->Property = $property; + $this->Value = $value; + $this->IsImportant = $isImportant; + $this->IsLast = $isLast; + } + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return $this->Property . ":" . $this->Value . ($this->IsImportant ? " !important" : "") . ($this->IsLast ? "" : ";"); + } +} + +/** + * Abstract definition of a for at-rule block start token. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +abstract class aCssAtBlockStartToken extends aCssToken +{ + +} + +/** + * Abstract definition of a for at-rule block end token. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +abstract class aCssAtBlockEndToken extends aCssToken +{ + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return "}"; + } +} + +/** + * {@link aCssFromatter Formatter} returning the CSS source in {@link http://goo.gl/etzLs Whitesmiths indent style}. + * + * @package CssMin/Formatter + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssWhitesmithsFormatter extends aCssFormatter +{ + /** + * Implements {@link aCssFormatter::__toString()}. + * + * @return string + */ + public function __toString() + { + $r = array(); + $level = 0; + for ($i = 0, $l = count($this->tokens); $i < $l; $i++) + { + $token = $this->tokens[$i]; + $class = get_class($token); + $indent = str_repeat($this->indent, $level); + if ($class === "CssCommentToken") + { + $lines = array_map("trim", explode("\n", $token->Comment)); + for ($ii = 0, $ll = count($lines); $ii < $ll; $ii++) + { + $r[] = $indent . (substr($lines[$ii], 0, 1) == "*" ? " " : "") . $lines[$ii]; + } + } + elseif ($class === "CssAtCharsetToken") + { + $r[] = $indent . "@charset " . $token->Charset . ";"; + } + elseif ($class === "CssAtFontFaceStartToken") + { + $r[] = $indent . "@font-face"; + $r[] = $this->indent . $indent . "{"; + $level++; + } + elseif ($class === "CssAtImportToken") + { + $r[] = $indent . "@import " . $token->Import . " " . implode(", ", $token->MediaTypes) . ";"; + } + elseif ($class === "CssAtKeyframesStartToken") + { + $r[] = $indent . "@keyframes " . $token->Name; + $r[] = $this->indent . $indent . "{"; + $level++; + } + elseif ($class === "CssAtMediaStartToken") + { + $r[] = $indent . "@media " . implode(", ", $token->MediaTypes); + $r[] = $this->indent . $indent . "{"; + $level++; + } + elseif ($class === "CssAtPageStartToken") + { + $r[] = $indent . "@page"; + $r[] = $this->indent . $indent . "{"; + $level++; + } + elseif ($class === "CssAtVariablesStartToken") + { + $r[] = $indent . "@variables " . implode(", ", $token->MediaTypes); + $r[] = $this->indent . $indent . "{"; + $level++; + } + elseif ($class === "CssRulesetStartToken" || $class === "CssAtKeyframesRulesetStartToken") + { + $r[] = $indent . implode(", ", $token->Selectors); + $r[] = $this->indent . $indent . "{"; + $level++; + } + elseif ($class === "CssAtFontFaceDeclarationToken" + || $class === "CssAtKeyframesRulesetDeclarationToken" + || $class === "CssAtPageDeclarationToken" + || $class === "CssAtVariablesDeclarationToken" + || $class === "CssRulesetDeclarationToken" + ) + { + $declaration = $indent . $token->Property . ": "; + if ($this->padding) + { + $declaration = str_pad($declaration, $this->padding, " ", STR_PAD_RIGHT); + } + $r[] = $declaration . $token->Value . ($token->IsImportant ? " !important" : "") . ";"; + } + elseif ($class === "CssAtFontFaceEndToken" + || $class === "CssAtMediaEndToken" + || $class === "CssAtKeyframesEndToken" + || $class === "CssAtKeyframesRulesetEndToken" + || $class === "CssAtPageEndToken" + || $class === "CssAtVariablesEndToken" + || $class === "CssRulesetEndToken" + ) + { + $r[] = $indent . "}"; + $level--; + } + } + return implode("\n", $r); + } +} + +/** + * This {@link aCssMinifierPlugin} will process var-statement and sets the declaration value to the variable value. + * + * This plugin only apply the variable values. The variable values itself will get parsed by the + * {@link CssVariablesMinifierFilter}. + * + * Example: + * + * @variables + * { + * defaultColor: black; + * } + * color: var(defaultColor); + * + * + * Will get converted to: + * + * color:black; + * + * + * @package CssMin/Minifier/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssVariablesMinifierPlugin extends aCssMinifierPlugin +{ + /** + * Regular expression matching a value. + * + * @var string + */ + private $reMatch = "/var\((.+)\)/iSU"; + /** + * Parsed variables. + * + * @var array + */ + private $variables = null; + /** + * Returns the variables. + * + * @return array + */ + public function getVariables() + { + return $this->variables; + } + /** + * Implements {@link aCssMinifierPlugin::minify()}. + * + * @param aCssToken $token Token to process + * @return boolean Return TRUE to break the processing of this token; FALSE to continue + */ + public function apply(aCssToken &$token) + { + if (stripos($token->Value, "var") !== false && preg_match_all($this->reMatch, $token->Value, $m)) + { + $mediaTypes = $token->MediaTypes; + if (!in_array("all", $mediaTypes)) + { + $mediaTypes[] = "all"; + } + for ($i = 0, $l = count($m[0]); $i < $l; $i++) + { + $variable = trim($m[1][$i]); + foreach ($mediaTypes as $mediaType) + { + if (isset($this->variables[$mediaType], $this->variables[$mediaType][$variable])) + { + // Variable value found => set the declaration value to the variable value and return + $token->Value = str_replace($m[0][$i], $this->variables[$mediaType][$variable], $token->Value); + continue 2; + } + } + // If no value was found trigger an error and replace the token with a CssNullToken + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": No value found for variable " . $variable . " in media types " . implode(", ", $mediaTypes) . "", (string) $token)); + $token = new CssNullToken(); + return true; + } + } + return false; + } + /** + * Implements {@link aMinifierPlugin::getTriggerTokens()} + * + * @return array + */ + public function getTriggerTokens() + { + return array + ( + "CssAtFontFaceDeclarationToken", + "CssAtPageDeclarationToken", + "CssRulesetDeclarationToken" + ); + } + /** + * Sets the variables. + * + * @param array $variables Variables to set + * @return void + */ + public function setVariables(array $variables) + { + $this->variables = $variables; + } +} + +/** + * This {@link aCssMinifierFilter minifier filter} will parse the variable declarations out of @variables at-rule + * blocks. The variables will get store in the {@link CssVariablesMinifierPlugin} that will apply the variables to + * declaration. + * + * @package CssMin/Minifier/Filters + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssVariablesMinifierFilter extends aCssMinifierFilter +{ + /** + * Implements {@link aCssMinifierFilter::filter()}. + * + * @param array $tokens Array of objects of type aCssToken + * @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array + */ + public function apply(array &$tokens) + { + $variables = array(); + $defaultMediaTypes = array("all"); + $mediaTypes = array(); + $remove = array(); + for($i = 0, $l = count($tokens); $i < $l; $i++) + { + // @variables at-rule block found + if (get_class($tokens[$i]) === "CssAtVariablesStartToken") + { + $remove[] = $i; + $mediaTypes = (count($tokens[$i]->MediaTypes) == 0 ? $defaultMediaTypes : $tokens[$i]->MediaTypes); + foreach ($mediaTypes as $mediaType) + { + if (!isset($variables[$mediaType])) + { + $variables[$mediaType] = array(); + } + } + // Read the variable declaration tokens + for($i = $i; $i < $l; $i++) + { + // Found a variable declaration => read the variable values + if (get_class($tokens[$i]) === "CssAtVariablesDeclarationToken") + { + foreach ($mediaTypes as $mediaType) + { + $variables[$mediaType][$tokens[$i]->Property] = $tokens[$i]->Value; + } + $remove[] = $i; + } + // Found the variables end token => break; + elseif (get_class($tokens[$i]) === "CssAtVariablesEndToken") + { + $remove[] = $i; + break; + } + } + } + } + // Variables in @variables at-rule blocks + foreach($variables as $mediaType => $null) + { + foreach($variables[$mediaType] as $variable => $value) + { + // If a var() statement in a variable value found... + if (stripos($value, "var") !== false && preg_match_all("/var\((.+)\)/iSU", $value, $m)) + { + // ... then replace the var() statement with the variable values. + for ($i = 0, $l = count($m[0]); $i < $l; $i++) + { + $variables[$mediaType][$variable] = str_replace($m[0][$i], (isset($variables[$mediaType][$m[1][$i]]) ? $variables[$mediaType][$m[1][$i]] : ""), $variables[$mediaType][$variable]); + } + } + } + } + // Remove the complete @variables at-rule block + foreach ($remove as $i) + { + $tokens[$i] = null; + } + if (!($plugin = $this->minifier->getPlugin("CssVariablesMinifierPlugin"))) + { + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin CssVariablesMinifierPlugin was not found but is required for " . __CLASS__ . "")); + } + else + { + $plugin->setVariables($variables); + } + return count($remove); + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for preserve parsing url() values. + * + * This plugin return no {@link aCssToken CssToken} but ensures that url() values will get parsed properly. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssUrlParserPlugin extends aCssParserPlugin +{ + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("(", ")"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return false; + } + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + // Start of string + if ($char === "(" && strtolower(substr($this->parser->getSource(), $index - 3, 4)) === "url(" && $state !== "T_URL") + { + $this->parser->pushState("T_URL"); + $this->parser->setExclusive(__CLASS__); + } + // Escaped LF in url => remove escape backslash and LF + elseif ($char === "\n" && $previousChar === "\\" && $state === "T_URL") + { + $this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -2)); + } + // Parse error: Unescaped LF in string literal + elseif ($char === "\n" && $previousChar !== "\\" && $state === "T_URL") + { + $line = $this->parser->getBuffer(); + $this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -1) . ")"); // Replace the LF with the url string delimiter + $this->parser->popState(); + $this->parser->unsetExclusive(); + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated string literal", $line . "_")); + } + // End of string + elseif ($char === ")" && $state === "T_URL") + { + $this->parser->popState(); + $this->parser->unsetExclusive(); + } + else + { + return false; + } + return true; + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for preserve parsing string values. + * + * This plugin return no {@link aCssToken CssToken} but ensures that string values will get parsed properly. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssStringParserPlugin extends aCssParserPlugin +{ + /** + * Current string delimiter char. + * + * @var string + */ + private $delimiterChar = null; + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("\"", "'", "\n"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return false; + } + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + // Start of string + if (($char === "\"" || $char === "'") && $state !== "T_STRING") + { + $this->delimiterChar = $char; + $this->parser->pushState("T_STRING"); + $this->parser->setExclusive(__CLASS__); + } + // Escaped LF in string => remove escape backslash and LF + elseif ($char === "\n" && $previousChar === "\\" && $state === "T_STRING") + { + $this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -2)); + } + // Parse error: Unescaped LF in string literal + elseif ($char === "\n" && $previousChar !== "\\" && $state === "T_STRING") + { + $line = $this->parser->getBuffer(); + $this->parser->popState(); + $this->parser->unsetExclusive(); + $this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -1) . $this->delimiterChar); // Replace the LF with the current string char + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated string literal", $line . "_")); + $this->delimiterChar = null; + } + // End of string + elseif ($char === $this->delimiterChar && $state === "T_STRING") + { + // If the Previous char is a escape char count the amount of the previous escape chars. If the amount of + // escape chars is uneven do not end the string + if ($previousChar == "\\") + { + $source = $this->parser->getSource(); + $c = 1; + $i = $index - 2; + while (substr($source, $i, 1) === "\\") + { + $c++; $i--; + } + if ($c % 2) + { + return false; + } + } + $this->parser->popState(); + $this->parser->unsetExclusive(); + $this->delimiterChar = null; + } + else + { + return false; + } + return true; + } +} + +/** + * This {@link aCssMinifierFilter minifier filter} sorts the ruleset declarations of a ruleset by name. + * + * @package CssMin/Minifier/Filters + * @link http://code.google.com/p/cssmin/ + * @author Rowan Beentje + * @copyright Rowan Beentje + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssSortRulesetPropertiesMinifierFilter extends aCssMinifierFilter +{ + /** + * Implements {@link aCssMinifierFilter::filter()}. + * + * @param array $tokens Array of objects of type aCssToken + * @return integer Count of added, changed or removed tokens; a return value larger than 0 will rebuild the array + */ + public function apply(array &$tokens) + { + $r = 0; + for ($i = 0, $l = count($tokens); $i < $l; $i++) + { + // Only look for ruleset start rules + if (get_class($tokens[$i]) !== "CssRulesetStartToken") { continue; } + // Look for the corresponding ruleset end + $endIndex = false; + for ($ii = $i + 1; $ii < $l; $ii++) + { + if (get_class($tokens[$ii]) !== "CssRulesetEndToken") { continue; } + $endIndex = $ii; + break; + } + if (!$endIndex) { break; } + $startIndex = $i; + $i = $endIndex; + // Skip if there's only one token in this ruleset + if ($endIndex - $startIndex <= 2) { continue; } + // Ensure that everything between the start and end is a declaration token, for safety + for ($ii = $startIndex + 1; $ii < $endIndex; $ii++) + { + if (get_class($tokens[$ii]) !== "CssRulesetDeclarationToken") { continue(2); } + } + $declarations = array_slice($tokens, $startIndex + 1, $endIndex - $startIndex - 1); + // Check whether a sort is required + $sortRequired = $lastPropertyName = false; + foreach ($declarations as $declaration) + { + if ($lastPropertyName) + { + if (strcmp($lastPropertyName, $declaration->Property) > 0) + { + $sortRequired = true; + break; + } + } + $lastPropertyName = $declaration->Property; + } + if (!$sortRequired) { continue; } + // Arrange the declarations alphabetically by name + usort($declarations, array(__CLASS__, "userDefinedSort1")); + // Update "IsLast" property + for ($ii = 0, $ll = count($declarations) - 1; $ii <= $ll; $ii++) + { + if ($ii == $ll) + { + $declarations[$ii]->IsLast = true; + } + else + { + $declarations[$ii]->IsLast = false; + } + } + // Splice back into the array. + array_splice($tokens, $startIndex + 1, $endIndex - $startIndex - 1, $declarations); + $r += $endIndex - $startIndex - 1; + } + return $r; + } + /** + * User defined sort function. + * + * @return integer + */ + public static function userDefinedSort1($a, $b) + { + return strcmp($a->Property, $b->Property); + } +} + +/** + * This {@link aCssToken CSS token} represents the start of a ruleset. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssRulesetStartToken extends aCssRulesetStartToken +{ + /** + * Array of selectors. + * + * @var array + */ + public $Selectors = array(); + /** + * Set the properties of a ruleset token. + * + * @param array $selectors Selectors of the ruleset + * @return void + */ + public function __construct(array $selectors = array()) + { + $this->Selectors = $selectors; + } + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return implode(",", $this->Selectors) . "{"; + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for parsing ruleset block with including declarations. + * + * Found rulesets will add a {@link CssRulesetStartToken} and {@link CssRulesetEndToken} to the + * parser; including declarations as {@link CssRulesetDeclarationToken}. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssRulesetParserPlugin extends aCssParserPlugin +{ + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array(",", "{", "}", ":", ";"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return array("T_DOCUMENT", "T_AT_MEDIA", "T_RULESET::SELECTORS", "T_RULESET", "T_RULESET_DECLARATION"); + } + /** + * Selectors. + * + * @var array + */ + private $selectors = array(); + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + // Start of Ruleset and selectors + if ($char === "," && ($state === "T_DOCUMENT" || $state === "T_AT_MEDIA" || $state === "T_RULESET::SELECTORS")) + { + if ($state !== "T_RULESET::SELECTORS") + { + $this->parser->pushState("T_RULESET::SELECTORS"); + } + $this->selectors[] = $this->parser->getAndClearBuffer(",{"); + } + // End of selectors and start of declarations + elseif ($char === "{" && ($state === "T_DOCUMENT" || $state === "T_AT_MEDIA" || $state === "T_RULESET::SELECTORS")) + { + if ($this->parser->getBuffer() !== "") + { + $this->selectors[] = $this->parser->getAndClearBuffer(",{"); + if ($state == "T_RULESET::SELECTORS") + { + $this->parser->popState(); + } + $this->parser->pushState("T_RULESET"); + $this->parser->appendToken(new CssRulesetStartToken($this->selectors)); + $this->selectors = array(); + } + } + // Start of declaration + elseif ($char === ":" && $state === "T_RULESET") + { + $this->parser->pushState("T_RULESET_DECLARATION"); + $this->buffer = $this->parser->getAndClearBuffer(":;", true); + } + // Unterminated ruleset declaration + elseif ($char === ":" && $state === "T_RULESET_DECLARATION") + { + // Ignore Internet Explorer filter declarations + if ($this->buffer === "filter") + { + return false; + } + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_")); + } + // End of declaration + elseif (($char === ";" || $char === "}") && $state === "T_RULESET_DECLARATION") + { + $value = $this->parser->getAndClearBuffer(";}"); + if (strtolower(substr($value, -10, 10)) === "!important") + { + $value = trim(substr($value, 0, -10)); + $isImportant = true; + } + else + { + $isImportant = false; + } + $this->parser->popState(); + $this->parser->appendToken(new CssRulesetDeclarationToken($this->buffer, $value, $this->parser->getMediaTypes(), $isImportant)); + // Declaration ends with a right curly brace; so we have to end the ruleset + if ($char === "}") + { + $this->parser->appendToken(new CssRulesetEndToken()); + $this->parser->popState(); + } + $this->buffer = ""; + } + // End of ruleset + elseif ($char === "}" && $state === "T_RULESET") + { + $this->parser->popState(); + $this->parser->clearBuffer(); + $this->parser->appendToken(new CssRulesetEndToken()); + $this->buffer = ""; + $this->selectors = array(); + } + else + { + return false; + } + return true; + } +} + +/** + * This {@link aCssToken CSS token} represents the end of a ruleset. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssRulesetEndToken extends aCssRulesetEndToken +{ + +} + +/** + * This {@link aCssToken CSS token} represents a ruleset declaration. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssRulesetDeclarationToken extends aCssDeclarationToken +{ + /** + * Media types of the declaration. + * + * @var array + */ + public $MediaTypes = array("all"); + /** + * Set the properties of a ddocument- or at-rule @media level declaration. + * + * @param string $property Property of the declaration + * @param string $value Value of the declaration + * @param mixed $mediaTypes Media types of the declaration + * @param boolean $isImportant Is the !important flag is set + * @param boolean $isLast Is the declaration the last one of the ruleset + * @return void + */ + public function __construct($property, $value, $mediaTypes = null, $isImportant = false, $isLast = false) + { + parent::__construct($property, $value, $isImportant, $isLast); + $this->MediaTypes = $mediaTypes ? $mediaTypes : array("all"); + } +} + +/** + * This {@link aCssMinifierFilter minifier filter} sets the IsLast property of any last declaration in a ruleset, + * @font-face at-rule or @page at-rule block. If the property IsLast is TRUE the decrations will get stringified + * without tailing semicolon. + * + * @package CssMin/Minifier/Filters + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssRemoveLastDelarationSemiColonMinifierFilter extends aCssMinifierFilter +{ + /** + * Implements {@link aCssMinifierFilter::filter()}. + * + * @param array $tokens Array of objects of type aCssToken + * @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array + */ + public function apply(array &$tokens) + { + for ($i = 0, $l = count($tokens); $i < $l; $i++) + { + $current = get_class($tokens[$i]); + $next = isset($tokens[$i+1]) ? get_class($tokens[$i+1]) : false; + if (($current === "CssRulesetDeclarationToken" && $next === "CssRulesetEndToken") || + ($current === "CssAtFontFaceDeclarationToken" && $next === "CssAtFontFaceEndToken") || + ($current === "CssAtPageDeclarationToken" && $next === "CssAtPageEndToken")) + { + $tokens[$i]->IsLast = true; + } + } + return 0; + } +} + +/** + * This {@link aCssMinifierFilter minifier filter} will remove any empty rulesets (including @keyframes at-rule block + * rulesets). + * + * @package CssMin/Minifier/Filters + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssRemoveEmptyRulesetsMinifierFilter extends aCssMinifierFilter +{ + /** + * Implements {@link aCssMinifierFilter::filter()}. + * + * @param array $tokens Array of objects of type aCssToken + * @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array + */ + public function apply(array &$tokens) + { + $r = 0; + for ($i = 0, $l = count($tokens); $i < $l; $i++) + { + $current = get_class($tokens[$i]); + $next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false; + if (($current === "CssRulesetStartToken" && $next === "CssRulesetEndToken") || + ($current === "CssAtKeyframesRulesetStartToken" && $next === "CssAtKeyframesRulesetEndToken" && !array_intersect(array("from", "0%", "to", "100%"), array_map("strtolower", $tokens[$i]->Selectors))) + ) + { + $tokens[$i] = null; + $tokens[$i + 1] = null; + $i++; + $r = $r + 2; + } + } + return $r; + } +} + +/** + * This {@link aCssMinifierFilter minifier filter} will remove any empty @font-face, @keyframes, @media and @page + * at-rule blocks. + * + * @package CssMin/Minifier/Filters + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssRemoveEmptyAtBlocksMinifierFilter extends aCssMinifierFilter +{ + /** + * Implements {@link aCssMinifierFilter::filter()}. + * + * @param array $tokens Array of objects of type aCssToken + * @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array + */ + public function apply(array &$tokens) + { + $r = 0; + for ($i = 0, $l = count($tokens); $i < $l; $i++) + { + $current = get_class($tokens[$i]); + $next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false; + if (($current === "CssAtFontFaceStartToken" && $next === "CssAtFontFaceEndToken") || + ($current === "CssAtKeyframesStartToken" && $next === "CssAtKeyframesEndToken") || + ($current === "CssAtPageStartToken" && $next === "CssAtPageEndToken") || + ($current === "CssAtMediaStartToken" && $next === "CssAtMediaEndToken")) + { + $tokens[$i] = null; + $tokens[$i + 1] = null; + $i++; + $r = $r + 2; + } + } + return $r; + } +} + +/** + * This {@link aCssMinifierFilter minifier filter} will remove any comments from the array of parsed tokens. + * + * @package CssMin/Minifier/Filters + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssRemoveCommentsMinifierFilter extends aCssMinifierFilter +{ + /** + * Regular expression whitelisting any important comments to preserve. + * + * @var string + */ + private $whitelistPattern = '/(^\/\*!|@preserve|copyright|license|author|https?:|www\.)/i'; + + /** + * Implements {@link aCssMinifierFilter::filter()}. + * + * @param array $tokens Array of objects of type aCssToken + * @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array + */ + public function apply(array &$tokens) + { + $r = 0; + for ($i = 0, $l = count($tokens); $i < $l; $i++) + { + if (get_class($tokens[$i]) === "CssCommentToken") + { + if (!preg_match($this->whitelistPattern, $tokens[$i]->Comment)) + { + $tokens[$i] = null; + $r++; + } + } + } + return $r; + } +} + +/** + * CSS Parser. + * + * @package CssMin/Parser + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssParser +{ + /** + * Parse buffer. + * + * @var string + */ + private $buffer = ""; + /** + * {@link aCssParserPlugin Plugins}. + * + * @var array + */ + private $plugins = array(); + /** + * Source to parse. + * + * @var string + */ + private $source = ""; + /** + * Current state. + * + * @var integer + */ + private $state = "T_DOCUMENT"; + /** + * Exclusive state. + * + * @var string + */ + private $stateExclusive = false; + /** + * Media types state. + * + * @var mixed + */ + private $stateMediaTypes = false; + /** + * State stack. + * + * @var array + */ + private $states = array("T_DOCUMENT"); + /** + * Parsed tokens. + * + * @var array + */ + private $tokens = array(); + /** + * Constructer. + * + * Create instances of the used {@link aCssParserPlugin plugins}. + * + * @param string $source CSS source [optional] + * @param array $plugins Plugin configuration [optional] + * @return void + */ + public function __construct($source = null, array $plugins = null) + { + $plugins = array_merge(array + ( + "Comment" => true, + "String" => true, + "Url" => true, + "Expression" => true, + "Ruleset" => true, + "AtCharset" => true, + "AtFontFace" => true, + "AtImport" => true, + "AtKeyframes" => true, + "AtMedia" => true, + "AtPage" => true, + "AtVariables" => true + ), is_array($plugins) ? $plugins : array()); + // Create plugin instances + foreach ($plugins as $name => $config) + { + if ($config !== false) + { + $class = "Css" . $name . "ParserPlugin"; + $config = is_array($config) ? $config : array(); + if (class_exists($class)) + { + $this->plugins[] = new $class($this, $config); + } + else + { + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin " . $name . " with the class name " . $class . " was not found")); + } + } + } + if (!is_null($source)) + { + $this->parse($source); + } + } + /** + * Append a token to the array of tokens. + * + * @param aCssToken $token Token to append + * @return void + */ + public function appendToken(aCssToken $token) + { + $this->tokens[] = $token; + } + /** + * Clears the current buffer. + * + * @return void + */ + public function clearBuffer() + { + $this->buffer = ""; + } + /** + * Returns and clear the current buffer. + * + * @param string $trim Chars to use to trim the returned buffer + * @param boolean $tolower if TRUE the returned buffer will get converted to lower case + * @return string + */ + public function getAndClearBuffer($trim = "", $tolower = false) + { + $r = $this->getBuffer($trim, $tolower); + $this->buffer = ""; + return $r; + } + /** + * Returns the current buffer. + * + * @param string $trim Chars to use to trim the returned buffer + * @param boolean $tolower if TRUE the returned buffer will get converted to lower case + * @return string + */ + public function getBuffer($trim = "", $tolower = false) + { + $r = $this->buffer; + if ($trim) + { + $r = trim($r, " \t\n\r\0\x0B" . $trim); + } + if ($tolower) + { + $r = strtolower($r); + } + return $r; + } + /** + * Returns the current media types state. + * + * @return array + */ + public function getMediaTypes() + { + return $this->stateMediaTypes; + } + /** + * Returns the CSS source. + * + * @return string + */ + public function getSource() + { + return $this->source; + } + /** + * Returns the current state. + * + * @return integer The current state + */ + public function getState() + { + return $this->state; + } + /** + * Returns a plugin by class name. + * + * @param string $name Class name of the plugin + * @return aCssParserPlugin + */ + public function getPlugin($class) + { + static $index = null; + if (is_null($index)) + { + $index = array(); + for ($i = 0, $l = count($this->plugins); $i < $l; $i++) + { + $index[get_class($this->plugins[$i])] = $i; + } + } + return isset($index[$class]) ? $this->plugins[$index[$class]] : false; + } + /** + * Returns the parsed tokens. + * + * @return array + */ + public function getTokens() + { + return $this->tokens; + } + /** + * Returns if the current state equals the passed state. + * + * @param integer $state State to compare with the current state + * @return boolean TRUE is the state equals to the passed state; FALSE if not + */ + public function isState($state) + { + return ($this->state == $state); + } + /** + * Parse the CSS source and return a array with parsed tokens. + * + * @param string $source CSS source + * @return array Array with tokens + */ + public function parse($source) + { + // Reset + $this->source = ""; + $this->tokens = array(); + // Create a global and plugin lookup table for trigger chars; set array of plugins as local variable and create + // several helper variables for plugin handling + $globalTriggerChars = ""; + $plugins = $this->plugins; + $pluginCount = count($plugins); + $pluginIndex = array(); + $pluginTriggerStates = array(); + $pluginTriggerChars = array(); + for ($i = 0, $l = count($plugins); $i < $l; $i++) + { + $tPluginClassName = get_class($plugins[$i]); + $pluginTriggerChars[$i] = implode("", $plugins[$i]->getTriggerChars()); + $tPluginTriggerStates = $plugins[$i]->getTriggerStates(); + $pluginTriggerStates[$i] = $tPluginTriggerStates === false ? false : "|" . implode("|", $tPluginTriggerStates) . "|"; + $pluginIndex[$tPluginClassName] = $i; + for ($ii = 0, $ll = strlen($pluginTriggerChars[$i]); $ii < $ll; $ii++) + { + $c = substr($pluginTriggerChars[$i], $ii, 1); + if (strpos($globalTriggerChars, $c) === false) + { + $globalTriggerChars .= $c; + } + } + } + // Normalise line endings + $source = str_replace("\r\n", "\n", $source); // Windows to Unix line endings + $source = str_replace("\r", "\n", $source); // Mac to Unix line endings + $this->source = $source; + // Variables + $buffer = &$this->buffer; + $exclusive = &$this->stateExclusive; + $state = &$this->state; + $c = $p = null; + // -- + for ($i = 0, $l = strlen($source); $i < $l; $i++) + { + // Set the current Char + $c = $source[$i]; // Is faster than: $c = substr($source, $i, 1); + // Normalize and filter double whitespace characters + if ($exclusive === false) + { + if ($c === "\n" || $c === "\t") + { + $c = " "; + } + if ($c === " " && $p === " ") + { + continue; + } + } + $buffer .= $c; + + // Fix case when value of url() contains parentheses, for example: url("data: ... ()") + if ($this->getState() == 'T_URL' && $c == ')') { + $trimmedBuffer = trim($buffer); + if (preg_match('@url\((\s+)?".+@', $trimmedBuffer) + && !preg_match('@url\((\s+)?".+"\)@', $trimmedBuffer) + || preg_match('@url\((\s+)?\'.+@', $trimmedBuffer) + && !preg_match('@url\((\s+)?\'.+\'\)@', $trimmedBuffer) + ) { + $p = $c; // Set the parent char + continue; + } + } + + // Extended processing only if the current char is a global trigger char + if (strpos($globalTriggerChars, $c) !== false) + { + // Exclusive state is set; process with the exclusive plugin + if ($exclusive) + { + $tPluginIndex = $pluginIndex[$exclusive]; + if (strpos($pluginTriggerChars[$tPluginIndex], $c) !== false && ($pluginTriggerStates[$tPluginIndex] === false || strpos($pluginTriggerStates[$tPluginIndex], $state) !== false)) + { + $r = $plugins[$tPluginIndex]->parse($i, $c, $p, $state); + // Return value is TRUE => continue with next char + if ($r === true) + { + continue; + } + // Return value is numeric => set new index and continue with next char + elseif ($r !== false && $r != $i) + { + $i = $r; + continue; + } + } + } + // Else iterate through the plugins + else + { + $triggerState = "|" . $state . "|"; + for ($ii = 0, $ll = $pluginCount; $ii < $ll; $ii++) + { + // Only process if the current char is one of the plugin trigger chars + if (strpos($pluginTriggerChars[$ii], $c) !== false && ($pluginTriggerStates[$ii] === false || strpos($pluginTriggerStates[$ii], $triggerState) !== false)) + { + // Process with the plugin + $r = $plugins[$ii]->parse($i, $c, $p, $state); + // Return value is TRUE => break the plugin loop and and continue with next char + if ($r === true) + { + break; + } + // Return value is numeric => set new index, break the plugin loop and and continue with next char + elseif ($r !== false && $r != $i) + { + $i = $r; + break; + } + } + } + } + } + $p = $c; // Set the parent char + } + return $this->tokens; + } + /** + * Remove the last state of the state stack and return the removed stack value. + * + * @return integer Removed state value + */ + public function popState() + { + $r = array_pop($this->states); + $this->state = $this->states[count($this->states) - 1]; + return $r; + } + /** + * Adds a new state onto the state stack. + * + * @param integer $state State to add onto the state stack. + * @return integer The index of the added state in the state stacks + */ + public function pushState($state) + { + $r = array_push($this->states, $state); + $this->state = $this->states[count($this->states) - 1]; + return $r; + } + /** + * Sets/restores the buffer. + * + * @param string $buffer Buffer to set + * @return void + */ + public function setBuffer($buffer) + { + $this->buffer = $buffer; + } + /** + * Set the exclusive state. + * + * @param string $exclusive Exclusive state + * @return void + */ + public function setExclusive($exclusive) + { + $this->stateExclusive = $exclusive; + } + /** + * Set the media types state. + * + * @param array $mediaTypes Media types state + * @return void + */ + public function setMediaTypes(array $mediaTypes) + { + $this->stateMediaTypes = $mediaTypes; + } + /** + * Sets the current state in the state stack; equals to {@link CssParser::popState()} + {@link CssParser::pushState()}. + * + * @param integer $state State to set + * @return integer + */ + public function setState($state) + { + $r = array_pop($this->states); + array_push($this->states, $state); + $this->state = $this->states[count($this->states) - 1]; + return $r; + } + /** + * Removes the exclusive state. + * + * @return void + */ + public function unsetExclusive() + { + $this->stateExclusive = false; + } + /** + * Removes the media types state. + * + * @return void + */ + public function unsetMediaTypes() + { + $this->stateMediaTypes = false; + } +} + +/** + * {@link aCssFromatter Formatter} returning the CSS source in {@link http://goo.gl/j4XdU OTBS indent style} (The One True Brace Style). + * + * @package CssMin/Formatter + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssOtbsFormatter extends aCssFormatter +{ + /** + * Implements {@link aCssFormatter::__toString()}. + * + * @return string + */ + public function __toString() + { + $r = array(); + $level = 0; + for ($i = 0, $l = count($this->tokens); $i < $l; $i++) + { + $token = $this->tokens[$i]; + $class = get_class($token); + $indent = str_repeat($this->indent, $level); + if ($class === "CssCommentToken") + { + $lines = array_map("trim", explode("\n", $token->Comment)); + for ($ii = 0, $ll = count($lines); $ii < $ll; $ii++) + { + $r[] = $indent . (substr($lines[$ii], 0, 1) == "*" ? " " : "") . $lines[$ii]; + } + } + elseif ($class === "CssAtCharsetToken") + { + $r[] = $indent . "@charset " . $token->Charset . ";"; + } + elseif ($class === "CssAtFontFaceStartToken") + { + $r[] = $indent . "@font-face {"; + $level++; + } + elseif ($class === "CssAtImportToken") + { + $r[] = $indent . "@import " . $token->Import . " " . implode(", ", $token->MediaTypes) . ";"; + } + elseif ($class === "CssAtKeyframesStartToken") + { + $r[] = $indent . "@keyframes " . $token->Name . " {"; + $level++; + } + elseif ($class === "CssAtMediaStartToken") + { + $r[] = $indent . "@media " . implode(", ", $token->MediaTypes) . " {"; + $level++; + } + elseif ($class === "CssAtPageStartToken") + { + $r[] = $indent . "@page {"; + $level++; + } + elseif ($class === "CssAtVariablesStartToken") + { + $r[] = $indent . "@variables " . implode(", ", $token->MediaTypes) . " {"; + $level++; + } + elseif ($class === "CssRulesetStartToken" || $class === "CssAtKeyframesRulesetStartToken") + { + $r[] = $indent . implode(", ", $token->Selectors) . " {"; + $level++; + } + elseif ($class === "CssAtFontFaceDeclarationToken" + || $class === "CssAtKeyframesRulesetDeclarationToken" + || $class === "CssAtPageDeclarationToken" + || $class === "CssAtVariablesDeclarationToken" + || $class === "CssRulesetDeclarationToken" + ) + { + $declaration = $indent . $token->Property . ": "; + if ($this->padding) + { + $declaration = str_pad($declaration, $this->padding, " ", STR_PAD_RIGHT); + } + $r[] = $declaration . $token->Value . ($token->IsImportant ? " !important" : "") . ";"; + } + elseif ($class === "CssAtFontFaceEndToken" + || $class === "CssAtMediaEndToken" + || $class === "CssAtKeyframesEndToken" + || $class === "CssAtKeyframesRulesetEndToken" + || $class === "CssAtPageEndToken" + || $class === "CssAtVariablesEndToken" + || $class === "CssRulesetEndToken" + ) + { + $level--; + $r[] = str_repeat($indent, $level) . "}"; + } + } + return implode("\n", $r); + } +} + +/** + * This {@link aCssToken CSS token} is a utility token that extends {@link aNullToken} and returns only a empty string. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssNullToken extends aCssToken +{ + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return ""; + } +} + +/** + * CSS Minifier. + * + * @package CssMin/Minifier + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssMinifier +{ + /** + * {@link aCssMinifierFilter Filters}. + * + * @var array + */ + private $filters = array(); + /** + * {@link aCssMinifierPlugin Plugins}. + * + * @var array + */ + private $plugins = array(); + /** + * Minified source. + * + * @var string + */ + private $minified = ""; + /** + * Constructer. + * + * Creates instances of {@link aCssMinifierFilter filters} and {@link aCssMinifierPlugin plugins}. + * + * @param string $source CSS source [optional] + * @param array $filters Filter configuration [optional] + * @param array $plugins Plugin configuration [optional] + * @return void + */ + public function __construct($source = null, array $filters = null, array $plugins = null) + { + $filters = array_merge(array + ( + "ImportImports" => false, + "RemoveComments" => true, + "RemoveEmptyRulesets" => true, + "RemoveEmptyAtBlocks" => true, + "ConvertLevel3Properties" => false, + "ConvertLevel3AtKeyframes" => false, + "Variables" => true, + "RemoveLastDelarationSemiColon" => true + ), is_array($filters) ? $filters : array()); + $plugins = array_merge(array + ( + "Variables" => true, + "ConvertFontWeight" => false, + "ConvertHslColors" => false, + "ConvertRgbColors" => false, + "ConvertNamedColors" => false, + "CompressColorValues" => false, + "CompressUnitValues" => false, + "CompressExpressionValues" => false + ), is_array($plugins) ? $plugins : array()); + // Filters + foreach ($filters as $name => $config) + { + if ($config !== false) + { + $class = "Css" . $name . "MinifierFilter"; + $config = is_array($config) ? $config : array(); + if (class_exists($class)) + { + $this->filters[] = new $class($this, $config); + } + else + { + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The filter " . $name . " with the class name " . $class . " was not found")); + } + } + } + // Plugins + foreach ($plugins as $name => $config) + { + if ($config !== false) + { + $class = "Css" . $name . "MinifierPlugin"; + $config = is_array($config) ? $config : array(); + if (class_exists($class)) + { + $this->plugins[] = new $class($this, $config); + } + else + { + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin " . $name . " with the class name " . $class . " was not found")); + } + } + } + // -- + if (!is_null($source)) + { + $this->minify($source); + } + } + /** + * Returns the minified Source. + * + * @return string + */ + public function getMinified() + { + return $this->minified; + } + /** + * Returns a plugin by class name. + * + * @param string $name Class name of the plugin + * @return aCssMinifierPlugin + */ + public function getPlugin($class) + { + static $index = null; + if (is_null($index)) + { + $index = array(); + for ($i = 0, $l = count($this->plugins); $i < $l; $i++) + { + $index[get_class($this->plugins[$i])] = $i; + } + } + return isset($index[$class]) ? $this->plugins[$index[$class]] : false; + } + /** + * Minifies the CSS source. + * + * @param string $source CSS source + * @return string + */ + public function minify($source) + { + // Variables + $r = ""; + $parser = new CssParser($source); + $tokens = $parser->getTokens(); + $filters = $this->filters; + $filterCount = count($this->filters); + $plugins = $this->plugins; + $pluginCount = count($plugins); + $pluginIndex = array(); + $pluginTriggerTokens = array(); + $globalTriggerTokens = array(); + for ($i = 0, $l = count($plugins); $i < $l; $i++) + { + $tPluginClassName = get_class($plugins[$i]); + $pluginTriggerTokens[$i] = $plugins[$i]->getTriggerTokens(); + foreach ($pluginTriggerTokens[$i] as $v) + { + if (!in_array($v, $globalTriggerTokens)) + { + $globalTriggerTokens[] = $v; + } + } + $pluginTriggerTokens[$i] = "|" . implode("|", $pluginTriggerTokens[$i]) . "|"; + $pluginIndex[$tPluginClassName] = $i; + } + $globalTriggerTokens = "|" . implode("|", $globalTriggerTokens) . "|"; + /* + * Apply filters + */ + for($i = 0; $i < $filterCount; $i++) + { + // Apply the filter; if the return value is larger than 0... + if ($filters[$i]->apply($tokens) > 0) + { + // ...then filter null values and rebuild the token array + $tokens = array_values(array_filter($tokens)); + } + } + $tokenCount = count($tokens); + /* + * Apply plugins + */ + for($i = 0; $i < $tokenCount; $i++) + { + $triggerToken = "|" . get_class($tokens[$i]) . "|"; + if (strpos($globalTriggerTokens, $triggerToken) !== false) + { + for($ii = 0; $ii < $pluginCount; $ii++) + { + if (strpos($pluginTriggerTokens[$ii], $triggerToken) !== false || $pluginTriggerTokens[$ii] === false) + { + // Apply the plugin; if the return value is TRUE continue to the next token + if ($plugins[$ii]->apply($tokens[$i]) === true) + { + continue 2; + } + } + } + } + } + // Stringify the tokens + for($i = 0; $i < $tokenCount; $i++) + { + $r .= (string) $tokens[$i]; + } + $this->minified = $r; + return $r; + } +} + +/** + * CssMin - A (simple) css minifier with benefits + * + * -- + * Copyright (c) 2011 Joe Scylla + * + * 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. + * -- + * + * @package CssMin + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssMin +{ + /** + * Index of classes + * + * @var array + */ + private static $classIndex = array(); + /** + * Parse/minify errors + * + * @var array + */ + private static $errors = array(); + /** + * Verbose output. + * + * @var boolean + */ + private static $isVerbose = false; + /** + * {@link http://goo.gl/JrW54 Autoload} function of CssMin. + * + * @param string $class Name of the class + * @return void + */ + public static function autoload($class) + { + if (isset(self::$classIndex[$class])) + { + require(self::$classIndex[$class]); + } + } + /** + * Return errors + * + * @return array of {CssError}. + */ + public static function getErrors() + { + return self::$errors; + } + /** + * Returns if there were errors. + * + * @return boolean + */ + public static function hasErrors() + { + return count(self::$errors) > 0; + } + /** + * Initialises CssMin. + * + * @return void + */ + public static function initialise() + { + // Create the class index for autoloading or including + $paths = array(dirname(__FILE__)); + for ($i = 0; $i < count($paths); $i++) + { + $path = $paths[$i]; + $subDirectorys = glob($path . "*", GLOB_MARK | GLOB_ONLYDIR | GLOB_NOSORT); + if (is_array($subDirectorys)) + { + foreach ($subDirectorys as $subDirectory) + { + $paths[] = $subDirectory; + } + } + $files = glob($path . "*.php", 0); + if (is_array($files)) + { + foreach ($files as $file) + { + $class = substr(basename($file), 0, -4); + self::$classIndex[$class] = $file; + } + } + } + krsort(self::$classIndex); + // Only use autoloading if spl_autoload_register() is available and no __autoload() is defined (because + // __autoload() breaks if spl_autoload_register() is used. + if (function_exists("spl_autoload_register") && !is_callable("__autoload")) + { + spl_autoload_register(array(__CLASS__, "autoload")); + } + // Otherwise include all class files + else + { + foreach (self::$classIndex as $class => $file) + { + if (!class_exists($class)) + { + require_once($file); + } + } + } + } + /** + * Minifies CSS source. + * + * @param string $source CSS source + * @param array $filters Filter configuration [optional] + * @param array $plugins Plugin configuration [optional] + * @return string Minified CSS + */ + public static function minify($source, array $filters = null, array $plugins = null) + { + self::$errors = array(); + $minifier = new CssMinifier($source, $filters, $plugins); + return $minifier->getMinified(); + } + /** + * Parse the CSS source. + * + * @param string $source CSS source + * @param array $plugins Plugin configuration [optional] + * @return array Array of aCssToken + */ + public static function parse($source, array $plugins = null) + { + self::$errors = array(); + $parser = new CssParser($source, $plugins); + return $parser->getTokens(); + } + /** + * -- + * + * @param boolean $to + * @return boolean + */ + public static function setVerbose($to) + { + self::$isVerbose = (boolean) $to; + return self::$isVerbose; + } + /** + * -- + * + * @param CssError $error + * @return void + */ + public static function triggerError(CssError $error) + { + self::$errors[] = $error; + if (self::$isVerbose) + { + trigger_error((string) $error, E_USER_WARNING); + } + } +} +// Initialises CssMin +CssMin::initialise(); + +/** + * This {@link aCssMinifierFilter minifier filter} import external css files defined with the @import at-rule into the + * current stylesheet. + * + * @package CssMin/Minifier/Filters + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssImportImportsMinifierFilter extends aCssMinifierFilter +{ + /** + * Array with already imported external stylesheets. + * + * @var array + */ + private $imported = array(); + /** + * Implements {@link aCssMinifierFilter::filter()}. + * + * @param array $tokens Array of objects of type aCssToken + * @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array + */ + public function apply(array &$tokens) + { + if (!isset($this->configuration["BasePath"]) || !is_dir($this->configuration["BasePath"])) + { + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Base path " . ($this->configuration["BasePath"] ? $this->configuration["BasePath"] : "null"). " is not a directory")); + return 0; + } + for ($i = 0, $l = count($tokens); $i < $l; $i++) + { + if (get_class($tokens[$i]) === "CssAtImportToken") + { + $import = $this->configuration["BasePath"] . "/" . $tokens[$i]->Import; + // Import file was not found/is not a file + if (!is_file($import)) + { + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Import file " . $import. " was not found.", (string) $tokens[$i])); + } + // Import file already imported; remove this @import at-rule to prevent recursions + elseif (in_array($import, $this->imported)) + { + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Import file " . $import. " was already imported.", (string) $tokens[$i])); + $tokens[$i] = null; + } + else + { + $this->imported[] = $import; + $parser = new CssParser(file_get_contents($import)); + $import = $parser->getTokens(); + // The @import at-rule has media types defined requiring special handling + if (count($tokens[$i]->MediaTypes) > 0 && !(count($tokens[$i]->MediaTypes) == 1 && $tokens[$i]->MediaTypes[0] == "all")) + { + $blocks = array(); + /* + * Filter or set media types of @import at-rule or remove the @import at-rule if no media type is matching the parent @import at-rule + */ + for($ii = 0, $ll = count($import); $ii < $ll; $ii++) + { + if (get_class($import[$ii]) === "CssAtImportToken") + { + // @import at-rule defines no media type or only the "all" media type; set the media types to the one defined in the parent @import at-rule + if (count($import[$ii]->MediaTypes) == 0 || (count($import[$ii]->MediaTypes) == 1 && $import[$ii]->MediaTypes[0] == "all")) + { + $import[$ii]->MediaTypes = $tokens[$i]->MediaTypes; + } + // @import at-rule defineds one or more media types; filter out media types not matching with the parent @import at-rule + elseif (count($import[$ii]->MediaTypes) > 0) + { + foreach ($import[$ii]->MediaTypes as $index => $mediaType) + { + if (!in_array($mediaType, $tokens[$i]->MediaTypes)) + { + unset($import[$ii]->MediaTypes[$index]); + } + } + $import[$ii]->MediaTypes = array_values($import[$ii]->MediaTypes); + // If there are no media types left in the @import at-rule remove the @import at-rule + if (count($import[$ii]->MediaTypes) == 0) + { + $import[$ii] = null; + } + } + } + } + /* + * Remove media types of @media at-rule block not defined in the @import at-rule + */ + for($ii = 0, $ll = count($import); $ii < $ll; $ii++) + { + if (get_class($import[$ii]) === "CssAtMediaStartToken") + { + foreach ($import[$ii]->MediaTypes as $index => $mediaType) + { + if (!in_array($mediaType, $tokens[$i]->MediaTypes)) + { + unset($import[$ii]->MediaTypes[$index]); + } + $import[$ii]->MediaTypes = array_values($import[$ii]->MediaTypes); + } + } + } + /* + * If no media types left of the @media at-rule block remove the complete block + */ + for($ii = 0, $ll = count($import); $ii < $ll; $ii++) + { + if (get_class($import[$ii]) === "CssAtMediaStartToken") + { + if (count($import[$ii]->MediaTypes) === 0) + { + for ($iii = $ii; $iii < $ll; $iii++) + { + if (get_class($import[$iii]) === "CssAtMediaEndToken") + { + break; + } + } + if (get_class($import[$iii]) === "CssAtMediaEndToken") + { + array_splice($import, $ii, $iii - $ii + 1, array()); + $ll = count($import); + } + } + } + } + /* + * If the media types of the @media at-rule equals the media types defined in the @import + * at-rule remove the CssAtMediaStartToken and CssAtMediaEndToken token + */ + for($ii = 0, $ll = count($import); $ii < $ll; $ii++) + { + if (get_class($import[$ii]) === "CssAtMediaStartToken" && count(array_diff($tokens[$i]->MediaTypes, $import[$ii]->MediaTypes)) === 0) + { + for ($iii = $ii; $iii < $ll; $iii++) + { + if (get_class($import[$iii]) == "CssAtMediaEndToken") + { + break; + } + } + if (get_class($import[$iii]) == "CssAtMediaEndToken") + { + unset($import[$ii]); + unset($import[$iii]); + $import = array_values($import); + $ll = count($import); + } + } + } + /** + * Extract CssAtImportToken and CssAtCharsetToken tokens + */ + for($ii = 0, $ll = count($import); $ii < $ll; $ii++) + { + $class = get_class($import[$ii]); + if ($class === "CssAtImportToken" || $class === "CssAtCharsetToken") + { + $blocks = array_merge($blocks, array_splice($import, $ii, 1, array())); + $ll = count($import); + } + } + /* + * Extract the @font-face, @media and @page at-rule block + */ + for($ii = 0, $ll = count($import); $ii < $ll; $ii++) + { + $class = get_class($import[$ii]); + if ($class === "CssAtFontFaceStartToken" || $class === "CssAtMediaStartToken" || $class === "CssAtPageStartToken" || $class === "CssAtVariablesStartToken") + { + for ($iii = $ii; $iii < $ll; $iii++) + { + $class = get_class($import[$iii]); + if ($class === "CssAtFontFaceEndToken" || $class === "CssAtMediaEndToken" || $class === "CssAtPageEndToken" || $class === "CssAtVariablesEndToken") + { + break; + } + } + $class = get_class($import[$iii]); + if (isset($import[$iii]) && ($class === "CssAtFontFaceEndToken" || $class === "CssAtMediaEndToken" || $class === "CssAtPageEndToken" || $class === "CssAtVariablesEndToken")) + { + $blocks = array_merge($blocks, array_splice($import, $ii, $iii - $ii + 1, array())); + $ll = count($import); + } + } + } + // Create the import array with extracted tokens and the rulesets wrapped into a @media at-rule block + $import = array_merge($blocks, array(new CssAtMediaStartToken($tokens[$i]->MediaTypes)), $import, array(new CssAtMediaEndToken())); + } + // Insert the imported tokens + array_splice($tokens, $i, 1, $import); + // Modify parameters of the for-loop + $i--; + $l = count($tokens); + } + } + } + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for preserve parsing expression() declaration values. + * + * This plugin return no {@link aCssToken CssToken} but ensures that expression() declaration values will get parsed + * properly. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssExpressionParserPlugin extends aCssParserPlugin +{ + /** + * Count of left braces. + * + * @var integer + */ + private $leftBraces = 0; + /** + * Count of right braces. + * + * @var integer + */ + private $rightBraces = 0; + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("(", ")", ";", "}"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return false; + } + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + // Start of expression + if ($char === "(" && strtolower(substr($this->parser->getSource(), $index - 10, 11)) === "expression(" && $state !== "T_EXPRESSION") + { + $this->parser->pushState("T_EXPRESSION"); + $this->leftBraces++; + } + // Count left braces + elseif ($char === "(" && $state === "T_EXPRESSION") + { + $this->leftBraces++; + } + // Count right braces + elseif ($char === ")" && $state === "T_EXPRESSION") + { + $this->rightBraces++; + } + // Possible end of expression; if left and right braces are equal the expressen ends + elseif (($char === ";" || $char === "}") && $state === "T_EXPRESSION" && $this->leftBraces === $this->rightBraces) + { + $this->leftBraces = $this->rightBraces = 0; + $this->parser->popState(); + return $index - 1; + } + else + { + return false; + } + return true; + } +} + +/** + * CSS Error. + * + * @package CssMin + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssError +{ + /** + * File. + * + * @var string + */ + public $File = ""; + /** + * Line. + * + * @var integer + */ + public $Line = 0; + /** + * Error message. + * + * @var string + */ + public $Message = ""; + /** + * Source. + * + * @var string + */ + public $Source = ""; + /** + * Constructor triggering the error. + * + * @param string $message Error message + * @param string $source Corresponding line [optional] + * @return void + */ + public function __construct($file, $line, $message, $source = "") + { + $this->File = $file; + $this->Line = $line; + $this->Message = $message; + $this->Source = $source; + } + /** + * Returns the error as formatted string. + * + * @return string + */ + public function __toString() + { + return $this->Message . ($this->Source ? ":
" . $this->Source . "": "") . "
in file " . $this->File . " at line " . $this->Line; + } +} + +/** + * This {@link aCssMinifierPlugin} will convert a color value in rgb notation to hexadecimal notation. + * + * Example: + * + * color: rgb(200,60%,5); + * + * + * Will get converted to: + * + * color:#c89905; + * + * + * @package CssMin/Minifier/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssConvertRgbColorsMinifierPlugin extends aCssMinifierPlugin +{ + /** + * Regular expression matching the value. + * + * @var string + */ + private $reMatch = "/rgb\s*\(\s*([0-9%]+)\s*,\s*([0-9%]+)\s*,\s*([0-9%]+)\s*\)/iS"; + /** + * Implements {@link aCssMinifierPlugin::minify()}. + * + * @param aCssToken $token Token to process + * @return boolean Return TRUE to break the processing of this token; FALSE to continue + */ + public function apply(aCssToken &$token) + { + if (stripos($token->Value, "rgb") !== false && preg_match($this->reMatch, $token->Value, $m)) + { + for ($i = 1, $l = count($m); $i < $l; $i++) + { + if (strpos("%", $m[$i]) !== false) + { + $m[$i] = substr($m[$i], 0, -1); + $m[$i] = (int) (256 * ($m[$i] / 100)); + } + $m[$i] = str_pad(dechex($m[$i]), 2, "0", STR_PAD_LEFT); + } + $token->Value = str_replace($m[0], "#" . $m[1] . $m[2] . $m[3], $token->Value); + } + return false; + } + /** + * Implements {@link aMinifierPlugin::getTriggerTokens()} + * + * @return array + */ + public function getTriggerTokens() + { + return array + ( + "CssAtFontFaceDeclarationToken", + "CssAtPageDeclarationToken", + "CssRulesetDeclarationToken" + ); + } +} + +/** + * This {@link aCssMinifierPlugin} will convert named color values to hexadecimal notation. + * + * Example: + * + * color: black; + * border: 1px solid indigo; + * + * + * Will get converted to: + * + * color:#000; + * border:1px solid #4b0082; + * + * + * @package CssMin/Minifier/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssConvertNamedColorsMinifierPlugin extends aCssMinifierPlugin +{ + + /** + * Regular expression matching the value. + * + * @var string + */ + private $reMatch = null; + /** + * Transformation table used by the {@link CssConvertNamedColorsMinifierPlugin::reReplace() replacement method}. + * + * @var array + */ + private $transformation = array + ( + "aliceblue" => "#f0f8ff", + "antiquewhite" => "#faebd7", + "aqua" => "#0ff", + "aquamarine" => "#7fffd4", + "azure" => "#f0ffff", + "beige" => "#f5f5dc", + "black" => "#000", + "blue" => "#00f", + "blueviolet" => "#8a2be2", + "brown" => "#a52a2a", + "burlywood" => "#deb887", + "cadetblue" => "#5f9ea0", + "chartreuse" => "#7fff00", + "chocolate" => "#d2691e", + "coral" => "#ff7f50", + "cornflowerblue" => "#6495ed", + "cornsilk" => "#fff8dc", + "crimson" => "#dc143c", + "darkblue" => "#00008b", + "darkcyan" => "#008b8b", + "darkgoldenrod" => "#b8860b", + "darkgray" => "#a9a9a9", + "darkgreen" => "#006400", + "darkkhaki" => "#bdb76b", + "darkmagenta" => "#8b008b", + "darkolivegreen" => "#556b2f", + "darkorange" => "#ff8c00", + "darkorchid" => "#9932cc", + "darkred" => "#8b0000", + "darksalmon" => "#e9967a", + "darkseagreen" => "#8fbc8f", + "darkslateblue" => "#483d8b", + "darkslategray" => "#2f4f4f", + "darkturquoise" => "#00ced1", + "darkviolet" => "#9400d3", + "deeppink" => "#ff1493", + "deepskyblue" => "#00bfff", + "dimgray" => "#696969", + "dodgerblue" => "#1e90ff", + "firebrick" => "#b22222", + "floralwhite" => "#fffaf0", + "forestgreen" => "#228b22", + "fuchsia" => "#f0f", + "gainsboro" => "#dcdcdc", + "ghostwhite" => "#f8f8ff", + "gold" => "#ffd700", + "goldenrod" => "#daa520", + "gray" => "#808080", + "green" => "#008000", + "greenyellow" => "#adff2f", + "honeydew" => "#f0fff0", + "hotpink" => "#ff69b4", + "indianred" => "#cd5c5c", + "indigo" => "#4b0082", + "ivory" => "#fffff0", + "khaki" => "#f0e68c", + "lavender" => "#e6e6fa", + "lavenderblush" => "#fff0f5", + "lawngreen" => "#7cfc00", + "lemonchiffon" => "#fffacd", + "lightblue" => "#add8e6", + "lightcoral" => "#f08080", + "lightcyan" => "#e0ffff", + "lightgoldenrodyellow" => "#fafad2", + "lightgreen" => "#90ee90", + "lightgrey" => "#d3d3d3", + "lightpink" => "#ffb6c1", + "lightsalmon" => "#ffa07a", + "lightseagreen" => "#20b2aa", + "lightskyblue" => "#87cefa", + "lightslategray" => "#789", + "lightsteelblue" => "#b0c4de", + "lightyellow" => "#ffffe0", + "lime" => "#0f0", + "limegreen" => "#32cd32", + "linen" => "#faf0e6", + "maroon" => "#800000", + "mediumaquamarine" => "#66cdaa", + "mediumblue" => "#0000cd", + "mediumorchid" => "#ba55d3", + "mediumpurple" => "#9370db", + "mediumseagreen" => "#3cb371", + "mediumslateblue" => "#7b68ee", + "mediumspringgreen" => "#00fa9a", + "mediumturquoise" => "#48d1cc", + "mediumvioletred" => "#c71585", + "midnightblue" => "#191970", + "mintcream" => "#f5fffa", + "mistyrose" => "#ffe4e1", + "moccasin" => "#ffe4b5", + "navajowhite" => "#ffdead", + "navy" => "#000080", + "oldlace" => "#fdf5e6", + "olive" => "#808000", + "olivedrab" => "#6b8e23", + "orange" => "#ffa500", + "orangered" => "#ff4500", + "orchid" => "#da70d6", + "palegoldenrod" => "#eee8aa", + "palegreen" => "#98fb98", + "paleturquoise" => "#afeeee", + "palevioletred" => "#db7093", + "papayawhip" => "#ffefd5", + "peachpuff" => "#ffdab9", + "peru" => "#cd853f", + "pink" => "#ffc0cb", + "plum" => "#dda0dd", + "powderblue" => "#b0e0e6", + "purple" => "#800080", + "red" => "#f00", + "rosybrown" => "#bc8f8f", + "royalblue" => "#4169e1", + "saddlebrown" => "#8b4513", + "salmon" => "#fa8072", + "sandybrown" => "#f4a460", + "seagreen" => "#2e8b57", + "seashell" => "#fff5ee", + "sienna" => "#a0522d", + "silver" => "#c0c0c0", + "skyblue" => "#87ceeb", + "slateblue" => "#6a5acd", + "slategray" => "#708090", + "snow" => "#fffafa", + "springgreen" => "#00ff7f", + "steelblue" => "#4682b4", + "tan" => "#d2b48c", + "teal" => "#008080", + "thistle" => "#d8bfd8", + "tomato" => "#ff6347", + "turquoise" => "#40e0d0", + "violet" => "#ee82ee", + "wheat" => "#f5deb3", + "white" => "#fff", + "whitesmoke" => "#f5f5f5", + "yellow" => "#ff0", + "yellowgreen" => "#9acd32" + ); + /** + * Overwrites {@link aCssMinifierPlugin::__construct()}. + * + * The constructor will create the {@link CssConvertNamedColorsMinifierPlugin::$reMatch replace regular expression} + * based on the {@link CssConvertNamedColorsMinifierPlugin::$transformation transformation table}. + * + * @param CssMinifier $minifier The CssMinifier object of this plugin. + * @param array $configuration Plugin configuration [optional] + * @return void + */ + public function __construct(CssMinifier $minifier, array $configuration = array()) + { + $this->reMatch = "/(^|\s)+(" . implode("|", array_keys($this->transformation)) . ")(\s|$)+/iS"; + parent::__construct($minifier, $configuration); + } + /** + * Implements {@link aCssMinifierPlugin::minify()}. + * + * @param aCssToken $token Token to process + * @return boolean Return TRUE to break the processing of this token; FALSE to continue + */ + public function apply(aCssToken &$token) + { + $lcValue = strtolower($token->Value); + // Declaration value equals a value in the transformation table => simple replace + if (isset($this->transformation[$lcValue])) + { + $token->Value = $this->transformation[$lcValue]; + } + // Declaration value contains a value in the transformation table => regular expression replace + elseif (preg_match($this->reMatch, $token->Value)) + { + $token->Value = preg_replace_callback($this->reMatch, array($this, 'reReplace'), $token->Value); + } + return false; + } + /** + * Callback for replacement value. + * + * @param array $match + * @return string + */ + private function reReplace($match) + { + return $match[1] . $this->transformation[strtolower($match[2])] . $match[3]; + } + /** + * Implements {@link aMinifierPlugin::getTriggerTokens()} + * + * @return array + */ + public function getTriggerTokens() + { + return array + ( + "CssAtFontFaceDeclarationToken", + "CssAtPageDeclarationToken", + "CssRulesetDeclarationToken" + ); + } +} + +/** + * This {@link aCssMinifierFilter minifier filter} triggers on CSS Level 3 properties and will add declaration tokens + * with browser-specific properties. + * + * @package CssMin/Minifier/Filters + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssConvertLevel3PropertiesMinifierFilter extends aCssMinifierFilter +{ + /** + * Css property transformations table. Used to convert CSS3 and proprietary properties to the browser-specific + * counterparts. + * + * @var array + */ + private $transformations = array + ( + // Property Array(Mozilla, Webkit, Opera, Internet Explorer); NULL values are placeholders and will get ignored + "animation" => array(null, "-webkit-animation", null, null), + "animation-delay" => array(null, "-webkit-animation-delay", null, null), + "animation-direction" => array(null, "-webkit-animation-direction", null, null), + "animation-duration" => array(null, "-webkit-animation-duration", null, null), + "animation-fill-mode" => array(null, "-webkit-animation-fill-mode", null, null), + "animation-iteration-count" => array(null, "-webkit-animation-iteration-count", null, null), + "animation-name" => array(null, "-webkit-animation-name", null, null), + "animation-play-state" => array(null, "-webkit-animation-play-state", null, null), + "animation-timing-function" => array(null, "-webkit-animation-timing-function", null, null), + "appearance" => array("-moz-appearance", "-webkit-appearance", null, null), + "backface-visibility" => array(null, "-webkit-backface-visibility", null, null), + "background-clip" => array(null, "-webkit-background-clip", null, null), + "background-composite" => array(null, "-webkit-background-composite", null, null), + "background-inline-policy" => array("-moz-background-inline-policy", null, null, null), + "background-origin" => array(null, "-webkit-background-origin", null, null), + "background-position-x" => array(null, null, null, "-ms-background-position-x"), + "background-position-y" => array(null, null, null, "-ms-background-position-y"), + "background-size" => array(null, "-webkit-background-size", null, null), + "behavior" => array(null, null, null, "-ms-behavior"), + "binding" => array("-moz-binding", null, null, null), + "border-after" => array(null, "-webkit-border-after", null, null), + "border-after-color" => array(null, "-webkit-border-after-color", null, null), + "border-after-style" => array(null, "-webkit-border-after-style", null, null), + "border-after-width" => array(null, "-webkit-border-after-width", null, null), + "border-before" => array(null, "-webkit-border-before", null, null), + "border-before-color" => array(null, "-webkit-border-before-color", null, null), + "border-before-style" => array(null, "-webkit-border-before-style", null, null), + "border-before-width" => array(null, "-webkit-border-before-width", null, null), + "border-border-bottom-colors" => array("-moz-border-bottom-colors", null, null, null), + "border-bottom-left-radius" => array("-moz-border-radius-bottomleft", "-webkit-border-bottom-left-radius", null, null), + "border-bottom-right-radius" => array("-moz-border-radius-bottomright", "-webkit-border-bottom-right-radius", null, null), + "border-end" => array("-moz-border-end", "-webkit-border-end", null, null), + "border-end-color" => array("-moz-border-end-color", "-webkit-border-end-color", null, null), + "border-end-style" => array("-moz-border-end-style", "-webkit-border-end-style", null, null), + "border-end-width" => array("-moz-border-end-width", "-webkit-border-end-width", null, null), + "border-fit" => array(null, "-webkit-border-fit", null, null), + "border-horizontal-spacing" => array(null, "-webkit-border-horizontal-spacing", null, null), + "border-image" => array("-moz-border-image", "-webkit-border-image", null, null), + "border-left-colors" => array("-moz-border-left-colors", null, null, null), + "border-radius" => array("-moz-border-radius", "-webkit-border-radius", null, null), + "border-border-right-colors" => array("-moz-border-right-colors", null, null, null), + "border-start" => array("-moz-border-start", "-webkit-border-start", null, null), + "border-start-color" => array("-moz-border-start-color", "-webkit-border-start-color", null, null), + "border-start-style" => array("-moz-border-start-style", "-webkit-border-start-style", null, null), + "border-start-width" => array("-moz-border-start-width", "-webkit-border-start-width", null, null), + "border-top-colors" => array("-moz-border-top-colors", null, null, null), + "border-top-left-radius" => array("-moz-border-radius-topleft", "-webkit-border-top-left-radius", null, null), + "border-top-right-radius" => array("-moz-border-radius-topright", "-webkit-border-top-right-radius", null, null), + "border-vertical-spacing" => array(null, "-webkit-border-vertical-spacing", null, null), + "box-align" => array("-moz-box-align", "-webkit-box-align", null, null), + "box-direction" => array("-moz-box-direction", "-webkit-box-direction", null, null), + "box-flex" => array("-moz-box-flex", "-webkit-box-flex", null, null), + "box-flex-group" => array(null, "-webkit-box-flex-group", null, null), + "box-flex-lines" => array(null, "-webkit-box-flex-lines", null, null), + "box-ordinal-group" => array("-moz-box-ordinal-group", "-webkit-box-ordinal-group", null, null), + "box-orient" => array("-moz-box-orient", "-webkit-box-orient", null, null), + "box-pack" => array("-moz-box-pack", "-webkit-box-pack", null, null), + "box-reflect" => array(null, "-webkit-box-reflect", null, null), + "box-shadow" => array("-moz-box-shadow", "-webkit-box-shadow", null, null), + "box-sizing" => array("-moz-box-sizing", null, null, null), + "color-correction" => array(null, "-webkit-color-correction", null, null), + "column-break-after" => array(null, "-webkit-column-break-after", null, null), + "column-break-before" => array(null, "-webkit-column-break-before", null, null), + "column-break-inside" => array(null, "-webkit-column-break-inside", null, null), + "column-count" => array("-moz-column-count", "-webkit-column-count", null, null), + "column-gap" => array("-moz-column-gap", "-webkit-column-gap", null, null), + "column-rule" => array("-moz-column-rule", "-webkit-column-rule", null, null), + "column-rule-color" => array("-moz-column-rule-color", "-webkit-column-rule-color", null, null), + "column-rule-style" => array("-moz-column-rule-style", "-webkit-column-rule-style", null, null), + "column-rule-width" => array("-moz-column-rule-width", "-webkit-column-rule-width", null, null), + "column-span" => array(null, "-webkit-column-span", null, null), + "column-width" => array("-moz-column-width", "-webkit-column-width", null, null), + "columns" => array(null, "-webkit-columns", null, null), + "filter" => array(__CLASS__, "filter"), + "float-edge" => array("-moz-float-edge", null, null, null), + "font-feature-settings" => array("-moz-font-feature-settings", null, null, null), + "font-language-override" => array("-moz-font-language-override", null, null, null), + "font-size-delta" => array(null, "-webkit-font-size-delta", null, null), + "font-smoothing" => array(null, "-webkit-font-smoothing", null, null), + "force-broken-image-icon" => array("-moz-force-broken-image-icon", null, null, null), + "highlight" => array(null, "-webkit-highlight", null, null), + "hyphenate-character" => array(null, "-webkit-hyphenate-character", null, null), + "hyphenate-locale" => array(null, "-webkit-hyphenate-locale", null, null), + "hyphens" => array(null, "-webkit-hyphens", null, null), + "force-broken-image-icon" => array("-moz-image-region", null, null, null), + "ime-mode" => array(null, null, null, "-ms-ime-mode"), + "interpolation-mode" => array(null, null, null, "-ms-interpolation-mode"), + "layout-flow" => array(null, null, null, "-ms-layout-flow"), + "layout-grid" => array(null, null, null, "-ms-layout-grid"), + "layout-grid-char" => array(null, null, null, "-ms-layout-grid-char"), + "layout-grid-line" => array(null, null, null, "-ms-layout-grid-line"), + "layout-grid-mode" => array(null, null, null, "-ms-layout-grid-mode"), + "layout-grid-type" => array(null, null, null, "-ms-layout-grid-type"), + "line-break" => array(null, "-webkit-line-break", null, "-ms-line-break"), + "line-clamp" => array(null, "-webkit-line-clamp", null, null), + "line-grid-mode" => array(null, null, null, "-ms-line-grid-mode"), + "logical-height" => array(null, "-webkit-logical-height", null, null), + "logical-width" => array(null, "-webkit-logical-width", null, null), + "margin-after" => array(null, "-webkit-margin-after", null, null), + "margin-after-collapse" => array(null, "-webkit-margin-after-collapse", null, null), + "margin-before" => array(null, "-webkit-margin-before", null, null), + "margin-before-collapse" => array(null, "-webkit-margin-before-collapse", null, null), + "margin-bottom-collapse" => array(null, "-webkit-margin-bottom-collapse", null, null), + "margin-collapse" => array(null, "-webkit-margin-collapse", null, null), + "margin-end" => array("-moz-margin-end", "-webkit-margin-end", null, null), + "margin-start" => array("-moz-margin-start", "-webkit-margin-start", null, null), + "margin-top-collapse" => array(null, "-webkit-margin-top-collapse", null, null), + "marquee " => array(null, "-webkit-marquee", null, null), + "marquee-direction" => array(null, "-webkit-marquee-direction", null, null), + "marquee-increment" => array(null, "-webkit-marquee-increment", null, null), + "marquee-repetition" => array(null, "-webkit-marquee-repetition", null, null), + "marquee-speed" => array(null, "-webkit-marquee-speed", null, null), + "marquee-style" => array(null, "-webkit-marquee-style", null, null), + "mask" => array(null, "-webkit-mask", null, null), + "mask-attachment" => array(null, "-webkit-mask-attachment", null, null), + "mask-box-image" => array(null, "-webkit-mask-box-image", null, null), + "mask-clip" => array(null, "-webkit-mask-clip", null, null), + "mask-composite" => array(null, "-webkit-mask-composite", null, null), + "mask-image" => array(null, "-webkit-mask-image", null, null), + "mask-origin" => array(null, "-webkit-mask-origin", null, null), + "mask-position" => array(null, "-webkit-mask-position", null, null), + "mask-position-x" => array(null, "-webkit-mask-position-x", null, null), + "mask-position-y" => array(null, "-webkit-mask-position-y", null, null), + "mask-repeat" => array(null, "-webkit-mask-repeat", null, null), + "mask-repeat-x" => array(null, "-webkit-mask-repeat-x", null, null), + "mask-repeat-y" => array(null, "-webkit-mask-repeat-y", null, null), + "mask-size" => array(null, "-webkit-mask-size", null, null), + "match-nearest-mail-blockquote-color" => array(null, "-webkit-match-nearest-mail-blockquote-color", null, null), + "max-logical-height" => array(null, "-webkit-max-logical-height", null, null), + "max-logical-width" => array(null, "-webkit-max-logical-width", null, null), + "min-logical-height" => array(null, "-webkit-min-logical-height", null, null), + "min-logical-width" => array(null, "-webkit-min-logical-width", null, null), + "object-fit" => array(null, null, "-o-object-fit", null), + "object-position" => array(null, null, "-o-object-position", null), + "opacity" => array(__CLASS__, "opacity"), + "outline-radius" => array("-moz-outline-radius", null, null, null), + "outline-bottom-left-radius" => array("-moz-outline-radius-bottomleft", null, null, null), + "outline-bottom-right-radius" => array("-moz-outline-radius-bottomright", null, null, null), + "outline-top-left-radius" => array("-moz-outline-radius-topleft", null, null, null), + "outline-top-right-radius" => array("-moz-outline-radius-topright", null, null, null), + "padding-after" => array(null, "-webkit-padding-after", null, null), + "padding-before" => array(null, "-webkit-padding-before", null, null), + "padding-end" => array("-moz-padding-end", "-webkit-padding-end", null, null), + "padding-start" => array("-moz-padding-start", "-webkit-padding-start", null, null), + "perspective" => array(null, "-webkit-perspective", null, null), + "perspective-origin" => array(null, "-webkit-perspective-origin", null, null), + "perspective-origin-x" => array(null, "-webkit-perspective-origin-x", null, null), + "perspective-origin-y" => array(null, "-webkit-perspective-origin-y", null, null), + "rtl-ordering" => array(null, "-webkit-rtl-ordering", null, null), + "scrollbar-3dlight-color" => array(null, null, null, "-ms-scrollbar-3dlight-color"), + "scrollbar-arrow-color" => array(null, null, null, "-ms-scrollbar-arrow-color"), + "scrollbar-base-color" => array(null, null, null, "-ms-scrollbar-base-color"), + "scrollbar-darkshadow-color" => array(null, null, null, "-ms-scrollbar-darkshadow-color"), + "scrollbar-face-color" => array(null, null, null, "-ms-scrollbar-face-color"), + "scrollbar-highlight-color" => array(null, null, null, "-ms-scrollbar-highlight-color"), + "scrollbar-shadow-color" => array(null, null, null, "-ms-scrollbar-shadow-color"), + "scrollbar-track-color" => array(null, null, null, "-ms-scrollbar-track-color"), + "stack-sizing" => array("-moz-stack-sizing", null, null, null), + "svg-shadow" => array(null, "-webkit-svg-shadow", null, null), + "tab-size" => array("-moz-tab-size", null, "-o-tab-size", null), + "table-baseline" => array(null, null, "-o-table-baseline", null), + "text-align-last" => array(null, null, null, "-ms-text-align-last"), + "text-autospace" => array(null, null, null, "-ms-text-autospace"), + "text-combine" => array(null, "-webkit-text-combine", null, null), + "text-decorations-in-effect" => array(null, "-webkit-text-decorations-in-effect", null, null), + "text-emphasis" => array(null, "-webkit-text-emphasis", null, null), + "text-emphasis-color" => array(null, "-webkit-text-emphasis-color", null, null), + "text-emphasis-position" => array(null, "-webkit-text-emphasis-position", null, null), + "text-emphasis-style" => array(null, "-webkit-text-emphasis-style", null, null), + "text-fill-color" => array(null, "-webkit-text-fill-color", null, null), + "text-justify" => array(null, null, null, "-ms-text-justify"), + "text-kashida-space" => array(null, null, null, "-ms-text-kashida-space"), + "text-overflow" => array(null, null, "-o-text-overflow", "-ms-text-overflow"), + "text-security" => array(null, "-webkit-text-security", null, null), + "text-size-adjust" => array(null, "-webkit-text-size-adjust", null, "-ms-text-size-adjust"), + "text-stroke" => array(null, "-webkit-text-stroke", null, null), + "text-stroke-color" => array(null, "-webkit-text-stroke-color", null, null), + "text-stroke-width" => array(null, "-webkit-text-stroke-width", null, null), + "text-underline-position" => array(null, null, null, "-ms-text-underline-position"), + "transform" => array("-moz-transform", "-webkit-transform", "-o-transform", null), + "transform-origin" => array("-moz-transform-origin", "-webkit-transform-origin", "-o-transform-origin", null), + "transform-origin-x" => array(null, "-webkit-transform-origin-x", null, null), + "transform-origin-y" => array(null, "-webkit-transform-origin-y", null, null), + "transform-origin-z" => array(null, "-webkit-transform-origin-z", null, null), + "transform-style" => array(null, "-webkit-transform-style", null, null), + "transition" => array("-moz-transition", "-webkit-transition", "-o-transition", null), + "transition-delay" => array("-moz-transition-delay", "-webkit-transition-delay", "-o-transition-delay", null), + "transition-duration" => array("-moz-transition-duration", "-webkit-transition-duration", "-o-transition-duration", null), + "transition-property" => array("-moz-transition-property", "-webkit-transition-property", "-o-transition-property", null), + "transition-timing-function" => array("-moz-transition-timing-function", "-webkit-transition-timing-function", "-o-transition-timing-function", null), + "user-drag" => array(null, "-webkit-user-drag", null, null), + "user-focus" => array("-moz-user-focus", null, null, null), + "user-input" => array("-moz-user-input", null, null, null), + "user-modify" => array("-moz-user-modify", "-webkit-user-modify", null, null), + "user-select" => array("-moz-user-select", "-webkit-user-select", null, null), + "white-space" => array(__CLASS__, "whiteSpace"), + "window-shadow" => array("-moz-window-shadow", null, null, null), + "word-break" => array(null, null, null, "-ms-word-break"), + "word-wrap" => array(null, null, null, "-ms-word-wrap"), + "writing-mode" => array(null, "-webkit-writing-mode", null, "-ms-writing-mode"), + "zoom" => array(null, null, null, "-ms-zoom") + ); + /** + * Implements {@link aCssMinifierFilter::filter()}. + * + * @param array $tokens Array of objects of type aCssToken + * @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array + */ + public function apply(array &$tokens) + { + $r = 0; + $transformations = &$this->transformations; + for ($i = 0, $l = count($tokens); $i < $l; $i++) + { + if (get_class($tokens[$i]) === "CssRulesetDeclarationToken") + { + $tProperty = $tokens[$i]->Property; + if (isset($transformations[$tProperty])) + { + $result = array(); + if (is_callable($transformations[$tProperty])) + { + $result = call_user_func_array($transformations[$tProperty], array($tokens[$i])); + if (!is_array($result) && is_object($result)) + { + $result = array($result); + } + } + else + { + $tValue = $tokens[$i]->Value; + $tMediaTypes = $tokens[$i]->MediaTypes; + foreach ($transformations[$tProperty] as $property) + { + if ($property !== null) + { + $result[] = new CssRulesetDeclarationToken($property, $tValue, $tMediaTypes); + } + } + } + if (count($result) > 0) + { + array_splice($tokens, $i + 1, 0, $result); + $i += count($result); + $l += count($result); + } + } + } + } + return $r; + } + /** + * Transforms the Internet Explorer specific declaration property "filter" to Internet Explorer 8+ compatible + * declaratiopn property "-ms-filter". + * + * @param aCssToken $token + * @return array + */ + private static function filter($token) + { + $r = array + ( + new CssRulesetDeclarationToken("-ms-filter", "\"" . $token->Value . "\"", $token->MediaTypes), + ); + return $r; + } + /** + * Transforms "opacity: {value}" into browser specific counterparts. + * + * @param aCssToken $token + * @return array + */ + private static function opacity($token) + { + // Calculate the value for Internet Explorer filter statement + $ieValue = (int) ((float) $token->Value * 100); + $r = array + ( + // Internet Explorer >= 8 + new CssRulesetDeclarationToken("-ms-filter", "\"alpha(opacity=" . $ieValue . ")\"", $token->MediaTypes), + // Internet Explorer >= 4 <= 7 + new CssRulesetDeclarationToken("filter", "alpha(opacity=" . $ieValue . ")", $token->MediaTypes), + new CssRulesetDeclarationToken("zoom", "1", $token->MediaTypes) + ); + return $r; + } + /** + * Transforms "white-space: pre-wrap" into browser specific counterparts. + * + * @param aCssToken $token + * @return array + */ + private static function whiteSpace($token) + { + if (strtolower($token->Value) === "pre-wrap") + { + $r = array + ( + // Firefox < 3 + new CssRulesetDeclarationToken("white-space", "-moz-pre-wrap", $token->MediaTypes), + // Webkit + new CssRulesetDeclarationToken("white-space", "-webkit-pre-wrap", $token->MediaTypes), + // Opera >= 4 <= 6 + new CssRulesetDeclarationToken("white-space", "-pre-wrap", $token->MediaTypes), + // Opera >= 7 + new CssRulesetDeclarationToken("white-space", "-o-pre-wrap", $token->MediaTypes), + // Internet Explorer >= 5.5 + new CssRulesetDeclarationToken("word-wrap", "break-word", $token->MediaTypes) + ); + return $r; + } + else + { + return array(); + } + } +} + +/** + * This {@link aCssMinifierFilter minifier filter} will convert @keyframes at-rule block to browser specific counterparts. + * + * @package CssMin/Minifier/Filters + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssConvertLevel3AtKeyframesMinifierFilter extends aCssMinifierFilter +{ + /** + * Implements {@link aCssMinifierFilter::filter()}. + * + * @param array $tokens Array of objects of type aCssToken + * @return integer Count of added, changed or removed tokens; a return value larger than 0 will rebuild the array + */ + public function apply(array &$tokens) + { + $r = 0; + $transformations = array("-moz-keyframes", "-webkit-keyframes"); + for ($i = 0, $l = count($tokens); $i < $l; $i++) + { + if (get_class($tokens[$i]) === "CssAtKeyframesStartToken") + { + for ($ii = $i; $ii < $l; $ii++) + { + if (get_class($tokens[$ii]) === "CssAtKeyframesEndToken") + { + break; + } + } + if (get_class($tokens[$ii]) === "CssAtKeyframesEndToken") + { + $add = array(); + $source = array(); + for ($iii = $i; $iii <= $ii; $iii++) + { + $source[] = clone($tokens[$iii]); + } + foreach ($transformations as $transformation) + { + $t = array(); + foreach ($source as $token) + { + $t[] = clone($token); + } + $t[0]->AtRuleName = $transformation; + $add = array_merge($add, $t); + } + if (isset($this->configuration["RemoveSource"]) && $this->configuration["RemoveSource"] === true) + { + array_splice($tokens, $i, $ii - $i + 1, $add); + } + else + { + array_splice($tokens, $ii + 1, 0, $add); + } + $l = count($tokens); + $i = $ii + count($add); + $r += count($add); + } + } + } + return $r; + } +} + +/** + * This {@link aCssMinifierPlugin} will convert a color value in hsl notation to hexadecimal notation. + * + * Example: + * + * color: hsl(232,36%,48%); + * + * + * Will get converted to: + * + * color:#4e5aa7; + * + * + * @package CssMin/Minifier/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssConvertHslColorsMinifierPlugin extends aCssMinifierPlugin +{ + /** + * Regular expression matching the value. + * + * @var string + */ + private $reMatch = "/^hsl\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*%\s*,\s*([0-9]+)\s*%\s*\)/iS"; + /** + * Implements {@link aCssMinifierPlugin::minify()}. + * + * @param aCssToken $token Token to process + * @return boolean Return TRUE to break the processing of this token; FALSE to continue + */ + public function apply(aCssToken &$token) + { + if (stripos($token->Value, "hsl") !== false && preg_match($this->reMatch, $token->Value, $m)) + { + $token->Value = str_replace($m[0], $this->hsl2hex($m[1], $m[2], $m[3]), $token->Value); + } + return false; + } + /** + * Implements {@link aMinifierPlugin::getTriggerTokens()} + * + * @return array + */ + public function getTriggerTokens() + { + return array + ( + "CssAtFontFaceDeclarationToken", + "CssAtPageDeclarationToken", + "CssRulesetDeclarationToken" + ); + } + /** + * Convert a HSL value to hexadecimal notation. + * + * Based on: {@link http://www.easyrgb.com/index.php?X=MATH&H=19#text19}. + * + * @param integer $hue Hue + * @param integer $saturation Saturation + * @param integer $lightness Lightnesss + * @return string + */ + private function hsl2hex($hue, $saturation, $lightness) + { + $hue = $hue / 360; + $saturation = $saturation / 100; + $lightness = $lightness / 100; + if ($saturation == 0) + { + $red = $lightness * 255; + $green = $lightness * 255; + $blue = $lightness * 255; + } + else + { + if ($lightness < 0.5 ) + { + $v2 = $lightness * (1 + $saturation); + } + else + { + $v2 = ($lightness + $saturation) - ($saturation * $lightness); + } + $v1 = 2 * $lightness - $v2; + $red = 255 * self::hue2rgb($v1, $v2, $hue + (1 / 3)); + $green = 255 * self::hue2rgb($v1, $v2, $hue); + $blue = 255 * self::hue2rgb($v1, $v2, $hue - (1 / 3)); + } + return "#" . str_pad(dechex(round($red)), 2, "0", STR_PAD_LEFT) . str_pad(dechex(round($green)), 2, "0", STR_PAD_LEFT) . str_pad(dechex(round($blue)), 2, "0", STR_PAD_LEFT); + } + /** + * Apply hue to a rgb color value. + * + * @param integer $v1 Value 1 + * @param integer $v2 Value 2 + * @param integer $hue Hue + * @return integer + */ + private function hue2rgb($v1, $v2, $hue) + { + if ($hue < 0) + { + $hue += 1; + } + if ($hue > 1) + { + $hue -= 1; + } + if ((6 * $hue) < 1) + { + return ($v1 + ($v2 - $v1) * 6 * $hue); + } + if ((2 * $hue) < 1) + { + return ($v2); + } + if ((3 * $hue) < 2) + { + return ($v1 + ($v2 - $v1) * (( 2 / 3) - $hue) * 6); + } + return $v1; + } +} + +/** + * This {@link aCssMinifierPlugin} will convert the font-weight values normal and bold to their numeric notation. + * + * Example: + * + * font-weight: normal; + * font: bold 11px monospace; + * + * + * Will get converted to: + * + * font-weight:400; + * font:700 11px monospace; + * + * + * @package CssMin/Minifier/Pluginsn + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssConvertFontWeightMinifierPlugin extends aCssMinifierPlugin +{ + /** + * Array of included declaration properties this plugin will process; others declaration properties will get + * ignored. + * + * @var array + */ + private $include = array + ( + "font", + "font-weight" + ); + /** + * Regular expression matching the value. + * + * @var string + */ + private $reMatch = null; + /** + * Transformation table used by the {@link CssConvertFontWeightMinifierPlugin::reReplace() replacement method}. + * + * @var array + */ + private $transformation = array + ( + "normal" => "400", + "bold" => "700" + ); + /** + * Overwrites {@link aCssMinifierPlugin::__construct()}. + * + * The constructor will create the {@link CssConvertFontWeightMinifierPlugin::$reMatch replace regular expression} + * based on the {@link CssConvertFontWeightMinifierPlugin::$transformation transformation table}. + * + * @param CssMinifier $minifier The CssMinifier object of this plugin. + * @return void + */ + public function __construct(CssMinifier $minifier) + { + $this->reMatch = "/(^|\s)+(" . implode("|", array_keys($this->transformation)). ")(\s|$)+/iS"; + parent::__construct($minifier); + } + /** + * Implements {@link aCssMinifierPlugin::minify()}. + * + * @param aCssToken $token Token to process + * @return boolean Return TRUE to break the processing of this token; FALSE to continue + */ + public function apply(aCssToken &$token) + { + if (in_array($token->Property, $this->include) && preg_match($this->reMatch, $token->Value, $m)) + { + $token->Value = preg_replace_callback($this->reMatch, array($this, 'reReplace'), $token->Value); + } + return false; + } + /** + * Callback for replacement value. + * + * @param array $match + * @return string + */ + private function reReplace($match) + { + return $match[1] . $this->transformation[strtolower($match[2])] . $match[3]; + } + /** + * Implements {@link aMinifierPlugin::getTriggerTokens()} + * + * @return array + */ + public function getTriggerTokens() + { + return array + ( + "CssAtFontFaceDeclarationToken", + "CssAtPageDeclarationToken", + "CssRulesetDeclarationToken" + ); + } +} + +/** + * This {@link aCssMinifierPlugin} will compress several unit values to their short notations. Examples: + * + * + * padding: 0.5em; + * border: 0px; + * margin: 0 0 0 0; + * + * + * Will get compressed to: + * + * + * padding:.5px; + * border:0; + * margin:0; + * + * + * -- + * + * @package CssMin/Minifier/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssCompressUnitValuesMinifierPlugin extends aCssMinifierPlugin +{ + /** + * Regular expression used for matching and replacing unit values. + * + * @var array + */ + private $re = array + ( + "/(^| |-)0\.([0-9]+?)(0+)?(%|em|ex|px|in|cm|mm|pt|pc)/iS" => "\${1}.\${2}\${4}", + "/(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/iS" => "\${1}0", + ); + /** + * Regular expression used for matching and replacing unit values only for non-blacklisted declarations. + * + * @var array + */ + private $reBlacklisted = array( + "/(^0\s0\s0\s0)|(^0\s0\s0$)|(^0\s0$)/iS" => "0", + ); + /** + * Blacklisted properties for the above regular expression. + * + * @var array + */ + private $propertiesBlacklist = array('background-position'); + /** + * Regular expression matching the value. + * + * @var string + */ + private $reMatch = "/(^| |-)0\.([0-9]+?)(0+)?(%|em|ex|px|in|cm|mm|pt|pc)|(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)|(^0\s0\s0\s0$)|(^0\s0\s0$)|(^0\s0$)/iS"; + /** + * Implements {@link aCssMinifierPlugin::minify()}. + * + * @param aCssToken $token Token to process + * @return boolean Return TRUE to break the processing of this token; FALSE to continue + */ + public function apply(aCssToken &$token) + { + if (preg_match($this->reMatch, $token->Value)) + { + foreach ($this->re as $reMatch => $reReplace) + { + $token->Value = preg_replace($reMatch, $reReplace, $token->Value); + } + if (!in_array($token->Property, $this->propertiesBlacklist)) + { + foreach ($this->reBlacklisted as $reMatch => $reReplace) + { + $token->Value = preg_replace($reMatch, $reReplace, $token->Value); + } + } + } + return false; + } + /** + * Implements {@link aMinifierPlugin::getTriggerTokens()} + * + * @return array + */ + public function getTriggerTokens() + { + return array + ( + "CssAtFontFaceDeclarationToken", + "CssAtPageDeclarationToken", + "CssRulesetDeclarationToken" + ); + } +} + +/** + * This {@link aCssMinifierPlugin} compress the content of expresssion() declaration values. + * + * For compression of expressions {@link https://github.com/rgrove/jsmin-php/ JSMin} will get used. JSMin have to be + * already included or loadable via {@link http://goo.gl/JrW54 PHP autoloading}. + * + * @package CssMin/Minifier/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssCompressExpressionValuesMinifierPlugin extends aCssMinifierPlugin +{ + /** + * Implements {@link aCssMinifierPlugin::minify()}. + * + * @param aCssToken $token Token to process + * @return boolean Return TRUE to break the processing of this token; FALSE to continue + */ + public function apply(aCssToken &$token) + { + if (class_exists("JSMin") && stripos($token->Value, "expression(") !== false) + { + $value = $token->Value; + $value = substr($token->Value, stripos($token->Value, "expression(") + 10); + $value = trim(JSMin::minify($value)); + $token->Value = "expression(" . $value . ")"; + } + return false; + } + /** + * Implements {@link aMinifierPlugin::getTriggerTokens()} + * + * @return array + */ + public function getTriggerTokens() + { + return array + ( + "CssAtFontFaceDeclarationToken", + "CssAtPageDeclarationToken", + "CssRulesetDeclarationToken" + ); + } +} + +/** + * This {@link aCssMinifierPlugin} will convert hexadecimal color value with 6 chars to their 3 char hexadecimal + * notation (if possible). + * + * Example: + * + * color: #aabbcc; + * + * + * Will get converted to: + * + * color:#abc; + * + * + * @package CssMin/Minifier/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssCompressColorValuesMinifierPlugin extends aCssMinifierPlugin +{ + /** + * Regular expression matching 6 char hexadecimal color values. + * + * @var string + */ + private $reMatch = "/\#([0-9a-f]{6})/iS"; + /** + * Implements {@link aCssMinifierPlugin::minify()}. + * + * @param aCssToken $token Token to process + * @return boolean Return TRUE to break the processing of this token; FALSE to continue + */ + public function apply(aCssToken &$token) + { + if (strpos($token->Value, "#") !== false && preg_match($this->reMatch, $token->Value, $m)) + { + $value = strtolower($m[1]); + if ($value[0] == $value[1] && $value[2] == $value[3] && $value[4] == $value[5]) + { + $token->Value = str_replace($m[0], "#" . $value[0] . $value[2] . $value[4], $token->Value); + } + } + return false; + } + /** + * Implements {@link aMinifierPlugin::getTriggerTokens()} + * + * @return array + */ + public function getTriggerTokens() + { + return array + ( + "CssAtFontFaceDeclarationToken", + "CssAtPageDeclarationToken", + "CssRulesetDeclarationToken" + ); + } +} + +/** + * This {@link aCssToken CSS token} represents a CSS comment. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssCommentToken extends aCssToken +{ + /** + * Comment as Text. + * + * @var string + */ + public $Comment = ""; + /** + * Set the properties of a comment token. + * + * @param string $comment Comment including comment delimiters + * @return void + */ + public function __construct($comment) + { + $this->Comment = $comment; + } + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return $this->Comment; + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for parsing comments. + * + * Adds a {@link CssCommentToken} to the parser if a comment was found. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssCommentParserPlugin extends aCssParserPlugin +{ + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("*", "/"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return false; + } + /** + * Stored buffer for restore. + * + * @var string + */ + private $restoreBuffer = ""; + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + if ($char === "*" && $previousChar === "/" && $state !== "T_COMMENT") + { + $this->parser->pushState("T_COMMENT"); + $this->parser->setExclusive(__CLASS__); + $this->restoreBuffer = substr($this->parser->getAndClearBuffer(), 0, -2); + } + elseif ($char === "/" && $previousChar === "*" && $state === "T_COMMENT") + { + $this->parser->popState(); + $this->parser->unsetExclusive(); + $this->parser->appendToken(new CssCommentToken("/*" . $this->parser->getAndClearBuffer())); + $this->parser->setBuffer($this->restoreBuffer); + } + else + { + return false; + } + return true; + } +} + +/** + * This {@link aCssToken CSS token} represents the start of a @variables at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtVariablesStartToken extends aCssAtBlockStartToken +{ + /** + * Media types of the @variables at-rule block. + * + * @var array + */ + public $MediaTypes = array(); + /** + * Set the properties of a @variables at-rule token. + * + * @param array $mediaTypes Media types + * @return void + */ + public function __construct($mediaTypes = null) + { + $this->MediaTypes = $mediaTypes ? $mediaTypes : array("all"); + } + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return ""; + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for parsing @variables at-rule block with including declarations. + * + * Found @variables at-rule blocks will add a {@link CssAtVariablesStartToken} and {@link CssAtVariablesEndToken} to the + * parser; including declarations as {@link CssAtVariablesDeclarationToken}. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtVariablesParserPlugin extends aCssParserPlugin +{ + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("@", "{", "}", ":", ";"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return array("T_DOCUMENT", "T_AT_VARIABLES::PREPARE", "T_AT_VARIABLES", "T_AT_VARIABLES_DECLARATION"); + } + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + // Start of @variables at-rule block + if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@variables") + { + $this->parser->pushState("T_AT_VARIABLES::PREPARE"); + $this->parser->clearBuffer(); + return $index + 10; + } + // Start of @variables declarations + elseif ($char === "{" && $state === "T_AT_VARIABLES::PREPARE") + { + $this->parser->setState("T_AT_VARIABLES"); + $mediaTypes = array_filter(array_map("trim", explode(",", $this->parser->getAndClearBuffer("{")))); + $this->parser->appendToken(new CssAtVariablesStartToken($mediaTypes)); + } + // Start of @variables declaration + if ($char === ":" && $state === "T_AT_VARIABLES") + { + $this->buffer = $this->parser->getAndClearBuffer(":"); + $this->parser->pushState("T_AT_VARIABLES_DECLARATION"); + } + // Unterminated @variables declaration + elseif ($char === ":" && $state === "T_AT_VARIABLES_DECLARATION") + { + // Ignore Internet Explorer filter declarations + if ($this->buffer === "filter") + { + return false; + } + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @variables declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_")); + } + // End of @variables declaration + elseif (($char === ";" || $char === "}") && $state === "T_AT_VARIABLES_DECLARATION") + { + $value = $this->parser->getAndClearBuffer(";}"); + if (strtolower(substr($value, -10, 10)) === "!important") + { + $value = trim(substr($value, 0, -10)); + $isImportant = true; + } + else + { + $isImportant = false; + } + $this->parser->popState(); + $this->parser->appendToken(new CssAtVariablesDeclarationToken($this->buffer, $value, $isImportant)); + $this->buffer = ""; + } + // End of @variables at-rule block + elseif ($char === "}" && $state === "T_AT_VARIABLES") + { + $this->parser->popState(); + $this->parser->clearBuffer(); + $this->parser->appendToken(new CssAtVariablesEndToken()); + } + else + { + return false; + } + return true; + } +} + +/** + * This {@link aCssToken CSS token} represents the end of a @variables at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtVariablesEndToken extends aCssAtBlockEndToken +{ + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return ""; + } +} + +/** + * This {@link aCssToken CSS token} represents a declaration of a @variables at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtVariablesDeclarationToken extends aCssDeclarationToken +{ + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return ""; + } +} + +/** + * This {@link aCssToken CSS token} represents the start of a @page at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtPageStartToken extends aCssAtBlockStartToken +{ + /** + * Selector. + * + * @var string + */ + public $Selector = ""; + /** + * Sets the properties of the @page at-rule. + * + * @param string $selector Selector + * @return void + */ + public function __construct($selector = "") + { + $this->Selector = $selector; + } + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return "@page" . ($this->Selector ? " " . $this->Selector : "") . "{"; + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for parsing @page at-rule block with including declarations. + * + * Found @page at-rule blocks will add a {@link CssAtPageStartToken} and {@link CssAtPageEndToken} to the + * parser; including declarations as {@link CssAtPageDeclarationToken}. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtPageParserPlugin extends aCssParserPlugin +{ + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("@", "{", "}", ":", ";"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return array("T_DOCUMENT", "T_AT_PAGE::SELECTOR", "T_AT_PAGE", "T_AT_PAGE_DECLARATION"); + } + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + // Start of @page at-rule block + if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 5)) === "@page") + { + $this->parser->pushState("T_AT_PAGE::SELECTOR"); + $this->parser->clearBuffer(); + return $index + 5; + } + // Start of @page declarations + elseif ($char === "{" && $state === "T_AT_PAGE::SELECTOR") + { + $selector = $this->parser->getAndClearBuffer("{"); + $this->parser->setState("T_AT_PAGE"); + $this->parser->clearBuffer(); + $this->parser->appendToken(new CssAtPageStartToken($selector)); + } + // Start of @page declaration + elseif ($char === ":" && $state === "T_AT_PAGE") + { + $this->parser->pushState("T_AT_PAGE_DECLARATION"); + $this->buffer = $this->parser->getAndClearBuffer(":", true); + } + // Unterminated @font-face declaration + elseif ($char === ":" && $state === "T_AT_PAGE_DECLARATION") + { + // Ignore Internet Explorer filter declarations + if ($this->buffer === "filter") + { + return false; + } + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @page declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_")); + } + // End of @page declaration + elseif (($char === ";" || $char === "}") && $state == "T_AT_PAGE_DECLARATION") + { + $value = $this->parser->getAndClearBuffer(";}"); + if (strtolower(substr($value, -10, 10)) == "!important") + { + $value = trim(substr($value, 0, -10)); + $isImportant = true; + } + else + { + $isImportant = false; + } + $this->parser->popState(); + $this->parser->appendToken(new CssAtPageDeclarationToken($this->buffer, $value, $isImportant)); + // -- + if ($char === "}") + { + $this->parser->popState(); + $this->parser->appendToken(new CssAtPageEndToken()); + } + $this->buffer = ""; + } + // End of @page at-rule block + elseif ($char === "}" && $state === "T_AT_PAGE") + { + $this->parser->popState(); + $this->parser->clearBuffer(); + $this->parser->appendToken(new CssAtPageEndToken()); + } + else + { + return false; + } + return true; + } +} + +/** + * This {@link aCssToken CSS token} represents the end of a @page at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtPageEndToken extends aCssAtBlockEndToken +{ + +} + +/** + * This {@link aCssToken CSS token} represents a declaration of a @page at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtPageDeclarationToken extends aCssDeclarationToken +{ + +} + +/** + * This {@link aCssToken CSS token} represents the start of a @media at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtMediaStartToken extends aCssAtBlockStartToken +{ + public $MediaTypes = array(); + + /** + * Sets the properties of the @media at-rule. + * + * @param array $mediaTypes Media types + * @return void + */ + public function __construct(array $mediaTypes = array()) + { + $this->MediaTypes = $mediaTypes; + } + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return "@media " . implode(",", $this->MediaTypes) . "{"; + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for parsing @media at-rule block. + * + * Found @media at-rule blocks will add a {@link CssAtMediaStartToken} and {@link CssAtMediaEndToken} to the parser. + * This plugin will also set the the current media types using {@link CssParser::setMediaTypes()} and + * {@link CssParser::unsetMediaTypes()}. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtMediaParserPlugin extends aCssParserPlugin +{ + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("@", "{", "}"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return array("T_DOCUMENT", "T_AT_MEDIA::PREPARE", "T_AT_MEDIA"); + } + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 6)) === "@media") + { + $this->parser->pushState("T_AT_MEDIA::PREPARE"); + $this->parser->clearBuffer(); + return $index + 6; + } + elseif ($char === "{" && $state === "T_AT_MEDIA::PREPARE") + { + $mediaTypes = array_filter(array_map("trim", explode(",", $this->parser->getAndClearBuffer("{")))); + $this->parser->setMediaTypes($mediaTypes); + $this->parser->setState("T_AT_MEDIA"); + $this->parser->appendToken(new CssAtMediaStartToken($mediaTypes)); + } + elseif ($char === "}" && $state === "T_AT_MEDIA") + { + $this->parser->appendToken(new CssAtMediaEndToken()); + $this->parser->clearBuffer(); + $this->parser->unsetMediaTypes(); + $this->parser->popState(); + } + else + { + return false; + } + return true; + } +} + +/** + * This {@link aCssToken CSS token} represents the end of a @media at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtMediaEndToken extends aCssAtBlockEndToken +{ + +} + +/** + * This {@link aCssToken CSS token} represents the start of a @keyframes at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtKeyframesStartToken extends aCssAtBlockStartToken +{ + /** + * Name of the at-rule. + * + * @var string + */ + public $AtRuleName = "keyframes"; + /** + * Name + * + * @var string + */ + public $Name = ""; + /** + * Sets the properties of the @page at-rule. + * + * @param string $selector Selector + * @return void + */ + public function __construct($name, $atRuleName = null) + { + $this->Name = $name; + if (!is_null($atRuleName)) + { + $this->AtRuleName = $atRuleName; + } + } + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + if ($this->AtRuleName === "-moz-keyframes") + { + return "@-moz-keyframes " . $this->Name . " {"; + } + return "@" . $this->AtRuleName . " " . $this->Name . "{"; + } +} + +/** + * This {@link aCssToken CSS token} represents the start of a ruleset of a @keyframes at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtKeyframesRulesetStartToken extends aCssRulesetStartToken +{ + /** + * Array of selectors. + * + * @var array + */ + public $Selectors = array(); + /** + * Set the properties of a ruleset token. + * + * @param array $selectors Selectors of the ruleset + * @return void + */ + public function __construct(array $selectors = array()) + { + $this->Selectors = $selectors; + } + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return implode(",", $this->Selectors) . "{"; + } +} + +/** + * This {@link aCssToken CSS token} represents the end of a ruleset of a @keyframes at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtKeyframesRulesetEndToken extends aCssRulesetEndToken +{ + +} + +/** + * This {@link aCssToken CSS token} represents a ruleset declaration of a @keyframes at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtKeyframesRulesetDeclarationToken extends aCssDeclarationToken +{ + +} + +/** + * {@link aCssParserPlugin Parser plugin} for parsing @keyframes at-rule blocks, rulesets and declarations. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtKeyframesParserPlugin extends aCssParserPlugin +{ + /** + * @var string Keyword + */ + private $atRuleName = ""; + /** + * Selectors. + * + * @var array + */ + private $selectors = array(); + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("@", "{", "}", ":", ",", ";"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return array("T_DOCUMENT", "T_AT_KEYFRAMES::NAME", "T_AT_KEYFRAMES", "T_AT_KEYFRAMES_RULESETS", "T_AT_KEYFRAMES_RULESET", "T_AT_KEYFRAMES_RULESET_DECLARATION"); + } + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + // Start of @keyframes at-rule block + if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@keyframes") + { + $this->atRuleName = "keyframes"; + $this->parser->pushState("T_AT_KEYFRAMES::NAME"); + $this->parser->clearBuffer(); + return $index + 10; + } + // Start of @keyframes at-rule block (@-moz-keyframes) + elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 15)) === "@-moz-keyframes") + { + $this->atRuleName = "-moz-keyframes"; + $this->parser->pushState("T_AT_KEYFRAMES::NAME"); + $this->parser->clearBuffer(); + return $index + 15; + } + // Start of @keyframes at-rule block (@-webkit-keyframes) + elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 18)) === "@-webkit-keyframes") + { + $this->atRuleName = "-webkit-keyframes"; + $this->parser->pushState("T_AT_KEYFRAMES::NAME"); + $this->parser->clearBuffer(); + return $index + 18; + } + // Start of @keyframes at-rule block (@-o-keyframes) + elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 13)) === "@-o-keyframes") + { + $this->atRuleName = "-o-keyframes"; + $this->parser->pushState("T_AT_KEYFRAMES::NAME"); + $this->parser->clearBuffer(); + return $index + 13; + } + // Start of @keyframes at-rule block (@-ms-keyframes) + elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 14)) === "@-ms-keyframes") + { + $this->atRuleName = "-ms-keyframes"; + $this->parser->pushState("T_AT_KEYFRAMES::NAME"); + $this->parser->clearBuffer(); + return $index + 14; + } + // Start of @keyframes rulesets + elseif ($char === "{" && $state === "T_AT_KEYFRAMES::NAME") + { + $name = $this->parser->getAndClearBuffer("{\"'"); + $this->parser->setState("T_AT_KEYFRAMES_RULESETS"); + $this->parser->clearBuffer(); + $this->parser->appendToken(new CssAtKeyframesStartToken($name, $this->atRuleName)); + } + // Start of @keyframe ruleset and selectors + if ($char === "," && $state === "T_AT_KEYFRAMES_RULESETS") + { + $this->selectors[] = $this->parser->getAndClearBuffer(",{"); + } + // Start of a @keyframes ruleset + elseif ($char === "{" && $state === "T_AT_KEYFRAMES_RULESETS") + { + if ($this->parser->getBuffer() !== "") + { + $this->selectors[] = $this->parser->getAndClearBuffer(",{"); + $this->parser->pushState("T_AT_KEYFRAMES_RULESET"); + $this->parser->appendToken(new CssAtKeyframesRulesetStartToken($this->selectors)); + $this->selectors = array(); + } + } + // Start of @keyframes ruleset declaration + elseif ($char === ":" && $state === "T_AT_KEYFRAMES_RULESET") + { + $this->parser->pushState("T_AT_KEYFRAMES_RULESET_DECLARATION"); + $this->buffer = $this->parser->getAndClearBuffer(":;", true); + } + // Unterminated @keyframes ruleset declaration + elseif ($char === ":" && $state === "T_AT_KEYFRAMES_RULESET_DECLARATION") + { + // Ignore Internet Explorer filter declarations + if ($this->buffer === "filter") + { + return false; + } + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @keyframes ruleset declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_")); + } + // End of declaration + elseif (($char === ";" || $char === "}") && $state === "T_AT_KEYFRAMES_RULESET_DECLARATION") + { + $value = $this->parser->getAndClearBuffer(";}"); + if (strtolower(substr($value, -10, 10)) === "!important") + { + $value = trim(substr($value, 0, -10)); + $isImportant = true; + } + else + { + $isImportant = false; + } + $this->parser->popState(); + $this->parser->appendToken(new CssAtKeyframesRulesetDeclarationToken($this->buffer, $value, $isImportant)); + // Declaration ends with a right curly brace; so we have to end the ruleset + if ($char === "}") + { + $this->parser->appendToken(new CssAtKeyframesRulesetEndToken()); + $this->parser->popState(); + } + $this->buffer = ""; + } + // End of @keyframes ruleset + elseif ($char === "}" && $state === "T_AT_KEYFRAMES_RULESET") + { + $this->parser->clearBuffer(); + + $this->parser->popState(); + $this->parser->appendToken(new CssAtKeyframesRulesetEndToken()); + } + // End of @keyframes rulesets + elseif ($char === "}" && $state === "T_AT_KEYFRAMES_RULESETS") + { + $this->parser->clearBuffer(); + $this->parser->popState(); + $this->parser->appendToken(new CssAtKeyframesEndToken()); + } + else + { + return false; + } + return true; + } +} + +/** + * This {@link aCssToken CSS token} represents the end of a @keyframes at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtKeyframesEndToken extends aCssAtBlockEndToken +{ + +} + +/** + * This {@link aCssToken CSS token} represents a @import at-rule. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1.b1 (2001-02-22) + */ +class CssAtImportToken extends aCssToken +{ + /** + * Import path of the @import at-rule. + * + * @var string + */ + public $Import = ""; + /** + * Media types of the @import at-rule. + * + * @var array + */ + public $MediaTypes = array(); + /** + * Set the properties of a @import at-rule token. + * + * @param string $import Import path + * @param array $mediaTypes Media types + * @return void + */ + public function __construct($import, $mediaTypes) + { + $this->Import = $import; + $this->MediaTypes = $mediaTypes ? $mediaTypes : array(); + } + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return "@import \"" . $this->Import . "\"" . (count($this->MediaTypes) > 0 ? " " . implode(",", $this->MediaTypes) : ""). ";"; + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for parsing @import at-rule. + * + * If a @import at-rule was found this plugin will add a {@link CssAtImportToken} to the parser. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtImportParserPlugin extends aCssParserPlugin +{ + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("@", ";", ",", "\n"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return array("T_DOCUMENT", "T_AT_IMPORT"); + } + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 7)) === "@import") + { + $this->parser->pushState("T_AT_IMPORT"); + $this->parser->clearBuffer(); + return $index + 7; + } + elseif (($char === ";" || $char === "\n") && $state === "T_AT_IMPORT") + { + $this->buffer = $this->parser->getAndClearBuffer(";"); + $pos = false; + foreach (array(")", "\"", "'") as $needle) + { + if (($pos = strrpos($this->buffer, $needle)) !== false) + { + break; + } + } + $import = substr($this->buffer, 0, $pos + 1); + if (stripos($import, "url(") === 0) + { + $import = substr($import, 4, -1); + } + $import = trim($import, " \t\n\r\0\x0B'\""); + $mediaTypes = array_filter(array_map("trim", explode(",", trim(substr($this->buffer, $pos + 1), " \t\n\r\0\x0B{")))); + if ($pos) + { + $this->parser->appendToken(new CssAtImportToken($import, $mediaTypes)); + } + else + { + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Invalid @import at-rule syntax", $this->buffer)); + } + $this->parser->popState(); + } + else + { + return false; + } + return true; + } +} + +/** + * This {@link aCssToken CSS token} represents the start of a @font-face at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtFontFaceStartToken extends aCssAtBlockStartToken +{ + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return "@font-face{"; + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for parsing @font-face at-rule block with including declarations. + * + * Found @font-face at-rule blocks will add a {@link CssAtFontFaceStartToken} and {@link CssAtFontFaceEndToken} to the + * parser; including declarations as {@link CssAtFontFaceDeclarationToken}. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtFontFaceParserPlugin extends aCssParserPlugin +{ + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("@", "{", "}", ":", ";"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return array("T_DOCUMENT", "T_AT_FONT_FACE::PREPARE", "T_AT_FONT_FACE", "T_AT_FONT_FACE_DECLARATION"); + } + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + // Start of @font-face at-rule block + if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@font-face") + { + $this->parser->pushState("T_AT_FONT_FACE::PREPARE"); + $this->parser->clearBuffer(); + return $index + 10 - 1; + } + // Start of @font-face declarations + elseif ($char === "{" && $state === "T_AT_FONT_FACE::PREPARE") + { + $this->parser->setState("T_AT_FONT_FACE"); + $this->parser->clearBuffer(); + $this->parser->appendToken(new CssAtFontFaceStartToken()); + } + // Start of @font-face declaration + elseif ($char === ":" && $state === "T_AT_FONT_FACE") + { + $this->parser->pushState("T_AT_FONT_FACE_DECLARATION"); + $this->buffer = $this->parser->getAndClearBuffer(":", true); + } + // Unterminated @font-face declaration + elseif ($char === ":" && $state === "T_AT_FONT_FACE_DECLARATION") + { + // Ignore Internet Explorer filter declarations + if ($this->buffer === "filter") + { + return false; + } + CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @font-face declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_")); + } + // End of @font-face declaration + elseif (($char === ";" || $char === "}") && $state === "T_AT_FONT_FACE_DECLARATION") + { + $value = $this->parser->getAndClearBuffer(";}"); + if (strtolower(substr($value, -10, 10)) === "!important") + { + $value = trim(substr($value, 0, -10)); + $isImportant = true; + } + else + { + $isImportant = false; + } + $this->parser->popState(); + $this->parser->appendToken(new CssAtFontFaceDeclarationToken($this->buffer, $value, $isImportant)); + $this->buffer = ""; + // -- + if ($char === "}") + { + $this->parser->appendToken(new CssAtFontFaceEndToken()); + $this->parser->popState(); + } + } + // End of @font-face at-rule block + elseif ($char === "}" && $state === "T_AT_FONT_FACE") + { + $this->parser->appendToken(new CssAtFontFaceEndToken()); + $this->parser->clearBuffer(); + $this->parser->popState(); + } + else + { + return false; + } + return true; + } +} + +/** + * This {@link aCssToken CSS token} represents the end of a @font-face at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtFontFaceEndToken extends aCssAtBlockEndToken +{ + +} + +/** + * This {@link aCssToken CSS token} represents a declaration of a @font-face at-rule block. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtFontFaceDeclarationToken extends aCssDeclarationToken +{ + +} + +/** + * This {@link aCssToken CSS token} represents a @charset at-rule. + * + * @package CssMin/Tokens + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtCharsetToken extends aCssToken +{ + /** + * Charset of the @charset at-rule. + * + * @var string + */ + public $Charset = ""; + /** + * Set the properties of @charset at-rule token. + * + * @param string $charset Charset of the @charset at-rule token + * @return void + */ + public function __construct($charset) + { + $this->Charset = $charset; + } + /** + * Implements {@link aCssToken::__toString()}. + * + * @return string + */ + public function __toString() + { + return "@charset " . $this->Charset . ";"; + } +} + +/** + * {@link aCssParserPlugin Parser plugin} for parsing @charset at-rule. + * + * If a @charset at-rule was found this plugin will add a {@link CssAtCharsetToken} to the parser. + * + * @package CssMin/Parser/Plugins + * @link http://code.google.com/p/cssmin/ + * @author Joe Scylla + * @copyright 2008 - 2011 Joe Scylla + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 3.0.1 + */ +class CssAtCharsetParserPlugin extends aCssParserPlugin +{ + /** + * Implements {@link aCssParserPlugin::getTriggerChars()}. + * + * @return array + */ + public function getTriggerChars() + { + return array("@", ";", "\n"); + } + /** + * Implements {@link aCssParserPlugin::getTriggerStates()}. + * + * @return array + */ + public function getTriggerStates() + { + return array("T_DOCUMENT", "T_AT_CHARSET"); + } + /** + * Implements {@link aCssParserPlugin::parse()}. + * + * @param integer $index Current index + * @param string $char Current char + * @param string $previousChar Previous char + * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing + */ + public function parse($index, $char, $previousChar, $state) + { + if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 8)) === "@charset") + { + $this->parser->pushState("T_AT_CHARSET"); + $this->parser->clearBuffer(); + return $index + 8; + } + elseif (($char === ";" || $char === "\n") && $state === "T_AT_CHARSET") + { + $charset = $this->parser->getAndClearBuffer(";"); + $this->parser->popState(); + $this->parser->appendToken(new CssAtCharsetToken($charset)); + } + else + { + return false; + } + return true; + } +} + +?> diff --git a/freescout-dist/overrides/nesbot/carbon/src/Carbon/Carbon.php b/freescout-dist/overrides/nesbot/carbon/src/Carbon/Carbon.php new file mode 100644 index 0000000..2407323 --- /dev/null +++ b/freescout-dist/overrides/nesbot/carbon/src/Carbon/Carbon.php @@ -0,0 +1,4944 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\InvalidDateException; +use Closure; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeInterface; +use DateTimeZone; +use InvalidArgumentException; +use JsonSerializable; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * A simple API extension for DateTime + * + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $timestamp seconds since the Unix Epoch + * @property \DateTimeZone $timezone the current timezone + * @property \DateTimeZone $tz alias of timezone + * @property-read int $micro + * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property-read int $dayOfYear 0 through 365 + * @property-read int $weekOfMonth 1 through 5 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read int $daysInMonth number of days in the given month + * @property-read int $age does a diffInYears() with default parameters + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $offset the timezone offset in seconds from UTC + * @property-read int $offsetHours the timezone offset in hours from UTC + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName + * @property-read string $tzName + * @property-read string $englishDayOfWeek the day of week in English + * @property-read string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property-read string $englishMonth the day of week in English + * @property-read string $shortEnglishMonth the abbreviated day of week in English + * @property-read string $localeDayOfWeek the day of week in current locale LC_TIME + * @property-read string $shortLocaleDayOfWeek the abbreviated day of week in current locale LC_TIME + * @property-read string $localeMonth the month in current locale LC_TIME + * @property-read string $shortLocaleMonth the abbreviated month in current locale LC_TIME + */ +class Carbon extends DateTime implements JsonSerializable +{ + const NO_ZERO_DIFF = 01; + const JUST_NOW = 02; + const ONE_DAY_WORDS = 04; + const TWO_DAY_WORDS = 010; + + /** + * The day constants. + */ + const SUNDAY = 0; + const MONDAY = 1; + const TUESDAY = 2; + const WEDNESDAY = 3; + const THURSDAY = 4; + const FRIDAY = 5; + const SATURDAY = 6; + + /** + * Names of days of the week. + * + * @var array + */ + protected static $days = array( + self::SUNDAY => 'Sunday', + self::MONDAY => 'Monday', + self::TUESDAY => 'Tuesday', + self::WEDNESDAY => 'Wednesday', + self::THURSDAY => 'Thursday', + self::FRIDAY => 'Friday', + self::SATURDAY => 'Saturday', + ); + + /** + * Number of X in Y. + */ + const YEARS_PER_CENTURY = 100; + const YEARS_PER_DECADE = 10; + const MONTHS_PER_YEAR = 12; + const MONTHS_PER_QUARTER = 3; + const WEEKS_PER_YEAR = 52; + const WEEKS_PER_MONTH = 4; + const DAYS_PER_WEEK = 7; + const HOURS_PER_DAY = 24; + const MINUTES_PER_HOUR = 60; + const SECONDS_PER_MINUTE = 60; + + /** + * RFC7231 DateTime format. + * + * @var string + */ + const RFC7231_FORMAT = 'D, d M Y H:i:s \G\M\T'; + + /** + * Default format to use for __toString method when type juggling occurs. + * + * @var string + */ + const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s'; + + /** + * Format for converting mocked time, includes microseconds. + * + * @var string + */ + const MOCK_DATETIME_FORMAT = 'Y-m-d H:i:s.u'; + + /** + * Customizable PHP_INT_SIZE override. + * + * @var int + */ + public static $PHPIntSize = PHP_INT_SIZE; + + /** + * Format to use for __toString method when type juggling occurs. + * + * @var string + */ + protected static $toStringFormat = self::DEFAULT_TO_STRING_FORMAT; + + /** + * First day of week. + * + * @var int + */ + protected static $weekStartsAt = self::MONDAY; + + /** + * Last day of week. + * + * @var int + */ + protected static $weekEndsAt = self::SUNDAY; + + /** + * Days of weekend. + * + * @var array + */ + protected static $weekendDays = array( + self::SATURDAY, + self::SUNDAY, + ); + + /** + * Midday/noon hour. + * + * @var int + */ + protected static $midDayAt = 12; + + /** + * Format regex patterns. + * + * @var array + */ + protected static $regexFormats = array( + 'd' => '(3[01]|[12][0-9]|0[1-9])', + 'D' => '([a-zA-Z]{3})', + 'j' => '([123][0-9]|[1-9])', + 'l' => '([a-zA-Z]{2,})', + 'N' => '([1-7])', + 'S' => '([a-zA-Z]{2})', + 'w' => '([0-6])', + 'z' => '(36[0-5]|3[0-5][0-9]|[12][0-9]{2}|[1-9]?[0-9])', + 'W' => '(5[012]|[1-4][0-9]|[1-9])', + 'F' => '([a-zA-Z]{2,})', + 'm' => '(1[012]|0[1-9])', + 'M' => '([a-zA-Z]{3})', + 'n' => '(1[012]|[1-9])', + 't' => '(2[89]|3[01])', + 'L' => '(0|1)', + 'o' => '([1-9][0-9]{0,4})', + 'Y' => '([1-9]?[0-9]{4})', + 'y' => '([0-9]{2})', + 'a' => '(am|pm)', + 'A' => '(AM|PM)', + 'B' => '([0-9]{3})', + 'g' => '(1[012]|[1-9])', + 'G' => '(2[0-3]|1?[0-9])', + 'h' => '(1[012]|0[1-9])', + 'H' => '(2[0-3]|[01][0-9])', + 'i' => '([0-5][0-9])', + 's' => '([0-5][0-9])', + 'u' => '([0-9]{1,6})', + 'v' => '([0-9]{1,3})', + 'e' => '([a-zA-Z]{1,5})|([a-zA-Z]*\/[a-zA-Z]*)', + 'I' => '(0|1)', + 'O' => '([\+\-](1[012]|0[0-9])[0134][05])', + 'P' => '([\+\-](1[012]|0[0-9]):[0134][05])', + 'T' => '([a-zA-Z]{1,5})', + 'Z' => '(-?[1-5]?[0-9]{1,4})', + 'U' => '([0-9]*)', + + // The formats below are combinations of the above formats. + 'c' => '(([1-9]?[0-9]{4})\-(1[012]|0[1-9])\-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[\+\-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP + 'r' => '(([a-zA-Z]{3}), ([123][0-9]|[1-9]) ([a-zA-Z]{3}) ([1-9]?[0-9]{4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [\+\-](1[012]|0[0-9])([0134][05]))', // D, j M Y H:i:s O + ); + + /** + * A test Carbon instance to be returned when now instances are created. + * + * @var \Carbon\Carbon + */ + protected static $testNow; + + /** + * A translator to ... er ... translate stuff. + * + * @var \Symfony\Component\Translation\TranslatorInterface + */ + protected static $translator; + + /** + * The errors that can occur. + * + * @var array + */ + protected static $lastErrors; + + /** + * The custom Carbon JSON serializer. + * + * @var callable|null + */ + protected static $serializer; + + /** + * The registered string macros. + * + * @var array + */ + protected static $localMacros = array(); + + /** + * Will UTF8 encoding be used to print localized date/time ? + * + * @var bool + */ + protected static $utf8 = false; + + /** + * Add microseconds to now on PHP < 7.1 and 7.1.3. true by default. + * + * @var bool + */ + protected static $microsecondsFallback = true; + + /** + * Indicates if months should be calculated with overflow. + * + * @var bool + */ + protected static $monthsOverflow = true; + + /** + * Indicates if years should be calculated with overflow. + * + * @var bool + */ + protected static $yearsOverflow = true; + + /** + * Indicates if years are compared with month by default so isSameMonth and isSameQuarter have $ofSameYear set + * to true by default. + * + * @var bool + */ + protected static $compareYearWithMonth = false; + + /** + * Options for diffForHumans(). + * + * @var int + */ + protected static $humanDiffOptions = self::NO_ZERO_DIFF; + + /** + * @param int $humanDiffOptions + */ + public static function setHumanDiffOptions($humanDiffOptions) + { + static::$humanDiffOptions = $humanDiffOptions; + } + + /** + * @param int $humanDiffOption + */ + public static function enableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption; + } + + /** + * @param int $humanDiffOption + */ + public static function disableHumanDiffOption($humanDiffOption) + { + static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption; + } + + /** + * @return int + */ + public static function getHumanDiffOptions() + { + return static::$humanDiffOptions; + } + + /** + * Add microseconds to now on PHP < 7.1 and 7.1.3 if set to true, + * let microseconds to 0 on those PHP versions if false. + * + * @param bool $microsecondsFallback + */ + public static function useMicrosecondsFallback($microsecondsFallback = true) + { + static::$microsecondsFallback = $microsecondsFallback; + } + + /** + * Return true if microseconds fallback on PHP < 7.1 and 7.1.3 is + * enabled. false if disabled. + * + * @return bool + */ + public static function isMicrosecondsFallbackEnabled() + { + return static::$microsecondsFallback; + } + + /** + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow($monthsOverflow = true) + { + static::$monthsOverflow = $monthsOverflow; + } + + /** + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow() + { + static::$monthsOverflow = true; + } + + /** + * Get the month overflow behavior. + * + * @return bool + */ + public static function shouldOverflowMonths() + { + return static::$monthsOverflow; + } + + /** + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow($yearsOverflow = true) + { + static::$yearsOverflow = $yearsOverflow; + } + + /** + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow() + { + static::$yearsOverflow = true; + } + + /** + * Get the month overflow behavior. + * + * @return bool + */ + public static function shouldOverflowYears() + { + return static::$yearsOverflow; + } + + /** + * Get the month comparison default behavior. + * + * @return bool + */ + public static function compareYearWithMonth($compareYearWithMonth = true) + { + static::$compareYearWithMonth = $compareYearWithMonth; + } + + /** + * Get the month comparison default behavior. + * + * @return bool + */ + public static function shouldCompareYearWithMonth() + { + return static::$compareYearWithMonth; + } + + /** + * Creates a DateTimeZone from a string, DateTimeZone or integer offset. + * + * @param \DateTimeZone|string|int|null $object + * + * @throws \InvalidArgumentException + * + * @return \DateTimeZone + */ + protected static function safeCreateDateTimeZone($object) + { + if ($object === null) { + // Don't return null... avoid Bug #52063 in PHP <5.3.6 + return new DateTimeZone(date_default_timezone_get()); + } + + if ($object instanceof DateTimeZone) { + return $object; + } + + if (is_numeric($object)) { + $tzName = timezone_name_from_abbr(null, $object * 3600, true); + + if ($tzName === false) { + throw new InvalidArgumentException('Unknown or bad timezone ('.$object.')'); + } + + $object = $tzName; + } + + $tz = @timezone_open($object = (string) $object); + + if ($tz !== false) { + return $tz; + } + + // Work-around for a bug fixed in PHP 5.5.10 https://bugs.php.net/bug.php?id=45528 + // See: https://stackoverflow.com/q/14068594/2646927 + // @codeCoverageIgnoreStart + if (strpos($object, ':') !== false) { + try { + return static::createFromFormat('O', $object)->getTimezone(); + } catch (InvalidArgumentException $e) { + // + } + } + // @codeCoverageIgnoreEnd + + throw new InvalidArgumentException('Unknown or bad timezone ('.$object.')'); + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// CONSTRUCTORS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Create a new Carbon instance. + * + * Please see the testing aids section (specifically static::setTestNow()) + * for more on the possibility of this constructor returning a test instance. + * + * @param string|null $time + * @param \DateTimeZone|string|null $tz + */ + public function __construct($time = null, $tz = null) + { + // If the class has a test now set and we are trying to create a now() + // instance then override as required + $isNow = empty($time) || $time === 'now'; + if (static::hasTestNow() && ($isNow || static::hasRelativeKeywords($time))) { + $testInstance = clone static::getTestNow(); + + //shift the time according to the given time zone + if ($tz !== null && $tz !== static::getTestNow()->getTimezone()) { + $testInstance->setTimezone($tz); + } else { + $tz = $testInstance->getTimezone(); + } + + if (static::hasRelativeKeywords($time)) { + $testInstance->modify($time); + } + + $time = $testInstance->format(static::MOCK_DATETIME_FORMAT); + } + + $timezone = static::safeCreateDateTimeZone($tz); + // @codeCoverageIgnoreStart + if ($isNow && !isset($testInstance) && static::isMicrosecondsFallbackEnabled() && ( + version_compare(PHP_VERSION, '7.1.0-dev', '<') + || + version_compare(PHP_VERSION, '7.1.3-dev', '>=') && version_compare(PHP_VERSION, '7.1.4-dev', '<') + ) + ) { + // Get microseconds from microtime() if "now" asked and PHP < 7.1 and PHP 7.1.3 if fallback enabled. + list($microTime, $timeStamp) = explode(' ', microtime()); + $dateTime = new DateTime('now', $timezone); + $dateTime->setTimestamp($timeStamp); // Use the timestamp returned by microtime as now can happen in the next second + $time = $dateTime->format(static::DEFAULT_TO_STRING_FORMAT).substr($microTime, 1, 7); + } + // @codeCoverageIgnoreEnd + + // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127 + if (strpos((string) .1, '.') === false) { + $locale = setlocale(LC_NUMERIC, '0'); + setlocale(LC_NUMERIC, 'C'); + } + parent::__construct($time ?? '', $timezone); + if (isset($locale)) { + setlocale(LC_NUMERIC, $locale); + } + static::setLastErrors(parent::getLastErrors()); + } + + /** + * Create a Carbon instance from a DateTime one. + * + * @param \DateTime|\DateTimeInterface $date + * + * @return static + */ + public static function instance($date) + { + if ($date instanceof static) { + return clone $date; + } + + static::expectDateTime($date); + + return new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + } + + /** + * Create a carbon instance from a string. + * + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * + * @param string|null $time + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function parse($time = null, $tz = null) + { + return new static($time, $tz); + } + + /** + * Get a Carbon instance for the current date and time. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function now($tz = null) + { + return new static(null, $tz); + } + + /** + * Create a Carbon instance for today. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function today($tz = null) + { + return static::parse('today', $tz); + } + + /** + * Create a Carbon instance for tomorrow. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function tomorrow($tz = null) + { + return static::parse('tomorrow', $tz); + } + + /** + * Create a Carbon instance for yesterday. + * + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function yesterday($tz = null) + { + return static::parse('yesterday', $tz); + } + + /** + * Create a Carbon instance for the greatest supported date. + * + * @return static + */ + public static function maxValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(PHP_INT_MAX); // @codeCoverageIgnore + } + + // 64 bit + return static::create(9999, 12, 31, 23, 59, 59); + } + + /** + * Create a Carbon instance for the lowest supported date. + * + * @return static + */ + public static function minValue() + { + if (self::$PHPIntSize === 4) { + // 32 bit + return static::createFromTimestamp(~PHP_INT_MAX); // @codeCoverageIgnore + } + + // 64 bit + return static::create(1, 1, 1, 0, 0, 0); + } + + /** + * Create a new Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function create($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $now = static::hasTestNow() ? static::getTestNow() : static::now($tz); + + $defaults = array_combine(array( + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + ), explode('-', $now->format('Y-n-j-G-i-s'))); + + $year = $year === null ? $defaults['year'] : $year; + $month = $month === null ? $defaults['month'] : $month; + $day = $day === null ? $defaults['day'] : $day; + + if ($hour === null) { + $hour = $defaults['hour']; + $minute = $minute === null ? $defaults['minute'] : $minute; + $second = $second === null ? $defaults['second'] : $second; + } else { + $minute = $minute === null ? 0 : $minute; + $second = $second === null ? 0 : $second; + } + + $fixYear = null; + + if ($year < 0) { + $fixYear = $year; + $year = 0; + } elseif ($year > 9999) { + $fixYear = $year - 9999; + $year = 9999; + } + + $instance = static::createFromFormat('!Y-n-j G:i:s', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz); + + if ($fixYear !== null) { + $instance->addYears($fixYear); + } + + return $instance; + } + + /** + * Create a new safe Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values will + * be used. + * + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * If one of the set values is not valid, an \InvalidArgumentException + * will be thrown. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param \DateTimeZone|string|null $tz + * + * @throws \Carbon\Exceptions\InvalidDateException|\InvalidArgumentException + * + * @return static + */ + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $fields = array( + 'year' => array(0, 9999), + 'month' => array(0, 12), + 'day' => array(0, 31), + 'hour' => array(0, 24), + 'minute' => array(0, 59), + 'second' => array(0, 59), + ); + + foreach ($fields as $field => $range) { + if ($$field !== null && (!is_int($$field) || $$field < $range[0] || $$field > $range[1])) { + throw new InvalidDateException($field, $$field); + } + } + + $instance = static::create($year, $month, $day, $hour, $minute, $second, $tz); + + foreach (array_reverse($fields) as $field => $range) { + if ($$field !== null && (!is_int($$field) || $$field !== $instance->$field)) { + throw new InvalidDateException($field, $$field); + } + } + + return $instance; + } + + /** + * Create a Carbon instance from just a date. The time portion is set to now. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, null, null, null, $tz); + } + + /** + * Create a Carbon instance from just a date. The time portion is set to midnight. + * + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null) + { + return static::create($year, $month, $day, 0, 0, 0, $tz); + } + + /** + * Create a Carbon instance from just a time. The date portion is set to today. + * + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function createFromTime($hour = null, $minute = null, $second = null, $tz = null) + { + return static::create(null, null, null, $hour, $minute, $second, $tz); + } + + /** + * Create a Carbon instance from a time string. The date portion is set to today. + * + * @param string $time + * @param \DateTimeZone|string|null $tz + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function createFromTimeString($time, $tz = null) + { + return static::today($tz)->setTimeFromTimeString($time); + } + + private static function createFromFormatAndTimezone($format, $time, $tz) + { + return $tz !== null + ? parent::createFromFormat($format, $time, static::safeCreateDateTimeZone($tz)) + : parent::createFromFormat($format, $time); + } + + /** + * Create a Carbon instance from a specific format. + * + * @param string $format Datetime format + * @param string $time + * @param \DateTimeZone|string|null $tz + * + * @throws InvalidArgumentException + * + * @return static + * : DateTime|false + */ + #[\ReturnTypeWillChange] + public static function createFromFormat($format, $time, $tz = null) + { + // FreeScout fix for PostgreSQL timestamp fields. + if ($format == 'Y-m-d H:i:s' && strstr($time, '+')) { + $time = preg_replace("/\+.*/", '', $time); + } + + // First attempt to create an instance, so that error messages are based on the unmodified format. + $date = self::createFromFormatAndTimezone($format, $time, $tz); + $lastErrors = parent::getLastErrors(); + + if (($mock = static::getTestNow()) && ($date instanceof DateTime || $date instanceof DateTimeInterface)) { + // Set timezone from mock if custom timezone was neither given directly nor as a part of format. + // First let's skip the part that will be ignored by the parser. + $nonEscaped = '(?getTimezone(); + } + + // Prepend mock datetime only if the format does not contain non escaped unix epoch reset flag. + if (!preg_match("/{$nonEscaped}[!|]/", $format)) { + $format = static::MOCK_DATETIME_FORMAT.' '.$format; + $time = $mock->format(static::MOCK_DATETIME_FORMAT).' '.$time; + } + + // Regenerate date from the modified format to base result on the mocked instance instead of now. + $date = self::createFromFormatAndTimezone($format, $time, $tz); + } + + if ($date instanceof DateTime || $date instanceof DateTimeInterface) { + $instance = static::instance($date); + $instance::setLastErrors($lastErrors); + + return $instance; + } + + throw new InvalidArgumentException(implode(PHP_EOL, $lastErrors['errors'])); + } + + /** + * Set last errors. + * + * @param array $lastErrors + * + * @return void + */ + private static function setLastErrors($lastErrors) + { + if (!is_array($lastErrors)) { + return; + } + static::$lastErrors = $lastErrors; + } + + /** + * {@inheritdoc} + * : array|false + */ + #[\ReturnTypeWillChange] + public static function getLastErrors() + { + return static::$lastErrors; + } + + /** + * Create a Carbon instance from a timestamp. + * + * @param int $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestamp($timestamp, $tz = null) + { + return static::today($tz)->setTimestamp($timestamp); + } + + /** + * Create a Carbon instance from a timestamp in milliseconds. + * + * @param int $timestamp + * @param \DateTimeZone|string|null $tz + * + * @return static + */ + public static function createFromTimestampMs($timestamp, $tz = null) + { + return static::createFromFormat('U.u', sprintf('%F', $timestamp / 1000)) + ->setTimezone($tz); + } + + /** + * Create a Carbon instance from an UTC timestamp. + * + * @param int $timestamp + * + * @return static + */ + public static function createFromTimestampUTC($timestamp) + { + return new static('@'.$timestamp); + } + + /** + * Make a Carbon instance from given variable if possible. + * + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * + * @param mixed $var + * + * @return static|null + */ + public static function make($var) + { + if ($var instanceof DateTime || $var instanceof DateTimeInterface) { + return static::instance($var); + } + + if (is_string($var)) { + $var = trim($var); + $first = substr($var, 0, 1); + + if (is_string($var) && $first !== 'P' && $first !== 'R' && preg_match('/[a-z0-9]/i', $var)) { + return static::parse($var); + } + } + } + + /** + * Get a copy of the instance. + * + * @return static + */ + public function copy() + { + return clone $this; + } + + /** + * Returns a present instance in the same timezone. + * + * @return static + */ + public function nowWithSameTz() + { + return static::now($this->getTimezone()); + } + + /** + * Throws an exception if the given object is not a DateTime and does not implement DateTimeInterface + * and not in $other. + * + * @param mixed $date + * @param string|array $other + * + * @throws \InvalidArgumentException + */ + protected static function expectDateTime($date, $other = array()) + { + $message = 'Expected '; + foreach ((array) $other as $expect) { + $message .= "{$expect}, "; + } + + if (!$date instanceof DateTime && !$date instanceof DateTimeInterface) { + throw new InvalidArgumentException( + $message.'DateTime or DateTimeInterface, '. + (is_object($date) ? get_class($date) : gettype($date)).' given' + ); + } + } + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + protected function resolveCarbon($date = null) + { + if (!$date) { + return $this->nowWithSameTz(); + } + + if (is_string($date)) { + return static::parse($date, $this->getTimezone()); + } + + static::expectDateTime($date, array('null', 'string')); + + return $date instanceof self ? $date : static::instance($date); + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @throws \InvalidArgumentException + * + * @return string|int|bool|\DateTimeZone + */ + public function __get($name) + { + static $formats = array( + 'year' => 'Y', + 'yearIso' => 'o', + 'month' => 'n', + 'day' => 'j', + 'hour' => 'G', + 'minute' => 'i', + 'second' => 's', + 'micro' => 'u', + 'dayOfWeek' => 'w', + 'dayOfWeekIso' => 'N', + 'dayOfYear' => 'z', + 'weekOfYear' => 'W', + 'daysInMonth' => 't', + 'timestamp' => 'U', + 'englishDayOfWeek' => 'l', + 'shortEnglishDayOfWeek' => 'D', + 'englishMonth' => 'F', + 'shortEnglishMonth' => 'M', + 'localeDayOfWeek' => '%A', + 'shortLocaleDayOfWeek' => '%a', + 'localeMonth' => '%B', + 'shortLocaleMonth' => '%b', + ); + + switch (true) { + case isset($formats[$name]): + $format = $formats[$name]; + $method = substr($format, 0, 1) === '%' ? 'formatLocalized' : 'format'; + $value = $this->$method($format); + + return is_numeric($value) ? (int) $value : $value; + + case $name === 'weekOfMonth': + return (int) ceil($this->day / static::DAYS_PER_WEEK); + + case $name === 'weekNumberInMonth': + return (int) ceil(($this->day + $this->copy()->startOfMonth()->dayOfWeek - 1) / static::DAYS_PER_WEEK); + + case $name === 'age': + return $this->diffInYears(); + + case $name === 'quarter': + return (int) ceil($this->month / static::MONTHS_PER_QUARTER); + + case $name === 'offset': + return $this->getOffset(); + + case $name === 'offsetHours': + return $this->getOffset() / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR; + + case $name === 'dst': + return $this->format('I') === '1'; + + case $name === 'local': + return $this->getOffset() === $this->copy()->setTimezone(date_default_timezone_get())->getOffset(); + + case $name === 'utc': + return $this->getOffset() === 0; + + case $name === 'timezone' || $name === 'tz': + return $this->getTimezone(); + + case $name === 'timezoneName' || $name === 'tzName': + return $this->getTimezone()->getName(); + + default: + throw new InvalidArgumentException(sprintf("Unknown getter '%s'", $name)); + } + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return bool + */ + public function __isset($name) + { + try { + $this->__get($name); + } catch (InvalidArgumentException $e) { + return false; + } + + return true; + } + + /** + * Set a part of the Carbon object + * + * @param string $name + * @param string|int|\DateTimeZone $value + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function __set($name, $value) + { + switch ($name) { + case 'year': + case 'month': + case 'day': + case 'hour': + case 'minute': + case 'second': + list($year, $month, $day, $hour, $minute, $second) = explode('-', $this->format('Y-n-j-G-i-s')); + $$name = $value; + $this->setDateTime($year, $month, $day, $hour, $minute, $second); + break; + + case 'timestamp': + parent::setTimestamp($value); + break; + + case 'timezone': + case 'tz': + $this->setTimezone($value); + break; + + default: + throw new InvalidArgumentException(sprintf("Unknown setter '%s'", $name)); + } + } + + /** + * Set the instance's year + * + * @param int $value + * + * @return static + */ + public function year($value) + { + $this->year = $value; + + return $this; + } + + /** + * Set the instance's month + * + * @param int $value + * + * @return static + */ + public function month($value) + { + $this->month = $value; + + return $this; + } + + /** + * Set the instance's day + * + * @param int $value + * + * @return static + */ + public function day($value) + { + $this->day = $value; + + return $this; + } + + /** + * Set the instance's hour + * + * @param int $value + * + * @return static + */ + public function hour($value) + { + $this->hour = $value; + + return $this; + } + + /** + * Set the instance's minute + * + * @param int $value + * + * @return static + */ + public function minute($value) + { + $this->minute = $value; + + return $this; + } + + /** + * Set the instance's second + * + * @param int $value + * + * @return static + */ + public function second($value) + { + $this->second = $value; + + return $this; + } + + /** + * Sets the current date of the DateTime object to a different date. + * Calls modify as a workaround for a php bug + * + * @param int $year + * @param int $month + * @param int $day + * + * @return static + * + * @see https://github.com/briannesbitt/Carbon/issues/539 + * @see https://bugs.php.net/bug.php?id=63863 + */ + public function setDate($year, $month, $day): DateTime + { + $this->modify('+0 day'); + + return parent::setDate($year, $month, $day); + } + + /** + * Set the date and time all together + * + * @param int $year + * @param int $month + * @param int $day + * @param int $hour + * @param int $minute + * @param int $second + * + * @return static + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second = 0) + { + return $this->setDate($year, $month, $day)->setTime($hour, $minute, $second); + } + + /** + * Set the time by time string + * + * @param string $time + * + * @return static + */ + public function setTimeFromTimeString($time) + { + if (strpos($time, ':') === false) { + $time .= ':0'; + } + + return $this->modify($time); + } + + /** + * Set the instance's timestamp + * + * @param int $value + * + * @return static + */ + public function timestamp($value) + { + return $this->setTimestamp($value); + } + + /** + * Alias for setTimezone() + * + * @param \DateTimeZone|string $value + * + * @return static + */ + public function timezone($value) + { + return $this->setTimezone($value); + } + + /** + * Alias for setTimezone() + * + * @param \DateTimeZone|string $value + * + * @return static + */ + public function tz($value) + { + return $this->setTimezone($value); + } + + /** + * Set the instance's timezone from a string or object + * + * @param \DateTimeZone|string $value + * + * @return static + */ + public function setTimezone($value): \DateTime + { + parent::setTimezone(static::safeCreateDateTimeZone($value)); + // https://bugs.php.net/bug.php?id=72338 + // just workaround on this bug + $this->getTimestamp(); + + return $this; + } + + /** + * Set the year, month, and date for this instance to that of the passed instance. + * + * @param \Carbon\Carbon|\DateTimeInterface $date + * + * @return static + */ + public function setDateFrom($date) + { + $date = static::instance($date); + + $this->setDate($date->year, $date->month, $date->day); + + return $this; + } + + /** + * Set the hour, day, and time for this instance to that of the passed instance. + * + * @param \Carbon\Carbon|\DateTimeInterface $date + * + * @return static + */ + public function setTimeFrom($date) + { + $date = static::instance($date); + + $this->setTime($date->hour, $date->minute, $date->second); + + return $this; + } + + /** + * Get the days of the week + * + * @return array + */ + public static function getDays() + { + return static::$days; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// WEEK SPECIAL DAYS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get the first day of week + * + * @return int + */ + public static function getWeekStartsAt() + { + return static::$weekStartsAt; + } + + /** + * Set the first day of week + * + * @param int $day week start day + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function setWeekStartsAt($day) + { + if ($day > static::SATURDAY || $day < static::SUNDAY) { + throw new InvalidArgumentException('Day of a week should be greater than or equal to 0 and less than or equal to 6.'); + } + + static::$weekStartsAt = $day; + } + + /** + * Get the last day of week + * + * @return int + */ + public static function getWeekEndsAt() + { + return static::$weekEndsAt; + } + + /** + * Set the last day of week + * + * @param int $day + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function setWeekEndsAt($day) + { + if ($day > static::SATURDAY || $day < static::SUNDAY) { + throw new InvalidArgumentException('Day of a week should be greater than or equal to 0 and less than or equal to 6.'); + } + + static::$weekEndsAt = $day; + } + + /** + * Get weekend days + * + * @return array + */ + public static function getWeekendDays() + { + return static::$weekendDays; + } + + /** + * Set weekend days + * + * @param array $days + * + * @return void + */ + public static function setWeekendDays($days) + { + static::$weekendDays = $days; + } + + /** + * get midday/noon hour + * + * @return int + */ + public static function getMidDayAt() + { + return static::$midDayAt; + } + + /** + * Set midday/noon hour + * + * @param int $hour midday hour + * + * @return void + */ + public static function setMidDayAt($hour) + { + static::$midDayAt = $hour; + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// TESTING AIDS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * @param \Carbon\Carbon|null $testNow real or mock Carbon instance + * @param \Carbon\Carbon|string|null $testNow + */ + public static function setTestNow($testNow = null) + { + static::$testNow = is_string($testNow) ? static::parse($testNow) : $testNow; + } + + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return static the current instance used for testing + */ + public static function getTestNow() + { + return static::$testNow; + } + + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return bool true if there is a test instance, otherwise false + */ + public static function hasTestNow() + { + return static::getTestNow() !== null; + } + + /** + * Determine if a time string will produce a relative date. + * + * @param string $time + * + * @return bool true if time match a relative date, false if absolute or invalid time string + */ + public static function hasRelativeKeywords($time) + { + if (strtotime($time) === false) { + return false; + } + + $date1 = new DateTime('2000-01-01T00:00:00Z'); + $date1->modify($time); + $date2 = new DateTime('2001-12-25T00:00:00Z'); + $date2->modify($time); + + return $date1 != $date2; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// LOCALIZATION ////////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Initialize the translator instance if necessary. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + protected static function translator() + { + if (static::$translator === null) { + static::$translator = Translator::get(); + } + + return static::$translator; + } + + /** + * Get the translator instance in use + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public static function getTranslator() + { + return static::translator(); + } + + /** + * Set the translator instance to use + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator) + { + static::$translator = $translator; + } + + /** + * Get the current translator locale + * + * @return string + */ + public static function getLocale() + { + return static::translator()->getLocale(); + } + + /** + * Set the current translator locale and indicate if the source locale file exists + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function setLocale($locale) + { + return static::translator()->setLocale($locale) !== false; + } + + /** + * Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * + * @param string $locale locale ex. en + * + * @return mixed + */ + public static function executeWithLocale($locale, $func) + { + $currentLocale = static::getLocale(); + $result = call_user_func($func, static::setLocale($locale) ? static::getLocale() : false, static::translator()); + static::setLocale($currentLocale); + + return $result; + } + + /** + * Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasShortUnits($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + ( + ($y = $translator->trans('y')) !== 'y' && + $y !== $translator->trans('year') + ) || ( + ($y = $translator->trans('d')) !== 'd' && + $y !== $translator->trans('day') + ) || ( + ($y = $translator->trans('h')) !== 'h' && + $y !== $translator->trans('hour') + ); + }); + } + + /** + * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('ago') !== 'ago' && + $translator->trans('from_now') !== 'from_now' && + $translator->trans('before') !== 'before' && + $translator->trans('after') !== 'after'; + }); + } + + /** + * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffOneDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('diff_now') !== 'diff_now' && + $translator->trans('diff_yesterday') !== 'diff_yesterday' && + $translator->trans('diff_tomorrow') !== 'diff_tomorrow'; + }); + } + + /** + * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasDiffTwoDayWords($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('diff_before_yesterday') !== 'diff_before_yesterday' && + $translator->trans('diff_after_tomorrow') !== 'diff_after_tomorrow'; + }); + } + + /** + * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * + * @param string $locale locale ex. en + * + * @return bool + */ + public static function localeHasPeriodSyntax($locale) + { + return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { + return $newLocale && + $translator->trans('period_recurrences') !== 'period_recurrences' && + $translator->trans('period_interval') !== 'period_interval' && + $translator->trans('period_start_date') !== 'period_start_date' && + $translator->trans('period_end_date') !== 'period_end_date'; + }); + } + + /** + * Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * + * @return array + */ + public static function getAvailableLocales() + { + $translator = static::translator(); + $locales = array(); + if ($translator instanceof Translator) { + foreach (glob(__DIR__.'/Lang/*.php') as $file) { + $locales[] = substr($file, strrpos($file, '/') + 1, -4); + } + + $locales = array_unique(array_merge($locales, array_keys($translator->getMessages()))); + } + + return $locales; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// STRING FORMATTING ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Set if UTF8 will be used for localized date/time + * + * @param bool $utf8 + */ + public static function setUtf8($utf8) + { + static::$utf8 = $utf8; + } + + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() http://php.net/setlocale. + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format) + { + // Check for Windows to find and replace the %e modifier correctly. + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $format = preg_replace('#(?toDateTimeString())); + $formatter = new \IntlDateFormatter( + config('app.locale'), + \IntlDateFormatter::LONG, + \IntlDateFormatter::NONE, + config('app.timezone'), + \IntlDateFormatter::GREGORIAN, + $format + ); + $formatted = datefmt_format($formatter, strtotime($this->toDateTimeString())); + + return static::$utf8 ? utf8_encode($formatted) : $formatted; + } + + /** + * Reset the format used to the default when type juggling a Carbon instance to a string + * + * @return void + */ + public static function resetToStringFormat() + { + static::setToStringFormat(static::DEFAULT_TO_STRING_FORMAT); + } + + /** + * Set the default format used when type juggling a Carbon instance to a string + * + * @param string|Closure $format + * + * @return void + */ + public static function setToStringFormat($format) + { + static::$toStringFormat = $format; + } + + /** + * Format the instance as a string using the set format + * + * @return string + */ + public function __toString() + { + $format = static::$toStringFormat; + + return $this->format($format instanceof Closure ? $format($this) : $format); + } + + /** + * Format the instance as date + * + * @return string + */ + public function toDateString() + { + return $this->format('Y-m-d'); + } + + /** + * Format the instance as a readable date + * + * @return string + */ + public function toFormattedDateString() + { + return $this->format('M j, Y'); + } + + /** + * Format the instance as time + * + * @return string + */ + public function toTimeString() + { + return $this->format('H:i:s'); + } + + /** + * Format the instance as date and time + * + * @return string + */ + public function toDateTimeString() + { + return $this->format('Y-m-d H:i:s'); + } + + /** + * Format the instance with day, date and time + * + * @return string + */ + public function toDayDateTimeString() + { + return $this->format('D, M j, Y g:i A'); + } + + /** + * Format the instance as ATOM + * + * @return string + */ + public function toAtomString() + { + return $this->format(static::ATOM); + } + + /** + * Format the instance as COOKIE + * + * @return string + */ + public function toCookieString() + { + return $this->format(static::COOKIE); + } + + /** + * Format the instance as ISO8601 + * + * @return string + */ + public function toIso8601String() + { + return $this->toAtomString(); + } + + /** + * Format the instance as RFC822 + * + * @return string + */ + public function toRfc822String() + { + return $this->format(static::RFC822); + } + + /** + * Convert the instance to UTC and return as Zulu ISO8601 + * + * @return string + */ + public function toIso8601ZuluString() + { + return $this->copy()->setTimezone('UTC')->format('Y-m-d\TH:i:s\Z'); + } + + /** + * Format the instance as RFC850 + * + * @return string + */ + public function toRfc850String() + { + return $this->format(static::RFC850); + } + + /** + * Format the instance as RFC1036 + * + * @return string + */ + public function toRfc1036String() + { + return $this->format(static::RFC1036); + } + + /** + * Format the instance as RFC1123 + * + * @return string + */ + public function toRfc1123String() + { + return $this->format(static::RFC1123); + } + + /** + * Format the instance as RFC2822 + * + * @return string + */ + public function toRfc2822String() + { + return $this->format(static::RFC2822); + } + + /** + * Format the instance as RFC3339 + * + * @return string + */ + public function toRfc3339String() + { + return $this->format(static::RFC3339); + } + + /** + * Format the instance as RSS + * + * @return string + */ + public function toRssString() + { + return $this->format(static::RSS); + } + + /** + * Format the instance as W3C + * + * @return string + */ + public function toW3cString() + { + return $this->format(static::W3C); + } + + /** + * Format the instance as RFC7231 + * + * @return string + */ + public function toRfc7231String() + { + return $this->copy() + ->setTimezone('GMT') + ->format(static::RFC7231_FORMAT); + } + + /** + * Get default array representation + * + * @return array + */ + public function toArray() + { + return array( + 'year' => $this->year, + 'month' => $this->month, + 'day' => $this->day, + 'dayOfWeek' => $this->dayOfWeek, + 'dayOfYear' => $this->dayOfYear, + 'hour' => $this->hour, + 'minute' => $this->minute, + 'second' => $this->second, + 'micro' => $this->micro, + 'timestamp' => $this->timestamp, + 'formatted' => $this->format(self::DEFAULT_TO_STRING_FORMAT), + 'timezone' => $this->timezone, + ); + } + + /////////////////////////////////////////////////////////////////// + ////////////////////////// COMPARISONS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Determines if the instance is equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function eq($date) + { + return $this == $date; + } + + /** + * Determines if the instance is equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see eq() + * + * @return bool + */ + public function equalTo($date) + { + return $this->eq($date); + } + + /** + * Determines if the instance is not equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function ne($date) + { + return !$this->eq($date); + } + + /** + * Determines if the instance is not equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see ne() + * + * @return bool + */ + public function notEqualTo($date) + { + return $this->ne($date); + } + + /** + * Determines if the instance is greater (after) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function gt($date) + { + return $this > $date; + } + + /** + * Determines if the instance is greater (after) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see gt() + * + * @return bool + */ + public function greaterThan($date) + { + return $this->gt($date); + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function gte($date) + { + return $this >= $date; + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see gte() + * + * @return bool + */ + public function greaterThanOrEqualTo($date) + { + return $this->gte($date); + } + + /** + * Determines if the instance is less (before) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lt($date) + { + return $this < $date; + } + + /** + * Determines if the instance is less (before) than another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lt() + * + * @return bool + */ + public function lessThan($date) + { + return $this->lt($date); + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @return bool + */ + public function lte($date) + { + return $this <= $date; + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see lte() + * + * @return bool + */ + public function lessThanOrEqualTo($date) + { + return $this->lte($date); + } + + /** + * Determines if the instance is between two others + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * @param bool $equal Indicates if an equal to comparison should be done + * + * @return bool + */ + public function between($date1, $date2, $equal = true) + { + if ($date1->gt($date2)) { + $temp = $date1; + $date1 = $date2; + $date2 = $temp; + } + + if ($equal) { + return $this->gte($date1) && $this->lte($date2); + } + + return $this->gt($date1) && $this->lt($date2); + } + + /** + * Get the closest date from the instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function closest($date1, $date2) + { + return $this->diffInSeconds($date1) < $this->diffInSeconds($date2) ? $date1 : $date2; + } + + /** + * Get the farthest date from the instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 + * + * @return static + */ + public function farthest($date1, $date2) + { + return $this->diffInSeconds($date1) > $this->diffInSeconds($date2) ? $date1 : $date2; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + public function min($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->lt($date) ? $this : $date; + } + + /** + * Get the minimum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see min() + * + * @return static + */ + public function minimum($date = null) + { + return $this->min($date); + } + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + public function max($date = null) + { + $date = $this->resolveCarbon($date); + + return $this->gt($date) ? $this : $date; + } + + /** + * Get the maximum instance between a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $date + * + * @see max() + * + * @return static + */ + public function maximum($date = null) + { + return $this->max($date); + } + + /** + * Determines if the instance is a weekday. + * + * @return bool + */ + public function isWeekday() + { + return !$this->isWeekend(); + } + + /** + * Determines if the instance is a weekend day. + * + * @return bool + */ + public function isWeekend() + { + return in_array($this->dayOfWeek, static::$weekendDays); + } + + /** + * Determines if the instance is yesterday. + * + * @return bool + */ + public function isYesterday() + { + return $this->toDateString() === static::yesterday($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is today. + * + * @return bool + */ + public function isToday() + { + return $this->toDateString() === $this->nowWithSameTz()->toDateString(); + } + + /** + * Determines if the instance is tomorrow. + * + * @return bool + */ + public function isTomorrow() + { + return $this->toDateString() === static::tomorrow($this->getTimezone())->toDateString(); + } + + /** + * Determines if the instance is within the next week. + * + * @return bool + */ + public function isNextWeek() + { + return $this->weekOfYear === $this->nowWithSameTz()->addWeek()->weekOfYear; + } + + /** + * Determines if the instance is within the last week. + * + * @return bool + */ + public function isLastWeek() + { + return $this->weekOfYear === $this->nowWithSameTz()->subWeek()->weekOfYear; + } + + /** + * Determines if the instance is within the next quarter. + * + * @return bool + */ + public function isNextQuarter() + { + return $this->quarter === $this->nowWithSameTz()->addQuarter()->quarter; + } + + /** + * Determines if the instance is within the last quarter. + * + * @return bool + */ + public function isLastQuarter() + { + return $this->quarter === $this->nowWithSameTz()->subQuarter()->quarter; + } + + /** + * Determines if the instance is within the next month. + * + * @return bool + */ + public function isNextMonth() + { + return $this->month === $this->nowWithSameTz()->addMonthNoOverflow()->month; + } + + /** + * Determines if the instance is within the last month. + * + * @return bool + */ + public function isLastMonth() + { + return $this->month === $this->nowWithSameTz()->subMonthNoOverflow()->month; + } + + /** + * Determines if the instance is within next year. + * + * @return bool + */ + public function isNextYear() + { + return $this->year === $this->nowWithSameTz()->addYear()->year; + } + + /** + * Determines if the instance is within the previous year. + * + * @return bool + */ + public function isLastYear() + { + return $this->year === $this->nowWithSameTz()->subYear()->year; + } + + /** + * Determines if the instance is in the future, ie. greater (after) than now. + * + * @return bool + */ + public function isFuture() + { + return $this->gt($this->nowWithSameTz()); + } + + /** + * Determines if the instance is in the past, ie. less (before) than now. + * + * @return bool + */ + public function isPast() + { + return $this->lt($this->nowWithSameTz()); + } + + /** + * Determines if the instance is a leap year. + * + * @return bool + */ + public function isLeapYear() + { + return $this->format('L') === '1'; + } + + /** + * Determines if the instance is a long year + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates + * + * @return bool + */ + public function isLongYear() + { + return static::create($this->year, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; + } + + /** + * Compares the formatted values of the two dates. + * + * @param string $format The date formats to compare. + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @throws \InvalidArgumentException + * + * @return bool + */ + public function isSameAs($format, $date = null) + { + $date = $date ?: static::now($this->tz); + + static::expectDateTime($date, 'null'); + + return $this->format($format) === $date->format($format); + } + + /** + * Determines if the instance is in the current year. + * + * @return bool + */ + public function isCurrentYear() + { + return $this->isSameYear(); + } + + /** + * Checks if the passed in date is in the same year as the instance year. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isSameYear($date = null) + { + return $this->isSameAs('Y', $date); + } + + /** + * Determines if the instance is in the current month. + * + * @return bool + */ + public function isCurrentQuarter() + { + return $this->isSameQuarter(); + } + + /** + * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameQuarter($date = null, $ofSameYear = null) + { + $date = $date ? static::instance($date) : static::now($this->tz); + + static::expectDateTime($date, 'null'); + + $ofSameYear = is_null($ofSameYear) ? static::shouldCompareYearWithMonth() : $ofSameYear; + + return $this->quarter === $date->quarter && (!$ofSameYear || $this->isSameYear($date)); + } + + /** + * Determines if the instance is in the current month. + * + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isCurrentMonth($ofSameYear = null) + { + return $this->isSameMonth($ofSameYear); + } + + /** + * Checks if the passed in date is in the same month as the instance´s month. + * + * Note that this defaults to only comparing the month while ignoring the year. + * To test if it is the same exact month of the same year, pass in true as the second parameter. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. + * + * @return bool + */ + public function isSameMonth($date = null, $ofSameYear = null) + { + $ofSameYear = is_null($ofSameYear) ? static::shouldCompareYearWithMonth() : $ofSameYear; + + return $this->isSameAs($ofSameYear ? 'Y-m' : 'm', $date); + } + + /** + * Determines if the instance is in the current day. + * + * @return bool + */ + public function isCurrentDay() + { + return $this->isSameDay(); + } + + /** + * Checks if the passed in date is the same exact day as the instance´s day. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameDay($date = null) + { + return $this->isSameAs('Y-m-d', $date); + } + + /** + * Determines if the instance is in the current hour. + * + * @return bool + */ + public function isCurrentHour() + { + return $this->isSameHour(); + } + + /** + * Checks if the passed in date is the same exact hour as the instance´s hour. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameHour($date = null) + { + return $this->isSameAs('Y-m-d H', $date); + } + + /** + * Determines if the instance is in the current minute. + * + * @return bool + */ + public function isCurrentMinute() + { + return $this->isSameMinute(); + } + + /** + * Checks if the passed in date is the same exact minute as the instance´s minute. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameMinute($date = null) + { + return $this->isSameAs('Y-m-d H:i', $date); + } + + /** + * Determines if the instance is in the current second. + * + * @return bool + */ + public function isCurrentSecond() + { + return $this->isSameSecond(); + } + + /** + * Checks if the passed in date is the same exact second as the instance´s second. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. + * + * @return bool + */ + public function isSameSecond($date = null) + { + return $this->isSameAs('Y-m-d H:i:s', $date); + } + + /** + * Checks if this day is a specific day of the week. + * + * @param int $dayOfWeek + * + * @return bool + */ + public function isDayOfWeek($dayOfWeek) + { + return $this->dayOfWeek === $dayOfWeek; + } + + /** + * Checks if this day is a Sunday. + * + * @return bool + */ + public function isSunday() + { + return $this->dayOfWeek === static::SUNDAY; + } + + /** + * Checks if this day is a Monday. + * + * @return bool + */ + public function isMonday() + { + return $this->dayOfWeek === static::MONDAY; + } + + /** + * Checks if this day is a Tuesday. + * + * @return bool + */ + public function isTuesday() + { + return $this->dayOfWeek === static::TUESDAY; + } + + /** + * Checks if this day is a Wednesday. + * + * @return bool + */ + public function isWednesday() + { + return $this->dayOfWeek === static::WEDNESDAY; + } + + /** + * Checks if this day is a Thursday. + * + * @return bool + */ + public function isThursday() + { + return $this->dayOfWeek === static::THURSDAY; + } + + /** + * Checks if this day is a Friday. + * + * @return bool + */ + public function isFriday() + { + return $this->dayOfWeek === static::FRIDAY; + } + + /** + * Checks if this day is a Saturday. + * + * @return bool + */ + public function isSaturday() + { + return $this->dayOfWeek === static::SATURDAY; + } + + /** + * Check if its the birthday. Compares the date/month values of the two dates. + * + * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * + * @return bool + */ + public function isBirthday($date = null) + { + return $this->isSameAs('md', $date); + } + + /** + * Check if today is the last day of the Month + * + * @return bool + */ + public function isLastOfMonth() + { + return $this->day === $this->daysInMonth; + } + + /** + * Check if the instance is start of day / midnight. + * + * @param bool $checkMicroseconds check time at microseconds precision + * /!\ Warning, this is not reliable with PHP < 7.1.4 + * + * @return bool + */ + public function isStartOfDay($checkMicroseconds = false) + { + return $checkMicroseconds + ? $this->format('H:i:s.u') === '00:00:00.000000' + : $this->format('H:i:s') === '00:00:00'; + } + + /** + * Check if the instance is end of day. + * + * @param bool $checkMicroseconds check time at microseconds precision + * /!\ Warning, this is not reliable with PHP < 7.1.4 + * + * @return bool + */ + public function isEndOfDay($checkMicroseconds = false) + { + return $checkMicroseconds + ? $this->format('H:i:s.u') === '23:59:59.999999' + : $this->format('H:i:s') === '23:59:59'; + } + + /** + * Check if the instance is start of day / midnight. + * + * @return bool + */ + public function isMidnight() + { + return $this->isStartOfDay(); + } + + /** + * Check if the instance is midday. + * + * @return bool + */ + public function isMidday() + { + return $this->format('G:i:s') === static::$midDayAt.':00:00'; + } + + /** + * Checks if the (date)time string is in a given format. + * + * @param string $date + * @param string $format + * + * @return bool + */ + public static function hasFormat($date, $format) + { + try { + // Try to create a DateTime object. Throws an InvalidArgumentException if the provided time string + // doesn't match the format in any way. + static::createFromFormat($format, $date); + + // createFromFormat() is known to handle edge cases silently. + // E.g. "1975-5-1" (Y-n-j) will still be parsed correctly when "Y-m-d" is supplied as the format. + // To ensure we're really testing against our desired format, perform an additional regex validation. + $regex = strtr( + preg_quote($format, '/'), + static::$regexFormats + ); + + return (bool) preg_match('/^'.$regex.'$/', $date); + } catch (InvalidArgumentException $e) { + } + + return false; + } + + /////////////////////////////////////////////////////////////////// + /////////////////// ADDITIONS AND SUBTRACTIONS //////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Add centuries to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addCenturies($value) + { + return $this->addYears(static::YEARS_PER_CENTURY * $value); + } + + /** + * Add a century to the instance + * + * @param int $value + * + * @return static + */ + public function addCentury($value = 1) + { + return $this->addCenturies($value); + } + + /** + * Remove centuries from the instance + * + * @param int $value + * + * @return static + */ + public function subCenturies($value) + { + return $this->addCenturies(-1 * $value); + } + + /** + * Remove a century from the instance + * + * @param int $value + * + * @return static + */ + public function subCentury($value = 1) + { + return $this->subCenturies($value); + } + + /** + * Add years to the instance. Positive $value travel forward while + * negative $value travel into the past. + * + * @param int $value + * + * @return static + */ + public function addYears($value) + { + if ($this->shouldOverflowYears()) { + return $this->addYearsWithOverflow($value); + } + + return $this->addYearsNoOverflow($value); + } + + /** + * Add a year to the instance + * + * @param int $value + * + * @return static + */ + public function addYear($value = 1) + { + return $this->addYears($value); + } + + /** + * Add years to the instance with no overflow of months + * Positive $value travel forward while + * negative $value travel into the past. + * + * @param int $value + * + * @return static + */ + public function addYearsNoOverflow($value) + { + return $this->addMonthsNoOverflow($value * static::MONTHS_PER_YEAR); + } + + /** + * Add year with overflow months set to false + * + * @param int $value + * + * @return static + */ + public function addYearNoOverflow($value = 1) + { + return $this->addYearsNoOverflow($value); + } + + /** + * Add years to the instance. + * Positive $value travel forward while + * negative $value travel into the past. + * + * @param int $value + * + * @return static + */ + public function addYearsWithOverflow($value) + { + return $this->modify((int) $value.' year'); + } + + /** + * Add year with overflow. + * + * @param int $value + * + * @return static + */ + public function addYearWithOverflow($value = 1) + { + return $this->addYearsWithOverflow($value); + } + + /** + * Remove years from the instance. + * + * @param int $value + * + * @return static + */ + public function subYears($value) + { + return $this->addYears(-1 * $value); + } + + /** + * Remove a year from the instance + * + * @param int $value + * + * @return static + */ + public function subYear($value = 1) + { + return $this->subYears($value); + } + + /** + * Remove years from the instance with no month overflow. + * + * @param int $value + * + * @return static + */ + public function subYearsNoOverflow($value) + { + return $this->subMonthsNoOverflow($value * static::MONTHS_PER_YEAR); + } + + /** + * Remove year from the instance with no month overflow + * + * @param int $value + * + * @return static + */ + public function subYearNoOverflow($value = 1) + { + return $this->subYearsNoOverflow($value); + } + + /** + * Remove years from the instance. + * + * @param int $value + * + * @return static + */ + public function subYearsWithOverflow($value) + { + return $this->subMonthsWithOverflow($value * static::MONTHS_PER_YEAR); + } + + /** + * Remove year from the instance. + * + * @param int $value + * + * @return static + */ + public function subYearWithOverflow($value = 1) + { + return $this->subYearsWithOverflow($value); + } + + /** + * Add quarters to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addQuarters($value) + { + return $this->addMonths(static::MONTHS_PER_QUARTER * $value); + } + + /** + * Add a quarter to the instance + * + * @param int $value + * + * @return static + */ + public function addQuarter($value = 1) + { + return $this->addQuarters($value); + } + + /** + * Remove quarters from the instance + * + * @param int $value + * + * @return static + */ + public function subQuarters($value) + { + return $this->addQuarters(-1 * $value); + } + + /** + * Remove a quarter from the instance + * + * @param int $value + * + * @return static + */ + public function subQuarter($value = 1) + { + return $this->subQuarters($value); + } + + /** + * Add months to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMonths($value) + { + if (static::shouldOverflowMonths()) { + return $this->addMonthsWithOverflow($value); + } + + return $this->addMonthsNoOverflow($value); + } + + /** + * Add a month to the instance + * + * @param int $value + * + * @return static + */ + public function addMonth($value = 1) + { + return $this->addMonths($value); + } + + /** + * Remove months from the instance + * + * @param int $value + * + * @return static + */ + public function subMonths($value) + { + return $this->addMonths(-1 * $value); + } + + /** + * Remove a month from the instance + * + * @param int $value + * + * @return static + */ + public function subMonth($value = 1) + { + return $this->subMonths($value); + } + + /** + * Add months to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMonthsWithOverflow($value) + { + return $this->modify((int) $value.' month'); + } + + /** + * Add a month to the instance + * + * @param int $value + * + * @return static + */ + public function addMonthWithOverflow($value = 1) + { + return $this->addMonthsWithOverflow($value); + } + + /** + * Remove months from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthsWithOverflow($value) + { + return $this->addMonthsWithOverflow(-1 * $value); + } + + /** + * Remove a month from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthWithOverflow($value = 1) + { + return $this->subMonthsWithOverflow($value); + } + + /** + * Add months without overflowing to the instance. Positive $value + * travels forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMonthsNoOverflow($value) + { + $day = $this->day; + + $this->modify((int) $value.' month'); + + if ($day !== $this->day) { + $this->modify('last day of previous month'); + } + + return $this; + } + + /** + * Add a month with no overflow to the instance + * + * @param int $value + * + * @return static + */ + public function addMonthNoOverflow($value = 1) + { + return $this->addMonthsNoOverflow($value); + } + + /** + * Remove months with no overflow from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthsNoOverflow($value) + { + return $this->addMonthsNoOverflow(-1 * $value); + } + + /** + * Remove a month with no overflow from the instance + * + * @param int $value + * + * @return static + */ + public function subMonthNoOverflow($value = 1) + { + return $this->subMonthsNoOverflow($value); + } + + /** + * Add days to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addDays($value) + { + return $this->modify((int) $value.' day'); + } + + /** + * Add a day to the instance + * + * @param int $value + * + * @return static + */ + public function addDay($value = 1) + { + return $this->addDays($value); + } + + /** + * Remove days from the instance + * + * @param int $value + * + * @return static + */ + public function subDays($value) + { + return $this->addDays(-1 * $value); + } + + /** + * Remove a day from the instance + * + * @param int $value + * + * @return static + */ + public function subDay($value = 1) + { + return $this->subDays($value); + } + + /** + * Add weekdays to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addWeekdays($value) + { + // Fix for weekday bug https://bugs.php.net/bug.php?id=54909 + $t = $this->toTimeString(); + $this->modify((int) $value.' weekday'); + + return $this->setTimeFromTimeString($t); + } + + /** + * Add a weekday to the instance + * + * @param int $value + * + * @return static + */ + public function addWeekday($value = 1) + { + return $this->addWeekdays($value); + } + + /** + * Remove weekdays from the instance + * + * @param int $value + * + * @return static + */ + public function subWeekdays($value) + { + return $this->addWeekdays(-1 * $value); + } + + /** + * Remove a weekday from the instance + * + * @param int $value + * + * @return static + */ + public function subWeekday($value = 1) + { + return $this->subWeekdays($value); + } + + /** + * Add weeks to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addWeeks($value) + { + return $this->modify((int) $value.' week'); + } + + /** + * Add a week to the instance + * + * @param int $value + * + * @return static + */ + public function addWeek($value = 1) + { + return $this->addWeeks($value); + } + + /** + * Remove weeks to the instance + * + * @param int $value + * + * @return static + */ + public function subWeeks($value) + { + return $this->addWeeks(-1 * $value); + } + + /** + * Remove a week from the instance + * + * @param int $value + * + * @return static + */ + public function subWeek($value = 1) + { + return $this->subWeeks($value); + } + + /** + * Add hours to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addHours($value) + { + return $this->modify((int) $value.' hour'); + } + + /** + * Add hours to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addRealHours($value) + { + return $this->addRealMinutes($value * static::MINUTES_PER_HOUR); + } + + /** + * Add an hour to the instance. + * + * @param int $value + * + * @return static + */ + public function addHour($value = 1) + { + return $this->addHours($value); + } + + /** + * Add an hour to the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function addRealHour($value = 1) + { + return $this->addRealHours($value); + } + + /** + * Remove hours from the instance. + * + * @param int $value + * + * @return static + */ + public function subHours($value) + { + return $this->addHours(-1 * $value); + } + + /** + * Remove hours from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealHours($value) + { + return $this->addRealHours(-1 * $value); + } + + /** + * Remove an hour from the instance. + * + * @param int $value + * + * @return static + */ + public function subHour($value = 1) + { + return $this->subHours($value); + } + + /** + * Remove an hour from the instance. + * + * @param int $value + * + * @return static + */ + public function subRealHour($value = 1) + { + return $this->subRealHours($value); + } + + /** + * Add minutes to the instance using timestamp. Positive $value + * travels forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addMinutes($value) + { + return $this->modify((int) $value.' minute'); + } + + /** + * Add minutes to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addRealMinutes($value) + { + return $this->addRealSeconds($value * static::SECONDS_PER_MINUTE); + } + + /** + * Add a minute to the instance. + * + * @param int $value + * + * @return static + */ + public function addMinute($value = 1) + { + return $this->addMinutes($value); + } + + /** + * Add a minute to the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function addRealMinute($value = 1) + { + return $this->addRealMinutes($value); + } + + /** + * Remove a minute from the instance. + * + * @param int $value + * + * @return static + */ + public function subMinute($value = 1) + { + return $this->subMinutes($value); + } + + /** + * Remove a minute from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealMinute($value = 1) + { + return $this->addRealMinutes(-1 * $value); + } + + /** + * Remove minutes from the instance. + * + * @param int $value + * + * @return static + */ + public function subMinutes($value) + { + return $this->addMinutes(-1 * $value); + } + + /** + * Remove a minute from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealMinutes($value = 1) + { + return $this->subRealMinute($value); + } + + /** + * Add seconds to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addSeconds($value) + { + return $this->modify((int) $value.' second'); + } + + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param int $value + * + * @return static + */ + public function addRealSeconds($value) + { + return $this->setTimestamp($this->getTimestamp() + $value); + } + + /** + * Add a second to the instance. + * + * @param int $value + * + * @return static + */ + public function addSecond($value = 1) + { + return $this->addSeconds($value); + } + + /** + * Add a second to the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function addRealSecond($value = 1) + { + return $this->addRealSeconds($value); + } + + /** + * Remove seconds from the instance. + * + * @param int $value + * + * @return static + */ + public function subSeconds($value) + { + return $this->addSeconds(-1 * $value); + } + + /** + * Remove seconds from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealSeconds($value) + { + return $this->addRealSeconds(-1 * $value); + } + + /** + * Remove a second from the instance + * + * @param int $value + * + * @return static + */ + public function subSecond($value = 1) + { + return $this->subSeconds($value); + } + + /** + * Remove a second from the instance using timestamp. + * + * @param int $value + * + * @return static + */ + public function subRealSecond($value = 1) + { + return $this->subRealSeconds($value); + } + + /////////////////////////////////////////////////////////////////// + /////////////////////////// DIFFERENCES /////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * @param DateInterval $diff + * @param bool $absolute + * @param bool $trimMicroseconds + * + * @return CarbonInterval + */ + protected static function fixDiffInterval(DateInterval $diff, $absolute, $trimMicroseconds) + { + $diff = CarbonInterval::instance($diff, $trimMicroseconds); + + // @codeCoverageIgnoreStart + if (version_compare(PHP_VERSION, '7.1.0-dev', '<')) { + return $diff; + } + + // Work-around for https://bugs.php.net/bug.php?id=77145 + if ($diff->f > 0 && $diff->y === -1 && $diff->m === 11 && $diff->d >= 29 && $diff->h === 23 && $diff->i === 59 && $diff->s === 59) { + $diff->y = 0; + $diff->m = 0; + $diff->d = 0; + $diff->h = 0; + $diff->i = 0; + $diff->s = 0; + $diff->f = (1000000 - round($diff->f * 1000000)) / 1000000; + $diff->invert(); + } elseif ($diff->f < 0) { + if ($diff->s !== 0 || $diff->i !== 0 || $diff->h !== 0 || $diff->d !== 0 || $diff->m !== 0 || $diff->y !== 0) { + $diff->f = (round($diff->f * 1000000) + 1000000) / 1000000; + $diff->s--; + if ($diff->s < 0) { + $diff->s += 60; + $diff->i--; + if ($diff->i < 0) { + $diff->i += 60; + $diff->h--; + if ($diff->h < 0) { + $diff->i += 24; + $diff->d--; + if ($diff->d < 0) { + $diff->d += 30; + $diff->m--; + if ($diff->m < 0) { + $diff->m += 12; + $diff->y--; + } + } + } + } + } + } else { + $diff->f *= -1; + $diff->invert(); + } + } + // @codeCoverageIgnoreEnd + if ($absolute && $diff->invert) { + $diff->invert(); + } + + return $diff; + } + + /** + * Get the difference as a CarbonInterval instance. + * + * Pass false as second argument to get a microseconds-precise interval. Else + * microseconds in the original interval will not be kept. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * @param bool $trimMicroseconds (true by default) + * + * @return CarbonInterval + */ + public function diffAsCarbonInterval($date = null, $absolute = true, $trimMicroseconds = true) + { + $from = $this; + $to = $this->resolveCarbon($date); + + if ($trimMicroseconds) { + $from = $from->copy()->startOfSecond(); + $to = $to->copy()->startOfSecond(); + } + + return static::fixDiffInterval($from->diff($to, $absolute), $absolute, $trimMicroseconds); + } + + /** + * Get the difference in years + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInYears($date = null, $absolute = true) + { + return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%y'); + } + + /** + * Get the difference in months + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMonths($date = null, $absolute = true) + { + $date = $this->resolveCarbon($date); + + return $this->diffInYears($date, $absolute) * static::MONTHS_PER_YEAR + (int) $this->diff($date, $absolute)->format('%r%m'); + } + + /** + * Get the difference in weeks + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeeks($date = null, $absolute = true) + { + return (int) ($this->diffInDays($date, $absolute) / static::DAYS_PER_WEEK); + } + + /** + * Get the difference in days + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDays($date = null, $absolute = true) + { + return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%a'); + } + + /** + * Get the difference in days using a filter closure + * + * @param Closure $callback + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true) + { + return $this->diffFiltered(CarbonInterval::day(), $callback, $date, $absolute); + } + + /** + * Get the difference in hours using a filter closure + * + * @param Closure $callback + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true) + { + return $this->diffFiltered(CarbonInterval::hour(), $callback, $date, $absolute); + } + + /** + * Get the difference by the given interval using a filter closure + * + * @param CarbonInterval $ci An interval to traverse by + * @param Closure $callback + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true) + { + $start = $this; + $end = $this->resolveCarbon($date); + $inverse = false; + + if ($end < $start) { + $start = $end; + $end = $this; + $inverse = true; + } + + $period = new DatePeriod($start, $ci, $end); + $values = array_filter(iterator_to_array($period), function ($date) use ($callback) { + return call_user_func($callback, Carbon::instance($date)); + }); + + $diff = count($values); + + return $inverse && !$absolute ? -$diff : $diff; + } + + /** + * Get the difference in weekdays + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekdays($date = null, $absolute = true) + { + return $this->diffInDaysFiltered(function (Carbon $date) { + return $date->isWeekday(); + }, $date, $absolute); + } + + /** + * Get the difference in weekend days using a filter + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInWeekendDays($date = null, $absolute = true) + { + return $this->diffInDaysFiltered(function (Carbon $date) { + return $date->isWeekend(); + }, $date, $absolute); + } + + /** + * Get the difference in hours. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInHours($date = null, $absolute = true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + + /** + * Get the difference in hours using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealHours($date = null, $absolute = true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + } + + /** + * Get the difference in minutes. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInMinutes($date = null, $absolute = true) + { + return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in minutes using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealMinutes($date = null, $absolute = true) + { + return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in seconds. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInSeconds($date = null, $absolute = true) + { + $diff = $this->diff($this->resolveCarbon($date)); + if (!$diff->days && version_compare(PHP_VERSION, '5.4.0-dev', '>=')) { + $diff = static::fixDiffInterval($diff, $absolute, false); + } + $value = $diff->days * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE + + $diff->h * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE + + $diff->i * static::SECONDS_PER_MINUTE + + $diff->s; + + return $absolute || !$diff->invert ? $value : -$value; + } + + /** + * Get the difference in seconds using timestamps. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return int + */ + public function diffInRealSeconds($date = null, $absolute = true) + { + $date = $this->resolveCarbon($date); + $value = $date->getTimestamp() - $this->getTimestamp(); + + return $absolute ? abs($value) : $value; + } + + /** + * The number of seconds since midnight. + * + * @return int + */ + public function secondsSinceMidnight() + { + return $this->diffInSeconds($this->copy()->startOfDay()); + } + + /** + * The number of seconds until 23:59:59. + * + * @return int + */ + public function secondsUntilEndOfDay() + { + return $this->diffInSeconds($this->copy()->endOfDay()); + } + + /** + * Get the difference in a human readable format in the current locale. + * + * When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the past to another value: + * 1 hour before + * 5 months before + * + * When comparing a value in the future to another value: + * 1 hour after + * 5 months after + * + * @param Carbon|null $other + * @param bool $absolute removes time difference modifiers ago, after, etc + * @param bool $short displays short format of time units + * @param int $parts displays number of parts in the interval + * + * @return string + */ + public function diffForHumans($other = null, $absolute = false, $short = false, $parts = 1) + { + $isNow = $other === null; + $interval = array(); + + $parts = min(6, max(1, (int) $parts)); + $count = 1; + $unit = $short ? 's' : 'second'; + + if ($isNow) { + $other = $this->nowWithSameTz(); + } elseif (!$other instanceof DateTime && !$other instanceof DateTimeInterface) { + $other = static::parse($other); + } + + $diffInterval = $this->diff($other); + + $diffIntervalArray = array( + array('value' => $diffInterval->y, 'unit' => 'year', 'unitShort' => 'y'), + array('value' => $diffInterval->m, 'unit' => 'month', 'unitShort' => 'm'), + array('value' => $diffInterval->d, 'unit' => 'day', 'unitShort' => 'd'), + array('value' => $diffInterval->h, 'unit' => 'hour', 'unitShort' => 'h'), + array('value' => $diffInterval->i, 'unit' => 'minute', 'unitShort' => 'min'), + array('value' => $diffInterval->s, 'unit' => 'second', 'unitShort' => 's'), + ); + + foreach ($diffIntervalArray as $diffIntervalData) { + if ($diffIntervalData['value'] > 0) { + $unit = $short ? $diffIntervalData['unitShort'] : $diffIntervalData['unit']; + $count = $diffIntervalData['value']; + + if ($diffIntervalData['unit'] === 'day' && $count >= static::DAYS_PER_WEEK) { + $unit = $short ? 'w' : 'week'; + $count = (int) ($count / static::DAYS_PER_WEEK); + + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + + // get the count days excluding weeks (might be zero) + $numOfDaysCount = (int) ($diffIntervalData['value'] - ($count * static::DAYS_PER_WEEK)); + + if ($numOfDaysCount > 0 && count($interval) < $parts) { + $unit = $short ? 'd' : 'day'; + $count = $numOfDaysCount; + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + } + } else { + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + } + } + + // break the loop after we get the required number of parts in array + if (count($interval) >= $parts) { + break; + } + } + + if (count($interval) === 0) { + if ($isNow && static::getHumanDiffOptions() & self::JUST_NOW) { + $key = 'diff_now'; + $translation = static::translator()->trans($key); + if ($translation !== $key) { + return $translation; + } + } + $count = static::getHumanDiffOptions() & self::NO_ZERO_DIFF ? 1 : 0; + $unit = $short ? 's' : 'second'; + $interval[] = static::translator()->transChoice($unit, $count, array(':count' => $count)); + } + + // join the interval parts by a space + $time = implode(' ', $interval); + + unset($diffIntervalArray, $interval); + + if ($absolute) { + return $time; + } + + $isFuture = $diffInterval->invert === 1; + + $transId = $isNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); + + if ($parts === 1) { + if ($isNow && $unit === 'day') { + if ($count === 1 && static::getHumanDiffOptions() & self::ONE_DAY_WORDS) { + $key = $isFuture ? 'diff_tomorrow' : 'diff_yesterday'; + $translation = static::translator()->trans($key); + if ($translation !== $key) { + return $translation; + } + } + if ($count === 2 && static::getHumanDiffOptions() & self::TWO_DAY_WORDS) { + $key = $isFuture ? 'diff_after_tomorrow' : 'diff_before_yesterday'; + $translation = static::translator()->trans($key); + if ($translation !== $key) { + return $translation; + } + } + } + // Some languages have special pluralization for past and future tense. + $key = $unit.'_'.$transId; + if ($key !== static::translator()->transChoice($key, $count)) { + $time = static::translator()->transChoice($key, $count, array(':count' => $count)); + } + } + + return static::translator()->trans($transId, array(':time' => $time)); + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// MODIFIERS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Resets the time to 00:00:00 start of day + * + * @return static + */ + public function startOfDay() + { + return $this->modify('00:00:00.000000'); + } + + /** + * Resets the time to 23:59:59 end of day + * + * @return static + */ + public function endOfDay() + { + return $this->modify('23:59:59.999999'); + } + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @return static + */ + public function startOfMonth() + { + return $this->setDate($this->year, $this->month, 1)->startOfDay(); + } + + /** + * Resets the date to end of the month and time to 23:59:59 + * + * @return static + */ + public function endOfMonth() + { + return $this->setDate($this->year, $this->month, $this->daysInMonth)->endOfDay(); + } + + /** + * Resets the date to the first day of the quarter and the time to 00:00:00 + * + * @return static + */ + public function startOfQuarter() + { + $month = ($this->quarter - 1) * static::MONTHS_PER_QUARTER + 1; + + return $this->setDate($this->year, $month, 1)->startOfDay(); + } + + /** + * Resets the date to end of the quarter and time to 23:59:59 + * + * @return static + */ + public function endOfQuarter() + { + return $this->startOfQuarter()->addMonths(static::MONTHS_PER_QUARTER - 1)->endOfMonth(); + } + + /** + * Resets the date to the first day of the year and the time to 00:00:00 + * + * @return static + */ + public function startOfYear() + { + return $this->setDate($this->year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the year and time to 23:59:59 + * + * @return static + */ + public function endOfYear() + { + return $this->setDate($this->year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the decade and the time to 00:00:00 + * + * @return static + */ + public function startOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the decade and time to 23:59:59 + * + * @return static + */ + public function endOfDecade() + { + $year = $this->year - $this->year % static::YEARS_PER_DECADE + static::YEARS_PER_DECADE - 1; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of the century and the time to 00:00:00 + * + * @return static + */ + public function startOfCentury() + { + $year = $this->year - ($this->year - 1) % static::YEARS_PER_CENTURY; + + return $this->setDate($year, 1, 1)->startOfDay(); + } + + /** + * Resets the date to end of the century and time to 23:59:59 + * + * @return static + */ + public function endOfCentury() + { + $year = $this->year - 1 - ($this->year - 1) % static::YEARS_PER_CENTURY + static::YEARS_PER_CENTURY; + + return $this->setDate($year, 12, 31)->endOfDay(); + } + + /** + * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 + * + * @return static + */ + public function startOfWeek() + { + while ($this->dayOfWeek !== static::$weekStartsAt) { + $this->subDay(); + } + + return $this->startOfDay(); + } + + /** + * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59 + * + * @return static + */ + public function endOfWeek() + { + while ($this->dayOfWeek !== static::$weekEndsAt) { + $this->addDay(); + } + + return $this->endOfDay(); + } + + /** + * Modify to start of current hour, minutes and seconds become 0 + * + * @return static + */ + public function startOfHour() + { + return $this->setTime($this->hour, 0, 0); + } + + /** + * Modify to end of current hour, minutes and seconds become 59 + * + * @return static + */ + public function endOfHour() + { + return $this->modify("$this->hour:59:59.999999"); + } + + /** + * Modify to start of current minute, seconds become 0 + * + * @return static + */ + public function startOfMinute() + { + return $this->setTime($this->hour, $this->minute, 0); + } + + /** + * Modify to end of current minute, seconds become 59 + * + * @return static + */ + public function endOfMinute() + { + return $this->modify("$this->hour:$this->minute:59.999999"); + } + + /** + * Modify to start of current minute, seconds become 0 + * + * @return static + */ + public function startOfSecond() + { + return $this->modify("$this->hour:$this->minute:$this->second.0"); + } + + /** + * Modify to end of current minute, seconds become 59 + * + * @return static + */ + public function endOfSecond() + { + return $this->modify("$this->hour:$this->minute:$this->second.999999"); + } + + /** + * Modify to midday, default to self::$midDayAt + * + * @return static + */ + public function midDay() + { + return $this->setTime(self::$midDayAt, 0, 0); + } + + /** + * Modify to the next occurrence of a given day of the week. + * If no dayOfWeek is provided, modify to the next occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function next($dayOfWeek = null) + { + if ($dayOfWeek === null) { + $dayOfWeek = $this->dayOfWeek; + } + + return $this->startOfDay()->modify('next '.static::$days[$dayOfWeek]); + } + + /** + * Go forward or backward to the next week- or weekend-day. + * + * @param bool $weekday + * @param bool $forward + * + * @return $this + */ + private function nextOrPreviousDay($weekday = true, $forward = true) + { + $step = $forward ? 1 : -1; + + do { + $this->addDay($step); + } while ($weekday ? $this->isWeekend() : $this->isWeekday()); + + return $this; + } + + /** + * Go forward to the next weekday. + * + * @return $this + */ + public function nextWeekday() + { + return $this->nextOrPreviousDay(); + } + + /** + * Go backward to the previous weekday. + * + * @return $this + */ + public function previousWeekday() + { + return $this->nextOrPreviousDay(true, false); + } + + /** + * Go forward to the next weekend day. + * + * @return $this + */ + public function nextWeekendDay() + { + return $this->nextOrPreviousDay(false); + } + + /** + * Go backward to the previous weekend day. + * + * @return $this + */ + public function previousWeekendDay() + { + return $this->nextOrPreviousDay(false, false); + } + + /** + * Modify to the previous occurrence of a given day of the week. + * If no dayOfWeek is provided, modify to the previous occurrence + * of the current day of the week. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function previous($dayOfWeek = null) + { + if ($dayOfWeek === null) { + $dayOfWeek = $this->dayOfWeek; + } + + return $this->startOfDay()->modify('last '.static::$days[$dayOfWeek]); + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * first day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function firstOfMonth($dayOfWeek = null) + { + $this->startOfDay(); + + if ($dayOfWeek === null) { + return $this->day(1); + } + + return $this->modify('first '.static::$days[$dayOfWeek].' of '.$this->format('F').' '.$this->year); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * last day of the current month. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek + * + * @return static + */ + public function lastOfMonth($dayOfWeek = null) + { + $this->startOfDay(); + + if ($dayOfWeek === null) { + return $this->day($this->daysInMonth); + } + + return $this->modify('last '.static::$days[$dayOfWeek].' of '.$this->format('F').' '.$this->year); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current month. If the calculated occurrence is outside the scope + * of the current month, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfMonth($nth, $dayOfWeek) + { + $date = $this->copy()->firstOfMonth(); + $check = $date->format('Y-m'); + $date->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $date->format('Y-m') === $check ? $this->modify($date) : false; + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * first day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER - 2, 1)->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * last day of the current quarter. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfQuarter($dayOfWeek = null) + { + return $this->setDate($this->year, $this->quarter * static::MONTHS_PER_QUARTER, 1)->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current quarter. If the calculated occurrence is outside the scope + * of the current quarter, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfQuarter($nth, $dayOfWeek) + { + $date = $this->copy()->day(1)->month($this->quarter * static::MONTHS_PER_QUARTER); + $lastMonth = $date->month; + $year = $date->year; + $date->firstOfQuarter()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return ($lastMonth < $date->month || $year !== $date->year) ? false : $this->modify($date); + } + + /** + * Modify to the first occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * first day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function firstOfYear($dayOfWeek = null) + { + return $this->month(1)->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurrence of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * last day of the current year. Use the supplied constants + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int|null $dayOfWeek day of the week default null + * + * @return static + */ + public function lastOfYear($dayOfWeek = null) + { + return $this->month(static::MONTHS_PER_YEAR)->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurrence of a given day of the week + * in the current year. If the calculated occurrence is outside the scope + * of the current year, then return false and no modifications are made. + * Use the supplied constants to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfYear($nth, $dayOfWeek) + { + $date = $this->copy()->firstOfYear()->modify('+'.$nth.' '.static::$days[$dayOfWeek]); + + return $this->year === $date->year ? $this->modify($date) : false; + } + + /** + * Modify the current instance to the average of a given instance (default now) and the current instance. + * + * @param \Carbon\Carbon|\DateTimeInterface|string|null $date + * + * @return static + */ + public function average($date = null) + { + return $this->addSeconds((int) ($this->diffInSeconds($this->resolveCarbon($date), false) / 2)); + } + + /////////////////////////////////////////////////////////////////// + /////////////////////////// SERIALIZATION ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Return a serialized string of the instance. + * + * @return string + */ + public function serialize() + { + return serialize($this); + } + + /** + * Create an instance from a serialized string. + * + * @param string $value + * + * @throws \InvalidArgumentException + * + * @return static + */ + public static function fromSerialized($value) + { + $instance = @unserialize($value); + + if (!$instance instanceof static) { + throw new InvalidArgumentException('Invalid serialized value.'); + } + + return $instance; + } + + /** + * The __set_state handler. + * + * @param array $array + * + * @return static + */ + public static function __set_state($array): \DateTime + { + return static::instance(parent::__set_state($array)); + } + + /** + * Prepare the object for JSON serialization. + * + * @return array|string + * : mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + if (static::$serializer) { + return call_user_func(static::$serializer, $this); + } + + $carbon = $this; + + return call_user_func(function () use ($carbon) { + return get_object_vars($carbon); + }); + } + + /** + * JSON serialize all Carbon instances using the given callback. + * + * @param callable $callback + * + * @return void + */ + public static function serializeUsing($callback) + { + static::$serializer = $callback; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////////////// MACRO ///////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Register a custom macro. + * + * @param string $name + * @param object|callable $macro + * + * @return void + */ + public static function macro($name, $macro) + { + static::$localMacros[$name] = $macro; + } + + /** + * Mix another object into the class. + * + * @param object $mixin + * + * @return void + */ + public static function mixin($mixin) + { + $reflection = new \ReflectionClass($mixin); + $methods = $reflection->getMethods( + \ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + $method->setAccessible(true); + + static::macro($method->name, $method->invoke($mixin)); + } + } + + /** + * Checks if macro is registered. + * + * @param string $name + * + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$localMacros[$name]); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * + * @throws \BadMethodCallException + * + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + if (!static::hasMacro($method)) { + throw new \BadMethodCallException("Method $method does not exist."); + } + + if (static::$localMacros[$method] instanceof Closure && method_exists('Closure', 'bind')) { + return call_user_func_array(Closure::bind(static::$localMacros[$method], null, get_called_class()), $parameters); + } + + return call_user_func_array(static::$localMacros[$method], $parameters); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * + * @throws \BadMethodCallException|\ReflectionException + * + * @return mixed + */ + public function __call($method, $parameters) + { + if (!static::hasMacro($method)) { + throw new \BadMethodCallException("Method $method does not exist."); + } + + $macro = static::$localMacros[$method]; + + $reflexion = new \ReflectionFunction($macro); + $reflectionParameters = $reflexion->getParameters(); + $expectedCount = count($reflectionParameters); + $actualCount = count($parameters); + if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') { + for ($i = $actualCount; $i < $expectedCount - 1; $i++) { + $parameters[] = $reflectionParameters[$i]->getDefaultValue(); + } + $parameters[] = $this; + } + + if ($macro instanceof Closure && method_exists($macro, 'bindTo')) { + return call_user_func_array($macro->bindTo($this, get_class($this)), $parameters); + } + + return call_user_func_array($macro, $parameters); + } +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/command.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/command.stub new file mode 100644 index 0000000..e9ef865 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/command.stub @@ -0,0 +1,68 @@ +define(Model::class, function (Faker $faker) { + return [ + // + ]; +}); diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/job-queued.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/job-queued.stub new file mode 100644 index 0000000..5bcccf1 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/job-queued.stub @@ -0,0 +1,34 @@ +view('view.name'); + } +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/middleware.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/middleware.stub new file mode 100644 index 0000000..954583e --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/middleware.stub @@ -0,0 +1,21 @@ +increments('id'); +$FIELDS$ + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('$TABLE$'); + } +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/delete.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/delete.stub new file mode 100644 index 0000000..53ec1b3 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/delete.stub @@ -0,0 +1,32 @@ +increments('id'); +$FIELDS$ + $table->timestamps(); + }); + } +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/plain.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/plain.stub new file mode 100644 index 0000000..cc014c6 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/migration/plain.stub @@ -0,0 +1,28 @@ +line('The introduction to the notification.') + ->action('Notification Action', 'https://laravel.com') + ->line('Thank you for using our application!'); + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/policy.plain.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/policy.plain.stub new file mode 100644 index 0000000..02e16df --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/policy.plain.stub @@ -0,0 +1,20 @@ +routesAreCached()) { + // require __DIR__ . '$ROUTES_PATH$'; + // } + } +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/routes.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/routes.stub new file mode 100644 index 0000000..2932357 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/routes.stub @@ -0,0 +1,6 @@ + 'web', 'prefix' => \Helper::getSubdirectory(), 'namespace' => '$MODULE_NAMESPACE$\$STUDLY_NAME$\Http\Controllers'], function() +{ + Route::get('/', '$STUDLY_NAME$Controller@index'); +}); diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/rule.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/rule.stub new file mode 100644 index 0000000..9b71632 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/rule.stub @@ -0,0 +1,40 @@ + '$STUDLY_NAME$' +]; diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/scaffold/provider.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/scaffold/provider.stub new file mode 100644 index 0000000..d619ca7 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/scaffold/provider.stub @@ -0,0 +1,114 @@ +registerConfig(); + $this->registerViews(); + $this->registerFactories(); + $this->loadMigrationsFrom(__DIR__ . '/../$MIGRATIONS_PATH$'); + $this->hooks(); + } + + /** + * Module hooks. + */ + public function hooks() + { + + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->registerTranslations(); + } + + /** + * Register config. + * + * @return void + */ + protected function registerConfig() + { + $this->publishes([ + __DIR__.'/../$PATH_CONFIG$/config.php' => config_path('$LOWER_NAME$.php'), + ], 'config'); + $this->mergeConfigFrom( + __DIR__.'/../$PATH_CONFIG$/config.php', '$LOWER_NAME$' + ); + } + + /** + * Register views. + * + * @return void + */ + public function registerViews() + { + $viewPath = resource_path('views/modules/$LOWER_NAME$'); + + $sourcePath = __DIR__.'/../$PATH_VIEWS$'; + + $this->publishes([ + $sourcePath => $viewPath + ],'views'); + + $this->loadViewsFrom(array_merge(array_map(function ($path) { + return $path . '/modules/$LOWER_NAME$'; + }, \Config::get('view.paths')), [$sourcePath]), '$LOWER_NAME$'); + } + + /** + * Register translations. + * + * @return void + */ + public function registerTranslations() + { + $this->loadJsonTranslationsFrom(__DIR__ .'/../$PATH_LANG$'); + } + + /** + * Register an additional directory of factories. + * @source https://github.com/sebastiaanluca/laravel-resource-flow/blob/develop/src/Modules/ModuleServiceProvider.php#L66 + */ + public function registerFactories() + { + if (! app()->environment('production')) { + app(Factory::class)->load(__DIR__ . '/../$FACTORIES_PATH$'); + } + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return []; + } +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/seeder.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/seeder.stub new file mode 100644 index 0000000..dd43490 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/seeder.stub @@ -0,0 +1,21 @@ +call("OthersTableSeeder"); + } +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/start.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/start.stub new file mode 100644 index 0000000..821c98d --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/start.stub @@ -0,0 +1,17 @@ +routesAreCached()) { + require __DIR__ . '$ROUTES_LOCATION$'; +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/unit-test.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/unit-test.stub new file mode 100644 index 0000000..d21c296 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/unit-test.stub @@ -0,0 +1,19 @@ +assertTrue(true); + } +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/views/index.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/views/index.stub new file mode 100644 index 0000000..4a64852 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/views/index.stub @@ -0,0 +1,9 @@ +@extends('$LOWER_NAME$::layouts.master') + +@section('content') +

Hello World

+ +

+ This view is loaded from module: {!! config('$LOWER_NAME$.name') !!} +

+@stop diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/views/master.stub b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/views/master.stub new file mode 100644 index 0000000..14fd322 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Commands/stubs/views/master.stub @@ -0,0 +1,12 @@ + + + + + + + Module $STUDLY_NAME$ + + + @yield('content') + + diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Json.php b/freescout-dist/overrides/nwidart/laravel-modules/src/Json.php new file mode 100644 index 0000000..3e38ac3 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Json.php @@ -0,0 +1,256 @@ +path = (string) $path; + $this->filesystem = $filesystem ?: new Filesystem(); + + // Here we allow to pass json data as array to avoid reading the file + if ($data) { + $this->attributes = new Collection($data); + } else { + $this->attributes = Collection::make($this->getAttributes()); + } + } + + /** + * Get filesystem. + * + * @return Filesystem + */ + public function getFilesystem() + { + return $this->filesystem; + } + + /** + * Set filesystem. + * + * @param Filesystem $filesystem + * + * @return $this + */ + public function setFilesystem(Filesystem $filesystem) + { + $this->filesystem = $filesystem; + + return $this; + } + + /** + * Get path. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Set path. + * + * @param mixed $path + * + * @return $this + */ + public function setPath($path) + { + $this->path = (string) $path; + + return $this; + } + + /** + * Make new instance. + * + * @param string $path + * @param \Illuminate\Filesystem\Filesystem $filesystem + * + * @return static + */ + public static function make($path, Filesystem $filesystem = null) + { + return new static($path, $filesystem); + } + + /** + * Get file content. + * + * @return string + */ + public function getContents() + { + return $this->filesystem->get($this->getPath()); + } + + /** + * Get file contents as array. + * + * @throws \Exception + * + * @return array + */ + public function getAttributes() + { + // Do not read file every time + if ($this->attributes && $this->attributes->toArray()) { + return $this->attributes->toArray(); + } else { + $attributes = json_decode($this->getContents(), 1); + + // any JSON parsing errors should throw an exception + if (json_last_error() > 0) { + throw new InvalidJsonException('Error processing file: '.$this->getPath().'. Error: '.json_last_error_msg()); + } + } + + // Do not cache individual module.json files, only alltogether + return $attributes; + + // if (config('modules.cache.enabled') === false) { + // return $attributes; + // } + + // return app('cache')->remember($this->getPath(), config('modules.cache.lifetime'), function () use ($attributes) { + // return $attributes; + // }); + } + + /** + * Convert the given array data to pretty json. + * + * @param array $data + * + * @return string + */ + public function toJsonPretty(array $data = null) + { + return json_encode($data ?: $this->attributes, JSON_PRETTY_PRINT); + } + + /** + * Update json contents from array data. + * + * @param array $data + * + * @return bool + */ + public function update(array $data) + { + $this->attributes = new Collection(array_merge($this->attributes->toArray(), $data)); + + return $this->save(); + } + + /** + * Set a specific key & value. + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function set($key, $value) + { + $this->attributes->offsetSet($key, $value); + + return $this; + } + + /** + * Save the current attributes array to the file storage. + * + * @return bool + */ + public function save() + { + return $this->filesystem->put($this->getPath(), $this->toJsonPretty()); + } + + /** + * Handle magic method __get. + * + * @param string $key + * + * @return mixed + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Get the specified attribute from json file. + * + * @param $key + * @param null $default + * + * @return mixed + */ + public function get($key, $default = null) + { + return $this->attributes->get($key, $default); + } + + /** + * Handle call to __call method. + * + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, $arguments = []) + { + if (method_exists($this, $method)) { + return call_user_func_array([$this, $method], $arguments); + } + + return call_user_func_array([$this->attributes, $method], $arguments); + } + + /** + * Handle call to __toString method. + * + * @return string + */ + public function __toString() + { + return $this->getContents(); + } +} diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Module.php b/freescout-dist/overrides/nwidart/laravel-modules/src/Module.php new file mode 100644 index 0000000..643f012 --- /dev/null +++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Module.php @@ -0,0 +1,510 @@ +name = $name; + $this->path = realpath($path); + } + + /** + * Get laravel instance. + * + * @return \Illuminate\Contracts\Foundation\Application|Laravel\Lumen\Application + */ + public function getLaravel() + { + return $this->app; + } + + /** + * Get name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get name in lower case. + * + * @return string + */ + public function getLowerName() + { + return strtolower($this->name); + } + + /** + * Get name in studly case. + * + * @return string + */ + public function getStudlyName() + { + return Str::studly($this->name); + } + + /** + * Get name in snake case. + * + * @return string + */ + public function getSnakeName() + { + return Str::snake($this->name); + } + + /** + * Get description. + * + * @return string + */ + public function getDescription() + { + return $this->get('description'); + } + + /** + * Get alias. + * + * @return string + */ + public function getAlias() + { + return $this->get('alias'); + } + + /** + * Get priority. + * + * @return string + */ + public function getPriority() + { + return $this->get('priority'); + } + + /** + * Get module requirements. + * + * @return array + */ + public function getRequires() + { + return $this->get('requires'); + } + + /** + * Get path. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Set path. + * + * @param string $path + * + * @return $this + */ + public function setPath($path) + { + $this->path = $path; + + return $this; + } + + /** + * Bootstrap the application events. + */ + public function boot() + { + if (config('modules.register.translations', true) === true) { + $this->registerTranslation(); + } + + if ($this->isLoadFilesOnBoot()) { + try { + $this->registerFiles(); + } catch (\Exception $e) { + $e = \Eventy::filter('modules.register_error', $e, $this); + if ($e) { + throw $e; + } + } + } + + $this->fireEvent('boot'); + } + + /** + * Register module's translation. + * + * @return void + */ + protected function registerTranslation() + { + $lowerName = $this->getLowerName(); + + $langPath = $this->getPath().'/Resources/lang'; + + if (is_dir($langPath)) { + $this->loadTranslationsFrom($langPath, $lowerName); + } + } + + /** + * Get json contents from the cache, setting as needed. + * + * @param string $file + * + * @return Json + */ + public function json($file = null) : Json + { + if ($file === null) { + $file = 'module.json'; + } + + return array_get($this->moduleJson, $file, function () use ($file) { + // In this Laravel-Modules package caching is not working, so we need to implement it. + // https://github.com/nWidart/laravel-modules/issues/659 + + // Cache stores module.json files as arrays + $cachedManifestsArray = $this->app['cache']->get($this->app['config']->get('modules.cache.key')); + + if ($cachedManifestsArray && count($cachedManifestsArray)) { + foreach ($cachedManifestsArray as $manifest) { + // We found manifest data in cache + if (!empty($manifest['name']) && $manifest['name'] == $this->getName()) { + return $this->moduleJson[$file] = new Json($this->getPath().'/'.$file, $this->app['files'], $manifest); + } + } + } + + // We have to set `active` flag from DB modules table. + $json = new Json($this->getPath().'/'.$file, $this->app['files']); + $json->set('active', (int) \App\Module::isActive($json->get('alias'))); + + return $this->moduleJson[$file] = $json; + //return $this->moduleJson[$file] = new Json($this->getPath() . '/' . $file, $this->app['files']); + }); + } + + /** + * Get a specific data from json file by given the key. + * + * @param string $key + * @param null $default + * + * @return mixed + */ + public function get(string $key, $default = null) + { + return $this->json()->get($key, $default); + } + + /** + * Get a specific data from composer.json file by given the key. + * + * @param $key + * @param null $default + * + * @return mixed + */ + public function getComposerAttr($key, $default = null) + { + return $this->json('composer.json')->get($key, $default); + } + + /** + * Register the module. + */ + public function register() + { + $this->registerAliases(); + + try { + $this->registerProviders(); + } catch (\Exception $e) { + $e = \Eventy::filter('modules.register_error', $e, $this); + if ($e) { + throw $e; + } + } + + if ($this->isLoadFilesOnBoot() === false) { + $this->registerFiles(); + } + + $this->fireEvent('register'); + } + + /** + * Register the module event. + * + * @param string $event + */ + protected function fireEvent($event) + { + $this->app['events']->fire(sprintf('modules.%s.'.$event, $this->getLowerName()), [$this]); + } + + /** + * Register the aliases from this module. + */ + abstract public function registerAliases(); + + /** + * Register the service providers from this module. + */ + abstract public function registerProviders(); + + /** + * Get the path to the cached *_module.php file. + * + * @return string + */ + abstract public function getCachedServicesPath(); + + /** + * Register the files from this module. + */ + protected function registerFiles() + { + try { + foreach ($this->get('files', []) as $file) { + include $this->path.'/'.$file; + } + } catch (\Exception $e) { + $e = \Eventy::filter('modules.register_error', $e, $this); + if ($e) { + throw $e; + } + } + } + + /** + * Handle call __toString. + * + * @return string + */ + public function __toString() + { + return $this->getStudlyName(); + } + + /** + * Determine whether the given status same with the current module status. + * + * @param $status + * + * @return bool + */ + public function isStatus($status) : bool + { + // echo "
";
+        // print_r($this->json());
+
+        //return (int)\App\Module::isActive($this->getAlias()) == $status;
+        return $this->get('active', 0) === $status;
+    }
+
+    /**
+     * Determine whether the current module activated.
+     *
+     * @return bool
+     */
+    public function enabled() : bool
+    {
+        return $this->isStatus(1);
+    }
+
+    /**
+     * Alternate for "enabled" method.
+     *
+     * @return bool
+     *
+     * @deprecated
+     */
+    public function active()
+    {
+        return $this->isStatus(1);
+    }
+
+    /**
+     * Determine whether the current module not activated.
+     *
+     * @return bool
+     *
+     * @deprecated
+     */
+    public function notActive()
+    {
+        return !$this->active();
+    }
+
+    /**
+     *  Determine whether the current module not disabled.
+     *
+     * @return bool
+     */
+    public function disabled() : bool
+    {
+        return !$this->enabled();
+    }
+
+    /**
+     * Set active state for current module.
+     *
+     * @param $active
+     *
+     * @return bool
+     */
+    public function setActive($active)
+    {
+        \App\Module::setActive($this->getAlias(), $active);
+        //return $this->json()->set('active', $active)->save();
+    }
+
+    /**
+     * Disable the current module.
+     */
+    public function disable()
+    {
+        $this->fireEvent('disabling');
+
+        $this->setActive(0);
+
+        $this->fireEvent('disabled');
+    }
+
+    /**
+     * Enable the current module.
+     */
+    public function enable()
+    {
+        $this->fireEvent('enabling');
+
+        $this->setActive(1);
+
+        $this->fireEvent('enabled');
+    }
+
+    /**
+     * Delete the current module.
+     *
+     * @return bool
+     */
+    public function delete()
+    {
+        return $this->json()->getFilesystem()->deleteDirectory($this->getPath());
+    }
+
+    /**
+     * Get extra path.
+     *
+     * @param string $path
+     *
+     * @return string
+     */
+    public function getExtraPath(string $path) : string
+    {
+        return $this->getPath().'/'.$path;
+    }
+
+    /**
+     * Handle call to __get method.
+     *
+     * @param $key
+     *
+     * @return mixed
+     */
+    public function __get($key)
+    {
+        return $this->get($key);
+    }
+
+    /**
+     * Check if can load files of module on boot method.
+     *
+     * @return bool
+     */
+    protected function isLoadFilesOnBoot()
+    {
+        return config('modules.register.files', 'register') === 'boot' &&
+            // force register method if option == boot && app is AsgardCms
+            !class_exists('\Modules\Core\Foundation\AsgardCms');
+    }
+
+    /**
+     * Check if module is official.
+     *
+     * @return bool [description]
+     */
+    public function isOfficial()
+    {
+        return \App\Module::isOfficial($this->get('authorUrl'));
+    }
+
+    /**
+     * Get module license from DB.
+     *
+     * @return [type] [description]
+     */
+    public function getLicense()
+    {
+        return \App\Module::getLicense($this->getAlias());
+    }
+}
diff --git a/freescout-dist/overrides/nwidart/laravel-modules/src/Repository.php b/freescout-dist/overrides/nwidart/laravel-modules/src/Repository.php
new file mode 100644
index 0000000..fc84eae
--- /dev/null
+++ b/freescout-dist/overrides/nwidart/laravel-modules/src/Repository.php
@@ -0,0 +1,843 @@
+app = $app;
+        $this->path = $path;
+    }
+
+    /**
+     * Add other module location.
+     *
+     * @param string $path
+     *
+     * @return $this
+     */
+    public function addLocation($path)
+    {
+        $this->paths[] = $path;
+
+        return $this;
+    }
+
+    /**
+     * Alternative method for "addPath".
+     *
+     * @param string $path
+     *
+     * @return $this
+     *
+     * @deprecated
+     */
+    public function addPath($path)
+    {
+        return $this->addLocation($path);
+    }
+
+    /**
+     * Get all additional paths.
+     *
+     * @return array
+     */
+    public function getPaths() : array
+    {
+        return $this->paths;
+    }
+
+    /**
+     * Get scanned modules paths.
+     *
+     * @return array
+     */
+    public function getScanPaths() : array
+    {
+        $paths = $this->paths;
+
+        $paths[] = $this->getPath();
+
+        if ($this->config('scan.enabled')) {
+            $paths = array_merge($paths, $this->config('scan.paths'));
+        }
+
+        $paths = array_map(function ($path) {
+            return ends_with($path, '/*') ? $path : str_finish($path, '/*');
+        }, $paths);
+
+        return $paths;
+    }
+
+    /**
+     * Creates a new Module instance.
+     *
+     * @param Container $app
+     * @param $name
+     * @param $path
+     *
+     * @return \Nwidart\Modules\Module
+     */
+    abstract protected function createModule(...$args);
+
+    /**
+     * Get & scan all modules.
+     *
+     * @return array
+     */
+    public function scan()
+    {
+        $paths = $this->getScanPaths();
+
+        $modules = [];
+
+        foreach ($paths as $key => $path) {
+            $manifests = $this->app['files']->glob("{$path}/module.json");
+
+            is_array($manifests) || $manifests = [];
+
+            foreach ($manifests as $manifest) {
+                $name = Json::make($manifest)->get('name');
+
+                $modules[$name] = $this->createModule($this->app, $name, dirname($manifest));
+
+                // Overwrite module `active` flag with value from DB modules table.
+                // Configuration is cached right when freescout:clear-cache is executed.
+                $alias = $modules[$name]->getAlias();
+                if ($alias) {
+                    $modules[$name]->json()->set('active', (int) \App\Module::isActive($alias));
+                }
+            }
+        }
+
+        return $modules;
+    }
+
+    /**
+     * Get all modules.
+     *
+     * @return array
+     */
+    public function all($forceScan = false) : array
+    {
+        if (!$this->config('cache.enabled') || $forceScan) {
+            return $this->scan();
+        }
+
+        return $this->formatCached($this->getCached());
+    }
+
+    /**
+     * Clear modules cache.
+     */
+    public function clearCache()
+    {
+        $this->cache = null;
+        $this->app['cache']->forget($this->config('cache.key'));
+    }
+
+    /**
+     * Format the cached data as array of modules.
+     *
+     * @param array $cached
+     *
+     * @return array
+     */
+    protected function formatCached($cached)
+    {
+        $modules = [];
+
+        foreach ($cached as $name => $module) {
+            $path = $module['path'];
+
+            $modules[$name] = $this->createModule($this->app, $name, $path);
+        }
+
+        return $modules;
+    }
+
+    /**
+     * Get cached modules.
+     *
+     * @return array
+     */
+    public function getCached()
+    {
+        if ($this->cache) {
+            return $this->cache;
+        }
+
+        return $this->app['cache']->remember($this->config('cache.key'), $this->config('cache.lifetime'), function () {
+
+            // By some reason when Nwidart\Modules\Module is converted into array
+            $array = $this->toCollection()->toArray();
+            // Set `active` flag from DB for each module
+            foreach ($array as $key => $item) {
+                if (!empty($item['alias'])) {
+                    $item['active'] = (int) \App\Module::isActive($item['alias']);
+                }
+            }
+
+            // Remember in memory cache to avoid reading cache file
+            $this->cache = $array;
+
+            return $array;
+        });
+    }
+
+    /**
+     * Get all modules as collection instance.
+     *
+     * @return Collection
+     */
+    public function toCollection() : Collection
+    {
+        return new Collection($this->scan());
+    }
+
+    /**
+     * Get modules by status.
+     *
+     * @param $status
+     *
+     * @return array
+     */
+    public function getByStatus($status) : array
+    {
+        $modules = [];
+
+        foreach ($this->all() as $name => $module) {
+            if ($module->isStatus($status)) {
+                $modules[$name] = $module;
+            }
+        }
+
+        return $modules;
+    }
+
+    /**
+     * Determine whether the given module exist.
+     *
+     * @param $name
+     *
+     * @return bool
+     */
+    public function has($name) : bool
+    {
+        return array_key_exists($name, $this->all());
+    }
+
+    /**
+     * Get list of enabled modules.
+     *
+     * @return array
+     */
+    public function enabled() : array
+    {
+        return $this->getByStatus(1);
+    }
+
+    /**
+     * Get active modules.
+     *
+     * @return [type] [description]
+     */
+    public function getActive() : array
+    {
+        return $this->enabled();
+    }
+
+    /**
+     * Get list of disabled modules.
+     *
+     * @return array
+     */
+    public function disabled() : array
+    {
+        return $this->getByStatus(0);
+    }
+
+    /**
+     * Get count from all modules.
+     *
+     * @return int
+     */
+    public function count() : int
+    {
+        return count($this->all());
+    }
+
+    /**
+     * Get all ordered modules.
+     *
+     * @param string $direction
+     *
+     * @return array
+     */
+    public function getOrdered($direction = 'asc') : array
+    {
+        $modules = $this->enabled();
+
+        uasort($modules, function (Module $a, Module $b) use ($direction) {
+            if ($a->order == $b->order) {
+                return 0;
+            }
+
+            if ($direction == 'desc') {
+                return $a->order < $b->order ? 1 : -1;
+            }
+
+            return $a->order > $b->order ? 1 : -1;
+        });
+
+        return $modules;
+    }
+
+    /**
+     * Get a module path.
+     *
+     * @return string
+     */
+    public function getPath() : string
+    {
+        return $this->path ?: $this->config('paths.modules', base_path('Modules'));
+    }
+
+    /**
+     * Register the modules.
+     */
+    public function register()
+    {
+        foreach ($this->getOrdered() as $module) {
+            $module->register();
+        }
+    }
+
+    /**
+     * Boot the modules.
+     */
+    public function boot()
+    {
+        foreach ($this->getOrdered() as $module) {
+            $module->boot();
+        }
+    }
+
+    /**
+     * Find a specific module.
+     *
+     * @param $name
+     *
+     * @return mixed|void
+     */
+    public function find($name)
+    {
+        foreach ($this->all() as $module) {
+            if ($module->getLowerName() === strtolower($name)) {
+                return $module;
+            }
+        }
+    }
+
+    /**
+     * Find a specific module by its alias.
+     *
+     * @param $alias
+     *
+     * @return mixed|void
+     */
+    public function findByAlias($alias)
+    {
+        foreach ($this->all() as $module) {
+            if (strtolower($module->getAlias()) === $alias) {
+                //if ($module->getAlias() === $alias) {
+                return $module;
+            }
+        }
+    }
+
+    /**
+     * Check by alias if module is active.
+     *
+     * @param [type] $alias [description]
+     *
+     * @return bool [description]
+     */
+    public function isActive($alias, $use_cache = true)
+    {
+        if ($use_cache && isset(self::$active_cache[$alias])) {
+            return self::$active_cache[$alias];
+        }
+        $module = $this->findByAlias($alias);
+        if ($module && $module->active()) {
+            $is_active = true;
+        } else {
+            $is_active = false;
+        }
+
+        self::$active_cache[$alias] = $is_active;
+
+        return $is_active;
+    }
+
+    /**
+     * Find all modules that are required by a module. If the module cannot be found, throw an exception.
+     *
+     * @param $name
+     *
+     * @throws ModuleNotFoundException
+     *
+     * @return array
+     */
+    public function findRequirements($name)
+    {
+        $requirements = [];
+
+        $module = $this->findOrFail($name);
+
+        foreach ($module->getRequires() as $requirementName) {
+            $requirements[] = $this->findByAlias($requirementName);
+        }
+
+        return $requirements;
+    }
+
+    /**
+     * Alternative for "find" method.
+     *
+     * @param $name
+     *
+     * @return mixed|void
+     *
+     * @deprecated
+     */
+    public function get($name)
+    {
+        return $this->find($name);
+    }
+
+    /**
+     * Find a specific module, if there return that, otherwise throw exception.
+     *
+     * @param $name
+     *
+     * @throws ModuleNotFoundException
+     *
+     * @return Module
+     */
+    public function findOrFail($name)
+    {
+        $module = $this->find($name);
+
+        if ($module !== null) {
+            return $module;
+        }
+
+        throw new ModuleNotFoundException("Module [{$name}] does not exist!");
+    }
+
+    /**
+     * Get all modules as laravel collection instance.
+     *
+     * @param $status
+     *
+     * @return Collection
+     */
+    public function collections($status = 1) : Collection
+    {
+        return new Collection($this->getByStatus($status));
+    }
+
+    /**
+     * Get module path for a specific module.
+     *
+     * @param $module
+     *
+     * @return string
+     */
+    public function getModulePath($module)
+    {
+        try {
+            return $this->findOrFail($module)->getPath().'/';
+        } catch (ModuleNotFoundException $e) {
+            return $this->getPath().'/'.Str::studly($module).'/';
+        }
+    }
+
+    public function getModulePathByAlias($module_alias)
+    {
+        try {
+            $module = $this->findByAlias($module_alias);
+            if (!$module) {
+                throw new ModuleNotFoundException('', 1);
+            }
+
+            return $module->getPath().'/';
+        } catch (ModuleNotFoundException $e) {
+            //return $this->getPath().'/'.Str::studly($module).'/';
+            return '';
+        }
+    }
+
+    /**
+     * Get asset path for a specific module.
+     *
+     * @param $module
+     *
+     * @return string
+     */
+    public function assetPath($module) : string
+    {
+        return $this->config('paths.assets').'/'.$module;
+    }
+
+    /**
+     * Get a specific config data from a configuration file.
+     *
+     * @param $key
+     * @param null $default
+     *
+     * @return mixed
+     */
+    public function config($key, $default = null)
+    {
+        return $this->app['config']->get('modules.'.$key, $default);
+    }
+
+    /**
+     * Get storage path for module used.
+     *
+     * @return string
+     */
+    public function getUsedStoragePath() : string
+    {
+        $directory = storage_path('app/modules');
+        if ($this->app['files']->exists($directory) === false) {
+            $this->app['files']->makeDirectory($directory, 0777, true);
+        }
+
+        $path = storage_path('app/modules/modules.used');
+        if (!$this->app['files']->exists($path)) {
+            $this->app['files']->put($path, '');
+        }
+
+        return $path;
+    }
+
+    /**
+     * Set module used for cli session.
+     *
+     * @param $name
+     *
+     * @throws ModuleNotFoundException
+     */
+    public function setUsed($name)
+    {
+        $module = $this->findOrFail($name);
+
+        $this->app['files']->put($this->getUsedStoragePath(), $module);
+    }
+
+    /**
+     * Forget the module used for cli session.
+     */
+    public function forgetUsed()
+    {
+        if ($this->app['files']->exists($this->getUsedStoragePath())) {
+            $this->app['files']->delete($this->getUsedStoragePath());
+        }
+    }
+
+    /**
+     * Get module used for cli session.
+     *
+     * @throws \Nwidart\Modules\Exceptions\ModuleNotFoundException
+     *
+     * @return string
+     */
+    public function getUsedNow() : string
+    {
+        return $this->findOrFail($this->app['files']->get($this->getUsedStoragePath()));
+    }
+
+    /**
+     * Get used now.
+     *
+     * @return string
+     *
+     * @deprecated
+     */
+    public function getUsed()
+    {
+        return $this->getUsedNow();
+    }
+
+    /**
+     * Get laravel filesystem instance.
+     *
+     * @return \Illuminate\Filesystem\Filesystem
+     */
+    public function getFiles()
+    {
+        return $this->app['files'];
+    }
+
+    /**
+     * Get module assets path.
+     *
+     * @return string
+     */
+    public function getAssetsPath() : string
+    {
+        return $this->config('paths.assets');
+    }
+
+    /**
+     * Get asset url from a specific module.
+     *
+     * @param string $asset
+     *
+     * @throws InvalidAssetPath
+     *
+     * @return string
+     */
+    public function asset($asset) : string
+    {
+        if (str_contains($asset, ':') === false) {
+            throw InvalidAssetPath::missingModuleName($asset);
+        }
+        list($name, $url) = explode(':', $asset);
+
+        $baseUrl = str_replace(public_path().DIRECTORY_SEPARATOR, '', $this->getAssetsPath());
+
+        $url = $this->app['url']->asset($baseUrl."/{$name}/".$url);
+
+        return str_replace(['http://', 'https://'], '//', $url);
+    }
+
+    /**
+     * Determine whether the given module is activated.
+     *
+     * @param string $name
+     *
+     * @return bool
+     */
+    public function active($name) : bool
+    {
+        return $this->findOrFail($name)->active();
+    }
+
+    /**
+     * Determine whether the given module is not activated.
+     *
+     * @param string $name
+     *
+     * @return bool
+     */
+    public function notActive($name) : bool
+    {
+        return !$this->active($name);
+    }
+
+    /**
+     * Enabling a specific module.
+     *
+     * @param string $name
+     *
+     * @throws \Nwidart\Modules\Exceptions\ModuleNotFoundException
+     *
+     * @return void
+     */
+    public function enable($name)
+    {
+        $this->findOrFail($name)->enable();
+    }
+
+    /**
+     * Disabling a specific module.
+     *
+     * @param string $name
+     *
+     * @throws \Nwidart\Modules\Exceptions\ModuleNotFoundException
+     *
+     * @return void
+     */
+    public function disable($name)
+    {
+        $this->findOrFail($name)->disable();
+    }
+
+    /**
+     * Delete a specific module.
+     *
+     * @param string $name
+     *
+     * @throws \Nwidart\Modules\Exceptions\ModuleNotFoundException
+     *
+     * @return bool
+     */
+    public function delete($name) : bool
+    {
+        return $this->findOrFail($name)->delete();
+    }
+
+    /**
+     * Update dependencies for the specified module.
+     *
+     * @param string $module
+     */
+    public function update($module)
+    {
+        with(new Updater($this))->update($module);
+    }
+
+    /**
+     * Install the specified module.
+     *
+     * @param string $name
+     * @param string $version
+     * @param string $type
+     * @param bool   $subtree
+     *
+     * @return \Symfony\Component\Process\Process
+     */
+    public function install($name, $version = 'dev-master', $type = 'composer', $subtree = false)
+    {
+        $installer = new Installer($name, $version, $type, $subtree);
+
+        return $installer->run();
+    }
+
+    /**
+     * Get stub path.
+     *
+     * @return string|null
+     */
+    public function getStubPath()
+    {
+        if ($this->stubPath !== null) {
+            return $this->stubPath;
+        }
+
+        if ($this->config('stubs.enabled') === true) {
+            return $this->config('stubs.path');
+        }
+
+        return $this->stubPath;
+    }
+
+    /**
+     * Set stub path.
+     *
+     * @param string $stubPath
+     *
+     * @return $this
+     */
+    public function setStubPath($stubPath)
+    {
+        $this->stubPath = $stubPath;
+
+        return $this;
+    }
+
+    /**
+     * Get module option.
+     *
+     * @param [type] $module_alias [description]
+     * @param [type] $option_name  [description]
+     * @param bool   $default      [description]
+     *
+     * @return [type] [description]
+     */
+    public function getOption($module_alias, $option_name, $default = false)
+    {
+        // If not passed, get default value from config
+        if (func_num_args() == 2) {
+            $options = \Config::get(strtolower($module_alias).'.options');
+
+            if (isset($options[$option_name]) && isset($options[$option_name]['default'])) {
+                $default = $options[$option_name]['default'];
+            }
+        }
+
+        return \Option::get($module_alias.'.'.$option_name, $default);
+    }
+
+    /**
+     * Set module option.
+     *
+     * @param [type] $module_alias [description]
+     * @param [type] $option_name  [description]
+     * @param [type] $option_value [description]
+     */
+    public function setOption($module_alias, $option_name, $option_value)
+    {
+        return \Option::set(strtolower($module_alias).'.'.$option_name, $option_value);
+    }
+
+    /**
+     * Get module public path.
+     *
+     * @param [type] $module_alias [description]
+     *
+     * @return [type] [description]
+     */
+    public function getPublicPath($module_alias)
+    {
+        return '/modules/'.$module_alias;
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Controllers/EnvironmentController.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Controllers/EnvironmentController.php
new file mode 100644
index 0000000..30e1eb7
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Controllers/EnvironmentController.php
@@ -0,0 +1,185 @@
+EnvironmentManager = $environmentManager;
+    }
+
+    /**
+     * Display the Environment menu page.
+     *
+     * @return \Illuminate\View\View
+     */
+    public function environmentMenu()
+    {
+        return view('vendor.installer.environment');
+    }
+
+    /**
+     * Display the Environment page.
+     *
+     * @return \Illuminate\View\View
+     */
+    public function environmentWizard()
+    {
+        $envConfig = $this->EnvironmentManager->getEnvContent();
+
+        return view('vendor.installer.environment-wizard', compact('envConfig'));
+    }
+
+    /**
+     * Display the Environment page.
+     *
+     * @return \Illuminate\View\View
+     */
+    public function environmentClassic()
+    {
+        $envConfig = $this->EnvironmentManager->getEnvContent();
+
+        return view('vendor.installer.environment-classic', compact('envConfig'));
+    }
+
+    /**
+     * Processes the newly saved environment configuration (Classic).
+     *
+     * @param Request    $input
+     * @param Redirector $redirect
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function saveClassic(Request $input, Redirector $redirect)
+    {
+        $message = $this->EnvironmentManager->saveFileClassic($input);
+
+        event(new EnvironmentSaved($input));
+
+        return $redirect->route('LaravelInstaller::environmentClassic')
+                        ->with(['message' => $message]);
+    }
+
+    // Save old values to sessions
+    public function rememberOldRequest($request)
+    {
+        foreach ($request->all() as $field => $value) {
+            session(['_old_input.'.$field => $value]);
+        }
+    }
+
+    /**
+     * Processes the newly saved environment configuration (Form Wizard).
+     *
+     * @param Request    $request
+     * @param Redirector $redirect
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function saveWizard(Request $request, Redirector $redirect)
+    {
+        $envConfig = $this->EnvironmentManager->getEnvContent();
+
+        $rules = config('installer.environment.form.rules');
+        $messages = [
+            'environment_custom.required_if' => trans('installer_messages.environment.wizard.form.name_required'),
+        ];
+
+        $validator = Validator::make($request->all(), $rules, $messages);
+
+        if ($request->app_force_https == 'true') {
+            $request->merge(['app_url' => preg_replace('/^http:/i', 'https:', $request->app_url)]);
+        }
+
+        $this->rememberOldRequest($request);
+
+        if ($validator->fails()) {
+            $errors = $validator->errors();
+
+            return view('vendor.installer.environment-wizard', compact('errors', 'envConfig'));
+        }
+
+        // Check DB connection
+        //$this->EnvironmentManager->saveFileWizard($request);
+        try {
+            try {
+                $this->testDbConnect($request);
+            } catch (\Exception $e) {
+                // Change utf8mb4 to utf8 if needed.
+                if ($request->database_connection == 'mysql' && strstr($e->getMessage(), 'Unknown character set')) {
+                    $this->testDbConnect($request, ['charset' => 'utf8', 'collation' => 'utf8_unicode_ci']);
+
+                    $request->database_charset = 'utf8';
+                    $request->database_collation = 'utf8_unicode_ci';
+
+                    // $this->testDbConnect($request);
+                } else {
+                    throw $e;
+                }
+            }
+        } catch (\Exception $e) {
+            $validator->getMessageBag()->add('general', 'Could not establish database connection: '.$e->getMessage());
+            $validator->getMessageBag()->add('database_hostname', 'Database Host: Please check entered value.');
+            $validator->getMessageBag()->add('database_port', 'Database Port: Please check entered value.');
+            $validator->getMessageBag()->add('database_name', 'Database Name: Please check entered value.');
+            $validator->getMessageBag()->add('database_username', 'Database User Name: Please check entered value.');
+            $validator->getMessageBag()->add('database_password', 'Database Password: Please check entered value.');
+            $errors = $validator->errors();
+
+            // We have to write request to session again, as saveFileWizard() clears the cache and session
+            $this->rememberOldRequest($request);
+
+            return view('vendor.installer.environment-wizard', compact('errors', 'envConfig'));
+        }
+
+        $results = $this->EnvironmentManager->saveFileWizard($request);
+
+        event(new EnvironmentSaved($request));
+
+        return $redirect->route('LaravelInstaller::database')
+                        ->with(['results' => $results]);
+    }
+
+    public function testDbConnect($request, $params = [])
+    {
+        $driver = $request->database_connection ?? 'mysql';
+        $config = config('database.connections.'.$driver);
+        if (!$config) {
+            $config = [];
+        }
+
+        $params = array_merge($config, $params);
+
+        $params = array_merge($params, [
+            'driver'    => $driver,
+            'host'      => $request->database_hostname,
+            'port'      => $request->database_port,
+            'database'  => $request->database_name,
+            'username'  => $request->database_username,
+            'password'  => $request->database_password,
+            // 'charset'   => 'utf8mb4',
+            // 'collation' => 'utf8mb4_unicode_ci',
+            // 'prefix'    => '',
+        ]);
+
+        $params_hash = md5(json_encode($params));
+        \Config::set('database.connections.install'.$params_hash, $params);
+        \DB::connection('install'.$params_hash)->getPdo();
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Controllers/FinalController.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Controllers/FinalController.php
new file mode 100644
index 0000000..1909f8b
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Controllers/FinalController.php
@@ -0,0 +1,38 @@
+runFinal();
+        $finalStatusMessage = $fileManager->update();
+        $finalEnvFile = $environment->getEnvContent();
+
+        // Now clear cache and cache config
+        \Artisan::call('freescout:clear-cache');
+
+        event(new LaravelInstallerFinished());
+
+        return view('vendor.installer.finished', compact('finalMessages', 'finalStatusMessage', 'finalEnvFile', 'dbMessage'));
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Events/EnvironmentSaved.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Events/EnvironmentSaved.php
new file mode 100644
index 0000000..e6624b3
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Events/EnvironmentSaved.php
@@ -0,0 +1,30 @@
+request = $request;
+    }
+    
+    public function getRequest()
+    {
+        return $this->request;
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/DatabaseManager.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/DatabaseManager.php
new file mode 100644
index 0000000..bea484e
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/DatabaseManager.php
@@ -0,0 +1,99 @@
+sqlite($outputLog);
+
+        return $this->migrate($outputLog);
+    }
+
+    /**
+     * Run the migration and call the seeder.
+     *
+     * @param collection $outputLog
+     *
+     * @return collection
+     */
+    private function migrate($outputLog)
+    {
+        try {
+            Artisan::call('migrate', ['--force'=> true], $outputLog);
+        } catch (Exception $e) {
+            return $this->response($e->getMessage(), 'error', $outputLog);
+        }
+
+        return $this->response(trans('installer_messages.final.finished'), 'success', $outputLog);
+        //return $this->seed($outputLog);
+    }
+
+    /**
+     * Seed the database.
+     *
+     * @param collection $outputLog
+     *
+     * @return array
+     */
+    private function seed($outputLog)
+    {
+        try {
+            Artisan::call('db:seed', ['--force' => true], $outputLog);
+        } catch (Exception $e) {
+            return $this->response($e->getMessage(), 'error', $outputLog);
+        }
+
+        return $this->response(trans('installer_messages.final.finished'), 'success', $outputLog);
+    }
+
+    /**
+     * Return a formatted error messages.
+     *
+     * @param $message
+     * @param string     $status
+     * @param collection $outputLog
+     *
+     * @return array
+     */
+    private function response($message, $status, $outputLog)
+    {
+        return [
+            'status'      => $status,
+            'message'     => $message,
+            'dbOutputLog' => $outputLog->fetch(),
+        ];
+    }
+
+    /**
+     * check database type. If SQLite, then create the database file.
+     *
+     * @param collection $outputLog
+     */
+    private function sqlite($outputLog)
+    {
+        if (DB::connection() instanceof SQLiteConnection) {
+            $database = DB::connection()->getDatabaseName();
+            if (!file_exists($database)) {
+                touch($database);
+                DB::reconnect(Config::get('database.default'));
+            }
+            $outputLog->write('Using SqlLite database: '.$database, 1);
+        }
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/EnvironmentManager.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/EnvironmentManager.php
new file mode 100644
index 0000000..fc7c890
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/EnvironmentManager.php
@@ -0,0 +1,160 @@
+envPath = base_path('.env');
+        $this->envExamplePath = base_path('.env.example');
+    }
+
+    /**
+     * Get the content of the .env file.
+     *
+     * @return string
+     */
+    public function getEnvContent()
+    {
+        if (!file_exists($this->envPath)) {
+            if (file_exists($this->envExamplePath)) {
+                copy($this->envExamplePath, $this->envPath);
+            } else {
+                touch($this->envPath);
+            }
+        }
+
+        return file_get_contents($this->envPath);
+    }
+
+    /**
+     * Get the the .env file path.
+     *
+     * @return string
+     */
+    public function getEnvPath()
+    {
+        return $this->envPath;
+    }
+
+    /**
+     * Get the the .env.example file path.
+     *
+     * @return string
+     */
+    public function getEnvExamplePath()
+    {
+        return $this->envExamplePath;
+    }
+
+    /**
+     * Save the edited content to the .env file.
+     *
+     * @param Request $input
+     *
+     * @return string
+     */
+    public function saveFileClassic(Request $input)
+    {
+        $message = trans('installer_messages.environment.success');
+
+        try {
+            file_put_contents($this->envPath, $input->get('envConfig'));
+        } catch (Exception $e) {
+            $message = trans('installer_messages.environment.errors');
+        }
+
+        return $message;
+    }
+
+    /**
+     * Save the form content to the .env file.
+     *
+     * @param Request $request
+     *
+     * @return string
+     */
+    public function saveFileWizard(Request $request)
+    {
+        $results = trans('installer_messages.environment.success');
+
+        $envFileData =
+        // 'APP_NAME=\'' . $request->app_name . "'\n" .
+        // 'APP_ENV=' . $request->environment . "\n" .
+        //'APP_KEY=' . 'base64:bODi8VtmENqnjklBmNJzQcTTSC8jNjBysfnjQN59btE=' . "\n" .
+        // 'APP_DEBUG=' . $request->app_debug . "\n" .
+        // 'APP_LOG_LEVEL=' . $request->app_log_level . "\n" .
+        '# Every time you are making changes in .env file, in order changes to take an effect you need to run:'."\n".
+        '# php artisan freescout:clear-cache'."\n\n".
+        '# Application URL'."\n".
+        'APP_URL='.$request->app_url."\n\n".
+        '# Improve security'."\n".
+        'SESSION_SECURE_COOKIE='.(\Helper::isHttps($request->app_url) ? 'true' : '')."\n\n".
+        '# Timezones: https://github.com/freescout-helpdesk/freescout/wiki/PHP-Timezones'."\n".
+        '# Comment it to use default timezone from php.ini'."\n".
+        'APP_TIMEZONE='.$request->app_timezone."\n\n".
+        '# Default language'."\n".
+        'APP_LOCALE='.$request->app_locale."\n\n".
+        '# Database settings'."\n".
+        'DB_CONNECTION='.$request->database_connection."\n".
+        'DB_HOST='.$request->database_hostname."\n".
+        'DB_PORT='.$request->database_port."\n".
+        'DB_DATABASE='.$request->database_name."\n".
+        'DB_USERNAME='.$request->database_username."\n".
+        'DB_PASSWORD="'.str_replace('"', '\"\"', $request->database_password)."\"\n".
+        (!empty($request->database_charset) ? 'DB_CHARSET='.$request->database_charset."\n" : '').
+        (!empty($request->database_collation) ? 'DB_COLLATION='.$request->database_collation."\n" : '').
+        "\n".
+        '# Run the following console command to generate the key: php artisan key:generate'."\n".
+        '# Otherwise application will show the following error: "Whoops, looks like something went wrong"'."\n".
+        'APP_KEY='.\Config::get('app.key')."\n\n".
+        '# Uncomment to see errors in your browser, don\'t forget to comment it back when debugging finished'."\n".
+        '#APP_DEBUG='.'true'."\n";
+        // 'BROADCAST_DRIVER=' . $request->broadcast_driver . "\n" .
+        // 'CACHE_DRIVER=' . $request->cache_driver . "\n" .
+        // 'SESSION_DRIVER=' . $request->session_driver . "\n" .
+        // 'QUEUE_DRIVER=' . $request->queue_driver . "\n\n" .
+        // 'REDIS_HOST=' . $request->redis_hostname . "\n" .
+        // 'REDIS_PASSWORD=' . $request->redis_password . "\n" .
+        // 'REDIS_PORT=' . $request->redis_port . "\n\n" .
+        // 'MAIL_DRIVER=' . $request->mail_driver . "\n" .
+        // 'MAIL_HOST=' . $request->mail_host . "\n" .
+        // 'MAIL_PORT=' . $request->mail_port . "\n" .
+        // 'MAIL_USERNAME=' . $request->mail_username . "\n" .
+        // 'MAIL_PASSWORD=' . $request->mail_password . "\n" .
+        // 'MAIL_ENCRYPTION=' . $request->mail_encryption . "\n\n" .
+        // 'PUSHER_APP_ID=' . $request->pusher_app_id . "\n" .
+        // 'PUSHER_APP_KEY=' . $request->pusher_app_key . "\n" .
+        // 'PUSHER_APP_SECRET=' . $request->pusher_app_secret;
+
+        try {
+            file_put_contents($this->envPath, $envFileData);
+        } catch (Exception $e) {
+            $results = trans('installer_messages.environment.errors');
+        }
+
+        // Clear and update cache
+        // If we cache config here, it caches env data from memory
+        //\Artisan::call('freescout:clear-cache');
+        \Artisan::call('freescout:clear-cache', ['--doNotCacheConfig' => true]);
+
+        return $results;
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/FinalInstallManager.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/FinalInstallManager.php
new file mode 100644
index 0000000..d928f6a
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/FinalInstallManager.php
@@ -0,0 +1,112 @@
+ old('admin_email'),
+            'password'   => old('admin_password'),
+            'role'       => \App\User::ROLE_ADMIN,
+            'first_name' => old('admin_first_name'),
+            'last_name'  => old('admin_last_name'),
+        ];
+        // Remove admin data from sessions
+        // We need it to display on the page
+        //session(['_old_input' => []]);
+
+        $outputLog = new BufferedOutput();
+
+        $this->runCommands($outputLog);
+        //$this->generateKey($outputLog);
+        //$this->publishVendorAssets($outputLog);
+
+        // Check if admin already exists
+        if (\App\User::where('role', \App\User::ROLE_ADMIN)->count() == 0) {
+            \App\User::create($user_data);
+        }
+
+        return $outputLog->fetch();
+    }
+
+    private static function runCommands($outputLog)
+    {
+        try {
+            Artisan::call('freescout:clear-cache', [], $outputLog);
+            Artisan::call('storage:link', [], $outputLog);
+        } catch (Exception $e) {
+            return static::response($e->getMessage(), $outputLog);
+        }
+
+        return $outputLog;
+    }
+
+    /**
+     * Generate New Application Key.
+     *
+     * @param collection $outputLog
+     *
+     * @return collection
+     */
+    private static function generateKey($outputLog)
+    {
+        try {
+            if (config('installer.final.key')) {
+                Artisan::call('key:generate', ['--force'=> true], $outputLog);
+            }
+        } catch (Exception $e) {
+            return static::response($e->getMessage(), $outputLog);
+        }
+
+        return $outputLog;
+    }
+
+    /**
+     * Publish vendor assets.
+     *
+     * @param collection $outputLog
+     *
+     * @return collection
+     */
+    private static function publishVendorAssets($outputLog)
+    {
+        try {
+            if (config('installer.final.publish')) {
+                Artisan::call('vendor:publish', ['--all' => true], $outputLog);
+            }
+        } catch (Exception $e) {
+            return static::response($e->getMessage(), $outputLog);
+        }
+
+        return $outputLog;
+    }
+
+    /**
+     * Return a formatted error messages.
+     *
+     * @param $message
+     * @param collection $outputLog
+     *
+     * @return array
+     */
+    private static function response($message, $outputLog)
+    {
+        return [
+            'status'      => 'error',
+            'message'     => $message,
+            'dbOutputLog' => $outputLog->fetch(),
+        ];
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/InstalledFileManager.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/InstalledFileManager.php
new file mode 100644
index 0000000..cc835d6
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/InstalledFileManager.php
@@ -0,0 +1,40 @@
+create();
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/PermissionsChecker.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/PermissionsChecker.php
new file mode 100644
index 0000000..99ceb1e
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/PermissionsChecker.php
@@ -0,0 +1,114 @@
+results['permissions'] = [];
+
+        $this->results['errors'] = null;
+    }
+
+    /**
+     * Check for the folders permissions.
+     *
+     * @param array $folders
+     *
+     * @return array
+     */
+    public function check(array $folders)
+    {
+        foreach ($folders as $folder => $permission) {
+            //if(!($this->getPermission($folder) >= $permission))
+            if (!$this->isWritable($folder)) {
+                $this->addFileAndSetErrors($folder, $permission, false);
+            } else {
+                $this->addFile($folder, $permission, true);
+            }
+        }
+
+        return $this->results;
+    }
+
+    /**
+     * Get a folder permission.
+     *
+     * @param $folder
+     *
+     * @return string
+     */
+    private function getPermission($folder)
+    {
+        return substr(sprintf('%o', fileperms(base_path($folder))), -4);
+    }
+
+    /**
+     * Check if folder is writable by creating a temp file.
+     *
+     * @param [type] $folder [description]
+     *
+     * @return [type] [description]
+     */
+    private function isWritable($folder)
+    {
+        $path = base_path($folder);
+
+        try {
+            if (!file_exists($path)) {
+                \File::makeDirectory($path, 0775, true);
+            }
+            $file = $path.'.installer_test';
+            if ($file && file_put_contents($file, 'test')) {
+                unlink($file);
+
+                return true;
+            } else {
+                return false;
+            }
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * Add the file to the list of results.
+     *
+     * @param $folder
+     * @param $permission
+     * @param $isSet
+     */
+    private function addFile($folder, $permission, $isSet)
+    {
+        array_push($this->results['permissions'], [
+            'folder'     => $folder,
+            'permission' => $permission,
+            'isSet'      => $isSet,
+        ]);
+    }
+
+    /**
+     * Add the file and set the errors.
+     *
+     * @param $folder
+     * @param $permission
+     * @param $isSet
+     */
+    private function addFileAndSetErrors($folder, $permission, $isSet)
+    {
+        $this->addFile($folder, $permission, $isSet);
+
+        $this->results['errors'] = true;
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/RequirementsChecker.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/RequirementsChecker.php
new file mode 100644
index 0000000..788fca3
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Helpers/RequirementsChecker.php
@@ -0,0 +1,125 @@
+ $requirement)
+        {
+            switch ($type) {
+                // check php requirements
+                case 'php':
+                    foreach($requirements[$type] as $requirement)
+                    {
+                        $results['requirements'][$type][$requirement] = true;
+
+                        if(!extension_loaded($requirement))
+                        {
+                            $results['requirements'][$type][$requirement] = false;
+
+                            $results['errors'] = true;
+                        }
+                    }
+                    break;
+                // check apache requirements
+                case 'apache':
+                    foreach ($requirements[$type] as $requirement) {
+                        // if function doesn't exist we can't check apache modules
+                        if(function_exists('apache_get_modules'))
+                        {
+                            $results['requirements'][$type][$requirement] = true;
+
+                            if(!in_array($requirement,apache_get_modules()))
+                            {
+                                $results['requirements'][$type][$requirement] = false;
+
+                                $results['errors'] = true;
+                            }
+                        }
+                    }
+                    break;
+            }
+        }
+
+        return $results;
+    }
+
+    /**
+     * Check PHP version requirement.
+     *
+     * @return array
+     */
+    public function checkPHPversion(string $minPhpVersion = null)
+    {
+        $minVersionPhp = $minPhpVersion;
+        $currentPhpVersion = $this->getPhpVersionInfo();
+        $supported = false;
+
+        if ($minPhpVersion == null) {
+            $minVersionPhp = $this->getMinPhpVersion();
+        }
+
+        if (version_compare($currentPhpVersion['version'], $minVersionPhp) >= 0) {
+            $supported = true;
+        }
+
+        if (!version_compare($currentPhpVersion['version'], config('installer.core.maxPhpVersion'), '<=')) {
+            $supported = false;
+        }
+
+        $phpStatus = [
+            'full' => $currentPhpVersion['full'],
+            'current' => $currentPhpVersion['version'],
+            'minimum' => $minVersionPhp,
+            'supported' => $supported
+        ];
+
+        return $phpStatus;
+    }
+
+    /**
+     * Get current Php version information
+     *
+     * @return array
+     */
+    private static function getPhpVersionInfo()
+    {
+        $currentVersionFull = PHP_VERSION;
+        preg_match("#^\d+(\.\d+)*#", $currentVersionFull, $filtered);
+        $currentVersion = $filtered[0];
+
+        return [
+            'full' => $currentVersionFull,
+            'version' => $currentVersion
+        ];
+    }
+
+    /**
+     * Get minimum PHP version ID.
+     *
+     * @return string _minPhpVersion
+     */
+    protected function getMinPhpVersion()
+    {
+        return $this->_minPhpVersion;
+    }
+
+}
\ No newline at end of file
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Middleware/canInstall.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Middleware/canInstall.php
new file mode 100644
index 0000000..b0d493c
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Middleware/canInstall.php
@@ -0,0 +1,92 @@
+alreadyInstalled()) {
+            $installedRedirect = config('installer.installedAlreadyAction');
+
+            switch ($installedRedirect) {
+
+                case 'route':
+                    $routeName = config('installer.installed.redirectOptions.route.name');
+                    $data = config('installer.installed.redirectOptions.route.message');
+
+                    return redirect()->route($routeName)->with(['data' => $data]);
+                    break;
+
+                case 'abort':
+                    abort(config('installer.installed.redirectOptions.abort.type'));
+                    break;
+
+                case 'dump':
+                    $dump = config('installer.installed.redirectOptions.dump.data');
+                    dd($dump);
+                    break;
+
+                case '404':
+                case 'default':
+                default:
+                    abort(404);
+                    break;
+            }
+        }
+
+        return $next($request);
+    }
+
+    /**
+     * If application is already installed.
+     *
+     * @return bool
+     */
+    public function alreadyInstalled()
+    {
+        // If file exists, the app is 100% installed
+        if (file_exists(storage_path('.installed'))) {
+            return true;
+        }
+
+        // If there is no file, make extra checks
+        // If config is cached env() will always return empty
+        if (config('app.url') && config('app.key')
+            && config('database.default') && config('database.connections.mysql.host')
+            && config('database.connections.mysql.port') && config('database.connections.mysql.database')
+            && config('database.connections.mysql.username') && config('database.connections.mysql.password')
+        ) {
+            // Check DB connection
+            try {
+                \DB::connection()->getPdo();
+            } catch (\Exception $e) {
+                return false;
+            }
+
+            // Allow to access the last installation page
+            if (\Route::current()->getName()== 'LaravelInstaller::database' || \Route::current()->getName() == 'LaravelInstaller::final') {
+                return false;
+            }
+
+            return true;
+        } else {
+            return false;
+        }
+        //return file_exists(storage_path('installed'));
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Providers/LaravelInstallerServiceProvider.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Providers/LaravelInstallerServiceProvider.php
new file mode 100644
index 0000000..44d0172
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Providers/LaravelInstallerServiceProvider.php
@@ -0,0 +1,64 @@
+publishFiles();
+        $this->loadRoutesFrom(__DIR__.'/../Routes/web.php');
+    }
+
+    /**
+     * Bootstrap the application events.
+     *
+     * @param $void
+     */
+    public function boot(Router $router)
+    {
+        $router->middlewareGroup('install',[CanInstall::class]);
+        $router->middlewareGroup('update',[CanUpdate::class]);
+    }
+
+    /**
+     * Publish config file for the installer.
+     *
+     * @return void
+     */
+    protected function publishFiles()
+    {
+        $this->publishes([
+            __DIR__.'/../Config/installer.php' => base_path('config/installer.php'),
+        ], 'laravelinstaller');
+
+        $this->publishes([
+            __DIR__.'/../assets' => public_path('installer'),
+        ], 'laravelinstaller');
+
+        $this->publishes([
+            __DIR__.'/../Views' => base_path('resources/views/vendor/installer'),
+        ], 'laravelinstaller');
+
+        $this->publishes([
+            __DIR__.'/../Lang' => base_path('resources/lang'),
+        ], 'laravelinstaller');
+    }
+}
diff --git a/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Routes/web.php b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Routes/web.php
new file mode 100644
index 0000000..a7912b4
--- /dev/null
+++ b/freescout-dist/overrides/rachidlaasri/laravel-installer/src/Routes/web.php
@@ -0,0 +1,86 @@
+ $prefix.'install','as' => 'LaravelInstaller::','namespace' => 'RachidLaasri\LaravelInstaller\Controllers','middleware' => ['web', 'install']], function() {
+    Route::get('/', [
+        'as' => 'welcome',
+        'uses' => 'WelcomeController@welcome'
+    ]);
+
+    Route::get('environment', [
+        'as' => 'environment',
+        'uses' => 'EnvironmentController@environmentMenu'
+    ]);
+
+    Route::get('environment/wizard', [
+        'as' => 'environmentWizard',
+        'uses' => 'EnvironmentController@environmentWizard'
+    ]);
+
+    Route::post('environment/saveWizard', [
+        'as' => 'environmentSaveWizard',
+        'uses' => 'EnvironmentController@saveWizard'
+    ]);
+
+    Route::get('environment/classic', [
+        'as' => 'environmentClassic',
+        'uses' => 'EnvironmentController@environmentClassic'
+    ]);
+
+    Route::post('environment/saveClassic', [
+        'as' => 'environmentSaveClassic',
+        'uses' => 'EnvironmentController@saveClassic'
+    ]);
+
+    Route::get('requirements', [
+        'as' => 'requirements',
+        'uses' => 'RequirementsController@requirements'
+    ]);
+
+    Route::get('permissions', [
+        'as' => 'permissions',
+        'uses' => 'PermissionsController@permissions'
+    ]);
+
+    Route::get('database', [
+        'as' => 'database',
+        'uses' => 'DatabaseController@database'
+    ]);
+
+    Route::get('final', [
+        'as' => 'final',
+        'uses' => 'FinalController@finish'
+    ]);
+
+});
+
+Route::group(['prefix' => $prefix.'update','as' => 'LaravelUpdater::','namespace' => 'RachidLaasri\LaravelInstaller\Controllers','middleware' => 'web'],function() {
+    Route::group(['middleware' => 'update'], function() {
+
+        Route::get('/', [
+            'as' => 'welcome',
+            'uses' => 'UpdateController@welcome'
+        ]);
+
+        Route::get('overview', [
+            'as' => 'overview',
+            'uses' => 'UpdateController@overview'
+        ]);
+
+        Route::get('database', [
+            'as' => 'database',
+            'uses' => 'UpdateController@database'
+        ]);
+    });
+
+    // This needs to be out of the middleware because right after the migration has been
+    // run, the middleware sends a 404.
+    Route::get('final', [
+        'as' => 'final',
+        'uses' => 'UpdateController@finish'
+    ]);
+});
diff --git a/freescout-dist/overrides/ramsey/uuid/src/Uuid.php b/freescout-dist/overrides/ramsey/uuid/src/Uuid.php
new file mode 100644
index 0000000..c518f97
--- /dev/null
+++ b/freescout-dist/overrides/ramsey/uuid/src/Uuid.php
@@ -0,0 +1,782 @@
+
+ * @license http://opensource.org/licenses/MIT MIT
+ * @link https://benramsey.com/projects/ramsey-uuid/ Documentation
+ * @link https://packagist.org/packages/ramsey/uuid Packagist
+ * @link https://github.com/ramsey/uuid GitHub
+ */
+
+namespace Ramsey\Uuid;
+
+use DateTime;
+use Exception;
+use InvalidArgumentException;
+use Ramsey\Uuid\Converter\NumberConverterInterface;
+use Ramsey\Uuid\Codec\CodecInterface;
+use Ramsey\Uuid\Exception\InvalidUuidStringException;
+use Ramsey\Uuid\Exception\UnsatisfiedDependencyException;
+use Ramsey\Uuid\Exception\UnsupportedOperationException;
+use ReturnTypeWillChange;
+
+/**
+ * Represents a universally unique identifier (UUID), according to RFC 4122.
+ *
+ * This class provides immutable UUID objects (the Uuid class) and the static
+ * methods `uuid1()`, `uuid3()`, `uuid4()`, and `uuid5()` for generating version
+ * 1, 3, 4, and 5 UUIDs as specified in RFC 4122.
+ *
+ * If all you want is a unique ID, you should probably call `uuid1()` or `uuid4()`.
+ * Note that `uuid1()` may compromise privacy since it creates a UUID containing
+ * the computer’s network address. `uuid4()` creates a random UUID.
+ *
+ * @link http://tools.ietf.org/html/rfc4122
+ * @link http://en.wikipedia.org/wiki/Universally_unique_identifier
+ * @link http://docs.python.org/3/library/uuid.html
+ * @link http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html
+ */
+class Uuid implements UuidInterface
+{
+    /**
+     * When this namespace is specified, the name string is a fully-qualified domain name.
+     * @link http://tools.ietf.org/html/rfc4122#appendix-C
+     */
+    const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
+
+    /**
+     * When this namespace is specified, the name string is a URL.
+     * @link http://tools.ietf.org/html/rfc4122#appendix-C
+     */
+    const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
+
+    /**
+     * When this namespace is specified, the name string is an ISO OID.
+     * @link http://tools.ietf.org/html/rfc4122#appendix-C
+     */
+    const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
+
+    /**
+     * When this namespace is specified, the name string is an X.500 DN in DER or a text output format.
+     * @link http://tools.ietf.org/html/rfc4122#appendix-C
+     */
+    const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8';
+
+    /**
+     * The nil UUID is special form of UUID that is specified to have all 128 bits set to zero.
+     * @link http://tools.ietf.org/html/rfc4122#section-4.1.7
+     */
+    const NIL = '00000000-0000-0000-0000-000000000000';
+
+    /**
+     * Reserved for NCS compatibility.
+     * @link http://tools.ietf.org/html/rfc4122#section-4.1.1
+     */
+    const RESERVED_NCS = 0;
+
+    /**
+     * Specifies the UUID layout given in RFC 4122.
+     * @link http://tools.ietf.org/html/rfc4122#section-4.1.1
+     */
+    const RFC_4122 = 2;
+
+    /**
+     * Reserved for Microsoft compatibility.
+     * @link http://tools.ietf.org/html/rfc4122#section-4.1.1
+     */
+    const RESERVED_MICROSOFT = 6;
+
+    /**
+     * Reserved for future definition.
+     * @link http://tools.ietf.org/html/rfc4122#section-4.1.1
+     */
+    const RESERVED_FUTURE = 7;
+
+    /**
+     * Regular expression pattern for matching a valid UUID of any variant.
+     */
+    const VALID_PATTERN = '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$';
+
+    /**
+     * Version 1 (time-based) UUID object constant identifier
+     */
+    const UUID_TYPE_TIME = 1;
+
+    /**
+     * Version 2 (identifier-based) UUID object constant identifier
+     */
+    const UUID_TYPE_IDENTIFIER = 2;
+
+    /**
+     * Version 3 (name-based and hashed with MD5) UUID object constant identifier
+     */
+    const UUID_TYPE_HASH_MD5 = 3;
+
+    /**
+     * Version 4 (random) UUID object constant identifier
+     */
+    const UUID_TYPE_RANDOM = 4;
+
+    /**
+     * Version 5 (name-based and hashed with SHA1) UUID object constant identifier
+     */
+    const UUID_TYPE_HASH_SHA1 = 5;
+
+    /**
+     * The factory to use when creating UUIDs.
+     * @var UuidFactoryInterface
+     */
+    private static $factory = null;
+
+    /**
+     * The codec to use when encoding or decoding UUID strings.
+     * @var CodecInterface
+     */
+    protected $codec;
+
+    /**
+     * The fields that make up this UUID.
+     *
+     * This is initialized to the nil value.
+     *
+     * @var array
+     * @see UuidInterface::getFieldsHex()
+     */
+    protected $fields = [
+        'time_low' => '00000000',
+        'time_mid' => '0000',
+        'time_hi_and_version' => '0000',
+        'clock_seq_hi_and_reserved' => '00',
+        'clock_seq_low' => '00',
+        'node' => '000000000000',
+    ];
+
+    /**
+     * The number converter to use for converting hex values to/from integers.
+     * @var NumberConverterInterface
+     */
+    protected $converter;
+
+    /**
+     * Creates a universally unique identifier (UUID) from an array of fields.
+     *
+     * Unless you're making advanced use of this library to generate identifiers
+     * that deviate from RFC 4122, you probably do not want to instantiate a
+     * UUID directly. Use the static methods, instead:
+     *
+     * ```
+     * use Ramsey\Uuid\Uuid;
+     *
+     * $timeBasedUuid     = Uuid::uuid1();
+     * $namespaceMd5Uuid  = Uuid::uuid3(Uuid::NAMESPACE_URL, 'http://php.net/');
+     * $randomUuid        = Uuid::uuid4();
+     * $namespaceSha1Uuid = Uuid::uuid5(Uuid::NAMESPACE_URL, 'http://php.net/');
+     * ```
+     *
+     * @param array $fields An array of fields from which to construct a UUID;
+     *     see {@see \Ramsey\Uuid\UuidInterface::getFieldsHex()} for array structure.
+     * @param NumberConverterInterface $converter The number converter to use
+     *     for converting hex values to/from integers.
+     * @param CodecInterface $codec The codec to use when encoding or decoding
+     *     UUID strings.
+     */
+    public function __construct(
+        array $fields,
+        NumberConverterInterface $converter,
+        CodecInterface $codec
+    ) {
+        $this->fields = $fields;
+        $this->codec = $codec;
+        $this->converter = $converter;
+    }
+
+    /**
+     * Converts this UUID object to a string when the object is used in any
+     * string context.
+     *
+     * @return string
+     * @link http://www.php.net/manual/en/language.oop5.magic.php#object.tostring
+     */
+    public function __toString()
+    {
+        return $this->toString();
+    }
+
+    /**
+     * Converts this UUID object to a string when the object is serialized
+     * with `json_encode()`
+     *
+     * @return string
+     * @link http://php.net/manual/en/class.jsonserializable.php
+     * : mixed
+     */
+    #[\ReturnTypeWillChange]
+    public function jsonSerialize()
+    {
+        return $this->toString();
+    }
+
+    /**
+     * Converts this UUID object to a string when the object is serialized
+     * with `serialize()`
+     *
+     * @return string
+     * @link http://php.net/manual/en/class.serializable.php
+     */
+    #[ReturnTypeWillChange]
+    public function serialize()
+    {
+        return $this->toString();
+    }
+
+    /**
+     * @return array{string: string}
+     */
+    #[ReturnTypeWillChange]
+    public function __serialize()
+    {
+        return ['string' => $this->toString()];
+    }
+
+    /**
+     * Re-constructs the object from its serialized form.
+     *
+     * @param string $serialized
+     * @link http://php.net/manual/en/class.serializable.php
+     * @throws InvalidUuidStringException
+     */
+    #[ReturnTypeWillChange]
+    public function unserialize($serialized)
+    {
+        $uuid = self::fromString($serialized);
+        $this->codec = $uuid->codec;
+        $this->converter = $uuid->converter;
+        $this->fields = $uuid->fields;
+    }
+
+    /**
+     * @param array{string: string} $serialized
+     * @return void
+     * @throws InvalidUuidStringException
+     */
+    #[ReturnTypeWillChange]
+    public function __unserialize(array $serialized)
+    {
+        // @codeCoverageIgnoreStart
+        if (!isset($serialized['string'])) {
+            throw new InvalidUuidStringException();
+        }
+        // @codeCoverageIgnoreEnd
+
+        $this->unserialize($serialized['string']);
+    }
+
+    public function compareTo(UuidInterface $other)
+    {
+        if ($this->getMostSignificantBitsHex() < $other->getMostSignificantBitsHex()) {
+            return -1;
+        }
+
+        if ($this->getMostSignificantBitsHex() > $other->getMostSignificantBitsHex()) {
+            return 1;
+        }
+
+        if ($this->getLeastSignificantBitsHex() < $other->getLeastSignificantBitsHex()) {
+            return -1;
+        }
+
+        if ($this->getLeastSignificantBitsHex() > $other->getLeastSignificantBitsHex()) {
+            return 1;
+        }
+
+        return 0;
+    }
+
+    public function equals($other)
+    {
+        if (!$other instanceof UuidInterface) {
+            return false;
+        }
+
+        return $this->compareTo($other) == 0;
+    }
+
+    public function getBytes()
+    {
+        return $this->codec->encodeBinary($this);
+    }
+
+    /**
+     * Returns the high field of the clock sequence multiplexed with the variant
+     * (bits 65-72 of the UUID).
+     *
+     * @return int Unsigned 8-bit integer value of clock_seq_hi_and_reserved
+     */
+    public function getClockSeqHiAndReserved()
+    {
+        return hexdec($this->getClockSeqHiAndReservedHex());
+    }
+
+    public function getClockSeqHiAndReservedHex()
+    {
+        return $this->fields['clock_seq_hi_and_reserved'];
+    }
+
+    /**
+     * Returns the low field of the clock sequence (bits 73-80 of the UUID).
+     *
+     * @return int Unsigned 8-bit integer value of clock_seq_low
+     */
+    public function getClockSeqLow()
+    {
+        return hexdec($this->getClockSeqLowHex());
+    }
+
+    public function getClockSeqLowHex()
+    {
+        return $this->fields['clock_seq_low'];
+    }
+
+    /**
+     * Returns the clock sequence value associated with this UUID.
+     *
+     * For UUID version 1, the clock sequence is used to help avoid
+     * duplicates that could arise when the clock is set backwards in time
+     * or if the node ID changes.
+     *
+     * For UUID version 3 or 5, the clock sequence is a 14-bit value
+     * constructed from a name as described in RFC 4122, Section 4.3.
+     *
+     * For UUID version 4, clock sequence is a randomly or pseudo-randomly
+     * generated 14-bit value as described in RFC 4122, Section 4.4.
+     *
+     * @return int Unsigned 14-bit integer value of clock sequence
+     * @link http://tools.ietf.org/html/rfc4122#section-4.1.5
+     */
+    public function getClockSequence()
+    {
+        return ($this->getClockSeqHiAndReserved() & 0x3f) << 8 | $this->getClockSeqLow();
+    }
+
+    public function getClockSequenceHex()
+    {
+        return sprintf('%04x', $this->getClockSequence());
+    }
+
+    public function getNumberConverter()
+    {
+        return $this->converter;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getDateTime()
+    {
+        if ($this->getVersion() != 1) {
+            throw new UnsupportedOperationException('Not a time-based UUID');
+        }
+
+        $unixTimeNanoseconds = $this->getTimestamp() - 0x01b21dd213814000;
+        $unixTime = ($unixTimeNanoseconds - $unixTimeNanoseconds % 1e7) / 1e7;
+
+        return new DateTime("@{$unixTime}");
+    }
+
+    /**
+     * Returns an array of the fields of this UUID, with keys named according
+     * to the RFC 4122 names for the fields.
+     *
+     * * **time_low**: The low field of the timestamp, an unsigned 32-bit integer
+     * * **time_mid**: The middle field of the timestamp, an unsigned 16-bit integer
+     * * **time_hi_and_version**: The high field of the timestamp multiplexed with
+     *   the version number, an unsigned 16-bit integer
+     * * **clock_seq_hi_and_reserved**: The high field of the clock sequence
+     *   multiplexed with the variant, an unsigned 8-bit integer
+     * * **clock_seq_low**: The low field of the clock sequence, an unsigned
+     *   8-bit integer
+     * * **node**: The spatially unique node identifier, an unsigned 48-bit
+     *   integer
+     *
+     * @return array The UUID fields represented as integer values
+     * @link http://tools.ietf.org/html/rfc4122#section-4.1.2
+     */
+    public function getFields()
+    {
+        return [
+            'time_low' => $this->getTimeLow(),
+            'time_mid' => $this->getTimeMid(),
+            'time_hi_and_version' => $this->getTimeHiAndVersion(),
+            'clock_seq_hi_and_reserved' => $this->getClockSeqHiAndReserved(),
+            'clock_seq_low' => $this->getClockSeqLow(),
+            'node' => $this->getNode(),
+        ];
+    }
+
+    public function getFieldsHex()
+    {
+        return $this->fields;
+    }
+
+    public function getHex()
+    {
+        return str_replace('-', '', $this->toString());
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getInteger()
+    {
+        return $this->converter->fromHex($this->getHex());
+    }
+
+    /**
+     * Returns the least significant 64 bits of this UUID's 128 bit value.
+     *
+     * @return mixed Converted representation of the unsigned 64-bit integer value
+     * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present
+     */
+    public function getLeastSignificantBits()
+    {
+        return $this->converter->fromHex($this->getLeastSignificantBitsHex());
+    }
+
+    public function getLeastSignificantBitsHex()
+    {
+        return sprintf(
+            '%02s%02s%012s',
+            $this->fields['clock_seq_hi_and_reserved'],
+            $this->fields['clock_seq_low'],
+            $this->fields['node']
+        );
+    }
+
+    /**
+     * Returns the most significant 64 bits of this UUID's 128 bit value.
+     *
+     * @return mixed Converted representation of the unsigned 64-bit integer value
+     * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present
+     */
+    public function getMostSignificantBits()
+    {
+        return $this->converter->fromHex($this->getMostSignificantBitsHex());
+    }
+
+    public function getMostSignificantBitsHex()
+    {
+        return sprintf(
+            '%08s%04s%04s',
+            $this->fields['time_low'],
+            $this->fields['time_mid'],
+            $this->fields['time_hi_and_version']
+        );
+    }
+
+    /**
+     * Returns the node value associated with this UUID
+     *
+     * For UUID version 1, the node field consists of an IEEE 802 MAC
+     * address, usually the host address. For systems with multiple IEEE
+     * 802 addresses, any available one can be used. The lowest addressed
+     * octet (octet number 10) contains the global/local bit and the
+     * unicast/multicast bit, and is the first octet of the address
+     * transmitted on an 802.3 LAN.
+     *
+     * For systems with no IEEE address, a randomly or pseudo-randomly
+     * generated value may be used; see RFC 4122, Section 4.5. The
+     * multicast bit must be set in such addresses, in order that they
+     * will never conflict with addresses obtained from network cards.
+     *
+     * For UUID version 3 or 5, the node field is a 48-bit value constructed
+     * from a name as described in RFC 4122, Section 4.3.
+     *
+     * For UUID version 4, the node field is a randomly or pseudo-randomly
+     * generated 48-bit value as described in RFC 4122, Section 4.4.
+     *
+     * @return int Unsigned 48-bit integer value of node
+     * @link http://tools.ietf.org/html/rfc4122#section-4.1.6
+     */
+    public function getNode()
+    {
+        return hexdec($this->getNodeHex());
+    }
+
+    public function getNodeHex()
+    {
+        return $this->fields['node'];
+    }
+
+    /**
+     * Returns the high field of the timestamp multiplexed with the version
+     * number (bits 49-64 of the UUID).
+     *
+     * @return int Unsigned 16-bit integer value of time_hi_and_version
+     */
+    public function getTimeHiAndVersion()
+    {
+        return hexdec($this->getTimeHiAndVersionHex());
+    }
+
+    public function getTimeHiAndVersionHex()
+    {
+        return $this->fields['time_hi_and_version'];
+    }
+
+    /**
+     * Returns the low field of the timestamp (the first 32 bits of the UUID).
+     *
+     * @return int Unsigned 32-bit integer value of time_low
+     */
+    public function getTimeLow()
+    {
+        return hexdec($this->getTimeLowHex());
+    }
+
+    public function getTimeLowHex()
+    {
+        return $this->fields['time_low'];
+    }
+
+    /**
+     * Returns the middle field of the timestamp (bits 33-48 of the UUID).
+     *
+     * @return int Unsigned 16-bit integer value of time_mid
+     */
+    public function getTimeMid()
+    {
+        return hexdec($this->getTimeMidHex());
+    }
+
+    public function getTimeMidHex()
+    {
+        return $this->fields['time_mid'];
+    }
+
+    /**
+     * Returns the timestamp value associated with this UUID.
+     *
+     * The 60 bit timestamp value is constructed from the time_low,
+     * time_mid, and time_hi fields of this UUID. The resulting
+     * timestamp is measured in 100-nanosecond units since midnight,
+     * October 15, 1582 UTC.
+     *
+     * The timestamp value is only meaningful in a time-based UUID, which
+     * has version type 1. If this UUID is not a time-based UUID then
+     * this method throws UnsupportedOperationException.
+     *
+     * @return int Unsigned 60-bit integer value of the timestamp
+     * @throws UnsupportedOperationException If this UUID is not a version 1 UUID
+     * @link http://tools.ietf.org/html/rfc4122#section-4.1.4
+     */
+    public function getTimestamp()
+    {
+        if ($this->getVersion() != 1) {
+            throw new UnsupportedOperationException('Not a time-based UUID');
+        }
+
+        return hexdec($this->getTimestampHex());
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getTimestampHex()
+    {
+        if ($this->getVersion() != 1) {
+            throw new UnsupportedOperationException('Not a time-based UUID');
+        }
+
+        return sprintf(
+            '%03x%04s%08s',
+            ($this->getTimeHiAndVersion() & 0x0fff),
+            $this->fields['time_mid'],
+            $this->fields['time_low']
+        );
+    }
+
+    public function getUrn()
+    {
+        return 'urn:uuid:' . $this->toString();
+    }
+
+    public function getVariant()
+    {
+        $clockSeq = $this->getClockSeqHiAndReserved();
+
+        if (0 === ($clockSeq & 0x80)) {
+            return self::RESERVED_NCS;
+        }
+
+        if (0 === ($clockSeq & 0x40)) {
+            return self::RFC_4122;
+        }
+
+        if (0 === ($clockSeq & 0x20)) {
+            return self::RESERVED_MICROSOFT;
+        }
+
+        return self::RESERVED_FUTURE;
+    }
+
+    public function getVersion()
+    {
+        if ($this->getVariant() == self::RFC_4122) {
+            return (int) (($this->getTimeHiAndVersion() >> 12) & 0x0f);
+        }
+
+        return null;
+    }
+
+    public function toString()
+    {
+        return $this->codec->encode($this);
+    }
+
+    /**
+     * Returns the currently set factory used to create UUIDs.
+     *
+     * @return UuidFactoryInterface
+     */
+    public static function getFactory()
+    {
+        if (!self::$factory) {
+            self::$factory = new UuidFactory();
+        }
+
+        return self::$factory;
+    }
+
+    /**
+     * Sets the factory used to create UUIDs.
+     *
+     * @param UuidFactoryInterface $factory
+     */
+    public static function setFactory(UuidFactoryInterface $factory)
+    {
+        self::$factory = $factory;
+    }
+
+    /**
+     * Creates a UUID from a byte string.
+     *
+     * @param string $bytes
+     * @return UuidInterface
+     * @throws InvalidUuidStringException
+     * @throws InvalidArgumentException
+     */
+    public static function fromBytes($bytes)
+    {
+        return self::getFactory()->fromBytes($bytes);
+    }
+
+    /**
+     * Creates a UUID from the string standard representation.
+     *
+     * @param string $name A string that specifies a UUID
+     * @return UuidInterface
+     * @throws InvalidUuidStringException
+     */
+    public static function fromString($name)
+    {
+        return self::getFactory()->fromString($name);
+    }
+
+    /**
+     * Creates a UUID from a 128-bit integer string.
+     *
+     * @param string $integer String representation of 128-bit integer
+     * @return UuidInterface
+     * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present
+     * @throws InvalidUuidStringException
+     */
+    public static function fromInteger($integer)
+    {
+        return self::getFactory()->fromInteger($integer);
+    }
+
+    /**
+     * Check if a string is a valid UUID.
+     *
+     * @param string $uuid The string UUID to test
+     * @return boolean
+     */
+    public static function isValid($uuid)
+    {
+        $uuid = str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid);
+
+        if ($uuid == self::NIL) {
+            return true;
+        }
+
+        if (!preg_match('/' . self::VALID_PATTERN . '/D', $uuid)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Generate a version 1 UUID from a host ID, sequence number, and the current time.
+     *
+     * @param int|string $node A 48-bit number representing the hardware address
+     *     This number may be represented as an integer or a hexadecimal string.
+     * @param int $clockSeq A 14-bit number used to help avoid duplicates that
+     *     could arise when the clock is set backwards in time or if the node ID
+     *     changes.
+     * @return UuidInterface
+     * @throws UnsatisfiedDependencyException if called on a 32-bit system and
+     *     `Moontoast\Math\BigNumber` is not present
+     * @throws InvalidArgumentException
+     * @throws Exception if it was not possible to gather sufficient entropy
+     */
+    public static function uuid1($node = null, $clockSeq = null)
+    {
+        return self::getFactory()->uuid1($node, $clockSeq);
+    }
+
+    /**
+     * Generate a version 3 UUID based on the MD5 hash of a namespace identifier
+     * (which is a UUID) and a name (which is a string).
+     *
+     * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID
+     * @param string $name The name to create a UUID for
+     * @return UuidInterface
+     * @throws InvalidUuidStringException
+     */
+    public static function uuid3($ns, $name)
+    {
+        return self::getFactory()->uuid3($ns, $name);
+    }
+
+    /**
+     * Generate a version 4 (random) UUID.
+     *
+     * @return UuidInterface
+     * @throws UnsatisfiedDependencyException if `Moontoast\Math\BigNumber` is not present
+     * @throws InvalidArgumentException
+     * @throws Exception
+     */
+    public static function uuid4()
+    {
+        return self::getFactory()->uuid4();
+    }
+
+    /**
+     * Generate a version 5 UUID based on the SHA-1 hash of a namespace
+     * identifier (which is a UUID) and a name (which is a string).
+     *
+     * @param string|UuidInterface $ns The UUID namespace in which to create the named UUID
+     * @param string $name The name to create a UUID for
+     * @return UuidInterface
+     * @throws InvalidUuidStringException
+     */
+    public static function uuid5($ns, $name)
+    {
+        return self::getFactory()->uuid5($ns, $name);
+    }
+}
diff --git a/freescout-dist/overrides/rap2hpoutre/laravel-log-viewer/src/Rap2hpoutre/LaravelLogViewer/LaravelLogViewer.php b/freescout-dist/overrides/rap2hpoutre/laravel-log-viewer/src/Rap2hpoutre/LaravelLogViewer/LaravelLogViewer.php
new file mode 100644
index 0000000..f03ffb8
--- /dev/null
+++ b/freescout-dist/overrides/rap2hpoutre/laravel-log-viewer/src/Rap2hpoutre/LaravelLogViewer/LaravelLogViewer.php
@@ -0,0 +1,376 @@
+level = new Level();
+        $this->pattern = new Pattern();
+        $this->storage_path = function_exists('config') ? config('logviewer.storage_path', storage_path('logs')) : storage_path('logs');
+
+    }
+
+    /**
+     * @param string $folder
+     */
+    public function setFolder($folder)
+    {
+        if (app('files')->exists($folder)) {
+          
+            $this->folder = $folder;
+        }
+        else if(is_array($this->storage_path)) {
+           
+            foreach ($this->storage_path as $value) {
+                
+                $logsPath = $value . '/' . $folder;
+               
+                if (app('files')->exists($logsPath)) {
+                    $this->folder = $folder;
+                    break;
+                }
+            }
+        } else {
+            
+                $logsPath = $this->storage_path . '/' . $folder;
+                if (app('files')->exists($logsPath)) {
+                    $this->folder = $folder;
+                }
+        
+        }
+    }
+
+    /**
+     * @param string $file
+     * @throws \Exception
+     */
+    public function setFile($file)
+    {
+        $file = $this->pathToLogFile($file);
+
+        if (app('files')->exists($file)) {
+            $this->file = $file;
+        }
+    }
+
+    /**
+     * @param string $file
+     * @return string
+     * @throws \Exception
+     */
+    public function pathToLogFile($file)
+    {
+
+        if (app('files')->exists($file)) { // try the absolute path
+      
+            return $file;
+        }
+        if (is_array($this->storage_path)) {
+     
+            foreach ($this->storage_path as $folder) {
+                if (app('files')->exists($folder . '/' . $file)) { // try the absolute path
+                    $file = $folder . '/' . $file;
+                    break;
+                }
+            }
+            return $file;
+        }
+
+        $logsPath = $this->storage_path;
+        $logsPath .= ($this->folder) ? '/' . $this->folder : '';
+        $file = $logsPath . '/' . $file;
+        // check if requested file is really in the logs directory
+        if (dirname($file) !== $logsPath) {
+            throw new \Exception('No such log file: '.$file);
+        }
+        
+        return $file;
+    }
+
+    /**
+     * @return string
+     */
+    public function getFolderName()
+    {
+        return $this->folder;
+    }
+
+    /**
+     * @return string
+     */
+    public function getFileName()
+    {
+        return basename($this->file ?? '');
+    }
+
+    /**
+     * @return array
+     */
+    public function all()
+    {
+        $log = array();
+
+        if (!$this->file) {
+            $log_file = (!$this->folder) ? $this->getFiles() : $this->getFolderFiles();
+            if (!count($log_file)) {
+                return [];
+            }
+            $this->file = $log_file[0];
+        }
+
+        $max_file_size = function_exists('config') ? config('logviewer.max_file_size', self::MAX_FILE_SIZE) : self::MAX_FILE_SIZE;
+        if (app('files')->size($this->file) > $max_file_size) {
+            return null;
+        }
+
+        if (!is_readable($this->file)) {
+            return [[
+                'context' => '',
+                'level' => '',
+                'date' => null,
+                'text' => 'Log file "' . $this->file . '" not readable',
+                'stack' => '',
+            ]];
+        }
+
+        $file = app('files')->get($this->file);
+
+        preg_match_all($this->pattern->getPattern('logs'), $file, $headings);
+
+        if (!is_array($headings)) {
+            return $log;
+        }
+
+        $log_data = preg_split($this->pattern->getPattern('logs'), $file);
+
+        if ($log_data[0] < 1) {
+            array_shift($log_data);
+        }
+
+        foreach ($headings as $h) {
+            for ($i = 0, $j = count($h); $i < $j; $i++) {
+                foreach ($this->level->all() as $level) {
+                    if (strpos(strtolower($h[$i]), '.' . $level) || strpos(strtolower($h[$i]), $level . ':')) {
+
+                        preg_match($this->pattern->getPattern('current_log', 0) . $level . $this->pattern->getPattern('current_log', 1), $h[$i], $current);
+                        if (!isset($current[4])) {
+                            continue;
+                        }
+
+                        $log[] = array(
+                            'context' => $current[3],
+                            'level' => $level,
+                            'folder' => $this->folder,
+                            'level_class' => $this->level->cssClass($level),
+                            'level_img' => $this->level->img($level),
+                            'date' => $current[1],
+                            'text' => $current[4],
+                            'in_file' => isset($current[5]) ? $current[5] : null,
+                            'stack' => preg_replace("/^\n*/", '', $log_data[$i])
+                        );
+                    }
+                }
+            }
+        }
+
+        if (empty($log)) {
+
+            $lines = explode(PHP_EOL, $file);
+            $log = [];
+
+            foreach ($lines as $key => $line) {
+                $log[] = [
+                    'context' => '',
+                    'level' => '',
+                    'folder' => '',
+                    'level_class' => '',
+                    'level_img' => '',
+                    'date' => $key + 1,
+                    'text' => $line,
+                    'in_file' => null,
+                    'stack' => '',
+                ];
+            }
+        }
+
+        return array_reverse($log);
+    }
+
+    /**Creates a multidimensional array
+	 * of subdirectories and files
+	 *
+	 * @param null $path
+	 *
+	 * @return array
+	 */
+    public function foldersAndFiles($path = null)
+    {
+	    $contents = array();
+	    $dir = $path ? $path : $this->storage_path;
+	    foreach (scandir($dir) as $node) {
+		    if ($node == '.' || $node == '..') continue;
+		    $path = $dir . '\\' . $node;
+		    if (is_dir($path)) {
+			    $contents[$path] = $this->foldersAndFiles($path);
+		    } else {
+			    $contents[] = $path;
+		    }
+	    }
+
+	    return $contents;
+    }
+
+   /**Returns an array of
+	 * all subdirectories of specified directory
+	 *
+	 * @param string $folder
+	 *
+	 * @return array
+	 */
+    public function getFolders($folder = '')
+    {
+	    $folders = [];
+	    $listObject = new \RecursiveIteratorIterator(
+		    new \RecursiveDirectoryIterator($this->storage_path.'/'.$folder, \RecursiveDirectoryIterator::SKIP_DOTS),
+		    \RecursiveIteratorIterator::CHILD_FIRST
+	    );
+	    foreach ($listObject as $fileinfo) {
+		    if($fileinfo->isDir()) $folders[] = $fileinfo->getRealPath();
+	    }
+	    return $folders;
+    }
+
+
+    /**
+     * @param bool $basename
+     * @return array
+     */
+    public function getFolderFiles($basename = false)
+    {
+        return $this->getFiles($basename, $this->folder);
+    }
+
+    /**
+     * @param bool $basename
+     * @param string $folder
+     * @return array
+     */
+    public function getFiles($basename = false, $folder = '')
+    {
+        $files = [];
+	    $pattern = function_exists('config') ? config('logviewer.pattern', '*.log') : '*.log';
+	    $fullPath = $this->storage_path.'/'.$folder;
+
+	    $listObject = new \RecursiveIteratorIterator(
+		    new \RecursiveDirectoryIterator($fullPath, \RecursiveDirectoryIterator::SKIP_DOTS),
+		    \RecursiveIteratorIterator::CHILD_FIRST
+	    );
+
+	    foreach ($listObject as $fileinfo) {
+		    if(!$fileinfo->isDir() && strtolower(pathinfo($fileinfo->getRealPath(), PATHINFO_EXTENSION)) == explode('.', $pattern)[1])
+			    $files[] = $basename ? basename($fileinfo->getRealPath()) : $fileinfo->getRealPath();
+	    }
+	    return $files;
+
+    }
+
+    /**
+	 * @return string
+	 */
+    public function getStoragePath()
+    {
+    	return $this->storage_path;
+    }
+
+	/**
+	 * @param $path
+	 *
+	 * @return void
+	 */
+	public function setStoragePath($path)
+	{
+		$this->storage_path = $path;
+	}
+
+    public static function directoryTreeStructure($storage_path, array $array)
+    {
+	    foreach ($array as $k => $v) {
+		    if(is_dir( $k )) {
+
+			    $exploded = explode( "\\", $k );
+			    $show = last( $exploded );
+
+			    echo '';
+
+			    if ( is_array( $v ) ) {
+				    self::directoryTreeStructure( $storage_path, $v );
+			    }
+
+		    }
+		    else {
+
+			    $exploded = explode( "\\", $v );
+			    $show2 = last( $exploded );
+			    $folder = str_replace( $storage_path, "", rtrim( str_replace( $show2, "", $v ), "\\" ) );
+			    $file = $v;
+
+
+			   echo '';
+
+		    }
+	    }
+
+        return;
+    }
+
+
+}
diff --git a/freescout-dist/overrides/spatie/string/src/Str.php b/freescout-dist/overrides/spatie/string/src/Str.php
new file mode 100644
index 0000000..026c682
--- /dev/null
+++ b/freescout-dist/overrides/spatie/string/src/Str.php
@@ -0,0 +1,460 @@
+string = (string) $string;
+    }
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->string;
+    }
+
+    /**
+     * Get the string between the given start and end.
+     *
+     * @param $start
+     * @param $end
+     *
+     * @return \Spatie\String\Str
+     */
+    public function between($start, $end)
+    {
+        if ($start == '' && $end == '') {
+            return $this;
+        }
+
+        if ($start != '' && strpos($this->string, $start) === false) {
+            return new static();
+        }
+
+        if ($end != '' && strpos($this->string, $end) === false) {
+            return new static();
+        }
+
+        if ($start == '') {
+            return new static(substr($this->string, 0, strpos($this->string, $end)));
+        }
+
+        if ($end == '') {
+            return new static(substr($this->string, strpos($this->string, $start) + strlen($start)));
+        }
+
+        $stringWithoutStart = explode($start, $this->string)[1];
+
+        $middle = explode($end, $stringWithoutStart)[0];
+
+        return new static($middle);
+    }
+
+    /**
+     * Convert the string to uppercase.
+     *
+     * @return \Spatie\String\Str
+     */
+    public function toUpper()
+    {
+        return new static(strtoupper($this->string));
+    }
+
+    /**
+     * Convert the string to lowercase.
+     *
+     * @return \Spatie\String\Str
+     */
+    public function toLower()
+    {
+        return new static(strtolower($this->string));
+    }
+
+    /**
+     * Shortens a string in a pretty way. It will clean it by trimming
+     * it, remove all double spaces and html. If the string is then still
+     * longer than the specified $length it will be shortened. The end
+     * of the string is always a full word concatinated with the
+     * specified moreTextIndicator.
+     *
+     * @param int    $length
+     * @param string $moreTextIndicator
+     *
+     * @return \Spatie\String\Str
+     */
+    public function tease($length = 200, $moreTextIndicator = '...')
+    {
+        $sanitizedString = $this->sanitizeForTease($this->string);
+
+        if (strlen($sanitizedString) == 0) {
+            return new static();
+        }
+
+        if (strlen($sanitizedString) <= $length) {
+            return new static($sanitizedString);
+        }
+
+        $ww = wordwrap($sanitizedString, $length, "\n");
+        $shortenedString = substr($ww, 0, strpos($ww, "\n")).$moreTextIndicator;
+
+        return new static($shortenedString);
+    }
+
+    /**
+     * Sanitize the string for teasing.
+     *
+     * @param $string
+     *
+     * @return string
+     */
+    private function sanitizeForTease($string)
+    {
+        $string = trim($string);
+
+        //remove html
+        $string = strip_tags($string);
+
+        //replace multiple spaces
+        $string = preg_replace("/\s+/", ' ', $string);
+
+        return $string;
+    }
+
+    /**
+     * Replace the first occurrence of a string.
+     *
+     * @param $search
+     * @param $replace
+     *
+     * @return \Spatie\String\Str
+     */
+    public function replaceFirst($search, $replace)
+    {
+        if ($search == '') {
+            return $this;
+        }
+
+        $position = strpos($this->string, $search);
+
+        if ($position === false) {
+            return $this;
+        }
+
+        $resultString = substr_replace($this->string, $replace, $position, strlen($search));
+
+        return new static($resultString);
+    }
+
+    /**
+     * Replace the last occurrence of a string.
+     *
+     * @param $search
+     * @param $replace
+     *
+     * @return \Spatie\String\Str
+     */
+    public function replaceLast($search, $replace)
+    {
+        if ($search == '') {
+            return $this;
+        }
+
+        $position = strrpos($this->string, $search);
+
+        if ($position === false) {
+            return $this;
+        }
+
+        $resultString = substr_replace($this->string, $replace, $position, strlen($search));
+
+        return new static($resultString);
+    }
+
+    /**
+     * Prefix a string.
+     *
+     * @param $string
+     *
+     * @return \Spatie\String\Str
+     */
+    public function prefix($string)
+    {
+        return new static($string.$this->string);
+    }
+
+    /**
+     * Suffix a string.
+     *
+     * @param $string
+     *
+     * @return \Spatie\String\Str
+     */
+    public function suffix($string)
+    {
+        return new static($this->string.$string);
+    }
+
+    /**
+     * Concatenate a string.
+     *
+     * @param $string
+     *
+     * @return \Spatie\String\Str
+     */
+    public function concat($string)
+    {
+        return $this->suffix($string);
+    }
+
+    /**
+     * Get the possessive version of a string.
+     *
+     * @return \Spatie\String\Str
+     */
+    public function possessive()
+    {
+        if ($this->string == '') {
+            return new static();
+        }
+
+        $noApostropheEdgeCases = ['it'];
+
+        if (in_array($this->string, $noApostropheEdgeCases)) {
+            return new static($this->string.'s');
+        }
+
+        return new static($this->string.'\''.($this->string[strlen($this->string) - 1] != 's' ? 's' : ''));
+    }
+
+    /**
+     * Get a segment from a string based on a delimiter.
+     * Returns an empty string when the offset doesn't exist.
+     * Use a negative index to start counting from the last element.
+     *
+     * @param string $delimiter
+     * @param int    $index
+     *
+     * @return \Spatie\String\Str
+     */
+    public function segment($delimiter, $index)
+    {
+        $segments = explode($delimiter, $this->string);
+
+        if ($index < 0) {
+            $segments = array_reverse($segments);
+            $index = abs($index) - 1;
+        }
+
+        $segment = isset($segments[$index]) ? $segments[$index] : '';
+
+        return new static($segment);
+    }
+
+    /**
+     * Get the first segment from a string based on a delimiter.
+     *
+     * @param string $delimiter
+     *
+     * @return \Spatie\String\Str
+     */
+    public function firstSegment($delimiter)
+    {
+        return (new static($this->string))->segment($delimiter, 0);
+    }
+
+    /**
+     * Get the last segment from a string based on a delimiter.
+     *
+     * @param string $delimiter
+     *
+     * @return \Spatie\String\Str
+     */
+    public function lastSegment($delimiter)
+    {
+        return (new static($this->string))->segment($delimiter, -1);
+    }
+
+    /**
+     * Pop (remove) the last segment of a string based on a delimiter.
+     *
+     * @param string $delimiter
+     *
+     * @return \Spatie\String\Str
+     */
+    public function pop($delimiter)
+    {
+        return (new static($this->string))->replaceLast($delimiter.$this->lastSegment($delimiter), '');
+    }
+
+    /**
+     * Strip whitespace (or other characters) from the beginning and end of a string.
+     *
+     * @param string $characterMask
+     *
+     * @return \Spatie\String\Str
+     */
+    public function trim($characterMask = " \t\n\r\0\x0B")
+    {
+        return new static(trim($this->string, $characterMask));
+    }
+
+    /**
+     * Alias for find.
+     *
+     * @param array|string $needle
+     * @param bool         $caseSensitive
+     * @param bool         $absolute
+     *
+     * @return bool
+     */
+    public function contains($needle, $caseSensitive = false, $absolute = false)
+    {
+        return $this->find($needle, $caseSensitive, $absolute);
+    }
+
+    /**
+     * Unknown methods calls will be handled by various integrations.
+     *
+     * @param $method
+     * @param $args
+     *
+     * @throws UnknownFunctionException
+     *
+     * @return mixed|\Spatie\String\Str
+     */
+    public function __call($method, $args)
+    {
+        $underscore = new Underscore();
+
+        if ($underscore->isSupportedMethod($method)) {
+            return $underscore->call($this, $method, $args);
+        }
+
+        throw new UnknownFunctionException(sprintf('String function %s does not exist', $method));
+    }
+
+    /**
+     * Whether a offset exists.
+     *
+     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
+     *
+     * @param mixed $offset An offset to check for.
+     *
+     * @return bool true on success or false on failure.
+     *              The return value will be casted to boolean if non-boolean was returned.
+     */
+    public function offsetExists($offset): bool
+    {
+        return strlen($this->string) >= ($offset + 1);
+    }
+
+    /**
+     * Offset to retrieve.
+     *
+     * @link http://php.net/manual/en/arrayaccess.offsetget.php
+     *
+     * @param mixed $offset The offset to retrieve.
+     *
+     * @return mixed Can return all value types.
+     * : mixed
+     */
+    #[\ReturnTypeWillChange]
+    public function offsetGet($offset)
+    {
+        $character = substr($this->string, $offset, 1);
+
+        return new static($character ?: '');
+    }
+
+    /**
+     * Offset to set.
+     *
+     * @link http://php.net/manual/en/arrayaccess.offsetset.php
+     *
+     * @param mixed $offset The offset to assign the value to.
+     * @param mixed $value  The value to set.
+     */
+    public function offsetSet($offset, $value): void
+    {
+        $this->string[$offset] = $value;
+    }
+
+    /**
+     * Offset to unset.
+     *
+     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
+     *
+     * @param mixed $offset The offset to unset.
+     *
+     * @throws UnsetOffsetException
+     */
+    public function offsetUnset($offset): void
+    {
+        throw new UnsetOffsetException();
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php
new file mode 100644
index 0000000..1f94360
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php
@@ -0,0 +1,56 @@
+createDependenciesFor('mime.attachment'));
+        
+        // call_user_func_array(
+        //     [$this, 'Swift_Mime_Attachment::__construct'],
+        //     Swift_DependencyContainer::getInstance()
+        //         ->createDependenciesFor('mime.attachment')
+        //     );
+
+        $this->setBody($data, $contentType);
+        $this->setFilename($filename);
+    }
+
+    /**
+     * Create a new Attachment from a filesystem path.
+     *
+     * @param string $path
+     * @param string $contentType optional
+     *
+     * @return Swift_Mime_Attachment
+     */
+    public static function fromPath($path, $contentType = null)
+    {
+        return (new self())->setFile(
+            new Swift_ByteStream_FileByteStream($path),
+            $contentType
+        );
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/EmbeddedFile.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/EmbeddedFile.php
new file mode 100644
index 0000000..2afc031
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/EmbeddedFile.php
@@ -0,0 +1,55 @@
+createDependenciesFor('mime.embeddedfile'));
+
+        // call_user_func_array(
+        //     [$this, 'Swift_Mime_EmbeddedFile::__construct'],
+        //     Swift_DependencyContainer::getInstance()
+        //         ->createDependenciesFor('mime.embeddedfile')
+        //     );
+
+        $this->setBody($data);
+        $this->setFilename($filename);
+        if ($contentType) {
+            $this->setContentType($contentType);
+        }
+    }
+
+    /**
+     * Create a new EmbeddedFile from a filesystem path.
+     *
+     * @param string $path
+     *
+     * @return Swift_Mime_EmbeddedFile
+     */
+    public static function fromPath($path)
+    {
+        return (new self())->setFile(new Swift_ByteStream_FileByteStream($path));
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php
new file mode 100644
index 0000000..65f34c1
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php
@@ -0,0 +1,48 @@
+createDependenciesFor('transport.mail')*/
+        //        [new Swift_Transport_SimpleMailInvoker(), new Swift_Events_SimpleEventDispatcher()]
+        //    );
+
+        $this->setExtraParams($extraParams);
+    }
+
+    /**
+     * Create a new MailTransport instance.
+     *
+     * @param string $extraParams To be passed to mail()
+     *
+     * @return Swift_MailTransport
+     */
+    public static function newInstance($extraParams = '-f%s')
+    {
+        return new self($extraParams);
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Message.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Message.php
new file mode 100644
index 0000000..99ac8dd
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Message.php
@@ -0,0 +1,280 @@
+createDependenciesFor('mime.message'));
+        // call_user_func_array(
+        //     [$this, 'Swift_Mime_SimpleMessage::__construct'],
+        //     Swift_DependencyContainer::getInstance()
+        //         ->createDependenciesFor('mime.message')
+        //     );
+
+        if (!isset($charset)) {
+            $charset = Swift_DependencyContainer::getInstance()
+                ->lookup('properties.charset');
+        }
+        $this->setSubject($subject);
+        $this->setBody($body);
+        $this->setCharset($charset);
+        if ($contentType) {
+            $this->setContentType($contentType);
+        }
+    }
+
+    /**
+     * Add a MimePart to this Message.
+     *
+     * @param string|Swift_OutputByteStream $body
+     * @param string                        $contentType
+     * @param string                        $charset
+     *
+     * @return $this
+     */
+    public function addPart($body, $contentType = null, $charset = null)
+    {
+        return $this->attach((new Swift_MimePart($body, $contentType, $charset))->setEncoder($this->getEncoder()));
+    }
+
+    /**
+     * Attach a new signature handler to the message.
+     *
+     * @return $this
+     */
+    public function attachSigner(Swift_Signer $signer)
+    {
+        if ($signer instanceof Swift_Signers_HeaderSigner) {
+            $this->headerSigners[] = $signer;
+        } elseif ($signer instanceof Swift_Signers_BodySigner) {
+            $this->bodySigners[] = $signer;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Detach a signature handler from a message.
+     *
+     * @return $this
+     */
+    public function detachSigner(Swift_Signer $signer)
+    {
+        if ($signer instanceof Swift_Signers_HeaderSigner) {
+            foreach ($this->headerSigners as $k => $headerSigner) {
+                if ($headerSigner === $signer) {
+                    unset($this->headerSigners[$k]);
+
+                    return $this;
+                }
+            }
+        } elseif ($signer instanceof Swift_Signers_BodySigner) {
+            foreach ($this->bodySigners as $k => $bodySigner) {
+                if ($bodySigner === $signer) {
+                    unset($this->bodySigners[$k]);
+
+                    return $this;
+                }
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Clear all signature handlers attached to the message.
+     *
+     * @return $this
+     */
+    public function clearSigners()
+    {
+        $this->headerSigners = [];
+        $this->bodySigners = [];
+
+        return $this;
+    }
+
+    /**
+     * Get this message as a complete string.
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        if (empty($this->headerSigners) && empty($this->bodySigners)) {
+            return parent::toString();
+        }
+
+        $this->saveMessage();
+
+        $this->doSign();
+
+        $string = parent::toString();
+
+        $this->restoreMessage();
+
+        return $string;
+    }
+
+    /**
+     * Write this message to a {@link Swift_InputByteStream}.
+     */
+    public function toByteStream(Swift_InputByteStream $is)
+    {
+        if (empty($this->headerSigners) && empty($this->bodySigners)) {
+            parent::toByteStream($is);
+
+            return;
+        }
+
+        $this->saveMessage();
+
+        $this->doSign();
+
+        parent::toByteStream($is);
+
+        $this->restoreMessage();
+    }
+
+    public function __wakeup()
+    {
+        Swift_DependencyContainer::getInstance()->createDependenciesFor('mime.message');
+    }
+
+    /**
+     * loops through signers and apply the signatures.
+     */
+    protected function doSign()
+    {
+        foreach ($this->bodySigners as $signer) {
+            $altered = $signer->getAlteredHeaders();
+            $this->saveHeaders($altered);
+            $signer->signMessage($this);
+        }
+
+        foreach ($this->headerSigners as $signer) {
+            $altered = $signer->getAlteredHeaders();
+            $this->saveHeaders($altered);
+            $signer->reset();
+
+            $signer->setHeaders($this->getHeaders());
+
+            $signer->startBody();
+            $this->bodyToByteStream($signer);
+            $signer->endBody();
+
+            $signer->addSignature($this->getHeaders());
+        }
+    }
+
+    /**
+     * save the message before any signature is applied.
+     */
+    protected function saveMessage()
+    {
+        $this->savedMessage = ['headers' => []];
+        $this->savedMessage['body'] = $this->getBody();
+        $this->savedMessage['children'] = $this->getChildren();
+        if (count($this->savedMessage['children']) > 0 && '' != $this->getBody()) {
+            $this->setChildren(array_merge([$this->becomeMimePart()], $this->savedMessage['children']));
+            $this->setBody('');
+        }
+    }
+
+    /**
+     * save the original headers.
+     */
+    protected function saveHeaders(array $altered)
+    {
+        foreach ($altered as $head) {
+            $lc = strtolower($head);
+
+            if (!isset($this->savedMessage['headers'][$lc])) {
+                $this->savedMessage['headers'][$lc] = $this->getHeaders()->getAll($head);
+            }
+        }
+    }
+
+    /**
+     * Remove or restore altered headers.
+     */
+    protected function restoreHeaders()
+    {
+        foreach ($this->savedMessage['headers'] as $name => $savedValue) {
+            $headers = $this->getHeaders()->getAll($name);
+
+            foreach ($headers as $key => $value) {
+                if (!isset($savedValue[$key])) {
+                    $this->getHeaders()->remove($name, $key);
+                }
+            }
+        }
+    }
+
+    /**
+     * Restore message body.
+     */
+    protected function restoreMessage()
+    {
+        $this->setBody($this->savedMessage['body']);
+        $this->setChildren($this->savedMessage['children']);
+
+        $this->restoreHeaders();
+        $this->savedMessage = [];
+    }
+
+    /**
+     * Clone Message Signers.
+     *
+     * @see Swift_Mime_SimpleMimeEntity::__clone()
+     */
+    public function __clone()
+    {
+        parent::__clone();
+        foreach ($this->bodySigners as $key => $bodySigner) {
+            $this->bodySigners[$key] = clone $bodySigner;
+        }
+
+        foreach ($this->headerSigners as $key => $headerSigner) {
+            $this->headerSigners[$key] = clone $headerSigner;
+        }
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php
new file mode 100644
index 0000000..2734bfa
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.php
@@ -0,0 +1,101 @@
+= $maxLineLength || 76 < $maxLineLength) {
+            $maxLineLength = 76;
+        }
+
+        $remainder = 0;
+        $base64ReadBufferRemainderBytes = null;
+
+        // To reduce memory usage, the output buffer is streamed to the input buffer like so:
+        //   Output Stream => base64encode => wrap line length => Input Stream
+        // HOWEVER it's important to note that base64_encode() should only be passed whole triplets of data (except for the final chunk of data)
+        // otherwise it will assume the input data has *ended* and it will incorrectly pad/terminate the base64 data mid-stream.
+        // We use $base64ReadBufferRemainderBytes to carry over 1-2 "remainder" bytes from the each chunk from OutputStream and pre-pend those onto the
+        // chunk of bytes read in the next iteration.
+        // When the OutputStream is empty, we must flush any remainder bytes.
+        while (true) {
+            $readBytes = $os->read(8192);
+            $atEOF = (false === $readBytes);
+
+            if ($atEOF) {
+                $streamTheseBytes = $base64ReadBufferRemainderBytes;
+            } else {
+                $streamTheseBytes = $base64ReadBufferRemainderBytes.$readBytes;
+            }
+            $base64ReadBufferRemainderBytes = null;
+            $bytesLength = strlen($streamTheseBytes ?? '');
+
+            if (0 === $bytesLength) { // no data left to encode
+                break;
+            }
+
+            // if we're not on the last block of the ouput stream, make sure $streamTheseBytes ends with a complete triplet of data
+            // and carry over remainder 1-2 bytes to the next loop iteration
+            if (!$atEOF) {
+                $excessBytes = $bytesLength % 3;
+                if (0 !== $excessBytes) {
+                    $base64ReadBufferRemainderBytes = substr($streamTheseBytes, -$excessBytes);
+                    $streamTheseBytes = substr($streamTheseBytes, 0, $bytesLength - $excessBytes);
+                }
+            }
+
+            $encoded = base64_encode($streamTheseBytes);
+            $encodedTransformed = '';
+            $thisMaxLineLength = $maxLineLength - $remainder - $firstLineOffset;
+
+            while ($thisMaxLineLength < strlen($encoded)) {
+                $encodedTransformed .= substr($encoded, 0, $thisMaxLineLength)."\r\n";
+                $firstLineOffset = 0;
+                $encoded = substr($encoded, $thisMaxLineLength);
+                $thisMaxLineLength = $maxLineLength;
+                $remainder = 0;
+            }
+
+            if (0 < $remainingLength = strlen($encoded)) {
+                $remainder += $remainingLength;
+                $encodedTransformed .= $encoded;
+                $encoded = null;
+            }
+
+            $is->write($encodedTransformed);
+
+            if ($atEOF) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Get the name of this encoding scheme.
+     * Returns the string 'base64'.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return 'base64';
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php
new file mode 100644
index 0000000..0889540
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php
@@ -0,0 +1,208 @@
+setContentType('text/plain');
+        if (null !== $charset) {
+            $this->setCharset($charset);
+        }
+    }
+
+    /**
+     * Set the body of this entity, either as a string, or as an instance of
+     * {@link Swift_OutputByteStream}.
+     *
+     * @param mixed  $body
+     * @param string $contentType optional
+     * @param string $charset     optional
+     *
+     * @return $this
+     */
+    public function setBody($body, $contentType = null, $charset = null)
+    {
+        if (isset($charset)) {
+            $this->setCharset($charset);
+        }
+        $body = $this->convertString($body);
+
+        parent::setBody($body, $contentType);
+
+        return $this;
+    }
+
+    /**
+     * Get the character set of this entity.
+     *
+     * @return string
+     */
+    public function getCharset()
+    {
+        return $this->getHeaderParameter('Content-Type', 'charset');
+    }
+
+    /**
+     * Set the character set of this entity.
+     *
+     * @param string $charset
+     *
+     * @return $this
+     */
+    public function setCharset($charset)
+    {
+        $this->setHeaderParameter('Content-Type', 'charset', $charset);
+        if ($charset !== $this->userCharset) {
+            $this->clearCache();
+        }
+        $this->userCharset = $charset;
+        parent::charsetChanged($charset);
+
+        return $this;
+    }
+
+    /**
+     * Get the format of this entity (i.e. flowed or fixed).
+     *
+     * @return string
+     */
+    public function getFormat()
+    {
+        return $this->getHeaderParameter('Content-Type', 'format');
+    }
+
+    /**
+     * Set the format of this entity (flowed or fixed).
+     *
+     * @param string $format
+     *
+     * @return $this
+     */
+    public function setFormat($format)
+    {
+        $this->setHeaderParameter('Content-Type', 'format', $format);
+        $this->userFormat = $format;
+
+        return $this;
+    }
+
+    /**
+     * Test if delsp is being used for this entity.
+     *
+     * @return bool
+     */
+    public function getDelSp()
+    {
+        return 'yes' === $this->getHeaderParameter('Content-Type', 'delsp');
+    }
+
+    /**
+     * Turn delsp on or off for this entity.
+     *
+     * @param bool $delsp
+     *
+     * @return $this
+     */
+    public function setDelSp($delsp = true)
+    {
+        $this->setHeaderParameter('Content-Type', 'delsp', $delsp ? 'yes' : null);
+        $this->userDelSp = $delsp;
+
+        return $this;
+    }
+
+    /**
+     * Get the nesting level of this entity.
+     *
+     * @see LEVEL_TOP, LEVEL_ALTERNATIVE, LEVEL_MIXED, LEVEL_RELATED
+     *
+     * @return int
+     */
+    public function getNestingLevel()
+    {
+        return $this->nestingLevel;
+    }
+
+    /**
+     * Receive notification that the charset has changed on this document, or a
+     * parent document.
+     *
+     * @param string $charset
+     */
+    public function charsetChanged($charset)
+    {
+        $this->setCharset($charset);
+    }
+
+    /** Fix the content-type and encoding of this entity */
+    protected function fixHeaders()
+    {
+        parent::fixHeaders();
+        if (count($this->getChildren())) {
+            $this->setHeaderParameter('Content-Type', 'charset', null);
+            $this->setHeaderParameter('Content-Type', 'format', null);
+            $this->setHeaderParameter('Content-Type', 'delsp', null);
+        } else {
+            $this->setCharset($this->userCharset);
+            $this->setFormat($this->userFormat);
+            $this->setDelSp($this->userDelSp);
+        }
+    }
+
+    /** Set the nesting level of this entity */
+    protected function setNestingLevel($level)
+    {
+        $this->nestingLevel = $level;
+    }
+
+    /** Encode charset when charset is not utf-8 */
+    protected function convertString($string)
+    {
+        $charset = strtolower($this->getCharset() ?? '');
+        if (!in_array($charset, ['utf-8', 'iso-8859-1', 'iso-8859-15', ''])) {
+            // mb_convert_encoding must be the first one to check, since iconv cannot convert some words.
+            if (function_exists('mb_convert_encoding')) {
+                $string = mb_convert_encoding($string, $charset, 'utf-8');
+            } elseif (function_exists('iconv')) {
+                $string = iconv('utf-8//TRANSLIT//IGNORE', $charset, $string);
+            } else {
+                throw new Swift_SwiftException('No suitable convert encoding function (use UTF-8 as your charset or install the mbstring or iconv extension).');
+            }
+
+            return $string;
+        }
+
+        return $string;
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php
new file mode 100644
index 0000000..8923ea1
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php
@@ -0,0 +1,46 @@
+createDependenciesFor('mime.part'));
+        // call_user_func_array(
+        //     [$this, 'Swift_Mime_MimePart::__construct'],
+        //     Swift_DependencyContainer::getInstance()
+        //         ->createDependenciesFor('mime.part')
+        //     );
+
+        if (!isset($charset)) {
+            $charset = Swift_DependencyContainer::getInstance()
+                ->lookup('properties.charset');
+        }
+        $this->setBody($body);
+        $this->setCharset($charset);
+        if ($contentType) {
+            $this->setContentType($contentType);
+        }
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SendmailTransport.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SendmailTransport.php
new file mode 100644
index 0000000..0f7628c
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SendmailTransport.php
@@ -0,0 +1,35 @@
+createDependenciesFor('transport.sendmail'));
+
+        // call_user_func_array(
+        //     [$this, 'Swift_Transport_SendmailTransport::__construct'],
+        //     Swift_DependencyContainer::getInstance()
+        //         ->createDependenciesFor('transport.sendmail')
+        //     );
+
+        $this->setCommand($command);
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php
new file mode 100644
index 0000000..79affab
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php
@@ -0,0 +1,43 @@
+createDependenciesFor('transport.smtp'));
+        // call_user_func_array(
+        //     [$this, 'Swift_Transport_EsmtpTransport::__construct'],
+        //     Swift_DependencyContainer::getInstance()
+        //         ->createDependenciesFor('transport.smtp')
+        //     );
+
+        $this->setHost($host);
+        $this->setPort($port);
+        $this->setEncryption($encryption);
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php
new file mode 100644
index 0000000..690867c
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php
@@ -0,0 +1,28 @@
+setAuthenticators($authenticators);
+    }
+
+    /**
+     * Set the Authenticators which can process a login request.
+     *
+     * @param Swift_Transport_Esmtp_Authenticator[] $authenticators
+     */
+    public function setAuthenticators(array $authenticators)
+    {
+        $this->authenticators = $authenticators;
+    }
+
+    /**
+     * Get the Authenticators which can process a login request.
+     *
+     * @return Swift_Transport_Esmtp_Authenticator[]
+     */
+    public function getAuthenticators()
+    {
+        return $this->authenticators;
+    }
+
+    /**
+     * Set the username to authenticate with.
+     *
+     * @param string $username
+     */
+    public function setUsername($username)
+    {
+        $this->username = $username;
+    }
+
+    /**
+     * Get the username to authenticate with.
+     *
+     * @return string
+     */
+    public function getUsername()
+    {
+        return $this->username;
+    }
+
+    /**
+     * Set the password to authenticate with.
+     *
+     * @param string $password
+     */
+    public function setPassword($password)
+    {
+        $this->password = $password;
+    }
+
+    /**
+     * Get the password to authenticate with.
+     *
+     * @return string
+     */
+    public function getPassword()
+    {
+        return $this->password;
+    }
+
+    /**
+     * Set the auth mode to use to authenticate.
+     *
+     * @param string $mode
+     */
+    public function setAuthMode($mode)
+    {
+        $this->auth_mode = $mode;
+    }
+
+    /**
+     * Get the auth mode to use to authenticate.
+     *
+     * @return string
+     */
+    public function getAuthMode()
+    {
+        return $this->auth_mode;
+    }
+
+    /**
+     * Get the name of the ESMTP extension this handles.
+     *
+     * @return string
+     */
+    public function getHandledKeyword()
+    {
+        return 'AUTH';
+    }
+
+    /**
+     * Set the parameters which the EHLO greeting indicated.
+     *
+     * @param string[] $parameters
+     */
+    public function setKeywordParams(array $parameters)
+    {
+        $this->esmtpParams = $parameters;
+    }
+
+    /**
+     * Runs immediately after a EHLO has been issued.
+     *
+     * @param Swift_Transport_SmtpAgent $agent to read/write
+     */
+    public function afterEhlo(Swift_Transport_SmtpAgent $agent)
+    {
+        if ($this->username) {
+            $count = 0;
+            $errors = [];
+            foreach ($this->getAuthenticatorsForAgent() as $authenticator) {
+                if (in_array(strtolower($authenticator->getAuthKeyword()), array_map('strtolower', $this->esmtpParams))) {
+                    ++$count;
+                    try {
+                        if ($authenticator->authenticate($agent, $this->username, $this->password)) {
+                            return;
+                        }
+                    } catch (Swift_TransportException $e) {
+                        // keep the error message, but tries the other authenticators
+                        $errors[] = [$authenticator->getAuthKeyword(), $e];
+                    }
+                }
+            }
+
+            $message = 'Failed to authenticate on SMTP server with username "'.$this->username.'" using '.$count.' possible authenticators.';
+            foreach ($errors as $error) {
+                $message .= ' Authenticator '.$error[0].' returned '.$error[1].'.';
+            }
+            throw new Swift_TransportException($message);
+        }
+    }
+
+    /**
+     * Not used.
+     */
+    public function getMailParams()
+    {
+        return [];
+    }
+
+    /**
+     * Not used.
+     */
+    public function getRcptParams()
+    {
+        return [];
+    }
+
+    /**
+     * Not used.
+     */
+    public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false)
+    {
+    }
+
+    /**
+     * Returns +1, -1 or 0 according to the rules for usort().
+     *
+     * This method is called to ensure extensions can be execute in an appropriate order.
+     *
+     * @param string $esmtpKeyword to compare with
+     *
+     * @return int
+     */
+    public function getPriorityOver($esmtpKeyword)
+    {
+        return 0;
+    }
+
+    /**
+     * Returns an array of method names which are exposed to the Esmtp class.
+     *
+     * @return string[]
+     */
+    public function exposeMixinMethods()
+    {
+        return ['setUsername', 'getUsername', 'setPassword', 'getPassword', 'setAuthMode', 'getAuthMode'];
+    }
+
+    /**
+     * Not used.
+     */
+    public function resetState()
+    {
+    }
+
+    /**
+     * Returns the authenticator list for the given agent.
+     *
+     * @return array
+     */
+    protected function getAuthenticatorsForAgent()
+    {
+        if (!$mode = strtolower($this->auth_mode ?? '')) {
+            return $this->authenticators;
+        }
+
+        foreach ($this->authenticators as $authenticator) {
+            if (strtolower($authenticator->getAuthKeyword()) == $mode) {
+                return [$authenticator];
+            }
+        }
+
+        throw new Swift_TransportException('Auth mode '.$mode.' is invalid');
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php
new file mode 100644
index 0000000..f4444cf
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php
@@ -0,0 +1,446 @@
+ 'tcp',
+        'host' => 'localhost',
+        'port' => 25,
+        'timeout' => 30,
+        'blocking' => 1,
+        'tls' => false,
+        'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
+        'stream_context_options' => [],
+        ];
+
+    /**
+     * Creates a new EsmtpTransport using the given I/O buffer.
+     *
+     * @param Swift_Transport_EsmtpHandler[] $extensionHandlers
+     * @param string                         $localDomain
+     */
+    public function __construct(Swift_Transport_IoBuffer $buf, array $extensionHandlers, Swift_Events_EventDispatcher $dispatcher, $localDomain = '127.0.0.1', Swift_AddressEncoder $addressEncoder = null)
+    {
+        parent::__construct($buf, $dispatcher, $localDomain, $addressEncoder);
+        $this->setExtensionHandlers($extensionHandlers);
+    }
+
+    /**
+     * Set the host to connect to.
+     *
+     * Literal IPv6 addresses should be wrapped in square brackets.
+     *
+     * @param string $host
+     *
+     * @return $this
+     */
+    public function setHost($host)
+    {
+        $this->params['host'] = $host;
+
+        return $this;
+    }
+
+    /**
+     * Get the host to connect to.
+     *
+     * @return string
+     */
+    public function getHost()
+    {
+        return $this->params['host'];
+    }
+
+    /**
+     * Set the port to connect to.
+     *
+     * @param int $port
+     *
+     * @return $this
+     */
+    public function setPort($port)
+    {
+        $this->params['port'] = (int) $port;
+
+        return $this;
+    }
+
+    /**
+     * Get the port to connect to.
+     *
+     * @return int
+     */
+    public function getPort()
+    {
+        return $this->params['port'];
+    }
+
+    /**
+     * Set the connection timeout.
+     *
+     * @param int $timeout seconds
+     *
+     * @return $this
+     */
+    public function setTimeout($timeout)
+    {
+        $this->params['timeout'] = (int) $timeout;
+        $this->buffer->setParam('timeout', (int) $timeout);
+
+        return $this;
+    }
+
+    /**
+     * Get the connection timeout.
+     *
+     * @return int
+     */
+    public function getTimeout()
+    {
+        return $this->params['timeout'];
+    }
+
+    /**
+     * Set the encryption type (tls or ssl).
+     *
+     * @param string $encryption
+     *
+     * @return $this
+     */
+    public function setEncryption($encryption)
+    {
+        $encryption = strtolower($encryption ?? '');
+        if ('tls' == $encryption) {
+            $this->params['protocol'] = 'tcp';
+            $this->params['tls'] = true;
+        } else {
+            $this->params['protocol'] = $encryption;
+            $this->params['tls'] = false;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Get the encryption type.
+     *
+     * @return string
+     */
+    public function getEncryption()
+    {
+        return $this->params['tls'] ? 'tls' : $this->params['protocol'];
+    }
+
+    /**
+     * Sets the stream context options.
+     *
+     * @param array $options
+     *
+     * @return $this
+     */
+    public function setStreamOptions($options)
+    {
+        $this->params['stream_context_options'] = $options;
+
+        return $this;
+    }
+
+    /**
+     * Returns the stream context options.
+     *
+     * @return array
+     */
+    public function getStreamOptions()
+    {
+        return $this->params['stream_context_options'];
+    }
+
+    /**
+     * Sets the source IP.
+     *
+     * IPv6 addresses should be wrapped in square brackets.
+     *
+     * @param string $source
+     *
+     * @return $this
+     */
+    public function setSourceIp($source)
+    {
+        $this->params['sourceIp'] = $source;
+
+        return $this;
+    }
+
+    /**
+     * Returns the IP used to connect to the destination.
+     *
+     * @return string
+     */
+    public function getSourceIp()
+    {
+        return $this->params['sourceIp'] ?? null;
+    }
+
+    /**
+     * Sets whether SMTP pipelining is enabled.
+     *
+     * By default, support is auto-detected using the PIPELINING SMTP extension.
+     * Use this function to override that in the unlikely event of compatibility
+     * issues.
+     *
+     * @param bool $enabled
+     *
+     * @return $this
+     */
+    public function setPipelining($enabled)
+    {
+        $this->pipelining = $enabled;
+
+        return $this;
+    }
+
+    /**
+     * Returns whether SMTP pipelining is enabled.
+     *
+     * @return bool|null a boolean if pipelining is explicitly enabled or disabled,
+     *                   or null if support is auto-detected.
+     */
+    public function getPipelining()
+    {
+        return $this->pipelining;
+    }
+
+    /**
+     * Set ESMTP extension handlers.
+     *
+     * @param Swift_Transport_EsmtpHandler[] $handlers
+     *
+     * @return $this
+     */
+    public function setExtensionHandlers(array $handlers)
+    {
+        $assoc = [];
+        foreach ($handlers as $handler) {
+            $assoc[$handler->getHandledKeyword()] = $handler;
+        }
+        uasort($assoc, function ($a, $b) {
+            return $a->getPriorityOver($b->getHandledKeyword());
+        });
+        $this->handlers = $assoc;
+        $this->setHandlerParams();
+
+        return $this;
+    }
+
+    /**
+     * Get ESMTP extension handlers.
+     *
+     * @return Swift_Transport_EsmtpHandler[]
+     */
+    public function getExtensionHandlers()
+    {
+        return array_values($this->handlers);
+    }
+
+    /**
+     * Run a command against the buffer, expecting the given response codes.
+     *
+     * If no response codes are given, the response will not be validated.
+     * If codes are given, an exception will be thrown on an invalid response.
+     *
+     * @param string   $command
+     * @param int[]    $codes
+     * @param string[] $failures An array of failures by-reference
+     * @param bool     $pipeline Do not wait for response
+     * @param string   $address  The address, if command is RCPT TO.
+     *
+     * @return string|null The server response, or null if pipelining is enabled
+     */
+    public function executeCommand($command, $codes = [], &$failures = null, $pipeline = false, $address = null)
+    {
+        $failures = (array) $failures;
+        $stopSignal = false;
+        $response = null;
+        foreach ($this->getActiveHandlers() as $handler) {
+            $response = $handler->onCommand(
+                $this, $command, $codes, $failures, $stopSignal
+                );
+            if ($stopSignal) {
+                return $response;
+            }
+        }
+
+        return parent::executeCommand($command, $codes, $failures, $pipeline, $address);
+    }
+
+    /** Mixin handling method for ESMTP handlers */
+    public function __call($method, $args)
+    {
+        foreach ($this->handlers as $handler) {
+            if (in_array(strtolower($method),
+                array_map('strtolower', (array) $handler->exposeMixinMethods())
+                )) {
+                $return = call_user_func_array([$handler, $method], $args);
+                // Allow fluid method calls
+                if (null === $return && 'set' == substr($method, 0, 3)) {
+                    return $this;
+                } else {
+                    return $return;
+                }
+            }
+        }
+        trigger_error('Call to undefined method '.$method, E_USER_ERROR);
+    }
+
+    /** Get the params to initialize the buffer */
+    protected function getBufferParams()
+    {
+        return $this->params;
+    }
+
+    /** Overridden to perform EHLO instead */
+    protected function doHeloCommand()
+    {
+        try {
+            $response = $this->executeCommand(
+                sprintf("EHLO %s\r\n", $this->domain), [250]
+                );
+        } catch (Swift_TransportException $e) {
+            return parent::doHeloCommand();
+        }
+
+        if ($this->params['tls']) {
+            try {
+                $this->executeCommand("STARTTLS\r\n", [220]);
+
+                if (!$this->buffer->startTLS()) {
+                    throw new Swift_TransportException('Unable to connect with TLS encryption');
+                }
+
+                try {
+                    $response = $this->executeCommand(
+                        sprintf("EHLO %s\r\n", $this->domain), [250]
+                        );
+                } catch (Swift_TransportException $e) {
+                    return parent::doHeloCommand();
+                }
+            } catch (Swift_TransportException $e) {
+                $this->throwException($e);
+            }
+        }
+
+        $this->capabilities = $this->getCapabilities($response);
+        if (!isset($this->pipelining)) {
+            $this->pipelining = isset($this->capabilities['PIPELINING']);
+        }
+
+        $this->setHandlerParams();
+        foreach ($this->getActiveHandlers() as $handler) {
+            $handler->afterEhlo($this);
+        }
+    }
+
+    /** Overridden to add Extension support */
+    protected function doMailFromCommand($address)
+    {
+        $address = $this->addressEncoder->encodeString($address);
+        $handlers = $this->getActiveHandlers();
+        $params = [];
+        foreach ($handlers as $handler) {
+            $params = array_merge($params, (array) $handler->getMailParams());
+        }
+        $paramStr = !empty($params) ? ' '.implode(' ', $params) : '';
+        $this->executeCommand(
+            sprintf("MAIL FROM:<%s>%s\r\n", $address, $paramStr), [250], $failures, true
+            );
+    }
+
+    /** Overridden to add Extension support */
+    protected function doRcptToCommand($address)
+    {
+        $address = $this->addressEncoder->encodeString($address);
+        $handlers = $this->getActiveHandlers();
+        $params = [];
+        foreach ($handlers as $handler) {
+            $params = array_merge($params, (array) $handler->getRcptParams());
+        }
+        $paramStr = !empty($params) ? ' '.implode(' ', $params) : '';
+        $this->executeCommand(
+            sprintf("RCPT TO:<%s>%s\r\n", $address, $paramStr), [250, 251, 252], $failures, true, $address
+            );
+    }
+
+    /** Determine ESMTP capabilities by function group */
+    private function getCapabilities($ehloResponse)
+    {
+        $capabilities = [];
+        $ehloResponse = trim($ehloResponse);
+        $lines = explode("\r\n", $ehloResponse);
+        array_shift($lines);
+        foreach ($lines as $line) {
+            if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) {
+                $keyword = strtoupper($matches[1]);
+                $paramStr = strtoupper(ltrim($matches[2], ' ='));
+                $params = !empty($paramStr) ? explode(' ', $paramStr) : [];
+                $capabilities[$keyword] = $params;
+            }
+        }
+
+        return $capabilities;
+    }
+
+    /** Set parameters which are used by each extension handler */
+    private function setHandlerParams()
+    {
+        foreach ($this->handlers as $keyword => $handler) {
+            if (array_key_exists($keyword, $this->capabilities)) {
+                $handler->setKeywordParams($this->capabilities[$keyword]);
+            }
+        }
+    }
+
+    /** Get ESMTP handlers which are currently ok to use */
+    private function getActiveHandlers()
+    {
+        $handlers = [];
+        foreach ($this->handlers as $keyword => $handler) {
+            if (array_key_exists($keyword, $this->capabilities)) {
+                $handlers[] = $handler;
+            }
+        }
+
+        return $handlers;
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php
new file mode 100644
index 0000000..77489ce
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php
@@ -0,0 +1,32 @@
+_invoker = $invoker;
+        $this->_eventDispatcher = $eventDispatcher;
+    }
+
+    /**
+     * Not used.
+     */
+    public function isStarted()
+    {
+        return false;
+    }
+
+    /**
+     * Not used.
+     */
+    public function start()
+    {
+    }
+
+    /**
+     * Not used.
+     */
+    public function stop()
+    {
+    }
+
+    /**
+     * Set the additional parameters used on the mail() function.
+     *
+     * This string is formatted for sprintf() where %s is the sender address.
+     *
+     * @param string $params
+     *
+     * @return Swift_Transport_MailTransport
+     */
+    public function setExtraParams($params)
+    {
+        $this->_extraParams = $params;
+
+        return $this;
+    }
+
+    /**
+     * Get the additional parameters used on the mail() function.
+     *
+     * This string is formatted for sprintf() where %s is the sender address.
+     *
+     * @return string
+     */
+    public function getExtraParams()
+    {
+        return $this->_extraParams;
+    }
+
+    /**
+     * Send the given Message.
+     *
+     * Recipient/sender data will be retrieved from the Message API.
+     * The return value is the number of recipients who were accepted for delivery.
+     *
+     * @param Swift_Mime_Message $message
+     * @param string[]           $failedRecipients An array of failures by-reference
+     *
+     * @return int
+     */
+    public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
+    {
+        $failedRecipients = (array) $failedRecipients;
+
+        if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
+            $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
+            if ($evt->bubbleCancelled()) {
+                return 0;
+            }
+        }
+
+        $count = (
+            count((array) $message->getTo())
+            + count((array) $message->getCc())
+            + count((array) $message->getBcc())
+            );
+
+        $toHeader = $message->getHeaders()->get('To');
+        $subjectHeader = $message->getHeaders()->get('Subject');
+
+        if (0 === $count) {
+            $this->_throwException(new Swift_TransportException('Cannot send message without a recipient'));
+        }
+        $to = $toHeader ? $toHeader->getFieldBody() : '';
+        $subject = $subjectHeader ? $subjectHeader->getFieldBody() : '';
+
+        $reversePath = $this->_getReversePath($message);
+
+        // Remove headers that would otherwise be duplicated
+        $message->getHeaders()->remove('To');
+        $message->getHeaders()->remove('Subject');
+
+        $messageStr = $message->toString();
+
+        if ($toHeader) {
+            $message->getHeaders()->set($toHeader);
+        }
+        $message->getHeaders()->set($subjectHeader);
+
+        // Separate headers from body
+        if (false !== $endHeaders = strpos($messageStr, "\r\n\r\n")) {
+            $headers = substr($messageStr, 0, $endHeaders)."\r\n"; //Keep last EOL
+            $body = substr($messageStr, $endHeaders + 4);
+        } else {
+            $headers = $messageStr."\r\n";
+            $body = '';
+        }
+
+        unset($messageStr);
+
+        if ("\r\n" != PHP_EOL) {
+            // Non-windows (not using SMTP)
+            $headers = str_replace("\r\n", PHP_EOL, $headers);
+            $subject = str_replace("\r\n", PHP_EOL, $subject);
+            $body = str_replace("\r\n", PHP_EOL, $body);
+        } else {
+            // Windows, using SMTP
+            $headers = str_replace("\r\n.", "\r\n..", $headers);
+            $subject = str_replace("\r\n.", "\r\n..", $subject);
+            $body = str_replace("\r\n.", "\r\n..", $body);
+        }
+
+        if ($this->_invoker->mail($to, $subject, $body, $headers, $this->_formatExtraParams($this->_extraParams, $reversePath))) {
+            if ($evt) {
+                $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
+                $evt->setFailedRecipients($failedRecipients);
+                $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
+            }
+        } else {
+            $failedRecipients = array_merge(
+                $failedRecipients,
+                array_keys((array) $message->getTo()),
+                array_keys((array) $message->getCc()),
+                array_keys((array) $message->getBcc())
+                );
+
+            if ($evt) {
+                $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
+                $evt->setFailedRecipients($failedRecipients);
+                $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
+            }
+
+            $message->generateId();
+
+            $count = 0;
+        }
+
+        return $count;
+    }
+
+    /**
+     * Register a plugin.
+     *
+     * @param Swift_Events_EventListener $plugin
+     */
+    public function registerPlugin(Swift_Events_EventListener $plugin)
+    {
+        $this->_eventDispatcher->bindEventListener($plugin);
+    }
+
+    /** Throw a TransportException, first sending it to any listeners */
+    protected function _throwException(Swift_TransportException $e)
+    {
+        if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e)) {
+            $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
+            if (!$evt->bubbleCancelled()) {
+                throw $e;
+            }
+        } else {
+            throw $e;
+        }
+    }
+
+    /** Determine the best-use reverse path for this message */
+    private function _getReversePath(Swift_Message $message)
+    {
+        $return = $message->getReturnPath();
+        $sender = $message->getSender();
+        $from = $message->getFrom();
+        $path = null;
+        if (!empty($return)) {
+            $path = $return;
+        } elseif (!empty($sender)) {
+            $keys = array_keys($sender);
+            $path = array_shift($keys);
+        } elseif (!empty($from)) {
+            $keys = array_keys($from);
+            $path = array_shift($keys);
+        }
+
+        return $path;
+    }
+
+    /**
+     * Return php mail extra params to use for invoker->mail.
+     *
+     * @param $extraParams
+     * @param $reversePath
+     *
+     * @return string|null
+     */
+    private function _formatExtraParams($extraParams, $reversePath)
+    {
+        if (false !== strpos($extraParams, '-f%s')) {
+            // no need to escape $reversePath) as mail() already does it
+            $extraParams = empty($reversePath) ? str_replace('-f%s', '', $extraParams) : sprintf($extraParams, $reversePath);
+        }
+
+        return !empty($extraParams) ? $extraParams : null;
+    }
+
+    public function ping() {
+        return true;
+    }
+}
diff --git a/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php
new file mode 100644
index 0000000..4cab66b
--- /dev/null
+++ b/freescout-dist/overrides/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php
@@ -0,0 +1,39 @@
+replacementFactory = $replacementFactory;
+    }
+
+    /**
+     * Perform any initialization needed, using the given $params.
+     *
+     * Parameters will vary depending upon the type of IoBuffer used.
+     */
+    public function initialize(array $params)
+    {
+        $this->params = $params;
+        switch ($params['type']) {
+            case self::TYPE_PROCESS:
+                $this->establishProcessConnection();
+                break;
+            case self::TYPE_SOCKET:
+            default:
+                $this->establishSocketConnection();
+                break;
+        }
+    }
+
+    /**
+     * Set an individual param on the buffer (e.g. switching to SSL).
+     *
+     * @param string $param
+     * @param mixed  $value
+     */
+    public function setParam($param, $value)
+    {
+        if (isset($this->stream)) {
+            switch ($param) {
+                case 'timeout':
+                    if ($this->stream) {
+                        stream_set_timeout($this->stream, $value);
+                    }
+                    break;
+
+                case 'blocking':
+                    if ($this->stream) {
+                        stream_set_blocking($this->stream, 1);
+                    }
+            }
+        }
+        $this->params[$param] = $value;
+    }
+
+    public function startTLS()
+    {
+        // STREAM_CRYPTO_METHOD_TLS_CLIENT only allow tls1.0 connections (some php versions)
+        // To support modern tls we allow explicit tls1.0, tls1.1, tls1.2
+        // Ssl3 and older are not allowed because they are vulnerable
+        // @TODO make tls arguments configurable
+        return stream_socket_enable_crypto($this->stream, true, STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
+    }
+
+    /**
+     * Perform any shutdown logic needed.
+     */
+    public function terminate()
+    {
+        if (isset($this->stream)) {
+            switch ($this->params['type']) {
+                case self::TYPE_PROCESS:
+                    fclose($this->in);
+                    fclose($this->out);
+                    proc_close($this->stream);
+                    break;
+                case self::TYPE_SOCKET:
+                default:
+                    fclose($this->stream);
+                    break;
+            }
+        }
+        $this->stream = null;
+        $this->out = null;
+        $this->in = null;
+    }
+
+    /**
+     * Set an array of string replacements which should be made on data written
+     * to the buffer.
+     *
+     * This could replace LF with CRLF for example.
+     *
+     * @param string[] $replacements
+     */
+    public function setWriteTranslations(array $replacements)
+    {
+        foreach ($this->translations as $search => $replace) {
+            if (!isset($replacements[$search])) {
+                $this->removeFilter($search);
+                unset($this->translations[$search]);
+            }
+        }
+
+        foreach ($replacements as $search => $replace) {
+            if (!isset($this->translations[$search])) {
+                $this->addFilter(
+                    $this->replacementFactory->createFilter($search, $replace), $search
+                    );
+                $this->translations[$search] = true;
+            }
+        }
+    }
+
+    /**
+     * Get a line of output (including any CRLF).
+     *
+     * The $sequence number comes from any writes and may or may not be used
+     * depending upon the implementation.
+     *
+     * @param int $sequence of last write to scan from
+     *
+     * @return string
+     *
+     * @throws Swift_IoException
+     */
+    public function readLine($sequence)
+    {
+        if (isset($this->out) && !feof($this->out)) {
+            $line = fgets($this->out);
+            if (0 == strlen($line)) {
+                $metas = stream_get_meta_data($this->out);
+                if ($metas['timed_out']) {
+                    throw new Swift_IoException(
+                        'Connection to '.
+                            $this->getReadConnectionDescription().
+                        ' Timed Out'
+                    );
+                }
+            }
+
+            return $line;
+        }
+    }
+
+    /**
+     * Reads $length bytes from the stream into a string and moves the pointer
+     * through the stream by $length.
+     *
+     * If less bytes exist than are requested the remaining bytes are given instead.
+     * If no bytes are remaining at all, boolean false is returned.
+     *
+     * @param int $length
+     *
+     * @return string|bool
+     *
+     * @throws Swift_IoException
+     */
+    public function read($length)
+    {
+        if (isset($this->out) && !feof($this->out)) {
+            $ret = fread($this->out, $length);
+            if (0 == strlen($ret)) {
+                $metas = stream_get_meta_data($this->out);
+                if ($metas['timed_out']) {
+                    throw new Swift_IoException(
+                        'Connection to '.
+                            $this->getReadConnectionDescription().
+                        ' Timed Out'
+                    );
+                }
+            }
+
+            return $ret;
+        }
+    }
+
+    /** Not implemented */
+    public function setReadPointer($byteOffset)
+    {
+    }
+
+    /** Flush the stream contents */
+    protected function flush()
+    {
+        if (isset($this->in)) {
+            fflush($this->in);
+        }
+    }
+
+    /** Write this bytes to the stream */
+    protected function doCommit($bytes)
+    {
+        if (isset($this->in)) {
+            $bytesToWrite = strlen($bytes);
+            $totalBytesWritten = 0;
+
+            while ($totalBytesWritten < $bytesToWrite) {
+                $bytesWritten = fwrite($this->in, substr($bytes, $totalBytesWritten));
+                if (false === $bytesWritten || 0 === $bytesWritten) {
+                    break;
+                }
+
+                $totalBytesWritten += $bytesWritten;
+            }
+
+            if ($totalBytesWritten > 0) {
+                return ++$this->sequence;
+            }
+        }
+    }
+
+    /**
+     * Establishes a connection to a remote server.
+     */
+    private function establishSocketConnection()
+    {
+        $host = $this->params['host'];
+        if (!empty($this->params['protocol'])) {
+            $host = $this->params['protocol'].'://'.$host;
+        }
+        $timeout = 15;
+        if (!empty($this->params['timeout'])) {
+            $timeout = $this->params['timeout'];
+        }
+        $options = [];
+        // https://github.com/freescout-helpdesk/freescout/issues/2714
+        $options['ssl'] = ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true];
+        if (!empty($this->params['sourceIp'])) {
+            $options['socket']['bindto'] = $this->params['sourceIp'].':0';
+        }
+
+        if (isset($this->params['stream_context_options'])) {
+            $options = array_merge($options, $this->params['stream_context_options']);
+        }
+        $streamContext = stream_context_create($options);
+        $this->stream = @stream_socket_client($host.':'.$this->params['port'], $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $streamContext);
+        if (false === $this->stream) {
+            throw new Swift_TransportException(
+                'Connection could not be established with host '.$this->params['host'].
+                ' ['.$errstr.' #'.$errno.']'
+                );
+        }
+        if (!empty($this->params['blocking'])) {
+            stream_set_blocking($this->stream, 1);
+        } else {
+            stream_set_blocking($this->stream, 0);
+        }
+        stream_set_timeout($this->stream, $timeout);
+        $this->in = &$this->stream;
+        $this->out = &$this->stream;
+    }
+
+    /**
+     * Opens a process for input/output.
+     */
+    private function establishProcessConnection()
+    {
+        $command = $this->params['command'];
+        $descriptorSpec = [
+            0 => ['pipe', 'r'],
+            1 => ['pipe', 'w'],
+            2 => ['pipe', 'w'],
+            ];
+        $pipes = [];
+        $this->stream = proc_open($command, $descriptorSpec, $pipes);
+        stream_set_blocking($pipes[2], 0);
+        if ($err = stream_get_contents($pipes[2])) {
+            throw new Swift_TransportException(
+                'Process could not be started ['.$err.']'
+                );
+        }
+        $this->in = &$pipes[0];
+        $this->out = &$pipes[1];
+    }
+
+    private function getReadConnectionDescription()
+    {
+        switch ($this->params['type']) {
+            case self::TYPE_PROCESS:
+                return 'Process '.$this->params['command'];
+                break;
+
+            case self::TYPE_SOCKET:
+            default:
+                $host = $this->params['host'];
+                if (!empty($this->params['protocol'])) {
+                    $host = $this->params['protocol'].'://'.$host;
+                }
+                $host .= ':'.$this->params['port'];
+
+                return $host;
+                break;
+        }
+    }
+}
diff --git a/freescout-dist/overrides/symfony/console/Descriptor/TextDescriptor.php b/freescout-dist/overrides/symfony/console/Descriptor/TextDescriptor.php
new file mode 100644
index 0000000..d258649
--- /dev/null
+++ b/freescout-dist/overrides/symfony/console/Descriptor/TextDescriptor.php
@@ -0,0 +1,342 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Descriptor;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Formatter\OutputFormatter;
+use Symfony\Component\Console\Helper\Helper;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+
+/**
+ * Text descriptor.
+ *
+ * @author Jean-François Simon 
+ *
+ * @internal
+ */
+class TextDescriptor extends Descriptor
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected function describeInputArgument(InputArgument $argument, array $options = [])
+    {
+        if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
+            $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault()));
+        } else {
+            $default = '';
+        }
+
+        $totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName());
+        $spacingWidth = $totalWidth - \strlen($argument->getName());
+
+        $this->writeText(sprintf('  %s  %s%s%s',
+            $argument->getName(),
+            str_repeat(' ', $spacingWidth),
+            // + 4 = 2 spaces before , 2 spaces after 
+            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
+            $default
+        ), $options);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function describeInputOption(InputOption $option, array $options = [])
+    {
+        if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
+            $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault()));
+        } else {
+            $default = '';
+        }
+
+        $value = '';
+        if ($option->acceptValue()) {
+            $value = '='.strtoupper($option->getName());
+
+            if ($option->isValueOptional()) {
+                $value = '['.$value.']';
+            }
+        }
+
+        $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]);
+        $synopsis = sprintf('%s%s',
+            $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : '    ',
+            sprintf('--%s%s', $option->getName(), $value)
+        );
+
+        $spacingWidth = $totalWidth - Helper::strlen($synopsis);
+
+        $this->writeText(sprintf('  %s  %s%s%s%s',
+            $synopsis,
+            str_repeat(' ', $spacingWidth),
+            // + 4 = 2 spaces before , 2 spaces after 
+            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
+            $default,
+            $option->isArray() ? ' (multiple values allowed)' : ''
+        ), $options);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
+    {
+        $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
+        foreach ($definition->getArguments() as $argument) {
+            $totalWidth = max($totalWidth, Helper::strlen($argument->getName()));
+        }
+
+        if ($definition->getArguments()) {
+            $this->writeText('Arguments:', $options);
+            $this->writeText("\n");
+            foreach ($definition->getArguments() as $argument) {
+                $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
+                $this->writeText("\n");
+            }
+        }
+
+        if ($definition->getArguments() && $definition->getOptions()) {
+            $this->writeText("\n");
+        }
+
+        if ($definition->getOptions()) {
+            $laterOptions = [];
+
+            $this->writeText('Options:', $options);
+            foreach ($definition->getOptions() as $option) {
+                if (\strlen($option->getShortcut() ?? '') > 1) {
+                    $laterOptions[] = $option;
+                    continue;
+                }
+                $this->writeText("\n");
+                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
+            }
+            foreach ($laterOptions as $option) {
+                $this->writeText("\n");
+                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function describeCommand(Command $command, array $options = [])
+    {
+        $command->getSynopsis(true);
+        $command->getSynopsis(false);
+        $command->mergeApplicationDefinition(false);
+
+        $this->writeText('Usage:', $options);
+        foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
+            $this->writeText("\n");
+            $this->writeText('  '.OutputFormatter::escape($usage), $options);
+        }
+        $this->writeText("\n");
+
+        $definition = $command->getNativeDefinition();
+        if ($definition->getOptions() || $definition->getArguments()) {
+            $this->writeText("\n");
+            $this->describeInputDefinition($definition, $options);
+            $this->writeText("\n");
+        }
+
+        if ($help = $command->getProcessedHelp()) {
+            $this->writeText("\n");
+            $this->writeText('Help:', $options);
+            $this->writeText("\n");
+            $this->writeText('  '.str_replace("\n", "\n  ", $help), $options);
+            $this->writeText("\n");
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function describeApplication(Application $application, array $options = [])
+    {
+        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
+        $description = new ApplicationDescription($application, $describedNamespace);
+
+        if (isset($options['raw_text']) && $options['raw_text']) {
+            $width = $this->getColumnWidth($description->getCommands());
+
+            foreach ($description->getCommands() as $command) {
+                $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
+                $this->writeText("\n");
+            }
+        } else {
+            if ('' != $help = $application->getHelp()) {
+                $this->writeText("$help\n\n", $options);
+            }
+
+            $this->writeText("Usage:\n", $options);
+            $this->writeText("  command [options] [arguments]\n\n", $options);
+
+            $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);
+
+            $this->writeText("\n");
+            $this->writeText("\n");
+
+            $commands = $description->getCommands();
+            $namespaces = $description->getNamespaces();
+            if ($describedNamespace && $namespaces) {
+                // make sure all alias commands are included when describing a specific namespace
+                $describedNamespaceInfo = reset($namespaces);
+                foreach ($describedNamespaceInfo['commands'] as $name) {
+                    $commands[$name] = $description->getCommand($name);
+                }
+            }
+
+            // calculate max. width based on available commands per namespace
+            $width = $this->getColumnWidth(\call_user_func_array('array_merge', array_map(function ($namespace) use ($commands) {
+                return array_intersect($namespace['commands'], array_keys($commands));
+            }, array_values($namespaces))));
+
+            if ($describedNamespace) {
+                $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options);
+            } else {
+                $this->writeText('Available commands:', $options);
+            }
+
+            foreach ($namespaces as $namespace) {
+                $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) {
+                    return isset($commands[$name]);
+                });
+
+                if (!$namespace['commands']) {
+                    continue;
+                }
+
+                if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
+                    $this->writeText("\n");
+                    $this->writeText(' '.$namespace['id'].'', $options);
+                }
+
+                foreach ($namespace['commands'] as $name) {
+                    $this->writeText("\n");
+                    $spacingWidth = $width - Helper::strlen($name);
+                    $command = $commands[$name];
+                    $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
+                    $this->writeText(sprintf('  %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
+                }
+            }
+
+            $this->writeText("\n");
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    private function writeText($content, array $options = [])
+    {
+        $this->write(
+            isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
+            isset($options['raw_output']) ? !$options['raw_output'] : true
+        );
+    }
+
+    /**
+     * Formats command aliases to show them in the command description.
+     *
+     * @return string
+     */
+    private function getCommandAliasesText(Command $command)
+    {
+        $text = '';
+        $aliases = $command->getAliases();
+
+        if ($aliases) {
+            $text = '['.implode('|', $aliases).'] ';
+        }
+
+        return $text;
+    }
+
+    /**
+     * Formats input option/argument default value.
+     *
+     * @param mixed $default
+     *
+     * @return string
+     */
+    private function formatDefaultValue($default)
+    {
+        if (\INF === $default) {
+            return 'INF';
+        }
+
+        if (\is_string($default)) {
+            $default = OutputFormatter::escape($default);
+        } elseif (\is_array($default)) {
+            foreach ($default as $key => $value) {
+                if (\is_string($value)) {
+                    $default[$key] = OutputFormatter::escape($value);
+                }
+            }
+        }
+
+        return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
+    }
+
+    /**
+     * @param (Command|string)[] $commands
+     *
+     * @return int
+     */
+    private function getColumnWidth(array $commands)
+    {
+        $widths = [];
+
+        foreach ($commands as $command) {
+            if ($command instanceof Command) {
+                $widths[] = Helper::strlen($command->getName());
+                foreach ($command->getAliases() as $alias) {
+                    $widths[] = Helper::strlen($alias);
+                }
+            } else {
+                $widths[] = Helper::strlen($command);
+            }
+        }
+
+        return $widths ? max($widths) + 2 : 0;
+    }
+
+    /**
+     * @param InputOption[] $options
+     *
+     * @return int
+     */
+    private function calculateTotalWidthForOptions(array $options)
+    {
+        $totalWidth = 0;
+        foreach ($options as $option) {
+            // "-" + shortcut + ", --" + name
+            $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName());
+
+            if ($option->acceptValue()) {
+                $valueLength = 1 + Helper::strlen($option->getName()); // = + value
+                $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
+
+                $nameLength += $valueLength;
+            }
+            $totalWidth = max($totalWidth, $nameLength);
+        }
+
+        return $totalWidth;
+    }
+}
diff --git a/freescout-dist/overrides/symfony/console/Helper/Helper.php b/freescout-dist/overrides/symfony/console/Helper/Helper.php
new file mode 100644
index 0000000..36611d1
--- /dev/null
+++ b/freescout-dist/overrides/symfony/console/Helper/Helper.php
@@ -0,0 +1,138 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Formatter\OutputFormatterInterface;
+
+/**
+ * Helper is the base class for all helper classes.
+ *
+ * @author Fabien Potencier 
+ */
+abstract class Helper implements HelperInterface
+{
+    protected $helperSet = null;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setHelperSet(HelperSet $helperSet = null)
+    {
+        $this->helperSet = $helperSet;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getHelperSet()
+    {
+        return $this->helperSet;
+    }
+
+    /**
+     * Returns the length of a string, using mb_strwidth if it is available.
+     *
+     * @param string $string The string to check its length
+     *
+     * @return int The length of the string
+     */
+    public static function strlen($string)
+    {
+        if (false === $encoding = mb_detect_encoding($string ?? '', null, true)) {
+            return \strlen($string);
+        }
+
+        return mb_strwidth($string ?? '', $encoding);
+    }
+
+    /**
+     * Returns the subset of a string, using mb_substr if it is available.
+     *
+     * @param string   $string String to subset
+     * @param int      $from   Start offset
+     * @param int|null $length Length to read
+     *
+     * @return string The string subset
+     */
+    public static function substr($string, $from, $length = null)
+    {
+        if (false === $encoding = mb_detect_encoding($string ?? '', null, true)) {
+            return substr($string, $from, $length);
+        }
+
+        return mb_substr($string, $from, $length, $encoding);
+    }
+
+    public static function formatTime($secs)
+    {
+        static $timeFormats = [
+            [0, '< 1 sec'],
+            [1, '1 sec'],
+            [2, 'secs', 1],
+            [60, '1 min'],
+            [120, 'mins', 60],
+            [3600, '1 hr'],
+            [7200, 'hrs', 3600],
+            [86400, '1 day'],
+            [172800, 'days', 86400],
+        ];
+
+        foreach ($timeFormats as $index => $format) {
+            if ($secs >= $format[0]) {
+                if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0])
+                    || $index == \count($timeFormats) - 1
+                ) {
+                    if (2 == \count($format)) {
+                        return $format[1];
+                    }
+
+                    return floor($secs / $format[2]).' '.$format[1];
+                }
+            }
+        }
+    }
+
+    public static function formatMemory($memory)
+    {
+        if ($memory >= 1024 * 1024 * 1024) {
+            return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
+        }
+
+        if ($memory >= 1024 * 1024) {
+            return sprintf('%.1f MiB', $memory / 1024 / 1024);
+        }
+
+        if ($memory >= 1024) {
+            return sprintf('%d KiB', $memory / 1024);
+        }
+
+        return sprintf('%d B', $memory);
+    }
+
+    public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string)
+    {
+        return self::strlen(self::removeDecoration($formatter, $string));
+    }
+
+    public static function removeDecoration(OutputFormatterInterface $formatter, $string)
+    {
+        $isDecorated = $formatter->isDecorated();
+        $formatter->setDecorated(false);
+        // remove <...> formatting
+        $string = $formatter->format($string);
+        // remove already formatted characters
+        $string = preg_replace("/\033\[[^m]*m/", '', $string);
+        $formatter->setDecorated($isDecorated);
+
+        return $string;
+    }
+}
diff --git a/freescout-dist/overrides/symfony/console/Helper/HelperSet.php b/freescout-dist/overrides/symfony/console/Helper/HelperSet.php
new file mode 100644
index 0000000..ca02631
--- /dev/null
+++ b/freescout-dist/overrides/symfony/console/Helper/HelperSet.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Helper;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+
+/**
+ * HelperSet represents a set of helpers to be used with a command.
+ *
+ * @author Fabien Potencier 
+ */
+class HelperSet implements \IteratorAggregate
+{
+    /**
+     * @var Helper[]
+     */
+    private $helpers = [];
+    private $command;
+
+    /**
+     * @param Helper[] $helpers An array of helper
+     */
+    public function __construct(array $helpers = [])
+    {
+        foreach ($helpers as $alias => $helper) {
+            $this->set($helper, \is_int($alias) ? null : $alias);
+        }
+    }
+
+    /**
+     * Sets a helper.
+     *
+     * @param HelperInterface $helper The helper instance
+     * @param string          $alias  An alias
+     */
+    public function set(HelperInterface $helper, $alias = null)
+    {
+        $this->helpers[$helper->getName()] = $helper;
+        if (null !== $alias) {
+            $this->helpers[$alias] = $helper;
+        }
+
+        $helper->setHelperSet($this);
+    }
+
+    /**
+     * Returns true if the helper if defined.
+     *
+     * @param string $name The helper name
+     *
+     * @return bool true if the helper is defined, false otherwise
+     */
+    public function has($name)
+    {
+        return isset($this->helpers[$name]);
+    }
+
+    /**
+     * Gets a helper value.
+     *
+     * @param string $name The helper name
+     *
+     * @return HelperInterface The helper instance
+     *
+     * @throws InvalidArgumentException if the helper is not defined
+     */
+    public function get($name)
+    {
+        if (!$this->has($name)) {
+            throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
+        }
+
+        return $this->helpers[$name];
+    }
+
+    public function setCommand(Command $command = null)
+    {
+        $this->command = $command;
+    }
+
+    /**
+     * Gets the command associated with this helper set.
+     *
+     * @return Command A Command instance
+     */
+    public function getCommand()
+    {
+        return $this->command;
+    }
+
+    /**
+     * @return Helper[]
+     */
+    public function getIterator(): \Traversable
+    {
+        return new \ArrayIterator($this->helpers);
+    }
+}
diff --git a/freescout-dist/overrides/symfony/css-selector/XPath/Extension/NodeExtension.php b/freescout-dist/overrides/symfony/css-selector/XPath/Extension/NodeExtension.php
new file mode 100644
index 0000000..bbc8d50
--- /dev/null
+++ b/freescout-dist/overrides/symfony/css-selector/XPath/Extension/NodeExtension.php
@@ -0,0 +1,242 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\CssSelector\XPath\Extension;
+
+use Symfony\Component\CssSelector\Node;
+use Symfony\Component\CssSelector\XPath\Translator;
+use Symfony\Component\CssSelector\XPath\XPathExpr;
+
+/**
+ * XPath expression translator node extension.
+ *
+ * This component is a port of the Python cssselect library,
+ * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
+ *
+ * @author Jean-François Simon 
+ *
+ * @internal
+ */
+class NodeExtension extends AbstractExtension
+{
+    const ELEMENT_NAME_IN_LOWER_CASE = 1;
+    const ATTRIBUTE_NAME_IN_LOWER_CASE = 2;
+    const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4;
+
+    private $flags;
+
+    /**
+     * @param int $flags
+     */
+    public function __construct($flags = 0)
+    {
+        $this->flags = $flags;
+    }
+
+    /**
+     * @param int  $flag
+     * @param bool $on
+     *
+     * @return $this
+     */
+    public function setFlag($flag, $on)
+    {
+        if ($on && !$this->hasFlag($flag)) {
+            $this->flags += $flag;
+        }
+
+        if (!$on && $this->hasFlag($flag)) {
+            $this->flags -= $flag;
+        }
+
+        return $this;
+    }
+
+    /**
+     * @param int $flag
+     *
+     * @return bool
+     */
+    public function hasFlag($flag)
+    {
+        return (bool) ($this->flags & $flag);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getNodeTranslators()
+    {
+        return array(
+            'Selector' => array($this, 'translateSelector'),
+            'CombinedSelector' => array($this, 'translateCombinedSelector'),
+            'Negation' => array($this, 'translateNegation'),
+            'Function' => array($this, 'translateFunction'),
+            'Pseudo' => array($this, 'translatePseudo'),
+            'Attribute' => array($this, 'translateAttribute'),
+            'Class' => array($this, 'translateClass'),
+            'Hash' => array($this, 'translateHash'),
+            'Element' => array($this, 'translateElement'),
+        );
+    }
+
+    /**
+     * @return XPathExpr
+     */
+    public function translateSelector(Node\SelectorNode $node, Translator $translator)
+    {
+        return $translator->nodeToXPath($node->getTree());
+    }
+
+    /**
+     * @return XPathExpr
+     */
+    public function translateCombinedSelector(Node\CombinedSelectorNode $node, Translator $translator)
+    {
+        return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector());
+    }
+
+    /**
+     * @return XPathExpr
+     */
+    public function translateNegation(Node\NegationNode $node, Translator $translator)
+    {
+        $xpath = $translator->nodeToXPath($node->getSelector());
+        $subXpath = $translator->nodeToXPath($node->getSubSelector());
+        $subXpath->addNameTest();
+
+        if ($subXpath->getCondition()) {
+            return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition()));
+        }
+
+        return $xpath->addCondition('0');
+    }
+
+    /**
+     * @return XPathExpr
+     */
+    public function translateFunction(Node\FunctionNode $node, Translator $translator)
+    {
+        $xpath = $translator->nodeToXPath($node->getSelector());
+
+        return $translator->addFunction($xpath, $node);
+    }
+
+    /**
+     * @return XPathExpr
+     */
+    public function translatePseudo(Node\PseudoNode $node, Translator $translator)
+    {
+        $xpath = $translator->nodeToXPath($node->getSelector());
+
+        return $translator->addPseudoClass($xpath, $node->getIdentifier());
+    }
+
+    /**
+     * @return XPathExpr
+     */
+    public function translateAttribute(Node\AttributeNode $node, Translator $translator)
+    {
+        $name = $node->getAttribute();
+        $safe = $this->isSafeName($name);
+
+        if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) {
+            $name = strtolower($name);
+        }
+
+        if ($node->getNamespace()) {
+            $name = sprintf('%s:%s', $node->getNamespace(), $name);
+            $safe = $safe && $this->isSafeName($node->getNamespace());
+        }
+
+        $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name));
+        $value = $node->getValue();
+        $xpath = $translator->nodeToXPath($node->getSelector());
+
+        if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) {
+            $value = strtolower($value);
+        }
+
+        return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value);
+    }
+
+    /**
+     * @return XPathExpr
+     */
+    public function translateClass(Node\ClassNode $node, Translator $translator)
+    {
+        $xpath = $translator->nodeToXPath($node->getSelector());
+
+        return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName());
+    }
+
+    /**
+     * @return XPathExpr
+     */
+    public function translateHash(Node\HashNode $node, Translator $translator)
+    {
+        $xpath = $translator->nodeToXPath($node->getSelector());
+
+        return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId());
+    }
+
+    /**
+     * @return XPathExpr
+     */
+    public function translateElement(Node\ElementNode $node)
+    {
+        $element = $node->getElement();
+
+        if ($this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) {
+            $element = strtolower($element ?? '');
+        }
+
+        if ($element) {
+            $safe = $this->isSafeName($element);
+        } else {
+            $element = '*';
+            $safe = true;
+        }
+
+        if ($node->getNamespace()) {
+            $element = sprintf('%s:%s', $node->getNamespace(), $element);
+            $safe = $safe && $this->isSafeName($node->getNamespace());
+        }
+
+        $xpath = new XPathExpr('', $element);
+
+        if (!$safe) {
+            $xpath->addNameTest();
+        }
+
+        return $xpath;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getName()
+    {
+        return 'node';
+    }
+
+    /**
+     * Tests if given name is safe.
+     *
+     * @param string $name
+     *
+     * @return bool
+     */
+    private function isSafeName($name)
+    {
+        return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name);
+    }
+}
diff --git a/freescout-dist/overrides/symfony/debug/ExceptionHandler.php b/freescout-dist/overrides/symfony/debug/ExceptionHandler.php
new file mode 100644
index 0000000..3179d60
--- /dev/null
+++ b/freescout-dist/overrides/symfony/debug/ExceptionHandler.php
@@ -0,0 +1,455 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Debug;
+
+use Symfony\Component\Debug\Exception\FlattenException;
+use Symfony\Component\Debug\Exception\OutOfMemoryException;
+use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
+
+/**
+ * ExceptionHandler converts an exception to a Response object.
+ *
+ * It is mostly useful in debug mode to replace the default PHP/XDebug
+ * output with something prettier and more useful.
+ *
+ * As this class is mainly used during Kernel boot, where nothing is yet
+ * available, the Response content is always HTML.
+ *
+ * @author Fabien Potencier 
+ * @author Nicolas Grekas 
+ */
+class ExceptionHandler
+{
+    private $debug;
+    private $charset;
+    private $handler;
+    private $caughtBuffer;
+    private $caughtLength;
+    private $fileLinkFormat;
+
+    public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
+    {
+        $this->debug = $debug;
+        $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
+        $this->fileLinkFormat = $fileLinkFormat;
+    }
+
+    /**
+     * Registers the exception handler.
+     *
+     * @param bool        $debug          Enable/disable debug mode, where the stack trace is displayed
+     * @param string|null $charset        The charset used by exception messages
+     * @param string|null $fileLinkFormat The IDE link template
+     *
+     * @return static
+     */
+    public static function register($debug = true, $charset = null, $fileLinkFormat = null)
+    {
+        $handler = new static($debug, $charset, $fileLinkFormat);
+
+        $prev = set_exception_handler([$handler, 'handle']);
+        if (\is_array($prev) && $prev[0] instanceof ErrorHandler) {
+            restore_exception_handler();
+            $prev[0]->setExceptionHandler([$handler, 'handle']);
+        }
+
+        return $handler;
+    }
+
+    /**
+     * Sets a user exception handler.
+     *
+     * @param callable $handler An handler that will be called on Exception
+     *
+     * @return callable|null The previous exception handler if any
+     */
+    public function setHandler(callable $handler = null)
+    {
+        $old = $this->handler;
+        $this->handler = $handler;
+
+        return $old;
+    }
+
+    /**
+     * Sets the format for links to source files.
+     *
+     * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
+     *
+     * @return string The previous file link format
+     */
+    public function setFileLinkFormat($fileLinkFormat)
+    {
+        $old = $this->fileLinkFormat;
+        $this->fileLinkFormat = $fileLinkFormat;
+
+        return $old;
+    }
+
+    /**
+     * Sends a response for the given Exception.
+     *
+     * To be as fail-safe as possible, the exception is first handled
+     * by our simple exception handler, then by the user exception handler.
+     * The latter takes precedence and any output from the former is cancelled,
+     * if and only if nothing bad happens in this handling path.
+     */
+    public function handle(\Exception $exception)
+    {
+        if (null === $this->handler || $exception instanceof OutOfMemoryException) {
+            $this->sendPhpResponse($exception);
+
+            return;
+        }
+
+        $caughtLength = $this->caughtLength = 0;
+
+        ob_start(function ($buffer) {
+            $this->caughtBuffer = $buffer;
+
+            return '';
+        });
+
+        $this->sendPhpResponse($exception);
+        while (null === $this->caughtBuffer && ob_end_flush()) {
+            // Empty loop, everything is in the condition
+        }
+        if (isset($this->caughtBuffer[0])) {
+            ob_start(function ($buffer) {
+                if ($this->caughtLength) {
+                    // use substr_replace() instead of substr() for mbstring overloading resistance
+                    $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
+                    if (isset($cleanBuffer[0])) {
+                        $buffer = $cleanBuffer;
+                    }
+                }
+
+                return $buffer;
+            });
+
+            echo $this->caughtBuffer;
+            $caughtLength = ob_get_length();
+        }
+        $this->caughtBuffer = null;
+
+        try {
+            \call_user_func($this->handler, $exception);
+            $this->caughtLength = $caughtLength;
+        } catch (\Exception $e) {
+            if (!$caughtLength) {
+                // All handlers failed. Let PHP handle that now.
+                throw $exception;
+            }
+        }
+    }
+
+    /**
+     * Sends the error associated with the given Exception as a plain PHP response.
+     *
+     * This method uses plain PHP functions like header() and echo to output
+     * the response.
+     *
+     * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
+     */
+    public function sendPhpResponse($exception)
+    {
+        if (!$exception instanceof FlattenException) {
+            $exception = FlattenException::create($exception);
+        }
+
+        if (!headers_sent()) {
+            header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
+            foreach ($exception->getHeaders() as $name => $value) {
+                header($name.': '.$value, false);
+            }
+            header('Content-Type: text/html; charset='.$this->charset);
+        }
+
+        echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
+    }
+
+    /**
+     * Gets the full HTML content associated with the given exception.
+     *
+     * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
+     *
+     * @return string The HTML content as a string
+     */
+    public function getHtml($exception)
+    {
+        if (!$exception instanceof FlattenException) {
+            $exception = FlattenException::create($exception);
+        }
+
+        return $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
+    }
+
+    /**
+     * Gets the HTML content associated with the given exception.
+     *
+     * @return string The content as a string
+     */
+    public function getContent(FlattenException $exception)
+    {
+        $title = $exception->getMessage();
+        
+        if (\Str::endsWith($title, '[display]')) {
+            $title = preg_replace("/\[display\]$/", '', $title);
+        } else {
+            switch ($exception->getStatusCode()) {
+                case 404:
+                    $title = 'Sorry, the page you are looking for could not be found.';
+                    break;
+                default:
+                    $title = 'Whoops, looks like something went wrong.';
+            }
+        }
+
+        if (!$title) {
+            $title = 'Whoops, looks like something went wrong.';
+        }
+
+        if (!$this->debug) {
+            return <<
+                    

$title

+
+EOF; + } + + $content = ''; + try { + $count = \count($exception->getAllPrevious()); + $total = $count + 1; + foreach ($exception->toArray() as $position => $e) { + $ind = $count - $position + 1; + $class = $this->formatClass($e['class']); + $message = nl2br($this->escapeHtml($e['message'])); + $content .= sprintf(<<<'EOF' +
+ + + +EOF + , $ind, $total, $class, $message); + foreach ($e['trace'] as $trace) { + $content .= '\n"; + } + + $content .= "\n
+

+ (%d/%d) + %s +

+

%s

+
'; + if ($trace['function']) { + $content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); + } + if (isset($trace['file']) && isset($trace['line'])) { + $content .= $this->formatPath($trace['file'], $trace['line']); + } + $content .= "
\n
\n"; + } + } catch (\Exception $e) { + // something nasty happened and we cannot throw an exception anymore + if ($this->debug) { + $title = sprintf('Exception thrown when handling an exception (%s: %s)', \get_class($e), $this->escapeHtml($e->getMessage())); + } else { + $title = 'Whoops, looks like something went wrong.'; + } + } + + $symfonyGhostImageContents = $this->getSymfonyGhostAsSvg(); + + return << +
+
+

$title

+
$symfonyGhostImageContents
+
+
+
+ +
+ $content +
+EOF; + } + + /** + * Gets the stylesheet associated with the given exception. + * + * @return string The stylesheet as a string + */ + public function getStylesheet(FlattenException $exception) + { + if (!$this->debug) { + return <<<'EOF' + body { background-color: #fff; color: #222; font-family: Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif; margin: 0; } + .container { text-align: center; align-items: center; display: flex; justify-content: center; height: 100vh; position: relative; } + #h1 { color: #dc3545; font-size: 24px; } + h1 { color: #636b6f; font-size: 36px; padding: 20px; font-weight: 100; padding: 20px; } +EOF; + } + + return <<<'EOF' + body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; } + + a { cursor: pointer; text-decoration: none; } + a:hover { text-decoration: underline; } + abbr[title] { border-bottom: none; cursor: help; text-decoration: none; } + + code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; } + + table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; } + table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; } + table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; } + table th { background-color: #E0E0E0; font-weight: bold; text-align: left; } + + .hidden-xs-down { display: none; } + .block { display: block; } + .break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; } + .text-muted { color: #999; } + + .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } + .container::after { content: ""; display: table; clear: both; } + + .exception-summary { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; } + + .exception-message-wrapper { display: flex; align-items: center; min-height: 70px; } + .exception-message { flex-grow: 1; padding: 30px 0; } + .exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; } + .exception-message.long { font-size: 18px; } + .exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; } + .exception-message a:hover { border-bottom-color: #ffffff; } + + .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } + + .trace + .trace { margin-top: 30px; } + .trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; } + + .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } + + .trace-file-path, .trace-file-path a { color: #222; margin-top: 3px; font-size: 13px; } + .trace-class { color: #B0413E; } + .trace-type { padding: 0 2px; } + .trace-method { color: #B0413E; font-weight: bold; } + .trace-arguments { color: #777; font-weight: normal; padding-left: 2px; } + + @media (min-width: 575px) { + .hidden-xs-down { display: initial; } + } +EOF; + } + + private function decorate($content, $css) + { + return << + + + + + + + + $content + + +EOF; + } + + private function formatClass($class) + { + $parts = explode('\\', $class); + + return sprintf('%s', $class, array_pop($parts)); + } + + private function formatPath($path, $line) + { + $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); + $fmt = $this->fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + + if (!$fmt) { + return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); + } + + if (\is_string($fmt)) { + $i = strpos($f = $fmt, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); + $fmt = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE); + + for ($i = 1; isset($fmt[$i]); ++$i) { + if (0 === strpos($path, $k = $fmt[$i++])) { + $path = substr_replace($path, $fmt[$i], 0, \strlen($k)); + break; + } + } + + $link = strtr($fmt[0], ['%f' => $path, '%l' => $line]); + } else { + try { + $link = $fmt->format($path, $line); + } catch (\Exception $e) { + return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); + } + } + + return sprintf('in %s%s', $this->escapeHtml($link), $file, 0 < $line ? ' line '.$line : ''); + } + + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ + private function formatArgs(array $args) + { + $result = []; + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $formattedValue = sprintf('object(%s)', $this->formatClass($item[1])); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true))); + } + + $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue); + } + + return implode(', ', $result); + } + + /** + * HTML-encodes a string. + */ + private function escapeHtml($str) + { + return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset); + } + + private function getSymfonyGhostAsSvg() + { + return ''; + } +} diff --git a/freescout-dist/overrides/symfony/finder/Finder.php b/freescout-dist/overrides/symfony/finder/Finder.php new file mode 100644 index 0000000..4fe7163 --- /dev/null +++ b/freescout-dist/overrides/symfony/finder/Finder.php @@ -0,0 +1,751 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +use Symfony\Component\Finder\Comparator\DateComparator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Iterator\CustomFilterIterator; +use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; +use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; +use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; +use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; +use Symfony\Component\Finder\Iterator\FilenameFilterIterator; +use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; +use Symfony\Component\Finder\Iterator\SortableIterator; + +/** + * Finder allows to build rules to find files and directories. + * + * It is a thin wrapper around several specialized iterator classes. + * + * All rules may be invoked several times. + * + * All methods return the current Finder object to allow easy chaining: + * + * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); + * + * @author Fabien Potencier + */ +class Finder implements \IteratorAggregate, \Countable +{ + const IGNORE_VCS_FILES = 1; + const IGNORE_DOT_FILES = 2; + + private $mode = 0; + private $names = array(); + private $notNames = array(); + private $exclude = array(); + private $filters = array(); + private $depths = array(); + private $sizes = array(); + private $followLinks = false; + private $sort = false; + private $ignore = 0; + private $dirs = array(); + private $dates = array(); + private $iterators = array(); + private $contains = array(); + private $notContains = array(); + private $paths = array(); + private $notPaths = array(); + private $ignoreUnreadableDirs = false; + + private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); + + public function __construct() + { + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + } + + /** + * Creates a new Finder. + * + * @return static + */ + public static function create() + { + return new static(); + } + + /** + * Restricts the matching to directories only. + * + * @return $this + */ + public function directories() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return $this + */ + public function files() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; + + return $this; + } + + /** + * Adds tests for the directory depth. + * + * Usage: + * + * $finder->depth('> 1') // the Finder will start matching at level 1. + * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. + * + * @param string|int $level The depth level expression + * + * @return $this + * + * @see DepthRangeFilterIterator + * @see NumberComparator + */ + public function depth($level) + { + $this->depths[] = new Comparator\NumberComparator($level); + + return $this; + } + + /** + * Adds tests for file dates (last modified). + * + * The date must be something that strtotime() is able to parse: + * + * $finder->date('since yesterday'); + * $finder->date('until 2 days ago'); + * $finder->date('> now - 2 hours'); + * $finder->date('>= 2005-10-15'); + * + * @param string $date A date range string + * + * @return $this + * + * @see strtotime + * @see DateRangeFilterIterator + * @see DateComparator + */ + public function date($date) + { + $this->dates[] = new Comparator\DateComparator($date); + + return $this; + } + + /** + * Adds rules that files must match. + * + * You can use patterns (delimited with / sign), globs or simple strings. + * + * $finder->name('*.php') + * $finder->name('/\.php$/') // same as above + * $finder->name('test.php') + * + * @param string $pattern A pattern (a regexp, a glob, or a string) + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function name($pattern) + { + $this->names[] = $pattern; + + return $this; + } + + /** + * Adds rules that files must not match. + * + * @param string $pattern A pattern (a regexp, a glob, or a string) + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notName($pattern) + { + $this->notNames[] = $pattern; + + return $this; + } + + /** + * Adds tests that file contents must match. + * + * Strings or PCRE patterns can be used: + * + * $finder->contains('Lorem ipsum') + * $finder->contains('/Lorem ipsum/i') + * + * @param string $pattern A pattern (string or regexp) + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function contains($pattern) + { + $this->contains[] = $pattern; + + return $this; + } + + /** + * Adds tests that file contents must not match. + * + * Strings or PCRE patterns can be used: + * + * $finder->notContains('Lorem ipsum') + * $finder->notContains('/Lorem ipsum/i') + * + * @param string $pattern A pattern (string or regexp) + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function notContains($pattern) + { + $this->notContains[] = $pattern; + + return $this; + } + + /** + * Adds rules that filenames must match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->path('some/special/dir') + * $finder->path('/some\/special\/dir/') // same as above + * + * Use only / as dirname separator. + * + * @param string $pattern A pattern (a regexp or a string) + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function path($pattern) + { + $this->paths[] = $pattern; + + return $this; + } + + /** + * Adds rules that filenames must not match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->notPath('some/special/dir') + * $finder->notPath('/some\/special\/dir/') // same as above + * + * Use only / as dirname separator. + * + * @param string $pattern A pattern (a regexp or a string) + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notPath($pattern) + { + $this->notPaths[] = $pattern; + + return $this; + } + + /** + * Adds tests for file sizes. + * + * $finder->size('> 10K'); + * $finder->size('<= 1Ki'); + * $finder->size(4); + * + * @param string|int $size A size range string or an integer + * + * @return $this + * + * @see SizeRangeFilterIterator + * @see NumberComparator + */ + public function size($size) + { + $this->sizes[] = new Comparator\NumberComparator($size); + + return $this; + } + + /** + * Excludes directories. + * + * Directories passed as argument must be relative to the ones defined with the `in()` method. For example: + * + * $finder->in(__DIR__)->exclude('ruby'); + * + * @param string|array $dirs A directory path or an array of directories + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function exclude($dirs) + { + $this->exclude = array_merge($this->exclude, (array) $dirs); + + return $this; + } + + /** + * Excludes "hidden" directories and files (starting with a dot). + * + * This option is enabled by default. + * + * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreDotFiles($ignoreDotFiles) + { + if ($ignoreDotFiles) { + $this->ignore |= static::IGNORE_DOT_FILES; + } else { + $this->ignore &= ~static::IGNORE_DOT_FILES; + } + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * This option is enabled by default. + * + * @param bool $ignoreVCS Whether to exclude VCS files or not + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreVCS($ignoreVCS) + { + if ($ignoreVCS) { + $this->ignore |= static::IGNORE_VCS_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_FILES; + } + + return $this; + } + + /** + * Adds VCS patterns. + * + * @see ignoreVCS() + * + * @param string|string[] $pattern VCS patterns to ignore + */ + public static function addVCSPattern($pattern) + { + foreach ((array) $pattern as $p) { + self::$vcsPatterns[] = $p; + } + + self::$vcsPatterns = array_unique(self::$vcsPatterns); + } + + /** + * Sorts files and directories by an anonymous function. + * + * The anonymous function receives two \SplFileInfo instances to compare. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sort(\Closure $closure) + { + $this->sort = $closure; + + return $this; + } + + /** + * Sorts files and directories by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByName() + { + $this->sort = Iterator\SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by type (directories before files), then by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByType() + { + $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; + + return $this; + } + + /** + * Sorts files and directories by the last accessed time. + * + * This is the time that the file was last accessed, read or written to. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByAccessedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last inode changed time. + * + * This is the time that the inode information was last modified (permissions, owner, group or other metadata). + * + * On Windows, since inode is not available, changed time is actually the file creation time. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByChangedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last modified time. + * + * This is the last time the actual contents of the file were last modified. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByModifiedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; + + return $this; + } + + /** + * Filters the iterator with an anonymous function. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @return $this + * + * @see CustomFilterIterator + */ + public function filter(\Closure $closure) + { + $this->filters[] = $closure; + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return $this + */ + public function followLinks() + { + $this->followLinks = true; + + return $this; + } + + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @param bool $ignore + * + * @return $this + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (bool) $ignore; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string|array $dirs A directory path or an array of directories + * + * @return $this + * + * @throws \InvalidArgumentException if one of the directories does not exist + */ + public function in($dirs) + { + $resolvedDirs = array(); + + foreach ((array) $dirs as $dir) { + if (is_dir($dir)) { + $resolvedDirs[] = $this->normalizeDir($dir); + } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR)) { + $resolvedDirs = array_merge($resolvedDirs, array_map(array($this, 'normalizeDir'), $glob)); + } else { + throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir)); + } + } + + $this->dirs = array_merge($this->dirs, $resolvedDirs); + + return $this; + } + + /** + * Returns an Iterator for the current Finder configuration. + * + * This method implements the IteratorAggregate interface. + * + * @return \Iterator|SplFileInfo[] An iterator + * + * @throws \LogicException if the in() method has not been called + */ + public function getIterator(): \Traversable + { + if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { + throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); + } + + if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { + return $this->searchInDirectory($this->dirs[0]); + } + + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append($this->searchInDirectory($dir)); + } + + foreach ($this->iterators as $it) { + $iterator->append($it); + } + + return $iterator; + } + + /** + * Appends an existing set of files/directories to the finder. + * + * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. + * + * @param iterable $iterator + * + * @return $this + * + * @throws \InvalidArgumentException when the given argument is not iterable + */ + public function append($iterator) + { + if ($iterator instanceof \IteratorAggregate) { + $this->iterators[] = $iterator->getIterator(); + } elseif ($iterator instanceof \Iterator) { + $this->iterators[] = $iterator; + } elseif ($iterator instanceof \Traversable || \is_array($iterator)) { + $it = new \ArrayIterator(); + foreach ($iterator as $file) { + $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); + } + $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); + } + + return $this; + } + + /** + * Check if the any results were found. + * + * @return bool + */ + public function hasResults() + { + foreach ($this->getIterator() as $_) { + return true; + } + + return false; + } + + /** + * Counts all the results collected by the iterators. + * + * @return int + */ + public function count(): int + { + return iterator_count($this->getIterator()); + } + + /** + * @param string $dir + * + * @return \Iterator + */ + private function searchInDirectory($dir) + { + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { + $this->exclude = array_merge($this->exclude, self::$vcsPatterns); + } + + if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { + $this->notPaths[] = '#(^|/)\..+(/|$)#'; + } + + $minDepth = 0; + $maxDepth = PHP_INT_MAX; + + foreach ($this->depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $minDepth = $comparator->getTarget(); + break; + case '<': + $maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $maxDepth = $comparator->getTarget(); + break; + default: + $minDepth = $maxDepth = $comparator->getTarget(); + } + } + + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); + + if ($this->exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); + } + + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + if ($minDepth > 0 || $maxDepth < PHP_INT_MAX) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->paths || $this->notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths); + } + + if ($this->sort) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); + $iterator = $iteratorAggregate->getIterator(); + } + + return $iterator; + } + + /** + * Normalizes given directory names by removing trailing slashes. + * + * Excluding: (s)ftp:// wrapper + * + * @param string $dir + * + * @return string + */ + private function normalizeDir($dir) + { + $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR); + + if (preg_match('#^s?ftp://#', $dir)) { + $dir .= '/'; + } + + return $dir; + } +} diff --git a/freescout-dist/overrides/symfony/finder/Iterator/DateRangeFilterIterator.php b/freescout-dist/overrides/symfony/finder/Iterator/DateRangeFilterIterator.php new file mode 100644 index 0000000..215b496 --- /dev/null +++ b/freescout-dist/overrides/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). + * + * @author Fabien Potencier + */ +class DateRangeFilterIterator extends FilterIterator +{ + private $comparators = array(); + + /** + * @param \Iterator $iterator The Iterator to filter + * @param DateComparator[] $comparators An array of DateComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept(): bool + { + $fileinfo = $this->current(); + + if (!file_exists($fileinfo->getPathname())) { + return false; + } + + $filedate = $fileinfo->getMTime(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filedate)) { + return false; + } + } + + return true; + } +} diff --git a/freescout-dist/overrides/symfony/finder/Iterator/DepthRangeFilterIterator.php b/freescout-dist/overrides/symfony/finder/Iterator/DepthRangeFilterIterator.php new file mode 100644 index 0000000..6359986 --- /dev/null +++ b/freescout-dist/overrides/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * DepthRangeFilterIterator limits the directory depth. + * + * @author Fabien Potencier + */ +class DepthRangeFilterIterator extends FilterIterator +{ + private $minDepth = 0; + + /** + * @param \RecursiveIteratorIterator $iterator The Iterator to filter + * @param int $minDepth The min depth + * @param int $maxDepth The max depth + */ + public function __construct(\RecursiveIteratorIterator $iterator, $minDepth = 0, $maxDepth = PHP_INT_MAX) + { + $this->minDepth = $minDepth; + $iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept(): bool + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} diff --git a/freescout-dist/overrides/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/freescout-dist/overrides/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 0000000..b5118ce --- /dev/null +++ b/freescout-dist/overrides/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @author Fabien Potencier + */ +class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator +{ + private $iterator; + private $isRecursive; + private $excludedDirs = array(); + private $excludedPattern; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param array $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + $this->iterator = $iterator; + $this->isRecursive = $iterator instanceof \RecursiveIterator; + $patterns = array(); + foreach ($directories as $directory) { + $directory = rtrim($directory, '/'); + if (!$this->isRecursive || false !== strpos($directory, '/')) { + $patterns[] = preg_quote($directory, '#'); + } else { + $this->excludedDirs[$directory] = true; + } + } + if ($patterns) { + $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool True if the value should be kept, false otherwise + */ + public function accept(): bool + { + if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { + return false; + } + + if ($this->excludedPattern) { + $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); + $path = str_replace('\\', '/', $path); + + return !preg_match($this->excludedPattern, $path); + } + + return true; + } + + public function hasChildren(): bool + { + return $this->isRecursive && $this->iterator->hasChildren(); + } + + public function getChildren(): ?\RecursiveIterator + { + $children = new self($this->iterator->getChildren(), array()); + $children->excludedDirs = $this->excludedDirs; + $children->excludedPattern = $this->excludedPattern; + + return $children; + } +} diff --git a/freescout-dist/overrides/symfony/finder/Iterator/FileTypeFilterIterator.php b/freescout-dist/overrides/symfony/finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 0000000..8ec6838 --- /dev/null +++ b/freescout-dist/overrides/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FileTypeFilterIterator only keeps files, directories, or both. + * + * @author Fabien Potencier + */ +class FileTypeFilterIterator extends FilterIterator +{ + const ONLY_FILES = 1; + const ONLY_DIRECTORIES = 2; + + private $mode; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + */ + public function __construct(\Iterator $iterator, $mode) + { + $this->mode = $mode; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept(): bool + { + $fileinfo = $this->current(); + if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { + return false; + } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { + return false; + } + + return true; + } +} diff --git a/freescout-dist/overrides/symfony/finder/Iterator/FilenameFilterIterator.php b/freescout-dist/overrides/symfony/finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 0000000..1eeabf1 --- /dev/null +++ b/freescout-dist/overrides/symfony/finder/Iterator/FilenameFilterIterator.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Glob; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept(): bool + { + return $this->isAccepted($this->current()->getFilename()); + } + + /** + * Converts glob to regexp. + * + * PCRE patterns are left unchanged. + * Glob strings are transformed with Glob::toRegex(). + * + * @param string $str Pattern: glob or regexp + * + * @return string regexp corresponding to a given glob or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : Glob::toRegex($str); + } +} diff --git a/freescout-dist/overrides/symfony/finder/Iterator/FilterIterator.php b/freescout-dist/overrides/symfony/finder/Iterator/FilterIterator.php new file mode 100644 index 0000000..f41e0fc --- /dev/null +++ b/freescout-dist/overrides/symfony/finder/Iterator/FilterIterator.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * This iterator just overrides the rewind method in order to correct a PHP bug, + * which existed before version 5.5.23/5.6.7. + * + * @see https://bugs.php.net/68557 + * + * @author Alex Bogomazov + * + * @deprecated since 3.4, to be removed in 4.0. + */ +abstract class FilterIterator extends \FilterIterator +{ + /** + * This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after + * rewind in some cases. + * + * @see FilterIterator::rewind() + */ + public function rewind(): void + { + if (\PHP_VERSION_ID > 50607 || (\PHP_VERSION_ID > 50523 && \PHP_VERSION_ID < 50600)) { + parent::rewind(); + + return; + } + + $iterator = $this; + while ($iterator instanceof \OuterIterator) { + $innerIterator = $iterator->getInnerIterator(); + + if ($innerIterator instanceof RecursiveDirectoryIterator) { + // this condition is necessary for iterators to work properly with non-local filesystems like ftp + if ($innerIterator->isRewindable()) { + $innerIterator->next(); + $innerIterator->rewind(); + } + } elseif ($innerIterator instanceof \FilesystemIterator) { + $innerIterator->next(); + $innerIterator->rewind(); + } + + $iterator = $innerIterator; + } + + parent::rewind(); + } +} diff --git a/freescout-dist/overrides/symfony/finder/Iterator/PathFilterIterator.php b/freescout-dist/overrides/symfony/finder/Iterator/PathFilterIterator.php new file mode 100644 index 0000000..02124ba --- /dev/null +++ b/freescout-dist/overrides/symfony/finder/Iterator/PathFilterIterator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept(): bool + { + $filename = $this->current()->getRelativePathname(); + + if ('\\' === \DIRECTORY_SEPARATOR) { + $filename = str_replace('\\', '/', $filename); + } + + return $this->isAccepted($filename); + } + + /** + * Converts strings to regexp. + * + * PCRE patterns are left unchanged. + * + * Default conversion: + * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' + * + * Use only / as directory separator (on Windows also). + * + * @param string $str Pattern: regexp or dirname + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/freescout-dist/overrides/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/freescout-dist/overrides/symfony/finder/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 0000000..bcdf461 --- /dev/null +++ b/freescout-dist/overrides/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Extends the \RecursiveDirectoryIterator to support relative paths. + * + * @author Victor Berchet + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + /** + * @var bool + */ + private $ignoreUnreadableDirs; + + /** + * @var bool + */ + private $rewindable; + + // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations + private $rootPath; + private $subPath; + private $directorySeparator = '/'; + + /** + * @param string $path + * @param int $flags + * @param bool $ignoreUnreadableDirs + * + * @throws \RuntimeException + */ + public function __construct($path, $flags, $ignoreUnreadableDirs = false) + { + if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { + throw new \RuntimeException('This iterator only support returning current as fileinfo.'); + } + + parent::__construct($path, $flags); + $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; + $this->rootPath = $path; + if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { + $this->directorySeparator = \DIRECTORY_SEPARATOR; + } + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + * + * @return SplFileInfo File information + * : \SplFileInfo|\FilesystemIterator|string + */ + #[\ReturnTypeWillChange] + public function current() + { + // the logic here avoids redoing the same work in all iterations + + if (null === $subPathname = $this->subPath) { + $subPathname = $this->subPath = (string) $this->getSubPath(); + } + if ('' !== $subPathname) { + $subPathname .= $this->directorySeparator; + } + $subPathname .= $this->getFilename(); + + return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname); + } + + /** + * @return \RecursiveIterator + * + * @throws AccessDeniedException + */ + public function getChildren(): RecursiveDirectoryIterator + { + try { + $children = parent::getChildren(); + + if ($children instanceof self) { + // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore + $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; + + // performance optimization to avoid redoing the same work in all children + $children->rewindable = &$this->rewindable; + $children->rootPath = $this->rootPath; + } + + return $children; + } catch (\UnexpectedValueException $e) { + if ($this->ignoreUnreadableDirs) { + // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. + return new \RecursiveArrayIterator(array()); + } else { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Do nothing for non rewindable stream. + */ + public function rewind(): void + { + if (false === $this->isRewindable()) { + return; + } + + // @see https://bugs.php.net/68557 + if (\PHP_VERSION_ID < 50523 || \PHP_VERSION_ID >= 50600 && \PHP_VERSION_ID < 50607) { + parent::next(); + } + + parent::rewind(); + } + + /** + * Checks if the stream is rewindable. + * + * @return bool true when the stream is rewindable, false otherwise + */ + public function isRewindable() + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + // workaround for an HHVM bug, should be removed when https://github.com/facebook/hhvm/issues/7281 is fixed + if ('' === $this->getPath()) { + return $this->rewindable = false; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } +} diff --git a/freescout-dist/overrides/symfony/finder/Iterator/SortableIterator.php b/freescout-dist/overrides/symfony/finder/Iterator/SortableIterator.php new file mode 100644 index 0000000..75ca6e9 --- /dev/null +++ b/freescout-dist/overrides/symfony/finder/Iterator/SortableIterator.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * SortableIterator applies a sort on a given Iterator. + * + * @author Fabien Potencier + */ +class SortableIterator implements \IteratorAggregate +{ + const SORT_BY_NAME = 1; + const SORT_BY_TYPE = 2; + const SORT_BY_ACCESSED_TIME = 3; + const SORT_BY_CHANGED_TIME = 4; + const SORT_BY_MODIFIED_TIME = 5; + + private $iterator; + private $sort; + + /** + * @param \Traversable $iterator The Iterator to filter + * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * + * @throws \InvalidArgumentException + */ + public function __construct(\Traversable $iterator, $sort) + { + $this->iterator = $iterator; + + if (self::SORT_BY_NAME === $sort) { + $this->sort = function ($a, $b) { + return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_TYPE === $sort) { + $this->sort = function ($a, $b) { + if ($a->isDir() && $b->isFile()) { + return -1; + } elseif ($a->isFile() && $b->isDir()) { + return 1; + } + + return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { + $this->sort = function ($a, $b) { + return $a->getATime() - $b->getATime(); + }; + } elseif (self::SORT_BY_CHANGED_TIME === $sort) { + $this->sort = function ($a, $b) { + return $a->getCTime() - $b->getCTime(); + }; + } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { + $this->sort = function ($a, $b) { + return $a->getMTime() - $b->getMTime(); + }; + } elseif (\is_callable($sort)) { + $this->sort = $sort; + } else { + throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); + } + } + + public function getIterator(): \Traversable + { + $array = iterator_to_array($this->iterator, true); + uasort($array, $this->sort); + + return new \ArrayIterator($array); + } +} diff --git a/freescout-dist/overrides/symfony/http-foundation/AcceptHeader.php b/freescout-dist/overrides/symfony/http-foundation/AcceptHeader.php new file mode 100644 index 0000000..a3fe8ec --- /dev/null +++ b/freescout-dist/overrides/symfony/http-foundation/AcceptHeader.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header. + * + * An accept header is compound with a list of items, + * sorted by descending quality. + * + * @author Jean-François Simon + */ +class AcceptHeader +{ + /** + * @var AcceptHeaderItem[] + */ + private $items = array(); + + /** + * @var bool + */ + private $sorted = true; + + /** + * @param AcceptHeaderItem[] $items + */ + public function __construct(array $items) + { + foreach ($items as $item) { + $this->add($item); + } + } + + /** + * Builds an AcceptHeader instance from a string. + * + * @param string $headerValue + * + * @return self + */ + public static function fromString($headerValue) + { + $index = 0; + + return new self(array_map(function ($itemValue) use (&$index) { + $item = AcceptHeaderItem::fromString($itemValue); + $item->setIndex($index++); + + return $item; + }, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', + $headerValue ?? '', + 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE))); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + return implode(',', $this->items); + } + + /** + * Tests if header has given value. + * + * @param string $value + * + * @return bool + */ + public function has($value) + { + return isset($this->items[$value]); + } + + /** + * Returns given value's item, if exists. + * + * @param string $value + * + * @return AcceptHeaderItem|null + */ + public function get($value) + { + return isset($this->items[$value]) ? $this->items[$value] : null; + } + + /** + * Adds an item. + * + * @return $this + */ + public function add(AcceptHeaderItem $item) + { + $this->items[$item->getValue()] = $item; + $this->sorted = false; + + return $this; + } + + /** + * Returns all items. + * + * @return AcceptHeaderItem[] + */ + public function all() + { + $this->sort(); + + return $this->items; + } + + /** + * Filters items on their value using given regex. + * + * @param string $pattern + * + * @return self + */ + public function filter($pattern) + { + return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { + return preg_match($pattern, $item->getValue()); + })); + } + + /** + * Returns first item. + * + * @return AcceptHeaderItem|null + */ + public function first() + { + $this->sort(); + + return !empty($this->items) ? reset($this->items) : null; + } + + /** + * Sorts items by descending quality. + */ + private function sort() + { + if (!$this->sorted) { + uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) { + $qA = $a->getQuality(); + $qB = $b->getQuality(); + + if ($qA === $qB) { + return $a->getIndex() > $b->getIndex() ? 1 : -1; + } + + return $qA > $qB ? -1 : 1; + }); + + $this->sorted = true; + } + } +} diff --git a/freescout-dist/overrides/symfony/http-foundation/Cookie.php b/freescout-dist/overrides/symfony/http-foundation/Cookie.php new file mode 100644 index 0000000..23047f6 --- /dev/null +++ b/freescout-dist/overrides/symfony/http-foundation/Cookie.php @@ -0,0 +1,292 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents a cookie. + * + * @author Johannes M. Schmitt + */ +class Cookie +{ + protected $name; + protected $value; + protected $domain; + protected $expire; + protected $path; + protected $secure; + protected $httpOnly; + private $raw; + private $sameSite; + + const SAMESITE_LAX = 'lax'; + const SAMESITE_STRICT = 'strict'; + const SAMESITE_NONE = 'none'; + + /** + * Creates cookie from raw header string. + * + * @param string $cookie + * @param bool $decode + * + * @return static + */ + public static function fromString($cookie, $decode = false) + { + $data = array( + 'expires' => 0, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => false, + 'raw' => !$decode, + 'samesite' => null, + ); + foreach (explode(';', $cookie) as $part) { + if (false === strpos($part, '=')) { + $key = trim($part); + $value = true; + } else { + list($key, $value) = explode('=', trim($part), 2); + $key = trim($key); + $value = trim($value); + } + if (!isset($data['name'])) { + $data['name'] = $decode ? urldecode($key) : $key; + $data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value); + continue; + } + switch ($key = strtolower($key)) { + case 'name': + case 'value': + break; + case 'max-age': + $data['expires'] = time() + (int) $value; + break; + default: + $data[$key] = $value; + break; + } + } + + return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); + } + + /** + * @param string $name The name of the cookie + * @param string|null $value The value of the cookie + * @param int|string|\DateTimeInterface $expire The time the cookie expires + * @param string $path The path on the server in which the cookie will be available on + * @param string|null $domain The domain that the cookie is available to + * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client + * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol + * @param bool $raw Whether the cookie value should be sent with no url encoding + * @param string|null $sameSite Whether the cookie will be available for cross-site requests + * + * @throws \InvalidArgumentException + */ + public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null) + { + // from PHP source code + if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + + if (empty($name)) { + throw new \InvalidArgumentException('The cookie name cannot be empty.'); + } + + // convert expiration time to a Unix timestamp + if ($expire instanceof \DateTimeInterface) { + $expire = $expire->format('U'); + } elseif (!is_numeric($expire)) { + $expire = strtotime($expire); + + if (false === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = 0 < $expire ? (int) $expire : 0; + $this->path = empty($path) ? '/' : $path; + $this->secure = (bool) $secure; + $this->httpOnly = (bool) $httpOnly; + $this->raw = (bool) $raw; + + if (null !== $sameSite) { + $sameSite = strtolower($sameSite); + } + + if (!\in_array($sameSite, array(self::SAMESITE_LAX, self::SAMESITE_STRICT, self::SAMESITE_NONE, null), true)) { + throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.'); + } + + $this->sameSite = $sameSite; + } + + /** + * Returns the cookie as a string. + * + * @return string The cookie + */ + public function __toString() + { + $str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'='; + + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0'; + } else { + $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue()); + + if (0 !== $this->getExpiresTime()) { + $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge(); + } + } + + if ($this->getPath()) { + $str .= '; path='.$this->getPath(); + } + + if ($this->getDomain()) { + $str .= '; domain='.$this->getDomain(); + } + + if (true === $this->isSecure() || $this->getSameSite() == self::SAMESITE_NONE) { + $str .= '; secure'; + } + + if (true === $this->isHttpOnly()) { + $str .= '; httponly'; + } + + if (null !== $this->getSameSite()) { + $str .= '; samesite='.$this->getSameSite(); + } + + return $str; + } + + /** + * Gets the name of the cookie. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string|null + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the domain that the cookie is available to. + * + * @return string|null + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Gets the time the cookie expires. + * + * @return int + */ + public function getExpiresTime() + { + return $this->expire; + } + + /** + * Gets the max-age attribute. + * + * @return int + */ + public function getMaxAge() + { + $maxAge = $this->expire - time(); + + return 0 >= $maxAge ? 0 : $maxAge; + } + + /** + * Gets the path on the server in which the cookie will be available on. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. + * + * @return bool + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Checks whether the cookie will be made accessible only through the HTTP protocol. + * + * @return bool + */ + public function isHttpOnly() + { + return $this->httpOnly; + } + + /** + * Whether this cookie is about to be cleared. + * + * @return bool + */ + public function isCleared() + { + return 0 !== $this->expire && $this->expire < time(); + } + + /** + * Checks if the cookie value should be sent with no url encoding. + * + * @return bool + */ + public function isRaw() + { + return $this->raw; + } + + /** + * Gets the SameSite attribute. + * + * @return string|null + */ + public function getSameSite() + { + return $this->sameSite; + } +} diff --git a/freescout-dist/overrides/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/freescout-dist/overrides/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php new file mode 100644 index 0000000..a3a3601 --- /dev/null +++ b/freescout-dist/overrides/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; + +/** + * Guesses the mime type with the binary "file" (only available on *nix). + * + * @author Bernhard Schussek + */ +class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $cmd; + + /** + * The $cmd pattern must contain a "%s" string that will be replaced + * with the file name to guess. + * + * The command output must start with the mime type of the file. + * + * @param string $cmd The command to run to get the mime type of a file + */ + public function __construct($cmd = 'file -b --mime -- %s 2>/dev/null') + { + $this->cmd = $cmd; + } + + /** + * Returns whether this guesser is supported on the current OS. + * + * @return bool + */ + public static function isSupported() + { + static $supported = null; + + if (null !== $supported) { + return $supported; + } + + if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) { + return $supported = false; + } + + ob_start(); + passthru('command -v file', $exitStatus); + $binPath = trim(ob_get_clean()); + + return $supported = 0 === $exitStatus && '' !== $binPath; + } + + /** + * {@inheritdoc} + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!self::isSupported()) { + return; + } + + ob_start(); + + // need to use --mime instead of -i. see #6641 + passthru(sprintf($this->cmd, escapeshellarg((0 === strpos($path, '-') ? './' : '').$path)), $return); + if ($return > 0) { + ob_end_clean(); + + return; + } + + $type = trim(ob_get_clean()); + + if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) { + // it's not a type, but an error message + return; + } + + return $match[1]; + } +} diff --git a/freescout-dist/overrides/symfony/http-foundation/FileBag.php b/freescout-dist/overrides/symfony/http-foundation/FileBag.php new file mode 100644 index 0000000..9877eaf --- /dev/null +++ b/freescout-dist/overrides/symfony/http-foundation/FileBag.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\UploadedFile; + +/** + * FileBag is a container for uploaded files. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBag extends ParameterBag +{ + private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type'); + + /** + * @param array $parameters An array of HTTP files + */ + public function __construct(array $parameters = array()) + { + $this->replace($parameters); + } + + /** + * {@inheritdoc} + */ + public function replace(array $files = array()) + { + $this->parameters = array(); + $this->add($files); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + if (!\is_array($value) && !$value instanceof UploadedFile) { + throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); + } + + parent::set($key, $this->convertFileInformation($value)); + } + + /** + * {@inheritdoc} + */ + public function add(array $files = array()) + { + foreach ($files as $key => $file) { + $this->set($key, $file); + } + } + + /** + * Converts uploaded files to UploadedFile instances. + * + * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information + * + * @return UploadedFile[]|UploadedFile|null A (multi-dimensional) array of UploadedFile instances + */ + protected function convertFileInformation($file) + { + if ($file instanceof UploadedFile) { + return $file; + } + + // freescout. + // PHP 8.1 adds full_path + if (isset($file['full_path'])) { + unset($file['full_path']); + } + + $file = $this->fixPhpFilesArray($file); + if (\is_array($file)) { + $keys = array_keys($file); + sort($keys); + + if ($keys == self::$fileKeys) { + if (UPLOAD_ERR_NO_FILE == $file['error']) { + $file = null; + } else { + $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']); + } + } else { + $file = array_map(array($this, 'convertFileInformation'), $file); + if (array_keys($keys) === $keys) { + $file = array_filter($file); + } + } + } + + return $file; + } + + /** + * Fixes a malformed PHP $_FILES array. + * + * PHP has a bug that the format of the $_FILES array differs, depending on + * whether the uploaded file fields had normal field names or array-like + * field names ("normal" vs. "parent[child]"). + * + * This method fixes the array to look like the "normal" $_FILES array. + * + * It's safe to pass an already converted array, in which case this method + * just returns the original array unmodified. + * + * @return array + */ + protected function fixPhpFilesArray($data) + { + if (!\is_array($data)) { + return $data; + } + + $keys = array_keys($data); + sort($keys); + + if (self::$fileKeys != $keys || !isset($data['name']) || !\is_array($data['name'])) { + return $data; + } + + $files = $data; + foreach (self::$fileKeys as $k) { + unset($files[$k]); + } + + foreach ($data['name'] as $key => $name) { + $files[$key] = $this->fixPhpFilesArray(array( + 'error' => $data['error'][$key], + 'name' => $name, + 'type' => $data['type'][$key], + 'tmp_name' => $data['tmp_name'][$key], + 'size' => $data['size'][$key], + )); + } + + return $files; + } +} diff --git a/freescout-dist/overrides/symfony/http-foundation/HeaderBag.php b/freescout-dist/overrides/symfony/http-foundation/HeaderBag.php new file mode 100644 index 0000000..99c47bf --- /dev/null +++ b/freescout-dist/overrides/symfony/http-foundation/HeaderBag.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HeaderBag is a container for HTTP headers. + * + * @author Fabien Potencier + */ +class HeaderBag implements \IteratorAggregate, \Countable +{ + protected $headers = array(); + protected $cacheControl = array(); + + /** + * @param array $headers An array of HTTP headers + */ + public function __construct(array $headers = array()) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the headers as a string. + * + * @return string The headers + */ + public function __toString() + { + if (!$headers = $this->all()) { + return ''; + } + + ksort($headers); + $max = max(array_map('strlen', array_keys($headers))) + 1; + $content = ''; + foreach ($headers as $name => $values) { + $name = implode('-', array_map('ucfirst', explode('-', $name))); + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value); + } + } + + return $content; + } + + /** + * Returns the headers. + * + * @return array An array of headers + */ + public function all() + { + return $this->headers; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + */ + public function keys() + { + return array_keys($this->all()); + } + + /** + * Replaces the current HTTP headers by a new set. + * + * @param array $headers An array of HTTP headers + */ + public function replace(array $headers = array()) + { + $this->headers = array(); + $this->add($headers); + } + + /** + * Adds new headers the current HTTP headers set. + * + * @param array $headers An array of HTTP headers + */ + public function add(array $headers) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns a header value by name. + * + * @param string $key The header name + * @param string|string[]|null $default The default value + * @param bool $first Whether to return the first value or all header values + * + * @return string|string[]|null The first header value or default value if $first is true, an array of values otherwise + */ + public function get($key, $default = null, $first = true) + { + $key = str_replace('_', '-', strtolower($key)); + $headers = $this->all(); + + if (!array_key_exists($key, $headers)) { + if (null === $default) { + return $first ? null : array(); + } + + return $first ? $default : array($default); + } + + if ($first) { + return \count($headers[$key]) ? $headers[$key][0] : $default; + } + + return $headers[$key]; + } + + /** + * Sets a header by name. + * + * @param string $key The key + * @param string|string[] $values The value or an array of values + * @param bool $replace Whether to replace the actual value or not (true by default) + */ + public function set($key, $values, $replace = true) + { + $key = str_replace('_', '-', strtolower($key)); + + if (\is_array($values)) { + $values = array_values($values); + + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = $values; + } else { + $this->headers[$key] = array_merge($this->headers[$key], $values); + } + } else { + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = array($values); + } else { + $this->headers[$key][] = $values; + } + } + + if ('cache-control' === $key) { + $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key])); + } + } + + /** + * Returns true if the HTTP header is defined. + * + * @param string $key The HTTP header + * + * @return bool true if the parameter exists, false otherwise + */ + public function has($key) + { + return array_key_exists(str_replace('_', '-', strtolower($key)), $this->all()); + } + + /** + * Returns true if the given HTTP header contains the given value. + * + * @param string $key The HTTP header name + * @param string $value The HTTP value + * + * @return bool true if the value is contained in the header, false otherwise + */ + public function contains($key, $value) + { + return \in_array($value, $this->get($key, null, false)); + } + + /** + * Removes a header. + * + * @param string $key The HTTP header name + */ + public function remove($key) + { + $key = str_replace('_', '-', strtolower($key)); + + unset($this->headers[$key]); + + if ('cache-control' === $key) { + $this->cacheControl = array(); + } + } + + /** + * Returns the HTTP header value converted to a date. + * + * @param string $key The parameter key + * @param \DateTime $default The default value + * + * @return \DateTime|null The parsed DateTime or the default value if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + */ + public function getDate($key, \DateTime $default = null) + { + if (null === $value = $this->get($key)) { + return $default; + } + + if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) { + throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value)); + } + + return $date; + } + + /** + * Adds a custom Cache-Control directive. + * + * @param string $key The Cache-Control directive name + * @param mixed $value The Cache-Control directive value + */ + public function addCacheControlDirective($key, $value = true) + { + $this->cacheControl[$key] = $value; + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns true if the Cache-Control directive is defined. + * + * @param string $key The Cache-Control directive + * + * @return bool true if the directive exists, false otherwise + */ + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl); + } + + /** + * Returns a Cache-Control directive value by name. + * + * @param string $key The directive name + * + * @return mixed|null The directive value if defined, null otherwise + */ + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null; + } + + /** + * Removes a Cache-Control directive. + * + * @param string $key The Cache-Control directive + */ + public function removeCacheControlDirective($key) + { + unset($this->cacheControl[$key]); + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns an iterator for headers. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->headers); + } + + /** + * Returns the number of headers. + * + * @return int The number of headers + */ + public function count(): int + { + return \count($this->headers); + } + + protected function getCacheControlHeader() + { + $parts = array(); + ksort($this->cacheControl); + foreach ($this->cacheControl as $key => $value) { + if (true === $value) { + $parts[] = $key; + } else { + if (preg_match('#[^a-zA-Z0-9._-]#', $value)) { + $value = '"'.$value.'"'; + } + + $parts[] = "$key=$value"; + } + } + + return implode(', ', $parts); + } + + /** + * Parses a Cache-Control HTTP header. + * + * @param string $header The value of the Cache-Control HTTP header + * + * @return array An array representing the attribute values + */ + protected function parseCacheControl($header) + { + $cacheControl = array(); + preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true); + } + + return $cacheControl; + } +} diff --git a/freescout-dist/overrides/symfony/http-foundation/ParameterBag.php b/freescout-dist/overrides/symfony/http-foundation/ParameterBag.php new file mode 100644 index 0000000..024f3fc --- /dev/null +++ b/freescout-dist/overrides/symfony/http-foundation/ParameterBag.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ParameterBag is a container for key/value pairs. + * + * @author Fabien Potencier + */ +class ParameterBag implements \IteratorAggregate, \Countable +{ + /** + * Parameter storage. + */ + protected $parameters; + + /** + * @param array $parameters An array of parameters + */ + public function __construct(array $parameters = array()) + { + $this->parameters = $parameters; + } + + /** + * Returns the parameters. + * + * @return array An array of parameters + */ + public function all() + { + return $this->parameters; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + */ + public function keys() + { + return array_keys($this->parameters); + } + + /** + * Replaces the current parameters by a new set. + * + * @param array $parameters An array of parameters + */ + public function replace(array $parameters = array()) + { + $this->parameters = $parameters; + } + + /** + * Adds parameters. + * + * @param array $parameters An array of parameters + */ + public function add(array $parameters = array()) + { + $this->parameters = array_replace($this->parameters, $parameters); + } + + /** + * Returns a parameter by name. + * + * @param string $key The key + * @param mixed $default The default value if the parameter key does not exist + * + * @return mixed + */ + public function get($key, $default = null) + { + return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; + } + + /** + * Sets a parameter by name. + * + * @param string $key The key + * @param mixed $value The value + */ + public function set($key, $value) + { + $this->parameters[$key] = $value; + } + + /** + * Returns true if the parameter is defined. + * + * @param string $key The key + * + * @return bool true if the parameter exists, false otherwise + */ + public function has($key) + { + return array_key_exists($key, $this->parameters); + } + + /** + * Removes a parameter. + * + * @param string $key The key + */ + public function remove($key) + { + unset($this->parameters[$key]); + } + + /** + * Returns the alphabetic characters of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * + * @return string The filtered value + */ + public function getAlpha($key, $default = '') + { + return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the alphabetic characters and digits of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * + * @return string The filtered value + */ + public function getAlnum($key, $default = '') + { + return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the digits of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * + * @return string The filtered value + */ + public function getDigits($key, $default = '') + { + // we need to remove - and + because they're allowed in the filter + return str_replace(array('-', '+'), '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT)); + } + + /** + * Returns the parameter value converted to integer. + * + * @param string $key The parameter key + * @param int $default The default value if the parameter key does not exist + * + * @return int The filtered value + */ + public function getInt($key, $default = 0) + { + return (int) $this->get($key, $default); + } + + /** + * Returns the parameter value converted to boolean. + * + * @param string $key The parameter key + * @param mixed $default The default value if the parameter key does not exist + * + * @return bool The filtered value + */ + public function getBoolean($key, $default = false) + { + return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN); + } + + /** + * Filter key. + * + * @param string $key Key + * @param mixed $default Default = null + * @param int $filter FILTER_* constant + * @param mixed $options Filter options + * + * @see http://php.net/manual/en/function.filter-var.php + * + * @return mixed + */ + public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = array()) + { + $value = $this->get($key, $default); + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!\is_array($options) && $options) { + $options = array('flags' => $options); + } + + // Add a convenience check for arrays. + if (\is_array($value) && !isset($options['flags'])) { + $options['flags'] = FILTER_REQUIRE_ARRAY; + } + + return filter_var($value, $filter, $options); + } + + /** + * Returns an iterator for parameters. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->parameters); + } + + /** + * Returns the number of parameters. + * + * @return int The number of parameters + */ + public function count(): int + { + return \count($this->parameters); + } +} diff --git a/freescout-dist/overrides/symfony/http-foundation/Request.php b/freescout-dist/overrides/symfony/http-foundation/Request.php new file mode 100644 index 0000000..acecb6f --- /dev/null +++ b/freescout-dist/overrides/symfony/http-foundation/Request.php @@ -0,0 +1,2188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Request represents an HTTP request. + * + * The methods dealing with URL accept / return a raw path (% encoded): + * * getBasePath + * * getBaseUrl + * * getPathInfo + * * getRequestUri + * * getUri + * * getUriForPath + * + * @author Fabien Potencier + */ +class Request +{ + const HEADER_FORWARDED = 0b00001; // When using RFC 7239 + const HEADER_X_FORWARDED_FOR = 0b00010; + const HEADER_X_FORWARDED_HOST = 0b00100; + const HEADER_X_FORWARDED_PROTO = 0b01000; + const HEADER_X_FORWARDED_PORT = 0b10000; + const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers + const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host + + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_IP = self::HEADER_X_FORWARDED_FOR; + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_HOST = self::HEADER_X_FORWARDED_HOST; + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_PROTO = self::HEADER_X_FORWARDED_PROTO; + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_PORT = self::HEADER_X_FORWARDED_PORT; + + const METHOD_HEAD = 'HEAD'; + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; + const METHOD_PUT = 'PUT'; + const METHOD_PATCH = 'PATCH'; + const METHOD_DELETE = 'DELETE'; + const METHOD_PURGE = 'PURGE'; + const METHOD_OPTIONS = 'OPTIONS'; + const METHOD_TRACE = 'TRACE'; + const METHOD_CONNECT = 'CONNECT'; + + /** + * @var string[] + */ + protected static $trustedProxies = array(); + + /** + * @var string[] + */ + protected static $trustedHostPatterns = array(); + + /** + * @var string[] + */ + protected static $trustedHosts = array(); + + /** + * Names for headers that can be trusted when + * using trusted proxies. + * + * The FORWARDED header is the standard as of rfc7239. + * + * The other headers are non-standard, but widely used + * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). + * + * @deprecated since version 3.3, to be removed in 4.0 + */ + protected static $trustedHeaders = array( + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + ); + + protected static $httpMethodParameterOverride = false; + + /** + * Custom parameters. + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $attributes; + + /** + * Request body parameters ($_POST). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $request; + + /** + * Query string parameters ($_GET). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $query; + + /** + * Server and execution environment parameters ($_SERVER). + * + * @var \Symfony\Component\HttpFoundation\ServerBag + */ + public $server; + + /** + * Uploaded files ($_FILES). + * + * @var \Symfony\Component\HttpFoundation\FileBag + */ + public $files; + + /** + * Cookies ($_COOKIE). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $cookies; + + /** + * Headers (taken from the $_SERVER). + * + * @var \Symfony\Component\HttpFoundation\HeaderBag + */ + public $headers; + + /** + * @var string|resource|false|null + */ + protected $content; + + /** + * @var array + */ + protected $languages; + + /** + * @var array + */ + protected $charsets; + + /** + * @var array + */ + protected $encodings; + + /** + * @var array + */ + protected $acceptableContentTypes; + + /** + * @var string + */ + protected $pathInfo; + + /** + * @var string + */ + protected $requestUri; + + /** + * @var string + */ + protected $baseUrl; + + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $method; + + /** + * @var string + */ + protected $format; + + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + /** + * @var string + */ + protected $locale; + + /** + * @var string + */ + protected $defaultLocale = 'en'; + + /** + * @var array + */ + protected static $formats; + + protected static $requestFactory; + + private $isHostValid = true; + private $isForwardedValid = true; + + private static $trustedHeaderSet = -1; + + /** @deprecated since version 3.3, to be removed in 4.0 */ + private static $trustedHeaderNames = array( + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + ); + + private static $forwardedParams = array( + self::HEADER_X_FORWARDED_FOR => 'for', + self::HEADER_X_FORWARDED_HOST => 'host', + self::HEADER_X_FORWARDED_PROTO => 'proto', + self::HEADER_X_FORWARDED_PORT => 'host', + ); + + /** + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Sets the parameters for this request. + * + * This method also re-initializes all properties. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->request = new ParameterBag($request); + $this->query = new ParameterBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new ParameterBag($cookies); + $this->files = new FileBag($files); + $this->server = new ServerBag($server); + $this->headers = new HeaderBag($this->server->getHeaders()); + + $this->content = $content; + $this->languages = null; + $this->charsets = null; + $this->encodings = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; + } + + /** + * Creates a new request with values from PHP's super globals. + * + * @return static + */ + public static function createFromGlobals() + { + // With the php's bug #66606, the php's built-in web server + // stores the Content-Type and Content-Length header values in + // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields. + $server = $_SERVER; + if ('cli-server' === \PHP_SAPI) { + if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) { + $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH']; + } + if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) { + $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE']; + } + } + + $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server); + + if (0 === strpos($request->headers->get('CONTENT_TYPE') ?? '', 'application/x-www-form-urlencoded') + && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH')) + ) { + parse_str($request->getContent(), $data); + $request->request = new ParameterBag($data); + } + + return $request; + } + + /** + * Creates a Request based on a given URI and configuration. + * + * The information contained in the URI always take precedence + * over the other information (server and parameters). + * + * @param string $uri The URI + * @param string $method The HTTP method + * @param array $parameters The query (GET) or request (POST) parameters + * @param array $cookies The request cookies ($_COOKIE) + * @param array $files The request files ($_FILES) + * @param array $server The server parameters ($_SERVER) + * @param string|resource|null $content The raw body data + * + * @return static + */ + public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null) + { + $server = array_replace(array( + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony/3.X', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'REMOTE_ADDR' => '127.0.0.1', + 'SCRIPT_NAME' => '', + 'SCRIPT_FILENAME' => '', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_TIME' => time(), + ), $server); + + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + + $components = parse_url($uri); + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] .= ':'.$components['port']; + } + + if (isset($components['user'])) { + $server['PHP_AUTH_USER'] = $components['user']; + } + + if (isset($components['pass'])) { + $server['PHP_AUTH_PW'] = $components['pass']; + } + + if (!isset($components['path'])) { + $components['path'] = '/'; + } + + switch (strtoupper($method)) { + case 'POST': + case 'PUT': + case 'DELETE': + if (!isset($server['CONTENT_TYPE'])) { + $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + } + // no break + case 'PATCH': + $request = $parameters; + $query = array(); + break; + default: + $request = array(); + $query = $parameters; + break; + } + + $queryString = ''; + if (isset($components['query'])) { + parse_str(html_entity_decode($components['query']), $qs); + + if ($query) { + $query = array_replace($qs, $query); + $queryString = http_build_query($query, '', '&'); + } else { + $query = $qs; + $queryString = $components['query']; + } + } elseif ($query) { + $queryString = http_build_query($query, '', '&'); + } + + $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : ''); + $server['QUERY_STRING'] = $queryString; + + return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content); + } + + /** + * Sets a callable able to create a Request instance. + * + * This is mainly useful when you need to override the Request class + * to keep BC with an existing system. It should not be used for any + * other purpose. + * + * @param callable|null $callable A PHP callable + */ + public static function setFactory($callable) + { + self::$requestFactory = $callable; + } + + /** + * Clones a request and overrides some of its parameters. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * + * @return static + */ + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + $dup = clone $this; + if (null !== $query) { + $dup->query = new ParameterBag($query); + } + if (null !== $request) { + $dup->request = new ParameterBag($request); + } + if (null !== $attributes) { + $dup->attributes = new ParameterBag($attributes); + } + if (null !== $cookies) { + $dup->cookies = new ParameterBag($cookies); + } + if (null !== $files) { + $dup->files = new FileBag($files); + } + if (null !== $server) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $dup->languages = null; + $dup->charsets = null; + $dup->encodings = null; + $dup->acceptableContentTypes = null; + $dup->pathInfo = null; + $dup->requestUri = null; + $dup->baseUrl = null; + $dup->basePath = null; + $dup->method = null; + $dup->format = null; + + if (!$dup->get('_format') && $this->get('_format')) { + $dup->attributes->set('_format', $this->get('_format')); + } + + if (!$dup->getRequestFormat(null)) { + $dup->setRequestFormat($this->getRequestFormat(null)); + } + + return $dup; + } + + /** + * Clones the current request. + * + * Note that the session is not cloned as duplicated requests + * are most of the time sub-requests of the main one. + */ + public function __clone() + { + $this->query = clone $this->query; + $this->request = clone $this->request; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->files = clone $this->files; + $this->server = clone $this->server; + $this->headers = clone $this->headers; + } + + /** + * Returns the request as a string. + * + * @return string The request + */ + public function __toString() + { + try { + $content = $this->getContent(); + } catch (\LogicException $e) { + return trigger_error($e, E_USER_ERROR); + } + + $cookieHeader = ''; + $cookies = array(); + + foreach ($this->cookies as $k => $v) { + $cookies[] = $k.'='.$v; + } + + if (!empty($cookies)) { + $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n"; + } + + return + sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". + $this->headers. + $cookieHeader."\r\n". + $content; + } + + /** + * Overrides the PHP global variables according to this request instance. + * + * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. + * $_FILES is never overridden, see rfc1867 + */ + public function overrideGlobals() + { + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&'))); + + $_GET = $this->query->all(); + $_POST = $this->request->all(); + $_SERVER = $this->server->all(); + $_COOKIE = $this->cookies->all(); + + foreach ($this->headers->all() as $key => $value) { + $key = strtoupper(str_replace('-', '_', $key)); + if (\in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) { + $_SERVER[$key] = implode(', ', $value); + } else { + $_SERVER['HTTP_'.$key] = implode(', ', $value); + } + } + + $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE); + + $requestOrder = ini_get('request_order') ?: ini_get('variables_order'); + $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; + + $_REQUEST = array(); + foreach (str_split($requestOrder) as $order) { + $_REQUEST = array_merge($_REQUEST, $request[$order]); + } + } + + /** + * Sets a list of trusted proxies. + * + * You should only list the reverse proxies that you manage directly. + * + * @param array $proxies A list of trusted proxies + * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies + * + * @throws \InvalidArgumentException When $trustedHeaderSet is invalid + */ + public static function setTrustedProxies(array $proxies/*, int $trustedHeaderSet*/) + { + self::$trustedProxies = $proxies; + + if (2 > \func_num_args()) { + @trigger_error(sprintf('The %s() method expects a bit field of Request::HEADER_* as second argument since Symfony 3.3. Defining it will be required in 4.0. ', __METHOD__), E_USER_DEPRECATED); + + return; + } + $trustedHeaderSet = (int) func_get_arg(1); + + foreach (self::$trustedHeaderNames as $header => $name) { + self::$trustedHeaders[$header] = $header & $trustedHeaderSet ? $name : null; + } + self::$trustedHeaderSet = $trustedHeaderSet; + } + + /** + * Gets the list of trusted proxies. + * + * @return array An array of trusted proxies + */ + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + + /** + * Gets the set of trusted headers from trusted proxies. + * + * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies + */ + public static function getTrustedHeaderSet() + { + return self::$trustedHeaderSet; + } + + /** + * Sets a list of trusted host patterns. + * + * You should only list the hosts you manage using regexs. + * + * @param array $hostPatterns A list of trusted host patterns + */ + public static function setTrustedHosts(array $hostPatterns) + { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('{%s}i', $hostPattern); + }, $hostPatterns); + // we need to reset trusted hosts on trusted host patterns change + self::$trustedHosts = array(); + } + + /** + * Gets the list of trusted host patterns. + * + * @return array An array of trusted host patterns + */ + public static function getTrustedHosts() + { + return self::$trustedHostPatterns; + } + + /** + * Sets the name for trusted headers. + * + * The following header keys are supported: + * + * * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp()) + * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getHost()) + * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getPort()) + * * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure()) + * * Request::HEADER_FORWARDED: defaults to Forwarded (see RFC 7239) + * + * Setting an empty value allows to disable the trusted header for the given key. + * + * @param string $key The header key + * @param string $value The header name + * + * @throws \InvalidArgumentException + * + * @deprecated since version 3.3, to be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead. + */ + public static function setTrustedHeaderName($key, $value) + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', __METHOD__), E_USER_DEPRECATED); + + if ('forwarded' === $key) { + $key = self::HEADER_FORWARDED; + } elseif ('client_ip' === $key) { + $key = self::HEADER_CLIENT_IP; + } elseif ('client_host' === $key) { + $key = self::HEADER_CLIENT_HOST; + } elseif ('client_proto' === $key) { + $key = self::HEADER_CLIENT_PROTO; + } elseif ('client_port' === $key) { + $key = self::HEADER_CLIENT_PORT; + } elseif (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key)); + } + + self::$trustedHeaders[$key] = $value; + + if (null !== $value) { + self::$trustedHeaderNames[$key] = $value; + self::$trustedHeaderSet |= $key; + } else { + self::$trustedHeaderSet &= ~$key; + } + } + + /** + * Gets the trusted proxy header name. + * + * @param string $key The header key + * + * @return string The header name + * + * @throws \InvalidArgumentException + * + * @deprecated since version 3.3, to be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead. + */ + public static function getTrustedHeaderName($key) + { + if (2 > \func_num_args() || func_get_arg(1)) { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.', __METHOD__), E_USER_DEPRECATED); + } + + if (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key)); + } + + return self::$trustedHeaders[$key]; + } + + /** + * Normalizes a query string. + * + * It builds a normalized query string, where keys/value pairs are alphabetized, + * have consistent escaping and unneeded delimiters are removed. + * + * @param string $qs Query string + * + * @return string A normalized query string for the Request + */ + public static function normalizeQueryString($qs) + { + if ('' == $qs) { + return ''; + } + + $parts = array(); + $order = array(); + + foreach (explode('&', $qs) as $param) { + if ('' === $param || '=' === $param[0]) { + // Ignore useless delimiters, e.g. "x=y&". + // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway. + // PHP also does not include them when building _GET. + continue; + } + + $keyValuePair = explode('=', $param, 2); + + // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded). + // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to + // RFC 3986 with rawurlencode. + $parts[] = isset($keyValuePair[1]) ? + rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) : + rawurlencode(urldecode($keyValuePair[0])); + $order[] = urldecode($keyValuePair[0]); + } + + array_multisort($order, SORT_ASC, $parts); + + return implode('&', $parts); + } + + /** + * Enables support for the _method request parameter to determine the intended HTTP method. + * + * Be warned that enabling this feature might lead to CSRF issues in your code. + * Check that you are using CSRF tokens when required. + * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered + * and used to send a "PUT" or "DELETE" request via the _method request parameter. + * If these methods are not protected against CSRF, this presents a possible vulnerability. + * + * The HTTP method can only be overridden when the real HTTP method is POST. + */ + public static function enableHttpMethodParameterOverride() + { + self::$httpMethodParameterOverride = true; + } + + /** + * Checks whether support for the _method request parameter is enabled. + * + * @return bool True when the _method request parameter is enabled, false otherwise + */ + public static function getHttpMethodParameterOverride() + { + return self::$httpMethodParameterOverride; + } + + /** + * Gets a "parameter" value from any bag. + * + * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the + * flexibility in controllers, it is better to explicitly get request parameters from the appropriate + * public property instead (attributes, query, request). + * + * Order of precedence: PATH (routing placeholders or custom attributes), GET, BODY + * + * @param string $key The key + * @param mixed $default The default value if the parameter key does not exist + * + * @return mixed + */ + public function get($key, $default = null) + { + if ($this !== $result = $this->attributes->get($key, $this)) { + return $result; + } + + if ($this !== $result = $this->query->get($key, $this)) { + return $result; + } + + if ($this !== $result = $this->request->get($key, $this)) { + return $result; + } + + return $default; + } + + /** + * Gets the Session. + * + * @return SessionInterface|null The session + */ + public function getSession() + { + return $this->session; + } + + /** + * Whether the request contains a Session which was started in one of the + * previous requests. + * + * @return bool + */ + public function hasPreviousSession() + { + // the check for $this->session avoids malicious users trying to fake a session cookie with proper name + return $this->hasSession() && $this->cookies->has($this->session->getName()); + } + + /** + * Whether the request contains a Session object. + * + * This method does not give any information about the state of the session object, + * like whether the session is started or not. It is just a way to check if this Request + * is associated with a Session instance. + * + * @return bool true when the Request contains a Session object, false otherwise + */ + public function hasSession() + { + return null !== $this->session; + } + + /** + * Sets the Session. + * + * @param SessionInterface $session The Session + */ + public function setSession(SessionInterface $session) + { + $this->session = $session; + } + + /** + * Returns the client IP addresses. + * + * In the returned array the most trusted IP address is first, and the + * least trusted one last. The "real" client IP address is the last one, + * but this is also the least trusted one. Trusted proxies are stripped. + * + * Use this method carefully; you should use getClientIp() instead. + * + * @return array The client IP addresses + * + * @see getClientIp() + */ + public function getClientIps() + { + // Fix for CloudFlare. + if (isset($_SERVER["HTTP_CF_CONNECTING_IP"]) + && $_SERVER['REMOTE_ADDR'] != $_SERVER["HTTP_CF_CONNECTING_IP"] + && config('app.cloudflare_is_used') + ) { + $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"]; + $this->server->set('REMOTE_ADDR', $_SERVER["HTTP_CF_CONNECTING_IP"]); + } + + $ip = $this->server->get('REMOTE_ADDR'); + + if (!$this->isFromTrustedProxy()) { + return array($ip); + } + + return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip); + } + + /** + * Returns the client IP address. + * + * This method can read the client IP address from the "X-Forwarded-For" header + * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" + * header value is a comma+space separated list of IP addresses, the left-most + * being the original client, and each successive proxy that passed the request + * adding the IP address where it received the request from. + * + * If your reverse proxy uses a different header name than "X-Forwarded-For", + * ("Client-Ip" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @return string|null The client IP address + * + * @see getClientIps() + * @see http://en.wikipedia.org/wiki/X-Forwarded-For + */ + public function getClientIp() + { + $ipAddresses = $this->getClientIps(); + + return $ipAddresses[0]; + } + + /** + * Returns current script name. + * + * @return string + */ + public function getScriptName() + { + return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); + } + + /** + * Returns the path being requested relative to the executed script. + * + * The path info always starts with a /. + * + * Suppose this request is instantiated from /mysite on localhost: + * + * * http://localhost/mysite returns an empty string + * * http://localhost/mysite/about returns '/about' + * * http://localhost/mysite/enco%20ded returns '/enco%20ded' + * * http://localhost/mysite/about?var=1 returns '/about' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getPathInfo() + { + if (null === $this->pathInfo) { + $this->pathInfo = $this->preparePathInfo(); + } + + return $this->pathInfo; + } + + /** + * Returns the root path from which this request is executed. + * + * Suppose that an index.php file instantiates this request object: + * + * * http://localhost/index.php returns an empty string + * * http://localhost/index.php/page returns an empty string + * * http://localhost/web/index.php returns '/web' + * * http://localhost/we%20b/index.php returns '/we%20b' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getBasePath() + { + if (null === $this->basePath) { + $this->basePath = $this->prepareBasePath(); + } + + return $this->basePath; + } + + /** + * Returns the root URL from which this request is executed. + * + * The base URL never ends with a /. + * + * This is similar to getBasePath(), except that it also includes the + * script filename (e.g. index.php) if one exists. + * + * @return string The raw URL (i.e. not urldecoded) + */ + public function getBaseUrl() + { + if (null === $this->baseUrl) { + $this->baseUrl = $this->prepareBaseUrl(); + } + + return $this->baseUrl; + } + + /** + * Gets the request's scheme. + * + * @return string + */ + public function getScheme() + { + return $this->isSecure() ? 'https' : 'http'; + } + + /** + * Returns the port on which the request is made. + * + * This method can read the client port from the "X-Forwarded-Port" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Port" header must contain the client port. + * + * If your reverse proxy uses a different header name than "X-Forwarded-Port", + * configure it via via the $trustedHeaderSet argument of the + * Request::setTrustedProxies() method instead. + * + * @return int|string can be a string if fetched from the server bag + */ + public function getPort() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) { + $host = $host[0]; + } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + return $this->server->get('SERVER_PORT'); + } + + if ('[' === $host[0]) { + $pos = strpos($host, ':', strrpos($host, ']')); + } else { + $pos = strrpos($host, ':'); + } + + if (false !== $pos) { + return (int) substr($host, $pos + 1); + } + + return 'https' === $this->getScheme() ? 443 : 80; + } + + /** + * Returns the user. + * + * @return string|null + */ + public function getUser() + { + return $this->headers->get('PHP_AUTH_USER'); + } + + /** + * Returns the password. + * + * @return string|null + */ + public function getPassword() + { + return $this->headers->get('PHP_AUTH_PW'); + } + + /** + * Gets the user info. + * + * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server + */ + public function getUserInfo() + { + $userinfo = $this->getUser(); + + $pass = $this->getPassword(); + if ('' != $pass) { + $userinfo .= ":$pass"; + } + + return $userinfo; + } + + /** + * Returns the HTTP host being requested. + * + * The port name will be appended to the host if it's non-standard. + * + * @return string + */ + public function getHttpHost() + { + $scheme = $this->getScheme(); + $port = $this->getPort(); + + if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) { + return $this->getHost(); + } + + return $this->getHost().':'.$port; + } + + /** + * Returns the requested URI (path and query string). + * + * @return string The raw URI (i.e. not URI decoded) + */ + public function getRequestUri() + { + if (null === $this->requestUri) { + $this->requestUri = $this->prepareRequestUri(); + } + + return $this->requestUri; + } + + /** + * Gets the scheme and HTTP host. + * + * If the URL was called with basic authentication, the user + * and the password are not added to the generated string. + * + * @return string The scheme and HTTP host + */ + public function getSchemeAndHttpHost() + { + return $this->getScheme().'://'.$this->getHttpHost(); + } + + /** + * Generates a normalized URI (URL) for the Request. + * + * @return string A normalized URI (URL) for the Request + * + * @see getQueryString() + */ + public function getUri() + { + if (null !== $qs = $this->getQueryString()) { + $qs = '?'.$qs; + } + + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs; + } + + /** + * Generates a normalized URI for the given path. + * + * @param string $path A path to use instead of the current one + * + * @return string The normalized URI for the path + */ + public function getUriForPath($path) + { + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; + } + + /** + * Returns the path as relative reference from the current Request path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @param string $path The target path + * + * @return string The relative target path + */ + public function getRelativeUriForPath($path) + { + // be sure that we are dealing with an absolute path + if (!isset($path[0]) || '/' !== $path[0]) { + return $path; + } + + if ($path === $basePath = $this->getPathInfo()) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', substr($path, 1)); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory 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 + // (see http://tools.ietf.org/html/rfc3986#section-4.2). + return !isset($path[0]) || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } + + /** + * Generates the normalized query string for the Request. + * + * It builds a normalized query string, where keys/value pairs are alphabetized + * and have consistent escaping. + * + * @return string|null A normalized query string for the Request + */ + public function getQueryString() + { + $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); + + return '' === $qs ? null : $qs; + } + + /** + * Checks whether the request is secure or not. + * + * This method can read the client protocol from the "X-Forwarded-Proto" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". + * + * If your reverse proxy uses a different header name than "X-Forwarded-Proto" + * ("SSL_HTTPS" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @return bool + */ + public function isSecure() + { + if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) { + return \in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true); + } + + // FreeScout determines protocol using app.url parameter + //$https = $this->server->get('HTTPS'); + + //return !empty($https) && 'off' !== strtolower($https); + return \Helper::isHttps(); + } + + /** + * Returns the host name. + * + * This method can read the client host name from the "X-Forwarded-Host" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Host" header must contain the client host name. + * + * If your reverse proxy uses a different header name than "X-Forwarded-Host", + * configure it via the $trustedHeaderSet argument of the + * Request::setTrustedProxies() method instead. + * + * @return string + * + * @throws SuspiciousOperationException when the host name is invalid or not trusted + */ + public function getHost() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + if (!$host = $this->server->get('SERVER_NAME')) { + $host = $this->server->get('SERVER_ADDR', ''); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) + // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) + // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names + if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host)); + } + + if (\count(self::$trustedHostPatterns) > 0) { + // to avoid host header injection attacks, you should provide a list of trusted host patterns + + if (\in_array($host, self::$trustedHosts)) { + return $host; + } + + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + + return $host; + } + } + + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host)); + } + + return $host; + } + + /** + * Sets the request method. + * + * @param string $method + */ + public function setMethod($method) + { + $this->method = null; + $this->server->set('REQUEST_METHOD', $method); + } + + /** + * Gets the request "intended" method. + * + * If the X-HTTP-Method-Override header is set, and if the method is a POST, + * then it is used to determine the "real" intended HTTP method. + * + * The _method request parameter can also be used to determine the HTTP method, + * but only if enableHttpMethodParameterOverride() has been called. + * + * The method is always an uppercased string. + * + * @return string The request method + * + * @see getRealMethod() + */ + public function getMethod() + { + if (null !== $this->method) { + return $this->method; + } + + $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + + if ('POST' !== $this->method) { + return $this->method; + } + + $method = $this->headers->get('X-HTTP-METHOD-OVERRIDE'); + + if (!$method && self::$httpMethodParameterOverride) { + $method = $this->request->get('_method', $this->query->get('_method', 'POST')); + } + + if (!\is_string($method)) { + return $this->method; + } + + $method = strtoupper($method); + + if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], true)) { + return $this->method = $method; + } + + if (!preg_match('/^[A-Z]++$/D', $method)) { + throw new SuspiciousOperationException(sprintf('Invalid method override "%s".', $method)); + } + + return $this->method = $method; + } + + /** + * Gets the "real" request method. + * + * @return string The request method + * + * @see getMethod() + */ + public function getRealMethod() + { + return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + } + + /** + * Gets the mime type associated with the format. + * + * @param string $format The format + * + * @return string|null The associated mime type (null if not found) + */ + public function getMimeType($format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; + } + + /** + * Gets the mime types associated with the format. + * + * @param string $format The format + * + * @return array The associated mime types + */ + public static function getMimeTypes($format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format] : array(); + } + + /** + * Gets the format associated with the mime type. + * + * @param string $mimeType The associated mime type + * + * @return string|null The format (null if not found) + */ + public function getFormat($mimeType) + { + $canonicalMimeType = null; + if (false !== $pos = strpos($mimeType, ';')) { + $canonicalMimeType = substr($mimeType, 0, $pos); + } + + if (null === static::$formats) { + static::initializeFormats(); + } + + foreach (static::$formats as $format => $mimeTypes) { + if (\in_array($mimeType, (array) $mimeTypes)) { + return $format; + } + if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes)) { + return $format; + } + } + } + + /** + * Associates a format with mime types. + * + * @param string $format The format + * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + */ + public function setFormat($format, $mimeTypes) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : array($mimeTypes); + } + + /** + * Gets the request format. + * + * Here is the process to determine the format: + * + * * format defined by the user (with setRequestFormat()) + * * _format request attribute + * * $default + * + * @param string|null $default The default format + * + * @return string The request format + */ + public function getRequestFormat($default = 'html') + { + if (null === $this->format) { + $this->format = $this->attributes->get('_format'); + } + + return null === $this->format ? $default : $this->format; + } + + /** + * Sets the request format. + * + * @param string $format The request format + */ + public function setRequestFormat($format) + { + $this->format = $format; + } + + /** + * Gets the format associated with the request. + * + * @return string|null The format (null if no content type is present) + */ + public function getContentType() + { + return $this->getFormat($this->headers->get('CONTENT_TYPE')); + } + + /** + * Sets the default locale. + * + * @param string $locale + */ + public function setDefaultLocale($locale) + { + $this->defaultLocale = $locale; + + if (null === $this->locale) { + $this->setPhpDefaultLocale($locale); + } + } + + /** + * Get the default locale. + * + * @return string + */ + public function getDefaultLocale() + { + return $this->defaultLocale; + } + + /** + * Sets the locale. + * + * @param string $locale + */ + public function setLocale($locale) + { + $this->setPhpDefaultLocale($this->locale = $locale); + } + + /** + * Get the locale. + * + * @return string + */ + public function getLocale() + { + return null === $this->locale ? $this->defaultLocale : $this->locale; + } + + /** + * Checks if the request method is of specified type. + * + * @param string $method Uppercase request method (GET, POST etc) + * + * @return bool + */ + public function isMethod($method) + { + return $this->getMethod() === strtoupper($method); + } + + /** + * Checks whether or not the method is safe. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 + * + * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default. + * + * @return bool + */ + public function isMethodSafe(/* $andCacheable = true */) + { + if (!\func_num_args() || func_get_arg(0)) { + // This deprecation should be turned into a BadMethodCallException in 4.0 (without adding the argument in the signature) + // then setting $andCacheable to false should be deprecated in 4.1 + @trigger_error('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since Symfony 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.', E_USER_DEPRECATED); + + return \in_array($this->getMethod(), array('GET', 'HEAD')); + } + + return \in_array($this->getMethod(), array('GET', 'HEAD', 'OPTIONS', 'TRACE')); + } + + /** + * Checks whether or not the method is idempotent. + * + * @return bool + */ + public function isMethodIdempotent() + { + return \in_array($this->getMethod(), array('HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE')); + } + + /** + * Checks whether the method is cacheable or not. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 + * + * @return bool + */ + public function isMethodCacheable() + { + return \in_array($this->getMethod(), array('GET', 'HEAD')); + } + + /** + * Returns the protocol version. + * + * If the application is behind a proxy, the protocol version used in the + * requests between the client and the proxy and between the proxy and the + * server might be different. This returns the former (from the "Via" header) + * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns + * the latter (from the "SERVER_PROTOCOL" server parameter). + * + * @return string + */ + public function getProtocolVersion() + { + if ($this->isFromTrustedProxy()) { + preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via'), $matches); + + if ($matches) { + return 'HTTP/'.$matches[2]; + } + } + + return $this->server->get('SERVER_PROTOCOL'); + } + + /** + * Returns the request body content. + * + * @param bool $asResource If true, a resource will be returned + * + * @return string|resource The request body content or a resource to read the body stream + * + * @throws \LogicException + */ + public function getContent($asResource = false) + { + $currentContentIsResource = \is_resource($this->content); + if (\PHP_VERSION_ID < 50600 && false === $this->content) { + throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.'); + } + + if (true === $asResource) { + if ($currentContentIsResource) { + rewind($this->content); + + return $this->content; + } + + // Content passed in parameter (test) + if (\is_string($this->content)) { + $resource = fopen('php://temp', 'r+'); + fwrite($resource, $this->content); + rewind($resource); + + return $resource; + } + + $this->content = false; + + return fopen('php://input', 'rb'); + } + + if ($currentContentIsResource) { + rewind($this->content); + + return stream_get_contents($this->content); + } + + if (null === $this->content || false === $this->content) { + $this->content = file_get_contents('php://input'); + } + + return $this->content; + } + + /** + * Gets the Etags. + * + * @return array The entity tags + */ + public function getETags() + { + return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY); + } + + /** + * @return bool + */ + public function isNoCache() + { + return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); + } + + /** + * Returns the preferred language. + * + * @param array $locales An array of ordered available locales + * + * @return string|null The preferred locale + */ + public function getPreferredLanguage(array $locales = null) + { + $preferredLanguages = $this->getLanguages(); + + if (empty($locales)) { + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null; + } + + if (!$preferredLanguages) { + return $locales[0]; + } + + $extendedPreferredLanguages = array(); + foreach ($preferredLanguages as $language) { + $extendedPreferredLanguages[] = $language; + if (false !== $position = strpos($language, '_')) { + $superLanguage = substr($language, 0, $position); + if (!\in_array($superLanguage, $preferredLanguages)) { + $extendedPreferredLanguages[] = $superLanguage; + } + } + } + + $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales)); + + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0]; + } + + /** + * Gets a list of languages acceptable by the client browser. + * + * @return array Languages ordered in the user browser preferences + */ + public function getLanguages() + { + if (null !== $this->languages) { + return $this->languages; + } + + $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); + $this->languages = array(); + foreach ($languages as $lang => $acceptHeaderItem) { + if (false !== strpos($lang, '-')) { + $codes = explode('-', $lang); + if ('i' === $codes[0]) { + // Language not listed in ISO 639 that are not variants + // of any listed language, which can be registered with the + // i-prefix, such as i-cherokee + if (\count($codes) > 1) { + $lang = $codes[1]; + } + } else { + for ($i = 0, $max = \count($codes); $i < $max; ++$i) { + if (0 === $i) { + $lang = strtolower($codes[0]); + } else { + $lang .= '_'.strtoupper($codes[$i]); + } + } + } + } + + $this->languages[] = $lang; + } + + return $this->languages; + } + + /** + * Gets a list of charsets acceptable by the client browser. + * + * @return array List of charsets in preferable order + */ + public function getCharsets() + { + if (null !== $this->charsets) { + return $this->charsets; + } + + return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()); + } + + /** + * Gets a list of encodings acceptable by the client browser. + * + * @return array List of encodings in preferable order + */ + public function getEncodings() + { + if (null !== $this->encodings) { + return $this->encodings; + } + + return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()); + } + + /** + * Gets a list of content types acceptable by the client browser. + * + * @return array List of content types in preferable order + */ + public function getAcceptableContentTypes() + { + if (null !== $this->acceptableContentTypes) { + return $this->acceptableContentTypes; + } + + return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); + } + + /** + * Returns true if the request is a XMLHttpRequest. + * + * It works if your JavaScript library sets an X-Requested-With HTTP header. + * It is known to work with common JavaScript frameworks: + * + * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript + * + * @return bool true if the request is an XMLHttpRequest, false otherwise + */ + public function isXmlHttpRequest() + { + return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + + /* + * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) + * + * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + + protected function prepareRequestUri() + { + $requestUri = ''; + + if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) { + // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) + $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->server->has('REQUEST_URI')) { + $requestUri = $this->server->get('REQUEST_URI'); + // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path + $schemeAndHttpHost = $this->getSchemeAndHttpHost(); + if (0 === strpos($requestUri, $schemeAndHttpHost)) { + $requestUri = substr($requestUri, \strlen($schemeAndHttpHost)); + } + } elseif ($this->server->has('ORIG_PATH_INFO')) { + // IIS 5.0, PHP as CGI + $requestUri = $this->server->get('ORIG_PATH_INFO'); + if ('' != $this->server->get('QUERY_STRING')) { + $requestUri .= '?'.$this->server->get('QUERY_STRING'); + } + $this->server->remove('ORIG_PATH_INFO'); + } + + // normalize the request URI to ease creating sub-requests from this request + $this->server->set('REQUEST_URI', $requestUri); + + return $requestUri; + } + + /** + * Prepares the base URL. + * + * @return string + */ + protected function prepareBaseUrl() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + + if (basename($this->server->get('SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('SCRIPT_NAME'); + } elseif (basename($this->server->get('PHP_SELF')) === $filename) { + $baseUrl = $this->server->get('PHP_SELF'); + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = $this->server->get('PHP_SELF', ''); + $file = $this->server->get('SCRIPT_FILENAME', ''); + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = \count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/'.$seg.$baseUrl; + ++$index; + } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + // full $baseUrl matches + return $prefix; + } + + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) { + // directory portion of $baseUrl matches + return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR); + } + + $truncatedRequestUri = $requestUri; + if (false !== $pos = strpos($requestUri, '?')) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl); + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { + // no match whatsoever; set it blank + return ''; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if (\strlen($requestUri) >= \strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) { + $baseUrl = substr($requestUri, 0, $pos + \strlen($baseUrl)); + } + + return rtrim($baseUrl, '/'.\DIRECTORY_SEPARATOR); + } + + /** + * Prepares the base path. + * + * @return string base path + */ + protected function prepareBasePath() + { + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + return ''; + } + + $filename = basename($this->server->get('SCRIPT_FILENAME')); + if (basename($baseUrl) === $filename) { + $basePath = \dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $basePath = str_replace('\\', '/', $basePath); + } + + return rtrim($basePath, '/'); + } + + /** + * Prepares the path info. + * + * @return string path info + */ + protected function preparePathInfo() + { + if (null === ($requestUri = $this->getRequestUri())) { + return '/'; + } + + // Remove the query string from REQUEST_URI + if (false !== $pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if (null === ($baseUrl = $this->getBaseUrl())) { + return $requestUri; + } + + $pathInfo = substr($requestUri, \strlen($baseUrl)); + if (false === $pathInfo || '' === $pathInfo) { + // If substr() returns false then PATH_INFO is set to an empty string + return '/'; + } + + return (string) $pathInfo; + } + + /** + * Initializes HTTP request formats. + */ + protected static function initializeFormats() + { + static::$formats = array( + 'html' => array('text/html', 'application/xhtml+xml'), + 'txt' => array('text/plain'), + 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'), + 'css' => array('text/css'), + 'json' => array('application/json', 'application/x-json'), + 'jsonld' => array('application/ld+json'), + 'xml' => array('text/xml', 'application/xml', 'application/x-xml'), + 'rdf' => array('application/rdf+xml'), + 'atom' => array('application/atom+xml'), + 'rss' => array('application/rss+xml'), + 'form' => array('application/x-www-form-urlencoded'), + ); + } + + /** + * Sets the default PHP locale. + * + * @param string $locale + */ + private function setPhpDefaultLocale($locale) + { + // if either the class Locale doesn't exist, or an exception is thrown when + // setting the default locale, the intl module is not installed, and + // the call can be ignored: + try { + if (class_exists('Locale', false)) { + \Locale::setDefault($locale); + } + } catch (\Exception $e) { + } + } + + /* + * Returns the prefix as encoded in the string when the string starts with + * the given prefix, false otherwise. + * + * @param string $string The urlencoded string + * @param string $prefix The prefix not encoded + * + * @return string|false The prefix as it is encoded in $string, or false + */ + private function getUrlencodedPrefix($string, $prefix) + { + if (0 !== strpos(rawurldecode($string), $prefix)) { + return false; + } + + $len = \strlen($prefix); + + if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) { + return $match[0]; + } + + return false; + } + + private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + if (self::$requestFactory) { + $request = \call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content); + + if (!$request instanceof self) { + throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); + } + + return $request; + } + + return new static($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Indicates whether this request originated from a trusted proxy. + * + * This can be useful to determine whether or not to trust the + * contents of a proxy-specific header. + * + * @return bool true if the request came from a trusted proxy, false otherwise + */ + public function isFromTrustedProxy() + { + return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies); + } + + private function getTrustedValues($type, $ip = null) + { + $clientValues = array(); + $forwardedValues = array(); + + if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) { + foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) { + $clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v); + } + } + + if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) { + $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]); + $forwardedValues = preg_match_all(sprintf('{(?:%s)="?([a-zA-Z0-9\.:_\-/\[\]]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array(); + if (self::HEADER_CLIENT_PORT === $type) { + foreach ($forwardedValues as $k => $v) { + if (']' === substr($v, -1) || false === $v = strrchr($v, ':')) { + $v = $this->isSecure() ? ':443' : ':80'; + } + $forwardedValues[$k] = '0.0.0.0'.$v; + } + } + } + + if (null !== $ip) { + $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip); + $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip); + } + + if ($forwardedValues === $clientValues || !$clientValues) { + return $forwardedValues; + } + + if (!$forwardedValues) { + return $clientValues; + } + + if (!$this->isForwardedValid) { + return null !== $ip ? array('0.0.0.0', $ip) : array(); + } + $this->isForwardedValid = false; + + throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type])); + } + + private function normalizeAndFilterClientIps(array $clientIps, $ip) + { + if (!$clientIps) { + return array(); + } + $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from + $firstTrustedIp = null; + + foreach ($clientIps as $key => $clientIp) { + if (strpos($clientIp, '.')) { + // Strip :port from IPv4 addresses. This is allowed in Forwarded + // and may occur in X-Forwarded-For. + $i = strpos($clientIp, ':'); + if ($i) { + $clientIps[$key] = $clientIp = substr($clientIp, 0, $i); + } + } elseif (0 === strpos($clientIp, '[')) { + // Strip brackets and :port from IPv6 addresses. + $i = strpos($clientIp, ']', 1); + $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1); + } + + if (!filter_var($clientIp, FILTER_VALIDATE_IP)) { + unset($clientIps[$key]); + + continue; + } + + if (IpUtils::checkIp($clientIp, self::$trustedProxies)) { + unset($clientIps[$key]); + + // Fallback to this when the client IP falls into the range of trusted proxies + if (null === $firstTrustedIp) { + $firstTrustedIp = $clientIp; + } + } + } + + // Now the IP chain contains only untrusted proxies and the client IP + return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp); + } +} diff --git a/freescout-dist/overrides/symfony/http-foundation/Response.php b/freescout-dist/overrides/symfony/http-foundation/Response.php new file mode 100644 index 0000000..4037ffd --- /dev/null +++ b/freescout-dist/overrides/symfony/http-foundation/Response.php @@ -0,0 +1,1307 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response. + * + * @author Fabien Potencier + */ +class Response +{ + const HTTP_CONTINUE = 100; + const HTTP_SWITCHING_PROTOCOLS = 101; + const HTTP_PROCESSING = 102; // RFC2518 + const HTTP_EARLY_HINTS = 103; // RFC8297 + const HTTP_OK = 200; + const HTTP_CREATED = 201; + const HTTP_ACCEPTED = 202; + const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + const HTTP_NO_CONTENT = 204; + const HTTP_RESET_CONTENT = 205; + const HTTP_PARTIAL_CONTENT = 206; + const HTTP_MULTI_STATUS = 207; // RFC4918 + const HTTP_ALREADY_REPORTED = 208; // RFC5842 + const HTTP_IM_USED = 226; // RFC3229 + const HTTP_MULTIPLE_CHOICES = 300; + const HTTP_MOVED_PERMANENTLY = 301; + const HTTP_FOUND = 302; + const HTTP_SEE_OTHER = 303; + const HTTP_NOT_MODIFIED = 304; + const HTTP_USE_PROXY = 305; + const HTTP_RESERVED = 306; + const HTTP_TEMPORARY_REDIRECT = 307; + const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 + const HTTP_BAD_REQUEST = 400; + const HTTP_UNAUTHORIZED = 401; + const HTTP_PAYMENT_REQUIRED = 402; + const HTTP_FORBIDDEN = 403; + const HTTP_NOT_FOUND = 404; + const HTTP_METHOD_NOT_ALLOWED = 405; + const HTTP_NOT_ACCEPTABLE = 406; + const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + const HTTP_REQUEST_TIMEOUT = 408; + const HTTP_CONFLICT = 409; + const HTTP_GONE = 410; + const HTTP_LENGTH_REQUIRED = 411; + const HTTP_PRECONDITION_FAILED = 412; + const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + const HTTP_REQUEST_URI_TOO_LONG = 414; + const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + const HTTP_EXPECTATION_FAILED = 417; + const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 + const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 + const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 + const HTTP_LOCKED = 423; // RFC4918 + const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + + /** + * @deprecated + */ + const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817 + const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04 + const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 + const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 + const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 + const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 + const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; + const HTTP_INTERNAL_SERVER_ERROR = 500; + const HTTP_NOT_IMPLEMENTED = 501; + const HTTP_BAD_GATEWAY = 502; + const HTTP_SERVICE_UNAVAILABLE = 503; + const HTTP_GATEWAY_TIMEOUT = 504; + const HTTP_VERSION_NOT_SUPPORTED = 505; + const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 + const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 + const HTTP_LOOP_DETECTED = 508; // RFC5842 + const HTTP_NOT_EXTENDED = 510; // RFC2774 + const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 + + /** + * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var string + */ + protected $version; + + /** + * @var int + */ + protected $statusCode; + + /** + * @var string + */ + protected $statusText; + + /** + * @var string + */ + protected $charset; + + /** + * Status codes translation table. + * + * The list of codes is complete according to the + * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry} + * (last updated 2016-03-01). + * + * Unless otherwise noted, the status code is defined in RFC2616. + * + * @var array + */ + public static $statusTexts = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 103 => 'Early Hints', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 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 Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Payload Too Large', + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 421 => 'Misdirected Request', // RFC7540 + 422 => 'Unprocessable Entity', // RFC4918 + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Too Early', // RFC-ietf-httpbis-replay-04 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 451 => 'Unavailable For Legal Reasons', // RFC7725 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ); + + /** + * @param mixed $content The response content, see setContent() + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + */ + public function __construct($content = '', $status = 200, $headers = array()) + { + $this->headers = new ResponseHeaderBag($headers); + $this->setContent($content); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return Response::create($body, 200) + * ->setSharedMaxAge(300); + * + * @param mixed $content The response content, see setContent() + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($content = '', $status = 200, $headers = array()) + { + return new static($content, $status, $headers); + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @return string The Response as an HTTP string + * + * @see prepare() + */ + public function __toString() + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Clones the current Response instance. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /** + * Prepares the Response before it is sent to the client. + * + * This method tweaks the Response to ensure that it is + * compliant with RFC 2616. Most of the changes are based on + * the Request that is "associated" with this Response. + * + * @return $this + */ + public function prepare(Request $request) + { + $headers = $this->headers; + + if ($this->isInformational() || $this->isEmpty()) { + $this->setContent(null); + $headers->remove('Content-Type'); + $headers->remove('Content-Length'); + } else { + // Content-type based on the Request + if (!$headers->has('Content-Type')) { + $format = $request->getRequestFormat(); + if (null !== $format && $mimeType = $request->getMimeType($format)) { + $headers->set('Content-Type', $mimeType); + } + } + + // Fix Content-Type + $charset = $this->charset ?: 'UTF-8'; + if (!$headers->has('Content-Type')) { + $headers->set('Content-Type', 'text/html; charset='.$charset); + } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) { + // add the charset + $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset); + } + + // Fix Content-Length + if ($headers->has('Transfer-Encoding')) { + $headers->remove('Content-Length'); + } + + if ($request->isMethod('HEAD')) { + // cf. RFC2616 14.13 + $length = $headers->get('Content-Length'); + $this->setContent(null); + if ($length) { + $headers->set('Content-Length', $length); + } + } + } + + // Fix protocol + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + // Check if we need to send extra expire info headers + if ('1.0' == $this->getProtocolVersion() && false !== strpos($this->headers->get('Cache-Control'), 'no-cache')) { + $this->headers->set('pragma', 'no-cache'); + $this->headers->set('expires', -1); + } + + $this->ensureIEOverSSLCompatibility($request); + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return $this + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + // headers + foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { + foreach ($values as $value) { + header($name.': '.$value, false, $this->statusCode); + } + } + + // cookies + foreach ($this->headers->getCookies() as $cookie) { + header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode); + } + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return $this + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return $this + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + + if (\function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) { + static::closeOutputBuffers(0, true); + } + + return $this; + } + + /** + * Sets the response content. + * + * Valid types are strings, numbers, null, and objects that implement a __toString() method. + * + * @param mixed $content Content that can be cast to string + * + * @return $this + * + * @throws \UnexpectedValueException + */ + public function setContent($content) + { + if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable(array($content, '__toString'))) { + throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * Gets the current response content. + * + * @return string Content + */ + public function getContent() + { + return $this->content; + } + + /** + * Sets the HTTP protocol version (1.0 or 1.1). + * + * @param string $version The HTTP protocol version + * + * @return $this + * + * @final since version 3.2 + */ + public function setProtocolVersion($version) + { + $this->version = $version; + + return $this; + } + + /** + * Gets the HTTP protocol version. + * + * @return string The HTTP protocol version + * + * @final since version 3.2 + */ + public function getProtocolVersion() + { + return $this->version; + } + + /** + * Sets the response status code. + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise. + * + * @param int $code HTTP status code + * @param mixed $text HTTP status text + * + * @return $this + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @final since version 3.2 + */ + public function setStatusCode($code, $text = null) + { + $this->statusCode = $code = (int) $code; + if ($this->isInvalid()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status'; + + return $this; + } + + if (false === $text) { + $this->statusText = ''; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + /** + * Retrieves the status code for the current web response. + * + * @return int Status code + * + * @final since version 3.2 + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Sets the response charset. + * + * @param string $charset Character set + * + * @return $this + * + * @final since version 3.2 + */ + public function setCharset($charset) + { + $this->charset = $charset; + + return $this; + } + + /** + * Retrieves the response charset. + * + * @return string Character set + * + * @final since version 3.2 + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Returns true if the response may safely be kept in a shared (surrogate) cache. + * + * Responses marked "private" with an explicit Cache-Control directive are + * considered uncacheable. + * + * Responses with neither a freshness lifetime (Expires, max-age) nor cache + * validator (Last-Modified, ETag) are considered uncacheable because there is + * no way to tell when or how to remove them from the cache. + * + * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation, + * for example "status codes that are defined as cacheable by default [...] + * can be reused by a cache with heuristic expiration unless otherwise indicated" + * (https://tools.ietf.org/html/rfc7231#section-6.1) + * + * @return bool true if the response is worth caching, false otherwise + * + * @final since version 3.3 + */ + public function isCacheable() + { + if (!\in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) { + return false; + } + + if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { + return false; + } + + return $this->isValidateable() || $this->isFresh(); + } + + /** + * Returns true if the response is "fresh". + * + * Fresh responses may be served from cache without any interaction with the + * origin. A response is considered fresh when it includes a Cache-Control/max-age + * indicator or Expires header and the calculated age is less than the freshness lifetime. + * + * @return bool true if the response is fresh, false otherwise + * + * @final since version 3.3 + */ + public function isFresh() + { + return $this->getTtl() > 0; + } + + /** + * Returns true if the response includes headers that can be used to validate + * the response with the origin server using a conditional GET request. + * + * @return bool true if the response is validateable, false otherwise + * + * @final since version 3.3 + */ + public function isValidateable() + { + return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); + } + + /** + * Marks the response as "private". + * + * It makes the response ineligible for serving other clients. + * + * @return $this + * + * @final since version 3.2 + */ + public function setPrivate() + { + $this->headers->removeCacheControlDirective('public'); + $this->headers->addCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "public". + * + * It makes the response eligible for serving other clients. + * + * @return $this + * + * @final since version 3.2 + */ + public function setPublic() + { + $this->headers->addCacheControlDirective('public'); + $this->headers->removeCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "immutable". + * + * @param bool $immutable enables or disables the immutable directive + * + * @return $this + * + * @final + */ + public function setImmutable($immutable = true) + { + if ($immutable) { + $this->headers->addCacheControlDirective('immutable'); + } else { + $this->headers->removeCacheControlDirective('immutable'); + } + + return $this; + } + + /** + * Returns true if the response is marked as "immutable". + * + * @return bool returns true if the response is marked as "immutable"; otherwise false + * + * @final + */ + public function isImmutable() + { + return $this->headers->hasCacheControlDirective('immutable'); + } + + /** + * Returns true if the response must be revalidated by caches. + * + * This method indicates that the response must not be served stale by a + * cache in any circumstance without first revalidating with the origin. + * When present, the TTL of the response should not be overridden to be + * greater than the value provided by the origin. + * + * @return bool true if the response must be revalidated by a cache, false otherwise + * + * @final since version 3.3 + */ + public function mustRevalidate() + { + return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate'); + } + + /** + * Returns the Date header as a DateTime instance. + * + * @return \DateTime A \DateTime instance + * + * @throws \RuntimeException When the header is not parseable + * + * @final since version 3.2 + */ + public function getDate() + { + return $this->headers->getDate('Date'); + } + + /** + * Sets the Date header. + * + * @return $this + * + * @final since version 3.2 + */ + public function setDate(\DateTime $date) + { + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the age of the response. + * + * @return int The age of the response in seconds + * + * @final since version 3.2 + */ + public function getAge() + { + if (null !== $age = $this->headers->get('Age')) { + return (int) $age; + } + + return max(time() - $this->getDate()->format('U'), 0); + } + + /** + * Marks the response stale by setting the Age header to be equal to the maximum age of the response. + * + * @return $this + */ + public function expire() + { + if ($this->isFresh()) { + $this->headers->set('Age', $this->getMaxAge()); + $this->headers->remove('Expires'); + } + + return $this; + } + + /** + * Returns the value of the Expires header as a DateTime instance. + * + * @return \DateTime|null A DateTime instance or null if the header does not exist + * + * @final since version 3.2 + */ + public function getExpires() + { + try { + return $this->headers->getDate('Expires'); + } catch (\RuntimeException $e) { + // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past + return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000'); + } + } + + /** + * Sets the Expires HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @param \DateTime|null $date A \DateTime instance or null to remove the header + * + * @return $this + * + * @final since version 3.2 + */ + public function setExpires(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Expires'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + } + + return $this; + } + + /** + * Returns the number of seconds after the time specified in the response's Date + * header when the response should no longer be considered fresh. + * + * First, it checks for a s-maxage directive, then a max-age directive, and then it falls + * back on an expires header. It returns null when no maximum age can be established. + * + * @return int|null Number of seconds + * + * @final since version 3.2 + */ + public function getMaxAge() + { + if ($this->headers->hasCacheControlDirective('s-maxage')) { + return (int) $this->headers->getCacheControlDirective('s-maxage'); + } + + if ($this->headers->hasCacheControlDirective('max-age')) { + return (int) $this->headers->getCacheControlDirective('max-age'); + } + + if (null !== $this->getExpires()) { + return $this->getExpires()->format('U') - $this->getDate()->format('U'); + } + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh. + * + * This methods sets the Cache-Control max-age directive. + * + * @param int $value Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setMaxAge($value) + { + $this->headers->addCacheControlDirective('max-age', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. + * + * This methods sets the Cache-Control s-maxage directive. + * + * @param int $value Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setSharedMaxAge($value) + { + $this->setPublic(); + $this->headers->addCacheControlDirective('s-maxage', $value); + + return $this; + } + + /** + * Returns the response's time-to-live in seconds. + * + * It returns null when no freshness information is present in the response. + * + * When the responses TTL is <= 0, the response may not be served from cache without first + * revalidating with the origin. + * + * @return int|null The TTL in seconds + * + * @final since version 3.2 + */ + public function getTtl() + { + if (null !== $maxAge = $this->getMaxAge()) { + return $maxAge - $this->getAge(); + } + } + + /** + * Sets the response's time-to-live for shared caches. + * + * This method adjusts the Cache-Control/s-maxage directive. + * + * @param int $seconds Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setTtl($seconds) + { + $this->setSharedMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Sets the response's time-to-live for private/client caches. + * + * This method adjusts the Cache-Control/max-age directive. + * + * @param int $seconds Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setClientTtl($seconds) + { + $this->setMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Returns the Last-Modified HTTP header as a DateTime instance. + * + * @return \DateTime|null A DateTime instance or null if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + * + * @final since version 3.2 + */ + public function getLastModified() + { + return $this->headers->getDate('Last-Modified'); + } + + /** + * Sets the Last-Modified HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @param \DateTime|null $date A \DateTime instance or null to remove the header + * + * @return $this + * + * @final since version 3.2 + */ + public function setLastModified(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Last-Modified'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + } + + return $this; + } + + /** + * Returns the literal value of the ETag HTTP header. + * + * @return string|null The ETag HTTP header or null if it does not exist + * + * @final since version 3.2 + */ + public function getEtag() + { + return $this->headers->get('ETag'); + } + + /** + * Sets the ETag value. + * + * @param string|null $etag The ETag unique identifier or null to remove the header + * @param bool $weak Whether you want a weak ETag or not + * + * @return $this + * + * @final since version 3.2 + */ + public function setEtag($etag = null, $weak = false) + { + if (null === $etag) { + $this->headers->remove('Etag'); + } else { + if (0 !== strpos($etag, '"')) { + $etag = '"'.$etag.'"'; + } + + $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag); + } + + return $this; + } + + /** + * Sets the response's cache headers (validation and/or expiration). + * + * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable. + * + * @param array $options An array of cache options + * + * @return $this + * + * @throws \InvalidArgumentException + * + * @final since version 3.3 + */ + public function setCache(array $options) + { + if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'))) { + throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff))); + } + + if (isset($options['etag'])) { + $this->setEtag($options['etag']); + } + + if (isset($options['last_modified'])) { + $this->setLastModified($options['last_modified']); + } + + if (isset($options['max_age'])) { + $this->setMaxAge($options['max_age']); + } + + if (isset($options['s_maxage'])) { + $this->setSharedMaxAge($options['s_maxage']); + } + + if (isset($options['public'])) { + if ($options['public']) { + $this->setPublic(); + } else { + $this->setPrivate(); + } + } + + if (isset($options['private'])) { + if ($options['private']) { + $this->setPrivate(); + } else { + $this->setPublic(); + } + } + + if (isset($options['immutable'])) { + $this->setImmutable((bool) $options['immutable']); + } + + return $this; + } + + /** + * Modifies the response so that it conforms to the rules defined for a 304 status code. + * + * This sets the status, removes the body, and discards any headers + * that MUST NOT be included in 304 responses. + * + * @return $this + * + * @see http://tools.ietf.org/html/rfc2616#section-10.3.5 + * + * @final since version 3.3 + */ + public function setNotModified() + { + $this->setStatusCode(304); + $this->setContent(null); + + // remove headers that MUST NOT be included with 304 Not Modified responses + foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) { + $this->headers->remove($header); + } + + return $this; + } + + /** + * Returns true if the response includes a Vary header. + * + * @return bool true if the response includes a Vary header, false otherwise + * + * @final since version 3.2 + */ + public function hasVary() + { + return null !== $this->headers->get('Vary'); + } + + /** + * Returns an array of header names given in the Vary header. + * + * @return array An array of Vary names + * + * @final since version 3.2 + */ + public function getVary() + { + if (!$vary = $this->headers->get('Vary', null, false)) { + return array(); + } + + $ret = array(); + foreach ($vary as $item) { + $ret = array_merge($ret, preg_split('/[\s,]+/', $item)); + } + + return $ret; + } + + /** + * Sets the Vary header. + * + * @param string|array $headers + * @param bool $replace Whether to replace the actual value or not (true by default) + * + * @return $this + * + * @final since version 3.2 + */ + public function setVary($headers, $replace = true) + { + $this->headers->set('Vary', $headers, $replace); + + return $this; + } + + /** + * Determines if the Response validators (ETag, Last-Modified) match + * a conditional value specified in the Request. + * + * If the Response is not modified, it sets the status code to 304 and + * removes the actual content by calling the setNotModified() method. + * + * @return bool true if the Response validators match the Request, false otherwise + * + * @final since version 3.3 + */ + public function isNotModified(Request $request) + { + if (!$request->isMethodCacheable()) { + return false; + } + + $notModified = false; + $lastModified = $this->headers->get('Last-Modified'); + $modifiedSince = $request->headers->get('If-Modified-Since'); + + if ($etags = $request->getETags()) { + $notModified = \in_array($this->getEtag(), $etags) || \in_array('*', $etags); + } + + if ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified); + } + + if ($notModified) { + $this->setNotModified(); + } + + return $notModified; + } + + /** + * Is response invalid? + * + * @return bool + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + * + * @final since version 3.2 + */ + public function isInvalid() + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Is response informative? + * + * @return bool + * + * @final since version 3.3 + */ + public function isInformational() + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * Is response successful? + * + * @return bool + * + * @final since version 3.2 + */ + public function isSuccessful() + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * Is the response a redirect? + * + * @return bool + * + * @final since version 3.2 + */ + public function isRedirection() + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Is there a client error? + * + * @return bool + * + * @final since version 3.2 + */ + public function isClientError() + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Was there a server side error? + * + * @return bool + * + * @final since version 3.3 + */ + public function isServerError() + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Is the response OK? + * + * @return bool + * + * @final since version 3.2 + */ + public function isOk() + { + return 200 === $this->statusCode; + } + + /** + * Is the response forbidden? + * + * @return bool + * + * @final since version 3.2 + */ + public function isForbidden() + { + return 403 === $this->statusCode; + } + + /** + * Is the response a not found error? + * + * @return bool + * + * @final since version 3.2 + */ + public function isNotFound() + { + return 404 === $this->statusCode; + } + + /** + * Is the response a redirect of some form? + * + * @param string $location + * + * @return bool + * + * @final since version 3.2 + */ + public function isRedirect($location = null) + { + return \in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location')); + } + + /** + * Is the response empty? + * + * @return bool + * + * @final since version 3.2 + */ + public function isEmpty() + { + return \in_array($this->statusCode, array(204, 304)); + } + + /** + * Cleans or flushes output buffers up to target level. + * + * Resulting level can be greater than target level if a non-removable buffer has been encountered. + * + * @param int $targetLevel The target output buffering level + * @param bool $flush Whether to flush or clean the buffers + * + * @final since version 3.3 + */ + public static function closeOutputBuffers($targetLevel, $flush) + { + $status = ob_get_status(true); + $level = \count($status); + // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3 + $flags = \defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1; + + while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { + if ($flush) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + } + + /** + * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9. + * + * @see http://support.microsoft.com/kb/323308 + * + * @final since version 3.3 + */ + protected function ensureIEOverSSLCompatibility(Request $request) + { + if (false !== stripos($this->headers->get('Content-Disposition') ?? '', 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT') ?? '', $match) && true === $request->isSecure()) { + if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { + $this->headers->remove('Cache-Control'); + } + } + } +} diff --git a/freescout-dist/overrides/symfony/http-foundation/ResponseHeaderBag.php b/freescout-dist/overrides/symfony/http-foundation/ResponseHeaderBag.php new file mode 100644 index 0000000..28e33f1 --- /dev/null +++ b/freescout-dist/overrides/symfony/http-foundation/ResponseHeaderBag.php @@ -0,0 +1,341 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ResponseHeaderBag is a container for Response HTTP headers. + * + * @author Fabien Potencier + */ +class ResponseHeaderBag extends HeaderBag +{ + const COOKIES_FLAT = 'flat'; + const COOKIES_ARRAY = 'array'; + + const DISPOSITION_ATTACHMENT = 'attachment'; + const DISPOSITION_INLINE = 'inline'; + + protected $computedCacheControl = array(); + protected $cookies = array(); + protected $headerNames = array(); + + public function __construct(array $headers = array()) + { + parent::__construct($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!isset($this->headers['date'])) { + $this->initDate(); + } + } + + /** + * Returns the headers, with original capitalizations. + * + * @return array An array of headers + */ + public function allPreserveCase() + { + $headers = array(); + foreach ($this->all() as $name => $value) { + $headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value; + } + + return $headers; + } + + public function allPreserveCaseWithoutCookies() + { + $headers = $this->allPreserveCase(); + if (isset($this->headerNames['set-cookie'])) { + unset($headers[$this->headerNames['set-cookie']]); + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function replace(array $headers = array()) + { + $this->headerNames = array(); + + parent::replace($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + + if (!isset($this->headers['date'])) { + $this->initDate(); + } + } + + /** + * {@inheritdoc} + */ + public function all() + { + $headers = parent::all(); + foreach ($this->getCookies() as $cookie) { + $headers['set-cookie'][] = (string) $cookie; + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function set($key, $values, $replace = true) + { + $uniqueKey = str_replace('_', '-', strtolower($key)); + + if ('set-cookie' === $uniqueKey) { + if ($replace) { + $this->cookies = array(); + } + foreach ((array) $values as $cookie) { + $this->setCookie(Cookie::fromString($cookie)); + } + $this->headerNames[$uniqueKey] = $key; + + return; + } + + $this->headerNames[$uniqueKey] = $key; + + parent::set($key, $values, $replace); + + // ensure the cache-control header has sensible defaults + if (\in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'), true)) { + $computed = $this->computeCacheControlValue(); + $this->headers['cache-control'] = array($computed); + $this->headerNames['cache-control'] = 'Cache-Control'; + $this->computedCacheControl = $this->parseCacheControl($computed); + } + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + $uniqueKey = str_replace('_', '-', strtolower($key)); + unset($this->headerNames[$uniqueKey]); + + if ('set-cookie' === $uniqueKey) { + $this->cookies = array(); + + return; + } + + parent::remove($key); + + if ('cache-control' === $uniqueKey) { + $this->computedCacheControl = array(); + } + + if ('date' === $uniqueKey) { + $this->initDate(); + } + } + + /** + * {@inheritdoc} + */ + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl); + } + + /** + * {@inheritdoc} + */ + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null; + } + + public function setCookie(Cookie $cookie) + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + $this->headerNames['set-cookie'] = 'Set-Cookie'; + } + + /** + * Removes a cookie from the array, but does not unset it in the browser. + * + * @param string $name + * @param string $path + * @param string $domain + */ + public function removeCookie($name, $path = '/', $domain = null) + { + if (null === $path) { + $path = '/'; + } + + unset($this->cookies[$domain][$path][$name]); + + if (empty($this->cookies[$domain][$path])) { + unset($this->cookies[$domain][$path]); + + if (empty($this->cookies[$domain])) { + unset($this->cookies[$domain]); + } + } + + if (empty($this->cookies)) { + unset($this->headerNames['set-cookie']); + } + } + + /** + * Returns an array with all cookies. + * + * @param string $format + * + * @return Cookie[] + * + * @throws \InvalidArgumentException When the $format is invalid + */ + public function getCookies($format = self::COOKIES_FLAT) + { + if (!\in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) { + throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY)))); + } + + if (self::COOKIES_ARRAY === $format) { + return $this->cookies; + } + + $flattenedCookies = array(); + foreach ($this->cookies as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Clears a cookie in the browser. + * + * @param string $name + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + */ + public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true) + { + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly)); + } + + /** + * Generates a HTTP Content-Disposition field-value. + * + * @param string $disposition One of "inline" or "attachment" + * @param string $filename A unicode string + * @param string $filenameFallback A string containing only ASCII characters that + * is semantically equivalent to $filename. If the filename is already ASCII, + * it can be omitted, or just copied from $filename + * + * @return string A string suitable for use as a Content-Disposition field-value + * + * @throws \InvalidArgumentException + * + * @see RFC 6266 + */ + public function makeDisposition($disposition, $filename, $filenameFallback = '') + { + if (!\in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + + if ('' == $filenameFallback) { + $filenameFallback = $filename; + } + + # 533 + // filenameFallback is not ASCII. + // if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { + // throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + // } + + // percent characters aren't safe in fallback. + // if (false !== strpos($filenameFallback, '%')) { + // throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + // } + + // path separators aren't allowed in either. + if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + + $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback)); + + if ($filename !== $filenameFallback) { + $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename)); + } + + return $output; + } + + /** + * Returns the calculated value of the cache-control header. + * + * This considers several other headers and calculates or modifies the + * cache-control header to a sensible, conservative value. + * + * @return string + */ + protected function computeCacheControlValue() + { + if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) { + return 'no-cache, private'; + } + + if (!$this->cacheControl) { + // conservative by default + return 'private, must-revalidate'; + } + + $header = $this->getCacheControlHeader(); + if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { + return $header; + } + + // public if s-maxage is defined, private otherwise + if (!isset($this->cacheControl['s-maxage'])) { + return $header.', private'; + } + + return $header; + } + + private function initDate() + { + $now = \DateTime::createFromFormat('U', time()); + $now->setTimezone(new \DateTimeZone('UTC')); + $this->set('Date', $now->format('D, d M Y H:i:s').' GMT'); + } +} diff --git a/freescout-dist/overrides/symfony/http-kernel/Exception/HttpException.php b/freescout-dist/overrides/symfony/http-kernel/Exception/HttpException.php new file mode 100644 index 0000000..295bc52 --- /dev/null +++ b/freescout-dist/overrides/symfony/http-kernel/Exception/HttpException.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * HttpException. + * + * @author Kris Wallsmith + */ +class HttpException extends \RuntimeException implements HttpExceptionInterface +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = array(), $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message ?? '', $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } + + /** + * Set response headers. + * + * @param array $headers Response headers + */ + public function setHeaders(array $headers) + { + $this->headers = $headers; + } +} diff --git a/freescout-dist/overrides/symfony/http-kernel/HttpCache/Store.php b/freescout-dist/overrides/symfony/http-kernel/HttpCache/Store.php new file mode 100644 index 0000000..80586ed --- /dev/null +++ b/freescout-dist/overrides/symfony/http-kernel/HttpCache/Store.php @@ -0,0 +1,503 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Store implements all the logic for storing cache metadata (Request and Response headers). + * + * @author Fabien Potencier + */ +class Store implements StoreInterface +{ + protected $root; + private $keyCache; + private $locks = []; + private $options; + + /** + * Constructor. + * + * The available options are: + * + * * private_headers Set of response headers that should not be stored + * when a response is cached. (default: Set-Cookie) + * + * @throws \RuntimeException + */ + public function __construct($root, array $options = []) + { + $this->root = $root; + if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { + throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root)); + } + $this->keyCache = new \SplObjectStorage(); + $this->options = array_merge([ + 'private_headers' => ['Set-Cookie'], + ], $options); + } + + /** + * Cleanups storage. + */ + public function cleanup() + { + // unlock everything + foreach ($this->locks as $lock) { + flock($lock, LOCK_UN); + fclose($lock); + } + + $this->locks = array(); + } + + /** + * Tries to lock the cache for a given Request, without blocking. + * + * @return bool|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request) + { + $key = $this->getCacheKey($request); + + if (!isset($this->locks[$key])) { + $path = $this->getPath($key); + if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { + return $path; + } + $h = fopen($path, 'cb'); + if (!flock($h, LOCK_EX | LOCK_NB)) { + fclose($h); + + return $path; + } + + $this->locks[$key] = $h; + } + + return true; + } + + /** + * Releases the lock for the given Request. + * + * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request) + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + flock($this->locks[$key], LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + + return true; + } + + return false; + } + + public function isLocked(Request $request) + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + return true; // shortcut if lock held by this process + } + + if (!file_exists($path = $this->getPath($key))) { + return false; + } + + $h = fopen($path, 'rb'); + flock($h, LOCK_EX | LOCK_NB, $wouldBlock); + flock($h, LOCK_UN); // release the lock we just acquired + fclose($h); + + return (bool) $wouldBlock; + } + + /** + * Locates a cached Response for the Request provided. + * + * @return Response|null A Response instance, or null if no cache entry was found + */ + public function lookup(Request $request) + { + $key = $this->getCacheKey($request); + + if (!$entries = $this->getMetadata($key)) { + return; + } + + // find a cached entry that matches the request. + $match = null; + foreach ($entries as $entry) { + if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) { + $match = $entry; + + break; + } + } + + if (null === $match) { + return; + } + + $headers = $match[1]; + if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) { + return $this->restoreResponse($headers, $body); + } + + // TODO the metaStore referenced an entity that doesn't exist in + // the entityStore. We definitely want to return nil but we should + // also purge the entry from the meta-store when this is detected. + } + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @return string The key under which the response is stored + * + * @throws \RuntimeException + */ + public function write(Request $request, Response $response) + { + $key = $this->getCacheKey($request); + $storedEnv = $this->persistRequest($request); + + // write the response body to the entity store if this is the original response + if (!$response->headers->has('X-Content-Digest')) { + $digest = $this->generateContentDigest($response); + + if (false === $this->save($digest, $response->getContent())) { + throw new \RuntimeException('Unable to store the entity.'); + } + + $response->headers->set('X-Content-Digest', $digest); + + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', \strlen($response->getContent())); + } + } + + // read existing cache entries, remove non-varying, and add this one to the list + $entries = array(); + $vary = $response->headers->get('vary'); + foreach ($this->getMetadata($key) as $entry) { + if (!isset($entry[1]['vary'][0])) { + $entry[1]['vary'] = array(''); + } + + if ($entry[1]['vary'][0] != $vary || !$this->requestsMatch($vary, $entry[0], $storedEnv)) { + $entries[] = $entry; + } + } + + $headers = $this->persistResponse($response); + unset($headers['age']); + + foreach ($this->options['private_headers'] as $h) { + unset($headers[strtolower($h)]); + } + + array_unshift($entries, array($storedEnv, $headers)); + + if (false === $this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + + return $key; + } + + /** + * Returns content digest for $response. + * + * @return string + */ + protected function generateContentDigest(Response $response) + { + return 'en'.hash('sha256', $response->getContent()); + } + + /** + * Invalidates all cache entries that match the request. + * + * @throws \RuntimeException + */ + public function invalidate(Request $request) + { + $modified = false; + $key = $this->getCacheKey($request); + + $entries = array(); + foreach ($this->getMetadata($key) as $entry) { + $response = $this->restoreResponse($entry[1]); + if ($response->isFresh()) { + $response->expire(); + $modified = true; + $entries[] = array($entry[0], $this->persistResponse($response)); + } else { + $entries[] = $entry; + } + } + + if ($modified && false === $this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + } + + /** + * Determines whether two Request HTTP header sets are non-varying based on + * the vary response header value provided. + * + * @param string $vary A Response vary header + * @param array $env1 A Request HTTP header array + * @param array $env2 A Request HTTP header array + * + * @return bool true if the two environments match, false otherwise + */ + private function requestsMatch($vary, $env1, $env2) + { + if (empty($vary)) { + return true; + } + + foreach (preg_split('/[\s,]+/', $vary) as $header) { + $key = str_replace('_', '-', strtolower($header)); + $v1 = isset($env1[$key]) ? $env1[$key] : null; + $v2 = isset($env2[$key]) ? $env2[$key] : null; + if ($v1 !== $v2) { + return false; + } + } + + return true; + } + + /** + * Gets all data associated with the given key. + * + * Use this method only if you know what you are doing. + * + * @param string $key The store key + * + * @return array An array of data associated with the key + */ + private function getMetadata($key) + { + if (!$entries = $this->load($key)) { + return array(); + } + + return unserialize($entries); + } + + /** + * Purges data for the given URL. + * + * This method purges both the HTTP and the HTTPS version of the cache entry. + * + * @param string $url A URL + * + * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise + */ + public function purge($url) + { + $http = preg_replace('#^https:#', 'http:', $url); + $https = preg_replace('#^http:#', 'https:', $url); + + $purgedHttp = $this->doPurge($http); + $purgedHttps = $this->doPurge($https); + + return $purgedHttp || $purgedHttps; + } + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + private function doPurge($url) + { + $key = $this->getCacheKey(Request::create($url)); + if (isset($this->locks[$key])) { + flock($this->locks[$key], LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + } + + if (file_exists($path = $this->getPath($key))) { + unlink($path); + + return true; + } + + return false; + } + + /** + * Loads data for the given key. + * + * @param string $key The store key + * + * @return string The data associated with the key + */ + private function load($key) + { + $path = $this->getPath($key); + + return file_exists($path) ? file_get_contents($path) : false; + } + + /** + * Save data for the given key. + * + * @param string $key The store key + * @param string $data The data to store + * + * @return bool + */ + private function save($key, $data) + { + $path = $this->getPath($key); + + if (isset($this->locks[$key])) { + $fp = $this->locks[$key]; + @ftruncate($fp, 0); + @fseek($fp, 0); + $len = @fwrite($fp, $data); + if (\strlen($data) !== $len) { + @ftruncate($fp, 0); + + return false; + } + } else { + if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { + return false; + } + + $tmpFile = tempnam(\dirname($path), basename($path)); + if (false === $fp = @fopen($tmpFile, 'wb')) { + @unlink($tmpFile); + + return false; + } + @fwrite($fp, $data); + @fclose($fp); + + if ($data != file_get_contents($tmpFile)) { + @unlink($tmpFile); + + return false; + } + + if (false === @rename($tmpFile, $path)) { + @unlink($tmpFile); + + return false; + } + } + + @chmod($path, 0666 & ~umask()); + } + + public function getPath($key) + { + return $this->root.\DIRECTORY_SEPARATOR.substr($key, 0, 2).\DIRECTORY_SEPARATOR.substr($key, 2, 2).\DIRECTORY_SEPARATOR.substr($key, 4, 2).\DIRECTORY_SEPARATOR.substr($key, 6); + } + + /** + * Generates a cache key for the given Request. + * + * This method should return a key that must only depend on a + * normalized version of the request URI. + * + * If the same URI can have more than one representation, based on some + * headers, use a Vary header to indicate them, and each representation will + * be stored independently under the same cache key. + * + * @return string A key for the given Request + */ + protected function generateCacheKey(Request $request) + { + return 'md'.hash('sha256', $request->getUri()); + } + + /** + * Returns a cache key for the given Request. + * + * @return string A key for the given Request + */ + private function getCacheKey(Request $request) + { + if (isset($this->keyCache[$request])) { + return $this->keyCache[$request]; + } + + return $this->keyCache[$request] = $this->generateCacheKey($request); + } + + /** + * Persists the Request HTTP headers. + * + * @return array An array of HTTP headers + */ + private function persistRequest(Request $request) + { + return $request->headers->all(); + } + + /** + * Persists the Response HTTP headers. + * + * @return array An array of HTTP headers + */ + private function persistResponse(Response $response) + { + $headers = $response->headers->all(); + $headers['X-Status'] = array($response->getStatusCode()); + + return $headers; + } + + /** + * Restores a Response from the HTTP headers and body. + * + * @param array $headers An array of HTTP headers for the Response + * @param string $body The Response body + * + * @return Response + */ + private function restoreResponse($headers, $body = null) + { + $status = $headers['X-Status'][0]; + unset($headers['X-Status']); + + if (null !== $body) { + $headers['X-Body-File'] = array($body); + } + + return new Response($body, $status, $headers); + } +} diff --git a/freescout-dist/overrides/symfony/http-kernel/UriSigner.php b/freescout-dist/overrides/symfony/http-kernel/UriSigner.php new file mode 100644 index 0000000..ecc0efa --- /dev/null +++ b/freescout-dist/overrides/symfony/http-kernel/UriSigner.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * Signs URIs. + * + * @author Fabien Potencier + */ +class UriSigner +{ + private $secret; + private $parameter; + + /** + * @param string $secret A secret + * @param string $parameter Query string parameter to use + */ + public function __construct($secret, $parameter = '_hash') + { + $this->secret = $secret; + $this->parameter = $parameter; + } + + /** + * Signs a URI. + * + * The given URI is signed by adding the query string parameter + * which value depends on the URI and the secret. + * + * @param string $uri A URI to sign + * + * @return string The signed URI + */ + public function sign($uri) + { + $url = parse_url($uri); + if (isset($url['query'])) { + parse_str($url['query'], $params); + } else { + $params = array(); + } + + $uri = $this->buildUrl($url, $params); + + return $uri.(false === strpos($uri, '?') ? '?' : '&').$this->parameter.'='.$this->computeHash($uri); + } + + /** + * Checks that a URI contains the correct hash. + * + * @param string $uri A signed URI + * + * @return bool True if the URI is signed correctly, false otherwise + */ + public function check($uri) + { + $url = parse_url($uri); + if (isset($url['query'])) { + parse_str($url['query'], $params); + } else { + $params = array(); + } + + if (empty($params[$this->parameter])) { + return false; + } + + $hash = urlencode($params[$this->parameter]); + unset($params[$this->parameter]); + + return hash_equals($this->computeHash($this->buildUrl($url, $params)), $hash); + } + + private function computeHash($uri) + { + return urlencode(base64_encode(hash_hmac('sha256', $uri, $this->secret, true))); + } + + private function buildUrl(array $url, array $params = array()) + { + ksort($params, SORT_STRING); + $url['query'] = http_build_query($params, '', '&'); + + $scheme = isset($url['scheme']) ? $url['scheme'].'://' : ''; + $host = isset($url['host']) ? $url['host'] : ''; + $port = isset($url['port']) ? ':'.$url['port'] : ''; + $user = isset($url['user']) ? $url['user'] : ''; + $pass = isset($url['pass']) ? ':'.$url['pass'] : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = isset($url['path']) ? $url['path'] : ''; + $query = isset($url['query']) && $url['query'] ? '?'.$url['query'] : ''; + $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : ''; + + return $scheme.$user.$pass.$host.$port.$path.$query.$fragment; + } +} diff --git a/freescout-dist/overrides/symfony/process/Process.php b/freescout-dist/overrides/symfony/process/Process.php new file mode 100644 index 0000000..a19b003 --- /dev/null +++ b/freescout-dist/overrides/symfony/process/Process.php @@ -0,0 +1,1751 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Pipes\PipesInterface; +use Symfony\Component\Process\Pipes\UnixPipes; +use Symfony\Component\Process\Pipes\WindowsPipes; + +/** + * Process is a thin wrapper around proc_* functions to easily + * start independent PHP processes. + * + * @author Fabien Potencier + * @author Romain Neutron + */ +class Process implements \IteratorAggregate +{ + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + // Timeout Precision in seconds. + const TIMEOUT_PRECISION = 0.2; + + const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking + const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory + const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating + const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating + + private $callback; + private $hasCallback = false; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $options = array('suppress_errors' => true); + private $exitcode; + private $fallbackStatus = array(); + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility = true; + private $enhanceSigchildCompatibility; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty; + private $pty; + private $inheritEnv = false; + + private $useFileHandles = false; + /** @var PipesInterface */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + */ + public static $exitCodes = array( + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ); + + /** + * @param string|array $commandline The command line to run + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * @param array $options An array of options for proc_open + * + * @throws RuntimeException When proc_open is not installed + */ + public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null) + { + if (!\function_exists('proc_open')) { + throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started + // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected + // @see : https://bugs.php.net/bug.php?id=51800 + // @see : https://bugs.php.net/bug.php?id=50524 + if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->setInput($input); + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR; + $this->pty = false; + $this->enhanceSigchildCompatibility = '\\' !== \DIRECTORY_SEPARATOR && $this->isSigchildEnabled(); + if (null !== $options) { + @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED); + $this->options = array_replace($this->options, $options); + } + } + + public function __destruct() + { + $this->stop(0); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * Runs the process. + * + * The callback receives the type of output (out or err) and + * some bytes from the output in real-time. It allows to have feedback + * from the independent process during execution. + * + * The STDOUT and STDERR are also available after the process is finished + * via the getOutput() and getErrorOutput() methods. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param array $env An array of additional env vars to set when running the process + * + * @return int The exit status code + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process stopped after receiving signal + * @throws LogicException In case a callback is provided and output has been disabled + * + * @final since version 3.3 + */ + public function run($callback = null/*, array $env = array()*/) + { + $env = 1 < \func_num_args() ? func_get_arg(1) : null; + $this->start($callback, $env); + + return $this->wait(); + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param callable|null $callback + * @param array $env An array of additional env vars to set when running the process + * + * @return self + * + * @throws RuntimeException if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled + * @throws ProcessFailedException if the process didn't terminate successfully + * + * @final since version 3.3 + */ + public function mustRun(callable $callback = null/*, array $env = array()*/) + { + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + $env = 1 < \func_num_args() ? func_get_arg(1) : null; + + if (0 !== $this->run($callback, $env)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * Starts the process and returns after writing the input to STDIN. + * + * This method blocks until all STDIN data is sent to the process then it + * returns while the process runs in the background. + * + * The termination of the process can be awaited with wait(). + * + * The callback receives the type of output (out or err) and some bytes from + * the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param array $env An array of additional env vars to set when running the process + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws LogicException In case a callback is provided and output has been disabled + */ + public function start(callable $callback = null/*, array $env = array()*/) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running'); + } + if (2 <= \func_num_args()) { + $env = func_get_arg(1); + } else { + if (__CLASS__ !== static::class) { + $r = new \ReflectionMethod($this, __FUNCTION__); + if (__CLASS__ !== $r->getDeclaringClass()->getName() && (2 > $r->getNumberOfParameters() || 'env' !== $r->getParameters()[1]->name)) { + @trigger_error(sprintf('The %s::start() method expects a second "$env" argument since Symfony 3.3. It will be made mandatory in 4.0.', static::class), E_USER_DEPRECATED); + } + } + $env = null; + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $this->hasCallback = null !== $callback; + $descriptors = $this->getDescriptors(); + $inheritEnv = $this->inheritEnv; + + if (\is_array($commandline = $this->commandline)) { + $commandline = implode(' ', array_map(array($this, 'escapeArgument'), $commandline)); + + if ('\\' !== \DIRECTORY_SEPARATOR) { + // exec is mandatory to deal with sending a signal to the process + $commandline = 'exec '.$commandline; + } + } + + if (null === $env) { + $env = $this->env; + } else { + if ($this->env) { + $env += $this->env; + } + $inheritEnv = true; + } + + if (null !== $env && $inheritEnv) { + $env += $this->getDefaultEnv(); + } elseif (null !== $env) { + @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', E_USER_DEPRECATED); + } else { + $env = $this->getDefaultEnv(); + } + if ('\\' === \DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { + $this->options['bypass_shell'] = true; + $commandline = $this->prepareWindowsCommandLine($commandline, $env); + } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors[3] = array('pipe', 'w'); + + // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input + $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; + $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code'; + + // Workaround for the bug, when PTS functionality is enabled. + // @see : https://bugs.php.net/69442 + $ptsWorkaround = fopen(__FILE__, 'r'); + } + if (\defined('HHVM_VERSION')) { + $envPairs = $env; + } else { + $envPairs = array(); + foreach ($env as $k => $v) { + if (false !== $v) { + $envPairs[] = $k.'='.$v; + } + } + } + + if (!is_dir($this->cwd)) { + @trigger_error('The provided cwd does not exist. Command is currently ran against getcwd(). This behavior is deprecated since Symfony 3.4 and will be removed in 4.0.', E_USER_DEPRECATED); + } + + $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options); + + if (!\is_resource($this->process)) { + throw new RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if (isset($descriptors[3])) { + $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); + } + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * Restarts the process. + * + * Be warned that the process is cloned before being started. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param array $env An array of additional env vars to set when running the process + * + * @return $this + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * + * @see start() + * + * @final since version 3.3 + */ + public function restart(callable $callback = null/*, array $env = array()*/) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running'); + } + $env = 1 < \func_num_args() ? func_get_arg(1) : null; + + $process = clone $this; + $process->start($callback, $env); + + return $process; + } + + /** + * Waits for the process to terminate. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A valid PHP callback + * + * @return int The exitcode of the process + * + * @throws RuntimeException When process timed out + * @throws RuntimeException When process stopped after receiving signal + * @throws LogicException When process is not yet started + */ + public function wait(callable $callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + + if (null !== $callback) { + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait'); + } + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running); + } while ($running); + + while ($this->isRunning()) { + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * Returns the Pid (process identifier), if applicable. + * + * @return int|null The process id if running, null otherwise + */ + public function getPid() + { + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php) + * + * @return $this + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * Disables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + * @throws LogicException if an idle timeout is set + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new LogicException('Output can not be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * Returns true in case the output is disabled, false otherwise. + * + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @return string The process output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the output incrementally. + * + * In comparison with the getOutput method which always return the whole + * output, this one returns the new output since the last call. + * + * @return string The process output since the last call + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + $this->incrementalOutputOffset = ftell($this->stdout); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR). + * + * @param int $flags A bit field of Process::ITER_* flags + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + * + * @return \Generator + */ + public function getIterator($flags = 0): \Traversable + { + $this->readPipesForOutput(__FUNCTION__, false); + + $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); + $blocking = !(self::ITER_NON_BLOCKING & $flags); + $yieldOut = !(self::ITER_SKIP_OUT & $flags); + $yieldErr = !(self::ITER_SKIP_ERR & $flags); + + while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { + if ($yieldOut) { + $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + + if (isset($out[0])) { + if ($clearOutput) { + $this->clearOutput(); + } else { + $this->incrementalOutputOffset = ftell($this->stdout); + } + + yield self::OUT => $out; + } + } + + if ($yieldErr) { + $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + + if (isset($err[0])) { + if ($clearOutput) { + $this->clearErrorOutput(); + } else { + $this->incrementalErrorOutputOffset = ftell($this->stderr); + } + + yield self::ERR => $err; + } + } + + if (!$blocking && !isset($out[0]) && !isset($err[0])) { + yield self::OUT => ''; + } + + $this->checkTimeout(); + $this->readPipesForOutput(__FUNCTION__, $blocking); + } + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearOutput() + { + ftruncate($this->stdout, 0); + fseek($this->stdout, 0); + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @return string The process error output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getErrorOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the errorOutput incrementally. + * + * In comparison with the getErrorOutput method which always return the + * whole error output, this one returns the new error output since the last + * call. + * + * @return string The process error output since the last call + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalErrorOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + $this->incrementalErrorOutputOffset = ftell($this->stderr); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearErrorOutput() + { + ftruncate($this->stderr, 0); + fseek($this->stderr, 0); + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * Returns the exit code returned by the process. + * + * @return int|null The exit status code, null if the Process is not terminated + * + * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled + */ + public function getExitCode() + { + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * Returns a string representation for the exit code returned by the process. + * + * This method relies on the Unix exit code status standardization + * and might not be relevant for other operating systems. + * + * @return string|null A string representation for the exit status code, null if the Process is not terminated + * + * @see http://tldp.org/LDP/abs/html/exitcodes.html + * @see http://en.wikipedia.org/wiki/Unix_signal + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return; + } + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + * + * @return bool true if the process ended successfully, false otherwise + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + return $this->processInformation['signaled']; + } + + /** + * Returns the number of the signal that caused the child process to terminate its execution. + * + * It is only meaningful if hasBeenSignaled() returns true. + * + * @return int + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopped']; + } + + /** + * Returns the number of the signal that caused the child process to stop its execution. + * + * It is only meaningful if hasBeenStopped() returns true. + * + * @return int + * + * @throws LogicException In case the process is not terminated + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + * + * @return bool true if the process is currently running, false otherwise + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * Checks if the process has been started with no regard to the current state. + * + * @return bool true if status is ready, false otherwise + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * Checks if the process is terminated. + * + * @return bool true if process is terminated, false otherwise + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + * + * @return string The current process status + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param int|float $timeout The timeout in seconds + * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) + * + * @return int The exit-code of the process + */ + public function stop($timeout = 10, $signal = null) + { + $timeoutMicro = microtime(true) + $timeout; + if ($this->isRunning()) { + // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here + $this->doSignal(15, false); + do { + usleep(1000); + } while ($this->isRunning() && microtime(true) < $timeoutMicro); + + if ($this->isRunning()) { + // Avoid exception here: process is supposed to be running, but it might have stopped just + // after this line. In any case, let's silently discard the error, we cannot do anything. + $this->doSignal($signal ?: 9, false); + } + } + + if ($this->isRunning()) { + if (isset($this->fallbackStatus['pid'])) { + unset($this->fallbackStatus['pid']); + + return $this->stop(0, $signal); + } + $this->close(); + } + + return $this->exitcode; + } + + /** + * Adds a line to the STDOUT stream. + * + * @internal + * + * @param string $line The line to append + */ + public function addOutput($line) + { + $this->lastOutputTime = microtime(true); + + fseek($this->stdout, 0, SEEK_END); + fwrite($this->stdout, $line); + fseek($this->stdout, $this->incrementalOutputOffset); + } + + /** + * Adds a line to the STDERR stream. + * + * @internal + * + * @param string $line The line to append + */ + public function addErrorOutput($line) + { + $this->lastOutputTime = microtime(true); + + fseek($this->stderr, 0, SEEK_END); + fwrite($this->stderr, $line); + fseek($this->stderr, $this->incrementalErrorOutputOffset); + } + + /** + * Gets the command line to be executed. + * + * @return string The command to execute + */ + public function getCommandLine() + { + return \is_array($this->commandline) ? implode(' ', array_map(array($this, 'escapeArgument'), $this->commandline)) : $this->commandline; + } + + /** + * Sets the command line to be executed. + * + * @param string|array $commandline The command to execute + * + * @return self The current Process instance + */ + public function setCommandLine($commandline) + { + $this->commandline = $commandline; + + return $this; + } + + /** + * Gets the process timeout (max. runtime). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Gets the process idle timeout (max. time since last output). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getIdleTimeout() + { + return $this->idleTimeout; + } + + /** + * Sets the process timeout (max. runtime). + * + * To disable the timeout, set this value to null. + * + * @param int|float|null $timeout The timeout in seconds + * + * @return self The current Process instance + * + * @throws InvalidArgumentException if the timeout is negative + */ + public function setTimeout($timeout) + { + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Sets the process idle timeout (max. time since last output). + * + * To disable the timeout, set this value to null. + * + * @param int|float|null $timeout The timeout in seconds + * + * @return self The current Process instance + * + * @throws LogicException if the output is disabled + * @throws InvalidArgumentException if the timeout is negative + */ + public function setIdleTimeout($timeout) + { + if (null !== $timeout && $this->outputDisabled) { + throw new LogicException('Idle timeout can not be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Enables or disables the TTY mode. + * + * @param bool $tty True to enabled and false to disable + * + * @return self The current Process instance + * + * @throws RuntimeException In case the TTY mode is not supported + */ + public function setTty($tty) + { + if ('\\' === \DIRECTORY_SEPARATOR && $tty) { + throw new RuntimeException('TTY mode is not supported on Windows platform.'); + } + if ($tty) { + static $isTtySupported; + + if (null === $isTtySupported) { + $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', array(array('file', '/dev/tty', 'r'), array('file', '/dev/tty', 'w'), array('file', '/dev/tty', 'w')), $pipes); + } + + if (!$isTtySupported) { + throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); + } + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * Checks if the TTY mode is enabled. + * + * @return bool true if the TTY mode is enabled, false otherwise + */ + public function isTty() + { + return $this->tty; + } + + /** + * Sets PTY mode. + * + * @param bool $bool + * + * @return self + */ + public function setPty($bool) + { + $this->pty = (bool) $bool; + + return $this; + } + + /** + * Returns PTY state. + * + * @return bool + */ + public function isPty() + { + return $this->pty; + } + + /** + * Gets the working directory. + * + * @return string|null The current working directory or null on failure + */ + public function getWorkingDirectory() + { + if (null === $this->cwd) { + // getcwd() will return false if any one of the parent directories does not have + // the readable or search mode set, even if the current directory does + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * Sets the current working directory. + * + * @param string $cwd The new working directory + * + * @return self The current Process instance + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + * + * @return array The current environment variables + */ + public function getEnv() + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * Each environment variable value should be a string. + * If it is an array, the variable is ignored. + * If it is false or null, it will be removed when + * env vars are otherwise inherited. + * + * That happens in PHP when 'argv' is registered into + * the $_ENV array for instance. + * + * @param array $env The new environment variables + * + * @return self The current Process instance + */ + public function setEnv(array $env) + { + // Process can not handle env values that are arrays + $env = array_filter($env, function ($value) { + return !\is_array($value); + }); + + $this->env = $env; + + return $this; + } + + /** + * Gets the Process input. + * + * @return resource|string|\Iterator|null The Process input + */ + public function getInput() + { + return $this->input; + } + + /** + * Sets the input. + * + * This content will be passed to the underlying process standard input. + * + * @param string|int|float|bool|resource|\Traversable|null $input The content + * + * @return self The current Process instance + * + * @throws LogicException In case the process is running + */ + public function setInput($input) + { + if ($this->isRunning()) { + throw new LogicException('Input can not be set while the process is running.'); + } + + $this->input = ProcessUtils::validateInput(__METHOD__, $input); + + return $this; + } + + /** + * Gets the options for proc_open. + * + * @return array The current options + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function getOptions() + { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + + return $this->options; + } + + /** + * Sets the options for proc_open. + * + * @param array $options The new options + * + * @return self The current Process instance + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function setOptions(array $options) + { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + + $this->options = $options; + + return $this; + } + + /** + * Gets whether or not Windows compatibility is enabled. + * + * This is true by default. + * + * @return bool + * + * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled. + */ + public function getEnhanceWindowsCompatibility() + { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); + + return $this->enhanceWindowsCompatibility; + } + + /** + * Sets whether or not Windows compatibility is enabled. + * + * @param bool $enhance + * + * @return self The current Process instance + * + * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled. + */ + public function setEnhanceWindowsCompatibility($enhance) + { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); + + $this->enhanceWindowsCompatibility = (bool) $enhance; + + return $this; + } + + /** + * Returns whether sigchild compatibility mode is activated or not. + * + * @return bool + * + * @deprecated since version 3.3, to be removed in 4.0. Sigchild compatibility will always be enabled. + */ + public function getEnhanceSigchildCompatibility() + { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); + + return $this->enhanceSigchildCompatibility; + } + + /** + * Activates sigchild compatibility mode. + * + * Sigchild compatibility mode is required to get the exit code and + * determine the success of a process when PHP has been compiled with + * the --enable-sigchild option + * + * @param bool $enhance + * + * @return self The current Process instance + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function setEnhanceSigchildCompatibility($enhance) + { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); + + $this->enhanceSigchildCompatibility = (bool) $enhance; + + return $this; + } + + /** + * Sets whether environment variables will be inherited or not. + * + * @param bool $inheritEnv + * + * @return self The current Process instance + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + if (!$inheritEnv) { + @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', E_USER_DEPRECATED); + } + + $this->inheritEnv = (bool) $inheritEnv; + + return $this; + } + + /** + * Returns whether environment variables will be inherited or not. + * + * @return bool + * + * @deprecated since version 3.3, to be removed in 4.0. Environment variables will always be inherited. + */ + public function areEnvironmentVariablesInherited() + { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Environment variables will always be inherited.', __METHOD__), E_USER_DEPRECATED); + + return $this->inheritEnv; + } + + /** + * Performs a check between the timeout definition and the time the process started. + * + * In case you run a background process (with the start method), you should + * trigger this method regularly to ensure the process timeout + * + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function checkTimeout() + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); + } + } + + /** + * Returns whether PTY is supported on the current operating system. + * + * @return bool + */ + public static function isPtySupported() + { + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + return $result = false; + } + + return $result = (bool) @proc_open('echo 1 >/dev/null', array(array('pty'), array('pty'), array('pty')), $pipes); + } + + /** + * Creates the descriptors needed by the proc_open. + * + * @return array + */ + private function getDescriptors() + { + if ($this->input instanceof \Iterator) { + $this->input->rewind(); + } + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback); + } else { + $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback); + } + + return $this->processPipes->getDescriptors(); + } + + /** + * Builds up the callback used by wait(). + * + * The callbacks adds all occurred output to the specific buffer and calls + * the user callback (if present) with the received output. + * + * @param callable|null $callback The user defined PHP callback + * + * @return \Closure A PHP closure + */ + protected function buildCallback(callable $callback = null) + { + if ($this->outputDisabled) { + return function ($type, $data) use ($callback) { + if (null !== $callback) { + \call_user_func($callback, $type, $data); + } + }; + } + + $out = self::OUT; + + return function ($type, $data) use ($callback, $out) { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + if (null !== $callback) { + \call_user_func($callback, $type, $data); + } + }; + } + + /** + * Updates the status of the process, reads pipes. + * + * @param bool $blocking Whether to use a blocking read call + */ + protected function updateStatus($blocking) + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $running = $this->processInformation['running']; + + $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running); + + if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + $this->processInformation = $this->fallbackStatus + $this->processInformation; + } + + if (!$running) { + $this->close(); + } + } + + /** + * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. + * + * @return bool + */ + protected function isSigchildEnabled() + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!\function_exists('phpinfo') || \defined('HHVM_VERSION')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + /** + * Reads pipes for the freshest output. + * + * @param string $caller The name of the method that needs fresh outputs + * @param bool $blocking Whether to use blocking calls or not + * + * @throws LogicException in case output has been disabled or process is not started + */ + private function readPipesForOutput($caller, $blocking = false) + { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted($caller); + + $this->updateStatus($blocking); + } + + /** + * Validates and returns the filtered timeout. + * + * @param int|float|null $timeout + * + * @return float|null + * + * @throws InvalidArgumentException if the given timeout is a negative number + */ + private function validateTimeout($timeout) + { + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * Reads pipes, executes callback. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close file handles or not + */ + private function readPipes($blocking, $close) + { + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 !== $type) { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + } + + /** + * Closes process resource, closes file handles, sets the exitcode. + * + * @return int The exitcode + */ + private function close() + { + $this->processPipes->close(); + if (\is_resource($this->process)) { + proc_close($this->process); + } + $this->exitcode = $this->processInformation['exitcode']; + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode) { + if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply Unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + $this->processInformation['signaled'] = true; + $this->processInformation['termsig'] = -1; + } + } + + // Free memory from self-reference callback created by buildCallback + // Doing so in other contexts like __destruct or by garbage collector is ineffective + // Now pipes are closed, so the callback is no longer necessary + $this->callback = null; + + return $this->exitcode; + } + + /** + * Resets data related to the latest run of the process. + */ + private function resetProcessData() + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackStatus = array(); + $this->processInformation = null; + $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b'); + $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b'); + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php) + * @param bool $throwException Whether to throw exception in case signal failed + * + * @return bool True if the signal was sent successfully, false otherwise + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + private function doSignal($signal, $throwException) + { + if (null === $pid = $this->getPid()) { + if ($throwException) { + throw new LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); + if ($exitCode && $this->isRunning()) { + if ($throwException) { + throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); + } + + return false; + } + } else { + if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) { + $ok = @proc_terminate($this->process, $signal); + } elseif (\function_exists('posix_kill')) { + $ok = @posix_kill($pid, $signal); + } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), array(2 => array('pipe', 'w')), $pipes)) { + $ok = false === fgets($pipes[2]); + } + if (!$ok) { + if ($throwException) { + throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + } + + return false; + } + } + + $this->latestSignal = (int) $signal; + $this->fallbackStatus['signaled'] = true; + $this->fallbackStatus['exitcode'] = -1; + $this->fallbackStatus['termsig'] = $this->latestSignal; + + return true; + } + + private function prepareWindowsCommandLine($cmd, array &$env) + { + $uid = uniqid('', true); + $varCount = 0; + $varCache = array(); + $cmd = preg_replace_callback( + '/"(?:( + [^"%!^]*+ + (?: + (?: !LF! | "(?:\^[%!^])?+" ) + [^"%!^]*+ + )++ + ) | [^"]*+ )"/x', + function ($m) use (&$env, &$varCache, &$varCount, $uid) { + if (!isset($m[1])) { + return $m[0]; + } + if (isset($varCache[$m[0]])) { + return $varCache[$m[0]]; + } + if (false !== strpos($value = $m[1], "\0")) { + $value = str_replace("\0", '?', $value); + } + if (false === strpbrk($value, "\"%!\n")) { + return '"'.$value.'"'; + } + + $value = str_replace(array('!LF!', '"^!"', '"^%"', '"^^"', '""'), array("\n", '!', '%', '^', '"'), $value); + $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"'; + $var = $uid.++$varCount; + + $env[$var] = $value; + + return $varCache[$m[0]] = '!'.$var.'!'; + }, + $cmd + ); + + $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $cmd .= ' '.$offset.'>"'.$filename.'"'; + } + + return $cmd; + } + + /** + * Ensures the process is running or terminated, throws a LogicException if the process has a not started. + * + * @param string $functionName The function name that was called + * + * @throws LogicException if the process has not run + */ + private function requireProcessIsStarted($functionName) + { + if (!$this->isStarted()) { + throw new LogicException(sprintf('Process must be started before calling %s.', $functionName)); + } + } + + /** + * Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`. + * + * @param string $functionName The function name that was called + * + * @throws LogicException if the process is not yet terminated + */ + private function requireProcessIsTerminated($functionName) + { + if (!$this->isTerminated()) { + throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); + } + } + + /** + * Escapes a string to be used as a shell argument. + * + * @param string $argument The argument that will be escaped + * + * @return string The escaped argument + */ + private function escapeArgument($argument) + { + if ('\\' !== \DIRECTORY_SEPARATOR) { + return "'".str_replace("'", "'\\''", $argument)."'"; + } + if ('' === $argument = (string) $argument) { + return '""'; + } + if (false !== strpos($argument, "\0")) { + $argument = str_replace("\0", '?', $argument); + } + if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) { + return $argument; + } + $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); + + return '"'.str_replace(array('"', '^', '%', '!', "\n"), array('""', '"^^"', '"^%"', '"^!"', '!LF!'), $argument).'"'; + } + + private function getDefaultEnv() + { + $env = array(); + + foreach ($_SERVER as $k => $v) { + if (\is_string($v) && false !== $v = getenv($k)) { + $env[$k] = $v; + } + } + + foreach ($_ENV as $k => $v) { + if (\is_string($v)) { + $env[$k] = $v; + } + } + + return $env; + } +} diff --git a/freescout-dist/overrides/symfony/routing/CompiledRoute.php b/freescout-dist/overrides/symfony/routing/CompiledRoute.php new file mode 100644 index 0000000..eea4ebf --- /dev/null +++ b/freescout-dist/overrides/symfony/routing/CompiledRoute.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * CompiledRoutes are returned by the RouteCompiler class. + * + * @author Fabien Potencier + */ +class CompiledRoute implements \Serializable +{ + private $variables; + private $tokens; + private $staticPrefix; + private $regex; + private $pathVariables; + private $hostVariables; + private $hostRegex; + private $hostTokens; + + /** + * @param string $staticPrefix The static prefix of the compiled route + * @param string $regex The regular expression to use to match this route + * @param array $tokens An array of tokens to use to generate URL for this route + * @param array $pathVariables An array of path variables + * @param string|null $hostRegex Host regex + * @param array $hostTokens Host tokens + * @param array $hostVariables An array of host variables + * @param array $variables An array of variables (variables defined in the path and in the host patterns) + */ + public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array()) + { + $this->staticPrefix = (string) $staticPrefix; + $this->regex = $regex; + $this->tokens = $tokens; + $this->pathVariables = $pathVariables; + $this->hostRegex = $hostRegex; + $this->hostTokens = $hostTokens; + $this->hostVariables = $hostVariables; + $this->variables = $variables; + } + + public function __serialize(): array + { + return [ + 'vars' => $this->variables, + 'path_prefix' => $this->staticPrefix, + 'path_regex' => $this->regex, + 'path_tokens' => $this->tokens, + 'path_vars' => $this->pathVariables, + 'host_regex' => $this->hostRegex, + 'host_tokens' => $this->hostTokens, + 'host_vars' => $this->hostVariables, + ]; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize($this->__serialize()); + } + + public function __unserialize(array $data): void + { + $this->variables = $data['vars']; + $this->staticPrefix = $data['path_prefix']; + $this->regex = $data['path_regex']; + $this->tokens = $data['path_tokens']; + $this->pathVariables = $data['path_vars']; + $this->hostRegex = $data['host_regex']; + $this->hostTokens = $data['host_tokens']; + $this->hostVariables = $data['host_vars']; + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + $this->__unserialize(unserialize($serialized, ['allowed_classes' => false])); + } + + /** + * Returns the static prefix. + * + * @return string The static prefix + */ + public function getStaticPrefix() + { + return $this->staticPrefix; + } + + /** + * Returns the regex. + * + * @return string The regex + */ + public function getRegex() + { + return $this->regex; + } + + /** + * Returns the host regex. + * + * @return string|null The host regex or null + */ + public function getHostRegex() + { + return $this->hostRegex; + } + + /** + * Returns the tokens. + * + * @return array The tokens + */ + public function getTokens() + { + return $this->tokens; + } + + /** + * Returns the host tokens. + * + * @return array The tokens + */ + public function getHostTokens() + { + return $this->hostTokens; + } + + /** + * Returns the variables. + * + * @return array The variables + */ + public function getVariables() + { + return $this->variables; + } + + /** + * Returns the path variables. + * + * @return array The variables + */ + public function getPathVariables() + { + return $this->pathVariables; + } + + /** + * Returns the host variables. + * + * @return array The variables + */ + public function getHostVariables() + { + return $this->hostVariables; + } +} diff --git a/freescout-dist/overrides/symfony/routing/Route.php b/freescout-dist/overrides/symfony/routing/Route.php new file mode 100644 index 0000000..f774bc2 --- /dev/null +++ b/freescout-dist/overrides/symfony/routing/Route.php @@ -0,0 +1,567 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * A Route describes a route and its parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class Route implements \Serializable +{ + private $path = '/'; + private $host = ''; + private $schemes = array(); + private $methods = array(); + private $defaults = array(); + private $requirements = array(); + private $options = array(); + private $condition = ''; + + /** + * @var CompiledRoute|null + */ + private $compiled; + + /** + * Constructor. + * + * Available options: + * + * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) + * * utf8: Whether UTF-8 matching is enforced ot not + * + * @param string $path The path pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @param array $options An array of options + * @param string $host The host pattern to match + * @param string|string[] $schemes A required URI scheme or an array of restricted schemes + * @param string|string[] $methods A required HTTP method or an array of restricted methods + * @param string $condition A condition that should evaluate to true for the route to match + */ + public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '') + { + $this->setPath($path); + $this->setDefaults($defaults); + $this->setRequirements($requirements); + $this->setOptions($options); + $this->setHost($host); + $this->setSchemes($schemes); + $this->setMethods($methods); + $this->setCondition($condition); + } + + public function __serialize(): array + { + return [ + 'path' => $this->path, + 'host' => $this->host, + 'defaults' => $this->defaults, + 'requirements' => $this->requirements, + 'options' => $this->options, + 'schemes' => $this->schemes, + 'methods' => $this->methods, + 'condition' => $this->condition, + 'compiled' => $this->compiled, + ]; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize($this->__serialize()); + } + + public function __unserialize(array $data): void + { + $this->path = $data['path']; + $this->host = $data['host']; + $this->defaults = $data['defaults']; + $this->requirements = $data['requirements']; + $this->options = $data['options']; + $this->schemes = $data['schemes']; + $this->methods = $data['methods']; + + if (isset($data['condition'])) { + $this->condition = $data['condition']; + } + if (isset($data['compiled'])) { + $this->compiled = $data['compiled']; + } + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + $this->__unserialize(unserialize($serialized)); + } + + /** + * Returns the pattern for the path. + * + * @return string The path pattern + */ + public function getPath() + { + return $this->path; + } + + /** + * Sets the pattern for the path. + * + * This method implements a fluent interface. + * + * @param string $pattern The path pattern + * + * @return $this + */ + public function setPath($pattern) + { + // A pattern must start with a slash and must not have multiple slashes at the beginning because the + // generated path for this route would be confused with a network path, e.g. '//domain.com/path'. + $this->path = '/'.ltrim(trim($pattern), '/'); + $this->compiled = null; + + return $this; + } + + /** + * Returns the pattern for the host. + * + * @return string The host pattern + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the pattern for the host. + * + * This method implements a fluent interface. + * + * @param string $pattern The host pattern + * + * @return $this + */ + public function setHost($pattern) + { + $this->host = (string) $pattern; + $this->compiled = null; + + return $this; + } + + /** + * Returns the lowercased schemes this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * @return string[] The schemes + */ + public function getSchemes() + { + return $this->schemes; + } + + /** + * Sets the schemes (e.g. 'https') this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * This method implements a fluent interface. + * + * @param string|string[] $schemes The scheme or an array of schemes + * + * @return $this + */ + public function setSchemes($schemes) + { + $this->schemes = array_map('strtolower', (array) $schemes); + $this->compiled = null; + + return $this; + } + + /** + * Checks if a scheme requirement has been set. + * + * @param string $scheme + * + * @return bool true if the scheme requirement exists, otherwise false + */ + public function hasScheme($scheme) + { + return \in_array(strtolower($scheme), $this->schemes, true); + } + + /** + * Returns the uppercased HTTP methods this route is restricted to. + * So an empty array means that any method is allowed. + * + * @return string[] The methods + */ + public function getMethods() + { + return $this->methods; + } + + /** + * Sets the HTTP methods (e.g. 'POST') this route is restricted to. + * So an empty array means that any method is allowed. + * + * This method implements a fluent interface. + * + * @param string|string[] $methods The method or an array of methods + * + * @return $this + */ + public function setMethods($methods) + { + $this->methods = array_map('strtoupper', (array) $methods); + $this->compiled = null; + + return $this; + } + + /** + * Returns the options. + * + * @return array The options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return $this + */ + public function setOptions(array $options) + { + $this->options = array( + 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', + ); + + return $this->addOptions($options); + } + + /** + * Adds options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return $this + */ + public function addOptions(array $options) + { + foreach ($options as $name => $option) { + $this->options[$name] = $option; + } + $this->compiled = null; + + return $this; + } + + /** + * Sets an option value. + * + * This method implements a fluent interface. + * + * @param string $name An option name + * @param mixed $value The option value + * + * @return $this + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + $this->compiled = null; + + return $this; + } + + /** + * Get an option value. + * + * @param string $name An option name + * + * @return mixed The option value or null when not given + */ + public function getOption($name) + { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * Checks if an option has been set. + * + * @param string $name An option name + * + * @return bool true if the option is set, false otherwise + */ + public function hasOption($name) + { + return array_key_exists($name, $this->options); + } + + /** + * Returns the defaults. + * + * @return array The defaults + */ + public function getDefaults() + { + return $this->defaults; + } + + /** + * Sets the defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return $this + */ + public function setDefaults(array $defaults) + { + $this->defaults = array(); + + return $this->addDefaults($defaults); + } + + /** + * Adds defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return $this + */ + public function addDefaults(array $defaults) + { + foreach ($defaults as $name => $default) { + $this->defaults[$name] = $default; + } + $this->compiled = null; + + return $this; + } + + /** + * Gets a default value. + * + * @param string $name A variable name + * + * @return mixed The default value or null when not given + */ + public function getDefault($name) + { + return isset($this->defaults[$name]) ? $this->defaults[$name] : null; + } + + /** + * Checks if a default value is set for the given variable. + * + * @param string $name A variable name + * + * @return bool true if the default value is set, false otherwise + */ + public function hasDefault($name) + { + return array_key_exists($name, $this->defaults); + } + + /** + * Sets a default value. + * + * @param string $name A variable name + * @param mixed $default The default value + * + * @return $this + */ + public function setDefault($name, $default) + { + $this->defaults[$name] = $default; + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirements. + * + * @return array The requirements + */ + public function getRequirements() + { + return $this->requirements; + } + + /** + * Sets the requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return $this + */ + public function setRequirements(array $requirements) + { + $this->requirements = array(); + + return $this->addRequirements($requirements); + } + + /** + * Adds requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return $this + */ + public function addRequirements(array $requirements) + { + foreach ($requirements as $key => $regex) { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + } + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirement for the given key. + * + * @param string $key The key + * + * @return string|null The regex or null when not given + */ + public function getRequirement($key) + { + return isset($this->requirements[$key]) ? $this->requirements[$key] : null; + } + + /** + * Checks if a requirement is set for the given key. + * + * @param string $key A variable name + * + * @return bool true if a requirement is specified, false otherwise + */ + public function hasRequirement($key) + { + return array_key_exists($key, $this->requirements); + } + + /** + * Sets a requirement for the given key. + * + * @param string $key The key + * @param string $regex The regex + * + * @return $this + */ + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + $this->compiled = null; + + return $this; + } + + /** + * Returns the condition. + * + * @return string The condition + */ + public function getCondition() + { + return $this->condition; + } + + /** + * Sets the condition. + * + * This method implements a fluent interface. + * + * @param string $condition The condition + * + * @return $this + */ + public function setCondition($condition) + { + $this->condition = (string) $condition; + $this->compiled = null; + + return $this; + } + + /** + * Compiles the route. + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + * + * @see RouteCompiler which is responsible for the compilation process + */ + public function compile() + { + if (null !== $this->compiled) { + return $this->compiled; + } + + $class = $this->getOption('compiler_class'); + + return $this->compiled = $class::compile($this); + } + + private function sanitizeRequirement($key, $regex) + { + if (!\is_string($regex)) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" must be a string.', $key)); + } + + if ('' !== $regex && '^' === $regex[0]) { + $regex = (string) substr($regex, 1); // returns false for a single character + } + + if ('$' === substr($regex, -1)) { + $regex = substr($regex, 0, -1); + } + + if ('' === $regex) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.', $key)); + } + + return $regex; + } +} diff --git a/freescout-dist/overrides/symfony/var-dumper/Cloner/Data.php b/freescout-dist/overrides/symfony/var-dumper/Cloner/Data.php new file mode 100644 index 0000000..e7461e6 --- /dev/null +++ b/freescout-dist/overrides/symfony/var-dumper/Cloner/Data.php @@ -0,0 +1,439 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; + +/** + * @author Nicolas Grekas + */ +class Data implements \ArrayAccess, \Countable, \IteratorAggregate +{ + private $data; + private $position = 0; + private $key = 0; + private $maxDepth = 20; + private $maxItemsPerDepth = -1; + private $useRefHandles = -1; + + /** + * @param array $data An array as returned by ClonerInterface::cloneVar() + */ + public function __construct(array $data) + { + $this->data = $data; + } + + /** + * @return string The type of the value + */ + public function getType() + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!$item instanceof Stub) { + return \gettype($item); + } + if (Stub::TYPE_STRING === $item->type) { + return 'string'; + } + if (Stub::TYPE_ARRAY === $item->type) { + return 'array'; + } + if (Stub::TYPE_OBJECT === $item->type) { + return $item->class; + } + if (Stub::TYPE_RESOURCE === $item->type) { + return $item->class.' resource'; + } + } + + /** + * @param bool $recursive Whether values should be resolved recursively or not + * + * @return string|int|float|bool|array|Data[]|null A native representation of the original value + */ + public function getValue($recursive = false) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub) { + return $item; + } + if (Stub::TYPE_STRING === $item->type) { + return $item->value; + } + + $children = $item->position ? $this->data[$item->position] : array(); + + foreach ($children as $k => $v) { + if ($recursive && !($v = $this->getStub($v)) instanceof Stub) { + continue; + } + $children[$k] = clone $this; + $children[$k]->key = $k; + $children[$k]->position = $item->position; + + if ($recursive) { + if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) { + $recursive = (array) $recursive; + if (isset($recursive[$v->position])) { + continue; + } + $recursive[$v->position] = true; + } + $children[$k] = $children[$k]->getValue($recursive); + } + } + + return $children; + } + + public function count(): int + { + return \count($this->getValue()); + } + + public function getIterator(): \Traversable + { + if (!\is_array($value = $this->getValue())) { + throw new \LogicException(sprintf('%s object holds non-iterable type "%s".', self::class, \gettype($value))); + } + + foreach ($value as $k => $v) { + yield $k => $v; + } + } + + public function __get($key) + { + if (null !== $data = $this->seek($key)) { + $item = $this->getStub($data->data[$data->position][$data->key]); + + return $item instanceof Stub || array() === $item ? $data : $item; + } + } + + public function __isset($key) + { + return null !== $this->seek($key); + } + + public function offsetExists($key): bool + { + return $this->__isset($key); + } + + // : mixed + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->__get($key); + } + + public function offsetSet($key, $value): void + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + public function offsetUnset($key): void + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + public function __toString() + { + $value = $this->getValue(); + + if (!\is_array($value)) { + return (string) $value; + } + + return sprintf('%s (count=%d)', $this->getType(), \count($value)); + } + + /** + * @return array The raw data structure + * + * @deprecated since version 3.3. Use array or object access instead. + */ + public function getRawData() + { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the array or object access instead.', __METHOD__)); + + return $this->data; + } + + /** + * Returns a depth limited clone of $this. + * + * @param int $maxDepth The max dumped depth level + * + * @return self A clone of $this + */ + public function withMaxDepth($maxDepth) + { + $data = clone $this; + $data->maxDepth = (int) $maxDepth; + + return $data; + } + + /** + * Limits the number of elements per depth level. + * + * @param int $maxItemsPerDepth The max number of items dumped per depth level + * + * @return self A clone of $this + */ + public function withMaxItemsPerDepth($maxItemsPerDepth) + { + $data = clone $this; + $data->maxItemsPerDepth = (int) $maxItemsPerDepth; + + return $data; + } + + /** + * Enables/disables objects' identifiers tracking. + * + * @param bool $useRefHandles False to hide global ref. handles + * + * @return self A clone of $this + */ + public function withRefHandles($useRefHandles) + { + $data = clone $this; + $data->useRefHandles = $useRefHandles ? -1 : 0; + + return $data; + } + + /** + * Seeks to a specific key in nested data structures. + * + * @param string|int $key The key to seek to + * + * @return self|null A clone of $this or null if the key is not set + */ + public function seek($key) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) { + return; + } + $keys = array($key); + + switch ($item->type) { + case Stub::TYPE_OBJECT: + $keys[] = Caster::PREFIX_DYNAMIC.$key; + $keys[] = Caster::PREFIX_PROTECTED.$key; + $keys[] = Caster::PREFIX_VIRTUAL.$key; + $keys[] = "\0$item->class\0$key"; + // no break + case Stub::TYPE_ARRAY: + case Stub::TYPE_RESOURCE: + break; + default: + return; + } + + $data = null; + $children = $this->data[$item->position]; + + foreach ($keys as $key) { + if (isset($children[$key]) || array_key_exists($key, $children)) { + $data = clone $this; + $data->key = $key; + $data->position = $item->position; + break; + } + } + + return $data; + } + + /** + * Dumps data with a DumperInterface dumper. + */ + public function dump(DumperInterface $dumper) + { + $refs = array(0); + $this->dumpItem($dumper, new Cursor(), $refs, $this->data[$this->position][$this->key]); + } + + /** + * Depth-first dumping of items. + * + * @param DumperInterface $dumper The dumper being used for dumping + * @param Cursor $cursor A cursor used for tracking dumper state position + * @param array &$refs A map of all references discovered while dumping + * @param mixed $item A Stub object or the original value being dumped + */ + private function dumpItem($dumper, $cursor, &$refs, $item) + { + $cursor->refIndex = 0; + $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; + $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0; + $firstSeen = true; + + if (!$item instanceof Stub) { + $cursor->attr = array(); + $type = \gettype($item); + if ($item && 'array' === $type) { + $item = $this->getStub($item); + } + } elseif (Stub::TYPE_REF === $item->type) { + if ($item->handle) { + if (!isset($refs[$r = $item->handle - (PHP_INT_MAX >> 1)])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->hardRefTo = $refs[$r]; + $cursor->hardRefHandle = $this->useRefHandles & $item->handle; + $cursor->hardRefCount = $item->refCount; + } + $cursor->attr = $item->attr; + $type = $item->class ?: \gettype($item->value); + $item = $this->getStub($item->value); + } + if ($item instanceof Stub) { + if ($item->refCount) { + if (!isset($refs[$r = $item->handle])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->softRefTo = $refs[$r]; + } + $cursor->softRefHandle = $this->useRefHandles & $item->handle; + $cursor->softRefCount = $item->refCount; + $cursor->attr = $item->attr; + $cut = $item->cut; + + if ($item->position && $firstSeen) { + $children = $this->data[$item->position]; + + if ($cursor->stop) { + if ($cut >= 0) { + $cut += \count($children); + } + $children = array(); + } + } else { + $children = array(); + } + switch ($item->type) { + case Stub::TYPE_STRING: + $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut); + break; + + case Stub::TYPE_ARRAY: + $item = clone $item; + $item->type = $item->class; + $item->class = $item->value; + // no break + case Stub::TYPE_OBJECT: + case Stub::TYPE_RESOURCE: + $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth; + $dumper->enterHash($cursor, $item->type, $item->class, $withChildren); + if ($withChildren) { + if ($cursor->skipChildren) { + $withChildren = false; + $cut = -1; + } else { + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class); + } + } elseif ($children && 0 <= $cut) { + $cut += \count($children); + } + $cursor->skipChildren = false; + $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); + break; + + default: + throw new \RuntimeException(sprintf('Unexpected Stub type: %s', $item->type)); + } + } elseif ('array' === $type) { + $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false); + $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0); + } elseif ('string' === $type) { + $dumper->dumpString($cursor, $item, false, 0); + } else { + $dumper->dumpScalar($cursor, $type, $item); + } + } + + /** + * Dumps children of hash structures. + * + * @param DumperInterface $dumper + * @param Cursor $parentCursor The cursor of the parent hash + * @param array &$refs A map of all references discovered while dumping + * @param array $children The children to dump + * @param int $hashCut The number of items removed from the original hash + * @param string $hashType A Cursor::HASH_* const + * @param bool $dumpKeys Whether keys should be dumped or not + * + * @return int The final number of removed items + */ + private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType, $dumpKeys) + { + $cursor = clone $parentCursor; + ++$cursor->depth; + $cursor->hashType = $hashType; + $cursor->hashIndex = 0; + $cursor->hashLength = \count($children); + $cursor->hashCut = $hashCut; + foreach ($children as $key => $child) { + $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key); + $cursor->hashKey = $dumpKeys ? $key : null; + $this->dumpItem($dumper, $cursor, $refs, $child); + if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) { + $parentCursor->stop = true; + + return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut; + } + } + + return $hashCut; + } + + private function getStub($item) + { + if (!$item || !\is_array($item)) { + return $item; + } + + $stub = new Stub(); + $stub->type = Stub::TYPE_ARRAY; + foreach ($item as $stub->class => $stub->position) { + } + if (isset($item[0])) { + $stub->cut = $item[0]; + } + $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0); + + return $stub; + } +} diff --git a/freescout-dist/overrides/symfony/var-dumper/Cloner/Stub.php b/freescout-dist/overrides/symfony/var-dumper/Cloner/Stub.php new file mode 100644 index 0000000..fae6177 --- /dev/null +++ b/freescout-dist/overrides/symfony/var-dumper/Cloner/Stub.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * Represents the main properties of a PHP variable. + * + * @author Nicolas Grekas + */ +class Stub implements \Serializable +{ + const TYPE_REF = 1; + const TYPE_STRING = 2; + const TYPE_ARRAY = 3; + const TYPE_OBJECT = 4; + const TYPE_RESOURCE = 5; + + const STRING_BINARY = 1; + const STRING_UTF8 = 2; + + const ARRAY_ASSOC = 1; + const ARRAY_INDEXED = 2; + + public $type = self::TYPE_REF; + public $class = ''; + public $value; + public $cut = 0; + public $handle = 0; + public $refCount = 0; + public $position = 0; + public $attr = array(); + + public function __serialize() + { + return array($this->class, $this->position, $this->cut, $this->type, $this->value, $this->handle, $this->refCount, $this->attr); + } + + /** + * @internal + */ + public function serialize() + { + return \serialize(array($this->class, $this->position, $this->cut, $this->type, $this->value, $this->handle, $this->refCount, $this->attr)); + } + + public function __unserialize($data) + { + list($this->class, $this->position, $this->cut, $this->type, $this->value, $this->handle, $this->refCount, $this->attr) = $data; + } + + /** + * @internal + */ + public function unserialize($serialized) + { + list($this->class, $this->position, $this->cut, $this->type, $this->value, $this->handle, $this->refCount, $this->attr) = \unserialize($serialized); + } +} diff --git a/freescout-dist/overrides/symfony/var-dumper/Dumper/HtmlDumper.php b/freescout-dist/overrides/symfony/var-dumper/Dumper/HtmlDumper.php new file mode 100644 index 0000000..3260ddc --- /dev/null +++ b/freescout-dist/overrides/symfony/var-dumper/Dumper/HtmlDumper.php @@ -0,0 +1,903 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * HtmlDumper dumps variables as HTML. + * + * @author Nicolas Grekas + */ +class HtmlDumper extends CliDumper +{ + public static $defaultOutput = 'php://output'; + + protected $dumpHeader; + protected $dumpPrefix = '
';
+    protected $dumpSuffix = '
'; + protected $dumpId = 'sf-dump'; + protected $colors = true; + protected $headerIsDumped = false; + protected $lastDepth = -1; + protected $styles = [ + 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#56DB3A', + 'note' => 'color:#1299DA', + 'ref' => 'color:#A0A0A0', + 'public' => 'color:#FFFFFF', + 'protected' => 'color:#FFFFFF', + 'private' => 'color:#FFFFFF', + 'meta' => 'color:#B729D9', + 'key' => 'color:#56DB3A', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#FF8400', + ]; + + private $displayOptions = [ + 'maxDepth' => 1, + 'maxStringLength' => 160, + 'fileLinkFormat' => null, + ]; + private $extraDisplayOptions = []; + + /** + * {@inheritdoc} + */ + public function __construct($output = null, $charset = null, $flags = 0) + { + AbstractDumper::__construct($output, $charset, $flags); + $this->dumpId = 'sf-dump-'.mt_rand(); + $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + } + + /** + * {@inheritdoc} + */ + public function setStyles(array $styles) + { + $this->headerIsDumped = false; + $this->styles = $styles + $this->styles; + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions) + { + $this->headerIsDumped = false; + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + /** + * Sets an HTML header that will be dumped once in the output stream. + * + * @param string $header An HTML string + */ + public function setDumpHeader($header) + { + $this->dumpHeader = $header; + } + + /** + * Sets an HTML prefix and suffix that will encapse every single dump. + * + * @param string $prefix The prepended HTML string + * @param string $suffix The appended HTML string + */ + public function setDumpBoundaries($prefix, $suffix) + { + $this->dumpPrefix = $prefix; + $this->dumpSuffix = $suffix; + } + + /** + * {@inheritdoc} + */ + public function dump(Data $data, $output = null, array $extraDisplayOptions = []) + { + $this->extraDisplayOptions = $extraDisplayOptions; + $result = parent::dump($data, $output); + $this->dumpId = 'sf-dump-'.mt_rand(); + + return $result; + } + + /** + * Dumps the HTML header. + */ + protected function getDumpHeader() + { + $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper; + + if (null !== $this->dumpHeader) { + return $this->dumpHeader; + } + + $line = str_replace('{$options}', json_encode($this->displayOptions, \JSON_FORCE_OBJECT), ''.$this->dumpHeader; + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, $type, $class, $hasChild) + { + parent::enterHash($cursor, $type, $class, false); + + if ($cursor->skipChildren) { + $cursor->skipChildren = false; + $eol = ' class=sf-dump-compact>'; + } elseif ($this->expandNextHash) { + $this->expandNextHash = false; + $eol = ' class=sf-dump-expanded>'; + } else { + $eol = '>'; + } + + if ($hasChild) { + $this->line .= 'refIndex) { + $r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2; + $r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex; + + $this->line .= sprintf(' id=%s-ref%s', $this->dumpId, $r); + } + $this->line .= $eol; + $this->dumpLine($cursor->depth); + } + } + + /** + * {@inheritdoc} + */ + public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut) + { + $this->dumpEllipsis($cursor, $hasChild, $cut); + if ($hasChild) { + $this->line .= ''; + } + parent::leaveHash($cursor, $type, $class, $hasChild, 0); + } + + /** + * {@inheritdoc} + */ + protected function style($style, $value, $attr = []) + { + if ('' === $value) { + return ''; + } + + $v = esc($value); + + if ('ref' === $style) { + if (empty($attr['count'])) { + return sprintf('%s', $v); + } + $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1); + + return sprintf('%s', $this->dumpId, $r, 1 + $attr['count'], $v); + } + + if ('const' === $style && isset($attr['value'])) { + $style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value']))); + } elseif ('public' === $style) { + $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); + } elseif ('str' === $style && 1 < $attr['length']) { + $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); + } elseif ('note' === $style && false !== $c = strrpos($v, '\\')) { + return sprintf('%s', $v, $style, substr($v, $c + 1)); + } elseif ('protected' === $style) { + $style .= ' title="Protected property"'; + } elseif ('meta' === $style && isset($attr['title'])) { + $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title']))); + } elseif ('private' === $style) { + $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); + } + $map = static::$controlCharsMap; + + if (isset($attr['ellipsis'])) { + $class = 'sf-dump-ellipsis'; + if (isset($attr['ellipsis-type'])) { + $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']); + } + $label = esc(substr($value, -$attr['ellipsis'])); + $style = str_replace(' title="', " title=\"$v\n", $style); + $v = sprintf('%s', $class, substr($v, 0, -\strlen($label))); + + if (!empty($attr['ellipsis-tail'])) { + $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); + $v .= sprintf('%s%s', substr($label, 0, $tail), substr($label, $tail)); + } else { + $v .= $label; + } + } + + $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { + $s = ''; + $c = $c[$i = 0]; + do { + $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', \ord($c[$i])); + } while (isset($c[++$i])); + + return $s.''; + }, $v).''; + + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) { + $attr['href'] = $href; + } + if (isset($attr['href'])) { + $target = isset($attr['file']) ? '' : ' target="_blank"'; + $v = sprintf('%s', esc($this->utf8Encode($attr['href'])), $target, $v); + } + if (isset($attr['lang'])) { + $v = sprintf('%s', esc($attr['lang']), $v); + } + + return $v; + } + + /** + * {@inheritdoc} + */ + protected function dumpLine($depth, $endOfValue = false) + { + if (-1 === $this->lastDepth) { + $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line; + } + if ($this->headerIsDumped !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) { + $this->line = $this->getDumpHeader().$this->line; + } + + if (-1 === $depth) { + $args = ['"'.$this->dumpId.'"']; + if ($this->extraDisplayOptions) { + $args[] = json_encode($this->extraDisplayOptions, \JSON_FORCE_OBJECT); + } + // Replace is for BC + $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args)); + } + $this->lastDepth = $depth; + + $this->line = htmlentities($this->line); + + if (-1 === $depth) { + AbstractDumper::dumpLine(0); + } + AbstractDumper::dumpLine($depth); + } + + private function getSourceLink($file, $line) + { + $options = $this->extraDisplayOptions + $this->displayOptions; + + if ($fmt = $options['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); + } + + return false; + } +} + +function esc($str) +{ + return htmlspecialchars($str, \ENT_QUOTES, 'UTF-8'); +} diff --git a/freescout-dist/overrides/tormjens/eventy/src/Action.php b/freescout-dist/overrides/tormjens/eventy/src/Action.php new file mode 100644 index 0000000..660fcf8 --- /dev/null +++ b/freescout-dist/overrides/tormjens/eventy/src/Action.php @@ -0,0 +1,29 @@ +getListeners($action) as $listener) { + $parameters = []; + for ($i = 0; $i < $listener['arguments']; $i++) { + if (isset($args[$i])) { + $parameters[] = $args[$i]; + } else { + $parameters[] = null; + } + } + call_user_func_array($this->getFunction($listener['callback']), $parameters); + } + } +} diff --git a/freescout-dist/overrides/tormjens/eventy/src/Event.php b/freescout-dist/overrides/tormjens/eventy/src/Event.php new file mode 100644 index 0000000..c1cfeaa --- /dev/null +++ b/freescout-dist/overrides/tormjens/eventy/src/Event.php @@ -0,0 +1,112 @@ +listeners[$hook][] = [ + 'callback' => $callback, + 'priority' => $priority, + 'arguments' => $arguments, + ]; + usort($this->listeners[$hook], function ($a, $b) { + return (int)$a['priority'] - (int)$b['priority']; + }); + + return $this; + } + + /** + * Removes a listener. + * + * @param string $hook Hook name + * @param mixed $callback Function to execute + * @param int $priority Priority of the action + */ + public function remove($hook, $callback, $priority = 20) + { + if (isset($this->listeners[$hook])) { + foreach ($this->listeners[$hook] as $key => $listener) { + if ($listener['callback'] == $callback && $listener['priority'] == $priority) { + unset($this->listeners[$hook][$key]); + } + } + } + } + + /** + * Remove all listeners with given hook in collection. If no hook, clear all listeners. + * + * @param string $hook Hook name + */ + public function removeAll($hook = null) + { + if ($hook) { + if (isset($this->listeners[$hook])) { + unset($this->listeners[$hook]); + } + } else { + $this->listeners = []; + } + } + + /** + * Gets a sorted list of all listeners. + * + * @return array + */ + public function getListeners($hook) + { + return $this->listeners[$hook] ?? []; + } + + /** + * Gets the function. + * + * @param mixed $callback Callback + * + * @return mixed A closure, an array if "class@method" or a string if "function_name" + */ + protected function getFunction($callback) + { + if (is_string($callback) && strpos($callback, '@')) { + $callback = explode('@', $callback); + + return [app('\\'.$callback[0]), $callback[1]]; + } elseif (is_callable($callback)) { + return $callback; + } else { + throw new Exception('$callback is not a Callable', 1); + } + } + + /** + * Fires a new action. + * + * @param string $action Name of action + * @param array $args Arguments passed to the action + */ + abstract public function fire($action, $args); +} diff --git a/freescout-dist/overrides/tormjens/eventy/src/Filter.php b/freescout-dist/overrides/tormjens/eventy/src/Filter.php new file mode 100644 index 0000000..7f225ac --- /dev/null +++ b/freescout-dist/overrides/tormjens/eventy/src/Filter.php @@ -0,0 +1,32 @@ +value = isset($args[0]) ? $args[0] : ''; // get the value, the first argument is always the value + foreach ($this->getListeners($action) as $listener) { + $parameters = []; + $args[0] = $this->value; + for ($i = 0; $i < $listener['arguments']; $i++) { + $value = $args[$i] ?? null; + $parameters[] = $value; + } + $this->value = call_user_func_array($this->getFunction($listener['callback']), $parameters); + } + + return $this->value; + } +} diff --git a/freescout-dist/overrides/vlucas/phpdotenv/src/Loader.php b/freescout-dist/overrides/vlucas/phpdotenv/src/Loader.php new file mode 100644 index 0000000..76a6afe --- /dev/null +++ b/freescout-dist/overrides/vlucas/phpdotenv/src/Loader.php @@ -0,0 +1,427 @@ +filePath = $filePath; + $this->immutable = $immutable; + } + + /** + * Set immutable value. + * + * @param bool $immutable + * @return $this + */ + public function setImmutable($immutable = false) + { + $this->immutable = $immutable; + + return $this; + } + + /** + * Get immutable value. + * + * @return bool + */ + public function getImmutable() + { + return $this->immutable; + } + + /** + * Load `.env` file in given directory. + * + * @return array + */ + public function load() + { + $this->ensureFileIsReadable(); + + $filePath = $this->filePath; + $lines = $this->readLinesFromFile($filePath); + foreach ($lines as $line) { + if (!$this->isComment($line) && $this->looksLikeSetter($line)) { + $this->setEnvironmentVariable($line); + } + } + + return $lines; + } + + /** + * Ensures the given filePath is readable. + * + * @throws \Dotenv\Exception\InvalidPathException + * + * @return void + */ + protected function ensureFileIsReadable() + { + if (!is_readable($this->filePath) || !is_file($this->filePath)) { + throw new InvalidPathException(sprintf('Unable to read the environment file at %s.', $this->filePath)); + } + } + + /** + * Normalise the given environment variable. + * + * Takes value as passed in by developer and: + * - ensures we're dealing with a separate name and value, breaking apart the name string if needed, + * - cleaning the value of quotes, + * - cleaning the name of quotes, + * - resolving nested variables. + * + * @param string $name + * @param string $value + * + * @return array + */ + protected function normaliseEnvironmentVariable($name, $value) + { + list($name, $value) = $this->processFilters($name, $value); + + $value = $this->resolveNestedVariables($value); + + return array($name, $value); + } + + /** + * Process the runtime filters. + * + * Called from `normaliseEnvironmentVariable` and the `VariableFactory`, passed as a callback in `$this->loadFromFile()`. + * + * @param string $name + * @param string $value + * + * @return array + */ + public function processFilters($name, $value) + { + list($name, $value) = $this->splitCompoundStringIntoParts($name, $value); + list($name, $value) = $this->sanitiseVariableName($name, $value); + list($name, $value) = $this->sanitiseVariableValue($name, $value); + + return array($name, $value); + } + + /** + * Read lines from the file, auto detecting line endings. + * + * @param string $filePath + * + * @return array + */ + protected function readLinesFromFile($filePath) + { + // Read file into an array of lines with auto-detected line endings + // $autodetect = ini_get('auto_detect_line_endings'); + // ini_set('auto_detect_line_endings', '1'); + $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + //ini_set('auto_detect_line_endings', $autodetect); + + return $lines; + } + + /** + * Determine if the line in the file is a comment, e.g. begins with a #. + * + * @param string $line + * + * @return bool + */ + protected function isComment($line) + { + $line = ltrim($line); + + return isset($line[0]) && $line[0] === '#'; + } + + /** + * Determine if the given line looks like it's setting a variable. + * + * @param string $line + * + * @return bool + */ + protected function looksLikeSetter($line) + { + return strpos($line, '=') !== false; + } + + /** + * Split the compound string into parts. + * + * If the `$name` contains an `=` sign, then we split it into 2 parts, a `name` & `value` + * disregarding the `$value` passed in. + * + * @param string $name + * @param string $value + * + * @return array + */ + protected function splitCompoundStringIntoParts($name, $value) + { + if (strpos($name, '=') !== false) { + list($name, $value) = array_map('trim', explode('=', $name, 2)); + } + + return array($name, $value); + } + + /** + * Strips quotes from the environment variable value. + * + * @param string $name + * @param string $value + * + * @throws \Dotenv\Exception\InvalidFileException + * + * @return array + */ + protected function sanitiseVariableValue($name, $value) + { + $value = trim($value); + if (!$value) { + return array($name, $value); + } + + if ($this->beginsWithAQuote($value)) { // value starts with a quote + $quote = $value[0]; + /*$regexPattern = sprintf( + '/^ + %1$s # match a quote at the start of the value + ( # capturing sub-pattern used + (?: # we do not need to capture this + [^%1$s\\\\]* # any character other than a quote or backslash + |\\\\\\\\ # or two backslashes together + |\\\\%1$s # or an escaped quote e.g \" + )* # as many characters that match the previous rules + ) # end of the capturing sub-pattern + %1$s # and the closing quote + .*$ # and discard any string after the closing quote + /mx', + $quote + ); + $value = preg_replace($regexPattern, '$1', $value);*/ + // Simply get everything before the last quote. + // https://github.com/freescout-helpdesk/freescout/issues/2822#issuecomment-1467700371 + if (preg_match(sprintf('#\\\\%1$s$#mx', $quote), $value)) { + $value = rtrim($value, $quote); + } + $regexPattern = sprintf('/(.*[^\\\\])%1$s[^%1$s]*/mx', $quote); + $value = preg_replace($regexPattern, '$1', $value); + $value = substr($value, 1); + + $value = str_replace("\\$quote", $quote, $value); + $value = str_replace('\\\\', '\\', $value); + } else { + $parts = explode(' #', $value, 2); + $value = trim($parts[0]); + + // Unquoted values cannot contain whitespace + if (preg_match('/\s+/', $value) > 0) { + // Check if value is a comment (usually triggered when empty value with comment) + if (preg_match('/^#/', $value) > 0) { + $value = ''; + } else { + throw new InvalidFileException('Dotenv values containing spaces must be surrounded by quotes.'); + } + } + } + + return array($name, trim($value)); + } + + /** + * Resolve the nested variables. + * + * Look for ${varname} patterns in the variable value and replace with an + * existing environment variable. + * + * @param string $value + * + * @return mixed + */ + protected function resolveNestedVariables($value) + { + if (strpos($value, '$') !== false) { + $loader = $this; + $value = preg_replace_callback( + '/\${([a-zA-Z0-9_.]+)}/', + function ($matchedPatterns) use ($loader) { + $nestedVariable = $loader->getEnvironmentVariable($matchedPatterns[1]); + if ($nestedVariable === null) { + return $matchedPatterns[0]; + } else { + return $nestedVariable; + } + }, + $value + ); + } + + return $value; + } + + /** + * Strips quotes and the optional leading "export " from the environment variable name. + * + * @param string $name + * @param string $value + * + * @return array + */ + protected function sanitiseVariableName($name, $value) + { + $name = trim(str_replace(array('export ', '\'', '"'), '', $name)); + + return array($name, $value); + } + + /** + * Determine if the given string begins with a quote. + * + * @param string $value + * + * @return bool + */ + protected function beginsWithAQuote($value) + { + return isset($value[0]) && ($value[0] === '"' || $value[0] === '\''); + } + + /** + * Search the different places for environment variables and return first value found. + * + * @param string $name + * + * @return string|null + */ + public function getEnvironmentVariable($name) + { + switch (true) { + case array_key_exists($name, $_ENV): + return $_ENV[$name]; + case array_key_exists($name, $_SERVER): + return $_SERVER[$name]; + default: + $value = getenv($name); + return $value === false ? null : $value; // switch getenv default to null + } + } + + /** + * Set an environment variable. + * + * This is done using: + * - putenv, + * - $_ENV, + * - $_SERVER. + * + * The environment variable value is stripped of single and double quotes. + * + * @param string $name + * @param string|null $value + * + * @return void + */ + public function setEnvironmentVariable($name, $value = null) + { + list($name, $value) = $this->normaliseEnvironmentVariable($name, $value); + + $this->variableNames[] = $name; + + // Don't overwrite existing environment variables if we're immutable + // Ruby's dotenv does this with `ENV[key] ||= value`. + if ($this->immutable && $this->getEnvironmentVariable($name) !== null) { + return; + } + + // If PHP is running as an Apache module and an existing + // Apache environment variable exists, overwrite it + if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name)) { + apache_setenv($name, $value); + } + + if (function_exists('putenv')) { + putenv("$name=$value"); + } + + $_ENV[$name] = $value; + $_SERVER[$name] = $value; + } + + /** + * Clear an environment variable. + * + * This is not (currently) used by Dotenv but is provided as a utility + * method for 3rd party code. + * + * This is done using: + * - putenv, + * - unset($_ENV, $_SERVER). + * + * @param string $name + * + * @see setEnvironmentVariable() + * + * @return void + */ + public function clearEnvironmentVariable($name) + { + // Don't clear anything if we're immutable. + if ($this->immutable) { + return; + } + + if (function_exists('putenv')) { + putenv($name); + } + + unset($_ENV[$name], $_SERVER[$name]); + } +} diff --git a/freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Attachment.php b/freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Attachment.php new file mode 100644 index 0000000..0092fe0 --- /dev/null +++ b/freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Attachment.php @@ -0,0 +1,308 @@ +oMessage = $oMessage; + $this->structure = $structure; + $this->part_number = ($part_number) ? $part_number : $this->part_number; + + $this->findType(); + $this->fetch(); + } + + /** + * Determine the structure type + */ + protected function findType() { + switch ($this->structure->type) { + case self::TYPE_MESSAGE: + $this->type = 'message'; + break; + case self::TYPE_APPLICATION: + $this->type = 'application'; + break; + case self::TYPE_AUDIO: + $this->type = 'audio'; + break; + case self::TYPE_IMAGE: + $this->type = 'image'; + break; + case self::TYPE_VIDEO: + $this->type = 'video'; + break; + case self::TYPE_MODEL: + $this->type = 'model'; + break; + case self::TYPE_TEXT: + $this->type = 'text'; + break; + case self::TYPE_MULTIPART: + $this->type = 'multipart'; + break; + default: + $this->type = 'other'; + break; + } + } + + /** + * Fetch the given attachment + * + * @throws Exceptions\ConnectionFailedException + */ + protected function fetch() { + + $content = imap_fetchbody($this->oMessage->getClient()->getConnection(), $this->oMessage->getUid(), $this->part_number, $this->oMessage->getFetchOptions() | FT_UID); + + $this->content_type = $this->type.'/'.strtolower($this->structure->subtype); + $this->content = $this->oMessage->decodeString($content, $this->structure->encoding); + + if (property_exists($this->structure, 'id')) { + $this->id = str_replace(['<', '>'], '', $this->structure->id); + } + + if (property_exists($this->structure, 'dparameters')) { + foreach ($this->structure->dparameters as $parameter) { + // RFC6266 defines use filename* parameter prior to filename. + if (strtolower($parameter->attribute) == "filename*") { + $this->setName($parameter->value); + $this->disposition = property_exists($this->structure, 'disposition') ? $this->structure->disposition : null; + break; + } + if (strtolower($parameter->attribute) == "filename") { + $this->setName($parameter->value); + $this->disposition = property_exists($this->structure, 'disposition') ? $this->structure->disposition : null; + break; + } + } + } + + if (self::TYPE_MESSAGE == $this->structure->type) { + if ($this->structure->ifdescription) { + $this->setName($this->structure->description); + } else { + $this->setName($this->structure->subtype); + } + } + + if (!$this->name && property_exists($this->structure, 'parameters')) { + foreach ($this->structure->parameters as $parameter) { + if (strtolower($parameter->attribute) == "name") { + $this->setName($parameter->value); + $this->disposition = property_exists($this->structure, 'disposition') ? $this->structure->disposition : null; + break; + } + } + } + + if ($this->type == 'image') { + $this->img_src = 'data:'.$this->content_type.';base64,'.base64_encode($this->content); + } + } + + /** + * Save the attachment content to your filesystem + * + * @param string|null $path + * @param string|null $filename + * + * @return boolean + */ + public function save($path = null, $filename = null) { + $path = $path ?: storage_path(); + $filename = $filename ?: $this->getName(); + + // sanitize $name + // order of '..' is important + $filename = str_replace(['\\', '../', '/..', '/', chr(0), ':'], '', $filename ?? ''); + + $path = substr($path, -1) == DIRECTORY_SEPARATOR ? $path : $path.DIRECTORY_SEPARATOR; + + return File::put($path.$filename, $this->getContent()) !== false; + } + + /** + * @return null|string + */ + public function getContent() { + return $this->content; + } + + /** + * @return null|string + */ + public function getType() { + return $this->type; + } + + /** + * @return null|string + */ + public function getContentType() { + return $this->content_type; + } + + /** + * @return null|string + */ + public function getId() { + return $this->id; + } + + /** + * @param $name + */ + public function setName($name) { + //$this->name = mb_decode_mimeheader($name); + + // // https://github.com/freescout-helpdesk/freescout/issues/3089 + if ($name !== null) { + // RFC6266 and RFC8187 + // UTF-8''%E3%80... + // utf-8'en'%C2%A3%20rates + preg_match("#([^']+)'([^']{2})?'(.+)#", $name, $m); + $name_charset = ''; + if (!empty($m[1]) && !empty($m[3])) { + $name = $m[3]; + $name_charset = $m[1]; + } + + // if ($decoder === 'utf-8' && extension_loaded('imap')) { + // $name = \imap_utf8($name); + // } + + //if (preg_match('/=\?([^?]+)\?(Q|B)\?(.+)\?=/i', $name, $matches)) { + $name = \MailHelper::decodeSubject($name); + //} + + // check if $name is url encoded + if (preg_match('/%[0-9A-F]{2}/i', $name)) { + $name = urldecode($name); + } + + // sanitize $name + // order of '..' is important + $name = str_replace(['\\', '../', '/..', '/', chr(0), ':'], '', $name); + } + + $this->name = $name; + } + + /** + * @return null|string + */ + public function getName() { + return $this->name; + } + + /** + * @return null|string + */ + public function getDisposition() { + return $this->disposition; + } + + /** + * @return null|string + */ + public function getImgSrc() { + return $this->img_src; + } + + /** + * @return string|null + */ + public function getMimeType(){ + return (new \finfo())->buffer($this->getContent(), FILEINFO_MIME_TYPE); + } + + /** + * @return string|null + */ + public function getExtension(){ + return ExtensionGuesser::getInstance()->guess($this->getMimeType()); + } +} \ No newline at end of file diff --git a/freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Client.php b/freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Client.php new file mode 100644 index 0000000..4a6ff74 --- /dev/null +++ b/freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Client.php @@ -0,0 +1,576 @@ +setConfig($config); + } + + /** + * Client destructor + */ + public function __destruct() { + $this->disconnect(); + } + + /** + * Set the Client configuration + * + * @param array $config + * + * @return self + */ + public function setConfig(array $config) { + $defaultAccount = config('imap.default'); + $defaultConfig = config("imap.accounts.$defaultAccount"); + + foreach ($this->validConfigKeys as $key) { + $this->$key = isset($config[$key]) ? $config[$key] : $defaultConfig[$key]; + } + + return $this; + } + + /** + * Get the current imap resource + * + * @return bool|resource + * @throws ConnectionFailedException + */ + public function getConnection() { + $this->checkConnection(); + return $this->connection; + } + + /** + * Set read only property and reconnect if it's necessary. + * + * @param bool $readOnly + * + * @return self + */ + public function setReadOnly($readOnly = true) { + $this->read_only = $readOnly; + + return $this; + } + + /** + * Determine if connection was established. + * + * @return bool + */ + public function isConnected() { + return $this->connected; + } + + /** + * Determine if connection is in read only mode. + * + * @return bool + */ + public function isReadOnly() { + return $this->read_only; + } + + /** + * Determine if connection was established and connect if not. + * + * @throws ConnectionFailedException + */ + public function checkConnection() { + if (!$this->isConnected() || $this->connection === false) { + $this->connect(); + } + } + + /** + * Connect to server. + * + * @param int $attempts + * + * @return $this + * @throws ConnectionFailedException + */ + public function connect($attempts = 3) { + $this->disconnect(); + + try { + $this->connection = imap_open( + $this->getAddress(), + $this->username, + $this->password, + $this->getOptions(), + $attempts, + config('imap.options.open') + ); + $this->connected = !!$this->connection; + } catch (\ErrorException $e) { + $errors = imap_errors(); + $message = $e->getMessage().'. '.implode("; ", (is_array($errors) ? $errors : array())); + + throw new ConnectionFailedException($message); + } + + return $this; + } + + /** + * Disconnect from server. + * + * @return $this + */ + public function disconnect() { + if ($this->isConnected() && $this->connection !== false && is_integer($this->connection) === false) { + $this->errors = array_merge($this->errors, imap_errors() ?: []); + $this->connected = !imap_close($this->connection, CL_EXPUNGE); + } + + return $this; + } + + /** + * Get a folder instance by a folder name + * --------------------------------------------- + * PLEASE NOTE: This is an experimental function + * --------------------------------------------- + * @param string $folder_name + * @param int $attributes + * @param null|string $delimiter + * + * @return Folder + */ + public function getFolder($folder_name, $attributes = 32, $delimiter = null) { + + $delimiter = $delimiter === null ? config('imap.options.delimiter', '/') : $delimiter; + + $oFolder = new Folder($this, (object) [ + // https://github.com/freescout-helpdesk/freescout/issues/3152 + 'name' => $this->getAddress().(function_exists('.imap_utf8_to_mutf7') ? imap_utf8_to_mutf7($folder_name) : $folder_name), + 'attributes' => $attributes, + 'delimiter' => $delimiter + ]); + + return $oFolder; + } + + /** + * Get folders list. + * If hierarchical order is set to true, it will make a tree of folders, otherwise it will return flat array. + * + * @param boolean $hierarchical + * @param string|null $parent_folder + * + * @return FolderCollection + * @throws ConnectionFailedException + */ + public function getFolders($hierarchical = true, $parent_folder = null) { + $this->checkConnection(); + $folders = FolderCollection::make([]); + + $pattern = $parent_folder.($hierarchical ? '%' : '*'); + + $items = imap_getmailboxes($this->connection, $this->getAddress(), $pattern); + + // FreeScout fix + if (!$items) { + return $folders; + } + + foreach ($items as $item) { + $folder = new Folder($this, $item); + + if ($hierarchical && $folder->hasChildren()) { + $pattern = $folder->fullName.$folder->delimiter.'%'; + + $children = $this->getFolders(true, $pattern); + $folder->setChildren($children); + } + + $folders->push($folder); + } + + return $folders; + } + + /** + * Open folder. + * + * @param Folder $folder + * @param int $attempts + * + * @throws ConnectionFailedException + */ + public function openFolder(Folder $folder, $attempts = 3) { + $this->checkConnection(); + + if ($this->activeFolder !== $folder) { + $this->activeFolder = $folder; + + imap_reopen($this->getConnection(), $folder->path, $this->getOptions(), $attempts); + } + } + + /** + * Create a new Folder + * @param string $name + * @param boolean $expunge + * + * @return bool + * @throws ConnectionFailedException + */ + public function createFolder($name, $expunge = true) { + $this->checkConnection(); + $status = imap_createmailbox($this->getConnection(), $this->getAddress() . imap_utf7_encode($name)); + if($expunge) $this->expunge(); + + return $status; + } + + /** + * Rename Folder + * @param string $old_name + * @param string $new_name + * @param boolean $expunge + * + * @return bool + * @throws ConnectionFailedException + */ + public function renameFolder($old_name, $new_name, $expunge = true) { + $this->checkConnection(); + $status = imap_renamemailbox($this->getConnection(), $this->getAddress() . imap_utf7_encode($old_name), $this->getAddress() . imap_utf7_encode($new_name)); + if($expunge) $this->expunge(); + + return $status; + } + + /** + * Delete Folder + * @param string $name + * @param boolean $expunge + * + * @return bool + * @throws ConnectionFailedException + */ + public function deleteFolder($name, $expunge = true) { + $this->checkConnection(); + $status = imap_deletemailbox($this->getConnection(), $this->getAddress() . imap_utf7_encode($name)); + if($expunge) $this->expunge(); + + return $status; + } + + /** + * Get messages from folder. + * + * @param Folder $folder + * @param string $criteria + * @param int|null $fetch_options + * @param boolean $fetch_body + * @param boolean $fetch_attachment + * + * @return MessageCollection + * @throws ConnectionFailedException + * @throws GetMessagesFailedException + * @throws MessageSearchValidationException + * + * @deprecated 1.0.5.2:2.0.0 No longer needed. Use Folder::getMessages() instead + * @see Folder::getMessages() + */ + public function getMessages(Folder $folder, $criteria = 'ALL', $fetch_options = null, $fetch_body = true, $fetch_attachment = true, $fetch_flags = false) { + return $folder->getMessages($criteria, $fetch_options, $fetch_body, $fetch_attachment, $fetch_flags); + } + + /** + * Get all unseen messages from folder + * + * @param Folder $folder + * @param string $criteria + * @param int|null $fetch_options + * @param boolean $fetch_body + * @param boolean $fetch_attachment + * + * @return MessageCollection + * @throws ConnectionFailedException + * @throws GetMessagesFailedException + * @throws MessageSearchValidationException + * + * @deprecated 1.0.5:2.0.0 No longer needed. Use Folder::getMessages('UNSEEN') instead + * @see Folder::getMessages() + */ + public function getUnseenMessages(Folder $folder, $criteria = 'UNSEEN', $fetch_options = null, $fetch_body = true, $fetch_attachment = true, $fetch_flags = false) { + return $folder->getUnseenMessages($criteria, $fetch_options, $fetch_body, $fetch_attachment, $fetch_flags); + } + + /** + * Search messages by a given search criteria + * + * @param array $where + * @param Folder $folder + * @param int|null $fetch_options + * @param boolean $fetch_body + * @param string $charset + * @param boolean $fetch_attachment + * + * @return MessageCollection + * @throws ConnectionFailedException + * @throws GetMessagesFailedException + * @throws MessageSearchValidationException + * + * @deprecated 1.0.5:2.0.0 No longer needed. Use Folder::searchMessages() instead + * @see Folder::searchMessages() + * + */ + public function searchMessages(array $where, Folder $folder, $fetch_options = null, $fetch_body = true, $charset = "UTF-8", $fetch_attachment = true, $fetch_flags = false) { + return $folder->searchMessages($where, $fetch_options, $fetch_body, $charset, $fetch_attachment, $fetch_flags); + } + + /** + * Get option for imap_open and imap_reopen. + * It supports only isReadOnly feature. + * + * @return int + */ + protected function getOptions() { + return ($this->isReadOnly()) ? OP_READONLY : 0; + } + + /** + * Get full address of mailbox. + * + * @return string + */ + protected function getAddress() { + $address = "{".$this->host.":".$this->port."/".($this->protocol ? $this->protocol : 'imap'); + if (!$this->validate_cert) { + $address .= '/novalidate-cert'; + } + if (in_array($this->encryption,['tls','ssl'])) { + $address .= '/'.$this->encryption; + } + $address .= '}'; + + return $address; + } + + /** + * Retrieve the quota level settings, and usage statics per mailbox + * + * @return array + * @throws ConnectionFailedException + */ + public function getQuota() { + $this->checkConnection(); + return imap_get_quota($this->getConnection(), 'user.'.$this->username); + } + + /** + * Retrieve the quota settings per user + * + * @param string $quota_root + * + * @return array + * @throws ConnectionFailedException + */ + public function getQuotaRoot($quota_root = 'INBOX') { + $this->checkConnection(); + return imap_get_quotaroot($this->getConnection(), $quota_root); + } + + /** + * Gets the number of messages in the current mailbox + * + * @return int + * @throws ConnectionFailedException + */ + public function countMessages() { + $this->checkConnection(); + return imap_num_msg($this->connection); + } + + /** + * Gets the number of recent messages in current mailbox + * + * @return int + * @throws ConnectionFailedException + */ + public function countRecentMessages() { + $this->checkConnection(); + return imap_num_recent($this->connection); + } + + /** + * Returns all IMAP alert messages that have occurred + * + * @return array + */ + public function getAlerts() { + return imap_alerts(); + } + + /** + * Returns all of the IMAP errors that have occurred + * + * @return array + */ + public function getErrors() { + $this->errors = array_merge($this->errors, imap_errors() ?: []); + + return $this->errors; + } + + /** + * Gets the last IMAP error that occurred during this page request + * + * @return string + */ + public function getLastError() { + return imap_last_error(); + } + + /** + * Delete all messages marked for deletion + * + * @return bool + * @throws ConnectionFailedException + */ + public function expunge() { + $this->checkConnection(); + return imap_expunge($this->connection); + } + + /** + * Check current mailbox + * + * @return object { + * Date [string(37) "Wed, 8 Mar 2017 22:17:54 +0100 (CET)"] current system time formatted according to » RFC2822 + * Driver [string(4) "imap"] protocol used to access this mailbox: POP3, IMAP, NNTP + * Mailbox ["{root@example.com:993/imap/user="root@example.com"}INBOX"] the mailbox name + * Nmsgs [int(1)] number of messages in the mailbox + * Recent [int(0)] number of recent messages in the mailbox + * } + * @throws ConnectionFailedException + */ + public function checkCurrentMailbox() { + $this->checkConnection(); + return imap_check($this->connection); + } +} diff --git a/freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Message.php b/freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Message.php new file mode 100644 index 0000000..4ba67ff --- /dev/null +++ b/freescout-dist/overrides/webklex/laravel-imap/src/IMAP/Message.php @@ -0,0 +1,1315 @@ +setFetchOption($fetch_options); + $this->setFetchBodyOption($fetch_body); + $this->setFetchAttachmentOption($fetch_attachment); + $this->setFetchFlagsOption($fetch_flags); + + $this->attachments = AttachmentCollection::make([]); + $this->flags = FlagCollection::make([]); + + $this->msglist = $msglist; + $this->client = $client; + + $this->uid = ($this->fetch_options == FT_UID) ? $uid : $uid; + $this->msgn = ($this->fetch_options == FT_UID) ? imap_msgno($this->client->getConnection(), $uid) : $uid; + + $this->parseHeader(); + + if ($this->getFetchFlagsOption() === true) { + $this->parseFlags(); + } + + if ($this->getFetchBodyOption() === true) { + $this->parseBody(); + } + } + + /** + * Copy the current Messages to a mailbox. + * + * @param $mailbox + * @param int $options + * + * @throws Exceptions\ConnectionFailedException + * + * @return bool + */ + public function copy($mailbox, $options = 0) + { + return imap_mail_copy($this->client->getConnection(), $this->msglist, $mailbox, $options); + } + + /** + * Move the current Messages to a mailbox. + * + * @param $mailbox + * @param int $options + * + * @throws Exceptions\ConnectionFailedException + * + * @return bool + */ + public function move($mailbox, $options = 0) + { + return imap_mail_move($this->client->getConnection(), $this->msglist, $mailbox, $options); + } + + /** + * Check if the Message has a text body. + * + * @return bool + */ + public function hasTextBody() + { + return isset($this->bodies['text']); + } + + /** + * Get the Message text body. + * + * @return mixed + */ + public function getTextBody() + { + if (!isset($this->bodies['text'])) { + return false; + } + + return $this->bodies['text']->content; + } + + /** + * Check if the Message has a html body. + * + * @return bool + */ + public function hasHTMLBody() + { + return isset($this->bodies['html']); + } + + /** + * Get the Message html body. + * + * @var bool + * + * @return mixed + */ + public function getHTMLBody($replaceImages = false) + { + if (!isset($this->bodies['html'])) { + return false; + } + + $body = $this->bodies['html']->content; + if ($replaceImages) { + $this->attachments->each(function ($oAttachment) use (&$body) { + if ($oAttachment->id && isset($oAttachment->img_src)) { + $body = str_replace('cid:'.$oAttachment->id, $oAttachment->img_src, $body); + } + }); + } + + return $body; + } + + /** + * Parse all defined headers. + * + * @throws Exceptions\ConnectionFailedException + * + * @return void + */ + private function parseHeader() + { + $this->header = $header = imap_fetchheader($this->client->getConnection(), $this->uid, FT_UID); + if ($this->header) { + $header = imap_rfc822_parse_headers($this->header); + } + + if (preg_match('/x\-priority\:.*([0-9]{1,2})/i', $this->header, $priority)) { + $priority = isset($priority[1]) ? (int) $priority[1] : 0; + switch ($priority) { + case self::PRIORITY_HIGHEST: + $this->priority = self::PRIORITY_HIGHEST; + break; + case self::PRIORITY_HIGH: + $this->priority = self::PRIORITY_HIGH; + break; + case self::PRIORITY_NORMAL: + $this->priority = self::PRIORITY_NORMAL; + break; + case self::PRIORITY_LOW: + $this->priority = self::PRIORITY_LOW; + break; + case self::PRIORITY_LOWEST: + $this->priority = self::PRIORITY_LOWEST; + break; + default: + $this->priority = self::PRIORITY_UNKNOWN; + break; + } + } + + if (property_exists($header, 'subject')) { + // https://github.com/freescout-helpdesk/freescout/issues/2965 + // Also imap_utf8() can't properly decode =?gb18030?B?1eLKx9K7t+Ky4srU08q8/g==?= + // iconv_mime_decode() - can. + // if (!\Str::startsWith(mb_strtolower($header->subject), '=?utf-8?')) { + // $this->subject = \imap_utf8($header->subject); + // } else { + $this->subject = \MailHelper::decodeSubject($header->subject); + //} + + // if (\Str::startsWith(mb_strtolower($this->subject), '=?utf-8?')) { + + // // https://bugs.php.net/bug.php?id=68821 + // $this->subject = preg_replace_callback('/(=\?[^\?]+\?Q\?)([^\?]+)(\?=)/i', function($matches) { + // return $matches[1] . str_replace('_', '=20', $matches[2]) . $matches[3]; + // }, $header->subject); + + // $this->subject = mb_decode_mimeheader($this->subject); + // } + } + + if (property_exists($header, 'date')) { + $date = $header->date; + + /* + * Exception handling for invalid dates + * + * Currently known invalid formats: + * ^ Datetime ^ Problem ^ Cause + * | Mon, 20 Nov 2017 20:31:31 +0800 (GMT+8:00) | Double timezone specification | A Windows feature + * | Thu, 8 Nov 2018 08:54:58 -0200 (-02) | + * | | and invalid timezone (max 6 char) | + * | 04 Jan 2018 10:12:47 UT | Missing letter "C" | Unknown + * | Thu, 31 May 2018 18:15:00 +0800 (added by) | Non-standard details added by the | Unknown + * | | mail server | + * | Sat, 31 Aug 2013 20:08:23 +0580 | Invalid timezone | PHPMailer bug https://sourceforge.net/p/phpmailer/mailman/message/6132703/ + * + * Please report any new invalid timestamps to [#45](https://github.com/Webklex/laravel-imap/issues/45) + */ + // try { + // $this->date = Carbon::parse($date); + // } catch (\Exception $e) { + // switch (true) { + // case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ [\-|\+][0-9]{4}\ \(.*)\)+$/i', $date) > 0: + // case preg_match('/([0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{2,4}\ [0-9]{2}\:[0-9]{2}\:[0-9]{2}\ [A-Z]{2}\ \-[0-9]{2}\:[0-9]{2}\ \([A-Z]{2,3}\ \-[0-9]{2}:[0-9]{2}\))+$/i', $date) > 0: + // $array = explode('(', $date); + // $array = array_reverse($array); + // $date = trim(array_pop($array)); + // break; + // case preg_match('/([0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ UT)+$/i', $date) > 0: + // $date .= 'C'; + // break; + // } + // $date = preg_replace('/[<>]/', '', $date); + // try { + // $this->date = Carbon::parse($date); + // } catch (\Exception $e) { + // \Helper::logException($e, '[Webklex\IMAP\Message]'); + // } + // } + if (preg_match('/\+0580/', $date)) { + $date = str_replace('+0580', '+0530', $date); + } + $date = trim(rtrim($date)); + $date = preg_replace('/[<>]/', '', $date); + $date = str_replace('_', ' ', $date); + try { + $this->date = Carbon::parse($date); + } catch (\Exception $e) { + switch (true) { + case preg_match('/([0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ UT)+$/i', $date) > 0: + case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ UT)+$/i', $date) > 0: + $date .= 'C'; + break; + case preg_match('/([A-Z]{2,3}[\,|\ \,]\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}.*)+$/i', $date) > 0: + case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ [\-|\+][0-9]{4}\ \(.*)\)+$/i', $date) > 0: + case preg_match('/([A-Z]{2,3}\, \ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ [\-|\+][0-9]{4}\ \(.*)\)+$/i', $date) > 0: + case preg_match('/([0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{2,4}\ [0-9]{2}\:[0-9]{2}\:[0-9]{2}\ [A-Z]{2}\ \-[0-9]{2}\:[0-9]{2}\ \([A-Z]{2,3}\ \-[0-9]{2}:[0-9]{2}\))+$/i', $date) > 0: + $array = explode('(', $date); + $array = array_reverse($array); + $date = trim(array_pop($array)); + break; + } + try { + $this->date = Carbon::parse($date); + } catch (\Exception $_e) { + $this->date = Carbon::now(); + // No need to write this to log. + // https://github.com/freescout-helpdesk/freescout/issues/2734 + // + // \Helper::logException($_e, '[Webklex\IMAP\Message]'); + // \Helper::logExceptionToActivityLog($_e, + // \App\ActivityLog::NAME_EMAILS_FETCHING, + // \App\ActivityLog::DESCRIPTION_EMAILS_FETCHING_ERROR + // ); + //throw new InvalidMessageDateException("Invalid message date. ID:".$this->getMessageId(), 1000, $e); + } + } + } + + if (property_exists($header, 'from')) { + $this->from = $this->parseAddresses($header->from); + } + if (property_exists($header, 'to')) { + $this->to = $this->parseAddresses($header->to); + } + if (property_exists($header, 'cc')) { + $this->cc = $this->parseAddresses($header->cc); + } + if (property_exists($header, 'bcc')) { + $this->bcc = $this->parseAddresses($header->bcc); + } + if (property_exists($header, 'references')) { + $this->references = $header->references; + } + + if (property_exists($header, 'reply_to')) { + $this->reply_to = $this->parseAddresses($header->reply_to); + } + if (property_exists($header, 'in_reply_to')) { + $this->in_reply_to = str_replace(['<', '>'], '', $header->in_reply_to); + } + if (property_exists($header, 'sender')) { + $this->sender = $this->parseAddresses($header->sender); + } + + if (property_exists($header, 'message_id')) { + $this->message_id = str_replace(['<', '>'], '', $header->message_id); + } + if (property_exists($header, 'Msgno')) { + $messageNo = (int) trim($header->Msgno); + $this->message_no = ($this->fetch_options == FT_UID) ? $messageNo : imap_msgno($this->client->getConnection(), $messageNo); + } else { + $this->message_no = imap_msgno($this->client->getConnection(), $this->getUid()); + } + } + + /** + * Parse additional flags. + * + * @throws Exceptions\ConnectionFailedException + * + * @return void + */ + private function parseFlags() + { + $flags = imap_fetch_overview($this->client->getConnection(), $this->uid, FT_UID); + if (is_array($flags) && isset($flags[0])) { + if (property_exists($flags[0], 'recent')) { + $this->flags->put('recent', $flags[0]->recent); + } + if (property_exists($flags[0], 'flagged')) { + $this->flags->put('flagged', $flags[0]->flagged); + } + if (property_exists($flags[0], 'answered')) { + $this->flags->put('answered', $flags[0]->answered); + } + if (property_exists($flags[0], 'deleted')) { + $this->flags->put('deleted', $flags[0]->deleted); + } + if (property_exists($flags[0], 'seen')) { + $this->flags->put('seen', $flags[0]->seen); + } + if (property_exists($flags[0], 'draft')) { + $this->flags->put('draft', $flags[0]->draft); + } + } + } + + /** + * Get the current Message header info. + * + * @throws Exceptions\ConnectionFailedException + * + * @return object + */ + public function getHeaderInfo() + { + if ($this->header_info == null) { + $this->header_info = + $this->header_info = imap_headerinfo($this->client->getConnection(), $this->getMessageNo()); + } + + return $this->header_info; + } + + /** + * Parse Addresses. + * + * @param $list + * + * @return array + */ + private function parseAddresses($list) + { + $addresses = []; + + foreach ($list as $item) { + $address = (object) $item; + + if (!property_exists($address, 'mailbox')) { + $address->mailbox = false; + } + if (!property_exists($address, 'host')) { + $address->host = false; + } + if (!property_exists($address, 'personal')) { + $address->personal = false; + } + + $personalParts = imap_mime_header_decode($address->personal); + + if (!is_array($personalParts)) { + $p = new \stdClass(); + $p->text = $address->personal; + $personalParts = [ + $p + ]; + } + + $address->personal = ''; + foreach ($personalParts as $p) { + //$address->personal .= $p->text; + $encoding = (property_exists($p, 'charset')) ? $p->charset : $this->getEncoding($p->text); + $address->personal .= $this->convertEncoding($p->text, $encoding); + } + + $address->mail = ($address->mailbox && $address->host) ? $address->mailbox.'@'.$address->host : false; + $address->full = ($address->personal) ? $address->personal.' <'.$address->mail.'>' : $address->mail; + + $addresses[] = $address; + } + + return $addresses; + } + + /** + * Parse the Message body. + * + * @throws Exceptions\ConnectionFailedException + * + * @return $this + */ + public function parseBody() + { + $structure = imap_fetchstructure($this->client->getConnection(), $this->uid, FT_UID); + + if (property_exists($structure, 'parts')) { + $parts = $structure->parts; + + foreach ($parts as $part) { + foreach ($part->parameters as $parameter) { + if ($parameter->attribute == 'charset') { + $encoding = $parameter->value; + $parameter->value = preg_replace('/Content-Transfer-Encoding/', '', $encoding); + } + } + } + } + + $this->fetchStructure($structure); + + return $this; + } + + /** + * Fetch the Message structure. + * + * @param $structure + * @param mixed $partNumber + * + * @throws Exceptions\ConnectionFailedException + */ + private function fetchStructure($structure, $partNumber = null) + { + if ($structure->type == self::TYPE_TEXT && + # FreeScout #320 + #($structure->ifdisposition == 0 || + # ($structure->ifdisposition == 1 && !isset($structure->parts) && $partNumber == null) + #) + (empty($structure->disposition) || strtolower($structure->disposition) != 'attachment') + ) { + // FreeScout improvement + /*if (strtolower($structure->subtype) == 'plain' || strtolower($structure->subtype) == 'csv') { + if (!$partNumber) { + $partNumber = 1; + } + + $encoding = $this->getEncoding($structure); + + $content = imap_fetchbody($this->client->getConnection(), $this->uid, $partNumber, $this->fetch_options | FT_UID); + $content = $this->decodeString($content, $structure->encoding); + $content = $this->convertEncoding($content, $encoding); + + $body = new \stdClass(); + $body->type = 'text'; + $body->content = $content; + + $this->bodies['text'] = $body; + + $this->fetchAttachment($structure, $partNumber); + } elseif (strtolower($structure->subtype) == 'html') { + if (!$partNumber) { + $partNumber = 1; + } + + $encoding = $this->getEncoding($structure); + + $content = imap_fetchbody($this->client->getConnection(), $this->uid, $partNumber, $this->fetch_options | FT_UID); + $content = $this->decodeString($content, $structure->encoding); + $content = $this->convertEncoding($content, $encoding); + + $body = new \stdClass(); + $body->type = 'html'; + $body->content = $content; + + $this->bodies['html'] = $body; + }*/ + if (strtolower($structure->subtype) == 'html') { + if (!$partNumber) { + $partNumber = 1; + } + + $encoding = $this->getEncoding($structure); + + $content = imap_fetchbody($this->client->getConnection(), $this->uid, $partNumber, $this->fetch_options | FT_UID); + $content = $this->decodeString($content, $structure->encoding); + $content = $this->convertEncoding($content, $encoding); + + // FreeScout #381 + // Some messages (for exaple Apple Mail) may have multiple HTML parts. + if (empty($this->bodies['html'])) { + $body = new \stdClass(); + $body->type = 'html'; + $body->content = $content; + + $this->bodies['html'] = $body; + } else { + $this->bodies['html']->content .= $content; + } + } else { + // PLAIN. + if (!$partNumber) { + $partNumber = 1; + } + + $encoding = $this->getEncoding($structure); + + $content = imap_fetchbody($this->client->getConnection(), $this->uid, $partNumber, $this->fetch_options | FT_UID); + $content = $this->decodeString($content, $structure->encoding); + $content = $this->convertEncoding($content, $encoding); + + if (empty($this->bodies['text'])) { + $body = new \stdClass(); + $body->type = 'text'; + $body->content = $content; + + $this->bodies['text'] = $body; + } else { + $this->bodies['text']->content .= $content; + } + + $this->fetchAttachment($structure, $partNumber); + } + } elseif ($structure->type == self::TYPE_MULTIPART) { + foreach ($structure->parts as $index => $subStruct) { + $prefix = ''; + if ($partNumber) { + $prefix = $partNumber.'.'; + } + $this->fetchStructure($subStruct, $prefix.($index + 1)); + } + } else { + if ($this->getFetchAttachmentOption() === true) { + $this->fetchAttachment($structure, $partNumber); + } + } + } + + /** + * Fetch the Message attachment. + * + * @param object $structure + * @param mixed $partNumber + * + * @throws Exceptions\ConnectionFailedException + */ + protected function fetchAttachment($structure, $partNumber) + { + $oAttachment = new Attachment($this, $structure, $partNumber); + + if ($oAttachment->getName() !== null) { + if ($oAttachment->getId() !== null) { + $this->attachments->put($oAttachment->getId(), $oAttachment); + } else { + $this->attachments->push($oAttachment); + } + } + } + + /** + * Fail proof setter for $fetch_option. + * + * @param $option + * + * @return $this + */ + public function setFetchOption($option) + { + if (is_int($option) === true) { + $this->fetch_options = $option; + } elseif (is_null($option) === true) { + $config = config('imap.options.fetch', FT_UID); + $this->fetch_options = is_int($config) ? $config : 1; + } + + return $this; + } + + /** + * Fail proof setter for $fetch_body. + * + * @param $option + * + * @return $this + */ + public function setFetchBodyOption($option) + { + if (is_bool($option)) { + $this->fetch_body = $option; + } elseif (is_null($option)) { + $config = config('imap.options.fetch_body', true); + $this->fetch_body = is_bool($config) ? $config : true; + } + + return $this; + } + + /** + * Fail proof setter for $fetch_attachment. + * + * @param $option + * + * @return $this + */ + public function setFetchAttachmentOption($option) + { + if (is_bool($option)) { + $this->fetch_attachment = $option; + } elseif (is_null($option)) { + $config = config('imap.options.fetch_attachment', true); + $this->fetch_attachment = is_bool($config) ? $config : true; + } + + return $this; + } + + /** + * Fail proof setter for $fetch_flags. + * + * @param $option + * + * @return $this + */ + public function setFetchFlagsOption($option) + { + if (is_bool($option)) { + $this->fetch_flags = $option; + } elseif (is_null($option)) { + $config = config('imap.options.fetch_flags', true); + $this->fetch_flags = is_bool($config) ? $config : true; + } + + return $this; + } + + /** + * Decode a given string. + * + * @param $string + * @param $encoding + * + * @return string + */ + public function decodeString($string, $encoding) + { + switch ($encoding) { + case self::ENC_7BIT: + return $string; + case self::ENC_8BIT: + return quoted_printable_decode(imap_8bit($string)); + case self::ENC_BINARY: + return imap_base64(imap_binary($string)); + case self::ENC_BASE64: + return imap_base64($string); + case self::ENC_QUOTED_PRINTABLE: + return quoted_printable_decode($string); + case self::ENC_OTHER: + return $string; + default: + return $string; + } + } + + /** + * Convert the encoding. + * + * @param $str + * @param string $from + * @param string $to + * + * @return mixed|string + */ + public function convertEncoding($str, $from = 'ISO-8859-2', $to = 'UTF-8') + { + + // FreeScout fix + // We don't need to do convertEncoding() if charset is ASCII (us-ascii): + // ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded + // https://stackoverflow.com/a/11303410 + // + // us-ascii is the same as ASCII: + // ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA) + // prefers the updated name US-ASCII, which clarifies that this system was developed in the US and + // based on the typographical symbols predominantly in use there. + // https://en.wikipedia.org/wiki/ASCII + // + // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken. + if (strtolower($from) == 'us-ascii' && $to == 'UTF-8') { + return $str; + } + + if (strtolower($from) == 'iso-2022-jp'){ + $from = 'iso-2022-jp-ms'; + } + + try { + try { + // if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') { + // // FreeScout #351 + // return iconv($from, $to, $str); + // } else { + // if (!$from) { + // return mb_convert_encoding($str, $to); + // } + + // return mb_convert_encoding($str, $to, $from); + // } + + // Try iconv. + if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7' && $from != 'iso-2022-jp-ms') { + try { + $result = iconv($from, $to.'//IGNORE', $str); + } catch (\Exception $e) { + $result = @iconv($from, $to, $str); + } + } + + // In some cases iconv can't decode the string and returns: + // Detected an illegal character in input string. + // https://github.com/freescout-helpdesk/freescout/issues/3089 + if (!$result) { + if (!$from) { + return mb_convert_encoding($str, $to); + } + return mb_convert_encoding($str, $to, $from); + } else { + return $result; + } + } catch (\Exception $e) { + // FreeScout #360 + if (strstr($from, '-')) { + $from = str_replace('-', '', $from); + return $this->convertEncoding($str, $from, $to); + } else { + // No need to log this error. + // \Helper::logException($e, '[Webklex\IMAP\Message]'); + // \Helper::logExceptionToActivityLog($e, + // \App\ActivityLog::NAME_EMAILS_FETCHING, + // \App\ActivityLog::DESCRIPTION_EMAILS_FETCHING_ERROR + // ); + return $str; + } + } + } catch (\Throwable $e) { + if (strstr($from, '-')) { + $from = str_replace('-', '', $from); + return $this->convertEncoding($str, $from, $to); + } else { + return $str; + } + } + } + + /** + * Get the encoding of a given abject. + * + * @param object|string $structure + * + * @return string + */ + public function getEncoding($structure) + { + if (property_exists($structure, 'parameters')) { + foreach ($structure->parameters as $parameter) { + if (strtolower($parameter->attribute) == 'charset') { + return EncodingAliases::get($parameter->value); + } + } + } elseif (is_string($structure) === true) { + return mb_detect_encoding($structure); + } + + return 'UTF-8'; + } + + /** + * Find the folder containing this message. + * + * @param null|Folder $folder where to start searching from (top-level inbox by default) + * + * @throws Exceptions\ConnectionFailedException + * + * @return null|Folder + */ + public function getContainingFolder(Folder $folder = null) + { + $folder = $folder ?: $this->client->getFolders()->first(); + $this->client->checkConnection(); + + // Try finding the message by uid in the current folder + $client = new Client(); + $client->openFolder($folder); + $uidMatches = imap_fetch_overview($client->getConnection(), $this->uid, FT_UID); + $uidMatch = count($uidMatches) + ? new self($uidMatches[0]->uid, $uidMatches[0]->msgno, $client) + : null; + $client->disconnect(); + + // imap_fetch_overview() on a parent folder will return the matching message + // even when the message is in a child folder so we need to recursively + // search the children + foreach ($folder->children as $child) { + $childFolder = $this->getContainingFolder($child); + + if ($childFolder) { + return $childFolder; + } + } + + // before returning the parent + if ($this->is($uidMatch)) { + return $folder; + } + + // or signalling that the message was not found in any folder + } + + /** + * Move the Message into an other Folder. + * + * @param string $mailbox + * + * @throws Exceptions\ConnectionFailedException + * + * @return bool + */ + public function moveToFolder($mailbox = 'INBOX') + { + $this->client->createFolder($mailbox); + + return imap_mail_move($this->client->getConnection(), $this->uid, $mailbox, CP_UID); + } + + /** + * Delete the current Message. + * + * @param bool $expunge + * + * @throws Exceptions\ConnectionFailedException + * + * @return bool + */ + public function delete($expunge = true) + { + $status = imap_delete($this->client->getConnection(), $this->uid, FT_UID); + if ($expunge) { + $this->client->expunge(); + } + + return $status; + } + + /** + * Restore a deleted Message. + * + * @param bool $expunge + * + * @throws Exceptions\ConnectionFailedException + * + * @return bool + */ + public function restore($expunge = true) + { + $status = imap_undelete($this->client->getConnection(), $this->uid, FT_UID); + if ($expunge) { + $this->client->expunge(); + } + + return $status; + } + + /** + * Get all message attachments. + * + * @return AttachmentCollection + */ + public function getAttachments() + { + return $this->attachments; + } + + /** + * Checks if there are any attachments present. + * + * @return bool + */ + public function hasAttachments() + { + return $this->attachments->isEmpty() === false; + } + + /** + * Set a given flag. + * + * @param string|array $flag + * + * @throws Exceptions\ConnectionFailedException + * + * @return bool + */ + public function setFlag($flag) + { + $flag = '\\'.trim(is_array($flag) ? implode(' \\', $flag) : $flag); + $status = imap_setflag_full($this->client->getConnection(), $this->getUid(), $flag, SE_UID); + $this->parseFlags(); + + return $status; + } + + /** + * Unset a given flag. + * + * @param string|array $flag + * + * @throws Exceptions\ConnectionFailedException + * + * @return bool + */ + public function unsetFlag($flag) + { + $flag = '\\'.trim(is_array($flag) ? implode(' \\', $flag) : $flag); + $status = imap_clearflag_full($this->client->getConnection(), $this->getUid(), $flag, SE_UID); + $this->parseFlags(); + + return $status; + } + + /** + * @throws Exceptions\ConnectionFailedException + * + * @return null|object|string + */ + public function getRawBody() + { + if ($this->raw_body === null) { + $this->raw_body = imap_fetchbody($this->client->getConnection(), $this->getUid(), '', $this->fetch_options | FT_UID); + } + + return $this->raw_body; + } + + /** + * @return string + */ + public function getHeader() + { + return $this->header; + } + + /** + * @return Client + */ + public function getClient() + { + return $this->client; + } + + /** + * @return int + */ + public function getUid() + { + return $this->uid; + } + + /** + * @return int + */ + public function getFetchOptions() + { + return $this->fetch_options; + } + + /** + * @return bool + */ + public function getFetchBodyOption() + { + return $this->fetch_body; + } + + /** + * @return int + */ + public function getPriority() + { + return $this->priority; + } + + /** + * @return bool + */ + public function getFetchAttachmentOption() + { + return $this->fetch_attachment; + } + + /** + * @return bool + */ + public function getFetchFlagsOption() + { + return $this->fetch_flags; + } + + /** + * @return int + */ + public function getMsglist() + { + return $this->msglist; + } + + /** + * @return mixed + */ + public function getMessageId() + { + return $this->message_id; + } + + /** + * @return int + */ + public function getMessageNo() + { + return $this->message_no; + } + + /** + * @return string + */ + public function getSubject() + { + return $this->subject; + } + + /** + * @return mixed + */ + public function getReferences() + { + return $this->references; + } + + /** + * @return Carbon|null + */ + public function getDate() + { + return $this->date; + } + + /** + * @return array + */ + public function getFrom() + { + return $this->from; + } + + /** + * @return array + */ + public function getTo() + { + return $this->to; + } + + /** + * @return array + */ + public function getCc() + { + return $this->cc; + } + + /** + * @return array + */ + public function getBcc() + { + return $this->bcc; + } + + /** + * @return array + */ + public function getReplyTo() + { + return $this->reply_to; + } + + /** + * @return string + */ + public function getInReplyTo() + { + return $this->in_reply_to; + } + + /** + * @return array + */ + public function getSender() + { + return $this->sender; + } + + /** + * @return mixed + */ + public function getBodies() + { + return $this->bodies; + } + + /** + * @return FlagCollection + */ + public function getFlags() + { + return $this->flags; + } + + /** + * Does this message match another one? + * + * A match means same uid, message id, subject and date/time. + * + * @param null|static $message + * + * @return bool + */ + public function is(self $message = null) + { + if (is_null($message)) { + return false; + } + + return $this->uid == $message->uid + && $this->message_id == $message->message_id + && $this->subject == $message->subject + && $this->date->eq($message->date); + } +} diff --git a/freescout-dist/overrides/webklex/php-imap/src/Attachment.php b/freescout-dist/overrides/webklex/php-imap/src/Attachment.php new file mode 100644 index 0000000..c1b35db --- /dev/null +++ b/freescout-dist/overrides/webklex/php-imap/src/Attachment.php @@ -0,0 +1,388 @@ + null, + 'type' => null, + 'part_number' => 0, + 'content_type' => null, + 'id' => null, + 'name' => null, + 'disposition' => null, + 'img_src' => null, + 'size' => null, + ]; + + /** + * Default mask + * + * @var string $mask + */ + protected $mask = AttachmentMask::class; + + /** + * Attachment constructor. + * @param Message $oMessage + * @param Part $part + */ + public function __construct(Message $oMessage, Part $part) { + $this->config = ClientManager::get('options'); + + $this->oMessage = $oMessage; + $this->part = $part; + $this->part_number = $part->part_number; + + $default_mask = $this->oMessage->getClient()->getDefaultAttachmentMask(); + if($default_mask != null) { + $this->mask = $default_mask; + } + + $this->findType(); + $this->fetch(); + } + + /** + * Call dynamic attribute setter and getter methods + * @param string $method + * @param array $arguments + * + * @return mixed + * @throws MethodNotFoundException + */ + public function __call(string $method, array $arguments) { + if(strtolower(substr($method, 0, 3)) === 'get') { + $name = Str::snake(substr($method, 3)); + + if(isset($this->attributes[$name])) { + return $this->attributes[$name]; + } + + return null; + }elseif (strtolower(substr($method, 0, 3)) === 'set') { + $name = Str::snake(substr($method, 3)); + + $this->attributes[$name] = array_pop($arguments); + + return $this->attributes[$name]; + } + + throw new MethodNotFoundException("Method ".self::class.'::'.$method.'() is not supported'); + } + + /** + * Magic setter + * @param $name + * @param $value + * + * @return mixed + */ + public function __set($name, $value) { + $this->attributes[$name] = $value; + + return $this->attributes[$name]; + } + + /** + * magic getter + * @param $name + * + * @return mixed|null + */ + public function __get($name) { + if(isset($this->attributes[$name])) { + return $this->attributes[$name]; + } + + return null; + } + + /** + * Determine the structure type + */ + protected function findType() { + switch ($this->part->type) { + case IMAP::ATTACHMENT_TYPE_MESSAGE: + $this->type = 'message'; + break; + case IMAP::ATTACHMENT_TYPE_APPLICATION: + $this->type = 'application'; + break; + case IMAP::ATTACHMENT_TYPE_AUDIO: + $this->type = 'audio'; + break; + case IMAP::ATTACHMENT_TYPE_IMAGE: + $this->type = 'image'; + break; + case IMAP::ATTACHMENT_TYPE_VIDEO: + $this->type = 'video'; + break; + case IMAP::ATTACHMENT_TYPE_MODEL: + $this->type = 'model'; + break; + case IMAP::ATTACHMENT_TYPE_TEXT: + $this->type = 'text'; + break; + case IMAP::ATTACHMENT_TYPE_MULTIPART: + $this->type = 'multipart'; + break; + default: + $this->type = 'other'; + break; + } + } + + /** + * Fetch the given attachment + */ + protected function fetch() { + + $content = $this->part->content; + + $this->content_type = $this->part->content_type; + $this->content = $this->oMessage->decodeString($content, $this->part->encoding); + + if (($id = $this->part->id) !== null) { + $this->id = str_replace(['<', '>'], '', $id); + } + + $this->size = $this->part->bytes; + $this->disposition = $this->part->disposition; + + if (($filename = $this->part->filename) !== null) { + $this->setName($filename); + } elseif (($name = $this->part->name) !== null) { + $this->setName($name); + }else { + $this->setName("undefined"); + } + + if (IMAP::ATTACHMENT_TYPE_MESSAGE == $this->part->type) { + if ($this->part->ifdescription) { + $this->setName($this->part->description); + } else { + $this->setName($this->part->subtype); + } + } + } + + /** + * Save the attachment content to your filesystem + * @param string $path + * @param string|null $filename + * + * @return boolean + */ + public function save(string $path, $filename = null): bool { + $filename = $filename ?: $this->getName(); + + // sanitize $name + // order of '..' is important + // https://github.com/freescout-helpdesk/freescout/issues/3592 + // https://github.com/Webklex/php-imap/issues/461 + $filename = str_replace(['\\', '../', '/..', '/', chr(0), ':'], '', $filename ?? ''); + + return file_put_contents($path.$filename, $this->getContent()) !== false; + } + + /** + * Set the attachment name and try to decode it + * @param $name + */ + public function setName($name) { + // $decoder = $this->config['decoder']['attachment']; + // if ($name !== null) { + // if($decoder === 'utf-8' && extension_loaded('imap')) { + // $this->name = \imap_utf8($name); + // }else{ + // $this->name = mb_decode_mimeheader($name); + // } + // } + // https://github.com/freescout-helpdesk/freescout/issues/3089 + if ($name !== null) { + // RFC6266 and RFC8187 + // UTF-8''%E3%80... + // utf-8'en'%C2%A3%20rates + preg_match("#([^']+)'([^']{2})?'(.+)#", $name, $m); + $name_charset = ''; + if (!empty($m[1]) && !empty($m[3])) { + $name = $m[3]; + $name_charset = $m[1]; + } + + // $decoder = $this->config['decoder']['message']; + // if ($decoder === 'utf-8' && extension_loaded('imap')) { + // $name = \imap_utf8($name); + // } + + //if (preg_match('/=\?([^?]+)\?(Q|B)\?(.+)\?=/i', $name, $matches)) { + $name = \MailHelper::decodeSubject($name); + //} + + // check if $name is url encoded + if (preg_match('/%[0-9A-F]{2}/i', $name)) { + $name = urldecode($name); + } + + // sanitize $name + // order of '..' is important + $name = str_replace(['\\', '../', '/..', '/', chr(0), ':'], '', $name); + } + $this->name = $name; + } + + /** + * Get the attachment mime type + * + * @return string|null + */ + public function getMimeType(){ + return (new \finfo())->buffer($this->getContent(), FILEINFO_MIME_TYPE); + } + + /** + * Try to guess the attachment file extension + * + * @return string|null + */ + public function getExtension(){ + $guesser = "\Symfony\Component\Mime\MimeTypes"; + if (class_exists($guesser) !== false) { + /** @var Symfony\Component\Mime\MimeTypes $guesser */ + $extensions = $guesser::getDefault()->getExtensions($this->getMimeType()); + return $extensions[0] ?? null; + } + + $deprecated_guesser = "\Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser"; + if (class_exists($deprecated_guesser) !== false){ + /** @var \Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser $deprecated_guesser */ + return $deprecated_guesser::getInstance()->guess($this->getMimeType()); + } + + return null; + } + + /** + * Get all attributes + * + * @return array + */ + public function getAttributes(): array { + return $this->attributes; + } + + /** + * @return Message + */ + public function getMessage(): Message { + return $this->oMessage; + } + + /** + * Set the default mask + * @param $mask + * + * @return $this + */ + public function setMask($mask): Attachment { + if(class_exists($mask)){ + $this->mask = $mask; + } + + return $this; + } + + /** + * Get the used default mask + * + * @return string + */ + public function getMask(): string { + return $this->mask; + } + + /** + * Get a masked instance by providing a mask name + * @param string|null $mask + * + * @return mixed + * @throws MaskNotFoundException + */ + public function mask($mask = null){ + $mask = $mask !== null ? $mask : $this->mask; + if(class_exists($mask)){ + return new $mask($this); + } + + throw new MaskNotFoundException("Unknown mask provided: ".$mask); + } +} \ No newline at end of file diff --git a/freescout-dist/overrides/webklex/php-imap/src/Connection/Protocols/ImapProtocol.php b/freescout-dist/overrides/webklex/php-imap/src/Connection/Protocols/ImapProtocol.php new file mode 100644 index 0000000..98ec9cb --- /dev/null +++ b/freescout-dist/overrides/webklex/php-imap/src/Connection/Protocols/ImapProtocol.php @@ -0,0 +1,1162 @@ +setCertValidation($cert_validation); + $this->encryption = $encryption; + } + + /** + * Public destructor + */ + public function __destruct() { + $this->logout(); + } + + /** + * Open connection to IMAP server + * @param string $host hostname or IP address of IMAP server + * @param int|null $port of IMAP server, default is 143 and 993 for ssl + * + * @throws ConnectionFailedException + */ + public function connect(string $host, $port = null) { + $transport = 'tcp'; + $encryption = ''; + + if ($this->encryption) { + $encryption = strtolower($this->encryption); + if (in_array($encryption, ['ssl', 'tls'])) { + $transport = $encryption; + $port = $port === null ? 993 : $port; + } + } + $port = $port === null ? 143 : $port; + try { + $this->stream = $this->createStream($transport, $host, $port, $this->connection_timeout); + if (!$this->assumedNextLine('* OK')) { + throw new ConnectionFailedException('connection refused'); + } + if ($encryption == 'starttls') { + $this->enableStartTls(); + } + } catch (Exception $e) { + throw new ConnectionFailedException('connection failed', 0, $e); + } + } + + /** + * Enable tls on the current connection + * + * @throws ConnectionFailedException + * @throws RuntimeException + */ + protected function enableStartTls(){ + $response = $this->requestAndResponse('STARTTLS'); + $result = $response && stream_socket_enable_crypto($this->stream, true, $this->getCryptoMethod()); + if (!$result) { + throw new ConnectionFailedException('failed to enable TLS'); + } + } + + /** + * Get the next line from stream + * + * @return string next line + * @throws RuntimeException + */ + public function nextLine(): string { + $line = ""; + while (($next_char = fread($this->stream, 1)) !== false && !in_array($next_char, ["","\n"])) { + $line .= $next_char; + } + if ($line === "" && $next_char === false) { + throw new RuntimeException('empty response'); + } + if ($this->debug) echo "<< ".$line."\n"; + return $line . "\n"; + } + + /** + * Get the next line and check if it starts with a given string + * @param string $start + * + * @return bool + * @throws RuntimeException + */ + protected function assumedNextLine(string $start): bool { + return strpos($this->nextLine(), $start) === 0; + } + + /** + * Get the next line and split the tag + * @param string|null $tag reference tag + * + * @return string next line + * @throws RuntimeException + */ + protected function nextTaggedLine(&$tag): string { + $line = $this->nextLine(); + if (str_contains($line, ' ')) { + list($tag, $line) = explode(' ', $line, 2); + } + + return $line ?? ''; + } + + /** + * Get the next line and check if it contains a given string and split the tag + * @param string $start + * @param $tag + * + * @return bool + * @throws RuntimeException + */ + protected function assumedNextTaggedLine(string $start, &$tag): bool { + $line = $this->nextTaggedLine($tag); + return strpos($line, $start) !== false; + } + + /** + * Split a given line in values. A value is literal of any form or a list + * @param string $line + * + * @return array + * @throws RuntimeException + */ + protected function decodeLine(string $line): array { + $tokens = []; + $stack = []; + + // replace any trailing including spaces with a single space + $line = rtrim($line) . ' '; + while (($pos = strpos($line, ' ')) !== false) { + $token = substr($line, 0, $pos); + if (!strlen($token)) { + $line = substr($line, $pos + 1); + continue; + } + while ($token[0] == '(') { + $stack[] = $tokens; + $tokens = []; + $token = substr($token, 1); + } + if ($token[0] == '"') { + if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) { + $tokens[] = $matches[1]; + $line = substr($line, strlen($matches[0])); + continue; + } + } + if ($token[0] == '{') { + $endPos = strpos($token, '}'); + $chars = substr($token, 1, $endPos - 1); + if (is_numeric($chars)) { + $token = ''; + while (strlen($token) < $chars) { + $token .= $this->nextLine(); + } + $line = ''; + if (strlen($token) > $chars) { + $line = substr($token, $chars); + $token = substr($token, 0, $chars); + } else { + $line .= $this->nextLine(); + } + $tokens[] = $token; + $line = trim($line) . ' '; + continue; + } + } + if ($stack && $token[strlen($token) - 1] == ')') { + // closing braces are not separated by spaces, so we need to count them + $braces = strlen($token); + $token = rtrim($token, ')'); + // only count braces if more than one + $braces -= strlen($token) + 1; + // only add if token had more than just closing braces + if (rtrim($token) != '') { + $tokens[] = rtrim($token); + } + $token = $tokens; + $tokens = array_pop($stack); + // special handline if more than one closing brace + while ($braces-- > 0) { + $tokens[] = $token; + $token = $tokens; + $tokens = array_pop($stack); + } + } + $tokens[] = $token; + $line = substr($line, $pos + 1); + } + + // maybe the server forgot to send some closing braces + while ($stack) { + $child = $tokens; + $tokens = array_pop($stack); + $tokens[] = $child; + } + + return $tokens; + } + + /** + * Read abd decode a response "line" + * @param array|string $tokens to decode + * @param string $wantedTag targeted tag + * @param bool $dontParse if true only the unparsed line is returned in $tokens + * + * @return bool + * @throws RuntimeException + */ + public function readLine(&$tokens = [], string $wantedTag = '*', bool $dontParse = false): bool { + $line = $this->nextTaggedLine($tag); // get next tag + if (!$dontParse) { + $tokens = $this->decodeLine($line); + } else { + $tokens = $line; + } + + // if tag is wanted tag we might be at the end of a multiline response + return $tag == $wantedTag; + } + + /** + * Read all lines of response until given tag is found + * @param string $tag request tag + * @param bool $dontParse if true every line is returned unparsed instead of the decoded tokens + * + * @return array|bool|null tokens if success, false if error, null if bad request + * @throws RuntimeException + */ + public function readResponse(string $tag, bool $dontParse = false) { + $lines = []; + $tokens = null; // define $tokens variable before first use + do { + $readAll = $this->readLine($tokens, $tag, $dontParse); + $lines[] = $tokens; + } while (!$readAll); + + if ($dontParse) { + // First two chars are still needed for the response code + $tokens = [substr($tokens, 0, 2)]; + } + + // last line has response code + if ($tokens[0] == 'OK') { + return $lines ? $lines : true; + } elseif ($tokens[0] == 'NO' || $tokens[0] == 'BAD' || $tokens[0] == 'BYE') { + return false; + } + + return null; + } + + /** + * Send a new request + * @param string $command + * @param array $tokens additional parameters to command, use escapeString() to prepare + * @param string|null $tag provide a tag otherwise an autogenerated is returned + * + * @throws RuntimeException + */ + public function sendRequest(string $command, array $tokens = [], string &$tag = null) { + if (!$tag) { + $this->noun++; + $tag = 'TAG' . $this->noun; + } + + $line = $tag . ' ' . $command; + + foreach ($tokens as $token) { + if (is_array($token)) { + $this->write($line . ' ' . $token[0]); + if (!$this->assumedNextLine('+ ')) { + throw new RuntimeException('failed to send literal string'); + } + $line = $token[1]; + } else { + $line .= ' ' . $token; + } + } + $this->write($line); + } + + /** + * Write data to the current stream + * @param string $data + * @return void + * @throws RuntimeException + */ + public function write(string $data) { + if ($this->debug) echo ">> ".$data ."\n"; + + if (fwrite($this->stream, $data . "\r\n") === false) { + throw new RuntimeException('failed to write - connection closed?'); + } + } + + /** + * Send a request and get response at once + * @param string $command + * @param array $tokens parameters as in sendRequest() + * @param bool $dontParse if true unparsed lines are returned instead of tokens + * + * @return array|bool|null response as in readResponse() + * @throws RuntimeException + */ + public function requestAndResponse(string $command, array $tokens = [], bool $dontParse = false) { + $this->sendRequest($command, $tokens, $tag); + + return $this->readResponse($tag, $dontParse); + } + + /** + * Escape one or more literals i.e. for sendRequest + * @param string|array $string the literal/-s + * + * @return string|array escape literals, literals with newline ar returned + * as array('{size}', 'string'); + */ + public function escapeString($string) { + if (func_num_args() < 2) { + if (strpos($string, "\n") !== false) { + return ['{' . strlen($string) . '}', $string]; + } else { + return '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $string) . '"'; + } + } + $result = []; + foreach (func_get_args() as $string) { + $result[] = $this->escapeString($string); + } + return $result; + } + + /** + * Escape a list with literals or lists + * @param array $list list with literals or lists as PHP array + * + * @return string escaped list for imap + */ + public function escapeList(array $list): string { + $result = []; + foreach ($list as $v) { + if (!is_array($v)) { + $result[] = $v; + continue; + } + $result[] = $this->escapeList($v); + } + return '(' . implode(' ', $result) . ')'; + } + + /** + * Login to a new session. + * @param string $user username + * @param string $password password + * + * @return bool|mixed + * @throws AuthFailedException + */ + public function login(string $user, string $password): bool { + try { + $response = $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true); + return $response !== null && $response !== false; + } catch (RuntimeException $e) { + throw new AuthFailedException("failed to authenticate", 0, $e); + } + } + + /** + * Authenticate your current IMAP session. + * @param string $user username + * @param string $token access token + * + * @return bool + * @throws AuthFailedException + */ + public function authenticate(string $user, string $token): bool { + try { + $authenticateParams = ['XOAUTH2', base64_encode("user=$user\1auth=Bearer $token\1\1")]; + $this->sendRequest('AUTHENTICATE', $authenticateParams); + + while (true) { + $response = ""; + $is_plus = $this->readLine($response, '+', true); + if ($is_plus) { + // try to log the challenge somewhere where it can be found + error_log("got an extra server challenge: $response"); + // respond with an empty response. + $this->sendRequest(''); + } else { + if (preg_match('/^NO /i', $response) || + preg_match('/^BAD /i', $response)) { + error_log("got failure response: $response"); + return false; + } else if (preg_match("/^OK /i", $response)) { + return true; + } + } + } + } catch (RuntimeException $e) { + throw new AuthFailedException("failed to authenticate", 0, $e); + } + } + + /** + * Logout of imap server + * + * @return bool success + */ + public function logout(): bool { + $result = false; + if ($this->stream) { + try { + $result = $this->requestAndResponse('LOGOUT', [], true); + } catch (Exception $e) {} + fclose($this->stream); + $this->stream = null; + $this->uid_cache = null; + } + + return $result !== false; + } + + /** + * Check if the current session is connected + * + * @return bool + */ + public function connected(): bool { + return (boolean) $this->stream; + } + + /** + * Get an array of available capabilities + * + * @return array list of capabilities + * @throws RuntimeException + */ + public function getCapabilities(): array { + $response = $this->requestAndResponse('CAPABILITY'); + + if (!$response) return []; + + $capabilities = []; + foreach ($response as $line) { + $capabilities = array_merge($capabilities, $line); + } + return $capabilities; + } + + /** + * Examine and select have the same response. + * @param string $command can be 'EXAMINE' or 'SELECT' + * @param string $folder target folder + * + * @return bool|array + * @throws RuntimeException + */ + public function examineOrSelect(string $command = 'EXAMINE', string $folder = 'INBOX') { + $this->sendRequest($command, [$this->escapeString($folder)], $tag); + + $result = []; + $tokens = null; // define $tokens variable before first use + while (!$this->readLine($tokens, $tag)) { + if ($tokens[0] == 'FLAGS') { + array_shift($tokens); + $result['flags'] = $tokens; + continue; + } + switch ($tokens[1]) { + case 'EXISTS': + case 'RECENT': + $result[strtolower($tokens[1])] = (int)$tokens[0]; + break; + case '[UIDVALIDITY': + $result['uidvalidity'] = (int)$tokens[2]; + break; + case '[UIDNEXT': + $result['uidnext'] = (int)$tokens[2]; + break; + case '[UNSEEN': + $result['unseen'] = (int)$tokens[2]; + break; + case '[NONEXISTENT]': + throw new RuntimeException("folder doesn't exist"); + default: + // ignore + break; + } + } + + if ($tokens[0] != 'OK') { + return false; + } + return $result; + } + + /** + * Change the current folder + * @param string $folder change to this folder + * + * @return bool|array see examineOrselect() + * @throws RuntimeException + */ + public function selectFolder(string $folder = 'INBOX') { + $this->uid_cache = null; + + return $this->examineOrSelect('SELECT', $folder); + } + + /** + * Examine a given folder + * @param string $folder examine this folder + * + * @return bool|array see examineOrselect() + * @throws RuntimeException + */ + public function examineFolder(string $folder = 'INBOX') { + return $this->examineOrSelect('EXAMINE', $folder); + } + + /** + * Fetch one or more items of one or more messages + * @param string|array $items items to fetch [RFC822.HEADER, FLAGS, RFC822.TEXT, etc] + * @param int|array $from message for items or start message if $to !== null + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message available + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * + * @return string|array if only one item of one message is fetched it's returned as string + * if items of one message are fetched it's returned as (name => value) + * if one item of messages are fetched it's returned as (msgno => value) + * if items of messages are fetched it's returned as (msgno => (name => value)) + * @throws RuntimeException + */ + public function fetch($items, $from, $to = null, $uid = IMAP::ST_UID) { + if (is_array($from)) { + $set = implode(',', $from); + } elseif ($to === null) { + $set = (int)$from; + } elseif ($to === INF) { + $set = (int)$from . ':*'; + } else { + $set = (int)$from . ':' . (int)$to; + } + + $items = (array)$items; + $itemList = $this->escapeList($items); + + $this->sendRequest($this->buildUIDCommand("FETCH", $uid), [$set, $itemList], $tag); + $result = []; + $tokens = null; // define $tokens variable before first use + while (!$this->readLine($tokens, $tag)) { + // ignore other responses + if ($tokens[1] != 'FETCH') { + continue; + } + + // find array key of UID value; try the last elements, or search for it + if ($uid) { + $count = count($tokens[2]); + if ($tokens[2][$count - 2] == 'UID') { + $uidKey = $count - 1; + } else if ($tokens[2][0] == 'UID') { + $uidKey = 1; + } else { + $found = array_search('UID', $tokens[2]); + if ($found === false || $found === -1) { + continue; + } + + $uidKey = $found + 1; + } + } + + // ignore other messages + if ($to === null && !is_array($from) && ($uid ? $tokens[2][$uidKey] != $from : $tokens[0] != $from)) { + continue; + } + + // if we only want one item we return that one directly + if (count($items) == 1) { + if ($tokens[2][0] == $items[0]) { + $data = $tokens[2][1]; + } elseif ($uid && $tokens[2][2] == $items[0]) { + $data = $tokens[2][3]; + } else { + $expectedResponse = 0; + // maybe the server send an other field we didn't wanted + $count = count($tokens[2]); + // we start with 2, because 0 was already checked + for ($i = 2; $i < $count; $i += 2) { + if ($tokens[2][$i] != $items[0]) { + continue; + } + $data = $tokens[2][$i + 1]; + $expectedResponse = 1; + break; + } + if (!$expectedResponse) { + continue; + } + } + } else { + $data = []; + while (key($tokens[2]) !== null) { + $data[current($tokens[2])] = next($tokens[2]); + next($tokens[2]); + } + } + + // if we want only one message we can ignore everything else and just return + if ($to === null && !is_array($from) && ($uid ? $tokens[2][$uidKey] == $from : $tokens[0] == $from)) { + // we still need to read all lines + while (!$this->readLine($tokens, $tag)) + + return $data; + } + if ($uid) { + $result[$tokens[2][$uidKey]] = $data; + }else{ + $result[$tokens[0]] = $data; + } + } + + if ($to === null && !is_array($from)) { + throw new RuntimeException('the single id was not found in response'); + } + + return $result; + } + + /** + * Fetch message headers + * @param array|int $uids + * @param string $rfc + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * + * @return array + * @throws RuntimeException + */ + public function content($uids, string $rfc = "RFC822", $uid = IMAP::ST_UID): array { + $result = $this->fetch(["$rfc.TEXT"], $uids, null, $uid); + return is_array($result) ? $result : []; + } + + /** + * Fetch message headers + * @param array|int $uids + * @param string $rfc + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * + * @return array + * @throws RuntimeException + */ + public function headers($uids, string $rfc = "RFC822", $uid = IMAP::ST_UID): array{ + $result = $this->fetch(["$rfc.HEADER"], $uids, null, $uid); + return $result === "" ? [] : $result; + } + + /** + * Fetch message flags + * @param array|int $uids + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * + * @return array + * @throws RuntimeException + */ + public function flags($uids, $uid = IMAP::ST_UID): array { + $result = $this->fetch(["FLAGS"], $uids, null, $uid); + return is_array($result) ? $result : []; + } + + /** + * Get uid for a given id + * @param int|null $id message number + * + * @return array|string message number for given message or all messages as array + * @throws MessageNotFoundException + */ + public function getUid($id = null) { + if (!$this->enable_uid_cache || $this->uid_cache === null || ($this->uid_cache && count($this->uid_cache) <= 0)) { + try { + $this->setUidCache($this->fetch('UID', 1, INF)); // set cache for this folder + } catch (RuntimeException $e) {} + } + $uids = $this->uid_cache; + + if ($id == null) { + return $uids; + } + + foreach ($uids as $k => $v) { + if ($k == $id) { + return $v; + } + } + + // clear uid cache and run method again + if ($this->enable_uid_cache && $this->uid_cache) { + $this->setUidCache(null); + return $this->getUid($id); + } + + throw new MessageNotFoundException('unique id not found'); + } + + /** + * Get a message number for a uid + * @param string $id uid + * + * @return int message number + * @throws MessageNotFoundException + */ + public function getMessageNumber(string $id): int { + $ids = $this->getUid(); + foreach ($ids as $k => $v) { + if ($v == $id) { + return (int)$k; + } + } + + throw new MessageNotFoundException('message number not found'); + } + + /** + * Get a list of available folders + * @param string $reference mailbox reference for list + * @param string $folder mailbox name match with wildcards + * + * @return array folders that matched $folder as array(name => array('delimiter' => .., 'flags' => ..)) + * @throws RuntimeException + */ + public function folders(string $reference = '', string $folder = '*'): array { + $result = []; + $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $folder)); + if (!$list || $list === true) { + return $result; + } + + foreach ($list as $item) { + if (count($item) != 4 || $item[0] != 'LIST') { + continue; + } + $result[$item[3]] = ['delimiter' => $item[2], 'flags' => $item[1]]; + } + + return $result; + } + + /** + * Manage flags + * @param array $flags flags to set, add or remove - see $mode + * @param int $from message for items or start message if $to !== null + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message available + * @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given + * @param bool $silent if false the return values are the new flags for the wanted messages + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * @param null|string $item command used to store a flag + * + * @return bool|array new flags if $silent is false, else true or false depending on success + * @throws RuntimeException + */ + public function store(array $flags, int $from, $to = null, $mode = null, bool $silent = true, $uid = IMAP::ST_UID, $item = null) { + $flags = $this->escapeList($flags); + $set = $this->buildSet($from, $to); + + $command = $this->buildUIDCommand("STORE", $uid); + $item = ($mode == '-' ? "-" : "+").($item === null ? "FLAGS" : $item).($silent ? '.SILENT' : ""); + + $response = $this->requestAndResponse($command, [$set, $item, $flags], $silent); + + if ($silent) { + return (bool)$response; + } + + $result = []; + foreach ($response as $token) { + if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') { + continue; + } + $result[$token[0]] = $token[2][1]; + } + + return $result; + } + + /** + * Append a new message to given folder + * @param string $folder name of target folder + * @param string $message full message content + * @param array|null $flags flags for new message + * @param string $date date for new message + * + * @return bool success + * @throws RuntimeException + */ + public function appendMessage(string $folder, string $message, $flags = null, $date = null): bool { + $tokens = []; + $tokens[] = $this->escapeString($folder); + if ($flags !== null) { + $tokens[] = $this->escapeList($flags); + } + if ($date !== null) { + $tokens[] = $this->escapeString($date); + } + $tokens[] = $this->escapeString($message); + + return (bool) $this->requestAndResponse('APPEND', $tokens, true); + } + + /** + * Copy a message set from current folder to an other folder + * @param string $folder destination folder + * @param $from + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message available + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * + * @return bool success + * @throws RuntimeException + */ + public function copyMessage(string $folder, $from, $to = null, $uid = IMAP::ST_UID): bool { + $set = $this->buildSet($from, $to); + $command = $this->buildUIDCommand("COPY", $uid); + return (bool)$this->requestAndResponse($command, [$set, $this->escapeString($folder)], true); + } + + /** + * Copy multiple messages to the target folder + * + * @param array $messages List of message identifiers + * @param string $folder Destination folder + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * @return array|bool Tokens if operation successful, false if an error occurred + * + * @throws RuntimeException + */ + public function copyManyMessages(array $messages, string $folder, $uid = IMAP::ST_UID) { + $command = $this->buildUIDCommand("COPY", $uid); + + $set = implode(',', $messages); + $tokens = [$set, $this->escapeString($folder)]; + + return $this->requestAndResponse($command, $tokens, true); + } + + /** + * Move a message set from current folder to an other folder + * @param string $folder destination folder + * @param $from + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message available + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * + * @return bool success + * @throws RuntimeException + */ + public function moveMessage(string $folder, $from, $to = null, $uid = IMAP::ST_UID): bool { + $set = $this->buildSet($from, $to); + $command = $this->buildUIDCommand("MOVE", $uid); + + return (bool)$this->requestAndResponse($command, [$set, $this->escapeString($folder)], true); + } + + /** + * Move multiple messages to the target folder + * @param array $messages List of message identifiers + * @param string $folder Destination folder + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * + * @return array|bool Tokens if operation successful, false if an error occurred + * @throws RuntimeException + */ + public function moveManyMessages(array $messages, string $folder, $uid = IMAP::ST_UID) { + $command = $this->buildUIDCommand("MOVE", $uid); + + $set = implode(',', $messages); + $tokens = [$set, $this->escapeString($folder)]; + + return $this->requestAndResponse($command, $tokens, true); + } + + /** + * Exchange identification information + * Ref.: https://datatracker.ietf.org/doc/html/rfc2971 + * + * @param null $ids + * @return array|bool|void|null + * + * @throws RuntimeException + */ + public function ID($ids = null) { + $token = "NIL"; + if (is_array($ids) && !empty($ids)) { + $token = "("; + foreach ($ids as $id) { + $token .= '"'.$id.'" '; + } + $token = rtrim($token).")"; + } + + return $this->requestAndResponse("ID", [$token], true); + } + + /** + * Create a new folder (and parent folders if needed) + * @param string $folder folder name + * + * @return bool success + * @throws RuntimeException + */ + public function createFolder(string $folder): bool { + return (bool)$this->requestAndResponse('CREATE', [$this->escapeString($folder)], true); + } + + /** + * Rename an existing folder + * @param string $old old name + * @param string $new new name + * + * @return bool success + * @throws RuntimeException + */ + public function renameFolder(string $old, string $new): bool { + return (bool)$this->requestAndResponse('RENAME', $this->escapeString($old, $new), true); + } + + /** + * Delete a folder + * @param string $folder folder name + * + * @return bool success + * @throws RuntimeException + */ + public function deleteFolder(string $folder): bool { + return (bool)$this->requestAndResponse('DELETE', [$this->escapeString($folder)], true); + } + + /** + * Subscribe to a folder + * @param string $folder folder name + * + * @return bool success + * @throws RuntimeException + */ + public function subscribeFolder(string $folder): bool { + return (bool)$this->requestAndResponse('SUBSCRIBE', [$this->escapeString($folder)], true); + } + + /** + * Unsubscribe from a folder + * @param string $folder folder name + * + * @return bool success + * @throws RuntimeException + */ + public function unsubscribeFolder(string $folder): bool { + return (bool)$this->requestAndResponse('UNSUBSCRIBE', [$this->escapeString($folder)], true); + } + + /** + * Apply session saved changes to the server + * + * @return bool success + * @throws RuntimeException + */ + public function expunge(): bool { + return (bool)$this->requestAndResponse('EXPUNGE'); + } + + /** + * Send noop command + * + * @return bool success + * @throws RuntimeException + */ + public function noop(): bool { + return (bool)$this->requestAndResponse('NOOP'); + } + + /** + * Retrieve the quota level settings, and usage statics per mailbox + * @param $username + * + * @return array + * @throws RuntimeException + */ + public function getQuota($username): array { + $result = $this->requestAndResponse("GETQUOTA", ['"#user/'.$username.'"']); + return is_array($result) ? $result : []; + } + + /** + * Retrieve the quota settings per user + * @param string $quota_root + * + * @return array + * @throws RuntimeException + */ + public function getQuotaRoot(string $quota_root = 'INBOX'): array { + $result = $this->requestAndResponse("QUOTA", [$quota_root]); + return is_array($result) ? $result : []; + } + + /** + * Send idle command + * + * @throws RuntimeException + */ + public function idle() { + $this->sendRequest("IDLE"); + if (!$this->assumedNextLine('+ ')) { + throw new RuntimeException('idle failed'); + } + } + + /** + * Send done command + * @throws RuntimeException + */ + public function done(): bool { + $this->write("DONE"); + if (!$this->assumedNextTaggedLine('OK', $tags)) { + throw new RuntimeException('done failed'); + } + return true; + } + + /** + * Search for matching messages + * @param array $params + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * + * @return array message ids + * @throws RuntimeException + */ + public function search(array $params, $uid = IMAP::ST_UID): array { + $command = $this->buildUIDCommand("SEARCH", $uid); + $response = $this->requestAndResponse($command, $params); + if (!$response) return []; + + foreach ($response as $ids) { + if ($ids[0] == 'SEARCH') { + array_shift($ids); + return $ids; + } + } + return []; + } + + /** + * Get a message overview + * @param string $sequence + * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use + * message numbers instead. + * + * @return array + * @throws RuntimeException + * @throws MessageNotFoundException + * @throws InvalidMessageDateException + */ + public function overview(string $sequence, $uid = IMAP::ST_UID): array { + $result = []; + list($from, $to) = explode(":", $sequence); + + $uids = $this->getUid(); + $ids = []; + foreach ($uids as $msgn => $v) { + $id = $uid ? $v : $msgn; + if ( ($to >= $id && $from <= $id) || ($to === "*" && $from <= $id) ){ + $ids[] = $id; + } + } + $headers = $this->headers($ids, "RFC822", $uid); + foreach ($headers as $id => $raw_header) { + $result[$id] = (new Header($raw_header, false))->getAttributes(); + } + return $result; + } + + /** + * Enable the debug mode + */ + public function enableDebug(){ + $this->debug = true; + } + + /** + * Disable the debug mode + */ + public function disableDebug(){ + $this->debug = false; + } + + /** + * Build a valid UID number set + * @param $from + * @param null $to + * + * @return int|string + */ + public function buildSet($from, $to = null) { + $set = (int)$from; + if ($to !== null) { + $set .= ':' . ($to == INF ? '*' : (int)$to); + } + return $set; + } +} diff --git a/freescout-dist/overrides/webklex/php-imap/src/Header.php b/freescout-dist/overrides/webklex/php-imap/src/Header.php new file mode 100644 index 0000000..cb0f76f --- /dev/null +++ b/freescout-dist/overrides/webklex/php-imap/src/Header.php @@ -0,0 +1,815 @@ +raw = $raw_header; + $this->config = ClientManager::get('options'); + $this->attributize = $attributize; + $this->parse(); + } + + /** + * Call dynamic attribute setter and getter methods + * @param string $method + * @param array $arguments + * + * @return Attribute|mixed + * @throws MethodNotFoundException + */ + public function __call(string $method, array $arguments) { + if (strtolower(substr($method, 0, 3)) === 'get') { + $name = preg_replace('/(.)(?=[A-Z])/u', '$1_', substr(strtolower($method), 3)); + + if (in_array($name, array_keys($this->attributes))) { + return $this->attributes[$name]; + } + + } + + throw new MethodNotFoundException("Method " . self::class . '::' . $method . '() is not supported'); + } + + /** + * Magic getter + * @param $name + * + * @return Attribute|null + */ + public function __get($name) { + return $this->get($name); + } + + /** + * Get a specific header attribute + * @param $name + * + * @return Attribute|mixed + */ + public function get($name) { + if (isset($this->attributes[$name])) { + return $this->attributes[$name]; + } + + return null; + } + + /** + * Set a specific attribute + * @param string $name + * @param array|mixed $value + * @param boolean $strict + * + * @return Attribute + */ + public function set(string $name, $value, bool $strict = false) { + if (isset($this->attributes[$name]) && $strict === false) { + if ($this->attributize) { + $this->attributes[$name]->add($value, true); + } else { + if (isset($this->attributes[$name])) { + if (!is_array($this->attributes[$name])) { + $this->attributes[$name] = [$this->attributes[$name], $value]; + } else { + $this->attributes[$name][] = $value; + } + } else { + $this->attributes[$name] = $value; + } + } + } elseif (!$this->attributize) { + $this->attributes[$name] = $value; + } else { + $this->attributes[$name] = new Attribute($name, $value); + } + + return $this->attributes[$name]; + } + + /** + * Perform a regex match all on the raw header and return the first result + * @param $pattern + * + * @return mixed|null + */ + public function find($pattern) { + if (preg_match_all($pattern, $this->raw, $matches)) { + if (isset($matches[1])) { + if (count($matches[1]) > 0) { + return $matches[1][0]; + } + } + } + return null; + } + + /** + * Try to find a boundary if possible + * + * @return string|null + */ + public function getBoundary() { + $regex = $this->config["boundary"] ?? "/boundary=(.*?(?=;)|(.*))/i"; + $boundary = $this->find($regex); + + if ($boundary === null) { + return null; + } + + return $this->clearBoundaryString($boundary); + } + + /** + * Remove all unwanted chars from a given boundary + * @param string $str + * + * @return string + */ + private function clearBoundaryString(string $str): string { + return str_replace(['"', '\r', '\n', "\n", "\r", ";", "\s"], "", $str); + } + + /** + * Parse the raw headers + * + * @throws InvalidMessageDateException + */ + protected function parse() { + $header = $this->rfc822_parse_headers($this->raw); + + $this->extractAddresses($header); + + if (property_exists($header, 'subject')) { + //$this->set("subject", $this->decode($header->subject)); + $subject = \MailHelper::decodeSubject($header->subject); + $this->set("subject", $subject); + } + if (property_exists($header, 'references')) { + $this->set("references", $this->decode($header->references)); + } + if (property_exists($header, 'message_id')) { + $this->set("message_id", str_replace(['<', '>'], '', $header->message_id)); + } + + $this->parseDate($header); + foreach ($header as $key => $value) { + $key = trim(rtrim(strtolower($key))); + if (!isset($this->attributes[$key])) { + $this->set($key, $value); + } + } + + $this->extractHeaderExtensions(); + $this->findPriority(); + } + + /** + * Parse mail headers from a string + * @link https://php.net/manual/en/function.imap-rfc822-parse-headers.php + * @param $raw_headers + * + * @return object + */ + public function rfc822_parse_headers($raw_headers) { + $headers = []; + $imap_headers = []; + if (extension_loaded('imap') && $this->config["rfc822"]) { + $raw_imap_headers = (array)\imap_rfc822_parse_headers($raw_headers); + foreach ($raw_imap_headers as $key => $values) { + $key = str_replace("-", "_", $key); + $imap_headers[$key] = $values; + } + } + $lines = explode("\r\n", preg_replace("/\r\n\s/", ' ', $raw_headers)); + $prev_header = null; + foreach ($lines as $line) { + if (substr($line, 0, 1) === "\n") { + $line = substr($line, 1); + } + + if (substr($line, 0, 1) === "\t") { + $line = substr($line, 1); + $line = trim(rtrim($line)); + if ($prev_header !== null) { + $headers[$prev_header][] = $line; + } + } elseif (substr($line, 0, 1) === " ") { + $line = substr($line, 1); + $line = trim(rtrim($line)); + if ($prev_header !== null) { + if (!isset($headers[$prev_header])) { + $headers[$prev_header] = ""; + } + if (is_array($headers[$prev_header])) { + $headers[$prev_header][] = $line; + } else { + $headers[$prev_header] .= $line; + } + } + } else { + if (($pos = strpos($line, ":")) > 0) { + $key = trim(rtrim(strtolower(substr($line, 0, $pos)))); + $key = str_replace("-", "_", $key); + + $value = trim(rtrim(substr($line, $pos + 1))); + if (isset($headers[$key])) { + $headers[$key][] = $value; + } else { + $headers[$key] = [$value]; + } + $prev_header = $key; + } + } + } + + foreach ($headers as $key => $values) { + if (isset($imap_headers[$key])) continue; + $value = null; + switch ($key) { + case 'from': + case 'to': + case 'cc': + case 'bcc': + case 'reply_to': + case 'sender': + $value = $this->decodeAddresses($values); + $headers[$key . "address"] = implode(", ", $values); + break; + case 'subject': + $value = implode(" ", $values); + break; + default: + if (is_array($values)) { + foreach ($values as $k => $v) { + if ($v == "") { + unset($values[$k]); + } + } + $available_values = count($values); + if ($available_values === 1) { + $value = array_pop($values); + } elseif ($available_values === 2) { + $value = implode(" ", $values); + } elseif ($available_values > 2) { + $value = array_values($values); + } else { + $value = ""; + } + } + break; + } + $headers[$key] = $value; + } + + return (object)array_merge($headers, $imap_headers); + } + + /** + * Decode MIME header elements + * @link https://php.net/manual/en/function.imap-mime-header-decode.php + * @param string $text The MIME text + * + * @return array The decoded elements are returned in an array of objects, where each + * object has two properties, charset and text. + */ + public function mime_header_decode(string $text): array { + + // imap_mime_header_decode() can't decode some headers: =?iso-2022-jp?B?...?= + if (\Str::startsWith($text, '=?iso-2022-jp?')) { + return [(object)[ + "charset" => 'iso-2022-jp', + "text" => \MailHelper::decodeSubject($text) + ]]; + } + + if (extension_loaded('imap')) { + $result = \imap_mime_header_decode($text); + return is_array($result) ? $result : []; + } + + $charset = $this->getEncoding($text); + return [(object)[ + "charset" => $charset, + "text" => $this->convertEncoding($text, $charset) + ]]; + } + + /** + * Check if a given pair of strings has been decoded + * @param $encoded + * @param $decoded + * + * @return bool + */ + private function notDecoded($encoded, $decoded): bool { + return 0 === strpos($decoded, '=?') + && strlen($decoded) - 2 === strpos($decoded, '?=') + && false !== strpos($encoded, $decoded); + } + + /** + * Convert the encoding + * @param $str + * @param string $from + * @param string $to + * + * @return mixed|string + */ + public function convertEncoding($str, $from = "ISO-8859-2", $to = "UTF-8") { + + $from = EncodingAliases::get($from, $this->fallback_encoding); + $to = EncodingAliases::get($to, $this->fallback_encoding); + + if ($from === $to) { + return $str; + } + + // We don't need to do convertEncoding() if charset is ASCII (us-ascii): + // ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded + // https://stackoverflow.com/a/11303410 + // + // us-ascii is the same as ASCII: + // ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA) + // prefers the updated name US-ASCII, which clarifies that this system was developed in the US and + // based on the typographical symbols predominantly in use there. + // https://en.wikipedia.org/wiki/ASCII + // + // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken. + if (strtolower($from) == 'us-ascii' && $to == 'UTF-8') { + return $str; + } + + if (strtolower($from) == 'iso-2022-jp'){ + $from = 'iso-2022-jp-ms'; + } + + try { + if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') { + return iconv($from, $to, $str); + } else { + if (!$from) { + return mb_convert_encoding($str, $to); + } + return mb_convert_encoding($str, $to, $from); + } + } catch (\Exception $e) { + if (strstr($from, '-')) { + $from = str_replace('-', '', $from); + return $this->convertEncoding($str, $from, $to); + } else { + return $str; + } + } + } + + /** + * Get the encoding of a given abject + * @param object|string $structure + * + * @return string + */ + public function getEncoding($structure): string { + if (property_exists($structure, 'parameters')) { + foreach ($structure->parameters as $parameter) { + if (strtolower($parameter->attribute) == "charset") { + return EncodingAliases::get($parameter->value, $this->fallback_encoding); + } + } + } elseif (property_exists($structure, 'charset')) { + return EncodingAliases::get($structure->charset, $this->fallback_encoding); + } elseif (is_string($structure) === true) { + $result = mb_detect_encoding($structure); + return $result === false ? $this->fallback_encoding : $result; + } + + return $this->fallback_encoding; + } + + /** + * Test if a given value is utf-8 encoded + * @param $value + * + * @return bool + */ + private function is_uft8($value): bool { + return strpos(strtolower($value), '=?utf-8?') === 0; + } + + /** + * Try to decode a specific header + * @param mixed $value + * + * @return mixed + */ + private function decode($value) { + if (is_array($value)) { + return $this->decodeArray($value); + } + $original_value = $value; + $decoder = $this->config['decoder']['message']; + + if ($value !== null) { + $is_utf8_base = $this->is_uft8($value); + + if ($decoder === 'utf-8' && extension_loaded('imap')) { + $value = \imap_utf8($value); + $is_utf8_base = $this->is_uft8($value); + if ($is_utf8_base) { + $value = mb_decode_mimeheader($value); + } + if ($this->notDecoded($original_value, $value)) { + $decoded_value = $this->mime_header_decode($value); + if (count($decoded_value) > 0) { + if (property_exists($decoded_value[0], "text")) { + $value = $decoded_value[0]->text; + } + } + } + } elseif ($decoder === 'iconv' && $is_utf8_base) { + $value = iconv_mime_decode($value); + } elseif ($is_utf8_base) { + $value = mb_decode_mimeheader($value); + } + + if ($this->is_uft8($value)) { + $value = mb_decode_mimeheader($value); + } + + if ($this->notDecoded($original_value, $value)) { + $value = $this->convertEncoding($original_value, $this->getEncoding($original_value)); + } + } + + return $value; + } + + /** + * Decode a given array + * @param array $values + * + * @return array + */ + private function decodeArray(array $values): array { + foreach ($values as $key => $value) { + $values[$key] = $this->decode($value); + } + return $values; + } + + /** + * Try to extract the priority from a given raw header string + */ + private function findPriority() { + if (($priority = $this->get("x_priority")) === null) return; + switch ((int)"$priority") { + case IMAP::MESSAGE_PRIORITY_HIGHEST; + $priority = IMAP::MESSAGE_PRIORITY_HIGHEST; + break; + case IMAP::MESSAGE_PRIORITY_HIGH; + $priority = IMAP::MESSAGE_PRIORITY_HIGH; + break; + case IMAP::MESSAGE_PRIORITY_NORMAL; + $priority = IMAP::MESSAGE_PRIORITY_NORMAL; + break; + case IMAP::MESSAGE_PRIORITY_LOW; + $priority = IMAP::MESSAGE_PRIORITY_LOW; + break; + case IMAP::MESSAGE_PRIORITY_LOWEST; + $priority = IMAP::MESSAGE_PRIORITY_LOWEST; + break; + default: + $priority = IMAP::MESSAGE_PRIORITY_UNKNOWN; + break; + } + + $this->set("priority", $priority); + } + + /** + * Extract a given part as address array from a given header + * @param $values + * + * @return array + */ + private function decodeAddresses($values): array { + $addresses = []; + + if (extension_loaded('mailparse') && $this->config["rfc822"]) { + foreach ($values as $address) { + foreach (\mailparse_rfc822_parse_addresses($address) as $parsed_address) { + if (isset($parsed_address['address'])) { + $mail_address = explode('@', $parsed_address['address']); + if (count($mail_address) == 2) { + $addresses[] = (object)[ + "personal" => $parsed_address['display'] ?? '', + "mailbox" => $mail_address[0], + "host" => $mail_address[1], + ]; + } + } + } + } + + return $addresses; + } + + foreach ($values as $address) { + foreach (preg_split('/, (?=(?:[^"]*"[^"]*")*[^"]*$)/', $address) as $split_address) { + $split_address = trim(rtrim($split_address)); + + if (strpos($split_address, ",") == strlen($split_address) - 1) { + $split_address = substr($split_address, 0, -1); + } + if (preg_match( + '/^(?:(?P.+)\s)?(?(name)<|[^\s]+?)(?(name)>|>?)$/', + $split_address, + $matches + )) { + $name = trim(rtrim($matches["name"])); + $email = trim(rtrim($matches["email"])); + list($mailbox, $host) = array_pad(explode("@", $email), 2, null); + $addresses[] = (object)[ + "personal" => $name, + "mailbox" => $mailbox, + "host" => $host, + ]; + } + } + } + + return $addresses; + } + + /** + * Extract a given part as address array from a given header + * @param object $header + */ + private function extractAddresses($header) { + foreach (['from', 'to', 'cc', 'bcc', 'reply_to', 'sender'] as $key) { + if (property_exists($header, $key)) { + $this->set($key, $this->parseAddresses($header->$key)); + } + } + } + + /** + * Parse Addresses + * @param $list + * + * @return array + */ + private function parseAddresses($list): array { + $addresses = []; + + if (is_array($list) === false) { + return $addresses; + } + + foreach ($list as $item) { + $address = (object)$item; + + if (!property_exists($address, 'mailbox')) { + $address->mailbox = false; + } + if (!property_exists($address, 'host')) { + $address->host = false; + } + if (!property_exists($address, 'personal')) { + $address->personal = false; + } else { + $personalParts = $this->mime_header_decode($address->personal); + + if (is_array($personalParts)) { + $address->personal = ''; + foreach ($personalParts as $p) { + $address->personal .= $this->convertEncoding($p->text, $this->getEncoding($p)); + } + } + + if (strpos($address->personal, "'") === 0) { + $address->personal = str_replace("'", "", $address->personal); + } + } + + $address->mail = ($address->mailbox && $address->host) ? $address->mailbox . '@' . $address->host : false; + $address->full = ($address->personal) ? $address->personal . ' <' . $address->mail . '>' : $address->mail; + + $addresses[] = new Address($address); + } + + return $addresses; + } + + /** + * Search and extract potential header extensions + */ + private function extractHeaderExtensions() { + foreach ($this->attributes as $key => $value) { + if (is_array($value)) { + $value = implode(", ", $value); + } else { + $value = (string)$value; + } + // Only parse strings and don't parse any attributes like the user-agent + // https://github.com/Webklex/php-imap/issues/401 + if (($key == "user_agent") === false && ($key == "subject") === false) { + if (($pos = strpos($value, ";")) !== false) { + $original = substr($value, 0, $pos); + $this->set($key, trim(rtrim($original)), true); + + // Get all potential extensions + $extensions = explode(";", substr($value, $pos + 1)); + + $previousKey = null; + $previousValue = ''; + + foreach ($extensions as $extension) { + if (($pos = strpos($extension, "=")) !== false) { + $key = substr($extension, 0, $pos); + $key = trim(rtrim(strtolower($key))); + + $matches = []; + + if (preg_match('/^(?P\w+)\*/', $key, $matches) !== 0) { + $key = $matches['key_name']; + $previousKey = $key; + + $value = substr($extension, $pos + 1); + $value = str_replace('"', "", $value); + $previousValue .= trim(rtrim($value)); + + continue; + } + + if ( + $previousKey !== null + && $previousKey !== $key + && isset($this->attributes[$previousKey]) === false + ) { + $this->set($previousKey, $previousValue); + + $previousValue = ''; + } + + if (isset($this->attributes[$key]) === false) { + $value = substr($extension, $pos + 1); + $value = str_replace('"', "", $value); + $value = trim(rtrim($value)); + + $this->set($key, $value); + } + + $previousKey = $key; + } + } + + if ($previousValue !== '') { + $this->set($previousKey, $previousValue); + } + } + } + } + } + + /** + * Exception handling for invalid dates + * + * Currently known invalid formats: + * ^ Datetime ^ Problem ^ Cause + * | Mon, 20 Nov 2017 20:31:31 +0800 (GMT+8:00) | Double timezone specification | A Windows feature + * | Thu, 8 Nov 2018 08:54:58 -0200 (-02) | + * | | and invalid timezone (max 6 char) | + * | 04 Jan 2018 10:12:47 UT | Missing letter "C" | Unknown + * | Thu, 31 May 2018 18:15:00 +0800 (added by) | Non-standard details added by the | Unknown + * | | mail server | + * | Sat, 31 Aug 2013 20:08:23 +0580 | Invalid timezone | PHPMailer bug https://sourceforge.net/p/phpmailer/mailman/message/6132703/ + * + * Please report any new invalid timestamps to [#45](https://github.com/Webklex/php-imap/issues) + * + * @param object $header + * + * @throws InvalidMessageDateException + */ + private function parseDate($header) { + + if (property_exists($header, 'date')) { + $date = $header->date; + + if (preg_match('/\+0580/', $date)) { + $date = str_replace('+0580', '+0530', $date); + } + + $date = trim(rtrim($date)); + try { + if(strpos($date, ' ') !== false){ + $date = str_replace(' ', ' ', $date); + } + $parsed_date = Carbon::parse($date); + } catch (\Exception $e) { + switch (true) { + case preg_match('/([0-9]{4}\.[0-9]{1,2}\.[0-9]{1,2}\-[0-9]{1,2}\.[0-9]{1,2}.[0-9]{1,2})+$/i', $date) > 0: + $date = Carbon::createFromFormat("Y.m.d-H.i.s", $date); + break; + case preg_match('/([0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ UT)+$/i', $date) > 0: + case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ UT)+$/i', $date) > 0: + $date .= 'C'; + break; + case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ \+[0-9]{2,4}\ \(\+[0-9]{1,2}\))+$/i', $date) > 0: + case preg_match('/([A-Z]{2,3}[\,|\ \,]\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}.*)+$/i', $date) > 0: + case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ [\-|\+][0-9]{4}\ \(.*)\)+$/i', $date) > 0: + case preg_match('/([A-Z]{2,3}\, \ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ [\-|\+][0-9]{4}\ \(.*)\)+$/i', $date) > 0: + case preg_match('/([0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{2,4}\ [0-9]{2}\:[0-9]{2}\:[0-9]{2}\ [A-Z]{2}\ \-[0-9]{2}\:[0-9]{2}\ \([A-Z]{2,3}\ \-[0-9]{2}:[0-9]{2}\))+$/i', $date) > 0: + $array = explode('(', $date); + $array = array_reverse($array); + $date = trim(array_pop($array)); + break; + } + try { + $parsed_date = Carbon::parse($date); + } catch (\Exception $_e) { + if (!isset($this->config["fallback_date"])) { + throw new InvalidMessageDateException("Invalid message date. ID:" . $this->get("message_id") . " Date:" . $header->date . "/" . $date, 1100, $e); + } else { + $parsed_date = Carbon::parse($this->config["fallback_date"]); + } + } + } + + $this->set("date", $parsed_date); + } + } + + /** + * Get all available attributes + * + * @return array + */ + public function getAttributes(): array { + return $this->attributes; + } + +} diff --git a/freescout-dist/overrides/webklex/php-imap/src/Message.php b/freescout-dist/overrides/webklex/php-imap/src/Message.php new file mode 100644 index 0000000..b75ce3f --- /dev/null +++ b/freescout-dist/overrides/webklex/php-imap/src/Message.php @@ -0,0 +1,1450 @@ +boot(); + + $default_mask = $client->getDefaultMessageMask(); + if($default_mask != null) { + $this->mask = $default_mask; + } + $this->events["message"] = $client->getDefaultEvents("message"); + $this->events["flag"] = $client->getDefaultEvents("flag"); + + $this->folder_path = $client->getFolderPath(); + + $this->setSequence($sequence); + $this->setFetchOption($fetch_options); + $this->setFetchBodyOption($fetch_body); + $this->setFetchFlagsOption($fetch_flags); + + $this->client = $client; + $this->client->openFolder($this->folder_path); + + $this->setSequenceId($uid, $msglist); + + if ($this->fetch_options == IMAP::FT_PEEK) { + $this->parseFlags(); + } + + $this->parseHeader(); + + if ($this->getFetchBodyOption() === true) { + $this->parseBody(); + } + + if ($this->getFetchFlagsOption() === true && $this->fetch_options !== IMAP::FT_PEEK) { + $this->parseFlags(); + } + } + + /** + * Create a new instance without fetching the message header and providing them raw instead + * @param int $uid + * @param int|null $msglist + * @param Client $client + * @param string $raw_header + * @param string $raw_body + * @param array $raw_flags + * @param null $fetch_options + * @param null $sequence + * + * @return Message + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\EventNotFoundException + * @throws InvalidMessageDateException + * @throws MessageContentFetchingException + * @throws ReflectionException + * @throws MessageFlagException + * @throws Exceptions\RuntimeException + * @throws Exceptions\MessageNotFoundException + */ + public static function make(int $uid, $msglist, Client $client, string $raw_header, string $raw_body, array $raw_flags, $fetch_options = null, $sequence = null): Message { + $reflection = new ReflectionClass(self::class); + /** @var self $instance */ + $instance = $reflection->newInstanceWithoutConstructor(); + $instance->boot(); + + $default_mask = $client->getDefaultMessageMask(); + if($default_mask != null) { + $instance->setMask($default_mask); + } + $instance->setEvents([ + "message" => $client->getDefaultEvents("message"), + "flag" => $client->getDefaultEvents("flag"), + ]); + $instance->setFolderPath($client->getFolderPath()); + $instance->setSequence($sequence); + $instance->setFetchOption($fetch_options); + + $instance->setClient($client); + $instance->setSequenceId($uid, $msglist); + + $instance->parseRawHeader($raw_header); + $instance->parseRawFlags($raw_flags); + $instance->parseRawBody($raw_body); + $instance->peek(); + + return $instance; + } + + /** + * Boot a new instance + */ + public function boot(){ + $this->attributes = []; + + $this->config = ClientManager::get('options'); + $this->available_flags = ClientManager::get('flags'); + + $this->attachments = AttachmentCollection::make([]); + $this->flags = FlagCollection::make([]); + } + + /** + * Call dynamic attribute setter and getter methods + * @param string $method + * @param array $arguments + * + * @return mixed + * @throws MethodNotFoundException + */ + public function __call(string $method, array $arguments) { + if(strtolower(substr($method, 0, 3)) === 'get') { + $name = Str::snake(substr($method, 3)); + return $this->get($name); + }elseif (strtolower(substr($method, 0, 3)) === 'set') { + $name = Str::snake(substr($method, 3)); + + if(in_array($name, array_keys($this->attributes))) { + return $this->__set($name, array_pop($arguments)); + } + + } + + throw new MethodNotFoundException("Method ".self::class.'::'.$method.'() is not supported'); + } + + /** + * Magic setter + * @param $name + * @param $value + * + * @return mixed + */ + public function __set($name, $value) { + $this->attributes[$name] = $value; + + return $this->attributes[$name]; + } + + /** + * Magic getter + * @param $name + * + * @return Attribute|mixed|null + */ + public function __get($name) { + return $this->get($name); + } + + /** + * Get an available message or message header attribute + * @param $name + * + * @return Attribute|mixed|null + */ + public function get($name) { + if(isset($this->attributes[$name])) { + return $this->attributes[$name]; + } + + return $this->header->get($name); + } + + /** + * Check if the Message has a text body + * + * @return bool + */ + public function hasTextBody(): bool { + return isset($this->bodies['text']); + } + + /** + * Get the Message text body + * + * @return mixed + */ + public function getTextBody() { + if (!isset($this->bodies['text'])) { + return null; + } + + return $this->bodies['text']; + } + + /** + * Check if the Message has a html body + * + * @return bool + */ + public function hasHTMLBody(): bool { + return isset($this->bodies['html']); + } + + /** + * Get the Message html body + * + * @return string|null + */ + public function getHTMLBody() { + if (!isset($this->bodies['html'])) { + return null; + } + + return $this->bodies['html']; + } + + /** + * Parse all defined headers + * + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\RuntimeException + * @throws InvalidMessageDateException + * @throws MessageHeaderFetchingException + */ + private function parseHeader() { + $sequence_id = $this->getSequenceId(); + $headers = $this->client->getConnection()->headers([$sequence_id], "RFC822", $this->sequence === IMAP::ST_UID); + if (!isset($headers[$sequence_id])) { + throw new MessageHeaderFetchingException("no headers found", 0); + } + + $this->parseRawHeader($headers[$sequence_id]); + } + + /** + * @param string $raw_header + * + * @throws InvalidMessageDateException + */ + public function parseRawHeader(string $raw_header){ + $this->header = new Header($raw_header); + } + + /** + * Parse additional raw flags + * @param array $raw_flags + */ + public function parseRawFlags(array $raw_flags) { + $this->flags = FlagCollection::make([]); + + foreach($raw_flags as $flag) { + if (strpos($flag, "\\") === 0){ + $flag = substr($flag, 1); + } + $flag_key = strtolower($flag); + if ($this->available_flags === null || in_array($flag_key, $this->available_flags)) { + $this->flags->put($flag_key, $flag); + } + } + } + + /** + * Parse additional flags + * + * @return void + * @throws Exceptions\ConnectionFailedException + * @throws MessageFlagException + * @throws Exceptions\RuntimeException + */ + private function parseFlags() { + $this->client->openFolder($this->folder_path); + $this->flags = FlagCollection::make([]); + + $sequence_id = $this->getSequenceId(); + try { + $flags = $this->client->getConnection()->flags([$sequence_id], $this->sequence === IMAP::ST_UID); + } catch (Exceptions\RuntimeException $e) { + throw new MessageFlagException("flag could not be fetched", 0, $e); + } + + if (isset($flags[$sequence_id])) { + $this->parseRawFlags($flags[$sequence_id]); + } + } + + /** + * Parse the Message body + * + * @return $this + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\MessageContentFetchingException + * @throws InvalidMessageDateException + * @throws Exceptions\EventNotFoundException + * @throws MessageFlagException + * @throws Exceptions\RuntimeException + */ + public function parseBody(): Message { + $this->client->openFolder($this->folder_path); + + $sequence_id = $this->getSequenceId(); + try { + $contents = $this->client->getConnection()->content([$sequence_id], "RFC822", $this->sequence === IMAP::ST_UID); + } catch (Exceptions\RuntimeException $e) { + throw new MessageContentFetchingException("failed to fetch content", 0); + } + if (!isset($contents[$sequence_id])) { + throw new MessageContentFetchingException("no content found", 0); + } + $content = $contents[$sequence_id]; + + $body = $this->parseRawBody($content); + $this->peek(); + + return $body; + } + + /** + * Handle auto "Seen" flag handling + * + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\EventNotFoundException + * @throws MessageFlagException + * @throws Exceptions\RuntimeException + */ + public function peek(){ + if ($this->fetch_options == IMAP::FT_PEEK) { + if ($this->getFlags()->get("seen") == null) { + $this->unsetFlag("Seen"); + } + }elseif ($this->getFlags()->get("seen") != null) { + $this->setFlag("Seen"); + } + } + + /** + * Parse a given message body + * @param string $raw_body + * + * @return $this + * @throws Exceptions\ConnectionFailedException + * @throws InvalidMessageDateException + * @throws MessageContentFetchingException + * @throws Exceptions\RuntimeException + */ + public function parseRawBody(string $raw_body): Message { + $this->structure = new Structure($raw_body, $this->header); + $this->fetchStructure($this->structure); + + return $this; + } + + /** + * Fetch the Message structure + * @param Structure $structure + * + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\RuntimeException + */ + private function fetchStructure(Structure $structure) { + $this->client->openFolder($this->folder_path); + + foreach ($structure->parts as $part) { + $this->fetchPart($part); + } + } + + /** + * Fetch a given part + * @param Part $part + */ + private function fetchPart(Part $part) { + if ($part->isAttachment()) { + $this->fetchAttachment($part); + }else{ + $encoding = $this->getEncoding($part); + + $content = $this->decodeString($part->content, $part->encoding); + + // We don't need to do convertEncoding() if charset is ASCII (us-ascii): + // ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded + // https://stackoverflow.com/a/11303410 + // + // us-ascii is the same as ASCII: + // ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA) + // prefers the updated name US-ASCII, which clarifies that this system was developed in the US and + // based on the typographical symbols predominantly in use there. + // https://en.wikipedia.org/wiki/ASCII + // + // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken. + if ($encoding != 'us-ascii') { + $content = $this->convertEncoding($content, $encoding); + } + + $subtype = strtolower($part->subtype ?? ''); + $subtype = $subtype == "plain" || $subtype == "" ? "text" : $subtype; + + if (isset($this->bodies[$subtype])) { + $this->bodies[$subtype] .= "\n".$content; + }else{ + $this->bodies[$subtype] = $content; + } + } + } + + /** + * Fetch the Message attachment + * @param Part $part + */ + protected function fetchAttachment(Part $part) { + $oAttachment = new Attachment($this, $part); + + if ($oAttachment->getName() !== null && $oAttachment->getSize() > 0) { + if ($oAttachment->getId() !== null && $this->attachments->offsetExists($oAttachment->getId())) { + $this->attachments->put($oAttachment->getId(), $oAttachment); + } else { + $this->attachments->push($oAttachment); + } + } + } + + /** + * Fail proof setter for $fetch_option + * @param $option + * + * @return $this + */ + public function setFetchOption($option): Message { + if (is_long($option) === true) { + $this->fetch_options = $option; + } elseif (is_null($option) === true) { + $config = ClientManager::get('options.fetch', IMAP::FT_UID); + $this->fetch_options = is_long($config) ? $config : 1; + } + + return $this; + } + + /** + * Set the sequence type + * @param int|null $sequence + * + * @return $this + */ + public function setSequence($sequence): Message { + if (is_long($sequence)) { + $this->sequence = $sequence; + } elseif (is_null($sequence)) { + $config = ClientManager::get('options.sequence', IMAP::ST_MSGN); + $this->sequence = is_long($config) ? $config : IMAP::ST_MSGN; + } + + return $this; + } + + /** + * Fail proof setter for $fetch_body + * @param $option + * + * @return $this + */ + public function setFetchBodyOption($option): Message { + if (is_bool($option)) { + $this->fetch_body = $option; + } elseif (is_null($option)) { + $config = ClientManager::get('options.fetch_body', true); + $this->fetch_body = is_bool($config) ? $config : true; + } + + return $this; + } + + /** + * Fail proof setter for $fetch_flags + * @param $option + * + * @return $this + */ + public function setFetchFlagsOption($option): Message { + if (is_bool($option)) { + $this->fetch_flags = $option; + } elseif (is_null($option)) { + $config = ClientManager::get('options.fetch_flags', true); + $this->fetch_flags = is_bool($config) ? $config : true; + } + + return $this; + } + + /** + * Decode a given string + * @param $string + * @param $encoding + * + * @return string + */ + public function decodeString($string, $encoding): string { + switch ($encoding) { + case IMAP::MESSAGE_ENC_BINARY: + if (extension_loaded('imap')) { + return base64_decode(\imap_binary($string)); + } + return base64_decode($string); + case IMAP::MESSAGE_ENC_BASE64: + return base64_decode($string); + case IMAP::MESSAGE_ENC_QUOTED_PRINTABLE: + return quoted_printable_decode($string); + case IMAP::MESSAGE_ENC_8BIT: + case IMAP::MESSAGE_ENC_7BIT: + case IMAP::MESSAGE_ENC_OTHER: + default: + return $string; + } + } + + /** + * Convert the encoding + * @param $str + * @param string $from + * @param string $to + * + * @return mixed|string + */ + public function convertEncoding($str, string $from = "ISO-8859-2", string $to = "UTF-8") { + + $from = EncodingAliases::get($from); + $to = EncodingAliases::get($to); + + if ($from === $to) { + return $str; + } + + // We don't need to do convertEncoding() if charset is ASCII (us-ascii): + // ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded + // https://stackoverflow.com/a/11303410 + // + // us-ascii is the same as ASCII: + // ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA) + // prefers the updated name US-ASCII, which clarifies that this system was developed in the US and + // based on the typographical symbols predominantly in use there. + // https://en.wikipedia.org/wiki/ASCII + // + // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken. + if (strtolower($from ?? '') == 'us-ascii' && $to == 'UTF-8') { + return $str; + } + + $result = ''; + + if (strtolower($from) == 'iso-2022-jp'){ + $from = 'iso-2022-jp-ms'; + } + + // Try iconv. + if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7' && $from != 'iso-2022-jp-ms') { + try { + $result = iconv($from, $to.'//IGNORE', $str); + } catch (\Exception $e) { + $result = @iconv($from, $to, $str); + } + } + + // In some cases iconv can't decode the string and returns: + // Detected an illegal character in input string. + // https://github.com/freescout-helpdesk/freescout/issues/3089 + if (!$result) { + if (!$from) { + return mb_convert_encoding($str, $to); + } + return mb_convert_encoding($str, $to, $from); + } else { + return $result; + } + } + + /** + * Get the encoding of a given abject + * @param string|object $structure + * + * @return string|null + */ + public function getEncoding($structure): string { + if (property_exists($structure, 'parameters')) { + foreach ($structure->parameters as $parameter) { + if (strtolower($parameter->attribute) == "charset") { + return EncodingAliases::get($parameter->value, "ISO-8859-2"); + } + } + }elseif (property_exists($structure, 'charset')){ + return EncodingAliases::get($structure->charset, "ISO-8859-2"); + }elseif (is_string($structure) === true){ + return mb_detect_encoding($structure); + } + + return 'UTF-8'; + } + + /** + * Get the messages folder + * + * @return mixed + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\FolderFetchingException + * @throws Exceptions\RuntimeException + */ + public function getFolder(){ + return $this->client->getFolderByPath($this->folder_path); + } + + /** + * Create a message thread based on the current message + * @param Folder|null $sent_folder + * @param MessageCollection|null $thread + * @param Folder|null $folder + * + * @return MessageCollection|null + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\FolderFetchingException + * @throws Exceptions\GetMessagesFailedException + * @throws Exceptions\RuntimeException + */ + public function thread(Folder $sent_folder = null, MessageCollection &$thread = null, Folder $folder = null): MessageCollection { + $thread = $thread ?: MessageCollection::make([]); + $folder = $folder ?: $this->getFolder(); + $sent_folder = $sent_folder ?: $this->client->getFolderByPath(ClientManager::get("options.common_folders.sent", "INBOX/Sent")); + + /** @var Message $message */ + foreach($thread as $message) { + if ($message->message_id->first() == $this->message_id->first()) { + return $thread; + } + } + $thread->push($this); + + $this->fetchThreadByInReplyTo($thread, $this->message_id, $folder, $folder, $sent_folder); + $this->fetchThreadByInReplyTo($thread, $this->message_id, $sent_folder, $folder, $sent_folder); + + if (is_array($this->in_reply_to)) { + foreach($this->in_reply_to as $in_reply_to) { + $this->fetchThreadByMessageId($thread, $in_reply_to, $folder, $folder, $sent_folder); + $this->fetchThreadByMessageId($thread, $in_reply_to, $sent_folder, $folder, $sent_folder); + } + } + + return $thread; + } + + /** + * Fetch a partial thread by message id + * @param MessageCollection $thread + * @param string $in_reply_to + * @param Folder $primary_folder + * @param Folder $secondary_folder + * @param Folder $sent_folder + * + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\GetMessagesFailedException + * @throws Exceptions\RuntimeException + * @throws Exceptions\FolderFetchingException + */ + protected function fetchThreadByInReplyTo(MessageCollection &$thread, string $in_reply_to, Folder $primary_folder, Folder $secondary_folder, Folder $sent_folder){ + $primary_folder->query()->inReplyTo($in_reply_to) + ->setFetchBody($this->getFetchBodyOption()) + ->leaveUnread()->get()->each(function($message) use(&$thread, $secondary_folder, $sent_folder){ + /** @var Message $message */ + $message->thread($sent_folder, $thread, $secondary_folder); + }); + } + + /** + * Fetch a partial thread by message id + * @param MessageCollection $thread + * @param string $message_id + * @param Folder $primary_folder + * @param Folder $secondary_folder + * @param Folder $sent_folder + * + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\GetMessagesFailedException + * @throws Exceptions\RuntimeException + * @throws Exceptions\FolderFetchingException + */ + protected function fetchThreadByMessageId(MessageCollection &$thread, string $message_id, Folder $primary_folder, Folder $secondary_folder, Folder $sent_folder){ + $primary_folder->query()->messageId($message_id) + ->setFetchBody($this->getFetchBodyOption()) + ->leaveUnread()->get()->each(function($message) use(&$thread, $secondary_folder, $sent_folder){ + /** @var Message $message */ + $message->thread($sent_folder, $thread, $secondary_folder); + }); + } + + /** + * Copy the current Messages to a mailbox + * @param string $folder_path + * @param boolean $expunge + * + * @return null|Message + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\FolderFetchingException + * @throws Exceptions\RuntimeException + * @throws InvalidMessageDateException + * @throws MessageContentFetchingException + * @throws MessageHeaderFetchingException + * @throws Exceptions\EventNotFoundException + * @throws MessageFlagException + * @throws Exceptions\MessageNotFoundException + */ + public function copy(string $folder_path, bool $expunge = false) { + $this->client->openFolder($folder_path); + $status = $this->client->getConnection()->examineFolder($folder_path); + + if (isset($status["uidnext"])) { + $next_uid = $status["uidnext"]; + + /** @var Folder $folder */ + $folder = $this->client->getFolderByPath($folder_path); + + $this->client->openFolder($this->folder_path); + if ($this->client->getConnection()->copyMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) { + return $this->fetchNewMail($folder, $next_uid, "copied", $expunge); + } + } + + return null; + } + + /** + * Move the current Messages to a mailbox + * @param string $folder_path + * @param boolean $expunge + * + * @return Message|null + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\FolderFetchingException + * @throws Exceptions\RuntimeException + * @throws InvalidMessageDateException + * @throws MessageContentFetchingException + * @throws MessageHeaderFetchingException + * @throws Exceptions\EventNotFoundException + * @throws MessageFlagException + * @throws Exceptions\MessageNotFoundException + */ + public function move(string $folder_path, bool $expunge = false) { + $this->client->openFolder($folder_path); + $status = $this->client->getConnection()->examineFolder($folder_path); + + if (isset($status["uidnext"])) { + $next_uid = $status["uidnext"]; + + /** @var Folder $folder */ + $folder = $this->client->getFolderByPath($folder_path); + + $this->client->openFolder($this->folder_path); + if ($this->client->getConnection()->moveMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) { + return $this->fetchNewMail($folder, $next_uid, "moved", $expunge); + } + } + + return null; + } + + /** + * Fetch a new message and fire a given event + * @param Folder $folder + * @param int $next_uid + * @param string $event + * @param boolean $expunge + * + * @return Message + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\EventNotFoundException + * @throws Exceptions\MessageNotFoundException + * @throws Exceptions\RuntimeException + * @throws InvalidMessageDateException + * @throws MessageContentFetchingException + * @throws MessageFlagException + * @throws MessageHeaderFetchingException + */ + protected function fetchNewMail(Folder $folder, int $next_uid, string $event, bool $expunge): Message { + if($expunge) $this->client->expunge(); + + $this->client->openFolder($folder->path); + + if ($this->sequence === IMAP::ST_UID) { + $sequence_id = $next_uid; + }else{ + $sequence_id = $this->client->getConnection()->getMessageNumber($next_uid); + } + + $message = $folder->query()->getMessage($sequence_id, null, $this->sequence); + $event = $this->getEvent("message", $event); + $event::dispatch($this, $message); + + return $message; + } + + /** + * Delete the current Message + * @param bool $expunge + * @param string|null $trash_path + * @param boolean $force_move + * + * @return bool + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\EventNotFoundException + * @throws Exceptions\FolderFetchingException + * @throws Exceptions\MessageNotFoundException + * @throws Exceptions\RuntimeException + * @throws InvalidMessageDateException + * @throws MessageContentFetchingException + * @throws MessageFlagException + * @throws MessageHeaderFetchingException + */ + public function delete(bool $expunge = true, string $trash_path = null, bool $force_move = false) { + $status = $this->setFlag("Deleted"); + if($force_move) { + $trash_path = $trash_path === null ? $this->config["common_folders"]["trash"]: $trash_path; + $status = $this->move($trash_path); + } + if($expunge) $this->client->expunge(); + + $event = $this->getEvent("message", "deleted"); + $event::dispatch($this); + + return $status; + } + + /** + * Restore a deleted Message + * @param boolean $expunge + * + * @return bool + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\EventNotFoundException + * @throws MessageFlagException + * @throws Exceptions\RuntimeException + */ + public function restore(bool $expunge = true): bool { + $status = $this->unsetFlag("Deleted"); + if($expunge) $this->client->expunge(); + + $event = $this->getEvent("message", "restored"); + $event::dispatch($this); + + return $status; + } + + /** + * Set a given flag + * @param string|array $flag + * + * @return bool + * @throws Exceptions\ConnectionFailedException + * @throws MessageFlagException + * @throws Exceptions\EventNotFoundException + * @throws Exceptions\RuntimeException + */ + public function setFlag($flag): bool { + $this->client->openFolder($this->folder_path); + $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag); + $sequence_id = $this->getSequenceId(); + try { + $status = $this->client->getConnection()->store([$flag], $sequence_id, $sequence_id, "+", true, $this->sequence === IMAP::ST_UID); + } catch (Exceptions\RuntimeException $e) { + throw new MessageFlagException("flag could not be set", 0, $e); + } + $this->parseFlags(); + + $event = $this->getEvent("flag", "new"); + $event::dispatch($this, $flag); + + return (bool)$status; + } + + /** + * Unset a given flag + * @param string|array $flag + * + * @return bool + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\EventNotFoundException + * @throws MessageFlagException + * @throws Exceptions\RuntimeException + */ + public function unsetFlag($flag): bool { + $this->client->openFolder($this->folder_path); + + $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag); + $sequence_id = $this->getSequenceId(); + try { + $status = $this->client->getConnection()->store([$flag], $sequence_id, $sequence_id, "-", true, $this->sequence === IMAP::ST_UID); + } catch (Exceptions\RuntimeException $e) { + throw new MessageFlagException("flag could not be removed", 0, $e); + } + $this->parseFlags(); + + $event = $this->getEvent("flag", "deleted"); + $event::dispatch($this, $flag); + + return (bool)$status; + } + + /** + * Set a given flag + * @param string|array $flag + * + * @return bool + * @throws Exceptions\ConnectionFailedException + * @throws MessageFlagException + * @throws Exceptions\EventNotFoundException + * @throws Exceptions\RuntimeException + */ + public function addFlag($flag): bool { + return $this->setFlag($flag); + } + + /** + * Unset a given flag + * @param string|array $flag + * + * @return bool + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\EventNotFoundException + * @throws MessageFlagException + * @throws Exceptions\RuntimeException + */ + public function removeFlag($flag): bool { + return $this->unsetFlag($flag); + } + + /** + * Get all message attachments. + * + * @return AttachmentCollection + */ + public function getAttachments(): AttachmentCollection { + return $this->attachments; + } + + /** + * Get all message attachments. + * + * @return AttachmentCollection + */ + public function attachments(): AttachmentCollection { + return $this->getAttachments(); + } + + /** + * Checks if there are any attachments present + * + * @return boolean + */ + public function hasAttachments(): bool { + return $this->attachments->isEmpty() === false; + } + + /** + * Get the raw body + * + * @return string + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\RuntimeException + */ + public function getRawBody() { + if ($this->raw_body === null) { + $this->client->openFolder($this->folder_path); + + $this->raw_body = $this->structure->raw; + } + + return $this->raw_body; + } + + /** + * Get the message header + * + * @return Header + */ + public function getHeader() { + return $this->header; + } + + /** + * Get the current client + * + * @return Client + */ + public function getClient(): Client { + return $this->client; + } + + /** + * Get the used fetch option + * + * @return integer + */ + public function getFetchOptions() { + return $this->fetch_options; + } + + /** + * Get the used fetch body option + * + * @return boolean + */ + public function getFetchBodyOption() { + return $this->fetch_body; + } + + /** + * Get the used fetch flags option + * + * @return boolean + */ + public function getFetchFlagsOption() { + return $this->fetch_flags; + } + + /** + * Get all available bodies + * + * @return array + */ + public function getBodies(): array { + return $this->bodies; + } + + /** + * Get all set flags + * + * @return FlagCollection + */ + public function getFlags(): FlagCollection { + return $this->flags; + } + + /** + * Get all set flags + * + * @return FlagCollection + */ + public function flags(): FlagCollection { + return $this->getFlags(); + } + + /** + * Get the fetched structure + * + * @return Structure|null + */ + public function getStructure(){ + return $this->structure; + } + + /** + * Check if a message matches an other by comparing basic attributes + * + * @param null|Message $message + * @return boolean + */ + public function is(Message $message = null): bool { + if (is_null($message)) { + return false; + } + + return $this->uid == $message->uid + && $this->message_id->first() == $message->message_id->first() + && $this->subject->first() == $message->subject->first() + && $this->date->toDate()->eq($message->date); + } + + /** + * Get all message attributes + * + * @return array + */ + public function getAttributes(): array { + return array_merge($this->attributes, $this->header->getAttributes()); + } + + /** + * Set the message mask + * @param $mask + * + * @return $this + */ + public function setMask($mask): Message { + if(class_exists($mask)){ + $this->mask = $mask; + } + + return $this; + } + + /** + * Get the used message mask + * + * @return string + */ + public function getMask(): string { + return $this->mask; + } + + /** + * Get a masked instance by providing a mask name + * @param string|mixed $mask + * + * @return mixed + * @throws MaskNotFoundException + */ + public function mask($mask = null){ + $mask = $mask !== null ? $mask : $this->mask; + if(class_exists($mask)){ + return new $mask($this); + } + + throw new MaskNotFoundException("Unknown mask provided: ".$mask); + } + + /** + * Get the message path aka folder path + * + * @return string + */ + public function getFolderPath(): string { + return $this->folder_path; + } + + /** + * Set the message path aka folder path + * @param $folder_path + * + * @return $this + */ + public function setFolderPath($folder_path): Message { + $this->folder_path = $folder_path; + + return $this; + } + + /** + * Set the config + * @param $config + * + * @return $this + */ + public function setConfig($config): Message { + $this->config = $config; + + return $this; + } + + /** + * Set the available flags + * @param $available_flags + * + * @return $this + */ + public function setAvailableFlags($available_flags): Message { + $this->available_flags = $available_flags; + + return $this; + } + + /** + * Set the attachment collection + * @param $attachments + * + * @return $this + */ + public function setAttachments($attachments): Message { + $this->attachments = $attachments; + + return $this; + } + + /** + * Set the flag collection + * @param $flags + * + * @return $this + */ + public function setFlags($flags): Message { + $this->flags = $flags; + + return $this; + } + + /** + * Set the client + * @param $client + * + * @return $this + * @throws Exceptions\RuntimeException + * @throws Exceptions\ConnectionFailedException + */ + public function setClient($client): Message { + $this->client = $client; + $this->client->openFolder($this->folder_path); + + return $this; + } + + /** + * Set the message number + * @param int $uid + * + * @return $this + * @throws Exceptions\MessageNotFoundException + * @throws Exceptions\ConnectionFailedException + */ + public function setUid(int $uid): Message { + $this->uid = $uid; + $this->msgn = $this->client->getConnection()->getMessageNumber($this->uid); + $this->msglist = null; + + return $this; + } + + /** + * Set the message number + * @param int $msgn + * @param int|null $msglist + * + * @return $this + * @throws Exceptions\MessageNotFoundException + * @throws Exceptions\ConnectionFailedException + */ + public function setMsgn(int $msgn, int $msglist = null): Message { + $this->msgn = $msgn; + $this->msglist = $msglist; + $this->uid = $this->client->getConnection()->getUid($this->msgn); + + return $this; + } + + /** + * Get the current sequence type + * + * @return int + */ + public function getSequence(): int { + return $this->sequence; + } + + /** + * Set the sequence type + * + * @return int + */ + public function getSequenceId(): int { + return $this->sequence === IMAP::ST_UID ? $this->uid : $this->msgn; + } + + /** + * Set the sequence id + * @param $uid + * @param int|null $msglist + * + * @throws Exceptions\ConnectionFailedException + * @throws Exceptions\MessageNotFoundException + */ + public function setSequenceId($uid, int $msglist = null){ + if ($this->getSequence() === IMAP::ST_UID) { + $this->setUid($uid); + $this->setMsglist($msglist); + }else{ + $this->setMsgn($uid, $msglist); + } + } +} diff --git a/freescout-dist/overrides/webklex/php-imap/src/Structure.php b/freescout-dist/overrides/webklex/php-imap/src/Structure.php new file mode 100644 index 0000000..f38d752 --- /dev/null +++ b/freescout-dist/overrides/webklex/php-imap/src/Structure.php @@ -0,0 +1,174 @@ +raw = $raw_structure; + $this->header = $header; + $this->config = ClientManager::get('options'); + $this->parse(); + } + + /** + * Parse the given raw structure + * + * @throws MessageContentFetchingException + * @throws InvalidMessageDateException + */ + protected function parse(){ + $this->findContentType(); + $this->parts = $this->find_parts(); + } + + /** + * Determine the message content type + */ + public function findContentType(){ + $content_type = $this->header->get("content_type"); + $content_type = (is_array($content_type)) ? implode(' ', $content_type) : $content_type; + if($content_type && stripos($content_type, 'multipart') === 0) { + $this->type = IMAP::MESSAGE_TYPE_MULTIPART; + }else{ + $this->type = IMAP::MESSAGE_TYPE_TEXT; + } + } + + /** + * Find all available headers and return the left over body segment + * @var string $context + * @var integer $part_number + * + * @return Part[] + * @throws InvalidMessageDateException + */ + private function parsePart(string $context, int $part_number = 0): array { + $body = $context; + while (($pos = strpos($body, "\r\n")) > 0) { + $body = substr($body, $pos + 2); + } + $headers = substr($context, 0, strlen($body) * -1); + $body = substr($body, 0, -2); + + $headers = new Header($headers); + if (($boundary = $headers->getBoundary()) !== null) { + return $this->detectParts($boundary, $body, $part_number); + } + return [new Part($body, $headers, $part_number)]; + } + + /** + * @param string $boundary + * @param string $context + * @param int $part_number + * + * @return array + * @throws InvalidMessageDateException + */ + private function detectParts(string $boundary, string $context, int $part_number = 0): array { + $base_parts = explode( $boundary, $context); + $final_parts = []; + foreach($base_parts as $ctx) { + $ctx = substr($ctx, 2); + if ($ctx !== "--" && $ctx != "" && $ctx != "\r\n") { + $parts = $this->parsePart($ctx, $part_number); + foreach ($parts as $part) { + $final_parts[] = $part; + $part_number = $part->part_number; + } + $part_number++; + } + } + return $final_parts; + } + + /** + * Find all available parts + * + * @return array + * @throws MessageContentFetchingException + * @throws InvalidMessageDateException + */ + public function find_parts(): array { + if($this->type === IMAP::MESSAGE_TYPE_MULTIPART) { + if (($boundary = $this->header->getBoundary()) === null) { + throw new MessageContentFetchingException("no content found", 0); + } + + return $this->detectParts($boundary, $this->raw); + } + + return [new Part($this->raw, $this->header)]; + } + + /** + * Try to find a boundary if possible + * + * @return string|null + * @Depricated since version 2.4.4 + */ + public function getBoundary(){ + return $this->header->getBoundary(); + } +} diff --git a/freescout-dist/package.json b/freescout-dist/package.json new file mode 100644 index 0000000..d5f4084 --- /dev/null +++ b/freescout-dist/package.json @@ -0,0 +1,15 @@ +{ + "private": true, + "scripts": { + "dev": "npm run development", + "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", + "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", + "watch-poll": "npm run watch -- --watch-poll", + "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", + "prod": "npm run production", + "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" + }, + "devDependencies": { + + } +} diff --git a/freescout-dist/phpcs.xml b/freescout-dist/phpcs.xml new file mode 100644 index 0000000..f77af0c --- /dev/null +++ b/freescout-dist/phpcs.xml @@ -0,0 +1,162 @@ + + + Coding Standard + + app + config + public + resources + routes + tests + + + + + + + + + + + + + + + + + + /app/Http/Resources/*\.php + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + *.php + + + database/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */.phpstorm.meta.php + */_ide_helper.php + */database/* + */cache/* + */*.js + */*.css + */*.xml + */*.blade.php + */autoload.php + */storage/* + */docs/* + */vendor/* + */migrations/* + */config/* + */public/index.php + */*.blade.php + */Console/Kernel.php + */Exceptions/Handler.php + */Http/Kernel.php + */Providers/* + */resources/lang/* + diff --git a/freescout-dist/phpunit.xml b/freescout-dist/phpunit.xml new file mode 100644 index 0000000..740c55f --- /dev/null +++ b/freescout-dist/phpunit.xml @@ -0,0 +1,33 @@ + + + + + ./tests/Feature + + + + ./tests/Unit + + + + + ./app + + + + + + + + + + + diff --git a/freescout-dist/public/.htaccess b/freescout-dist/public/.htaccess new file mode 100644 index 0000000..b75525b --- /dev/null +++ b/freescout-dist/public/.htaccess @@ -0,0 +1,21 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Handle Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/freescout-dist/public/android-chrome-192x192.png b/freescout-dist/public/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..02d8df91c922f5d9359d3dd0d322113fe4bdf7a8 GIT binary patch literal 5405 zcmeHLWmB9%mwg6@L4ppJ3H&E&+lL1RWp{G`PFFYY2n@Ap}W~00Y6@U4n-o z!3oQ|-}b})fvv6T?pxi}r|#`jU8m~YXe|vz0z7Iw000P-mE?3DW9NS+4*KJYK+EHQ z3>bFO>e2vEp9sFQ#C+V-Su5$N1Ay-f00;^LfSbpopj`m)UaPhk z0HCdEsp-i-UI4sj|9|{H1ON9JczAfY23G_>{_ryo9SvOoAYvBPus}v7mN#M$(+P*X zZy#Mbya5TEUH^l<%;;UeB9Qc~-MsVa+_U|-gD(*`b4j9A1^G&5-hB=>YP!IcsXj>| zMNHZ}K@tfY-8L25*`8gUw z(FZaoepO=P&5OMYo(p7J*o|jNN_c%-!aYs+L)xQ2=r^{#tfIAxpn5p z{WtB@rz&e^SV5{U#zck!3>d~E!$;)4XG>XD_#B|9O8fQm7yX*wma!@6=zdOLc54+U z_jl!skmvGMH&uS_P*|j(=Wdd=-8)qH_R3h^Fs;EH##J@b)I@~`xsN^j!J3IUP*7NPe2mVA2yR5F;gT4eX4xgK@ zMQ3xpZ{TN=3zUmAluS&5kuMH&K5?6cH@A!1Pos9?4@a7)b(1uRfbQT# z4CZC_YGPZwfaxVVw-_<2&Ej@xb3b#>TeK}m&%$F3R^ZcKdq7LbtHAjrSMBTa$48e$~0veBtwJWgqIS6RuPSF2PSO*D(_}>Hi4gt4Tt5ctS`F_yuZS@wRyM zp0`x&HwD* z$r8GR1=CkbKb-qk!QOWoY}mIt~MjzoR*qjH8slZP)J{6`cDotF{I z<3#xXoJZa9a!36ng5+!cIV!^BOagz0-t9ioP%MWPT4Ga4Ztm-wP5-8aGgss->zn$S z%$XJ(W9Ed2n-)H_d@rHJwwSx6HDn%wtz{v#&c!rMe9CVzZp1Cj?Vlx1?Zjru&^xj! z$|7zTw>k3l>QNW>Vxl`b8GT@UF4segJ!~<#HZG7Dp{w^1y17N1iT8N!@<11d1CxK| zJ_q)E!*}zZ_})CKLjg80A?v2Ngr3&V3<-3T8>usrMWv+|Jtneb#|91YtP)-nJie9? zKXAamI`?{^pzcGaIvgpCc4VA|ZC3W@|&!JFUTs`ip>phj|1&hYg1?4k>c8cPW= zvc{%yH%4M^6tJ~hUm*a=Y^tzUOiY8I&YpS?QR~%f4bc@!012Zut`RezPtadC&}!zX zRg0t_7%quF_v{U5sr)dZQQ$hXp+thlV);>?H<2_qxP}jFvkUFl1sIiZGc;-EuXHG3 zr(rkaRhP?6R(BD~w-JD^OC@T#M1i^?;-HzP0;L7FM7oFsG|X!zL$L^QQCn z1}L)2P%;dIrGWaj*1Hk_aZmC{_*VFEn7Dzce)vBoL+xX{E3$-~r=GhZSw zEqA`WlA&IXT_n>UM_1r$?kx(`oL3i*L#t6s+q>aXHC4_xESireD#-!~z`fUQ6m;TU zxo7MKLy^UYg}TgWTXH2e_0Q1BvZ88(MzM5z8->J~pdCNY_YohWF}mLO!Cb5)6>wy} zOn(`#pfSz%T%o>uF?nrl_}Y2rYw@D7jTV$4h~i7B*Uh9&qfzW?3FsqHXCO1GS$a`0 zSc(^PYd8OP03WL~b^2LT&oDpCun>8COCq){Yye0L&B6}wV9%n6mz zH?}fOEK+Z@sL-$s7Ilg0M)EArNoS$A#22eND}SxwJESjU zFv*LFqJOaGWW02~r%b+5uYlV`Vv`9lsBN|~gI?E9i$(0>U|weSKFZ#!x6F7l!&?A{ zkwir*^qvc>sszOD)yiJEU3B{7K)acv@LZ-Hz#1K0$ zmz9OXv2i!QvyZvt#I%u3!ad9Q=ly-B0)-A1jHkh9)KShFi59`QZEp-0Get14E|~*- zBB}fnUNhr4O3hHNCfj1x34crQNWSGO(f+jGvU zzp_-D`#)0wH<3iF^c#u$9Wg5l)70qAz)jktpp>a_$D}IydUARf)U88$DCB;JhEYMxtlo4#FLwrwFzgLOCGt&%HBvjQPn$(Gs{Y!84Y+>~`Y)oG4-^&1}3W zjN}~0QeLMwYual9Bm@o&%J?6;$fQt)96t@w`y0}iyGo_7H%Q;oD-TIM&!}bVk-dE= z26)04VV#0uv`IZeRI*BCXnCnw?OeD8Jt^egQyJPn@v>YbUeaWHV6rViS#@Pv(Jr`u zg|S%>y`k1Fdan8bZ3sPH2n0ko$=U5Qx zf$4N?ARRZj1VON98K{1mEQ_)}aK8Cusf%E-I6Y8lsB$De0cW==C2%b9I*fNVjV#}I z?MiltHmcHxJ}tzrCVbI6YeMG!2||xL>Y|6M6o_KkesEfVGj@*me4}IF>U1Mi&0Z0@ z(!xDHDSwZT2j6~u%m%UYh1#D@6=~z%wt6E+3Gq+!4Y~U-g|O_G_WyGkpz3$C-<4|i7whk~#vjYfDJPd%)u~#0MFA_-J9y;z{LO7UlPj0qSG??6I@Ag6@XT&C-V4QxNuk@@8S5@k>T+ z(tCL)n?_d`7$Y^e^m|+~l1&lL5n7$dclN^#5MHSmBPOTZ<+*g5UYp1KVWePmQTg|arxGvZYs)U( zf8Xn|2eSogHjd4;G(|{~uXLp}i7?yjjIQ2rI_w0Ge_r4Q~zgbtY0~|J$hC(O!j5 zI%rn5{o$@DZQ%VzhonJC%vcdTP-lICznfj}boP57c))POEn33OXW+enW&Jjy1vfBmKUnpAMnHOFd)Ew_!Yzu>G9 zXv#leOZZH%!ZVo@N0oFU@XLoH>XJ^C?XxmDF?+?{j$`5Sb+E5qQV0}}%(tvLF--hG z3Dwc6VR?rJond5k4L4(66(PB4WGzNl!GH+U$o+t3K8@?(Uh+-y(An@=4aTF)No%Ah z^R;LR#KwSS<(4jy&PuW-^<2-T(dwKgpG028KNzq}s_)sY_>pNvvNVQORI2Nac&t=}3Fybzn7rNMyy>AS`t?QROk(m85+@d?u!x|+aS76yo0wbo?v>>2by;J%rDZrcmCha z^79)V5~k>LVQ$-9M}Ax_?CLIrbF#Yxh47VE1*M9@@#n5z7t6LMy06wg@5U-dindMZ zHA|EcbB=W9M+l3&?+v_o01cnM6Feu?hxZy$^a5x7q zw;r5V1kNwQBLw4yi*R!v!Z|7a2f!I=V{hy8zXS4}GCn*4sQ!~d7isI^W$BIpq-~H^ z2s&kFOFM)P!qUda?I+@~p$HiI>KS?HSbEX9x+87voe*>$KCTEldsh$Z#~x$e<|#go zn*j^Ufac(k^0p3u&H(1L29q+7s-}{mlhVlr@papvGkn&g8=RZ!?-}Z$?ZF-lQYup6 Z1ok1s9;Y(=>5nM@WqA#`S{aLw{{pFR=9K^d literal 0 HcmV?d00001 diff --git a/freescout-dist/public/android-chrome-256x256.png b/freescout-dist/public/android-chrome-256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..b1fb563d971f1c4d859f297cd8db43492246eba3 GIT binary patch literal 7380 zcmeI1WmDWiu zxG(M}xTk8WdS+^Rx~i*Zrk>wKs3^-~VUS_~0DuLNlTrf!&`TEtpdr2(+bSK67lULa zt|Sfs^|6=_Cde;iDl<7XB>?cE2Y`St0C4v*6|e^YuABgHU!Z1uUrHJx9vxUp z%Pqd5|JTL+13IH@KxWsRZb)(8+8qKiiAF@r%pQozs(ShGhR3sg_xe%5uHoc~*&^r9 zD@fen<(s+<8pn}S7?NPnEW1zOD89|mKWwF@bCiU?#8a5pP_O*Dgrz(t&q3>zVYr(ATP}88D4qVO=dJjrk$-m#mG5{cYo3gQ0Y7Q4 z@Q;#j+24~FK6U=Abzzw|sftw4u`d7jE#vAX2_RDiS*g2udldPX0V)ilB#pX(`4$U@ zd<tP&&kZh(sv!g^ zLpZ0mv1uu?4T-M`E}$V_E8~+M%P#!cBc~@gV!V1roQr}V1cE@_KYgQ)7o0a4T48y^ z7QHvlX7)p^9X+1|5LzJ&${9k^#e*YHWaXOb^dB=AUA^7Fz!X$is8splTq^+sE-CL= zYppIU+?ZkI$OO>UU!GlE(2OZRhwhcwA=qrEs+)>Jd%puh^qSvx z=M~N|Hv%Z2?`TbcT2NSY#y!!Hh>O9axsUslDL@G3upsoyNSK0YH{>T4@5M1*@2)Is zI8Vss>gzTtQ@_?l2_U0v9h0anL-|43#h}s2BWaKv3_xItN2WP*Zz=W(hX$oGGdt~g z{z;@V@;iF!V5qR7^RjT`ZEU2{r%9b*NsoRhK6Bm)0#qW#UQ5Wsl_D)7c8tkEVwdM{ zY}U8m=MZYK#-h}VBIP9*$}8hZJ}ApW{FopsngB~a;$lCgQa2y1Ce@3mpVA_TdHq6(*M4>^g2yGZ z@6iBLcH@Lf*Eljvi)6?!GLq{bO3a`tKm3_;KJ;c<1iGK#_4rga=^eQcabSfnmapMA zT&aCJGvt&yc%CqM5!&^mNZY=i3ydH$>up<8RzfLUyyL)oybti4p)@V1&BQO^I?B7_ zZFy!Ko}W;6j&e_$qXqO;2AX0HD+bmIrlEm`;e<5JFk+0w6h{msprFr?K#z~SlaTOd z7d_JHB753wA`7zJ^*R}7s+9=s;A%@|6v5r^J>t|+IIqH?Bje1#-!%eI9)|n*2XSBd z!J2}Cf-hUhmz;wftXn7MfQB==BLOBe+j3{Ak6M&tNTRx>MPrjI%%UVw zJ;ZVT99|7K&?4<$7NZ;Rp$0M}a<;D7iR&#K&qNO2+0;3gy*uJl#PLl<@n3 zuz5Y{`*=7ve~;twwyNABg8*t{i|zGx!Kiwt=Cj**A!_#{uIz<&dj0xoe6{P<0v)=B z@9Hnwu=nOp8kl#Ie_x(;5#+2tAvH^t`Mrgz%FSTJVKQN6((e^*(FSXMB%r#*2@7m- zTT;Wx-zs+F_<(%kvsWJ}yNzngYA7wj)a${XC-2QYFiDG?h@AOQ%#R*F6p)G1%3zC6 zV&_H6Bv}QhXIn^YF$L)FD3WK*?8bGHhA&HzKWi>FVG!h_ zLYrv+yP4Qh^rrcN8RF)MEf_8W9c6jiACf|q%^Q3=Aga$QuaqU-~L)x&Xh zf;q#0C9U|=ITB`R#owXwi1C`O714O0sVaRX8vk$OU#uuC3(QB}p;Z}<5Hkh5!{?~` zv&QH!fDk|%F)Ps`9DxVmW0mD{1sCrkmU&fX*ha7!BzaU1Vo>nz;{fAS(}?Aoit*)p z=0R_g$7P-ao6B9c;}7u?8!$)ZcS*_{tEjwP`mbnNSTc38ilZ}5F-DRK19O&Lg82JU zsw}1t2lU=h-aENNE~pBTcvFv_PIcEIxzET1&|O zg=RKtxos&0KSr1E7pUM5FK3=L7{<%>k!_8gI&?=dCm>^5t6qqzRms%38&uyw{a#0C zFGAbbCy{H$LYqT@4gXln*G?pk&t%bv5*i>KFTl3XY) z#ZW$qRH4(b$|b#)6WsQCSjBRS%^9tSes6=JVA3W66%Xj=KwNl^frqy;clzb(T-Iz1 zTk?Bm4#W&ci9K9()Diqk8Uq-fcVpE3&0h+>4fmPs4occ9*6smmotjZPNvSGo-N5a2 z)38V{_h0iV_n-F|*A9$gs$!5MD3%Orhq_=~v=zED0j2U6)JHh2lGHhEsjLqK`L8p1UEavhhcRU;de)a1y0s@A$@evdKFL zZD~I!>%xNZKmnhJg*KDg9*cl5&aF)Tu{1Z7==W1vBqRvYQIdK&(ly`f{H$4VMRBhC zJU^ttpRJB&@1W(E+&jE=h%B-`lv-3cgOAGglg3$yMXScCdZASF(LA9iI2%Xv{DwWg zb|j~1!T0H+iCAdomGqyUS7E#AgD~$!cJfMr&b~H)+leIqU@~2Z*C~UsMoP}Bc2;q- zluf%(CuDrDDNT%vzzPW|$5b~+#3-$?S?f6jXO_VOLwmI%b5S8*?3i_>k%OCfTcu^F zf54($_!YI`a7R?Iikwx&LD^}|#L0d- zijY%&a^a=s;uImZoNF~#&>w1?+g5_W=h8lp9#Ek*y@X*!W z+Ds-c)~omrLH_-!Qm>-HqL@222>O1V1WLtLuo^Eu8?YD)OKeXIST-Mz`^vKSEkBP> zR_^wLpo`6Z3ZNP2a`y2r7jus<#?Cs{+eXYa!jHTsI_E#6JCKCdZRhge|+E}M*7Rg)rSb4(HvbZSuMN1%X)hiSUK zEb@$;@-I~lJPZvDZKE4G7i!a|A~iy0O4`YLj1O#Vw?G;7X=fEJGtmqJ9~+TdlYFkk z)oPJ!0sG3yTV5#!$Z8o+`}=Qin($(HncbX-!#rW4t6=g1IWIdTUxQ>3s?L>gaj$vm z1Z5>URTy{0a_l;I!sB~L{3bZ|ZCNQvOvXnQlF#vEZB52m%**QgP8S*@VlQ+|M5(%aCY}Syo809SjpWXTLQO#}JPcM`G`%r=`BR0ja)u zcWn)!=#3mZEhZ@yLExTutu_}H(tYIAr-e?Gt$iJy(^&cxB?i=0 z22Ef~-D;%9xh5mBFWU}7@5yzO?Q{0 zfjPYY0{VUA9r#5>^8sp#x5_=dfxvJP2}AuW@a!kWp|wCvTf!jwPc%RKxo#1u0w>m> zJqs&VkPc6A7JApF#*hgy$*O#10N3hcdypQqXUp;reJAsq^FK0m*|MlW$*UzaphVX% zkXIWRh1B{6e0Qkuvk4F>G^`zQeXmR4lCJiV+2NhEYY)wl@d_h>+BHyTPnCj1=e9&tHRX0(I8 zO5C`gka`R3waz-E^h{6Uv8*+n+yLE*eVas_llEj@J4is9RZ#ymG2pU%1LR5CPkKnV zo-%R8q^%FaH<1Hk0PhQh1dM^LBoT5ivH;P9BJ@m?41E}hW!*`ruDwjM5{w{zW=aGW zr6AZX;Dj)M|WENCF&vJ|F=a)D=5*?~wAfi!;{!WWuiS{O|!;yV|C4-tHUQA zxgM#!gjbL!40@-3UFe1YVdG(zR=|-iM21TxD?TIJ_-9( zq+IdAqfIx@^sJ|3!gl{iHw%A{ROi_DnS+vK|Lo&QID^Q^z$|xey6tu@E`0mq0}`vx zZm@@Ao=U83o-tp)KW21#Khf6+2tBmm+Z#MtUP>-;GK%BLpv$iHFvTgkk)S)gWi=fi zE(i---2-IYAp48roMN;tQ;z0Gsc<*1zd!lk-8ar6Rw|f&4Nat+RykwgjnvEyjm*wS z91@u-p;c5+D$@L2T5P$C&(GTIgvMo{uDQv9iOz9C5Xfoox*1Qe)YaUcJ@weL5-U|U zIBGK7YPtY0a0s<>G=EV%&-~Wpwp+VP`{6uNqQs%*Pg9_e@=U+9j+(#8!N0rguG)Ld z+<$q3;i3*Cl#6M1K_ToYrFHJFjKwDYTxw$z9bHmW*DUO9P<4g4&=(9JnxQ?uH=NKx zF!(kzAbBBd6dmY)tA#yvcVf$*+Yr)WS^5>SqHl;h^}J!u(+HGJInS?=A-6m09ox>Ft*$`4kVuBesg9lv(xq{!9giC%`(DrUvxtS$nHT3Uw z$CiXcSLR;xDf9tXrOR;< zgF=~TPtwneI&y6lqzTt0c*MEr@ad$y9<+VtwHGwI3Khu{+_j+?5nlO@7f=`|=C|rL zZV>N#Gq$XY{cx*%Fa-}1IbVOgC_P7GF1UFopQ3o4USkLm`IyJ!kUTW;=X&XC2`IY~ z$I-6p5c!}V-0=IBp^OJNyGaX=nCMYPJ|UZ*Z@;Di{*OhNXSa*8>-O4&kEZjk2GS#* zaMD$17M-;GHK?_yjDG0tor!&?$2a$n4BY8*PZ~y1SeI3}0nORQ&nHD!MfR-Y8F|$e z>55O++|n}|CGqfs+%GXH!&SR-etGyf1eyoL?p1aP2lm_yP91V3Pf4|olEUk5^VX5~NOHTCo^dhu zC9(LTCv!Uf`bP1fxg(}t65Mml(dMktP1y@TbNSqjYMI}AxXf$)4HXm{3AXp}yl+0x zH#Min5My?i|4Nz3aYKJz2vKEWRdZv)%ZSo}knum&{WrAfOBFGlM6^Rnp{HNmfpaXh zd@X8K`k_D){qt2Q?>oAyM@Y-6Ow@I4#J;PBJJ!S1H}wQ5$t4xlKYOavYXfXuQUaLp z&PdtY{Nkz;A!YV|k^Iipnn5`7^S|wdT|6XFhLC6kIWnRQYBz)Q7rG;Xt>Q#T8p z2KfXtmNJ5KQ2%LHwi%grTtpGG+s5URRX`O!Wjt<|JPW)~2#*caWb@8ikCPNUWFZ41 zT{XtY2dC;NH(l{3Z9)Jp}!*<&w2zNGqsRu z`#?Q-+MLJ}6A1*ZHF%ySqZh$%W!L2wlJDoDpvTRK@wxnpzDg61Nwbq^2xsO&a z*@>H#kR0_i`7HSpa)E^`zEW7J&_!iO)Gg&@Fr2lvh;3PN6FQjpB)(o$OFR9DKrHt^ zu$3@mfJQ=$SLX5NcJBjqR?A?({*&Jikrey}uooI<+;FH&uG-oYF#?g;sJ?6x3%|Ld#%q}FSMR0;o&~O1%W_#D$4RtfxmTseju2@ z@0zM4I0ytDcGNZWHq=lTwQ+alva)rzhI9G5dH{3~NLRoxmr$Hi6DIBDM@tlDOjjqQC&IaBnN9zpINILeyV^ z;jeK;f&c$}=4OEY#p3NO!C=w< z=u>~Md_|AGAvu332jq@&3j8M}DFzu2%MNb-0_ow}S)&FO28k zC|^*(ngoL|ANPNm$mqW%{W%m-Ek}R2i=n)uE8GnM zjQY=Y{F7?*KT+lWNI(bB9blV(n+-TpQ9vY)b^-EM-oRHr9^mHk0NWPUi@?7=e|S5fgTzfqRNg4Z+EYNzdrp<33T9|e{21%>Eig; z?T14Dx&xwCHh%;r!QkQLZtG(MxBW|Iz;J(~BHZn~{j9v;GWNjnOEAdT**OBD4S;I> z(HbZ(7cUQ&;J^0pbASV*{m*;j{`<wU}3ob zVPb&BKa3RI4X{jJfC=j9s?7j_Fv1*x+x8!~3b{MNETCpZ`69~!46JN$mEj{wM^P7u`T10 zk!$CPxcBd*jY{_36jB?ZdC@P@D%=iVd$cCUbK&Mg8mPTw(9|Z0WM@+=^5IghLSG83 zJjQV^iQ8c(acRW`I4IP<^E>$Rs^p29xKp5=v2~n3KMTDg&Z12YQPg@-Hb&;<8dUmi z1#8l;<6J2fMK7{UA1}(sNl=y7zS`*}P3*i}P3(*c!#lUX+OqcZqzjrJK|Uy<0$uN% zviuf)yt&j-a}d1{6n5y%D^*Ky?Oy0kDtP(@Dp3H~(Z z2?Z>eJ_cHX7Hd-@l1hGr=4n+;k-26LDTnjVxt)*g*H(KpJAtPJ$3xi&0Rr74`SXJT z%F3YxfuJB2c^O^*obB8xgz3-V)e+x@of(d>p6RcONU(vYJ!^lGZTIWPz11!pdI=_)@0)DLdGl>wjBl>T&5pA)1biC;8Vn~xG7;F z9J$pNOnq-I-@00vd^Gs-So8L*5QhXd^i~5@cx<_sQ-`MU=ZQYxMwWvGcgO+j-9TP z2>LqomJ{7i8hu*HKg@=Ozw{b~7rlu;=JI-C&86Rnb*y5AYqQSsW3qzlg|o^TAtq5* zS>zI~6COHMRg|GojJ;U5B9@g2st`sTj)|nKj9q!5J4nKeKa8=|Q9A2k*l#To5%{!^ zbaoxdZ{oFza+`nC)P)yq#AUCi3x`6OFv&2UgEO=|>?6IE`V$&oC6H)xsoYUwE`}(7 zCG32p%DjhDW`d^C8rDcpSO272fME6$x7Ylg+uxGHCK?Cd!1+?Ccu!B7@F?>8!_M`L zZ|Q^=BwwVTZp9{l;z<}#W+9Y21DDx8i|eW-VNMxJpR3N1NUm2(-|lY#mg6N_J~;^2 z%ft?aLLf*hB4)WVG4>^fJ9_xu%4}pDU{|q3+nJay z#AAW%A<{XMxF1t0OI=h5aeE5&m2Z`2MBw{Nd=Hdkd!PNbzeo+>|^g?E<7X>Y-)AGM~15?Kon)_$h_SD?+agpY572 z&&L*txon!1hC;#g7~3vH=Cf2cU)+u@d&vdqP1I;|u*Q}%3lYBG{WlYQ>1`f6d2c?; zy@5hLm7m!f2f!0A)bo=QaQLQ-&HKoSv6tRBd^d?ZY4)oRZ8o9mpU%3Lr;7gtmVFcJ z0AmhuGdMeJ9uMBOR_`(CMY@oA^5DPbIGU}y8Jn5LPEjmfGu(OI_z_Q% z$bO`A(kHld2QS&3NB>l9px_*`YuET<*bNsi|4gU8#vxicxmAyqI(1DK-Kp@NJ4tS22TeGwuU@`(CxSZTd7ZO%;>eU(M94%RhmMlN~zF9gZ z1}#)+G6|tVT#zsrb}jSjr2%f01;|UyBl+NP*!*g3>Dz}trMp#+PaDC83Hy^E04VP^^f&E}o zMec-Y_FZ>n9?Usy^vt8B(2&Qkaovw@eC4`3VSP?J)WWFSrJxY+DSRy|^FniZSqW{g zG9Age@*H(k1gEte|D%@2Db-|`vOVI$aMOHYd)4wNrjG1bB1$K+ThzO+KTnjNZbT_s z3O%8jr&=?ibG|*aMo;HB-t_~i62=cWK-qFsIW$k%m711!SaEQuNjHAetl?*Keg+f{ zq0@67&-;nkgCSXcLC5UCA~&YJ#$>MVa14(ad-aXwcmAb|+cw=ROIdDgQUMvlNCkmc zqhfg)PwF0e~m|To6Xd|JI3p>fH`Bm|K z1|E&|%oI(3X0y>QI)2;eg)fk8mM1u)H~WA=sTr;?88h;kXwidegf!Q0*NIO30gCKF zUl$V*Y?kG^)BHG1VneIEEEeJ(p5*}!XD>A4w6>4ja!#Lj8Vm6CgyVpF0t`xdQXFmA0`@t~sjH69X!gZfc9FDg2^RaOZJ>?Z!*1D=Ibp zZj1cbtZ_BJyjVh2d(&c&zQ2w<94`BjPMD1Ah9el{+4<{y*zdu`-BJ|yNS0KKeiO`Q z%CPLo8z*qE z(mryZdlzrJi6`bPB$m&6ks=Fh+{txo@w>FA2mZm`%kIdV`Bqi^qGMPA1lfp8^zOHq zFAoV}ou#IDaQzJ&6NBE--`|WbRJn6|_Q@AM{Fw5FmK(5QMZ0t5TTVnXUy`D@xbR4$ zHO_|=HgmPN=T*%<+>L~?Ea^Bd0~Tr(Th8pI@Y`@k%SmdAHdsZJ0+aPwxnq&#MX0f9 z&u%4bH(uYj;YE9`T#|Cpj$+Z*PFMyro?v+iqH$jt!;%Sl@1{28*7)N5d9hl>WfrY^3b2e-T|CoGZk{zYHB z@Q8Y++pY#1%}0=nl`L<+570@F1zYi?;-*{(v&3ViduD$)xfpf*AU5dzd7ZBZ@C50h zBupff#DFC;r=ug1LvC!pl_qpC=)V_1zgxA+AGLejZDKkczZosM{F*Q!22bSFIR?hM znu%PRC=&_E$LHq1R8GH|4(~g}I%bbREby3rn)G@;oIdwCS_Q4QUovn5kx)eo6 zh^-5zO!Ii!$wuazty~PlZmu6Sbql1W~&ZJrlQnnGq(e^0SoB*$B zqsHtUix2}VMGH*4*3)OW>D4=Dcp4AQ6BXe2B&8@--`x=KQ({MTkq;cr#|GQ?w>4X9 zuH;s%tl*VS4SF}Gx1#Hbuq^8(a?;WC_q52gWo6jX0LR9Rb1_fI)1Z9vgLU_=<6}i= z$s%xsWZ7w)zQObM8U1m}rhSxezpMJie=S~+7+V0$Rt8&>{oA8zvRk5ncxg>_f4Jt^ zp&a@HWPjDZVnWwEOOVTMaK^~FbZzDS`rD!u5q4cd*HKTlX^sZ zb=?ukeL|K4*mv%p<1VuUH955Xr5x@1{Nv%7_Le5@WVS{QVaNHsfGSn;)(l?v6O&*Z z88R$Mc6>vcydrPkfsDR$VQS*=-oW4P4`1Nb$4aJl!uAG_0n;r-H}>{k0vi17sMo~r zar#Uu`F)`zKdzxHZgpyl89I@1Hl^Y$11gNJ0{#7LCF_(st5T1=L-#2LI^>5%#Nau~ z9BBCtH^+`e9@CRi3oW!hlF*ZI+*h9$?uGw&fd9_wuU);H^HSe{ACG4` z8(bD>8gtS)L+tyrIGY+MwyU-bLf!3Qv=qe;Ox$8917!-CU(OF0-z+v{4N2zkN*aF@ zWq>h(9mSEPz>EG)HdLfU^LJ2QAU#A>_W=9Z1Yq8lr$>BM$(yS!Zw@uKx3yjQe(fPu z2c$cTfrHLWM)*yg4}=_BM$@D?Uk2J@S^&VRhVYDcmjb<`Wh{scFconsC9N?^QdN?QxVIUR--r(ti@eLme z^^2aN1HTapH~UJEj*c@Dp;a^anXSl@C+?tXJCTXh^g6MeL9|{@WUkyc_CtGD0a>nj zeuMe!I*S}@8CG@26~)-{ss7d|eCF3rIYJs2BEWAx{^~SCMXr94GBXW~ENMI|0I#rW zx++rGY+k)5MLUXXhev>;_~wx!z>CLpCzPOV{a_S+IUhT6d8>saif$;KHL9_XO=#yI zjfs3xlE!G}b$lbV`o^IK5U*6@)0XX@69hET2Qx;S*aoq?iR%irQFpkQ-T?Ri9j&VT z!C{Hkuzl=sE_1UkDqAwk@+*2Qmi-X6_(SsL zlmPn=zb|GEu2?W)A`q4^7jgTMvmv@C&-=$(RsHJjD!q59KHXV&Pjuo4G!wDILn1yF zjx{LN1K;4~#@n)xrlQ~8RmlevpS$5frEBr4E{tzh;$&j5!jijL4xY^KAv7q%=Uf+G zc;nec@OcE!UyTkO*oGjQ0(G(AmVCQ^y!DF~qicmIus0v)u4B0?|1NqN{^WOVGTGo4 z;xHKHsz}6>mE2hF!-r{mFY3f68T1{6VA%JvE`h7EY=nptPOCKy3HTATWEUWVC=YUh zf+IK=U)0g0IZfhJX;)9#&i7LZ5RMUr!C0}3US5=|$Pjuf_Vs(?*;7ZMBs0E+N|vT? zLkVQ)uYMbZrMg}m@?J;a%z2atd+8G8s&nKLNx8i-P~^b}f-AA2v~^jYd$@|yUzCa7sJ}UNZH&+65~gv}*1t8;x?6g07~qi0 zJ=pp5-Wn5vL<>3kW&68#f0(GZrFr~%-L&6_GF`frk3gE{HHu%M*FsTNSsGic_(Zv$ zW!z)3#As>5)Jz5Sa9%uhr|eZi$}mJF%EM3^1nM%=U>+o$ol4fZ6{nc9GFTl%)vhGK za189BGmK}J(MLBKZev-yPV;ZfDGc-~u8l);L0_$4LF~I{Wp)vSOmYsP(YkQZYs5#b zQXpP64ZaS8f_ZTW-hNGST(*2Wo=<2jJIV0?1cJpolbejL+s=QMbT`Z4Rjl!q$XqmM zxap=^@~--BVK(qwec=Y5jY?Lbe5EyGXYXB$&A5T-nsycJN0ZJO0@hR{dyL(KzhonF z>HCA-$65TuFUVBh$sM(MiS7Z>d2h z{P@{TT$r3kNX>rv=0O{470wRY`qEF|cd@CEjdrcxkqaXt<&9UiZdMIk0=&1DfO7E_ z;e{%zShvNGkgZH>EAogC2{m?3}js30(5Sq0YFg2*~jLnR+-W@0KR+ zd)+jFmWSXXl<)8!8=fW=5P}77OM8D*@ZTcYnh;cA@-EYPs!$BsJ9cIcop?qQmLmhe zAj`NKta|k;+RT&rr1JNJY5Z33nsf_j#y69GG^fFHMqXdRXh0x6S+T{5(w@fnk@UHD z$fB8$8X0=9nfAeQaMDkFc|ZMvcx)ox*(rt~*U+Ph;G_@637$U}Xx4=C*P z-_Uv%Uf_ON$Is`yXmZIIP`d&Tz;ES-)&~dsgdCDFFtH9t?@vg~L zaqT@y{7(>zdFp@=DCwb(_%cu4#OwwZ9XQ-KZ9L0+tzgEbfJR!kz|9l1;A3>OU+YQr zh|Cn;Q-|I=ugMn9NW_-y@BkuuD?fkDfzYM9p2rH~=+yQc|2*Nzps7kPx^SpEJB;g+ zs8Az7|K7qL41}sKyqsE*fwb&=pP03C*G!!#YhNMvq{>xB0bzhBhu9Ye(OG7owLeYg zP{j?`FVah3I-Jcd<3@GDAWRRb!j}7}Lw=BgW#nZiuxGrpSMNKC3X0KChJRoMyn}{! z$FC!0K-0QGWjZ?vxb=5?VA!Qyt%1$#q4GqQbthhwi7iCZTbv*ix~c|Gjxy<$^84vH z#6ms&DSxrj7gk!53u4)8IZApkuFN$KJ?*2X+g3X1`^gH&8#yeHaCu94*_-Kv*G^Ir zml~LmmU9HG<*tR^!_a^TjMgvp#=n%wfy-VOg>>AKA#}k(4@34xx01Ye(hd#I>3Jgg z_NPwiUj^k`CI<(BFkY$P9rCsEQq;FmL`u{1P<=s%i5f~nbaCpVsuNWb^1cqe@2UmM zOjQ*&sMaL5Uunc!Dx2n7?;FAv;xT%hq#lFApp@iVVROr1aGJ4g>s{=Ho3~{fh!}9B zeB+BT5Efx{`z5}!RD1bBo)Atf=xa4xEIhC=-hhT&cbF5@QQES2w*HI&LmB{xEPY0v zZ)47_gfA!m942?Zp%$3u%O7)Oe)B+OSf<-fB=-Ul))HW)cN3cKIp*#r3&sCI!iDc~ z>)@k6V5eV+I*WZ$F0~d!c)uXsOMh8-RpWZ!ZzBB6FqqHD&LQJDDl{?S)S<5>;{QD24?uiwE7jWGQVU0WE*?C6HcT&s-GYnQA zhjx=V>$>>5&I7?B>0p*c@8(?IQ_r+oBn$+POLGck+-=2ld`24vyUHEY!WR(h)A-8h z!AjRZ4TOb4$(17c&+xJjk}9r$&~Bd;Zp)29zC&Twsjl`U6+il*nryYV0kxaBsbEpfgwdI*+El2w z{0b9fdY4?VVGZRKDL+m$h4~r_^A+x;c7{QQMewji>kcM1fCCJfEDO86-l-h_Vb))I zH6MG}GxR;XVE|F$z-)81o|_7WT*ZPS%U zN%iD~o{-(XBERNJuPT#A9dYLpgTj#GCaXLBSExp=P(bnmE^gMRTZSAmW|KmCmN7H$(_=BPb#I<{+`%kF~2JDz@z0?9HP10pT{uLLFjm zU$LZO4v`Gb&#R^J6)SI@F=ReIV!6+xz#J3&7{boZLDKk$ z`Qg?pOgi#9jTbR%Q*GbMFUP$(5fVPpW|@LjBiT3l_BdT_ky^oLE7jmbGh*lb}xRmn7Nk_m9Rp;o@-1(zU$IviQrF+x=kt`dBd&c{-90KN$OX z&F8YTy`v2TJ0m+TvIbu9(xyG&&KwzGuP=8M-!Ml?jZs|mpw|E| z!#GWbiqh=UFREjAA@F_d>7zH$4v0u9He|ue;^(B+#4Kvfm1l4Nl59 z>96Kn2evEB;E&=(lOTX|2ON^cR23k=qWaze#NA*Z{oppRm%I2N9awBY*-)r+Ry6U| zH}uCCAZRP>A0Tgr*?_+j9#%_3jOES%PpKXDP?L4--IMc|*(J3pJvH;vBuJu|m@|il zt(W+7M5iP>d}aWEH=ya}d@pNck4WaD6r1=QHLgc~&BcIB8zpEVWaDtv_c`qVZ4Y23 zk168CA0RDFC~wi7eGQr{zMmkJUw&A%JYv2<#hlcNO|R9Meab*>(2BN139p+IEhIGV zeX;C2{bmIU(>O1oI((KM$EbGmNaG!Y=zI~)+N<{jzjRgh7D1=^OMp~~z`xo}TSV;z ztPK^(10bPnPYyebImPIR_g#2m2sb1w%t~I? zjOgiVqZcSl?tHE0I)bBHjsK=s3eb;~FZ`{l?>B6dHXLJM9oL6{ChB6i7#*siu&cWw zKb0WG=qg4M_^Mhw&~F#0IQbf~{g_zhmG0iXhyrw~zw5lXlxs+xr3P8fGVZY|gCUu= z1Ox*Kl3|5X5?ej(l;EwYG7}vwqS=1^jO43d12p>J9rAFc^5Al*shu)ATKJW1DAwRz zha6z;4Aky6ZbP}PecV|bPO$gXFm>IqxD^Pb2z~r!KGLJfARs_lqF&DJp44xb?#-NW zY6?+?-wqApc?e1K8Y%OdUc_+F)DX39Z6+E(qIx=~Yp`~?X}~f@VeeyzaaG?HbJGKu z>S^7JX5(&OG7jYJ)CkuS0)9F-I$+|*&aZI0M8|^}!3hYY;Gu;L`k@+DH^#%s>xOcF z&1`>P)pkB-jg?&Fx()-l^CLuz@wasJk&DIJx+L5nu1vDVIniJSNMMNS0x5-ob(asn zhtp<1*VLAcrr~lL4SHmtz5Wh-mO*&f(Zxs|_MLb_Zn7G|ct&Dkts#lvhhM2R4{tJV zmS(1ve;f#tja-^~Qst|2L^l^J{lfSqpii@Kbo(j+Z|)cbG00(js4!vOoZ3@xoRJt9I(j4oJ@HqKj{uPD5-$qH*k z7d)yG=2oC9xN4K1VYUTizaN zpBs!twP*fzjs$yGn4E7}HllvkRk2UOJS(cF7ZjN*>1)cLi^b1yHLy0Y`LSAT)?>H& z24be-q$*oR1?%jcuO=|O{DI1DeAy-$M>X5Em&`A!Ex|lU;xK9O zRfupS_HLH_jPRSmrh>`TM6MaBwvuZUtt*rB`R2D)0NgKG1TaFWF}hd|ynVT!lj@%0 zebWzqDUY?`_!pr~9pXB`g%&cMzfmY;RN1_3uwYHMV#7XfmOVf8P9J z$+5QrfigzHy;1F5?j-S8b5{Hcr&o3RCuM+XH5^Cc++&0ocb!d1E+d-1tQ&SwkS0D- zqA@R*0vAMXigq4d?=*F-sP3eR91b>yxB+OcSlUIcX#6(>!JCeU^F?Nl1Pj-TN?3ILOhZyX_V4hz1o9A7my| zyX-(3=~8cdbVk#ozf1_<#f^L3bqxn~qf1KQ%yOXE$l4vafH`EJuG99jbjO8C;y=2Yt z(njGe0JWmsLozMCNv^Lb0vW-wN2({qKt}Z7#`hgJy}y+nj$C|)v5QZuSQ~kORZjE$ z46d=qd6kWThX&y7Hk}v#{U<$_LS+(>!qt}}3Eejb{w*^<`@V}#oh|MgGdV>>uE+L&ckGMq2VZL)0r}jEl$W@b5?R}v_}uSq-G`TNc8?E=OnUuA z%;qgCKE^EK#-b*vHQ&W5lfQ{f`sv?f&5v3xMWM(!O#YsFKD-IGX_BpATjEaa#Wwjm z6wB&rli%gM?PF(pt3a0Nn}aogac8!)#kTsK4@AJ)Bi3@7BQXFwZHf^1&LfT)FKr-y z_G5G{|KSVw*F$@gJpiQH{@J0G|Gfr95Z8rstkGrk!iQl3LQI^lo8rAk(Kdf&+~?Hc zVcQ(|J)J>Q*Qd>CYPF@>$I7jh@jpdE-J6eIzAdbxjj1cozMXKnAv^2Zw7PKj<4{X` z$<^cn26z78k(}lX^$GpGT=20y0Uch;%vGolq|9u$^Aj9yjCWiGnJ~wA(ZD@ddp0p9 zCR>SqDTWm+F!Pkg=;h5;!U;;HC=WbW8Ac%hgegFB?B^;y2op ze3Ay*2`Uy=^jXSyAl@B>(!Tk5&o=b-WwqKHpV@vZFi6(LjZU z8`QhbWG9x;lRr7x$$BOR3-b+OU6onjCru*W95zwvF_P9N5F`=KF>@q|<2m5v>g4{Z zU3odkhx2Ni)E_~`o#pZ#sCVH9R1U~=ughXFuJ|xmQl^heKNX*6lp1@x<(}STBTuFc zb!4XQ@+HP@%y-tQJ`b8|6|%qa8fX-?2jJb7PM3kd3UYR_Lri4-p=>m}+D}f|u| + + + + + #da532c + + + diff --git a/freescout-dist/public/css/bootstrap-rtl.css b/freescout-dist/public/css/bootstrap-rtl.css new file mode 100644 index 0000000..bdb5c53 --- /dev/null +++ b/freescout-dist/public/css/bootstrap-rtl.css @@ -0,0 +1,1473 @@ +html { + direction: rtl; +} +body { + direction: rtl; +} +.flip.text-left { + text-align: right; +} +.flip.text-right { + text-align: left; +} +.list-unstyled { + padding-right: 0; + padding-left: initial; +} +.list-inline { + padding-right: 0; + padding-left: initial; + margin-right: -5px; + margin-left: 0; +} +dd { + margin-right: 0; + margin-left: initial; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: right; + clear: right; + text-align: left; + } + .dl-horizontal dd { + margin-right: 180px; + margin-left: 0; + } +} +blockquote { + border-right: 5px solid #eeeeee; + border-left: 0; +} +.blockquote-reverse, +blockquote.pull-left { + padding-left: 15px; + padding-right: 0; + border-left: 5px solid #eeeeee; + border-right: 0; + text-align: left; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: right; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + left: 100%; + right: auto; +} +.col-xs-pull-11 { + left: 91.66666667%; + right: auto; +} +.col-xs-pull-10 { + left: 83.33333333%; + right: auto; +} +.col-xs-pull-9 { + left: 75%; + right: auto; +} +.col-xs-pull-8 { + left: 66.66666667%; + right: auto; +} +.col-xs-pull-7 { + left: 58.33333333%; + right: auto; +} +.col-xs-pull-6 { + left: 50%; + right: auto; +} +.col-xs-pull-5 { + left: 41.66666667%; + right: auto; +} +.col-xs-pull-4 { + left: 33.33333333%; + right: auto; +} +.col-xs-pull-3 { + left: 25%; + right: auto; +} +.col-xs-pull-2 { + left: 16.66666667%; + right: auto; +} +.col-xs-pull-1 { + left: 8.33333333%; + right: auto; +} +.col-xs-pull-0 { + left: auto; + right: auto; +} +.col-xs-push-12 { + right: 100%; + left: 0; +} +.col-xs-push-11 { + right: 91.66666667%; + left: 0; +} +.col-xs-push-10 { + right: 83.33333333%; + left: 0; +} +.col-xs-push-9 { + right: 75%; + left: 0; +} +.col-xs-push-8 { + right: 66.66666667%; + left: 0; +} +.col-xs-push-7 { + right: 58.33333333%; + left: 0; +} +.col-xs-push-6 { + right: 50%; + left: 0; +} +.col-xs-push-5 { + right: 41.66666667%; + left: 0; +} +.col-xs-push-4 { + right: 33.33333333%; + left: 0; +} +.col-xs-push-3 { + right: 25%; + left: 0; +} +.col-xs-push-2 { + right: 16.66666667%; + left: 0; +} +.col-xs-push-1 { + right: 8.33333333%; + left: 0; +} +.col-xs-push-0 { + right: auto; + left: 0; +} +.col-xs-offset-12 { + margin-right: 100%; + margin-left: 0; +} +.col-xs-offset-11 { + margin-right: 91.66666667%; + margin-left: 0; +} +.col-xs-offset-10 { + margin-right: 83.33333333%; + margin-left: 0; +} +.col-xs-offset-9 { + margin-right: 75%; + margin-left: 0; +} +.col-xs-offset-8 { + margin-right: 66.66666667%; + margin-left: 0; +} +.col-xs-offset-7 { + margin-right: 58.33333333%; + margin-left: 0; +} +.col-xs-offset-6 { + margin-right: 50%; + margin-left: 0; +} +.col-xs-offset-5 { + margin-right: 41.66666667%; + margin-left: 0; +} +.col-xs-offset-4 { + margin-right: 33.33333333%; + margin-left: 0; +} +.col-xs-offset-3 { + margin-right: 25%; + margin-left: 0; +} +.col-xs-offset-2 { + margin-right: 16.66666667%; + margin-left: 0; +} +.col-xs-offset-1 { + margin-right: 8.33333333%; + margin-left: 0; +} +.col-xs-offset-0 { + margin-right: 0%; + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: right; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + left: 100%; + right: auto; + } + .col-sm-pull-11 { + left: 91.66666667%; + right: auto; + } + .col-sm-pull-10 { + left: 83.33333333%; + right: auto; + } + .col-sm-pull-9 { + left: 75%; + right: auto; + } + .col-sm-pull-8 { + left: 66.66666667%; + right: auto; + } + .col-sm-pull-7 { + left: 58.33333333%; + right: auto; + } + .col-sm-pull-6 { + left: 50%; + right: auto; + } + .col-sm-pull-5 { + left: 41.66666667%; + right: auto; + } + .col-sm-pull-4 { + left: 33.33333333%; + right: auto; + } + .col-sm-pull-3 { + left: 25%; + right: auto; + } + .col-sm-pull-2 { + left: 16.66666667%; + right: auto; + } + .col-sm-pull-1 { + left: 8.33333333%; + right: auto; + } + .col-sm-pull-0 { + left: auto; + right: auto; + } + .col-sm-push-12 { + right: 100%; + left: 0; + } + .col-sm-push-11 { + right: 91.66666667%; + left: 0; + } + .col-sm-push-10 { + right: 83.33333333%; + left: 0; + } + .col-sm-push-9 { + right: 75%; + left: 0; + } + .col-sm-push-8 { + right: 66.66666667%; + left: 0; + } + .col-sm-push-7 { + right: 58.33333333%; + left: 0; + } + .col-sm-push-6 { + right: 50%; + left: 0; + } + .col-sm-push-5 { + right: 41.66666667%; + left: 0; + } + .col-sm-push-4 { + right: 33.33333333%; + left: 0; + } + .col-sm-push-3 { + right: 25%; + left: 0; + } + .col-sm-push-2 { + right: 16.66666667%; + left: 0; + } + .col-sm-push-1 { + right: 8.33333333%; + left: 0; + } + .col-sm-push-0 { + right: auto; + left: 0; + } + .col-sm-offset-12 { + margin-right: 100%; + margin-left: 0; + } + .col-sm-offset-11 { + margin-right: 91.66666667%; + margin-left: 0; + } + .col-sm-offset-10 { + margin-right: 83.33333333%; + margin-left: 0; + } + .col-sm-offset-9 { + margin-right: 75%; + margin-left: 0; + } + .col-sm-offset-8 { + margin-right: 66.66666667%; + margin-left: 0; + } + .col-sm-offset-7 { + margin-right: 58.33333333%; + margin-left: 0; + } + .col-sm-offset-6 { + margin-right: 50%; + margin-left: 0; + } + .col-sm-offset-5 { + margin-right: 41.66666667%; + margin-left: 0; + } + .col-sm-offset-4 { + margin-right: 33.33333333%; + margin-left: 0; + } + .col-sm-offset-3 { + margin-right: 25%; + margin-left: 0; + } + .col-sm-offset-2 { + margin-right: 16.66666667%; + margin-left: 0; + } + .col-sm-offset-1 { + margin-right: 8.33333333%; + margin-left: 0; + } + .col-sm-offset-0 { + margin-right: 0%; + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: right; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + left: 100%; + right: auto; + } + .col-md-pull-11 { + left: 91.66666667%; + right: auto; + } + .col-md-pull-10 { + left: 83.33333333%; + right: auto; + } + .col-md-pull-9 { + left: 75%; + right: auto; + } + .col-md-pull-8 { + left: 66.66666667%; + right: auto; + } + .col-md-pull-7 { + left: 58.33333333%; + right: auto; + } + .col-md-pull-6 { + left: 50%; + right: auto; + } + .col-md-pull-5 { + left: 41.66666667%; + right: auto; + } + .col-md-pull-4 { + left: 33.33333333%; + right: auto; + } + .col-md-pull-3 { + left: 25%; + right: auto; + } + .col-md-pull-2 { + left: 16.66666667%; + right: auto; + } + .col-md-pull-1 { + left: 8.33333333%; + right: auto; + } + .col-md-pull-0 { + left: auto; + right: auto; + } + .col-md-push-12 { + right: 100%; + left: 0; + } + .col-md-push-11 { + right: 91.66666667%; + left: 0; + } + .col-md-push-10 { + right: 83.33333333%; + left: 0; + } + .col-md-push-9 { + right: 75%; + left: 0; + } + .col-md-push-8 { + right: 66.66666667%; + left: 0; + } + .col-md-push-7 { + right: 58.33333333%; + left: 0; + } + .col-md-push-6 { + right: 50%; + left: 0; + } + .col-md-push-5 { + right: 41.66666667%; + left: 0; + } + .col-md-push-4 { + right: 33.33333333%; + left: 0; + } + .col-md-push-3 { + right: 25%; + left: 0; + } + .col-md-push-2 { + right: 16.66666667%; + left: 0; + } + .col-md-push-1 { + right: 8.33333333%; + left: 0; + } + .col-md-push-0 { + right: auto; + left: 0; + } + .col-md-offset-12 { + margin-right: 100%; + margin-left: 0; + } + .col-md-offset-11 { + margin-right: 91.66666667%; + margin-left: 0; + } + .col-md-offset-10 { + margin-right: 83.33333333%; + margin-left: 0; + } + .col-md-offset-9 { + margin-right: 75%; + margin-left: 0; + } + .col-md-offset-8 { + margin-right: 66.66666667%; + margin-left: 0; + } + .col-md-offset-7 { + margin-right: 58.33333333%; + margin-left: 0; + } + .col-md-offset-6 { + margin-right: 50%; + margin-left: 0; + } + .col-md-offset-5 { + margin-right: 41.66666667%; + margin-left: 0; + } + .col-md-offset-4 { + margin-right: 33.33333333%; + margin-left: 0; + } + .col-md-offset-3 { + margin-right: 25%; + margin-left: 0; + } + .col-md-offset-2 { + margin-right: 16.66666667%; + margin-left: 0; + } + .col-md-offset-1 { + margin-right: 8.33333333%; + margin-left: 0; + } + .col-md-offset-0 { + margin-right: 0%; + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: right; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + left: 100%; + right: auto; + } + .col-lg-pull-11 { + left: 91.66666667%; + right: auto; + } + .col-lg-pull-10 { + left: 83.33333333%; + right: auto; + } + .col-lg-pull-9 { + left: 75%; + right: auto; + } + .col-lg-pull-8 { + left: 66.66666667%; + right: auto; + } + .col-lg-pull-7 { + left: 58.33333333%; + right: auto; + } + .col-lg-pull-6 { + left: 50%; + right: auto; + } + .col-lg-pull-5 { + left: 41.66666667%; + right: auto; + } + .col-lg-pull-4 { + left: 33.33333333%; + right: auto; + } + .col-lg-pull-3 { + left: 25%; + right: auto; + } + .col-lg-pull-2 { + left: 16.66666667%; + right: auto; + } + .col-lg-pull-1 { + left: 8.33333333%; + right: auto; + } + .col-lg-pull-0 { + left: auto; + right: auto; + } + .col-lg-push-12 { + right: 100%; + left: 0; + } + .col-lg-push-11 { + right: 91.66666667%; + left: 0; + } + .col-lg-push-10 { + right: 83.33333333%; + left: 0; + } + .col-lg-push-9 { + right: 75%; + left: 0; + } + .col-lg-push-8 { + right: 66.66666667%; + left: 0; + } + .col-lg-push-7 { + right: 58.33333333%; + left: 0; + } + .col-lg-push-6 { + right: 50%; + left: 0; + } + .col-lg-push-5 { + right: 41.66666667%; + left: 0; + } + .col-lg-push-4 { + right: 33.33333333%; + left: 0; + } + .col-lg-push-3 { + right: 25%; + left: 0; + } + .col-lg-push-2 { + right: 16.66666667%; + left: 0; + } + .col-lg-push-1 { + right: 8.33333333%; + left: 0; + } + .col-lg-push-0 { + right: auto; + left: 0; + } + .col-lg-offset-12 { + margin-right: 100%; + margin-left: 0; + } + .col-lg-offset-11 { + margin-right: 91.66666667%; + margin-left: 0; + } + .col-lg-offset-10 { + margin-right: 83.33333333%; + margin-left: 0; + } + .col-lg-offset-9 { + margin-right: 75%; + margin-left: 0; + } + .col-lg-offset-8 { + margin-right: 66.66666667%; + margin-left: 0; + } + .col-lg-offset-7 { + margin-right: 58.33333333%; + margin-left: 0; + } + .col-lg-offset-6 { + margin-right: 50%; + margin-left: 0; + } + .col-lg-offset-5 { + margin-right: 41.66666667%; + margin-left: 0; + } + .col-lg-offset-4 { + margin-right: 33.33333333%; + margin-left: 0; + } + .col-lg-offset-3 { + margin-right: 25%; + margin-left: 0; + } + .col-lg-offset-2 { + margin-right: 16.66666667%; + margin-left: 0; + } + .col-lg-offset-1 { + margin-right: 8.33333333%; + margin-left: 0; + } + .col-lg-offset-0 { + margin-right: 0%; + margin-left: 0; + } +} +caption { + text-align: right; +} +th { + text-align: right; +} +@media screen and (max-width: 767px) { + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-right: 0; + border-left: initial; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-left: 0; + border-right: initial; + } +} +.radio label, +.checkbox label { + padding-right: 20px; + padding-left: initial; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + margin-right: -20px; + margin-left: auto; +} +.radio-inline, +.checkbox-inline { + padding-right: 20px; + padding-left: 0; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-right: 10px; + margin-left: 0; +} +.has-feedback .form-control { + padding-left: 42.5px; + padding-right: 12px; +} +.form-control-feedback { + left: 0; + right: auto; +} +@media (min-width: 768px) { + .form-inline label { + padding-right: 0; + padding-left: initial; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + margin-right: 0; + margin-left: auto; + } +} +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: left; + } +} +.form-horizontal .has-feedback .form-control-feedback { + left: 15px; + right: auto; +} +.caret { + margin-right: 2px; + margin-left: 0; +} +.dropdown-menu { + right: 0; + left: auto; + float: left; + text-align: right; +} +.dropdown-menu.pull-right { + left: 0; + right: auto; + float: right; +} +.dropdown-menu-right { + left: auto; + right: 0; +} +.dropdown-menu-left { + left: 0; + right: auto; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + left: auto; + right: 0; + } + .navbar-right .dropdown-menu-left { + left: 0; + right: auto; + } +} +.btn-group > .btn, +.btn-group-vertical > .btn { + float: right; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-right: -1px; + margin-left: 0px; +} +.btn-toolbar { + margin-right: -5px; + margin-left: 0px; +} +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: right; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-right: 5px; + margin-left: 0px; +} +.btn-group > .btn:first-child { + margin-right: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn-group { + float: right; +} +.btn-group.btn-group-justified > .btn, +.btn-group.btn-group-justified > .btn-group { + float: none; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child > .btn:last-child, +.btn-group > .btn-group:first-child > .dropdown-toggle { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group > .btn-group:last-child > .btn:first-child { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn .caret { + margin-right: 0; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-right: 0; +} +.input-group .form-control { + float: right; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.input-group-addon:first-child { + border-left: 0px; + border-right: 1px solid; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.input-group-addon:last-child { + border-left-width: 1px; + border-left-style: solid; + border-right: 0px; +} +.input-group-btn > .btn + .btn { + margin-right: -1px; + margin-left: auto; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-left: -1px; + margin-right: auto; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + margin-right: -1px; + margin-left: auto; +} +.nav { + padding-right: 0; + padding-left: initial; +} +.nav-tabs > li { + float: right; +} +.nav-tabs > li > a { + margin-left: auto; + margin-right: -2px; + border-radius: 4px 4px 0 0; +} +.nav-pills > li { + float: right; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-right: 2px; + margin-left: auto; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-right: 0; + margin-left: auto; +} +.nav-justified > .dropdown .dropdown-menu { + right: auto; +} +.nav-tabs-justified > li > a { + margin-left: 0; + margin-right: auto; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-radius: 4px 4px 0 0; + } +} +@media (min-width: 768px) { + .navbar-header { + float: right; + } +} +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; +} +.navbar-brand { + float: right; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-right: -15px; + margin-left: auto; + } +} +.navbar-toggle { + float: left; + margin-left: 15px; + margin-right: auto; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 25px 5px 15px; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: right; + } + .navbar-right { + float: left !important; + } + .navbar-left { + float: right !important; + } + .navbar-nav > li { + float: right !important; + } +} +@media (min-width: 768px) { + .navbar-left.flip { + float: right !important; + } + .navbar-right:last-child { + margin-left: -15px; + margin-right: auto; + } + .navbar-right.flip { + float: left !important; + margin-left: -15px; + margin-right: auto; + } + .navbar-right .dropdown-menu { + left: 0; + right: auto; + } +} +@media (min-width: 768px) { + .navbar-text { + float: right; + } + .navbar-text.navbar-right:last-child { + margin-left: 0; + margin-right: auto; + } +} +.pagination { + padding-right: 0; +} +.pagination > li > a, +.pagination > li > span { + float: right; + margin-right: -1px; + margin-left: 0px; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + margin-right: -1px; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.pager { + padding-right: 0; + padding-left: initial; +} +.pager .next > a, +.pager .next > span { + float: left; +} +.pager .previous > a, +.pager .previous > span { + float: right; +} +.nav-pills > li > a > .badge { + margin-left: 0px; + margin-right: 3px; +} +.list-group-item > .badge { + float: left; +} +.list-group-item > .badge + .badge { + margin-left: 5px; + margin-right: auto; +} +.alert-dismissable, +.alert-dismissible { + padding-left: 35px; + padding-right: 15px; +} +.alert-dismissable .close, +.alert-dismissible .close { + right: auto; + left: -21px; +} +.progress-bar { + float: right; +} +.media > .pull-left { + margin-right: 10px; +} +.media > .pull-left.flip { + margin-right: 0; + margin-left: 10px; +} +.media > .pull-right { + margin-left: 10px; +} +.media > .pull-right.flip { + margin-left: 0; + margin-right: 10px; +} +.media-right, +.media > .pull-right { + padding-right: 10px; + padding-left: initial; +} +.media-left, +.media > .pull-left { + padding-left: 10px; + padding-right: initial; +} +.media-list { + padding-right: 0; + padding-left: initial; + list-style: none; +} +.list-group { + padding-right: 0; + padding-left: initial; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-right-radius: 3px; + border-top-left-radius: 0; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-left-radius: 3px; + border-top-right-radius: 0; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; + border-top-right-radius: 0; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; + border-top-left-radius: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-right: 0; + border-left: none; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: none; + border-left: 0; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object { + right: 0; + left: auto; +} +.close { + float: left; +} +.modal-footer { + text-align: left; +} +.modal-footer.flip { + text-align: right; +} +.modal-footer .btn + .btn { + margin-left: auto; + margin-right: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-right: -1px; + margin-left: auto; +} +.modal-footer .btn-block + .btn-block { + margin-right: 0; + margin-left: auto; +} +.popover { + left: auto; + text-align: right; +} +.popover.top > .arrow { + right: 50%; + left: auto; + margin-right: -11px; + margin-left: auto; +} +.popover.top > .arrow:after { + margin-right: -10px; + margin-left: auto; +} +.popover.bottom > .arrow { + right: 50%; + left: auto; + margin-right: -11px; + margin-left: auto; +} +.popover.bottom > .arrow:after { + margin-right: -10px; + margin-left: auto; +} +.carousel-control { + right: 0; + bottom: 0; +} +.carousel-control.left { + right: auto; + left: 0; + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0%), color-stop(rgba(0, 0, 0, 0.0001) 100%)); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); +} +.carousel-control.right { + left: auto; + right: 0; + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0%), color-stop(rgba(0, 0, 0, 0.5) 100%)); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + right: auto; + margin-right: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + left: auto; + margin-left: -10px; +} +.carousel-indicators { + right: 50%; + left: 0; + margin-right: -30%; + margin-left: 0; + padding-left: 0; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: 0; + margin-right: -15px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-left: 0; + margin-right: -15px; + } + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } +} +.pull-right.flip { + float: left !important; +} +.pull-left.flip { + float: right !important; +} diff --git a/freescout-dist/public/css/bootstrap.css b/freescout-dist/public/css/bootstrap.css new file mode 100644 index 0000000..ed7fead --- /dev/null +++ b/freescout-dist/public/css/bootstrap.css @@ -0,0 +1,6831 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + /*font: inherit;*/ + color: inherit; + + font-size: 13px; + font-weight: 400; + line-height: 18px; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../../fonts/glyphicons/glyphicons-halflings-regular.eot'); + src: url('../../fonts/glyphicons/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../../fonts/glyphicons/glyphicons-halflings-regular.woff2') format('woff2'), url('../../fonts/glyphicons/glyphicons-halflings-regular.woff') format('woff'), url('../../fonts/glyphicons/glyphicons-halflings-regular.ttf') format('truetype'), url('../../fonts/glyphicons/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\002a"; +} +.glyphicon-plus:before { + content: "\002b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Liberation Sans","Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13.4px; + line-height: 1.6; + color: #394956; + /*background-color: #f4f5f5;*/ + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #0078D7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 700; + line-height: 1.22em; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 24px; +} +h3, +.h3 { + font-size: 16px; +} +h4, +.h4 { + font-size: 15px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + padding: .2em; + background-color: #fcf8e3; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777; +} +.text-primary { + color: #0078D7; +} +a.text-primary:hover, +a.text-primary:focus { + color: #286090; +} +.text-success { + color: #6ac27b; +} +a.text-success:hover, +a.text-success:focus { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover, +a.text-info:focus { + color: #245269; +} +.text-warning { + color: #b37100; +} +a.text-warning:hover, +a.text-warning:focus { + color: #66512c; +} +.text-danger { + color: #d9534f; +} +a.text-danger:hover, +a.text-danger:focus { + color: #d9534f; +} +.bg-primary { + color: #fff; + background-color: #0078D7; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #286090; +} +.bg-success { + background-color: #deffde; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f5e2e1; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +/*blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +}*/ +blockquote { + padding: 0px 13px; + margin: 0; + border-left: 2px solid #e3e8eb; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } + .navbar .container { + width: 100%; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } + .navbar .container { + width: 100%; + } +} +@media (min-width: 1200px) { + .container { + /*width: 1170px;*/ + width:100% + } + .navbar .container { + width: 100%; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #deffde; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f5e2e1; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +.table-responsive { + min-height: .01%; + overflow-x: auto; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + /*font-weight: bold;*/ +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; +} +.form-control { + display: block; + width: 100%; + height: 28px; + padding: 4px 6px 4px 8px; + font-size: 13px; + line-height: 18px; + color: #555; + background-color: #fff; + background-image: none; + /*border: 1px solid #ccc;*/ + border: 1px solid #c1cbd4; + border-radius: 3px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); +} +.form-control::-moz-placeholder { + /*color: #999;*/ + color: #b4c0ca; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #b4c0ca; +} +.form-control::-webkit-input-placeholder { + color: #b4c0ca; +} +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #eee; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { + line-height: 34px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + min-height: 34px; + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.form-group-sm select.form-control { + height: 30px; + line-height: 30px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + min-height: 32px; + padding: 6px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.form-group-lg select.form-control { + height: 46px; + line-height: 46px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 46px; + min-height: 38px; + padding: 11px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #6ac27b; +} +.has-success .form-control { + border-color: #6ac27b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #6ac27b; + background-color: #deffde; + border-color: #6ac27b; +} +.has-success .form-control-feedback { + color: #6ac27b; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #b37100; +} +.has-warning .form-control { + border-color: #b37100; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #b37100; + background-color: #fcf8e3; + border-color: #b37100; +} +.has-warning .form-control-feedback { + color: #b37100; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f5e2e1; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + /*color: #737373;*/ + color: #93a1af; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + padding-top: 4px; + margin-bottom: 0; + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 11px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + font-size: 12px; + } +} +.btn { + display: inline-block; + padding: 0 14px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + /*height: 30px;*/ + line-height: 28px; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 2px; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #c4c8cc; +} +.btn-default:focus, +.btn-default.focus { + border-color: #a5a9ac; + background-color: #f1f3f5; + outline: none!important; +} +.btn-default:hover { + background-color: #f1f3f5; + border-color: #a5a9ac; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333; + background-color: #f4f5f5; + border-color: #a5a9ac; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #333; + background-color: #f4f5f5; + border-color: #a5a9ac; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus { + background-color: #f4f5f5; + border-color: #a5a9ac; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #0078d7; + border-color: #0078d7; +} +.btn-primary:focus, +.btn-primary.focus { + color: #fff; + background-color: #0068bd; + border-color: #0068bd; + outline: none; +} +.btn-primary:hover { + color: #fff; + background-color: #0068bd; + border-color: #0068bd; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #fff; + background-color: #104a7d; + border-color: #104a7d; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #fff; + background-color: #104a7d; + border-color: #104a7d; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus { + background-color: #0078D7; + border-color: #2e6da4; +} +.btn-primary .badge { + color: #0078D7; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:focus, +.btn-success.focus { + color: #fff; + background-color: #449d44; + border-color: #255625; +} +.btn-success:hover { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #fff; + background-color: #398439; + border-color: #255625; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:focus, +.btn-info.focus { + color: #fff; + background-color: #31b0d5; + border-color: #1b6d85; +} +.btn-info:hover { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #fff; + background-color: #269abc; + border-color: #1b6d85; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:focus, +.btn-warning.focus { + color: #fff; + background-color: #ec971f; + border-color: #985f0d; +} +.btn-warning:hover { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #fff; + background-color: #d58512; + border-color: #985f0d; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:focus, +.btn-danger.focus { + color: #fff; + background-color: #c9302c; + border-color: #761c19; +} +.btn-danger:hover { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #fff; + background-color: #ac2925; + border-color: #761c19; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: normal; + color: #0078D7; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + /*color: #777;*/ + color: #7abce4; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-transition-duration: .35s; + -o-transition-duration: .35s; + transition-duration: .35s; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 13px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border-left: 1px solid #dedede; + border-right: 1px solid #dedede; + border-bottom: 1px solid #dedede; + -webkit-box-shadow:0 0 10px rgba(0,0,0,.2); + box-shadow:0 0 10px rgba(0,0,0,.2); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #4f5d6b; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + /*color: #3197d6;*/ + text-decoration: none; + /*background-color: #eaeaea;*/ + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + /*background-color: #eaeaea;*/ + background-color: #deecf9; + color: #005a9e; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn, +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-left-radius: 2px; + border-top-right-radius: 2px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 2px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group .form-control:focus { + z-index: 3; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 2px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + z-index: 2; + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + /*background-color: #eee;*/ +} +.nav > li.disabled > a { + color: #777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eee; + border-color: #0078D7; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eee #eee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #0078D7; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + /*margin-bottom: 20px;*/ + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + height: 50px; + padding: 14px 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + /*background-color: #005689;*/ + background-color: #0078d7; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #2a3b47; +} +.navbar-default .navbar-brand:hover/*, +.navbar-default .navbar-brand:focus*/ { + color: #5e5e5e; + background-color: #104a7d; +} +.navbar-default .navbar-brand.active { + background-color: #104a7d; +} +.navbar-default .navbar-brand.active:hover/*, +.navbar-default .navbar-brand.active:focus*/ { + background-color: #104a7d; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + /*color: #83b8db;*/ + color: #fff; + font-size: 14px; +} +.navbar-default .navbar-nav > li > a:hover/*, +.navbar-default .navbar-nav > li > a:focus*/ { + color: #fff; + /*background-color: transparent;*/ + background-color: #104a7d; +} + +/*.navbar-default .navbar-nav > li > a:focus { + background-color: transparent; +}*/ + +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + /*color: #555; + background-color: #e7e7e7;*/ + color: #fff; + background-color: #104a7d; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #83b8db; +} +.navbar-default .navbar-toggle:hover +/*.navbar-default .navbar-toggle:focus*/ { + background-color:transparent; + border-color:#fff +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #83b8db; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #fff; + /*background-color: transparent;*/ + background-color: #104a7d; +} +@media (max-width: 767px) { + + .navbar-default .navbar-nav > .open > a, + .navbar-default .navbar-nav > .open > a:hover, + .navbar-default .navbar-nav > .open > a:focus { + color: #4f5d6b; + background-color: #f5f5f5; + } + /* selected item */ + .navbar-default .navbar-nav > .active > a, + .navbar-default .navbar-nav > .active > a:hover, + .navbar-default .navbar-nav > .active > a:focus { + color: #4f5d6b; + background-color: #deecf9; + } + .navbar-default .navbar-nav > li > a:hover, + .navbar-default .navbar-nav > li > a:focus { + color: #4f5d6b; + background-color: #f5f5f5; + } + /* submenu */ + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #4f5d6b; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #4f5d6b; + background-color: #f5f5f5; + } + /* do not highlight selected item on hover */ + .navbar-default .navbar-nav .open .dropdown-menu > li.active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li.active > a:focus { + background-color: #deecf9; + } + /*.navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + }*/ +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-default .btn-link { + color: #777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #9d9d9d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #9d9d9d; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.navbar-inverse .btn-link { + color: #9d9d9d; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #0078D7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eee; + border-color: #ddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #0078D7; + border-color: #0078D7; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .2em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #909eab; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #0078D7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 5px 5px 3px; + font-size: 10px; + font-weight: bold; + line-height: 10px; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: middle; + background-color: #9598a1; + border-radius: 2px; + font-style: normal; + text-transform: uppercase; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #0078D7; + background-color: #fff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron, +.container-fluid .jumbotron { + padding-right: 15px; + padding-left: 15px; + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: border .2s ease-in-out; + -o-transition: border .2s ease-in-out; + transition: border .2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #0078D7; +} +.thumbnail .caption { + padding: 9px; + color: #333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + /*border-radius: 4px;*/ +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + /*color: #6ac27b; + background-color: #dff0d8;*/ + color: #6ac27b; + background-color: #deffde; + border-color: #6ac27b; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #b37100; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #d9534f; + background-color: #f5e2e1; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); +} +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #0078D7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-object.img-thumbnail { + max-width: none; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +a.list-group-item, +button.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #eee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #0078D7; + border-color: #0078D7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef; +} +.list-group-item-success { + color: #6ac27b; + background-color: #deffde; +} +a.list-group-item-success, +button.list-group-item-success { + color: #6ac27b; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #6ac27b; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #6ac27b; + border-color: #6ac27b; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info, +button.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #b37100; + background-color: #fcf8e3; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #b37100; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #b37100; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #b37100; + border-color: #b37100; +} +.list-group-item-danger { + color: #a94442; + background-color: #f5e2e1; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + /*border-radius: 4px;*/ + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-right: 15px; + padding-left: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #c1cbd4; +} +.panel-default > .panel-heading { + /*color: #86929e;*/ + color: #253540; + font-size: 16px; + /*line-height: 30px;*/ + line-height: 29px; + background-color: #deecf9; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #0078D7; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #0078D7; + border-color: #0078D7; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #0078D7; +} +.panel-primary > .panel-heading .badge { + color: #0078D7; + background-color: #fff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #0078D7; +} +.panel-success { + border-color: #6ac27b; +} +.panel-success > .panel-heading { + color: #6ac27b; + background-color: #deffde; + border-color: #6ac27b; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #6ac27b; +} +.panel-success > .panel-heading .badge { + color: #deffde; + background-color: #6ac27b; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #6ac27b; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #b37100; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #b37100; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f5e2e1; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f5e2e1; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #337ab7; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5; +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; + font-weight: normal; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + filter: alpha(opacity=0); + opacity: 0; + + line-break: auto; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + right: 5px; + bottom: 0; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + + line-break: auto; +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + color: #5F7282; + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform .6s ease-in-out; + -o-transition: -o-transform .6s ease-in-out; + transition: transform .6s ease-in-out; + + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + left: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + left: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + left: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + background-color: rgba(0, 0, 0, 0); + filter: alpha(opacity=50); + opacity: .5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: 0; + opacity: .9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + margin-top: -10px; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + font-family: serif; + line-height: 1; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -10px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -10px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -10px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-header:before, +.modal-header:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-header:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/freescout-dist/public/css/fonts.css b/freescout-dist/public/css/fonts.css new file mode 100644 index 0000000..7998114 --- /dev/null +++ b/freescout-dist/public/css/fonts.css @@ -0,0 +1,64 @@ +/* Liberation Sans */ +@font-face { + font-family: 'Liberation Sans'; + src: url('../../fonts/liberation-sans/LiberationSans-Bold-webfont.eot'); + src: url('../../fonts/liberation-sans/LiberationSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), + local('Liberation Sans Bold'), + local('LiberationSans-Bold'), + url('../../fonts/liberation-sans/LiberationSans-Bold-webfont.woff') format('woff'), + url('../../fonts/liberation-sans/LiberationSans-Bold-webfont.ttf') format('truetype'), + url('../../fonts/liberation-sans/LiberationSans-Bold-webfont.svg#liberation_sansbold') format('svg'); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: 'Liberation Sans'; + src: url('../../fonts/liberation-sans/LiberationSans-BoldItalic-webfont.eot'); + src: url('../../fonts/liberation-sans/LiberationSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), + local('Liberation Sans Bold Italic'), + local('LiberationSans-BoldItalic'), + url('../../fonts/liberation-sans/LiberationSans-BoldItalic-webfont.woff') format('woff'), + url('../../fonts/liberation-sans/LiberationSans-BoldItalic-webfont.ttf') format('truetype'), + url('../../fonts/liberation-sans/LiberationSans-BoldItalic-webfont.svg#liberation_sansbold_italic') format('svg'); + font-weight: 700; + font-style: italic; +} +@font-face { + font-family: 'Liberation Sans'; + src: url('../../fonts/liberation-sans/LiberationSans-Italic-webfont.eot'); + src: url('../../fonts/liberation-sans/LiberationSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), + local('Liberation Sans Italic'), + local('LiberationSans-Italic'), + url('../../fonts/liberation-sans/LiberationSans-Italic-webfont.woff') format('woff'), + url('../../fonts/liberation-sans/LiberationSans-Italic-webfont.ttf') format('truetype'), + url('../../fonts/liberation-sans/LiberationSans-Italic-webfont.svg#liberation_sansitalic') format('svg'); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: 'Liberation Sans'; + src: url('../../fonts/liberation-sans/LiberationSans-Regular-webfont.eot'); + src: url('../../fonts/liberation-sans/LiberationSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), + local('Liberation Sans Regular'), + local('LiberationSans-Regular'), + url('../../fonts/liberation-sans/LiberationSans-Regular-webfont.woff') format('woff'), + url('../../fonts/liberation-sans/LiberationSans-Regular-webfont.ttf') format('truetype'), + url('../../fonts/liberation-sans/LiberationSans-Regular-webfont.svg#liberation_sansregular') format('svg'); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: 'Yekan'; + src: url('../../fonts/yekan/Yekan.eot'); + /* IE9 Compat Modes */ + src: url('../../fonts/yekan/Yekan.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('../../fonts/yekan/Yekan.woff2') format('woff2'), /* Modern Browsers */ + url('../../fonts/yekan/Yekan.woff') format('woff'), /* Modern Browsers */ + url('../../fonts/yekan/Yekan.otf') format('opentype'), /* Open Type Font */ + url('../../fonts/yekan/Yekan.ttf') format('truetype'); + /* Safari, Android, iOS */ + font-weight: normal; + font-style: normal; + text-rendering: optimizeLegibility; + font-display: auto; +} diff --git a/freescout-dist/public/css/magic-check.css b/freescout-dist/public/css/magic-check.css new file mode 100644 index 0000000..4e8b56b --- /dev/null +++ b/freescout-dist/public/css/magic-check.css @@ -0,0 +1 @@ +@keyframes a{0%{border-color:silver}to{border-color:#3e97eb}}.magic-checkbox,.magic-radio{position:absolute;display:none}.magic-checkbox[disabled],.magic-radio[disabled]{cursor:not-allowed}.magic-checkbox+label,.magic-radio+label{position:relative;display:block;padding-left:30px;cursor:pointer;vertical-align:middle}.magic-checkbox+label:hover:before,.magic-radio+label:hover:before{animation-duration:.4s;animation-fill-mode:both;animation-name:a}.magic-checkbox+label:before,.magic-radio+label:before{position:absolute;top:0;left:0;display:inline-block;width:20px;height:20px;content:'';border:1px solid silver}.magic-checkbox+label:after,.magic-radio+label:after{position:absolute;display:none;content:''}.magic-checkbox[disabled]+label,.magic-radio[disabled]+label{cursor:not-allowed;color:#e4e4e4}.magic-checkbox[disabled]+label:after,.magic-checkbox[disabled]+label:before,.magic-checkbox[disabled]+label:hover,.magic-radio[disabled]+label:after,.magic-radio[disabled]+label:before,.magic-radio[disabled]+label:hover{cursor:not-allowed}.magic-checkbox[disabled]+label:hover:before,.magic-radio[disabled]+label:hover:before{border:1px solid #e4e4e4;animation-name:none}.magic-checkbox[disabled]+label:before,.magic-radio[disabled]+label:before{border-color:#e4e4e4}.magic-checkbox:checked+label:before,.magic-radio:checked+label:before{animation-name:none}.magic-checkbox:checked+label:after,.magic-radio:checked+label:after{display:block}.magic-radio+label:before{border-radius:50%}.magic-radio+label:after{top:6px;left:6px;width:8px;height:8px;border-radius:50%;background:#3e97eb}.magic-radio:checked+label:before{border:1px solid #3e97eb}.magic-radio:checked[disabled]+label:before{border:1px solid #c9e2f9}.magic-radio:checked[disabled]+label:after{background:#c9e2f9}.magic-checkbox+label:before{border-radius:3px}.magic-checkbox+label:after{top:2px;left:7px;box-sizing:border-box;width:6px;height:12px;transform:rotate(45deg);border:2px solid #fff;border-top:0;border-left:0}.magic-checkbox:checked+label:before{border:#3e97eb;background:#3e97eb}.magic-checkbox:checked[disabled]+label:before{border:#c9e2f9;background:#c9e2f9} \ No newline at end of file diff --git a/freescout-dist/public/css/select2/select2.css b/freescout-dist/public/css/select2/select2.css new file mode 100644 index 0000000..447b2b8 --- /dev/null +++ b/freescout-dist/public/css/select2/select2.css @@ -0,0 +1,484 @@ +.select2-container { + box-sizing: border-box; + display: inline-block; + margin: 0; + position: relative; + vertical-align: middle; } + .select2-container .select2-selection--single { + box-sizing: border-box; + cursor: pointer; + display: block; + height: 28px; + user-select: none; + -webkit-user-select: none; } + .select2-container .select2-selection--single .select2-selection__rendered { + display: block; + padding-left: 8px; + padding-right: 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + .select2-container .select2-selection--single .select2-selection__clear { + position: relative; } + .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered { + padding-right: 8px; + padding-left: 20px; } + .select2-container .select2-selection--multiple { + box-sizing: border-box; + cursor: pointer; + display: block; + min-height: 32px; + user-select: none; + -webkit-user-select: none; } + .select2-container .select2-selection--multiple .select2-selection__rendered { + display: inline-block; + overflow: hidden; + padding-left: 8px; + text-overflow: ellipsis; + white-space: nowrap; } + .select2-container .select2-search--inline { + float: left; } + .select2-container .select2-search--inline .select2-search__field { + box-sizing: border-box; + border: none; + font-size: 100%; + margin-top: 5px; + padding: 0; } + .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button { + -webkit-appearance: none; } + +.select2-dropdown { + background-color: white; + border: 1px solid #aaa; + border-radius: 4px; + box-sizing: border-box; + display: block; + position: absolute; + left: -100000px; + width: 100%; + z-index: 1051; } + +.select2-results { + display: block; } + +.select2-results__options { + list-style: none; + margin: 0; + padding: 0; } + +.select2-results__option { + padding: 6px; + user-select: none; + -webkit-user-select: none; } + .select2-results__option[aria-selected] { + cursor: pointer; } + +.select2-container--open .select2-dropdown { + left: 0; } + +.select2-container--open .select2-dropdown--above { + border-bottom: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +.select2-container--open .select2-dropdown--below { + border-top: none; + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.select2-search--dropdown { + display: block; + padding: 4px; } + .select2-search--dropdown .select2-search__field { + padding: 4px; + width: 100%; + box-sizing: border-box; } + .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button { + -webkit-appearance: none; } + .select2-search--dropdown.select2-search--hide { + display: none; } + +.select2-close-mask { + border: 0; + margin: 0; + padding: 0; + display: block; + position: fixed; + left: 0; + top: 0; + min-height: 100%; + min-width: 100%; + height: auto; + width: auto; + opacity: 0; + z-index: 99; + background-color: #fff; + filter: alpha(opacity=0); } + +.select2-hidden-accessible { + border: 0 !important; + clip: rect(0 0 0 0) !important; + height: 1px !important; + margin: -1px !important; + overflow: hidden !important; + padding: 0 !important; + position: absolute !important; + width: 1px !important; } + +.select2-container--default .select2-selection--single { + background-color: #fff; + border: 1px solid #aaa; + border-radius: 4px; } + .select2-container--default .select2-selection--single .select2-selection__rendered { + color: #444; + line-height: 28px; } + .select2-container--default .select2-selection--single .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; } + .select2-container--default .select2-selection--single .select2-selection__placeholder { + color: #999; } + .select2-container--default .select2-selection--single .select2-selection__arrow { + height: 26px; + position: absolute; + top: 1px; + right: 1px; + width: 20px; } + .select2-container--default .select2-selection--single .select2-selection__arrow b { + border-color: #888 transparent transparent transparent; + border-style: solid; + border-width: 5px 4px 0 4px; + height: 0; + left: 50%; + margin-left: -4px; + margin-top: -2px; + position: absolute; + top: 50%; + width: 0; } + +.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear { + float: left; } + +.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow { + left: 1px; + right: auto; } + +.select2-container--default.select2-container--disabled .select2-selection--single { + background-color: #eee; + cursor: default; } + .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear { + display: none; } + +.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-color: transparent transparent #888 transparent; + border-width: 0 4px 5px 4px; } + +.select2-container--default .select2-selection--multiple { + background-color: white; + border: 1px solid #aaa; + border-radius: 4px; + cursor: text; } + .select2-container--default .select2-selection--multiple .select2-selection__rendered { + box-sizing: border-box; + list-style: none; + margin: 0; + padding: 0 5px; + width: 100%; } + .select2-container--default .select2-selection--multiple .select2-selection__rendered li { + list-style: none; } + .select2-container--default .select2-selection--multiple .select2-selection__placeholder { + color: #999; + margin-top: 5px; + float: left; } + .select2-container--default .select2-selection--multiple .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; + margin-top: 5px; + margin-right: 10px; } + .select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #e4e4e4; + border: 1px solid #aaa; + border-radius: 4px; + cursor: default; + float: left; + margin-right: 5px; + margin-top: 5px; + padding: 0 5px; } + .select2-container--default .select2-selection--multiple .select2-selection__choice__remove { + color: #999; + cursor: pointer; + display: inline-block; + font-weight: bold; + margin-right: 2px; } + .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover { + color: #333; } + +.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline { + float: right; } + +.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + margin-left: 5px; + margin-right: auto; } + +.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { + margin-left: 2px; + margin-right: auto; } + +.select2-container--default.select2-container--focus .select2-selection--multiple { + border: solid black 1px; + outline: 0; } + +.select2-container--default.select2-container--disabled .select2-selection--multiple { + background-color: #eee; + cursor: default; } + +.select2-container--default.select2-container--disabled .select2-selection__choice__remove { + display: none; } + +.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple { + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +.select2-container--default .select2-search--dropdown .select2-search__field { + border: 1px solid #aaa; } + +.select2-container--default .select2-search--inline .select2-search__field { + background: transparent; + border: none; + outline: 0; + box-shadow: none; + -webkit-appearance: textfield; } + +.select2-container--default .select2-results > .select2-results__options { + max-height: 200px; + overflow-y: auto; } + +.select2-container--default .select2-results__option[role=group] { + padding: 0; } + +.select2-container--default .select2-results__option[aria-disabled=true] { + color: #999; } + +.select2-container--default .select2-results__option[aria-selected=true] { + background-color: #ddd; } + +.select2-container--default .select2-results__option .select2-results__option { + padding-left: 1em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__group { + padding-left: 0; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option { + margin-left: -1em; + padding-left: 2em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -2em; + padding-left: 3em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -3em; + padding-left: 4em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -4em; + padding-left: 5em; } + .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { + margin-left: -5em; + padding-left: 6em; } + +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #5897fb; + color: white; } + +.select2-container--default .select2-results__group { + cursor: default; + display: block; + padding: 6px; } + +.select2-container--classic .select2-selection--single { + background-color: #f7f7f7; + border: 1px solid #aaa; + border-radius: 4px; + outline: 0; + background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%); + background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%); + background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); } + .select2-container--classic .select2-selection--single:focus { + border: 1px solid #5897fb; } + .select2-container--classic .select2-selection--single .select2-selection__rendered { + color: #444; + line-height: 28px; } + .select2-container--classic .select2-selection--single .select2-selection__clear { + cursor: pointer; + float: right; + font-weight: bold; + margin-right: 10px; } + .select2-container--classic .select2-selection--single .select2-selection__placeholder { + color: #999; } + .select2-container--classic .select2-selection--single .select2-selection__arrow { + background-color: #ddd; + border: none; + border-left: 1px solid #aaa; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + height: 26px; + position: absolute; + top: 1px; + right: 1px; + width: 20px; + background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%); + background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%); + background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); } + .select2-container--classic .select2-selection--single .select2-selection__arrow b { + border-color: #888 transparent transparent transparent; + border-style: solid; + border-width: 5px 4px 0 4px; + height: 0; + left: 50%; + margin-left: -4px; + margin-top: -2px; + position: absolute; + top: 50%; + width: 0; } + +.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear { + float: left; } + +.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow { + border: none; + border-right: 1px solid #aaa; + border-radius: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + left: 1px; + right: auto; } + +.select2-container--classic.select2-container--open .select2-selection--single { + border: 1px solid #5897fb; } + .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow { + background: transparent; + border: none; } + .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b { + border-color: transparent transparent #888 transparent; + border-width: 0 4px 5px 4px; } + +.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single { + border-top: none; + border-top-left-radius: 0; + border-top-right-radius: 0; + background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%); + background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%); + background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); } + +.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single { + border-bottom: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%); + background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%); + background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); } + +.select2-container--classic .select2-selection--multiple { + background-color: white; + border: 1px solid #aaa; + border-radius: 4px; + cursor: text; + outline: 0; } + .select2-container--classic .select2-selection--multiple:focus { + border: 1px solid #5897fb; } + .select2-container--classic .select2-selection--multiple .select2-selection__rendered { + list-style: none; + margin: 0; + padding: 0 5px; } + .select2-container--classic .select2-selection--multiple .select2-selection__clear { + display: none; } + .select2-container--classic .select2-selection--multiple .select2-selection__choice { + background-color: #e4e4e4; + border: 1px solid #aaa; + border-radius: 4px; + cursor: default; + float: left; + margin-right: 5px; + margin-top: 5px; + padding: 0 5px; } + .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove { + color: #888; + cursor: pointer; + display: inline-block; + font-weight: bold; + margin-right: 2px; } + .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover { + color: #555; } + +.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + float: right; } + +.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + margin-left: 5px; + margin-right: auto; } + +.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { + margin-left: 2px; + margin-right: auto; } + +.select2-container--classic.select2-container--open .select2-selection--multiple { + border: 1px solid #5897fb; } + +.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple { + border-top: none; + border-top-left-radius: 0; + border-top-right-radius: 0; } + +.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple { + border-bottom: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + +.select2-container--classic .select2-search--dropdown .select2-search__field { + border: 1px solid #aaa; + outline: 0; } + +.select2-container--classic .select2-search--inline .select2-search__field { + outline: 0; + box-shadow: none; } + +.select2-container--classic .select2-dropdown { + background-color: white; + border: 1px solid transparent; } + +.select2-container--classic .select2-dropdown--above { + border-bottom: none; } + +.select2-container--classic .select2-dropdown--below { + border-top: none; } + +.select2-container--classic .select2-results > .select2-results__options { + max-height: 200px; + overflow-y: auto; } + +.select2-container--classic .select2-results__option[role=group] { + padding: 0; } + +.select2-container--classic .select2-results__option[aria-disabled=true] { + color: grey; } + +.select2-container--classic .select2-results__option--highlighted[aria-selected] { + background-color: #3875d7; + color: white; } + +.select2-container--classic .select2-results__group { + cursor: default; + display: block; + padding: 6px; } + +.select2-container--classic.select2-container--open .select2-dropdown { + border-color: #5897fb; } diff --git a/freescout-dist/public/css/select2/select2.min.css b/freescout-dist/public/css/select2/select2.min.css new file mode 100644 index 0000000..76de04d --- /dev/null +++ b/freescout-dist/public/css/select2/select2.min.css @@ -0,0 +1 @@ +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;height:1px !important;margin:-1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} diff --git a/freescout-dist/public/css/style-rtl.css b/freescout-dist/public/css/style-rtl.css new file mode 100644 index 0000000..c70de4c --- /dev/null +++ b/freescout-dist/public/css/style-rtl.css @@ -0,0 +1,500 @@ +body.locale-fa { + font-family: yekan; +} + +.conv-next-prev { + vertical-align: middle; + margin-left: 0!important; + margin-right: 4px !important; + margin-top: -4px!important; +} + +.conv-next-prev a { + float: left; +} + +.conv-info .btn-group button.conv-info-val { + padding-right: 8px; + padding-left: 22px; +} + +.conv-info .btn-group button.conv-info-val .caret { + right: auto; + left: 8px; +} + +#conv-layout-customer .customer-photo-container { + float: left; +} + +.customer-extra { + position: relative; +} + +.customer-social-profiles { + right: 92px; + left: auto; +} + +.accordion .panel-title > a .caret { + float: left; +} + +ul.sidebar-block-list { + padding-right: 0; +} + +.sidebar-block-list .glyphicon { + margin-left: 4px; + margin-right: 0; +} + +.thread-photo { + left: 0; +} + +.sidebar-menu>li>a { + padding: 11px 50px 9px 20px; + border-radius: 17px 0 0 17px; +} + +.sidebar-menu>li { + border-radius: 17px 0 0 17px; +} + +.sidebar-menu .glyphicon { + left: auto; + right: 20px; + margin-right: 0; + margin-left: 10px; +} + +.icon-info { + margin-left: 0; + margin-right: 7px; +} + +.checkbox, +.radio { + padding-left: 7px; + padding-right: 22px; +} + +.navbar-nav .photo-sm { + margin-right: 0; + margin-left: 2px; +} + +.person-photo-auto { + float: right; +} + +.sidebar-buttons .dropdown-menu i { + margin-right: 0; + margin-left: 3px; +} + +.dropdown-menu.with-icons .glyphicon, +.dash-card-footer .dropdown-menu .glyphicon, +.sidebar-buttons .dropdown-menu .glyphicon { + right: -5px; + left: auto; +} + +.sidebar-buttons .btn-group a.btn { + border-left: none; + border-right: 1px solid #d3dae0; + border-radius: 0 14px 14px 0 !important; +} + +.sidebar-buttons>a.btn { + border-radius: 14px 0 0 14px !important; +} + +.btn-group-rounded .btn:last-child, +.btn-group-rounded *:last-child .btn { + border-radius: 14px 0 0 14px !important; +} + +.btn-group-rounded .btn:first-child, +.btn-group-rounded *:first-child .btn { + border-left: none; + border-right: 1px solid #d3dae0; + border-radius: 0 14px 14px 0 !important; +} + +.note-actions-select { + float: left; +} + +.select2-container--default .select2-selection--multiple .select2-selection__choice__remove { + float: left !important; + margin-right: 5px; + margin-left: 0; +} + +.select2-container--default .select2-selection--multiple .select2-selection__choice { + float: right; + margin: 3px 0 0 5px; +} + +#permissions-fields .control-label { + text-align: right; +} + +.sidebar-2col { + padding-right: 0; + padding-left: 12px; +} + +.conv-new .conv-info { + float: left; + margin: 0px 0px 0 20px; +} + +.conv-new #conv-toolbar h2 { + padding: 0 20px 0 20px; + float: right; +} + +.note-editor.note-frame .note-statusbar.note-statusbar-toolbar { + text-align: left; +} + +.btn-group-send { + margin-left: 0; + margin-right: 12px; +} + +.note-actions { + float: left; +} + +.note-popover .popover-content, +.panel-heading.note-toolbar { + padding: 0 5px 4px 0 !important; +} + +.note-popover .popover-content>.btn-group, +.panel-heading.note-toolbar>.btn-group { + margin-top: 4px !important; + margin-right: 0 !important; + margin-left: 5px !important; +} + +#conv-layout-header { + float: right; + padding-left: 280px; + padding-right: 0; + width: 100%; +} + +#conv-layout-customer { + left: 0 !important; + right: inherit; + height: 100%; +} + +#conv-layout-main { + padding-left: 280px; + padding-right: 0; +} + +.btn-send-menu { + border-right-color: #659ac8; +} + +.table.table-conversations th.conv-customer { + padding-right: 0; +} + +.table.table-conversations th { + padding: 4px 13px 1px 14px; +} + +thead .conv-cb label { + top: -9px; + right: -12px; +} + +.magic-checkbox+label, +.magic-radio+label { + padding-right: 30px; + padding-left: 0; +} + +.table-conversations .conv-number i { + float: right; + margin-left: 2px; + margin-right: 0; +} + +.pull-right { + float: left !important; +} + +.conv-sort { + position: relative; + left: -1px; + top: 1px; +} + +#conversations-bulk-actions .btn .caret { + margin-right: 2px; +} + +.navbar-toggle { + left: 0; +} + +.navbar-header .web-notifications { + left: 71px; + right: inherit; +} + +.btn-sidebar { + margin: 8px 20px 0 0; +} + +.table-conversations .conv-fader { + left: 0; + right: inherit; + background: linear-gradient(to left, rgba(255, 255, 255, 0), #fff); +} + +.sidebar-menu-toggle { + float: right; +} + +#conv-layout-customer { + height: 0; +} + +.dropdown-with-icons .glyphicon { + left: 0; +} + +@media (max-width: 582px) { + .note-actions, + .note-actions-select { + float: none; + } +} + +@media (max-width: 991px) { + .sidebar-buttons { + left: 11px; + right: auto; + } + .sidebar-title-extra { + margin-left: 120px; + margin-right: 50px; + } + .sidebar-menu.active { + transform: translate(0, 0); + left: auto; + } + .sidebar-menu>li>a { + border-radius: 0; + } + .sidebar-buttons .dropdown-menu { + left: 0 !important; + right: inherit; + } + .sidebar-title { + margin: 0 50px 13px 20px; + } +} + +@media (max-width: 1000px) { + .conv-customer a, + .conv-date a { + padding: 11px 0 0 8px !important; + float: left; + } + .conv-subject { + position: absolute !important; + top: 24px; + padding-right: 2px !important; + padding-left: 20px !important; + } + .conv-thread-count { + position: absolute; + left: 0; + right: inherit; + } + .conv-thread-count a, + .conv-thread-count .conv-star { + padding-left: 6px !important; + padding-right: inherit; + } + .conv-totals { + text-align: right; + } +} + +@media (max-width:1100px) { + #conv-layout-header { + padding-left: 0; + } + #conv-layout-main { + padding-left: 0 !important; + padding-right: inherit; + } + #conv-layout-customer { + padding-left: 280px; + padding-right: 0; + } +} + +.sidebar-2col{ + padding: 0 0 0 12px ; +} + +.sidebar-menu-toggle { + float: right; +} + +.pull-right { + float: left !important; +} + +a.btn.btn-bordered { + margin-right: 14px; +} + +a.btn.btn-bordered.btn-sidebar { + margin: 8px 20px 0 0; +} + +.card{ + float: right; + margin: 0 0 18px 18px; +} + +.card > h4 { + margin: 2px 66px 4px 0; +} + +.card > p { + margin: 0 66px 0 0; +} + +i.card-avatar{ + float: right ; +} + +.user-admin-badge { + left: 7px !important; + right: auto; +} + +#permissions-fields .control-label { + text-align: right; +} + +.module-card { + margin: 0 0 18px 18px; +} + +.module-card img{ + float: right; +} + +.module-card .label { + float: left; +} + +.module-wrap { + margin: 0 144px 0 0; +} + +table.dataTable thead .sorting_asc { + background-position: left !important; +} + +table.dataTable thead .sorting_desc { + background-position: left !important; +} + +table.dataTable thead .sorting_asc { + background-position: left !important; +} + +table.dataTable thead .sorting { + background-position: left !important; +} + +.dataTables_wrapper .dataTables_filter { + float: left !important; +} + +.dataTables_wrapper .dataTables_filter input { + margin-right: 0.5em; +} + +.dataTables_wrapper .dataTables_filter input { + margin-left: 0em !important; +} + +.dataTables_wrapper .dataTables_length { + float: right !important; +} + +.dataTables_wrapper .dataTables_paginate { + float: left !important; +} + +.dataTables_wrapper .dataTables_info { + float: right !important; +} + +.margin-left-10 { + margin-right: 10px !important; + margin-left: 0 !important; +} + +@media (max-width: 582px){ + .module-card img { + float: none; + width: 128px; + margin: 0 auto; + display: block; + } + .module-wrap { + margin-right: 0; + } + .module-card h4 { + margin-top: 10px; + text-align: center; + } + .module-card .label { + display: block; + margin-top: 3px; + float: none; + } + .card, .dash-card { + width: 100%; + margin: 0 0 20px 0; + } +} + +@media screen and (max-width: 640px){ + .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter { + float: none !important; + text-align: center !important; + } + .dataTables_wrapper .dataTables_filter { + margin-top: 0.5em !important; + } +} + +@media screen and (max-width: 767px){ + .dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_paginate { + float: none !important; + text-align: center !important; + } + .dataTables_wrapper .dataTables_paginate { + margin-top: 0.5em !important; + } +} diff --git a/freescout-dist/public/css/style.css b/freescout-dist/public/css/style.css new file mode 100644 index 0000000..840d242 --- /dev/null +++ b/freescout-dist/public/css/style.css @@ -0,0 +1,4524 @@ +/** + * Custom styles + */ + +/** + * Layout + */ +body.user-is-guest { + background-color: #f8f9f9; +} +#app { + min-height: 100%; + overflow: hidden; +} +.footer { + text-align: center; + font-size: 12px; + /*position: absolute; + bottom: 0;*/ + color: #b4c0ca; + margin-bottom: 20px; + margin-top: 20px; + width: 100%; +} +.footer-hide .footer { + display: none; +} +.content { + margin-top: 20px; + /*padding-bottom: 60px;*/ + min-height: 400px; +} +.content-full { + margin-top: 0; +} +.content-full .container { + padding: 0; + width: 100% !important; + min-height: 415px; +} +.layout-2col { + display: flex; + background-color: #fff; + align-items: stretch; + -ms-flex: 1; + flex: 1; + -webkit-flex: 1; + justify-content: space-between; + min-height: calc((100vh) - (54px)); + width: 100%; +} +.sidebar-2col { + width: 260px; + /*background-color: #f4f5f5;*/ + background-color: #fff; + padding-right: 12px; +} +.content-2col { + background-color: #fff; + /*-webkit-box-shadow: -1px 0 0 #d6dde3, 1px 0 0 #d6dde3, 0 1px 0 #d6dde3; + box-shadow: -1px 0 0 #d6dde3, 1px 0 0 #d6dde3, 0 1px 0 #d6dde3;*/ + /*-webkit-box-shadow: -10px 0 0 rgba(0,0,0,.2), 10px 0 0 rgba(0,0,0,.2), 0 10px 0 rgba(0,0,0,.2); + box-shadow: -10px 0 0 rgba(0,0,0,.2), 10px 0 0 rgba(0,0,0,.2), 0 10px 0 rgba(0,0,0,.2); + border-right:1px solid #dedede; + border-bottom:1px solid #dedede; + border-left:1px solid #dedede;*/ + -webkit-box-shadow:0 0 10px rgba(0,0,0,.2); + box-shadow:0 0 10px rgba(0,0,0,.2); + box-sizing: border-box; + flex: 1; + flex-basis: auto; + -ms-flex: 1; + -webkit-flex: 1; + max-width: 100%; + min-width: 0; + position: relative; + z-index: 2; + /*overflow: hidden;*/ +} +.sidebar-title { + color: #2a3b47; + font-size: 20px; + font-weight: 400; + line-height: 24.4px; + margin: 16px 20px 6px; +} +.sidebar-title .dropdown-menu .glyphicon { + color: inherit; + left: 0; + left: 1px; +} +.dropdown-with-icons .glyphicon { + left: -5px; +} +.sidebar-title .dropdown-toggle { + cursor: pointer; +} +.sidebar-title-extra-value { + display: none; +} +.sidebar-title-email { + overflow: hidden!important; + text-overflow: ellipsis!important; + white-space: nowrap!important; + font-size: 13.4px; + color: #93a1af; + margin-top: 3px; + display: block; +} +.sidebar-menu { + margin-left: 0; + margin-bottom: 18px; + list-style: none; + padding: 0; +} +.sidebar-menu h3 { + margin: 0; +} +.sidebar-menu > li { + /*margin: 0 0 2px 0;*/ + position: relative; + line-height: 18px; + display: list-item; + text-align: -webkit-match-parent; + border-radius: 0 17px 17px 0; +} +.sidebar-menu > li.no-link { + padding: 9px 20px 9px 20px; +} +.sidebar-menu > li:hover { + background-color: #f1f3f5; +} +.sidebar-menu > li.no-link:hover { + background-color: transparent; +} +.sidebar-menu > li > a { + color: #4f5d6b; + display: block; + font-size: 14px; + line-height: 16px; + padding: 11px 20px 9px 50px; + position: relative; + word-wrap: break-word; + border-radius: 0 17px 17px 0; + height: 36px; +} +.sidebar-menu > li.menu-link > a { + color: #337ab7; +} +.sidebar-menu-noicons > li > a { + padding: 9px 20px 9px 20px; +} +.sidebar-menu > li.menu-padded > a { + padding-left: 30px; +} +.sidebar-menu > li > a:hover, +.sidebar-menu > li > a:focus { + text-decoration: none; + color: #394956; +} +.sidebar-menu > li.menu-link > a:hover, +.sidebar-menu > li.menu-link > a:focus { + color: #23527c; +} +.sidebar-menu > li > a:hover .glyphicon { + text-decoration: none; + color: #72808e; +} +.sidebar-menu .active a, +.sidebar-menu .active .glyphicon { + color: #005a9e !important; + font-weight: 700; + background-color: #deecf9; +} +.sidebar-menu .glyphicon { + color: #b5c1cc; + font-size: 16px; + left: 20px; + margin-right: 10px; + position: absolute; + top: 10px; + z-index: 9; +} +.sidebar-menu-toggle { + background: #fff; + border: 1px solid #d3dae0; + border-radius: 3px; + display: none; + height: 36px; + padding: 0 9px; + position: relative; + margin-left: 12px; + margin-right: 12px; + top: 7px; + width: 36px; + cursor: pointer; + float: left; +} +.sidebar-menu-toggle:hover { + background-color: #e6e5e5; +} +.sidebar-menu-toggle .icon-bar { + background-color: #72808e; + display: block; + height: 2px; + border-radius: 1px; + margin-top: 3px; + position: relative; + top: -2px; +} +.sidebar-menu-toggle.active { + background: #3197d6; + border-color: #3197d6; +} +.sidebar-menu-toggle.active .icon-bar { + background-color: #fff; +} +.sidebar-menu a.no-active { + color: #93a1af; +} +.sidebar-buttons { + padding-left: 15px; + padding-right: 5px; +} +.sidebar-buttons .btn { + font-size: 16px; +} +.sidebar-buttons .btn-group.open .btn.dropdown-toggle { + -webkit-box-shadow: none; + box-shadow: none; +} +.sidebar-buttons .btn-group .btn { + border-right: none; +} +.sidebar-buttons .btn i { + position: relative; + top: 3px; +} +.sidebar-buttons .dropdown-menu i { + margin-right: 3px; +} +.sidebar-buttons .btn-group a.btn { + /*background-color: #e0e0e0;*/ + /*border-right: 1px solid #dbdbdb;*/ + border-radius: 14px 0 0 14px; +} +.sidebar-buttons > a.btn { + /*width: 154px;*/ + /*margin-left: 0!important;*/ + border-radius: 0 14px 14px 0; +} +@media (max-width:991px) { + .layout-2col { + display: block; + align-items: stretch; + -ms-flex: 0; + flex: 0; + -webkit-flex: 0; + width: 100%; + } + .content-2col { + flex: 0; + -ms-flex: 0; + -webkit-flex: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .sidebar-2col { + border-bottom: 1px solid #cad3db; + width: 100%; + position: relative; + height: 51px; + } + .sidebar-no-height .sidebar-2col { + height: auto; + } + .sidebar-menu { + background-color: #f4f5f5; + border-radius: 0 4px 4px 0; + -webkit-box-shadow: 0 2px 6px rgba(179,190,195,.8); + box-shadow: 0 2px 6px rgba(179,190,195,.8); + left: -270px; + overflow-y: auto; + padding-top: 15px; + position: absolute; + max-height: 90vh; + max-width: 250px; + min-width: 225px; + top: 106%; + transition: transform .2s ease-in-out; + z-index: 110; + } + .sidebar-menu.active { + transform: translate(270px,0); + } + .sidebar-menu > li > a { + border-radius: 0; + } + .sidebar-menu-toggle { + display: block; + } + .sidebar-title { + padding: 13px 0 0 0; + margin: 0 20px 13px 50px; + white-space: nowrap; + } + .sidebar-title-extra { + padding-top: 5px; + margin-right: 120px; + white-space: nowrap; + overflow: hidden; + } + .sidebar-title-extra-value { + font-size: 14px; + font-weight: bold; + } + .sidebar-title-real { + display: block; + font-size: 13.4px; + line-height: 14.4px; + color: #4f5d6b; + overflow: hidden!important; + text-overflow: ellipsis!important; + white-space: nowrap!important; + } + .sidebar-title-real.mailbox-name .glyphicon { + top: 2px !important; + font-size: 13.4px !important; + } + .sidebar-title-email { + display: none; + } + .sidebar-title-extra-value { + display: block; + } + .sidebar-buttons { + position: absolute; + top: 10px; + right: 11px; + width: 50px; + padding: 0; + } + .sidebar-buttons.has-settings { + width: 100px; + } + .sidebar-buttons .dropdown-menu { + right: 0; + } +} + +/** + * Typography + */ +h1, h2, h3, h4, h5, h6 { + color: #2a3b47; +} + +/** + * Navbar + */ +.navbar-brand { + padding: 12px 15px 12px; + } + .navbar-brand-with-text { + text-overflow: ellipsis; + line-height: 24px; + overflow: hidden; + max-width: 100vw; + white-space: nowrap; + padding-right: 60px; + } +.navbar-brand.navbar-brand-with-text > img { + display: inline-block; +} +.navbar-brand-with-text:hover { + background-color: transparent !important; +} +.navbar-brand-with-text span { + color: #fff; + font-weight: bold; +} +a.navbar-brand.active img, +a.navbar-brand:hover img { + -webkit-filter: brightness(1.1); + filter: brightness(1.1); +} +.navbar-brand i { + color: #fff; +} +.dropdown-menu .dropdown-header { + font-size: 14px; +} +.navbar-default .navbar-nav > li > a.dropdown-toggle-icon { + font-size: 18px; +} +.navbar-default .navbar-nav > li > a.dropdown-toggle-account { + font-size: 17px; + padding-right: 7px; + padding-left: 10px; +} +.navbar-nav .dropdown-toggle:hover { + color: #fff; +} +.navbar-nav .dropdown-toggle .glyphicon { + top: 3px; +} +.navbar-nav .photo-sm { + margin-right: 2px; +} +.navbar-toggle { + position: absolute; + right: 0; +} +.navbar-default .navbar-toggle .icon-bar, +.navbar-default .navbar-toggle.collapsed:hover .icon-bar { + background-color: #fff; +} +.navbar-default .navbar-toggle.collapsed .icon-bar { + background-color: #fff; +} +.navbar-default .navbar-toggle, +.navbar-default .navbar-toggle.collapsed:hover, +.navbar-default .navbar-toggle:hover { + border-color: transparent; +} +.navbar-default .navbar-toggle.collapsed { + border-color: transparent; +} +.dropdown-menu>li>a { + padding-top: 7px; + padding-bottom: 7px; +} +.dropdown-menu a.disabled, +.dropdown-menu a.disabled:hover { + color: #a5b2bd; + background-color: transparent; +} +.nav-user { + font-size: 14px; +} +.form-nav-search { + padding: 1px 11px 4px; + width: 250px; +} +.form-nav-search .input-group { + padding-top: 3px; +} +/*.navbar-nav > li > .dropdown-menu { + margin-top: -10px; + border-radius: 3px; +}*/ +/*.navbar-nav.navbar-right > li > .dropdown-menu { + margin-top: -8px; +}*/ +.section-heading-right { + display: block; +} +@media (min-width: 768px) { + .navbar-nav > li > a.dropdown-toggle-icon { + padding-top: 14px; + padding-bottom: 14px; + } + .section-heading-right { + float: right; + } +} +@media (max-width:767px) { + .navbar-brand-with-text img + span { + display: none; + } + .navbar-collapse { + background-color:#fff; + } + .navbar-default .navbar-nav > li > a { + color: #4f5d6b; + } +} + +/** + * Forms + */ +input.disabled { + background-color: #eee; +} +.input-group-addon { + line-height: 11px; + background-color: transparent; +} +.input-group-flex { + display: flex; +} +.input-group-addon-grey { + background-color: #eee; +} +select.placeholdered { + color: #B4C0D4; +} +.required-asterisk:before { + content: "*"; + color: #d43f3a; + margin-left: -1px; +} +label.disabled { + color: #93a1af; +} +label.checkbox, +label.radio { + cursor: pointer; +} +select.form-control { + height: 28px; + line-height: 28px; +} +.input-sized-xs { + width: 87px; +} +.input-sized-sm { + width: 187px; +} +.input-sized { + width: 270px; +} +.input-sized-lg { + width: 320px; + max-width: 100%; +} +.control-label { + font-size: 13.4px; +} +.controls>.checkbox:first-child, +.controls>.radio:first-child { + padding-top: 5px; +} +.checkbox.inline, +.radio.inline { + display: inline-block; + margin-bottom: 0; + padding-top: 5px; + vertical-align: middle; + font-size: 13.4px; +} +.checkbox, +.radio { + min-height: 18px; + padding-left: 22px; + padding-right: 7px; + position: relative; +} +.input-group-btn .btn { + height: 28px; +} +.btn-sidebar { + margin: 8px 0 0 20px; +} +.btn-grey { + color: #fff; + /*background-color: #87939f; + border-color: #727f8b;*/ + background-color: #6b6b6b; + border-color: #585858; +} +.btn-grey:focus, +.btn-grey:hover { + color: #fff; + /*background-color: #64717b; + border-color: #4f5d69;*/ + background-color: #444444; + border-color: #292929; +} +.btn-lightgrey { + color: #fff; + /*background-color: #a5b2bd; + border-color: #8796a1;*/ + background-color: #8b98a6; + border-color: #69737d; +} +.btn-lightgrey:focus, +.btn-lightgrey:hover { + color: #fff; + /*background-color: #72808e; + border-color: #72808e;*/ + background-color: #747f8a; + border-color: #636c75; +} +.btn-success.btn-light { + background-color: #6ac27b; +} +.btn-success.btn-light:hover { + background-color: #51a862; +} +.btn-warning.btn-light { + background-color: #f2b661; +} +.btn-warning.btn-light:hover { + background-color: #ec971f; +} +.btn-danger.btn-light { + background-color: #de6864; +} +.btn-danger.btn-light:hover { + background-color: #c9302c; +} +.has-error.help-block { + color: #a94442; +} +.has-error .form-control.parsley-exclude { + border-color: #ccc; +} +.has-error .select2-container--default .select2-selection--multiple, +.has-error .select2-container--default.select2-container--focus .select2-selection--multiple { + border-color: #a94442; +} +.input-md { + height: 38px; + /*padding: 10px 16px;*/ + /*font-size: 18px;*/ + line-height: 18px; + /*border-radius: 3px;*/ +} +.btn .glyphicon { + top: 2px; +} +select.input-lg { + height: 38px; + line-height: 38px; +} +textarea.input-md, +select[multiple].input-md { + height: auto; +} +.btn-group-xs .btn-default { + color: #72808e; +} +.btn-group-xs .btn-default:hover { + color: #4f5d6b; +} +.btn-xs, .btn-group-xs > .btn { + padding: 1px 7px; +} +.btn-input-size { + padding: 3px 9px; + font-size: 12px; + line-height: 20px; + border-radius: 3px; +} +.form-help { + color: #93a1af; + display: block; + font-size: 13.4px; + margin: 4px 0 0; +} +.btn-group-rounded .btn:first-child, +.btn-group-rounded *:first-child .btn { + border-radius: 14px 0 0 14px; +} +.btn-group-rounded .btn:last-child, +.btn-group-rounded *:last-child .btn { + border-radius: 0 14px 14px 0; +} +.btn-group-rounded .btn:only-child, +.btn-group-rounded *:only-child .btn { + border-radius: 14px; +} +.btn-trans { + color: #93a1af; + background-color: transparent; + border-color: #d3dae0; +} +.btn-trans:hover { + color: #0078D7; +} +.btn-trans:focus { + -webkit-box-shadow: none; + box-shadow: none; + background-color: transparent; + color: #0078D7; +} +.btn-bordered { + color: #0078D7; + background-color: transparent; + border-color: #dedede; +} +.btn-bordered:hover, +.btn-bordered:focus { + color: #fff; + background-color: #0078D7; + border-color: #0078D7; +} +.btn-text { + color: #0078D7; + background-color: transparent; +} +.btn-text:hover, +.btn-text:focus { + color: #0068bd; +} +.btn-group.open .glyphicon.dropdown-toggle { + -webkit-box-shadow: none; + box-shadow: none; +} +.form-container { + margin-bottom: 25px; +} +.input-group .input-group-btn { + vertical-align: top; +} +@media (min-width: 435px) { + .input-sized-lg { + width: 387px; + } +} + +/** + * Cards + */ +.card { + border: 1px solid #dedede; + box-sizing: border-box; + display: block; + position: relative; + margin: 0 18px 18px 0; + padding: 14px; + width: 270px; + height: 82px; + float: left; + -webkit-box-shadow:0 0 5px rgba(0,0,0,.2); + box-shadow:0 0 5px rgba(0,0,0,.2); +} +a.card:active, +a.card:focus, +a.card:hover { + text-decoration: none; +} +a h4 { + color: #2a3b47; +} +.card > h4 { + font-size: 18px; + font-weight: 400; + margin: 2px 0 4px 66px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.card > p { + color: #93a1af; + font-size: 14px; + margin: 0 0 0 66px; +} +.card > img { + border: 1px solid #fff; + border-radius: 50%; + display: block; + float: left; + max-height: 50px; + max-width: 50px; +} +.card-avatar { + border-radius: 4px; + float: left; + line-height: 11px; + text-align: center; + width: 50px; + height: 50px; + line-height: 50px; + font-size: 18px; +} +.card-icon { + background-color: #c1cbd4; + border: 1px solid #fff; + border-radius: 50%; + color: #fff; + float: left; + text-align: center; + width: 50px; + height: 50px; + line-height: 49px; + font-size: 18px; +} +.person-photo-auto { + float: left; + line-height: 11px; + text-align: center; + font-size: 18px; +} +.card-avatar:before, +.person-photo-auto:before { + background: #c1cbd4; + color: #fff; + content: attr(data-initial); + border-radius: 50%; + display: block; + /*font-size: 18px;*/ + font-size: inherit; + font-style: normal; + font-weight: 300; + /*height: 50px; + line-height: 50px;*/ + line-height: inherit; + overflow: hidden; + position: relative; + text-transform: uppercase; + /*width: 50px;*/ +} +.photo-sm .person-photo-auto:before, +.photo-xs .person-photo-auto:before { + background: #a5b2bd; + color: #fff; + content: attr(data-initial); + border-radius: 50%; + display: block; + font-size: inherit; + font-style: normal; + font-weight: 300; + line-height: inherit; + overflow: hidden; + position: relative; + text-transform: uppercase; +} +.card.no-img h4, +.card.no-img p { + margin-left: 0; +} +/*.card-active { + border-left: 4px solid #5cb85c; +}*/ +.card-inactive { + /*border-left: 4px solid #93a1af;*/ + background-color: #f4f5f5; +} +.card .invite-state { + background-color: #f4f5f5; + border-radius: 100%; + color: #5cb85c; + display: block; + padding-top: 5px; + padding-left: 5px; + font-size: 14px; + height: 24px; + position: absolute; + left: 45px; + top: 49px; + width: 24px; + z-index: 1; +} +.card .invite-state.invited { + padding-top: 6px; + /*color: #5cb85c;*/ + color: #333; + font-size: 13px; +} +.card .invite-state.not-invited { + color: #d9534f; + padding-left: 5px; +} +.dash-cards { + box-sizing:border-box; + display:flex; + flex-wrap:wrap; +} +.dash-card { + float: left; + position: relative; + border: 1px solid #dedede; + border-radius: 15px; + /*border-left: 4px solid #5cb85c;*/ + /*-webkit-box-shadow: 0 1px 7px 0 rgba(0,0,0,.08); + box-shadow: 0 1px 7px 0 rgba(0,0,0,.08);*/ + -webkit-box-shadow:0 0 5px rgba(0,0,0,.2); + box-shadow:0 0 5px rgba(0,0,0,.2); + box-sizing: border-box; + min-height: 256px; + margin: 0 20px 20px 0; + width: 268px; +} +.dash-card-content { + padding-bottom: 60px; + padding-top: 15px; + border-radius: 15px; + overflow:hidden; +} +.dash-card-content h3 a, +.dash-card-content h3 a:hover, +.dash-card-content h3 a:focus { + color: #194c6e; +} +.dash-card-footer { + bottom: 19px; + left: 0; + padding: 0 18px; + position: absolute; + right: 0; + text-align: right; +} +.dash-card .btn-trans { + background-color: #f9f9f9; +} +.dash-card h3 { + padding-left: 20px; + padding-right: 20px; + margin-bottom: 8px; + margin-top: -15px; + padding-top: 13px; + margin-bottom: 0; + background-color: #eff1f4; +} +.dash-card a.dash-card-list-item { + color: #93a1af; +} +.dash-card a.dash-card-list-item:hover { + text-decoration: underline; +} +.dash-card-link { + padding-left: 20px; + padding-right: 20px; + margin-bottom: 10px; + padding-right: 20px; + padding-bottom: 7px; + background-color: #eff1f4; + border-bottom: 1px solid #d9e1e7; + min-height: 30px; +} +.dash-card-list-item { + font-size: 14px; + text-decoration: none!important; + padding-bottom: 5px; + padding-top: 5px; + padding-left: 20px; + padding-right: 20px; + margin: 0; + display: block; +} +.dash-card .waiting-since { + color: #93a1af; + font-size: 12.4px; + margin-left: 4px; +} +.dash-card-list-item:hover { + background-color: #f4f5f5; +} +.dash-card-list-item strong { + display: block; + float: right; +} +.dash-card-list a:nth-of-type(-n+2), +.dash-card-list a:nth-of-type(-n+2) strong { + color: #2a3b47; +} +.dash-card-list a:nth-of-type(n+3) strong { + font-weight: normal; +} +.dash-card-list a:nth-of-type(1) strong.has-value { + background-color: #85919e; + color: #fff; + padding: 0px 7px 0 7px !important; + border-radius: 18px; + text-align: center; + height: 20px; + margin-right: -7px !important; +} +.dash-card-inactive-content { + display: none; + padding: 8px 20px; +} +.dash-card-inactive-content .block-help { + font-size: 14px; +} +.dash-card-inactive-content .btn-link { + padding-left: 0; +} +.dash-card-inactive .dash-card-inactive-content { + display: block; +} +.dash-card-inactive .dash-card-list { + display: none; +} +.dash-card.dash-card-inactive { + /*border-left-color: #93a1af;*/ + background: #f4f5f5; +} +.dash-card-item-empty strong { + display: none; +} +.dash-card a.dash-card-item-empty, +.dash-card a.dash-card-item-empty:hover, +.dash-card a.dash-card-item-empty:focus { + color: #93a1af; +} + +@media (max-width:582px) { + .card, + .dash-card { + width: 100%; + margin: 0 0 20px 0; + } +} + +/** + * Editor + */ +.summernote-inservar { + max-width: 200px; +} +.note-editor.note-frame { + border-color: #dee4e9 !important; +} +.note-editor .btn-default { + color: #86929e; + background-color: #f9fafa; + border-color: #f9fafa; +} +.note-editor .btn-default.active, +.note-editor .btn-default:hover { + background-color: #e6e6e6; + border-color: #e6e6e6; + color: #72808e; +} +.note-editor .note-actions .btn-default.active, +.note-editor .note-actions .btn-default:hover { + background-color: transparent; + border-color: transparent; +} +.note-editor.panel { + margin: 0; +} +.note-btn, +.note-btn.btn.active, +.note-btn:focus { + outline: none!important; + -webkit-box-shadow: none; + box-shadow: none; +} +.note-btn i.glyphicon { + position: relative; + top: 2px; +} +.note-btn.btn-sm { + font-size: 14px; + line-height: 18px; + height: 30px; +} +.note-actions { + float: right; +} +.note-actions .note-btn-save-draft { + font-size: 16px; +} +.note-actions-select { + margin-top: 3px!important; + float: right; +} +.note-actions-select select { + color: #2a3b47; + border-color: #c1cbd4; + height: 24px; + line-height: 24px; + padding-top: 2px; +} +.note-actions-select .note-btn, +.note-actions-select .note-btn:hover { + background-color: transparent; + border-color: transparent; + padding-top: 3px; + cursor: default; + -webkit-box-shadow: none; + box-shadow: none; + outline: none; +} +.note-editor.note-frame .note-statusbar.note-statusbar-toolbar { + /*border-top: 1px solid #ddd;*/ + background-color: #f9fafa; + text-align: right; + color: #72808e; +} +.note-bottom-div { + margin: 0 4px; + color: #c1cbd4; + font-size: 9px; +} +.btn-group-send { + margin-left: 12px; + margin-top: -1px; + margin-bottom: -1px; +} +.btn-group-send .btn { + height: 38px; +} +.btn-send-menu { + padding-left: 8px; + padding-right: 8px; + font-size: 9px; + border-left-color: #659ac8; +} +.btn-send-text { + min-width: 94px; +} +.btn-reply-submit { + font-size: 13.4px; +} +.note-statusbar-toolbar select.form-control { + height: 24px; + line-height: 24px; + width: auto; + display: inline-block; + padding-top: 2px; + padding-bottom: 1px; +} +.note-editor .btn-default.text-success { + color: #53b961; +} +.draft-saved { + color: #a5b2bd; + font-size: 12.4px; + float: left; + position: relative; + top: 2px; +} +#editor_signature { + text-align: left; + font-size: 14px; + line-height: 20px; + background-color: #fff; + padding: 8px; + border-bottom: 1px solid #ddd; + color: #394956; +} +.note-toolbar .note-btn-group .note-btn { + border-radius: 0; +} +.note-toolbar .note-btn-group.open .note-btn { + border-color: #e6e6e6; +} +.note-toolbar.panel-heading { + background-color: #f9fafa; +} +.panel-heading.note-toolbar .note-btn-group .dropdown-menu { + min-width: 83px; + padding-left: 5px; +} +@media (max-width:390px) { + .note-bottom-div { + display: none; + } + .note-statusbar-toolbar select.form-control { + width: 85px; + } +} +@media (max-width:582px) { + .draft-saved { + display: none!important; + } + .note-actions, + .note-actions-select { + float: none; + } + .note-btn .glyphicon-paperclip { + font-size: 13px; + } + .btn-sm, .btn-group-sm > .btn { + padding: 5px 9px!important; + } + .panel-heading.note-toolbar>.btn-group { + margin-right: 1px!important; + } + .panel-heading.note-toolbar { + padding: 0 0 4px 1px!important; + } + .editor-btm-text { + display: none; + } +} + +/** + * Customer + */ +.customer-profile-menu { + position: absolute; + right: 20px; + top: 15px; + z-index: 10; + font-size: 16px; +} +.customer-profile-menu a { + color: #394956; +} +.customer-photo-container { + margin: 8px 0 0 12px; + /*margin: 0 0 0 12px;*/ + max-width: 86px; + position: relative; + height: 70px; +} +#conv-layout-customer .customer-photo-container { + margin-top: -53px; +} +.customer-photo { + border: 3px solid #fff; + border-radius: 50%; + box-shadow:0 1px 6px rgba(147,161,175,.5); + display: block; + max-height: 70px; + max-width: 70px; +} +.customer-name { + color: #2a3b47; + font-size: 17px; + line-height: 21px; +} +.customer-name:hover, +.customer-name:focus { + color: #2a3b47; +} +.customer-snippet h3 { + text-align: center; + word-wrap: break-word; + color: #253540; + font-size: 1.6em; + font-weight: 400; + line-height: 1.2em; + margin: 0 0 4px 0; +} +.customer-social-profiles { + position: absolute; + top: 0; + left: 92px; +} +.customer-social-profiles a { + font-size: 17px; + color: #b6bec6; +} +.customer-social-profiles a:hover { + color: #72808e; + text-decoration: none; +} +.profile-preview { + position: relative; +} +.profile-preview .customer-social-profiles { + top: 61px; +} +.profile-preview .customer-photo { + max-width: 100%; +} +.profile-preview .customer-data { + margin: 15px 0 0 0!important; +} +.customer-data { + padding: 0px 26px 0 12px; + margin-top: 15px; + margin-bottom: 15px; +} +.customer-contacts { + margin: 0; + padding: 0; +} +.customer-contacts li, +.customer-contacts li a { + /*overflow: hidden; + text-overflow: ellipsis;*/ + word-wrap: break-word; + overflow-wrap: break-word; + /*white-space: nowrap;*/ + display: block; + margin-top: 2px; + color: #93a1af; + line-height: 16px; +} +.customer-contacts li a:hover { + color: #93a1af; +} +.customer-contacts li i { + color: #b5c1cc; + font-size: 14px; + margin-right: 3px; + vertical-align: top; + display: inline-block; +} +.customer-contacts .contact-main, +.customer-contacts .contact-main:hover { + color: #3197d6; +} +.customer-extra { + clip: both; +} +.customer-section { + margin-top: 9px; + color: #93a1af; + word-wrap: break-word; +} +.customer-divider { + display: block; + text-align: center; + color: #93a1af; + font-size: 1.5em; + margin: 12px 0 18px; + line-height: 6px; + letter-spacing: 3px; +} + +/** + * Conversations list + */ +.mailbox-toolbar { + border-bottom: 1px solid #D4D9DD; +} +.table.table-conversations { + margin: 0; + table-layout: fixed; + width: 100%; +} +.table-conversations thead { + background-color: #deecf9; +} +.table-conversations .conv-fader { + background: linear-gradient(to right,rgba(255,255,255,0), #fff); + position: absolute; + width: 18px; + bottom: 1px; + right: 0; + z-index: 2; + height: 100%; +} +.table-conversations .conv-active .conv-fader { + background: linear-gradient(to right,rgba(255,255,255,0), #eff6fc); +} +.table-conversations .conv-fader::after { + content: ""; + position: absolute; + width: 18px; + bottom: 0; + height: 100%; +} +.table.table-conversations th { + padding: 4px 13px 1px 13px; + white-space: nowrap; + border-bottom: 1px solid #e3e8eb; +} +.table.table-conversations th.conv-customer { + padding-left: 0; +} +.table.table-conversations th.conv-subject { + padding-left: 3px; +} +.table-conversations .conv-cb { + padding: 4px 0; + text-align: center; + vertical-align: middle; +} +.conv-cb label { + position: relative; + top: -8px; + left: 9px; +} +thead .conv-cb label { + top: -9px; + left: -3px; +} +.table-conversations td input { + margin: 0; +} +.table-conversations th a, +.table-conversations th a:hover, +.table-conversations th a:focus, +.table-conversations th span { + color: #005a9e; + text-decoration: none; + cursor: default; + line-height: 26px; + display: block; + font-weight: bold; +} +.table-conversations th .conv-col-sort { + cursor: pointer; +} +.table.table-conversations tbody td { + line-height: 1em; + padding: 0; + vertical-align: middle; + border-bottom: 1px solid #e3e8eb; +} +.table.table-conversations tbody .conv-row-extended td { + border-bottom: 0!important; +} +.table-conversations td a { + color: #2a3b47; + display: block; + font-size: 14px; + padding: 14px; +} +.table-conversations td a:hover, +.table-conversations td a:focus { + text-decoration: none; +} +.table-conversations .conv-active td { + font-weight: bold; + line-height: 1.2em; +} +.table.table-conversations tbody .conv-active { + /*background-color: #eff6fc;*/ + /*background-color: #f2f7fd;*/ + background-color: #f4f8fd; +} +/*.conv-row:hover { + filter: brightness(98%); + -webkit-filter: brightness(98%); + -moz-filter: brightness(98%); + -o-filter: brightness(98%); + -ms-filter: brightness(98%); +} +.conv-row:hover { + background-color: #fbfcfd; +}*/ +.conv-active .conv-date a { + /*background-color: #deecf9;*/ + background-color: #e5effb; +} +.conv-sort { + position: relative; + left: 1px; + top: -1px; +} +.table-conversations td.conv-customer a { + line-height: 1.2em; + padding-left: 0; + word-wrap: break-word; +} +table.table-conversations td.conv-subject { + overflow: hidden; + white-space: nowrap; + vertical-align: top; + /* Probably not needed */ + /*padding-bottom: 6px;*/ +} +table.table-conversations td.conv-attachment { + color: #93a1af; + vertical-align: top; + padding: 7px 0 0 0; + text-align: center; +} +.table-conversations td.conv-attachment i { + top: 4px; + font-size: 12px; +} +.table-conversations td.conv-subject a { + overflow: hidden; + margin-top: 8.4px; + padding: 0 4px 7px 4px; + position: relative; + white-space: nowrap; +} +.table-conversations td p { + color: #2a3b47; + font-size: 13.4px; +} +.table-conversations .conv-spam td p { + color: #93a1af; +} +.table-conversations td .conv-preview { + color: #93a1af; + font-weight: normal; +} +.table-conversations td.conv-subject p { + line-height: 1.5em; + margin: 0; +} +.table-conversations .conv-current { + width: 2px; + position: relative; +} +.table-conversations .conv-cb { + width: 40px; +} +.table-conversations .conv-customer { + width: 150px; +} +.table-conversations .conv-spam .conv-customer a { + color: #93a1af; +} +.table-conversations .conv-attachment { + width: 20px; +} +.table-conversations .conv-attachment .conv-star { + font-size: 15px; + margin-bottom: 6px; +} +.table-conversations .conv-attachment .glyphicon-star-empty { + color: #d6dbe0; +} + +.table-conversations .conv-attachment-mobile +{ + font-size: 12px; + color: #93a1af; + margin-right: 3px; + display:none; +} + +.table-conversations .conv-thread-count { + /*width: 42px;*/ + width: 1px; + text-align: right; +} +.table-conversations .conv-thread-count span { + background: #f8f9f9; + border: 1px solid #d6dde3; + color: #a5b2bd; + font-size: 11px; + font-weight: 400; + padding: .1em .4em; + border-radius: 4px; +} +.conv-counter { + /*border: 1px solid #d6dde3; + background: #f8f9f9;*/ + color: #a5b2bd; + line-height: 17px; + position: relative; + margin-left: 3px; + /*top: -1px;*/ + font-size: 12px; + /*padding: 0 3px;*/ +} +.table-conversations .conv-thread-count a { + padding: 0; +} +.table-conversations .conv-thread-count .conv-star { + font-size: 14px; + margin-top: 2px; + padding-bottom: 8px; +} +.table-conversations .conv-number { + width: 86px; +} +.table-conversations .conv-number i { + font-size: 10px; + margin-right: 2px; + color: #93a1af; +} +.table-conversations .conv-date { + width: 138px; +} +.table.table-conversations tfoot td { + border: 0; + color: #7a8da0; + font-size: 13px; + height: 28px; + padding: 0 17px; + vertical-align: middle; +} +.table-conversations tfoot strong { + color: #676d7a; +} +.table-conversations .glyphicon-earphone { + color: #93a1af; +} +.table-pager { + float: right; +} +.table-pager a.pager-nav { + display: inline-block; + color: #a5b2bd; + padding: 4px 0; + width: 25px; + line-height: 24px; + font-size: 16px; +} +.table-pager a.pager-nav.disabled { + color: #a5b2bd; + opacity: 0.3; +} +.table-pager a.pager-nav.disabled:hover { + color: #a5b2bd; +} +.table-pager a.pager-nav:hover { + color: #333; +} +/*.conv-subject-number { + display: none; +}*/ +.conv-owner-mobile { + display: none; + font-weight: normal; +} +.conv-date a { + /*background-color: #f8f9f9;*/ + border-radius: 17px 0 0 17px; + padding-top: 11px!important; + padding-bottom: 11px!important; +} +button.conv-checkbox-clear { + display: none; +} +.viewer-badge { + color: #fecb5a; + border-top: 0 solid transparent; + border-bottom: 12px solid transparent; + border-left: 12px solid; + left: 0; + top: 0; + position: absolute; + width: 0; + height: 0; +} +.viewer-badge.viewer-replying { + color: #d85552; +} +@media (max-width:1000px) { + /** + * Make conversations table responsive + */ + .table-conversations { + display: block; + } + .table-conversations thead { + border: none; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + } + .table.table-conversations td, + .table.table-conversations tr, + .table.table-conversations tbody, + .table.table-conversations tfoot { + display: block; + width: 100%; + border: 0; + + -webkit-touch-callout:none; + -webkit-user-select:none; + -khtml-user-select:none; + -moz-user-select:none; + -ms-user-select:none; + user-select:none; + } + + .table.table-conversations tr.selected { + background-color: #e5effb; + } + .table-conversations tr.selected .conv-fader { + background: linear-gradient(to right,rgba(255,255,255,0), #e5effb); + } + + .table.table-conversations tbody td { + border: 0; + } + .table.table-conversations tbody tr { + position: relative; + height: 80px; + border-bottom: 1px solid #e3e8eb; + } + .table.table-conversations.show-mailbox tbody tr { + height: 97px; + } + + .table-conversations .conv-attachment-mobile + { + display: inline; + } + + .conv-cb { + display: none !important; + } + .conv-attachment { + display: none !important; + } + .conv-subject { + position: absolute !important; + top: 24px; + padding-left: 2px!important; + padding-right: 20px!important; + } + .table-conversations .conv-fader { + width: 80px; + } + .conv-owner { + display: none!important; + } + .conv-owner-mobile { + display: inline!important; + } + .conv-owner-mobile .glyphicon { + top: 0; + } + .conv-customer { + position: absolute; + top: 0; + margin-left: 10px!important; + } + .conv-customer a { + width: 350px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .conv-customer a, + .conv-date a { + padding: 11px 8px 0 0 !important; + line-height: 1.2em; + } + .table-conversations td.conv-subject a { + padding-left: 8px; + } + .conv-date a { + padding-top: 13px!important; + font-size: 12px!important; + background-color: transparent!important; + font-weight: normal; + } + .conv-counter { + border: 1px solid #d6dde3; + background: #f8f9f9; + top: -1px; + font-size: 10px; + padding: 0 3px; + } + /*.conv-subject-number { + display: inline; + }*/ + .conv-number { + display: none!important; + } + .conv-attachment { + position: absolute; + left: 7px; + top: 24px; + text-align: center; + width: 25px!important; + } + .conv-date { + width: 50%; + position: absolute; + top: 0; + right: 0; + text-align: right; + } + .conv-thread-count { + position: absolute; + right: 0; + top: 30px; + z-index: 999; + text-align: right; + width: auto !important; + } + .conv-thread-count a, + .conv-thread-count .conv-star { + padding-right: 6px!important; + } + .table.table-conversations tfoot tr { + display: block; + position: relative; + height: 32px; + } + .conv-totals { + line-height: 31px!important; + } + .conv-totals { + position: absolute; + left: 0; + text-align: left; + } + .conv-nav { + position: absolute; + right: 0; + } + + button.conv-checkbox-clear { + display: block; + } +} +@media (max-width:700px) { + .conv-customer a { + width: 250px; + } +} + +/** + * Conversation + */ +.body-conv .content-2col { + display: flex; + /*flex-wrap: wrap;*/ +} +#conv-layout { + position: relative; + width: 100%; +} +#conv-layout-header { + float: left; + width: 100%; + padding-right: 280px; +} +#conv-layout-main { + float: left; + width: 100%; + padding-right: 280px; +} +#conv-layout-customer { + box-shadow: -1px 0 0 #d6dde3, 1px 0 0 #d6dde3, 0 1px 0 #d6dde3; + position: absolute; + right: 0; + /*background-color: #f4f5f5;*/ + /*background-color: #eff6fc;*/ + background-color: #fff; + width: 280px; + height: 100%; + z-index: 9; +} +#conv-toolbar { + background-color: #deecf9; + /*border-bottom: 1px solid #e3e8eb;*/ + padding-left: 9px; +} +.conv-actions .conv-action { + display: inline-block; + padding: 16px 16px 9px; + /*color: #4f5d69;*/ + color: #005a9e; + font-size: 19px; + cursor: pointer; +} +.conv-actions .conv-action:hover { + /*color: #394956;*/ + color: #0078d7; +} +.conv-action.inactive { + color: #aeb6c0; +} +.conv-action.dropdown { + padding: 0!important; +} +.conv-action .dropdown-toggle { + font-size: 17px; + padding-left: 13px; +} +#conv-subject { + border-bottom: 1px solid #e3e8eb; +} +.conv-subj-block { + padding: 16px 9px 18px 20px; +} +.conv-following .conv-subjtext > span:before { + content: "🔔 "; + font-size: 15px; + position: relative; + top: -3px; +} +.conv-top-block { + border-top: 1px solid #e3e8eb; + padding: 10px 20px; +} +.action-visible .conv-top-block + .conv-action-wrapper .conv-action-block { + margin-top: 0; + padding-top: 15px; +} +.action-visible .conv-top-block + .conv-action-wrapper { + border-top: 1px solid #e3e8eb; +} +.conv-subjwrap { + padding-right: 145px; + position: relative; + /*overflow: hidden;*/ + display: inline-block; + width: 100%; +} +.conv-subjtext { + float: left; + display: inline-block; + color: #2a3b47; + font-weight: 400; + font-size: 23px; + line-height: 30px; + word-wrap: break-word; + margin-right: 8px; + width: 100%; + max-width: 100%; +} +.conv-subj-editor, +.conv-subj-editing > span { + display: none; +} +.conv-subj-editing .conv-subj-editor { + display: table; +} +.conv-numnav { + margin: 0; + position: absolute; + color: #93a1af; + right: 0; + top: 5px; +} +.conv-numnav strong { + color: #2a3b47; + font-size: 18px; + font-weight: bold; + padding-right: 8px; +} +.conv-numnav i { + cursor: pointer; + font-size: 15px; +} +.conv-numnav i:hover { + color: #364a5a; +} +#conv-viewers { + height: 20px; + margin: 0; + position: absolute; + right: 7px; + top: 31px; + max-width: 140px; + text-align: right; + line-height: 18px; + overflow: hidden; +} +/*#conv-viewers > span { + display: inline-block; +}*/ +.viewer-replying .person-photo { + background-color: #d43f3a!important; +} +.conv-star { + color: #bbc4cc; +} +.conv-star.glyphicon-star { + color: #ffd76e; +} +.conv-star:hover { + color: #f4b400!important; +} +.conv-info { + overflow: visible; + float: right; + text-align: right; + list-style: none; + margin: 11px 11px 12px 0; + padding: 0; + white-space: nowrap; +} +.conv-info .btn { + height: 30px; +} +.conv-info > li { + display: inline-block; + margin: 0 4px; +} +/*.conv-info .btn-group button.btn-default.conv-info-val { + color: #727d87!important; +}*/ +.conv-info .btn-group button.conv-info-icon { + padding: 0 7px; +} +.conv-info .btn-group button.conv-info-val { + max-width: 100px; + text-overflow: ellipsis; + overflow: hidden; + padding-right: 22px; +} +.conv-info .btn-group button.conv-info-val .caret { + position: absolute; + top: 12px; + right: 8px; +} +.conv-info .btn-group button i { + position: relative; + top: 2px; +} +.conv-info .btn-group button .caret { + margin-left: 3px; +} +.conv-actions { + display: inline-block; +} +.conv-sidebar-block { + margin: 20px 10px 5px 10px; + /*background: #deecf9;*/ + /*box-shadow: 0 1px 3px 0 rgba(0,0,0,.1);*/ + /*box-shadow: 0 1px 3px 0 #c7e0f4;*/ + border-radius: 14px 0 0 14px; +} +.conv-sidebar-block .panel { + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + background-color: #f8f9f9; +} +.conv-sidebar-block .panel-heading { + background-color: #f1f3f5 !important; +} +.conv-customer-block { + position: relative; + margin-top: 7px; +} +.conv-customer-header { + background-color: #f1f3f5; + height: 53px; +} +.conv-sidebar-block .accordion .panel-title a { + color: #2a3b47; + font-size: 13.4px; + font-weight: bold; +} +.conv-sidebar-block .accordion .panel-default > .panel-heading:hover { + background-color: transparent; +} +.customer-snippet { + padding: 5px 0; + /*margin-top: 60px;*/ + position: relative; +} +.conv-next-prev { + vertical-align: middle; + margin-right: 0!important; +} +.conv-next-prev a { + font-size: 16px; + color: #a5b2bd; + position: relative; + top: 3px; + text-decoration: none; +} +.conv-next-prev a:hover, +.conv-next-prev a:focus { + cursor: pointer; + color: #4f5d69; +} +.customer-trigger { + display: inline-block; + position: absolute; + font-size: 16px; + right: 6px; + top: -43px; + height: 22px; +} +.customer-trigger a { + color: #93a1af; +} +.customer-trigger a:hover, +.customer-trigger a:focus { + color: #0078D7; + text-decoration: none; +} +.thread { + position: relative; + /*border-bottom: 1px solid #ebeff1;*/ + border-bottom: 1px solid #e3e8eb; +} +.thread:last-child { + border-bottom: 0; +} +.thread-header { + overflow: hidden; + min-height: 42px; + padding-left: 72px; +} +.thread-message { + margin: 0 24px 0 0; + padding: 25px 13px 20px 12px; +} +.thread-photo { + /*padding: 16px 18px 25px 20px;*/ + padding: 21px 18px 25px 20px; + position: absolute; +} +.thread-photo .person-photo { + border-radius: 50%; + /*box-shadow: 0 1px 2px rgba(0,0,0,.2);*/ + -webkit-box-shadow: 0 1px 6px rgba(147,161,175,.5); + box-shadow: 0 1px 6px rgba(147,161,175,.5); + width: 45px; + height: 45px; + /* for automatic photo */ + line-height: 41px; + padding: 2px; + background-color: #fff; +} +.thread-title { + float: left; + max-width: 68%; +} +.thread-person { + color: #8494a4; + font-size: 18px; + line-height: 1.2em; + display: inline-block; + margin-bottom: 12px; + overflow-wrap: break-word; + word-wrap: break-word; +} +.thread-person strong { + font-weight: bold; + color: #253540; + font-size: 18px; +} +.thread-person strong a, +.thread-person strong a:hover, +.thread-person strong a:focus { + color: #253540; +} +.draft-actions { + display: inline-block; + margin-bottom: 5px; +} +.thread-info { + color: #a5b2bd; + float: right; + text-align: right; + font-size: 12px; +} +.thread-date { + color: #a5b2bd; +} +.thread-date:hover { + color: #2a3b47; +} +.thread-badge { + color: #ffe19d; + border-top: 0 solid transparent; + border-bottom: 39px solid transparent; + border-left: 39px solid; + left: 0; + top: 0; + position: absolute; + width: 0; + height: 0; +} +.thread-badge .glyphicon { + position: relative; + left: -34px; + top: 3px; + color: #c1861e; +} +.thread-info-icon, +.thread-to-first { + margin-right: 1px; + color: #a5b2bd; +} +.thread-to-first .glyphicon { + top: 2px; +} +.thread-to-first:hover { + text-decoration: none; +} +.thread-recipients { + clear: both; + margin: -8px 0 14px; + color: #a5b2bd; + font-size: 12px; + line-height: 14px; +} +.thread-recipients strong { + color: #72808e; +} +.thread-recipients div { + margin-top: 2px; +} +.thread-body { + color: #253540; + font-size: 14px; + line-height: 1.45em; + overflow-y: hidden; + + margin: 0 -18px 0 50px; + padding: 0 18px 0 22px; + /*border-radius: 10px;*/ + + /*margin-top: 5px;*/ + /*padding-top: 12px;*/ + /*padding-bottom: 10px;*/ +} +.thread-body .alert .glyphicon { + top: 2px; +} +.thread-meta { + font-size: 12.5px; + margin: 15px 0 0 0; + color: #a5b2bd; + display: block; +} +.thread-meta .glyphicon { + font-size: 12px; + color: #b8c7d4; + margin-right: 5px; +} +.thread-options { + position: absolute; + top: 22px; + cursor: pointer; + /*right: 5px;*/ + right: 7px; + color: #a5b2bd; +} +.thread-options .dropdown-toggle { + /*padding: 4px 8px;*/ + padding: 0px 11px 4px 8px; + display: block; +} +.thread-type-new { + background-color: #d1ecff; + height: 40px; + text-align: center; +} +.thread-type-new .view-new-trigger { + line-height: 40px; + display: block; +} +.thread-type-new .view-new-trigger:hover, +.thread-type-new .view-new-trigger:focus { + text-decoration: none; +} +.thread-type-new .thread { + display: none; +} +.thread-type-lineitem { + background: #f9fafa; + /*overflow: auto;*/ + /*-webkit-box-shadow:inset 5px 0 0 0 #bcc4cc; + box-shadow:inset 5px 0 0 0 #bcc4cc;*/ +} +.thread-type-lineitem .thread-options { + top: 8px; +} +.thread-type-lineitem .thread-message { + padding-top: 12px; + padding-bottom: 9px; + color: #a5b2bd; + font-size: 12px; +} +.thread-type-lineitem .thread-header { + min-height: auto; +} +.thread-type-lineitem .thread-title a, +.thread-meta a { + color: #7d878f; + font-weight: bold; +} +.thread-attachments { + position: relative; +} +/*.thread-type-customer .thread-body { + border: 1px solid #d9dee3; + -webkit-box-shadow:inset 3px 0 0 0 #d9dee3; + box-shadow:inset 3px 0 0 0 #d9dee3; +}*/ +/*.thread-type-message .thread-body { + background-color: #f1f7fc; + /*border: 1px solid #2b8fdd; + -webkit-box-shadow:inset 3px 0 0 0 #2b8fdd; + box-shadow:inset 3px 0 0 0 #2b8fdd;* / + border: 1px solid #abd2f1; + -webkit-box-shadow:inset 3px 0 0 0 #abd2f1; + box-shadow:inset 3px 0 0 0 #abd2f1; +}*/ +.thread-type-message { + background-color: #f4f8fd; +} +/*.thread-type-draft .thread-body { + border: 1px solid #6db2e8; + -webkit-box-shadow:inset 3px 0 0 0 #6db2e8; + box-shadow:inset 3px 0 0 0 #6db2e8; +}*/ +.thread-type-draft { + -webkit-box-shadow:inset 5px 0 0 0 #abd2f1; + box-shadow:inset 5px 0 0 0 #abd2f1; +} +/*.thread-type-note .thread-body { + background-color: #fffbf1; + /*border: 1px solid #ffc646; + -webkit-box-shadow:inset 3px 0 0 0 #ffc646; + box-shadow:inset 3px 0 0 0 #ffc646;* / + border: 1px solid #ffe19d; + -webkit-box-shadow:inset 3px 0 0 0 #ffe19d; + box-shadow:inset 3px 0 0 0 #ffe19d; +}*/ +.thread-type-note { + background-color: #fffbf1; + -webkit-box-shadow:inset 5px 0 0 0 #ffe19d; + box-shadow:inset 5px 0 0 0 #ffe19d; +} +/*.thread-type-message .thread-message, +.thread-type-note .thread-message { + padding-bottom: 17px; +}*/ +.thread-type-note .thread-person { + color: #f7b126; +} +.thread-type-message .thread-person { + /*color: #8494a4;*/ + color: #3197d6; +} +/*.thread-type-draft { + background-color: #e9f2fb; + /*-webkit-box-shadow:inset 5px 0 0 0 #3197d6; + box-shadow:inset 5px 0 0 0 #3197d6* / +}*/ +.thread-type-draft .thread-person { + color: #3197d6; + margin-right: 10px; +} +.thread-type-draft .thread-header { + padding-top: 2px; +} +.thread-attachments ul { + list-style: none; + padding-left: 23px; +} +.thread-attachments li { + border-radius: 2px; + /*border: 1px solid #dadada;*/ + display: inline-block; + line-height: 1; + background-color: #f1f3f4; + margin: 8px 4px 8px 0; + padding: 4px 10px; + position: relative; +} +.thread-attachments i.glyphicon-paperclip { + color: #a5b2bd; + font-size: 14px; + position: absolute; + top: 12px; +} +.thread-attachments a { + display: inline-block; + color: #337ab7; + font-size: 13.4px; +} +.thread-attachments a.disabled, +.thread-attachments a.disabled:hover, +.thread-attachments a.disabled:focus { + color: #93a1af; + text-decoration: none; + cursor: default; +} +.thread-editor-container { + padding: 20px; +} +.thread-editor-statusbar { + text-align: right; + background-color: #f9fafa; + border: 1px solid #dee4e9; + border-top: none; +} +.thread-editor-statusbar button, +.thread-editor-statusbar a { + display: inline-block; +} +.thread-editor-statusbar .btn-primary { + height: 38px; + padding-left: 20px; + padding-right: 20px; +} +.conv-note-block .thread-editor-statusbar { + background-color: #fff7dd; + border: 1px solid #ffe39d; +} +.note-editor.note-frame.panel { + box-shadow: none; +} +.thread-original { + margin-top: 10px; + border-left: 5px solid #dbe1e9; + padding-left: 13px; +} +.thread-text { + color: #253540; + font-size: 14px; + line-height: 1.45em; +} +/*.thread-num { + position: absolute; + left: 20px; + padding: 1px 5px; + color: #a5b2bd; + text-align: center; + top: 75px; +}*/ +.thread-content img { + max-width: 100%; +} +.conv-new #conv-toolbar { + border-bottom: 1px solid #d4d9dd; + padding: 6px 0 7px 0; +} +.conv-new #conv-toolbar h2 { + color: #253540; + float: left; + font-size: 20px; + font-weight: normal; + line-height: 39px; + margin: 0; + padding: 0 0 0 20px; +} +.conv-new #conv-toolbar .btn-group { + margin: 5px 0 0 20px; + color: #4f5d6b; +} +.conv-new #conv-toolbar .btn-group i { + color: #4f5d6b; + position: relative; + top: 3px; +} +.conv-new .conv-info { + font-size: 14px; + color: #253540; + margin: 0px 20px 0px 0; +} +.conv-new .conv-info strong { + font-size: 20px; + line-height: 39px; + font-weight: normal; +} +.conv-new-form { + padding-top: 15px; + padding-bottom: 150px; +} +.conv-new-form .form-group { + margin-bottom: 10px; +} +.phone-conv-fields { + display: none; +} +.toggle-field { + padding-left: 4px; + padding-bottom: 5px; + margin-top: -5px; +} +.conv-block { + margin: 0 20px; +} +.conv-block.conv-reply-block { + margin-top: -7px; + margin-bottom: 12px; +} +.conv-reply.glyphicon { + transform: scaleX(-1); + -moz-transform: scaleX(-1); + -webkit-transform: scaleX(-1); + -ms-transform: scaleX(-1); +} +.conv-reply-block .form-group { + margin-bottom: 5px; +} +.conv-reply-label, +.conv-reply-block .form-group label { + width: 33px; + padding-right: 0; + padding-left: 0px; +} +.conv-reply-block .form-group input { + max-width: 600px; + min-width: 150px; +} +.conv-reply-label, +.conv-reply-block .control-label { + float: left; + text-align: right; +} +.conv-reply-body { + margin-top: 13px; +} +.conv-reply-field { + padding-left: 10px; + float: left; + width: 68%; +} +.conv-forward-block .alert-switch-to-note { + display: none; +} +.btn-add-note-text, +.btn-send-forward, +.btn-create-conv, +.conv-forward-block .btn-send-text, +.conv-note-block .btn-send-text, +.conv-note-block .conv-recipient, +.conv-note-block .conv-from-alias { + display: none; +} +.conv-note-block .btn-add-note-text { + display: block; +} +.conv-forward-block .btn-send-forward { + display: block; +} +.conv-phone-block .btn-create-conv { + display: block; +} +.conv-phone-block .btn-add-note-text { + display: none; +} +.conv-forward-block .conv-recipient-to.hidden { + display: block !important; +} +.conv-note-block .conv-reply-body { + margin-top: 5px; +} +.conv-note-block .note-editor.note-frame .note-statusbar.note-statusbar-toolbar { + border-top: 1px solid #ffd56d; + background-color: #fff7dd; + color: #d79400; +} +.conv-note-block .note-editor.note-frame { + border-color: #ffe39d !important; + color: #d79400; +} +.conv-note-block .note-editor .form-control { + border-color: #ffd56d; + color: #b37100; +} +.conv-note-block .btn-primary, +.conv-note-block .btn-primary:focus { + background-color: #f5b126; + border-color: #f5b126; + color: #fff; +} +.conv-note-block .btn-primary:hover { + background-color: #de980a; + border-color: #de980a; + color: #fff; +} +.conv-note-block .btn-send-menu { + border-left-color: #ffd56d; +} +.conv-note-block .note-bottom-div { + color: #b37100; +} +.conv-note-block .panel-default > .panel-heading { + border-color: #ffd56d; + background-color: #fff7dd; + color: #d79400; +} +.conv-note-block .note-actions .note-btn-save-draft, +.conv-note-block .note-actions .draft-saved { + display: none; +} +.conv-note-block.conv-phone-block .note-actions .note-btn-save-draft, +.conv-note-block.conv-phone-block .note-actions .draft-saved { + color: #b37100; + display: block; +} +.conv-note-block .note-editor .btn-default { + color: #b37100; + background-color: #fff7dd; + border-color: #fff7dd; +} +.conv-note-block .alert-switch-to-note { + display: none; +} +.conv-note-block #editor_signature, +.conv-type-chat #editor_signature, +.conv-type-chat .cc-toggler, +.conv-type-chat .conv-reply-block:not(.conv-note-block) .note-btn { + display: none; +} +.conv-type-chat .conv-reply-block:not(.conv-note-block) .note-btn-attachment, +.conv-type-chat .conv-reply-block:not(.conv-note-block) .note-actions .note-btn, +.conv-type-chat .conv-reply-block:not(.conv-note-block) .btn-codeview ~ .note-btn-group .note-btn { + display: block; +} +.alert-switch-to-note { + -webkit-box-shadow: inset 5px 0 0 0 #ffc646; + box-shadow: inset 5px 0 0 0 #ffc646; + margin: 8px -15px -9px; + padding-top: 8px; + padding-bottom: 8px; +} +.alert-switch-to-note .glyphicon { + font-size: 15px; + position: relative; + top: 2px; + margin-left: 3px; + margin-right: 5px; +} +.alert-switch-to-note a, +.alert-switch-to-note a:hover, +.alert-switch-to-note a:focus { + color: #b37100; + text-decoration: underline; +} +ul.sidebar-block-list { + padding-top: 2px; + padding-left: 0; + list-style: none; + margin-bottom: 5px; +} +.sidebar-block-list li a { + padding-top: 2px; + padding-bottom: 2px; + display: inline-block; +} +/*.prev-convs li:first-child { + margin-top: 0; +}*/ +.sidebar-block-list .glyphicon { + margin-right: 4px; + position: relative; + top: 2px; +} +.conv-sidebar-block .panel-body { + border-top: 0!important; + padding-top: 0; + padding-bottom: 6px; +} +.sidebar-block-link { + margin-bottom: 4px; + display: inline-block; +} +.sidebar-block-header2 { + display: none; +} +.conv-star { + cursor: pointer; +} +@media (max-width:700px) { + .conv-sidebar-block .panel-body { + padding-left: 66px!important; + } + #conv-viewers { + max-width: 81px; + top: 26px; + } + .conv-info .btn-group button.conv-info-val span:first-child { + display: none!important; + } + .conv-info .dropdown-menu { + right: 0!important; + left: auto!important; + } + .conv-actions .conv-action { + padding: 18px 10px 9px; + } + .conv-subjtext { + font-size: 20px; + } + .conv-subjwrap { + padding-right: 90px; + } + .conv-subj-block { + padding: 16px 2px 12px 13px; + } + .conv-top-block { + padding: 10px 13px; + } + .conv-customer-block { + padding-left: 12px!important; + padding-right: 15px!important; + } + .customer-data { + margin-left: 43px!important; + } + .conv-numnav { + top: 0; + } + .conv-info .btn-group button.conv-info-val { + padding-right: 17px; + } + .conv-info { + margin-right: 6px; + } + /*.conv-info > li { + margin: 0 2px; + }*/ + #conv-toolbar { + padding-left: 6px; + } + .conv-actions .conv-action { + font-size: 17px; + } + .thread-photo { + padding: 13px 15px 25px 12px; + } + .conv-customer-block { + padding: 15px; + } + .thread-message { + margin-right: 15px; + margin-left: 0; + padding-top: 17px; + } + .thread-header { + padding-left: 55px; + min-height: 53px; + } + .thread-type-draft .thread-header { + padding-left: 3px; + } + .thread-body { + margin-left: 0; + padding-left: 12px; + } + .thread-to-first, + .thread-type { + display: none; + } + /*.thread-type-note .thread-body, + .thread-type-message .thread-body { + border-radius: 0; + margin: 0 -17px 0 -3px; + padding-left: 10px; + }*/ + .thread-info { + max-width: 32%; + } + .thread-options { + /*right: 0;*/ + right: 2px; + top: 15px; + } + .thread-badge { + border-bottom: 31px solid transparent; + border-left: 31px solid; + } + .thread-badge .glyphicon { + left: -27px; + top: -1px; + font-size: 10px; + } + .conv-new #conv-toolbar h2 { + padding-left: 13px; + font-size: 17px; + } + .conv-new .conv-info { + margin-right: 13px; + } + .conv-new .conv-info strong { + font-size: 17px; + } + .conv-new-form { + padding-top: 0; + } + .conv-new #conv-toolbar .btn-group { + margin-left: 13px; + } + .conv-block { + margin: 0 13px; + } + .conv-reply-field { + width: 80%; + } +} +/** + * Without right sidebar. + */ +@media (max-width:1100px) { + #conv-layout-header, + #conv-layout-main, + #conv-layout-customer { + float: none; + width: 100%; + padding-right: 0; + position: static; + /*background-color: #fff;*/ + height: auto; + } + #conv-layout-customer { + border-bottom: 1px solid #e3e8eb; + } + .customer-snippet { + min-height: 46px; + } + .customer-snippet, + .conv-sidebar-block { + padding: 0; + margin: 0; + } + .conv-customer-header { + display: none; + } + #conv-layout-customer, + .conv-sidebar-block { + -webkit-box-shadow: none; + box-shadow: none; + } + #conv-layout-customer .customer-photo-container { + margin: 0; + width: 45px; + float: left; + margin-top: 0; + height: auto; + } + .customer-photo { + max-width: 45px; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2); + box-shadow: 0 1px 2px rgba(0,0,0,.2); + } + .conv-customer-block { + padding: 15px 20px 15px; + background-color: #f8f9f9; + border-radius: 0; + } + .customer-data { + margin: 0 0 0 50px; + } + .thread-person { + font-size: 16px; + } + .customer-contacts { + margin: 0; + padding-top: 2px; + } + .customer-contacts li { + display: inline-block; + } + .customer-contacts li a { + display: inline; + } + .customer-name { + float: left; + margin-right: 7px; + /*font-weight: bold; + font-size: 18px;*/ + } + .customer-section { + margin-top: 0px; + } + .customer-trigger { + top: 16px; + right: 11px; + } + .profile-preview { + padding: 15px; + } + /*.customer-email { + width: 100%; + }*/ + .customer-email:before { + content: '<'; + } + .customer-email:after { + content: '>,'; + } + .customer-email:last-child:after { + content: '>'; + } + .customer-phone:before { + content: 'tel:'; + } + .customer-phone:after { + content: ','; + } + .customer-phone:last-child:after { + content: ''; + } + .customer-social-profiles { + position: static; + } + .profile-preview .customer-social-profiles { + position: absolute; + top: 43px; + } + .conv-sidebar-block .panel-heading { + display: none; + } + .conv-sidebar-block .panel { + background-color: #fff; + } + .conv-sidebar-block .panel-group { + margin: 0; + } + .conv-sidebar-block .panel-body { + /*border-top: 1px solid #e3e8eb!important;*/ + border-bottom: 0; + padding-left: 83px; + } + .sidebar-block-header2 { + display: block; + } + .conv-sidebar-block .collapse { + display: block!important; + height: auto!important; + } + .conv-sidebar-block .collapse.in { + display: none!important; + height: 0!important; + } +} +@media (max-width:991px) { + .conv-info .btn-group button.conv-info-val { + max-width: 148px; + } + .conv-attachment .conv-star { + display: none; + } +} +@media (min-width:992px) { + .conv-info .btn-group button.conv-info-val { + max-width: 170px; + } + .conv-thread-count .conv-star { + display: none; + } +} +@media (min-width:1100px) and (max-width:1239px) { + .conv-info .btn-group button.conv-info-val { + max-width: 94px; + } + .conv-actions .conv-action { + padding: 17px 10px 9px; + } +} +@media (min-width:1240px) { + .conv-info .btn-group button.conv-info-val { + max-width: 150px; + } +} + +/** + * Subscriptions + */ +.subs-cb { + text-align: center; +} +.user-subscriptions { + margin-bottom: 30px; +} +.user-subscriptions .table-header th, +.user-subscriptions .table-header td { + border-top: none; + font-size: 14px; + padding-top: 20px; + vertical-align: bottom; + line-height: 18px; +} +.user-subscriptions .table-header td { + padding-bottom: 4px; +} +.user-subscriptions .table-header input { + margin-top: 6px; +} + +/** + * Alerts + */ +.alerts { + flex: 0 0 100%; +} +.alerts .alert { + margin-bottom: 0; + padding-top: 5px; + padding-bottom: 5px; +} +.alert-floating { + font-size: 14px; + left: 50%; + display: none; + /*display: flex;*/ + font-size: 15px; + position: fixed; + text-align: center; + top: 16px; + transform: translate(-50%,0) translate(-1px,0); + z-index: 1099; + max-width: 300px; + border-radius: 0; + flex-direction: row; + padding: 9px 14px; + /*-webkit-box-shadow: 4px 4px 12px rgba(0, 0, 0, .175); + box-shadow: 4px 4px 12px rgba(0, 0, 0, .175);*/ + -webkit-box-shadow:0 0 5px rgba(0,0,0,.4); + box-shadow:0 0 5px rgba(0,0,0,.4); + cursor: default; + + overflow-wrap: break-word; + word-wrap: break-word; + -ms-word-break: break-all; + /* This is the dangerous one in WebKit, as it breaks things wherever */ + /*word-break: break-all;*/ + /* Instead use this non-standard one: */ + word-break: break-word; + /* Adds a hyphen where the word breaks, if supported (No Blink) */ + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; + border: 0; +} +.alert-floating.alert-danger, +.alert-floating.alert-warning { + position: fixed; +} +.alert-floating .glyphicon { + margin-right: 8px; + /*position: relative; + top: 4px;*/ +} +.alert-floating a, +.alert-floating a:hover, +.alert-floating a:focus { + color: #2a3b47; + text-decoration: underline; +} +.alert-floating.alert-success .glyphicon { + color: #53b961; +} +.alert-floating.alert-danger .glyphicon { + color: #d43f3a; +} +.alert-floating.alert-success { + background-color: #deffde; + /*color: #3c763d;*/ + color: #6ac27b; + /*border: 1px solid #6ac27b;*/ +} +/*.alert-floating.alert-danger { + border: 1px solid #a94442; +}*/ +.alert-floating.alert-warning { + background-color: #fff6e2; + color: #b37100; + /*border: 1px solid #f5b126;*/ +} +.alert-info.alert-striped { + -webkit-box-shadow:inset 4px 0 0 0 #3197d6; + box-shadow:inset 4px 0 0 0 #3197d6; +} +.alert-narrow { + padding-top: 5px; + padding-bottom: 5px; +} +.thread-body .alert-narrow { + padding-top: 6px; +} +.alert-danger.alert-light { + background-color: #f5e7e9; + border-color: #f0d9dc; +} +.alert-note { + color: #b37100; + border-color: #ffe19d; + background-color: #fff1cf; +} +/** + * Tables + */ +.table-dark-header th { + background-color: #f9f9f9; +} +.table-main-col { + width: 70%; +} +.table-header { + font-size: 14px; +} +.table-narrow > thead > tr > th, +.table-narrow > tbody > tr > th, +.table-narrow > tbody > tr > td { + padding-top: 4px; + padding-bottom: 4px; +} +.table-header-nb td, +.table-header-nb th { + border-top: none !important; +} + +/** + * Modal + */ +.modal-header { + cursor: default; +} +.modal .close { + opacity: 0.7; +} +.modal-body { + min-height: 115px; +} +.modal-loader img { + position: absolute; + top: 50%; + height: 31px; + margin-top: -15px; +} +.modal-body p.block-help { + font-size: 11.4px; +} +.modal-body.modal-body-fit { + max-height: calc(100vh - 200px); + overflow-y: auto; +} +.modal-width-auto { + display: table; + width: auto; + min-width: 320px; +} +.modal-iframe { + width: 100%; + height: calc(100vh - 151px); +} +@media (max-width:767px) { + .modal-iframe { + height: calc(100vh - 112px); + } +} +/** + * Attachments + */ +.attachments-upload { + display: none; + clear: both; + /*margin-right: -15px; + margin-left: -15px;*/ +} +.attachments-upload img { + margin-right: 3px; +} +.attachments-upload.thread-attachments ul { + margin-bottom: 0; + padding-left: 0; +} +.attachments-upload.thread-attachments li { + margin-bottom: 0; +} +.attachments-upload .glyphicon-remove { + display: none; +} +.attachment-loaded img, +.attachment-loaded .ellipsis { + display: none; +} +.attachment-loaded .glyphicon-remove { + display: inline-block; + color: #999; + cursor: pointer; + position: relative; + top: 2px; +} +.attachment-loaded .glyphicon-remove:hover { + color: #333; +} + +/** + * Fancy checkbox + * https://proto.io/freebies/onoff/ + */ +.onoffswitch { + position: relative; width: 40px; + -webkit-user-select:none; -moz-user-select:none; -ms-user-select: none; + display: inline-block; +} +.onoffswitch-wrap { + padding-top: 4px; + line-height: 11px; +} +.onoffswitch-wrap .icon-info { + top: -2px; +} +.onoffswitch-checkbox { + display: none; +} +.onoffswitch-label { + display: block; overflow: hidden; cursor: pointer; + height: 20px; padding: 0; line-height: 20px; + border: 4px solid #D7D7D7; border-radius: 20px; + background-color: #D7D7D7; + transition: background-color 0.3s ease-in; + margin-bottom: 0; +} +.onoffswitch-label:before { + content: ""; + display: block; width: 20px; margin: 0px; + background: #FFFFFF; + position: absolute; top: 0; bottom: 0; + right: 19px; + border: 4px solid #D7D7D7; border-radius: 20px; + transition: all 0.3s ease-in 0s; + height: 20px; +} +.onoffswitch-checkbox:checked + .onoffswitch-label { + background-color: #106ebe; +} +.onoffswitch-checkbox:checked + .onoffswitch-label, .onoffswitch-checkbox:checked + .onoffswitch-label:before { + border-color: #106ebe; +} +.onoffswitch-checkbox:checked + .onoffswitch-label:before { + right: 0px; +} + +/** + * Change customer + */ +.customer-not-found-title { + display: none; +} +.customer-not-found .customer-not-found-title { + display: block; +} +.customer-not-found .customer-create-title { + display: none; +} + +/** + * Select2 + */ +.select2-container--default .select2-selection--multiple { + border-radius: 3px; + border: 1px solid #c1cbd4; +} +.select2-container--default.select2-container--focus .select2-selection--multiple { + border: 1px solid #c1cbd4; +} +.select2-dropdown { + border: 1px solid #c1cbd4; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice__remove { + float: right; + color: #a5b2bd; + margin-right: 0; + margin-left: 2px; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #f4f5f5; + border: 1px solid #d6dde3; + color: #2a3b47; + border-radius: 3px; + font-size: 13.4px; +} +.select2-multi-dropdown.select2-dropdown { + border-bottom: 0; +} +.select2-multi-dropdown .select2-results__message { + display: none !important; +} +.select2-multi-dropdown .select2-results__option:last-child { + border-bottom: 1px solid #c1cbd4; +} +.select2-container--default .select2-selection--single { + border-color: rgb(193, 203, 212); +} +.select2-selection--single .select2-selection__rendered { + font-size: 13px; + color: #555; +} +.select2-recipient .select2-selection__arrow { + display: none; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + margin-top: 3px; +} +.select2-selection.select2-selection--multiple { + line-height: 17px; +} +.select2-container .select2-selection--multiple { + min-height: auto; +} +.dropdown-menu .select2-selection__choice__remove { + font-size: 13.4px; +} +.input-group .select2:nth-child(2) .select2-selection { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +/*.select2-with-loader .select2-selection__rendered { + padding-right: 30px; +} +.select2-with-loader.loading .select2-selection__rendered:after { + content: ""; + position: absolute; + width: 16px; + height: 16px; + background: url('/img/loader-tiny.gif') 50% 50% no-repeat transparent; + right: 10px; + top: 50%; + transform: translate(0,-50%); +}*/ + +/** + * Website notifications + */ +.web-notifications .dropdown-menu { + min-width: 270px; + max-width: 460px; + min-height: 208px; + max-height: inherit; + width: 460px; + padding: 0; +} +.web-notifications-list { + overflow-y: auto; + list-style: none; + padding: 0; + max-height: 70vh; + clear: both; +} +.web-notifications-header { + background-color: #fff; + border-bottom: 1px solid #e3e8eb; + -webkit-box-shadow: 0 2px 3px -1px rgba(0,0,0,.05); + box-shadow: 0 2px 3px -1px rgba(0,0,0,.05); + padding: 13px 16px 10px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.web-notifications-header h1 { + font-size: 18px; + font-weight: normal; + display: inline-block; + margin: 0; +} +.web-notifications-header a { + color: #93a1af; +} +.web-notifications-header a:hover, +.web-notifications-header a:focus { + color:#4f5d6b; + text-decoration:none +} +.web-notifications-count { + color: #ff9139; + margin-left: 3px; + position: relative; + top: -1px; + cursor: default; +} +.web-notifications-mark-read { + float: right; + position: relative; + top: 1px; +} +.web-notification-date { + background-color: #f9fafa; + border-bottom: 1px solid #e3e8eb; + color: #c1cbd4; + font-size: 11px; + font-weight: bold; + letter-spacing: .5px; + line-height: 1; + padding: 9px 16px 8px; + text-transform: uppercase; +} +.web-notification { + border-bottom: 1px solid #e3e8eb; +} +.web-notification a, +.web-notification a:hover, +.web-notification a:focus { + color: #4f5d6b; + padding: 16px 16px 14px 14px; + text-decoration: none; + outline: 0; + display: flex; +} +.web-notification a:hover, +.web-notification a:focus { + background-color: #f9fafa; +} +.web-notification.is-unread a { + background-color: #f1faff; +} +.web-notification.is-unread a:hover, +.web-notification.is-unread a:focus { + background-color: #e7f6ff; +} +.web-notification-msg { + line-height: 18px; + overflow: hidden; +} +.web-notification-msg-header { + font-size: 14px; + margin-bottom: 4px; +} +.web-notification-img { + width: 36px; + min-width: 36px; + margin-right: 8px; + overflow: hidden; +} +.web-notification-img .person-photo { + -webkit-box-shadow: 0 1px 0 0 #e3e8eb; + box-shadow: 0 1px 0 0 #e3e8eb; + height: 36px; + width: 36px; + line-height: 32px; + border: 2px solid #fff; + border-radius: 50%; + font-size: 13px; +} +.web-notification-msg-preview { + color: #93a1af; + font-size: 13.4px; + overflow: hidden!important; + -o-text-overflow: ellipsis!important; + text-overflow: ellipsis!important; + white-space: nowrap!important; +} +.web-notifications .dropdown-toggle.has-unread:after { + content: ""; + background-color: #ff734c; + /*border: 2px solid #fff;*/ + border-radius: 50%; + width: 10px; + height: 10px; + position: absolute; + right: 11px; + top: 12px; +} +.web-notification-more .btn { + padding: 7px 0 5px; + outline: none!important; +} +.web-notification-more .btn.disabled { + cursor: default; +} +/* Take notifications bell out of the dropdown menu */ +.navbar-header .web-notifications { + position: absolute; + top: 1px; + right: 71px; + display: block; +} +.navbar-header .web-notifications .dropdown-menu { + right: 0; + left: auto; + width: calc(90vw - 71px); +} +.navbar-header .web-notifications > a { + color: #fff; + font-size: 18px; + display: block; + padding: 13px 15px; +} +.navbar-header .web-notifications > a:focus, +.navbar-header .web-notifications > a:hover { + color: #fff!important; +} + +/** + * Modules + */ +.module-card { + background-color: #ffffff; + border: 1px solid #dedede; + box-sizing: border-box; + display: block; + position: relative; + margin: 0 18px 18px 0; + padding: 18px; + -webkit-box-shadow:0 0 5px rgba(0,0,0,.2); + box-shadow:0 0 5px rgba(0,0,0,.2); +} +.module-card.active { + background-color: #deffde; +} +.module-card.not-installed { + background-color: #f4f5f5; +} +.module-card img { + width: 128px; + height: 128px; + float: left; + border-radius: 5px; +} +.module-card h4 { + font-size: 18px; + font-weight: 400; + margin-top: 2px; +} +.module-details { + color: #93a1af; +} +.module-wrap { + margin-left: 144px; +} +.module-actions { + margin-top: 13px; +} +.module-actions .input-group { + max-width: 378px; +} +.module-card .label { + float: right; + font-size: 13.4px; + font-weight: normal; + padding-top: 2px; +} +.label-lightgrey { + background-color: #8b98a6; +} +.label-grey { + background-color: #6b6b6b; +} +.alert-module-update { + margin: 15px 0 0; + padding: 10px 15px; +} +@media (max-width:582px) { + .module-card img { + float: none; + width: 128px; + margin: 0 auto; + display: block; + } + .module-wrap { + margin-left: 0; + } + .module-card h4 { + margin-top: 10px; + text-align: center; + } + .module-card .label { + display: block; + margin-top: 3px; + float: none; + } +} + +/** + * Search + */ +#search-filters .remove { + cursor: pointer; + margin-left: 3px; +} +.search-results .table-conversations thead { + background-color: #f9fafa; +} +.search-tab-conv a { + background-color: #f9fafa!important; +} +.search-results .table.table-conversations th { + padding-top: 7px; +} +#search-filters .form-group { + display: none; +} +#search-filters .form-group.active { + display: block; +} +#search-filters .select2-container { + width: 100% !important; +} +.customers-pager { + margin: 0 13px 20px 13px; + padding: 4px 0 0 10px; + font-size: 20px; + background-color: #f1f3f5; +} +.customers-pager a.disabled { + color: #93a1af; + opacity: 0.3; +} +.customers-pager a { + color: #93a1af; + padding-right: 8px; + text-decoration: none !important; +} +.customers-pager a.disabled:hover { + color: #93a1af; +} +.customers-pager a:hover { + color: #333; +} +/*.customers-pager a:hover, +.customers-pager a:active +.customers-pager a:focus, { + text-decoration: none !important; + border-bottom: 0; +}*/ + +/** + * Panel + */ +.panel-sortable .handle { + cursor: move; + position: absolute; + left: 19px; + padding: 6px 10px; + color: #a5b2bd; +} +.panel-sortable .handle .glyphicon { + font-size: 9px; + top: -1px; +} +.panel-sortable-input .handle { + padding-top: 9px; +} +.panel.panel-sortable > .panel-heading { + margin-left: 35px; +} +.panel.panel-sortable { + background-color: #fff; +} +.sortable-placeholder { + border: 1px dashed #c1cbd4; + margin-top: 5px; + margin-bottom: 5px; + height: 41px; +} +.panel-grey { + background-color: #f8f9f9; + border-color: #dde2e6; +} + +/** + * Print version + */ +body.print { + margin: 8px; +} +.print #conv-layout-customer, +.print .navbar, +.print .footer, +.print #conv-toolbar, +.print .conv-top-block, +.print .thread-type-draft, +.print .thread-options, +.print .thread-to-first, +.print .thread-status, +.print .thread-photo, +.print .conv-star, +.print .thread-type-lineitem, +.print #conv_tags, +.print .sidebar-2col { + display: none; +} +.print #conv-layout-header, +.print .content-2col { + display: block; + float: none; +} +.print .thread-header, +.print #conv-layout-header, +.print #conv-layout-main { + float: none; + padding: 0; +} +.print .thread { + background-color: #ffffff !important; + box-shadow: none !important; +} +.print .thread .thread-person { + color: #253540; +} +.print .thread-body { + margin: 0; + padding-left: 12px; +} +@media print { + a[href]:after { + content: none !important; + } + .thread-content a[href]:after { + content: " (" attr(href) ") " !important; + } +} + +/** + * Misc + */ +.label { + font-size: 13.4px; + font-weight: normal; + padding-top: 2px; +} +.banner { + text-align: center; + margin-top: 30px; + margin-bottom: 50px; +} +.margin-top { + margin-top: 20px !important; +} +.margin-top-10 { + margin-top: 10px !important; +} +.margin-top-25 { + margin-top: 25px !important; +} +.margin-top-30 { + margin-top: 30px !important; +} +.margin-top-40 { + margin-top: 40px !important; +} +.margin-left { + margin-left: 20px !important; +} +.margin-right { + margin-right: 20px !important; +} +.margin-left-10 { + margin-left: 10px !important; +} +.margin-bottom { + margin-bottom: 20px !important; +} +.margin-bottom-0 { + margin-bottom: 0 !important; +} +.margin-bottom-10 { + margin-bottom: 10px !important; +} +.margin-bottom-30 { + margin-bottom: 30px !important; +} +.margin-bottom-40 { + margin-bottom: 40px !important; +} +.margin-bottom-5 { + margin-bottom: 5px !important; +} +.margin-0 { + margin: 0 !important; +} +.margin-top-0 { + margin-top: 0 !important; +} +.padding-0 { + padding: 0 !important; +} +.padding-top-0 { + padding-top: 0 !important; +} +.padding-left-0 { + padding-left: 0 !important; +} +.padding-right-0 { + padding-right: 0 !important; +} +.heading { + color: #253540; + font-size: 20px; + font-weight: 400; +} +.main-heading { + color: #253540; + font-size: 20px; + font-weight: 400; + background-color: #deecf9; + padding: 12px 18px; +} +.main-heading:after { + content: ""; + display: table; + clear: both; +} +.in-heading { + float: right; + display: inline-block; + font-size: 13.4px; + margin-top: 2px; +} +.in-heading-item { + display: inline-block; + margin-right: 15px; +} +.in-heading-item .btn { + vertical-align: top; +} +.in-heading-item .form-control { + display: inline-block; + width: auto; + max-width: 120px; + margin-left: 5px; +} +.section-heading { + background-color: #deecf9; + /*border-bottom: 1px solid #d6dde3;*/ + padding: 12px 18px; + color: #253540; + font-size: 16px; + font-weight: 400; + line-height: 30px; +} +.section-heading-noborder { + padding: 12px 18px; + color: #253540; + font-size: 16px; + font-weight: 400; + line-height: 30px; +} +.subheading { + font-size: 16px; + margin: 20px 0 15px 15px; + font-weight: bold; +} +.section-search { + line-height: inherit; +} +.flexy { + display: flex; +} +.flexy-container { + box-sizing: border-box; + align-items: center; + display: flex; + justify-content: space-between; + flex-wrap: wrap; +} +.flexy-item { + max-width: 100%; + min-width: 0; + box-sizing: border-box; +} +.flexy-block { + max-width: 100%; + min-width: 0; + flex: 1; +} +.icon-info { + color: #a5b2bd; + cursor: help; + display: inline-block; + font-size: 15px; + margin-left: 7px; + float: left; + top: 7px; +} +.icon-info-inline { + float: none; + top: 0; +} +.help-icon { + color: #a5b2bd; + cursor: pointer; + display: inline-block; + font-size: 15px; + top: 3px; +} +a.help-icon:focus, +a.help-icon:hover { + color: #a5b2bd; + text-decoration: none; +} +.popover { + color:#4f5d6b; + font-size:13.4px; + z-index: 1040; +} +.descr-block { + margin: 20px; + color: #93a1af; +} +.block-help { + color: #93a1af; + display: block; + font-size: 13.4px; + margin: 4px 0 9px; +} +.block-help a { + color: #93a1af; +} +.block-help a:hover { + color: #23527c; +} +.text-help { + color: #93a1af; +} +.text-help a:hover { + color: #23527c; +} +.link-active { + color: #23527c; +} +.link-black, +.link-black:hover, +.link-black:focus { + color: #4f5d6b!important; + font-weight: bold; +} +.link-black:hover { + text-decoration: underline; +} +.link-blue, +.link-blue:hover, +.link-blue:focus { + color: #3197d6!important; +} +.link-blue:hover { + text-decoration: underline; +} +.help-link, +.help-link:hover, +.help-link:focus { + color: #93a1af!important; +} +.help-link:hover { + text-decoration: underline; +} +.link-grey, +.link-grey:hover, +.link-grey:focus { + color: #93a1af!important; + text-decoration: none!important; +} +.link-grey:hover { + color: #4f5d6b!important; + text-decoration: none!important; +} +.link-grey-blue, +.link-grey-blue:focus { + color: #93a1af!important; + text-decoration: none!important; +} +.link-grey-blue:hover { + color: #0078D7!important; + text-decoration: none!important; +} +.link-underlined { + text-decoration: underline; +} +.link-underlined:hover { + text-decoration: none; +} +.wizard-header { + padding: 40px 0 0; + text-align: center; +} +.wizard-header-small { + padding: 25px 0 0; +} +.wizard-header h1 { + font-size: 24px; + margin: 12px 0 10px; + color: #2a3b47; +} +.wizard-header-small h1 { + font-size: 21px; +} +.wizard-header p { + color: #93a1af; + font-size: 15px; + margin: 0; +} +.wizard-header .glyphicon { + position: relative; + top: 3px; +} +.wizard-body { + padding: 10px 0 0; +} +.wizard-footer { + border-radius: 0 0 4px 4px; + padding: 0 50px 40px; + text-align: center; +} +#permissions-fields .control-group { + display: inline-block; + vertical-align: top; + width: 200px; + text-overflow: ellipsis; +} +#permissions-fields .control-label { + margin-top: 0; + margin-bottom: 0; +} +#permissions-fields .control-label { + text-align: left; +} +.control-padded { + padding-top: 4px; +} +.nav-tabs-main { + padding-left: 18px; +} +.nav-tabs-no-bottom { + position: relative; + top: -1px; + border-bottom: 0; +} +.accordion .panel-title > a, +.accordion-disabled .panel-title > div { + display: block; + text-decoration: none; + color: #2a3b47; + font-size: 16px; + font-weight: normal; + padding: 10px 15px; +} +.accordion .panel-title > a .caret { + float: right; + top: 7px; + right: 2px; + position: relative; + border-top-color: #a5b2bd; +} +/*.accordion .panel-title > a .label { + padding-bottom: 1px; + position: relative; + padding-left: 5px; + top: -2px; + font-style: normal; +}*/ +.accordion > .panel-default > .panel-heading { + background-color: #fff; + padding: 0; +} +.accordion > .panel-default > .panel-heading:hover { + background-color: #f4f5f5; +} +.accordion-disabled > .panel-default > .panel-heading:hover { + background-color: #fff; +} +.accordion-status { + margin-left: 4px; +} +.form-divider { + border: 0; + border-top: 1px solid #eee; + margin-top: 10px; + margin-bottom: 10px; +} +.empty-content { + -webkit-box-shadow: none; + box-shadow: none; + margin: 12em auto 0; + text-align: center; + text-align: center; +} +.empty-content i { + font-size: 57px; + color: #a5b2bd; +} +.empty-content p { + color: #a5b2bd; + font-size: 16px; + padding: 0 5%; +} +.multi-item { + margin-bottom: 5px; +} +.multi-item > div { + display: flex; + flex: 1; + flex-basis: auto; + -ms-flex: 1; + -webkit-flex: 1; +} +.multi-remove, +.multi-remove:focus { + color: #c1cbd4; + font-size: 13px; + position: relative; + top: 5px; + margin-left: 5px; + outline: none; +} +.multi-remove:hover { + color: #8899aa; + text-decoration: none; +} +.text-truncate { + overflow: hidden!important; + text-overflow: ellipsis!important; + white-space: nowrap!important; +} +.text-wrap-break { + word-wrap: break-word!important; +} +.text-charcoal { + color: #2a3b47!important; +} +.text-warning-light { + color: #ffc646; +} +.text-large { + font-size: 14px; +} +.text-larger { + font-size: 16.8px; +} +#loader-main { + position: fixed; + display: none; + left: 0px; + top: 0px; + width: 100%; + height: 100%; + z-index: 9999; + background: url('../../img/loader-main.gif') 50% 50% no-repeat #fff; + opacity: 0.60; + filter: alpha(opacity=60); /* For IE8 and earlier */ +} +a.text-danger, +a.text-danger:hover, +a.text-danger:focus { + color: #d9534f!important; +} +a.selected, +a.selected:hover, +a.selected:focus { + color: #93a1af; + text-decoration: none; +} +.break-words { + /* These are technically the same, but use both */ + overflow-wrap: break-word; + word-wrap: break-word; + + -ms-word-break: break-all; + /* This is the dangerous one in WebKit, as it breaks things wherever */ + word-break: break-all; + /* Instead use this non-standard one: */ + word-break: break-word; + + /* Adds a hyphen where the word breaks, if supported (No Blink) */ + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} +.nowrap { + white-space: nowrap; +} +.row-container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +a.disabled, +a.disabled:hover, +a.disabled:focus { + color: #7abce4; + text-decoration: none; + cursor: default; +} +.link-dark { + color: #237ab3!important; +} +.link-dark:hover, +.link-dark:focus { + color: #1b5d88!important; +} +.pre-wrap { + word-wrap: break-word; + white-space: pre-wrap; +} +.panel-shaded { + -webkit-box-shadow:0 0 10px rgba(0,0,0,.2); + box-shadow:0 0 10px rgba(0,0,0,.2); +} +.panel-padded { + padding: 20px; +} +.icon-large { + font-size: 57px; + color: #a5b2bd; +} +#user-photo-delete { + line-height: 25px; +} +.console { + background: #000; + border: 1px groove #ccc; + color: #ccc; + display: block; + padding: 5px; + white-space: pre-wrap; /* Since CSS 2.1 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} +.badge.success { + background-color: #53b961; +} +.badge.danger { + background-color: #d43f3a; +} +.badge .glyphicon { + line-height: 5px; +} + +#conversations-bulk-actions { + z-index: 100; + display: none; + pointer-events: none; +} +#conversations-bulk-actions>.btn-group +{ + pointer-events: auto; +} +#conversations-bulk-actions.affix-top { + position: absolute; +} +#conversations-bulk-actions.affix { + top: 0; + right: 0; +} +#conversations-bulk-actions .btn .caret { + margin-left: 2px; +} +.inline-block { + display: inline-block; +} +.panel-wizard { + background-color: #f8f9f9; + -webkit-box-shadow:0 0 10px rgba(0,0,0,.2); + box-shadow:0 0 10px rgba(0,0,0,.2); +} +.dropdown-menu.with-icons .glyphicon, +.dash-card-footer .dropdown-menu .glyphicon, +.sidebar-buttons .dropdown-menu .glyphicon { + position: relative; + color: #0078D7; + left: -5px; +} +.hover-shade:hover { + -webkit-box-shadow:0 0 13px rgba(0,0,0,.2); + box-shadow:0 0 13px rgba(0,0,0,.2); +} +.dropdown-menu > li > span { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + cursor: default; +} +.jobs-list { + max-height: 300px; + overflow-y: scroll; +} +.conv-reply-block:not(.conv-forward-block) .form-group.cc-shifted { + margin-bottom: -8px; +} +.conv-reply-block:not(.conv-forward-block) .cc-shifted .control-label { + display: none; +} +.conv-reply-block:not(.conv-forward-block) .cc-shifted .conv-reply-field { + padding-left: 0; +} +.conv-note-block .cc-toggler { + display: none; +} +.photo-sm .person-photo { + border-radius: 50%; + width: 21px; + height: 21px; + line-height: 22px; + background-color: #fff; + font-size: 9px; +} +.photo-xs .person-photo { + border-radius: 50%; + width: 20px; + height: 20px; + line-height: 18px; + font-size: 7px; + padding: 1px; + background-color: transparent; +} +.preview-iframe { + width: 100%; + min-height: 50vh; +} +.tab-body { + margin-top: -1px; + padding: 10px; + border: 1px solid #ddd; +} +.clickable { + cursor: pointer; +} +#change-customer-create .form-group { + margin-bottom: 4px; +} +.folder-name { + max-width: 142px; + display: inline-block; + overflow: hidden; + word-wrap: normal; + text-overflow: ellipsis; +} +.mailbox-name .glyphicon { + top: 2px; +} +.sidebar-title-real.mailbox-name .glyphicon { + top: 1px; + font-size: 16px; +} +.subheader { + background-color: #F1F3F5; + padding: 10px; + margin-bottom: 20px; +} +.glyphicon-spin { + -webkit-animation: spin 1500ms infinite linear; + -moz-animation: spin 1500ms infinite linear; + -o-animation: spin 1500ms infinite linear; + animation: spin 1500ms infinite linear; +} +@-moz-keyframes spin { + from { + -moz-transform: rotate(0deg); + } + to { + -moz-transform: rotate(360deg); + } +} +@-webkit-keyframes spin { + from { + -webkit-transform: rotate(0deg); + } + to { + -webkit-transform: rotate(360deg); + } +} +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} +.dm-scrollable { + max-height: 395px; + overflow-y: auto; + overflow-x: hidden; +} +.user-admin-badge { + position: absolute; + top: 0; + right: 7px; + color: #93A1AF; +} +.alert .glyphicon { + vertical-align: text-top; +} +.conv-merge-list { + max-height: 200px; + overflow-y: auto; +} +.conv-merge-list .checkbox, +.conv-merge-search-result .checkbox, +.conv-merge-selected .checkbox { + margin-top: 0; + margin-bottom: 0; +} +.conv-merge-selected .alert { + margin-bottom: 10px; +} +.conv-tags { + float: left; + overflow: hidden; +} +.conv-tags .fs-tag { + margin-top: 8px; + margin-right: 8px; +} +.fs-tag { + background: #9eaab5; + border-radius: 2px; + color: #fff; + display: inline-block; + font-size: 13px; + font-weight: normal; + padding: 2px 4px 1px 4px; + line-height: 14px; + margin: 0 4px 0 0; +} +.fs-tag a { + color: #fff!important; +} +.fs-tag-name, +.fs-tag-name:hover, +.fs-tag-name:focus { + text-decoration: none; + color: #fff; +} +.fs-tag-name:hover[href="#"] { + cursor: default; +} +.fs-tag-md { + padding: 5px 7px 5px 7px; + font-size: 15px; + border-radius: 3px; + background-color: #97a4b0; +} +.fs-tag-green { + background-color: #52ad67; +} +.conv-subject .fs-tag { + margin-right: 6px; + position: relative; + top: 1px; +} +.fs-tag-btn { + margin-top: 8px; + margin-right: 8px; + line-height: 14px; + font-size: 15px; + padding: 4px 6px 4px 6px; + vertical-align: inherit; +} +.glyphicon-spin { + -webkit-animation: spin 1000ms infinite linear; + animation: spin 1000ms infinite linear; +} +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +.chats { + height: calc(100vh - 125px); + overflow-y: auto; + overflow-x: hidden; + margin-right: -11px; +} +.chats.sidebar-menu > li:nth-child(n+2) > a { + padding-left: 22px; +} +.chat-mode .note-statusbar { + display: none; +} +.chat-mode .conv-note-block .note-statusbar { + display: block; +} +.chat-mode .conv-note-block .note-placeholder { + /*color: #fff;*/ + display: none !important; +} +.conv-top-chat { + line-height: 21px; +} +.chats.sidebar-menu > li > a { + height: auto; + border-radius: 0; +} +.chats.sidebar-menu > li { + border-radius: 0; +} +.chat-preview { + color: #93a1af; + font-size: 13.4px; + display: inline-block; + padding-top: 1px; + max-width: 217px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-weight: normal; +} +.chats li { + border-bottom: 1px solid #e3e8eb; +} +.chats li.active { + border-bottom: 0; +} +.chats li.new { + background-color: #f4f8fd; +} +.chat-tags .fs-tag-green { + opacity: 0.9; +} +.chats-load-more { + text-align: center; + height: 45px !important; + line-height: 35px !important; +} +.chats-load-more i { + font-size: 18px !important; + position: static !important; + top: 0 !important; +} + +@media (max-width: 350px) { + .conv-next-prev { + display: none !important; + } +} + +/** + * Mobile + */ +@media (max-width:767px) { + .descr-block { + margin: 20px 0; + } + /* Chat placeholder is not needed on mobile as mobile devices don't have ENTER button */ + .chat-mode .note-placeholder { + display: none !important; + } +} +/** + * Only main content is visible + */ +@media (max-width:991px) { + .col1-hidden { + display: none; + } +} +@media (min-width:992px) { + .sidebar-2col { + overflow-y: auto; + } +} +/** + * Left sidebar and main content are visible + */ +@media (max-width:1100px) { + .col2-hidden { + display: none; + } +} +/** + * Left sidebar, main content and right siderbar are visible + */ +@media (min-width:1101px) { + .col3-hidden { + display: none; + } +} diff --git a/freescout-dist/public/favicon.gif b/freescout-dist/public/favicon.gif new file mode 100644 index 0000000000000000000000000000000000000000..565fa16f49d8e42a054a2922fe7890d7ae09b1c4 GIT binary patch literal 165 zcmZ?wbhEHb6krfw*v!K)|ICMi%`f|pyuJVK*Z#X-1sZO>|Nh5e*2DA9z8g%uU%&e` zL&f#~|Nk?tIFpMxb_$i3FaZQ@oNtU9# N+%!*D(@q8kYXCk&NBsZ* literal 0 HcmV?d00001 diff --git a/freescout-dist/public/favicon.ico b/freescout-dist/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..66d4292324859a6d42dfe42e2a727a50899dd1ce GIT binary patch literal 5430 zcmb_gF>>2L3sI=0hidkh+>Idc%`~;RMRk}0PrHY@xHl3e9=RUBd*aLW0t?a!w7%Go_vaHv8SmnbHtD{o)6=y!+ekD z?D)V}KCLBocz*(>ia3<3MU1Att`%$SLr&Pv!D}6Jf9YBd`hkaMh|R5gx)wPtV7bW8 zjYysSVSM*Rgr*O38(_A;9#F$t{>~Jz62{-7{#m{(m;6QVrRhIDOL+-*sy1joV%2$1 ztqp5ZeCNc2_LN>K-=f~V-!pq4PfBA>e0`3d#X`G)#oyQ*O^azP?>Rp7*I@7N3)J&GWw1$2Z;qihb!Wru;#$U(orX%Fbl%h#IMbqa@kYW^O774hEM`2?SJoH@-(cdg1dA53?qQ6v$CPegJEpC<89ZYesoY=R zlWL__^e}c8DUDQobmk*HaIWpdOjDYSvv#V#_Jsf60elYOJ1} zt=XNfx~|T;PUOC;kd&gcPg)hoM!Lx8yFQQI* z7b-o(6QmOUK!FVxl8n42GLI|0b7vhz&a;52+|lL;d4y~LG}3+q&mH6zG9p}xorLsp zV&o;{Llep&qxG8>L?47`Nd!5mbgJ(O*m`{ENgO+=Y!`v!c0&NgNY*e#dkw=ED6p6e zGyj}T#8R!+JEGPRS0@ zfyg6G17qckF=k`b9?3U%A=W})93<%>Bj8TJk4HK5)UQ~T{QJxF<8^)sFaVr{T>Q8# R3eNxl002ovPDHLkV1gRglsW(a literal 0 HcmV?d00001 diff --git a/freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.eot b/freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..b93a4953fff68df523aa7656497ee339d6026d64 GIT binary patch literal 20127 zcma%hV{j!vx9y2-`@~L8?1^pLwlPU2wr$&<*tR|KBoo`2;LUg6eW-eW-tKDb)vH%` z^`A!Vd<6hNSRMcX|Cb;E|1qflDggj6Kmr)xA10^t-vIc3*Z+F{r%|K(GyE^?|I{=9 zNq`(c8=wS`0!RZy0g3{M(8^tv41d}oRU?8#IBFtJy*9zAN5dcxqGlMZGL>GG%R#)4J zDJ2;)4*E1pyHia%>lMv3X7Q`UoFyoB@|xvh^)kOE3)IL&0(G&i;g08s>c%~pHkN&6 z($7!kyv|A2DsV2mq-5Ku)D#$Kn$CzqD-wm5Q*OtEOEZe^&T$xIb0NUL}$)W)Ck`6oter6KcQG9Zcy>lXip)%e&!lQgtQ*N`#abOlytt!&i3fo)cKV zP0BWmLxS1gQv(r_r|?9>rR0ZeEJPx;Vi|h1!Eo*dohr&^lJgqJZns>&vexP@fs zkPv93Nyw$-kM5Mw^{@wPU47Y1dSkiHyl3dtHLwV&6Tm1iv{ve;sYA}Z&kmH802s9Z zyJEn+cfl7yFu#1^#DbtP7k&aR06|n{LnYFYEphKd@dJEq@)s#S)UA&8VJY@S2+{~> z(4?M();zvayyd^j`@4>xCqH|Au>Sfzb$mEOcD7e4z8pPVRTiMUWiw;|gXHw7LS#U< zsT(}Z5SJ)CRMXloh$qPnK77w_)ctHmgh}QAe<2S{DU^`!uwptCoq!Owz$u6bF)vnb zL`bM$%>baN7l#)vtS3y6h*2?xCk z>w+s)@`O4(4_I{L-!+b%)NZcQ&ND=2lyP+xI#9OzsiY8$c)ys-MI?TG6 zEP6f=vuLo!G>J7F4v|s#lJ+7A`^nEQScH3e?B_jC&{sj>m zYD?!1z4nDG_Afi$!J(<{>z{~Q)$SaXWjj~%ZvF152Hd^VoG14rFykR=_TO)mCn&K$ z-TfZ!vMBvnToyBoKRkD{3=&=qD|L!vb#jf1f}2338z)e)g>7#NPe!FoaY*jY{f)Bf>ohk-K z4{>fVS}ZCicCqgLuYR_fYx2;*-4k>kffuywghn?15s1dIOOYfl+XLf5w?wtU2Og*f z%X5x`H55F6g1>m~%F`655-W1wFJtY>>qNSdVT`M`1Mlh!5Q6#3j={n5#za;!X&^OJ zgq;d4UJV-F>gg?c3Y?d=kvn3eV)Jb^ zO5vg0G0yN0%}xy#(6oTDSVw8l=_*2k;zTP?+N=*18H5wp`s90K-C67q{W3d8vQGmr zhpW^>1HEQV2TG#8_P_0q91h8QgHT~8=-Ij5snJ3cj?Jn5_66uV=*pq(j}yHnf$Ft;5VVC?bz%9X31asJeQF2jEa47H#j` zk&uxf3t?g!tltVP|B#G_UfDD}`<#B#iY^i>oDd-LGF}A@Fno~dR72c&hs6bR z2F}9(i8+PR%R|~FV$;Ke^Q_E_Bc;$)xN4Ti>Lgg4vaip!%M z06oxAF_*)LH57w|gCW3SwoEHwjO{}}U=pKhjKSZ{u!K?1zm1q? zXyA6y@)}_sONiJopF}_}(~}d4FDyp|(@w}Vb;Fl5bZL%{1`}gdw#i{KMjp2@Fb9pg ziO|u7qP{$kxH$qh8%L+)AvwZNgUT6^zsZq-MRyZid{D?t`f|KzSAD~C?WT3d0rO`0 z=qQ6{)&UXXuHY{9g|P7l_nd-%eh}4%VVaK#Nik*tOu9lBM$<%FS@`NwGEbP0&;Xbo zObCq=y%a`jSJmx_uTLa{@2@}^&F4c%z6oe-TN&idjv+8E|$FHOvBqg5hT zMB=7SHq`_-E?5g=()*!V>rIa&LcX(RU}aLm*38U_V$C_g4)7GrW5$GnvTwJZdBmy6 z*X)wi3=R8L=esOhY0a&eH`^fSpUHV8h$J1|o^3fKO|9QzaiKu>yZ9wmRkW?HTkc<*v7i*ylJ#u#j zD1-n&{B`04oG>0Jn{5PKP*4Qsz{~`VVA3578gA+JUkiPc$Iq!^K|}*p_z3(-c&5z@ zKxmdNpp2&wg&%xL3xZNzG-5Xt7jnI@{?c z25=M>-VF|;an2Os$Nn%HgQz7m(ujC}Ii0Oesa(y#8>D+P*_m^X##E|h$M6tJr%#=P zWP*)Px>7z`E~U^2LNCNiy%Z7!!6RI%6fF@#ZY3z`CK91}^J$F!EB0YF1je9hJKU7!S5MnXV{+#K;y zF~s*H%p@vj&-ru7#(F2L+_;IH46X(z{~HTfcThqD%b{>~u@lSc<+f5#xgt9L7$gSK ziDJ6D*R%4&YeUB@yu@4+&70MBNTnjRyqMRd+@&lU#rV%0t3OmouhC`mkN}pL>tXin zY*p)mt=}$EGT2E<4Q>E2`6)gZ`QJhGDNpI}bZL9}m+R>q?l`OzFjW?)Y)P`fUH(_4 zCb?sm1=DD0+Q5v}BW#0n5;Nm(@RTEa3(Y17H2H67La+>ptQHJ@WMy2xRQT$|7l`8c zYHCxYw2o-rI?(fR2-%}pbs$I%w_&LPYE{4bo}vRoAW>3!SY_zH3`ofx3F1PsQ?&iq z*BRG>?<6%z=x#`NhlEq{K~&rU7Kc7Y-90aRnoj~rVoKae)L$3^z*Utppk?I`)CX&& zZ^@Go9fm&fN`b`XY zt0xE5aw4t@qTg_k=!-5LXU+_~DlW?53!afv6W(k@FPPX-`nA!FBMp7b!ODbL1zh58 z*69I}P_-?qSLKj}JW7gP!la}K@M}L>v?rDD!DY-tu+onu9kLoJz20M4urX_xf2dfZ zORd9Zp&28_ff=wdMpXi%IiTTNegC}~RLkdYjA39kWqlA?jO~o1`*B&85Hd%VPkYZT z48MPe62;TOq#c%H(`wX5(Bu>nlh4Fbd*Npasdhh?oRy8a;NB2(eb}6DgwXtx=n}fE zx67rYw=(s0r?EsPjaya}^Qc-_UT5|*@|$Q}*|>V3O~USkIe6a0_>vd~6kHuP8=m}_ zo2IGKbv;yA+TBtlCpnw)8hDn&eq?26gN$Bh;SdxaS04Fsaih_Cfb98s39xbv)=mS0 z6M<@pM2#pe32w*lYSWG>DYqB95XhgAA)*9dOxHr{t)er0Xugoy)!Vz#2C3FaUMzYl zCxy{igFB901*R2*F4>grPF}+G`;Yh zGi@nRjWyG3mR(BVOeBPOF=_&}2IWT%)pqdNAcL{eP`L*^FDv#Rzql5U&Suq_X%JfR_lC!S|y|xd5mQ0{0!G#9hV46S~A` z0B!{yI-4FZEtol5)mNWXcX(`x&Pc*&gh4k{w%0S#EI>rqqlH2xv7mR=9XNCI$V#NG z4wb-@u{PfQP;tTbzK>(DF(~bKp3;L1-A*HS!VB)Ae>Acnvde15Anb`h;I&0)aZBS6 z55ZS7mL5Wp!LCt45^{2_70YiI_Py=X{I3>$Px5Ez0ahLQ+ z9EWUWSyzA|+g-Axp*Lx-M{!ReQO07EG7r4^)K(xbj@%ZU=0tBC5shl)1a!ifM5OkF z0w2xQ-<+r-h1fi7B6waX15|*GGqfva)S)dVcgea`lQ~SQ$KXPR+(3Tn2I2R<0 z9tK`L*pa^+*n%>tZPiqt{_`%v?Bb7CR-!GhMON_Fbs0$#|H}G?rW|{q5fQhvw!FxI zs-5ZK>hAbnCS#ZQVi5K0X3PjL1JRdQO+&)*!oRCqB{wen60P6!7bGiWn@vD|+E@Xq zb!!_WiU^I|@1M}Hz6fN-m04x=>Exm{b@>UCW|c8vC`aNbtA@KCHujh^2RWZC}iYhL^<*Z93chIBJYU&w>$CGZDRcHuIgF&oyesDZ#&mA;?wxx4Cm#c0V$xYG?9OL(Smh}#fFuX(K;otJmvRP{h ze^f-qv;)HKC7geB92_@3a9@MGijS(hNNVd%-rZ;%@F_f7?Fjinbe1( zn#jQ*jKZTqE+AUTEd3y6t>*=;AO##cmdwU4gc2&rT8l`rtKW2JF<`_M#p>cj+)yCG zgKF)y8jrfxTjGO&ccm8RU>qn|HxQ7Z#sUo$q)P5H%8iBF$({0Ya51-rA@!It#NHN8MxqK zrYyl_&=}WVfQ?+ykV4*@F6)=u_~3BebR2G2>>mKaEBPmSW3(qYGGXj??m3L zHec{@jWCsSD8`xUy0pqT?Sw0oD?AUK*WxZn#D>-$`eI+IT)6ki>ic}W)t$V32^ITD zR497@LO}S|re%A+#vdv-?fXsQGVnP?QB_d0cGE+U84Q=aM=XrOwGFN3`Lpl@P0fL$ zKN1PqOwojH*($uaQFh8_)H#>Acl&UBSZ>!2W1Dinei`R4dJGX$;~60X=|SG6#jci} z&t4*dVDR*;+6Y(G{KGj1B2!qjvDYOyPC}%hnPbJ@g(4yBJrViG1#$$X75y+Ul1{%x zBAuD}Q@w?MFNqF-m39FGpq7RGI?%Bvyyig&oGv)lR>d<`Bqh=p>urib5DE;u$c|$J zwim~nPb19t?LJZsm{<(Iyyt@~H!a4yywmHKW&=1r5+oj*Fx6c89heW@(2R`i!Uiy* zp)=`Vr8sR!)KChE-6SEIyi(dvG3<1KoVt>kGV=zZiG7LGonH1+~yOK-`g0)r#+O|Q>)a`I2FVW%wr3lhO(P{ksNQuR!G_d zeTx(M!%brW_vS9?IF>bzZ2A3mWX-MEaOk^V|4d38{1D|KOlZSjBKrj7Fgf^>JyL0k zLoI$adZJ0T+8i_Idsuj}C;6jgx9LY#Ukh;!8eJ^B1N}q=Gn4onF*a2vY7~`x$r@rJ z`*hi&Z2lazgu{&nz>gjd>#eq*IFlXed(%$s5!HRXKNm zDZld+DwDI`O6hyn2uJ)F^{^;ESf9sjJ)wMSKD~R=DqPBHyP!?cGAvL<1|7K-(=?VO zGcKcF1spUa+ki<`6K#@QxOTsd847N8WSWztG~?~ z!gUJn>z0O=_)VCE|56hkT~n5xXTp}Ucx$Ii%bQ{5;-a4~I2e|{l9ur#*ghd*hSqO= z)GD@ev^w&5%k}YYB~!A%3*XbPPU-N6&3Lp1LxyP@|C<{qcn&?l54+zyMk&I3YDT|E z{lXH-e?C{huu<@~li+73lMOk&k)3s7Asn$t6!PtXJV!RkA`qdo4|OC_a?vR!kE_}k zK5R9KB%V@R7gt@9=TGL{=#r2gl!@3G;k-6sXp&E4u20DgvbY$iE**Xqj3TyxK>3AU z!b9}NXuINqt>Htt6fXIy5mj7oZ{A&$XJ&thR5ySE{mkxq_YooME#VCHm2+3D!f`{) zvR^WSjy_h4v^|!RJV-RaIT2Ctv=)UMMn@fAgjQV$2G+4?&dGA8vK35c-8r)z9Qqa=%k(FU)?iec14<^olkOU3p zF-6`zHiDKPafKK^USUU+D01>C&Wh{{q?>5m zGQp|z*+#>IIo=|ae8CtrN@@t~uLFOeT{}vX(IY*;>wAU=u1Qo4c+a&R);$^VCr>;! zv4L{`lHgc9$BeM)pQ#XA_(Q#=_iSZL4>L~8Hx}NmOC$&*Q*bq|9Aq}rWgFnMDl~d*;7c44GipcpH9PWaBy-G$*MI^F0 z?Tdxir1D<2ui+Q#^c4?uKvq=p>)lq56=Eb|N^qz~w7rsZu)@E4$;~snz+wIxi+980O6M#RmtgLYh@|2}9BiHSpTs zacjGKvwkUwR3lwTSsCHlwb&*(onU;)$yvdhikonn|B44JMgs*&Lo!jn`6AE>XvBiO z*LKNX3FVz9yLcsnmL!cRVO_qv=yIM#X|u&}#f%_?Tj0>8)8P_0r0!AjWNw;S44tst zv+NXY1{zRLf9OYMr6H-z?4CF$Y%MdbpFIN@a-LEnmkcOF>h16cH_;A|e)pJTuCJ4O zY7!4FxT4>4aFT8a92}84>q0&?46h>&0Vv0p>u~k&qd5$C1A6Q$I4V(5X~6{15;PD@ ze6!s9xh#^QI`J+%8*=^(-!P!@9%~buBmN2VSAp@TOo6}C?az+ALP8~&a0FWZk*F5N z^8P8IREnN`N0i@>O0?{i-FoFShYbUB`D7O4HB`Im2{yzXmyrg$k>cY6A@>bf7i3n0 z5y&cf2#`zctT>dz+hNF&+d3g;2)U!#vsb-%LC+pqKRTiiSn#FH#e!bVwR1nAf*TG^ z!RKcCy$P>?Sfq6n<%M{T0I8?p@HlgwC!HoWO>~mT+X<{Ylm+$Vtj9};H3$EB}P2wR$3y!TO#$iY8eO-!}+F&jMu4%E6S>m zB(N4w9O@2=<`WNJay5PwP8javDp~o~xkSbd4t4t8)9jqu@bHmJHq=MV~Pt|(TghCA}fhMS?s-{klV>~=VrT$nsp7mf{?cze~KKOD4 z_1Y!F)*7^W+BBTt1R2h4f1X4Oy2%?=IMhZU8c{qk3xI1=!na*Sg<=A$?K=Y=GUR9@ zQ(ylIm4Lgm>pt#%p`zHxok%vx_=8Fap1|?OM02|N%X-g5_#S~sT@A!x&8k#wVI2lo z1Uyj{tDQRpb*>c}mjU^gYA9{7mNhFAlM=wZkXcA#MHXWMEs^3>p9X)Oa?dx7b%N*y zLz@K^%1JaArjgri;8ptNHwz1<0y8tcURSbHsm=26^@CYJ3hwMaEvC7 z3Wi-@AaXIQ)%F6#i@%M>?Mw7$6(kW@?et@wbk-APcvMCC{>iew#vkZej8%9h0JSc? zCb~K|!9cBU+))^q*co(E^9jRl7gR4Jihyqa(Z(P&ID#TPyysVNL7(^;?Gan!OU>au zN}miBc&XX-M$mSv%3xs)bh>Jq9#aD_l|zO?I+p4_5qI0Ms*OZyyxA`sXcyiy>-{YN zA70%HmibZYcHW&YOHk6S&PQ+$rJ3(utuUra3V0~@=_~QZy&nc~)AS>v&<6$gErZC3 zcbC=eVkV4Vu0#}E*r=&{X)Kgq|8MGCh(wsH4geLj@#8EGYa})K2;n z{1~=ghoz=9TSCxgzr5x3@sQZZ0FZ+t{?klSI_IZa16pSx6*;=O%n!uXVZ@1IL;JEV zfOS&yyfE9dtS*^jmgt6>jQDOIJM5Gx#Y2eAcC3l^lmoJ{o0T>IHpECTbfYgPI4#LZq0PKqnPCD}_ zyKxz;(`fE0z~nA1s?d{X2!#ZP8wUHzFSOoTWQrk%;wCnBV_3D%3@EC|u$Ao)tO|AO z$4&aa!wbf}rbNcP{6=ajgg(`p5kTeu$ji20`zw)X1SH*x zN?T36{d9TY*S896Ijc^!35LLUByY4QO=ARCQ#MMCjudFc7s!z%P$6DESz%zZ#>H|i zw3Mc@v4~{Eke;FWs`5i@ifeYPh-Sb#vCa#qJPL|&quSKF%sp8*n#t?vIE7kFWjNFh zJC@u^bRQ^?ra|%39Ux^Dn4I}QICyDKF0mpe+Bk}!lFlqS^WpYm&xwIYxUoS-rJ)N9 z1Tz*6Rl9;x`4lwS1cgW^H_M*)Dt*DX*W?ArBf?-t|1~ge&S}xM0K;U9Ibf{okZHf~ z#4v4qc6s6Zgm8iKch5VMbQc~_V-ZviirnKCi*ouN^c_2lo&-M;YSA>W>>^5tlXObg zacX$k0=9Tf$Eg+#9k6yV(R5-&F{=DHP8!yvSQ`Y~XRnUx@{O$-bGCksk~3&qH^dqX zkf+ZZ?Nv5u>LBM@2?k%k&_aUb5Xjqf#!&7%zN#VZwmv65ezo^Y4S#(ed0yUn4tFOB zh1f1SJ6_s?a{)u6VdwUC!Hv=8`%T9(^c`2hc9nt$(q{Dm2X)dK49ba+KEheQ;7^0) ziFKw$%EHy_B1)M>=yK^=Z$U-LT36yX>EKT zvD8IAom2&2?bTmX@_PBR4W|p?6?LQ+&UMzXxqHC5VHzf@Eb1u)kwyfy+NOM8Wa2y@ zNNDL0PE$F;yFyf^jy&RGwDXQwYw6yz>OMWvJt98X@;yr!*RQDBE- zE*l*u=($Zi1}0-Y4lGaK?J$yQjgb+*ljUvNQ!;QYAoCq@>70=sJ{o{^21^?zT@r~hhf&O;Qiq+ ziGQQLG*D@5;LZ%09mwMiE4Q{IPUx-emo*;a6#DrmWr(zY27d@ezre)Z1BGZdo&pXn z+);gOFelKDmnjq#8dL7CTiVH)dHOqWi~uE|NM^QI3EqxE6+_n>IW67~UB#J==QOGF zp_S)c8TJ}uiaEiaER}MyB(grNn=2m&0yztA=!%3xUREyuG_jmadN*D&1nxvjZ6^+2 zORi7iX1iPi$tKasppaR9$a3IUmrrX)m*)fg1>H+$KpqeB*G>AQV((-G{}h=qItj|d zz~{5@{?&Dab6;0c7!!%Se>w($RmlG7Jlv_zV3Ru8b2rugY0MVPOOYGlokI7%nhIy& z-B&wE=lh2dtD!F?noD{z^O1~Tq4MhxvchzuT_oF3-t4YyA*MJ*n&+1X3~6quEN z@m~aEp=b2~mP+}TUP^FmkRS_PDMA{B zaSy(P=$T~R!yc^Ye0*pl5xcpm_JWI;@-di+nruhqZ4gy7cq-)I&s&Bt3BkgT(Zdjf zTvvv0)8xzntEtp4iXm}~cT+pi5k{w{(Z@l2XU9lHr4Vy~3ycA_T?V(QS{qwt?v|}k z_ST!s;C4!jyV5)^6xC#v!o*uS%a-jQ6< z)>o?z7=+zNNtIz1*F_HJ(w@=`E+T|9TqhC(g7kKDc8z~?RbKQ)LRMn7A1p*PcX2YR zUAr{);~c7I#3Ssv<0i-Woj0&Z4a!u|@Xt2J1>N-|ED<3$o2V?OwL4oQ%$@!zLamVz zB)K&Ik^~GOmDAa143{I4?XUk1<3-k{<%?&OID&>Ud%z*Rkt*)mko0RwC2=qFf-^OV z=d@47?tY=A;=2VAh0mF(3x;!#X!%{|vn;U2XW{(nu5b&8kOr)Kop3-5_xnK5oO_3y z!EaIb{r%D{7zwtGgFVri4_!yUIGwR(xEV3YWSI_+E}Gdl>TINWsIrfj+7DE?xp+5^ zlr3pM-Cbse*WGKOd3+*Qen^*uHk)+EpH-{u@i%y}Z!YSid<}~kA*IRSk|nf+I1N=2 zIKi+&ej%Al-M5`cP^XU>9A(m7G>58>o|}j0ZWbMg&x`*$B9j#Rnyo0#=BMLdo%=ks zLa3(2EinQLXQ(3zDe7Bce%Oszu%?8PO648TNst4SMFvj=+{b%)ELyB!0`B?9R6aO{i-63|s@|raSQGL~s)9R#J#duFaTSZ2M{X z1?YuM*a!!|jP^QJ(hAisJuPOM`8Y-Hzl~%d@latwj}t&0{DNNC+zJARnuQfiN`HQ# z?boY_2?*q;Qk)LUB)s8(Lz5elaW56p&fDH*AWAq7Zrbeq1!?FBGYHCnFgRu5y1jwD zc|yBz+UW|X`zDsc{W~8m$sh@VVnZD$lLnKlq@Hg^;ky!}ZuPdKNi2BI70;hrpvaA4+Q_+K)I@|)q1N-H zrycZU`*YUW``Qi^`bDX-j7j^&bO+-Xg$cz2#i##($uyW{Nl&{DK{=lLWV3|=<&si||2)l=8^8_z+Vho-#5LB0EqQ3v5U#*DF7 zxT)1j^`m+lW}p$>WSIG1eZ>L|YR-@Feu!YNWiw*IZYh03mq+2QVtQ}1ezRJM?0PA< z;mK(J5@N8>u@<6Y$QAHWNE};rR|)U_&bv8dsnsza7{=zD1VBcxrALqnOf-qW(zzTn zTAp|pEo#FsQ$~*$j|~Q;$Zy&Liu9OM;VF@#_&*nL!N2hH!Q6l*OeTxq!l>dEc{;Hw zCQni{iN%jHU*C;?M-VUaXxf0FEJ_G=C8)C-wD!DvhY+qQ#FT3}Th8;GgV&AV94F`D ztT6=w_Xm8)*)dBnDkZd~UWL|W=Glu!$hc|1w7_7l!3MAt95oIp4Xp{M%clu&TXehO z+L-1#{mjkpTF@?|w1P98OCky~S%@OR&o75P&ZHvC}Y=(2_{ib(-Al_7aZ^U?s34#H}= zGfFi5%KnFVCKtdO^>Htpb07#BeCXMDO8U}crpe1Gm`>Q=6qB4i=nLoLZ%p$TY=OcP z)r}Et-Ed??u~f09d3Nx3bS@ja!fV(Dfa5lXxRs#;8?Y8G+Qvz+iv7fiRkL3liip}) z&G0u8RdEC9c$$rdU53=MH`p!Jn|DHjhOxHK$tW_pw9wCTf0Eo<){HoN=zG!!Gq4z4 z7PwGh)VNPXW-cE#MtofE`-$9~nmmj}m zlzZscQ2+Jq%gaB9rMgVJkbhup0Ggpb)&L01T=%>n7-?v@I8!Q(p&+!fd+Y^Pu9l+u zek(_$^HYFVRRIFt@0Fp52g5Q#I`tC3li`;UtDLP*rA{-#Yoa5qp{cD)QYhldihWe+ zG~zuaqLY~$-1sjh2lkbXCX;lq+p~!2Z=76cvuQe*Fl>IFwpUBP+d^&E4BGc{m#l%Kuo6#{XGoRyFc%Hqhf|%nYd<;yiC>tyEyk z4I+a`(%%Ie=-*n z-{mg=j&t12)LH3R?@-B1tEb7FLMePI1HK0`Ae@#)KcS%!Qt9p4_fmBl5zhO10n401 zBSfnfJ;?_r{%R)hh}BBNSl=$BiAKbuWrNGQUZ)+0=Mt&5!X*D@yGCSaMNY&@`;^a4 z;v=%D_!K!WXV1!3%4P-M*s%V2b#2jF2bk!)#2GLVuGKd#vNpRMyg`kstw0GQ8@^k^ zuqK5uR<>FeRZ#3{%!|4X!hh7hgirQ@Mwg%%ez8pF!N$xhMNQN((yS(F2-OfduxxKE zxY#7O(VGfNuLv-ImAw5+h@gwn%!ER;*Q+001;W7W^waWT%@(T+5k!c3A-j)a8y11t zx4~rSN0s$M8HEOzkcWW4YbKK9GQez2XJ|Nq?TFy;jmGbg;`m&%U4hIiarKmdTHt#l zL=H;ZHE?fYxKQQXKnC+K!TAU}r086{4m}r()-QaFmU(qWhJlc$eas&y?=H9EYQy8N$8^bni9TpDp zkA^WRs?KgYgjxX4T6?`SMs$`s3vlut(YU~f2F+id(Rf_)$BIMibk9lACI~LA+i7xn z%-+=DHV*0TCTJp~-|$VZ@g2vmd*|2QXV;HeTzt530KyK>v&253N1l}bP_J#UjLy4) zBJili9#-ey8Kj(dxmW^ctorxd;te|xo)%46l%5qE-YhAjP`Cc03vT)vV&GAV%#Cgb zX~2}uWNvh`2<*AuxuJpq>SyNtZwzuU)r@@dqC@v=Ocd(HnnzytN+M&|Qi#f4Q8D=h ziE<3ziFW%+!yy(q{il8H44g^5{_+pH60Mx5Z*FgC_3hKxmeJ+wVuX?T#ZfOOD3E4C zRJsj#wA@3uvwZwHKKGN{{Ag+8^cs?S4N@6(Wkd$CkoCst(Z&hp+l=ffZ?2m%%ffI3 zdV7coR`R+*dPbNx=*ivWeNJK=Iy_vKd`-_Hng{l?hmp=|T3U&epbmgXXWs9ySE|=G zeQ|^ioL}tveN{s72_&h+F+W;G}?;?_s@h5>DX(rp#eaZ!E=NivgLI zWykLKev+}sHH41NCRm7W>K+_qdoJ8x9o5Cf!)|qLtF7Izxk*p|fX8UqEY)_sI_45O zL2u>x=r5xLE%s|d%MO>zU%KV6QKFiEeo12g#bhei4!Hm+`~Fo~4h|BJ)%ENxy9)Up zOxupSf1QZWun=)gF{L0YWJ<(r0?$bPFANrmphJ>kG`&7E+RgrWQi}ZS#-CQJ*i#8j zM_A0?w@4Mq@xvk^>QSvEU|VYQoVI=TaOrsLTa`RZfe8{9F~mM{L+C`9YP9?OknLw| zmkvz>cS6`pF0FYeLdY%>u&XpPj5$*iYkj=m7wMzHqzZ5SG~$i_^f@QEPEC+<2nf-{ zE7W+n%)q$!5@2pBuXMxhUSi*%F>e_g!$T-_`ovjBh(3jK9Q^~OR{)}!0}vdTE^M+m z9QWsA?xG>EW;U~5gEuKR)Ubfi&YWnXV;3H6Zt^NE725*`;lpSK4HS1sN?{~9a4JkD z%}23oAovytUKfRN87XTH2c=kq1)O5(fH_M3M-o{{@&~KD`~TRot-gqg7Q2U2o-iiF}K>m?CokhmODaLB z1p6(6JYGntNOg(s!(>ZU&lzDf+Ur)^Lirm%*}Z>T)9)fAZ9>k(kvnM;ab$ptA=hoh zVgsVaveXbMpm{|4*d<0>?l_JUFOO8A3xNLQOh%nVXjYI6X8h?a@6kDe5-m&;M0xqx z+1U$s>(P9P)f0!{z%M@E7|9nn#IWgEx6A6JNJ(7dk`%6$3@!C!l;JK-p2?gg+W|d- ziEzgk$w7k48NMqg$CM*4O~Abj3+_yUKTyK1p6GDsGEs;}=E_q>^LI-~pym$qhXPJf z2`!PJDp4l(TTm#|n@bN!j;-FFOM__eLl!6{*}z=)UAcGYloj?bv!-XY1TA6Xz;82J zLRaF{8ayzGa|}c--}|^xh)xgX>6R(sZD|Z|qX50gu=d`gEwHqC@WYU7{%<5VOnf9+ zB@FX?|UL%`8EIAe!*UdYl|6wRz6Y>(#8x92$#y}wMeE|ZM2X*c}dKJ^4NIf;Fm zNwzq%QcO?$NR-7`su!*$dlIKo2y(N;qgH@1|8QNo$0wbyyJ2^}$iZ>M{BhBjTdMjK z>gPEzgX4;g3$rU?jvDeOq`X=>)zdt|jk1Lv3u~bjHI=EGLfIR&+K3ldcc4D&Um&04 z3^F*}WaxR(ZyaB>DlmF_UP@+Q*h$&nsOB#gwLt{1#F4i-{A5J@`>B9@{^i?g_Ce&O z<<}_We-RUFU&&MHa1#t56u_oM(Ljn7djja!T|gcxSoR=)@?owC*NkDarpBj=W4}=i1@)@L|C) zQKA+o<(pMVp*Su(`zBC0l1yTa$MRfQ#uby|$mlOMs=G`4J|?apMzKei%jZql#gP@IkOaOjB7MJM=@1j(&!jNnyVkn5;4lvro1!vq ztXiV8HYj5%)r1PPpIOj)f!>pc^3#LvfZ(hz}C@-3R(Cx7R427*Fwd!XO z4~j&IkPHcBm0h_|iG;ZNrYdJ4HI!$rSyo&sibmwIgm1|J#g6%>=ML1r!kcEhm(XY& zD@mIJt;!O%WP7CE&wwE3?1-dt;RTHdm~LvP7K`ccWXkZ0kfFa2S;wGtx_a}S2lslw z$<4^Jg-n#Ypc(3t2N67Juasu=h)j&UNTPNDil4MQMTlnI81kY46uMH5B^U{~nmc6+ z9>(lGhhvRK9ITfpAD!XQ&BPphL3p8B4PVBN0NF6U49;ZA0Tr75AgGw7(S=Yio+xg_ zepZ*?V#KD;sHH+15ix&yCs0eSB-Z%D%uujlXvT#V$Rz@$+w!u#3GIo*AwMI#Bm^oO zLr1e}k5W~G0xaO!C%Mb{sarxWZ4%Dn9vG`KHmPC9GWZwOOm11XJp#o0-P-${3m4g( z6~)X9FXw%Xm~&99tj>a-ri})ZcnsfJtc10F@t9xF5vq6E)X!iUXHq-ohlO`gQdS&k zZl})3k||u)!_=nNlvMbz%AuIr89l#I$;rG}qvDGiK?xTd5HzMQkw*p$YvFLGyQM!J zNC^gD!kP{A84nGosi~@MLKqWQNacfs7O$dkZtm4-BZ~iA8xWZPkTK!HpA5zr!9Z&+icfAJ1)NWkTd!-9`NWU>9uXXUr;`Js#NbKFgrNhTcY4GNv*71}}T zFJh?>=EcbUd2<|fiL+H=wMw8hbX6?+_cl4XnCB#ddwdG>bki* zt*&6Dy&EIPluL@A3_;R%)shA-tDQA1!Tw4ffBRyy;2n)vm_JV06(4Or&QAOKNZB5f(MVC}&_!B>098R{Simr!UG}?CW1Ah+X+0#~0`X)od zLYablwmFxN21L))!_zc`IfzWi`5>MxPe(DmjjO1}HHt7TJtAW+VXHt!aKZk>y6PoMsbDXRJnov;D~Ur~2R_7(Xr)aa%wJwZhS3gr7IGgt%@;`jpL@gyc6bGCVx!9CE7NgIbUNZ!Ur1RHror0~ zr(j$^yM4j`#c2KxSP61;(Tk^pe7b~}LWj~SZC=MEpdKf;B@on9=?_n|R|0q;Y*1_@ z>nGq>)&q!;u-8H)WCwtL&7F4vbnnfSAlK1mwnRq2&gZrEr!b1MA z(3%vAbh3aU-IX`d7b@q`-WiT6eitu}ZH9x#d&qx}?CtDuAXak%5<-P!{a`V=$|XmJ zUn@4lX6#ulB@a=&-9HG)a>KkH=jE7>&S&N~0X0zD=Q=t|7w;kuh#cU=NN7gBGbQTT z;?bdSt8V&IIi}sDTzA0dkU}Z-Qvg;RDe8v>468p3*&hbGT1I3hi9hh~Z(!H}{+>eUyF)H&gdrX=k$aB%J6I;6+^^kn1mL+E+?A!A}@xV(Qa@M%HD5C@+-4Mb4lI=Xp=@9+^x+jhtOc zYgF2aVa(uSR*n(O)e6tf3JEg2xs#dJfhEmi1iOmDYWk|wXNHU?g23^IGKB&yHnsm7 zm_+;p?YpA#N*7vXCkeN2LTNG`{QDa#U3fcFz7SB)83=<8rF)|udrEbrZL$o6W?oDR zQx!178Ih9B#D9Ko$H(jD{4MME&<|6%MPu|TfOc#E0B}!j^MMpV69D#h2`vsEQ{(?c zJ3Lh!3&=yS5fWL~;1wCZ?)%nmK`Eqgcu)O6rD^3%ijcxL50^z?OI(LaVDvfL0#zjZ z2?cPvC$QCzpxpt5jMFp05OxhK0F!Q`rPhDi5)y=-0C} zIM~ku&S@pl1&0=jl+rlS<4`riV~LC-#pqNde@44MB(j%)On$0Ko(@q?4`1?4149Z_ zZi!5aU@2vM$dHR6WSZpj+VboK+>u-CbNi7*lw4K^ZxxM#24_Yc`jvb9NPVi75L+MlM^U~`;a7`4H0L|TYK>%hfEfXLsu1JGM zbh|8{wuc7ucV+`Ys1kqxsj`dajwyM;^X^`)#<+a~$WFy8b2t_RS{8yNYKKlnv+>vB zX(QTf$kqrJ;%I@EwEs{cIcH@Z3|#^S@M+5jsP<^`@8^I4_8MlBb`~cE^n+{{;qW2q z=p1=&+fUo%T{GhVX@;56kH8K_%?X=;$OTYqW1L*)hzelm^$*?_K;9JyIWhsn4SK(| zSmXLTUE8VQX{se#8#Rj*lz`xHtT<61V~fb;WZUpu(M)f#;I+2_zR+)y5Jv?l`CxAinx|EY!`IJ*x9_gf_k&Gx2alL!hK zUWj1T_pk|?iv}4EP#PZvYD_-LpzU!NfcLL%fK&r$W8O1KH9c2&GV~N#T$kaXGvAOl)|T zuF9%6(i=Y3q?X%VK-D2YIYFPH3f|g$TrXW->&^Ab`WT z7>Oo!u1u40?jAJ8Hy`bv}qbgs8)cF0&qeVjD?e+3Ggn1Im>K77ZSpbU*08 zfZkIFcv?y)!*B{|>nx@cE{KoutP+seQU?bCGE`tS0GKUO3PN~t=2u7q_6$l;uw^4c zVu^f{uaqsZ{*a-N?2B8ngrLS8E&s6}Xtv9rR9C^b`@q8*iH)pFzf1|kCfiLw6u{Z%aC z!X^5CzF6qofFJgklJV3oc|Qc2XdFl+y5M9*P8}A>Kh{ zWRgRwMSZ(?Jw;m%0etU5BsWT-Dj-5F;Q$OQJrQd+lv`i6>MhVo^p*^w6{~=fhe|bN z*37oV0kji)4an^%3ABbg5RC;CS50@PV5_hKfXjYx+(DqQdKC^JIEMo6X66$qDdLRc z!YJPSKnbY`#Ht6`g@xGzJmKzzn|abYbP+_Q(v?~~ z96%cd{E0BCsH^0HaWt{y(Cuto4VE7jhB1Z??#UaU(*R&Eo+J`UN+8mcb51F|I|n*J zJCZ3R*OdyeS9hWkc_mA7-br>3Tw=CX2bl(=TpVt#WP8Bg^vE_9bP&6ccAf3lFMgr` z{3=h@?Ftb$RTe&@IQtiJfV;O&4fzh)e1>7seG; z=%mA4@c7{aXeJnhEg2J@Bm;=)j=O=cl#^NNkQ<{r;Bm|8Hg}bJ-S^g4`|itx)~!LN zXtL}?f1Hs6UQ+f0-X6&TBCW=A4>bU0{rv8C4T!(wD-h>VCK4YJk`6C9$by!fxOYw- zV#n+0{E(0ttq_#16B} ze8$E#X9o{B!0vbq#WUwmv5Xz6{(!^~+}sBW{xctdNHL4^vDk!0E}(g|W_q;jR|ZK< z8w>H-8G{%R#%f!E7cO_^B?yFRKLOH)RT9GJsb+kAKq~}WIF)NRLwKZ^Q;>!2MNa|} z-mh?=B;*&D{Nd-mQRcfVnHkChI=DRHU4ga%xJ%+QkBd|-d9uRI76@BT(bjsjwS+r) zvx=lGNLv1?SzZ;P)Gnn>04fO7Culg*?LmbEF0fATG8S@)oJ>NT3pYAXa*vX!eUTDF ziBrp(QyDqr0ZMTr?4uG_Nqs6f%S0g?h`1vO5fo=5S&u#wI2d4+3hWiolEU!=3_oFo zfie?+4W#`;1dd#X@g9Yj<53S<6OB!TM8w8})7k-$&q5(smc%;r z(BlXkTp`C47+%4JA{2X}MIaPbVF!35P#p;u7+fR*46{T+LR8+j25oduCfDzDv6R-hU{TVVo9fz?^N3ShMt!t0NsH)pB zRK8-S{Dn*y3b|k^*?_B70<2gHt==l7c&cT>r`C#{S}J2;s#d{M)ncW(#Y$C*lByLQ z&?+{dR7*gpdT~(1;M(FfF==3z`^eW)=5a9RqvF-)2?S-(G zhS;p(u~_qBum*q}On@$#08}ynd0+spzyVco0%G6;<-i5&016cV5UKzhQ~)fX03|>L z8ej+HzzgVr6_5ZUpa4HW0Ca!=r1%*}Oo;2no&Zz8DfR)L!@r<5 z2viSZpmvo5XqXyAz{Ms7`7kX>fnr1gi4X~7KpznRT0{Xc5Cfz@43PjBMBoH@z_{~( z(Wd}IPJ9hH+%)Fc)0!hrV+(A;76rhtI|YHbEDeERV~Ya>SQg^IvlazFkSK(KG9&{q zkPIR~EeQaaBmwA<20}mBO?)N$(z1@p)5?%}rM| zGF()~Z&Kx@OIDRI$d0T8;JX@vj3^2%pd_+@l9~a4lntZ;AvUIjqIZbuNTR6@hNJoV zk4F;ut)LN4ARuyn2M6F~eg-e#UH%2P;8uPGFW^vq1vj8mdIayFOZo(tphk8C7hpT~ z1Fv8?b_LNR3QD9J+!v=p%}# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.ttf b/freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1413fc609ab6f21774de0cb7e01360095584f65b GIT binary patch literal 45404 zcmd?Sd0-pWwLh*qi$?oCk~i6sWlOeWJC3|4juU5JNSu9hSVACzERcmjLV&P^utNzg zIE4Kr1=5g!SxTX#Ern9_%4&01rlrW`Z!56xXTGQR4C z3vR~wXq>NDx$c~e?;ia3YjJ*$!C>69a?2$lLyhpI!CFfJsP=|`8@K0|bbMpWwVUEygg0=0x_)HeHpGSJagJNLA3c!$EuOV>j$wi! zbo{vZ(s8tl>@!?}dmNHXo)ABy7ohD7_1G-P@SdJWT8*oeyBVYVW9*vn}&VI4q++W;Z+uz=QTK}^C75!`aFYCX# zf7fC2;o`%!huaTNJAB&VWrx=szU=VLhwnbT`vc<#<`4WI6n_x@AofA~2d90o?1L3w z9!I|#P*NQ)$#9aASijuw>JRld^-t)Zhmy|i-`Iam|IWkguaMR%lhi4p~cX-9& zjfbx}yz}s`4-6>D^+6FzihR)Y!GsUy=_MWi_v7y#KmYi-{iZ+s@ekkq!@Wxz!~BQwiI&ti z>hC&iBe2m(dpNVvSbZe3DVgl(dxHt-k@{xv;&`^c8GJY%&^LpM;}7)B;5Qg5J^E${ z7z~k8eWOucjX6)7q1a%EVtmnND8cclz8R1=X4W@D8IDeUGXxEWe&p>Z*voO0u_2!! zj3dT(Ki+4E;uykKi*yr?w6!BW2FD55PD6SMj`OfBLwXL5EA-9KjpMo4*5Eqs^>4&> z8PezAcn!9jk-h-Oo!E9EjX8W6@EkTHeI<@AY{f|5fMW<-Ez-z)xCvW3()Z#x0oydB zzm4MzY^NdpIF9qMp-jU;99LjlgY@@s+=z`}_%V*xV7nRV*Kwrx-i`FzI0BZ#yOI8# z!SDeNA5b6u9!Imj89v0(g$;dT_y|Yz!3V`i{{_dez8U@##|X9A};s^7vEd!3AcdyVlhVk$v?$O442KIM1-wX^R{U7`JW&lPr3N(%kXfXT_`7w^? z=#ntx`tTF|N$UT?pELvw7T*2;=Q-x@KmDUIbLyXZ>f5=y7z1DT<7>Bp0k;eItHF?1 zErzhlD2B$Tm|^7DrxnTYm-tgg`Mt4Eivp5{r$o9e)8(fXBO4g|G^6Xy?y$SM*&V52 z6SR*%`%DZC^w(gOWQL?6DRoI*hBNT)xW9sxvmi@!vI^!mI$3kvAMmR_q#SGn3zRb_ zGe$=;Tv3dXN~9XuIHow*NEU4y&u}FcZEZoSlXb9IBOA}!@J3uovp}yerhPMaiI8|SDhvWVr z^BE&yx6e3&RYqIg;mYVZ*3#A-cDJ;#ms4txEmwm@g^s`BB}KmSr7K+ruIoKs=s|gOXP|2 zb1!)87h9?(+1^QRWb(Vo8+@G=o24gyuzF3ytfsKjTHZJ}o{YznGcTDm!s)DRnmOX} z3pPL4wExoN$kyc2>#J`k+<67sy-VsfbQ-1u+HkyFR?9G`9r6g4*8!(!c65Be-5hUg zZHY$M0k(Yd+DT1*8)G(q)1&tDl=g9H7!bZTOvEEFnBOk_K=DXF(d4JOaH zI}*A3jGmy{gR>s}EQzyJa_q_?TYPNXRU1O;fcV_&TQZhd{@*8Tgpraf~nT0BYktu*n{a~ub^UUqQPyr~yBY{k2O zgV)honv{B_CqY|*S~3up%Wn%7i*_>Lu|%5~j)}rQLT1ZN?5%QN`LTJ}vA!EE=1`So z!$$Mv?6T)xk)H8JTrZ~m)oNXxS}pwPd#);<*>zWsYoL6iK!gRSBB{JCgB28C#E{T? z5VOCMW^;h~eMke(w6vLlKvm!!TyIf;k*RtK)|Q>_@nY#J%=h%aVb)?Ni_By)XNxY)E3`|}_u}fn+Kp^3p4RbhFUBRtGsDyx9Eolg77iWN z2iH-}CiM!pfYDIn7;i#Ui1KG01{3D<{e}uWTdlX4Vr*nsb^>l0%{O?0L9tP|KGw8w z+T5F}md>3qDZQ_IVkQ|BzuN08uN?SsVt$~wcHO4pB9~ykFTJO3g<4X({-Tm1w{Ufo zI03<6KK`ZjqVyQ(>{_aMxu7Zm^ck&~)Q84MOsQ-XS~{6j>0lTl@lMtfWjj;PT{nlZ zIn0YL?kK7CYJa)(8?unZ)j8L(O}%$5S#lTcq{rr5_gqqtZ@*0Yw4}OdjL*kBv+>+@ z&*24U=y{Nl58qJyW1vTwqsvs=VRAzojm&V zEn6=WzdL1y+^}%Vg!ap>x%%nFi=V#wn# zUuheBR@*KS)5Mn0`f=3fMwR|#-rPMQJg(fW*5e`7xO&^UUH{L(U8D$JtI!ac!g(Ze89<`UiO@L+)^D zjPk2_Ie0p~4|LiI?-+pHXuRaZKG$%zVT0jn!yTvvM^jlcp`|VSHRt-G@_&~<4&qW@ z?b#zIN)G(}L|60jer*P7#KCu*Af;{mpWWvYK$@Squ|n-Vtfgr@ZOmR5Xpl;0q~VILmjk$$mgp+`<2jP z@+nW5Oap%fF4nFwnVwR7rpFaOdmnfB$-rkO6T3#w^|*rft~acgCP|ZkgA6PHD#Of| zY%E!3tXtsWS`udLsE7cSE8g@p$ceu*tI71V31uA7jwmXUCT7+Cu3uv|W>ZwD{&O4Nfjjvl43N#A$|FWxId! z%=X!HSiQ-#4nS&smww~iXRn<-`&zc)nR~js?|Ei-cei$^$KsqtxNDZvl1oavXK#Pz zT&%Wln^Y5M95w=vJxj0a-ko_iQt(LTX_5x#*QfQLtPil;kkR|kz}`*xHiLWr35ajx zHRL-QQv$|PK-$ges|NHw8k6v?&d;{A$*q15hz9{}-`e6ys1EQ1oNNKDFGQ0xA!x^( zkG*-ueZT(GukSnK&Bs=4+w|(kuWs5V_2#3`!;f}q?>xU5IgoMl^DNf+Xd<=sl2XvkqviJ>d?+G@Z5nxxd5Sqd$*ENUB_mb8Z+7CyyU zA6mDQ&e+S~w49csl*UePzY;^K)Fbs^%?7;+hFc(xz#mWoek4_&QvmT7Fe)*{h-9R4 zqyXuN5{)HdQ6yVi#tRUO#M%;pL>rQxN~6yoZ)*{{!?jU)RD*oOxDoTjVh6iNmhWNC zB5_{R=o{qvxEvi(khbRS`FOXmOO|&Dj$&~>*oo)bZz%lPhEA@ zQ;;w5eu5^%i;)w?T&*=UaK?*|U3~{0tC`rvfEsRPgR~16;~{_S2&=E{fE2=c>{+y} zx1*NTv-*zO^px5TA|B```#NetKg`19O!BK*-#~wDM@KEllk^nfQ2quy25G%)l72<> zzL$^{DDM#jKt?<>m;!?E2p0l12`j+QJjr{Lx*47Nq(v6i3M&*P{jkZB{xR?NOSPN% zU>I+~d_ny=pX??qjF*E78>}Mgts@_yn`)C`wN-He_!OyE+gRI?-a>Om>Vh~3OX5+& z6MX*d1`SkdXwvb7KH&=31RCC|&H!aA1g_=ZY0hP)-Wm6?A7SG0*|$mC7N^SSBh@MG z9?V0tv_sE>X==yV{)^LsygK2=$Mo_0N!JCOU?r}rmWdHD%$h~~G3;bt`lH& zAuOOZ=G1Mih**0>lB5x+r)X^8mz!0K{SScj4|a=s^VhUEp#2M=^#WRqe?T&H9GnWa zYOq{+gBn9Q0e0*Zu>C(BAX=I-Af9wIFhCW6_>TsIH$d>|{fIrs&BX?2G>GvFc=<8` zVJ`#^knMU~65dWGgXcht`Kb>{V2oo%<{NK|iH+R^|Gx%q+env#Js*(EBT3V0=w4F@W+oLFsA)l7Qy8mx_;6Vrk;F2RjKFvmeq} zro&>@b^(?f))OoQ#^#s)tRL>b0gzhRYRG}EU%wr9GjQ#~Rpo|RSkeik^p9x2+=rUr}vfnQoeFAlv=oX%YqbLpvyvcZ3l$B z5bo;hDd(fjT;9o7g9xUg3|#?wU2#BJ0G&W1#wn?mfNR{O7bq747tc~mM%m%t+7YN}^tMa24O4@w<|$lk@pGx!;%pKiq&mZB z?3h<&w>un8r?Xua6(@Txu~Za9tI@|C4#!dmHMzDF_-_~Jolztm=e)@vG11bZQAs!tFvd9{C;oxC7VfWq377Y(LR^X_TyX9bn$)I765l=rJ%9uXcjggX*r?u zk|0!db_*1$&i8>d&G3C}A`{Fun_1J;Vx0gk7P_}8KBZDowr*8$@X?W6v^LYmNWI)lN92yQ;tDpN zOUdS-W4JZUjwF-X#w0r;97;i(l}ZZT$DRd4u#?pf^e2yaFo zbm>I@5}#8FjsmigM8w_f#m4fEP~r~_?OWB%SGWcn$ThnJ@Y`ZI-O&Qs#Y14To( zWAl>9Gw7#}eT(!c%D0m>5D8**a@h;sLW=6_AsT5v1Sd_T-C4pgu_kvc?7+X&n_fct znkHy(_LExh=N%o3I-q#f$F4QJpy>jZBW zRF7?EhqTGk)w&Koi}QQY3sVh?@e-Z3C9)P!(hMhxmXLC zF_+ZSTQU`Gqx@o(~B$dbr zHlEUKoK&`2gl>zKXlEi8w6}`X3kh3as1~sX5@^`X_nYl}hlbpeeVlj#2sv)CIMe%b zBs7f|37f8qq}gA~Is9gj&=te^wN8ma?;vF)7gce;&sZ64!7LqpR!fy)?4cEZposQ8 zf;rZF7Q>YMF1~eQ|Z*!5j0DuA=`~VG$Gg6B?Om1 z6fM@`Ck-K*k(eJ)Kvysb8sccsFf@7~3vfnC=<$q+VNv)FyVh6ZsWw}*vs>%k3$)9| zR9ek-@pA23qswe1io)(Vz!vS1o*XEN*LhVYOq#T`;rDkgt86T@O`23xW~;W_#ZS|x zvwx-XMb7_!hIte-#JNpFxskMMpo2OYhHRr0Yn8d^(jh3-+!CNs0K2B!1dL$9UuAD= zQ%7Ae(Y@}%Cd~!`h|wAdm$2WoZ(iA1(a_-1?znZ%8h72o&Mm*4x8Ta<4++;Yr6|}u zW8$p&izhdqF=m8$)HyS2J6cKyo;Yvb>DTfx4`4R{ zPSODe9E|uflE<`xTO=r>u~u=NuyB&H!(2a8vwh!jP!yfE3N>IiO1jI>7e&3rR#RO3_}G23W?gwDHgSgekzQ^PU&G5z&}V5GO? zfg#*72*$DP1T8i`S7=P;bQ8lYF9_@8^C(|;9v8ZaK2GnWz4$Th2a0$)XTiaxNWfdq z;yNi9veH!j)ba$9pke8`y2^63BP zIyYKj^7;2don3se!P&%I2jzFf|LA&tQ=NDs{r9fIi-F{-yiG-}@2`VR^-LIFN8BC4 z&?*IvLiGHH5>NY(Z^CL_A;yISNdq58}=u~9!Ia7 zm7MkDiK~lsfLpvmPMo!0$keA$`%Tm`>Fx9JpG^EfEb(;}%5}B4Dw!O3BCkf$$W-dF z$BupUPgLpHvr<<+QcNX*w@+Rz&VQz)Uh!j4|DYeKm5IC05T$KqVV3Y|MSXom+Jn8c zgUEaFW1McGi^44xoG*b0JWE4T`vka7qTo#dcS4RauUpE{O!ZQ?r=-MlY#;VBzhHGU zS@kCaZ*H73XX6~HtHd*4qr2h}Pf0Re@!WOyvres_9l2!AhPiV$@O2sX>$21)-3i+_ z*sHO4Ika^!&2utZ@5%VbpH(m2wE3qOPn-I5Tbnt&yn9{k*eMr3^u6zG-~PSr(w$p> zw)x^a*8Ru$PE+{&)%VQUvAKKiWiwvc{`|GqK2K|ZMy^Tv3g|zENL86z7i<c zW`W>zV1u}X%P;Ajn+>A)2iXZbJ5YB_r>K-h5g^N=LkN^h0Y6dPFfSBh(L`G$D%7c` z&0RXDv$}c7#w*7!x^LUes_|V*=bd&aP+KFi((tG*gakSR+FA26%{QJdB5G1F=UuU&koU*^zQA=cEN9}Vd?OEh| zgzbFf1?@LlPkcXH$;YZe`WEJ3si6&R2MRb}LYK&zK9WRD=kY-JMPUurX-t4(Wy{%` zZ@0WM2+IqPa9D(^*+MXw2NWwSX-_WdF0nMWpEhAyotIgqu5Y$wA=zfuXJ0Y2lL3#ji26-P3Z?-&0^KBc*`T$+8+cqp`%g0WB zTH9L)FZ&t073H4?t=(U6{8B+uRW_J_n*vW|p`DugT^3xe8Tomh^d}0k^G7$3wLgP& zn)vTWiMA&=bR8lX9H=uh4G04R6>C&Zjnx_f@MMY!6HK5v$T%vaFm;E8q=`w2Y}ucJ zkz~dKGqv9$E80NTtnx|Rf_)|3wxpnY6nh3U9<)fv2-vhQ6v=WhKO@~@X57N-`7Ppc zF;I7)eL?RN23FmGh0s;Z#+p)}-TgTJE%&>{W+}C`^-sy{gTm<$>rR z-X7F%MB9Sf%6o7A%ZHReD4R;imU6<9h81{%avv}hqugeaf=~^3A=x(Om6Lku-Pn9i zC;LP%Q7Xw*0`Kg1)X~nAsUfdV%HWrpr8dZRpd-#%)c#Fu^mqo|^b{9Mam`^Zw_@j@ zR&ZdBr3?@<@%4Z-%LT&RLgDUFs4a(CTah_5x4X`xDRugi#vI-cw*^{ncwMtA4NKjByYBza)Y$hozZCpuxL{IP&=tw6ZO52WY3|iwGf&IJCn+u(>icK zZB1~bWXCmwAUz|^<&ysd#*!DSp8}DLNbl5lRFat4NkvItxy;9tpp9~|@ z;JctShv^Iq4(z+y7^j&I?GCdKMVg&jCwtCkc4*@O7HY*veGDBtAIn*JgD$QftP}8= zxFAdF=(S>Ra6(4slk#h%b?EOU-96TIX$Jbfl*_7IY-|R%H zF8u|~hYS-YwWt5+^!uGcnKL~jM;)ObZ#q68ZkA?}CzV-%6_vPIdzh_wHT_$mM%vws9lxUj;E@#1UX?WO2R^41(X!nk$+2oJGr!sgcbn1f^yl1 z#pbPB&Bf;1&2+?};Jg5qgD1{4_|%X#s48rOLE!vx3@ktstyBsDQWwDz4GYlcgu$UJ zp|z_32yN72T*oT$SF8<}>e;FN^X&vWNCz>b2W0rwK#<1#kbV)Cf`vN-F$&knLo5T& z8!sO-*^x4=kJ$L&*h%rQ@49l?7_9IG99~xJDDil00<${~D&;kiqRQqeW5*22A`8I2 z(^@`qZoF7_`CO_e;8#qF!&g>UY;wD5MxWU>azoo=E{kW(GU#pbOi%XAn%?W{b>-bTt&2?G=E&BnK9m0zs{qr$*&g8afR_x`B~o zd#dxPpaap;I=>1j8=9Oj)i}s@V}oXhP*{R|@DAQXzQJekJnmuQ;vL90_)H_nD1g6e zS1H#dzg)U&6$fz0g%|jxDdz|FQN{KJ&Yx0vfuzAFewJjv`pdMRpY-wU`-Y6WQnJ(@ zGVb!-8DRJZvHnRFiR3PG3Tu^nCn(CcZHh7hQvyd7i6Q3&ot86XI{jo%WZqCPcTR0< zMRg$ZE=PQx66ovJDvI_JChN~k@L^Pyxv#?X^<)-TS5gk`M~d<~j%!UOWG;ZMi1af< z+86U0=sm!qAVJAIqqU`Qs1uJhQJA&n@9F1PUrYuW!-~IT>l$I!#5dBaiAK}RUufjg{$#GdQBkxF1=KU2E@N=i^;xgG2Y4|{H>s` z$t`k8c-8`fS7Yfb1FM#)vPKVE4Uf(Pk&%HLe z%^4L>@Z^9Z{ZOX<^e)~adVRkKJDanJ6VBC_m@6qUq_WF@Epw>AYqf%r6qDzQ~AEJ!jtUvLp^CcqZ^G-;Kz3T;O4WG45Z zFhrluCxlY`M+OKr2SeI697btH7Kj`O>A!+2DTEQ=48cR>Gg2^5uqp(+y5Sl09MRl* zp|28!v*wvMd_~e2DdKDMMQ|({HMn3D%%ATEecGG8V9>`JeL)T0KG}=}6K8NiSN5W< z79-ZdYWRUb`T}(b{RjN8>?M~opnSRl$$^gT`B27kMym5LNHu-k;A;VF8R(HtDYJHS zU7;L{a@`>jd0svOYKbwzq+pWSC(C~SPgG~nWR3pBA8@OICK$Cy#U`kS$I;?|^-SBC zBFkoO8Z^%8Fc-@X!KebF2Ob3%`8zlVHj6H;^(m7J35(_bS;cZPd}TY~qixY{MhykQ zV&7u7s%E=?i`}Ax-7dB0ih47w*7!@GBt<*7ImM|_mYS|9_K7CH+i}?*#o~a&tF-?C zlynEu1DmiAbGurEX2Flfy$wEVk7AU;`k#=IQE*6DMWafTL|9-vT0qs{A3mmZGzOyN zcM9#Rgo7WgB_ujU+?Q@Ql?V-!E=jbypS+*chI&zA+C_3_@aJal}!Q54?qsL0In({Ly zjH;e+_SK8yi0NQB%TO+Dl77jp#2pMGtwsgaC>K!)NimXG3;m7y`W+&<(ZaV>N*K$j zLL~I+6ouPk6_(iO>61cIsinx`5}DcKSaHjYkkMuDoVl>mKO<4$F<>YJ5J9A2Vl}#BP7+u~L8C6~D zsk`pZ$9Bz3teQS1Wb|8&c2SZ;qo<#F&gS;j`!~!ADr(jJXMtcDJ9cVi>&p3~{bqaP zgo%s8i+8V{UrYTc9)HiUR_c?cfx{Yan2#%PqJ{%?Wux4J;T$#cumM0{Es3@$>}DJg zqe*c8##t;X(4$?A`ve)e@YU3d2Balcivot{1(ahlE5qg@S-h(mPNH&`pBX$_~HdG48~)$x5p z{>ghzqqn_t8~pY<5?-To>cy^6o~mifr;KWvx_oMtXOw$$d6jddXG)V@a#lL4o%N@A zNJlQAz6R8{7jax-kQsH6JU_u*En%k^NHlvBB!$JAK!cYmS)HkLAkm0*9G3!vwMIWv zo#)+EamIJHEUV|$d|<)2iJ`lqBQLx;HgD}c3mRu{iK23C>G{0Mp1K)bt6OU?xC4!_ zZLqpFzeu&+>O1F>%g-%U^~yRg(-wSp@vmD-PT#bCWy!%&H;qT7rfuRCEgw67V!Qob z&tvPU@*4*$YF#2_>M0(75QxqrJr3Tvh~iDeFhxl=MzV@(psx%G8|I{~9;tv#BBE`l z3)_98eZqFNwEF1h)uqhBmT~mSmT8k$7vSHdR97K~kM)P9PuZdS;|Op4A?O<*%!?h` zn`}r_j%xvffs46x2hCWuo0BfIQWCw9aKkH==#B(TJ%p}p-RuIVzsRlaPL_Co{&R0h zQrqn=g1PGjQg3&sc2IlKG0Io#v%@p>tFwF)RG0ahYs@Zng6}M*d}Xua)+h&?$`%rb z;>M=iMh5eIHuJ5c$aC`y@CYjbFsJnSPH&}LQz4}za9YjDuao>Z^EdL@%saRm&LGQWXs*;FzwN#pH&j~SLhDZ+QzhplV_ij(NyMl z;v|}amvxRddO81LJFa~2QFUs z+Lk zZck)}9uK^buJNMo4G(rSdX{57(7&n=Q6$QZ@lIO9#<3pA2ceDpO_340B*pHlh_y{>i&c1?vdpN1j>3UN-;;Yq?P+V5oY`4Z(|P8SwWq<)n`W@AwcQ?E9 zd5j8>FT^m=MHEWfN9jS}UHHsU`&SScib$qd0i=ky0>4dz5ADy70AeIuSzw#gHhQ_c zOp1!v6qU)@8MY+ zMNIID?(CysRc2uZQ$l*QZVY)$X?@4$VT^>djbugLQJdm^P>?51#lXBkdXglYm|4{L zL%Sr?2f`J+xrcN@=0tiJt(<-=+v>tHy{XaGj7^cA6felUn_KPa?V4ebfq7~4i~GKE zpm)e@1=E;PP%?`vK6KVPKXjUXyLS1^NbnQ&?z>epHCd+J$ktT1G&L~T)nQeExe;0Z zlei}<_ni ztFo}j7nBl$)s_3odmdafVieFxc)m!wM+U`2u%yhJ90giFcU1`dR6BBTKc2cQ*d zm-{?M&%(={xYHy?VCx!ogr|4g5;V{2q(L?QzJGsirn~kWHU`l`rHiIrc-Nan!hR7zaLsPr4uR zG{En&gaRK&B@lyWV@yfFpD_^&z>84~_0Rd!v(Nr%PJhFF_ci3D#ixf|(r@$igZiWw za*qbXIJ_Hm4)TaQ=zW^g)FC6uvyO~Hg-#Z5Vsrybz6uOTF>Rq1($JS`imyNB7myWWpxYL(t7`H8*voI3Qz6mvm z$JxtArLJ(1wlCO_te?L{>8YPzQ})xJlvc5wv8p7Z=HviPYB#^#_vGO#*`<0r%MR#u zN_mV4vaBb2RwtoOYCw)X^>r{2a0kK|WyEYoBjGxcObFl&P*??)WEWKU*V~zG5o=s@ z;rc~uuQQf9wf)MYWsWgPR!wKGt6q;^8!cD_vxrG8GMoFGOVV=(J3w6Xk;}i)9(7*U zwR4VkP_5Zx7wqn8%M8uDj4f1aP+vh1Wue&ry@h|wuN(D2W;v6b1^ z`)7XBZ385zg;}&Pt@?dunQ=RduGRJn^9HLU&HaeUE_cA1{+oSIjmj3z+1YiOGiu-H zf8u-oVnG%KfhB8H?cg%@#V5n+L$MO2F4>XoBjBeX>css^h}Omu#)ExTfUE^07KOQS znMfQY2wz?!7!{*C^)aZ^UhMZf=TJNDv8VrrW;JJ9`=|L0`w9DE8MS>+o{f#{7}B4P z{I34>342vLsP}o=ny1eZkEabr@niT5J2AhByUz&i3Ck0H*H`LRHz;>3C_ru!X+EhJ z6(+(lI#4c`2{`q0o9aZhI|jRjBZOV~IA_km7ItNtUa(Wsr*Hmb;b4=;R(gF@GmsRI`pF+0tmq0zy~wnoJD(LSEwHjTOt4xb0XB-+ z&4RO{Snw4G%gS9w#uSUK$Zbb#=jxEl;}6&!b-rSY$0M4pftat-$Q)*y!bpx)R%P>8 zrB&`YEX2%+s#lFCIV;cUFUTIR$Gn2%F(3yLeiG8eG8&)+cpBlzx4)sK?>uIlH+$?2 z9q9wk5zY-xr_fzFSGxYp^KSY0s%1BhsI>ai2VAc8&JiwQ>3RRk?ITx!t~r45qsMnj zkX4bl06ojFCMq<9l*4NHMAtIxDJOX)H=K*$NkkNG<^nl46 zHWH1GXb?Og1f0S+8-((5yaeegCT62&4N*pNQY;%asz9r9Lfr;@Bl${1@a4QAvMLbV6JDp>8SO^q1)#(o%k!QiRSd0eTmzC< zNIFWY5?)+JTl1Roi=nS4%@5iF+%XztpR^BSuM~DX9q`;Mv=+$M+GgE$_>o+~$#?*y zAcD4nd~L~EsAjXV-+li6Lua4;(EFdi|M2qV53`^4|7gR8AJI;0Xb6QGLaYl1zr&eu zH_vFUt+Ouf4SXA~ z&Hh8K@ms^`(hJfdicecj>J^Aqd00^ccqN!-f-!=N7C1?`4J+`_f^nV!B3Q^|fuU)7 z1NDNT04hd4QqE+qBP+>ZE7{v;n3OGN`->|lHjNL5w40pePJ?^Y6bFk@^k%^5CXZ<+4qbOplxpe)l7c6m%o-l1oWmCx%c6@rx85hi(F=v(2 zJ$jN>?yPgU#DnbDXPkHLeQwED5)W5sH#-eS z%#^4dxiVs{+q(Yd^ShMN3GH)!h!@W&N`$L!SbElXCuvnqh{U7lcCvHI#{ZjwnKvu~ zAeo7Pqot+Ohm{8|RJsTr3J4GjCy5UTo_u_~p)MS&Z5UrUc|+;Mc(YS+ju|m3Y_Dvt zonVtpBWlM718YwaN3a3wUNqX;7TqvAFnVUoD5v5WTh~}r)KoLUDw%8Rrqso~bJqd> z_T!&Rmr6ebpV^4|knJZ%qmzL;OvG3~A*loGY7?YS%hS{2R0%NQ@fRoEK52Aiu%gj( z_7~a}eQUh8PnyI^J!>pxB(x7FeINHHC4zLDT`&C*XUpp@s0_B^!k5Uu)^j_uuu^T> z8WW!QK0SgwFHTA%M!L`bl3hHjPp)|wL5Var_*A1-H8LV?uY5&ou{hRjj>#X@rxV>5%-9hbP+v?$4}3EfoRH;l_wSiz{&1<+`Y5%o%q~4rdpRF0jOsCoLnWY5x?V)0ga>CDo`NpqS) z@x`mh1QGkx;f)p-n^*g5M^zRTHz%b2IkLBY{F+HsjrFC9_H(=9Z5W&Eymh~A_FUJ} znhTc9KG((OnjFO=+q>JQZJbeOoUM77M{)$)qQMcxK9f;=L;IOv_J>*~w^YOW744QZ zoG;!b9VD3ww}OX<8sZ0F##8hvfDP{hpa3HjaLsKbLJ8 z0WpY2E!w?&cWi7&N%bOMZD~o7QT*$xCRJ@{t31~qx~+0yYrLXubXh2{_L699Nl_pn z6)9eu+uUTUdjHXYs#pX^L)AIb!FjjNsTp7C399w&B{Q4q%yKfmy}T2uQdU|1EpNcY zDk~(h#AdxybjfzB+mg6rdU9mDZ^V>|U13Dl$Gj+pAL}lR2a1u!SJXU_YqP9N{ose4 zk+$v}BIHX60WSGVWv;S%zvHOWdDP(-ceo(<8`y@Goy%4wDu>57QZNJc)f>Ls+}9h7 z^N=#3q3|l?aG8K#HwiW2^PJu{v|x5;awYfahC?>_af3$LmMc4%N~JwVlRZa4c+eW2 zE!zosAjOv&UeCeu;Bn5OQUC=jtZjF;NDk9$fGbxf3d29SUBekX1!a$Vmq_VK*MHQ4)eB!dQrHH)LVYNF%-t8!d`@!cb z2CsKs3|!}T^7fSZm?0dJ^JE`ZGxA&a!jC<>6_y67On0M)hd$m*RAzo_qM?aeqkm`* zXpDYcc_>TFZYaC3JV>{>mp(5H^efu!Waa7hGTAts29jjuVd1vI*fEeB?A&uG<8dLZ z(j6;-%vJ7R0U9}XkH)1g>&uptXPHBEA*7PSO2TZ+dbhVxspNW~ZQT3fApz}2 z_@0-lZODcd>dLrYp!mHn4k>>7kibI!Em+Vh*;z}l?0qro=aJt68joCr5Jo(Vk<@i) z5BCKb4p6Gdr9=JSf(2Mgr=_6}%4?SwhV+JZj3Ox^_^OrQk$B^v?eNz}d^xRaz&~ zKVnlLnK#8^y=If2f1zmb~^5lPLe?%l}>?~wN4IN((2~U{e9fKhLMtYFj)I$(y zgnKv?R+ZpxA$f)Q2l=aqE6EPTK=i0sY&MDFJp!vQayyvzh4wee<}kybNthRlX>SHh z7S}9he^EBOqzBCww^duHu!u+dnf9veG{HjW!}aT7aJqzze9K6-Z~8pZAgdm1n~aDs z8_s7?WXMPJ3EPJHi}NL&d;lZP8hDhAXf5Hd!x|^kEHu`6QukXrVdLnq5zbI~oPo?7 z2Cbu8U?$K!Z4_yNM1a(bL!GRe!@{Qom+DxjrJ!B99qu5b*Ma%^&-=6UEbC+S2zX&= zQ!%bgJTvmv^2}hhvNQg!l=kbapAgM^hruE3k@jTxsG(B6d=4thBC*4tzVpCYXFc$a zeqgVB^zua)y-YjpiibCCdU%txXYeNFnXcbNj*D?~)5AGjL+!!ij_4{5EWKGav0^={~M^q}baAFOPzxfUM>`KPf|G z&hsaR*7(M6KzTj8Z?;45zX@L#xU{4n$9Q_<-ac(y4g~S|Hyp^-<*d8+P4NHe?~vfm z@y309=`lGdvN8*jw-CL<;o#DKc-%lb0i9a3%{v&2X($|Qxv(_*()&=xD=5oBg=$B0 zU?41h9)JKvP0yR{KsHoC>&`(Uz>?_`tlLjw1&5tPH3FoB%}j;yffm$$s$C=RHi`I3*m@%CPqWnP@B~%DEe;7ZT{9!IMTo1hT3Q347HJ&!)BM2 z3~aClf>aFh0_9||4G}(Npu`9xYY1*SD|M~9!CCFn{-J$u2&Dg*=5$_nozpoD2nxqq zB!--eA8UWZlcEDp4r#vhZ6|vq^9sFvRnA9HpHch5Mq4*T)oGbruj!U8Lx_G%Lby}o zTQ-_4A7b)5A42vA0U}hUJq6&wQ0J%$`w#ph!EGmW96)@{AUx>q6E>-r^Emk!iCR+X zdIaNH`$}7%57D1FyTccs3}Aq0<0Ei{`=S7*>pyg=Kv3nrqblqZcpsCWSQl^uMSsdj zYzh73?6th$c~CI0>%5@!Ej`o)Xm38u0fp9=HE@Sa6l2oX9^^4|Aq%GA z3(AbFR9gA_2T2i%Ck5V2Q2WW-(a&(j#@l6wE4Z`xg#S za#-UWUpU2U!TmIo`CN0JwG^>{+V#9;zvx;ztc$}@NlcyJr?q(Y`UdW6qhq!aWyB5xV1#Jb{I-ghFNO0 zFU~+QgPs{FY1AbiU&S$QSix>*rqYVma<-~s%ALhFyVhAYepId1 zs!gOB&weC18yhE-v6ltKZMV|>JwTX+X)Y_EI(Ff^3$WTD|Ea-1HlP;6L~&40Q&5{0 z$e$2KhUgH8ucMJxJV#M%cs!d~#hR^nRwk|uuCSf6irJCkSyI<%CR==tftx6d%;?ef zYIcjZrP@APzbtOeUe>m-TW}c-ugh+U*RbL1eIY{?>@8aW9bb1NGRy@MTse@>= za%;5=U}X%K2tKTYe9gjMcBvX%qrC&uZ`d(t)g)X8snf?vBe3H%dG=bl^rv8Z@YN$gd9yveHY0@Wt0$s zh^7jCp(q+6XDoekb;=%y=Wr8%6;z0ANH5dDR_VudDG|&_lYykJaiR+(y{zpR=qL3|2e${8 z2V;?jgHj7}Kl(d8C9xWRjhpf_)KOXl+@c4wrHy zL3#9U(`=N59og2KqVh>nK~g9>fX*PI0`>i;;b6KF|8zg+k2hViCt}4dfMdvb1NJ-Rfa7vL2;lPK{Lq*u`JT>S zoM_bZ_?UY6oV6Ja14X^;LqJPl+w?vf*C!nGK;uU^0GRN|UeFF@;H(Hgp8x^|;ygh? zIZx3DuO(lD01ksanR@Mn#lti=p28RTNYY6yK={RMFiVd~k8!@a&^jicZ&rxD3CCI! zVb=fI?;c#f{K4Pp2lnb8iF2mig)|6JEmU86Y%l}m>(VnI*Bj`a6qk8QL&~PFDxI8b z2mcsQBe9$q`Q$LfG2wdvK`M1}7?SwLAV&)nO;kAk`SAz%x9CDVHVbUd$O(*aI@D|s zLxJW7W(QeGpQY<$dSD6U$ja(;Hb3{Zx@)*fIQaW{8<$KJ&fS0caI2Py^clOq9@Irt z7th7F?7W`j{&UmM==Lo~T&^R7A?G=K_e-zfTX|)i`pLitlNE(~tq*}sS1x2}Jlul6 z5+r#4SpQu8h{ntIv#qCVH`uG~+I8l+7ZG&d`Dm!+(rZQDV*1LS^WfH%-!5aTAxry~ z4xl&rot5ct{xQ$w$MtVTUi6tBFSJWq2Rj@?HAX1H$eL*fk{Hq;E`x|hghRkipYNyt zKCO=*KSziiVk|+)qQCGrTYH9X!Z0$k{Nde~0Wl`P{}ca%nv<6fnYw^~9dYxTnTZB&&962jX0DM&wy&8fdxX8xeHSe=UU&Mq zRTaUKnQO|A>E#|PUo+F=Q@dMdt`P*6e92za(TH{5C*2I2S~p?~O@hYiT>1(n^Lqqn zqewq3ctAA%0E)r53*P-a8Ak32mGtUG`L^WVcm`QovX`ecB4E9X60wrA(6NZ7z~*_DV_e z8$I*eZ8m=WtChE{#QzeyHpZ%7GwFHlwo2*tAuloI-j2exx3#x7EL^&D;Re|Kj-XT- zt908^soV2`7s+Hha!d^#J+B)0-`{qIF_x=B811SZlbUe%kvPce^xu7?LY|C z@f1gRPha1jq|=f}Se)}v-7MWH9)YAs*FJ&v3ZT9TSi?e#jarin0tjPNmxZNU_JFJG z+tZi!q)JP|4pQ)?l8$hRaPeoKf!3>MM-bp06RodLa*wD=g3)@pYJ^*YrwSIO!SaZo zDTb!G9d!hb%Y0QdYxqNSCT5o0I!GDD$Z@N!8J3eI@@0AiJmD7brkvF!pJGg_AiJ1I zO^^cKe`w$DsO|1#^_|`6XTfw6E3SJ(agG*G9qj?JiqFSL|6tSD6vUwK?Cwr~gg)Do zp@$D~7~66-=p4`!!UzJDKAymb!!R(}%O?Uel|rMH>OpRGINALtg%gpg`=}M^Q#V5( zMgJY&gF)+;`e38QHI*c%B}m94o&tOfae;og&!J2;6ENW}QeL73jatbI1*9X~y=$Dm%6FwDcnCyMRL}zo`0=y7=}*Uw zo3!qZncAL{HCgY!+}eKr{P8o27ye+;qJP;kOB%RpSesGoHLT6tcYp*6v~Z9NCyb6m zP#qds0jyqXX46qMNhXDn3pyIxw2f_z;L_X9EIB}AhyC`FYI}G3$WnW>#NMy{0aw}nB%1=Z4&*(FaCn5QG(zvdG^pQRU25;{wwG4h z@kuLO0F->{@g2!;NNd!PfqM-;@F0;&wK}0fT9UrH}(8A5I zt33(+&U;CLN|8+71@g z(s!f-kZZZILUG$QXm9iYiE*>2w;gpM>lgM{R9vT3q>qI{ELO2hJHVi`)*jzOk$r)9 zq}$VrE0$GUCm6A3H5J-=Z9i*biw8ng zi<1nM0lo^KqRY@Asucc#DMmWsnCS;5uPR)GL3pL=-IqSd>4&D&NKSGHH?pG;=Xo`w zw~VV9ddkwbp~m>9G0*b?j7-0fOwR?*U#BE#n7A=_fDS>`fwatxQ+`FzhBGQUAyIRZ??eJt46vHBlR>9m!vfb6I)8!v6TmtZ%G6&E|1e zOtx5xy%yOSu+<9Ul5w5N=&~4Oph?I=ZKLX5DXO(*&Po>5KjbY7s@tp$8(fO|`Xy}Y z;NmMypLoG7r#Xz4aHz7n)MYZ7Z1v;DFHLNV{)to;(;TJ=bbMgud96xRMME#0d$z-S z-r1ROBbW^&YdQWA>U|Y>{whex#~K!ZgEEk=LYG8Wqo28NFv)!t!~}quaAt}I^y-m| z8~E{9H2VnyVxb_wCZ7v%y(B@VrM6lzk~|ywCi3HeiSV`TF>j+Ijd|p*kyn;=mqtf8&DK^|*f+y$38+9!sis9N=S)nINm9=CJ<;Y z!t&C>MIeyou4XLM*ywT_JuOXR>VkpFwuT9j5>667A=CU*{TBrMTgb4HuW&!%Yt`;#md7-`R`ouOi$rEd!ErI zo#>qggAcx?C7`rQ2;)~PYCw%CkS(@EJHZ|!!lhi@Dp$*n^mgrrImsS~(ioGak>3)w zvop0lq@IISuA0Ou*#1JkG{U>xSQV1e}c)!d$L1plFX5XDXX5N7Ns{kT{y5|6MfhBD+esT)e7&CgSW8FxsXTAY=}?0A!j_V9 zJ;IJ~d%av<@=fNPJ9)T3qE78kaz64E>dJaYab5uaU`n~Zdp2h{8DV%SKE5G^$LfuOTRRjB;TnT(Jk$r{Pfe4CO!SM_7d)I zquW~FVCpSycJ~c*B*V8?Qqo=GwU8CkmmLFugfHQ7;A{yCy1OL-+X=twLYg9|H=~8H znnN@|tCs^ZLlCBl5wHvYF}2vo>a6%mUWpTds_mt*@wMN4-r`%NTA%+$(`m6{MNpi@ zMx)8f>U4hd!row@gM&PVo&Hx+lV@$j9yWTjTue zG9n0DP<*HUmJ7ZZWwI2x+{t3QEfr6?T}2iXl=6e0b~)J>X3`!fXd9+2wc1%cj&F@Z zgYR|r5Xd5jy9;YW&=4{-0rJ*L5CgDPj9^3%bp-`HkyBs`j1iTUGD4?WilZ6RO8mIE z+~Joc?GID6K96dyuv(dWREK9Os~%?$$FxswxQsoOi8M?RnL%B~Lyk&(-09D0M?^Jy zWjP)n(b)TF<-|CG%!Vz?8Fu&6iU<>oG#kGcrcrrBlfZMVl0wOJvsq%RL9To%iCW@)#& zZAJWhgzYAq)#NTNb~3GBcD%ZZOc43!YWSyA7TD6xkk)n^FaRAz73b}%9d&YisBic(?mv=Iq^r%Ug zzHq-rRrhfOOF+yR=AN!a9*Rd#sM9ONt5h~w)yMP7Dl9lfpi$H0%GPW^lS4~~?vI8Z z%^ToK#NOe0ExmUsb`lLO$W*}yXNOxPe@zD*90uTDULnH6C?InP3J=jYEO2d)&e|mP z1DSd0QOZeuLWo*NqZzopA+LXy9)fJC00NSX=_4Mi1Z)YyZVC>C!g}cY(Amaj%QN+bev|Xxd2OPD zk!dfkY6k!(sDBvsFC2r^?}hb81(WG5Lt9|riT`2?P;B%jaf5UX<~OJ;uAL$=Ien+V zC!V8u0v?CUa)4*Q+Q_u zkx{q;NjLcvyMuU*{+uDsCQ4U{JLowYby-tn@hatL zy}X>9y08#}oytdn^qfFesF)Tt(2!XGw#r%?7&zzFFh2U;#U9XBO8W--#gOpfbJ`Ey z|M8FCKlWQrOJwE;@Sm02l9OBr7N}go4V8ur)}M@m2uWjggb)DC4s`I4d7_8O&E(j; z?3$9~R$QDxNM^rNh9Y;6P7w+bo2q}NEd6f&_raor-v`UCaTM3TT8HK2-$|n{N@U>_ zL-`P7EXoEU5JRMa)?tNUEe8XFis+w8g9k(QQ)%?&Oac}S`2V$b?%`DwXBgja&&fR@ zH_XidF$p1wA)J|Wk1;?lCl?fgc)=TB3>Y8;BoMqHwJqhL)Tgydv9(?(TBX)fq%=~C zmLj!iX-kn7QA(9snzk0LRf<%SzO&~IhLor6A3f*U^UcoAygRe!H#@UCv$JUP&vPxs zeDj$1%#<2T1!e|!7xI+~_VXLl5|jHqvOhU7ZDUGee;HnkcPP=_k_FFxPjXg*9KyI+ zIh0@+s)1JDSuKMeaDZ3|<_*J8{TUFDLl|mXmY8B>Wj_?4mC#=XjsCKPEO=p0c&t&Z zd1%kHxR#o9S*C?du*}tEHfAC7WetnvS}`<%j=o7YVna)6pw(xzkUi7f#$|^y4WQ{7 zu@@lu=j6xr*11VEIY+`B{tgd(c3zO8%nGk0U^%ec6h)G_`ki|XQXr!?NsQkxzV6Bn1ea9L+@ z(Zr7CU_oXaW>VOdfzENm+FlFQ7Se0ROrNdw(QLvb6{f}HRQ{$Je>(c&rws#{dFI^r zZ4^(`J*G0~Pu_+p5AAh>RRpkcbaS2a?Fe&JqxDTp`dIW9;DL%0wxX5;`KxyA4F{(~_`93>NF@bj4LF!NC&D6Zm+Di$Q-tb2*Q z&csGmXyqA%Z9s(AxNO3@Ij=WGt=UG6J7F;r*uqdQa z?7j!nV{8eQE-cwY7L(3AEXF3&V*9{DpSYdyCjRhv#&2johwf{r+k`QB81%!aRVN<& z@b*N^xiw_lU>H~@4MWzgHxSOGVfnD|iC7=hf0%CPm_@@4^t-nj#GHMug&S|FJtr?i z^JVrobltd(-?Ll>)6>jwgX=dUy+^n_ifzM>3)an3iOzpG9Tu;+96TP<0Jm_PIqof3 zMn=~M!#Ky{CTN_2f7Y-i#|gW~32RCWKA4-J9sS&>kYpTOx#xVNLCo)A$LUme^fVNH z@^S7VU^UJ0YR8?Oy$^IYuG*bm|g;@aX~i60%`7XLy*AYpYvZ^F^U(!|RW z*C!rJ@+7TGdL=nNd1gv^%B+;Fcr$y)i0!GRsZXRHPs>QVGVR{9r_#&Qd(wL|5;H;> zD>HUw=4CF++&{7$<8G@j*nGjhEO%BQYfjeItp4mPvY*JYb1HKd!{HJ9*)(3%BR%{Pp?AM&*yHAJsW({ivOzj*qS!-7|XEn6@zo z3L*tBT%<4RxoAh>q{0n_JBmgW6&8hx?kL(_^k%VL>?xjAyrKBmSl`$=V|SK}ELl}@ zd|d0eo#RfG`bw9SK3%r4Y+rdvc}w}~ixV%tqawbdqvE-WcgE+BUpxMT%F@btm76MG zn=oQRWWuTm+a{dy)Oc2V4yX(@M{QAkx>(QB59*`dLT`Pz3Lsj9iB=HSHAiCq()ns|Cr)1*c605Cx}3V&x}Lg?b+6Q?)z7Kl zQh&1Hx`y6JY-Cwvd*ozeps}a1xAA0CR+Da;+O(i)P1C;SjOI}Dtmf6tPqo-Bl`U78 zv$kYgPntPp@G)n1an9tEoL*Vumu9`>_@I(;+5+fBa-*?fEx=mTEjZ7wq}#@Gd5_cW z!mP{N=yqEntDo)|>oy6{9cu+-3*GTnmb^`O0^FzRPO^&aG`f@F_R*aQ_e{F+_9%NW z4KG_B`@X3EVV9L>?_RNDMddA>w=e0KfAiw5?#i1NFT%Zz#nuv(&!yIU>lVxmzYKQ` zzJ*0w9<&L4aJ6A;0j|_~i>+y(q-=;2Xxhx2v%CYY^{} z^J@LO()eLo|7!{ghQ+(u$wxO*xY#)cL(|miH2_ck2yN{mu4O9=hBW*pM_()-_YdH#Ru{JtwJ^R2}3?!>>m1pohh zrn(!xCjE0Q&EH1QK?zA%sxVh&H99cObJUY$veZhQ)MLu-h%`!*G)s$2k;~+A z)Kk->Ri?`oGDEJEtI*wijm(s5f$W78FH{+qBxiU{~kq((J3uK{m z$|C8K#j-?hm8H@x%VfFqpnvu@xn1s%J7uNZC9C99a<_b1J|mx%)$%!6gPU|~<@2&m zz99GDp`|a%m*iggvfL;4%X;~WY>)@!tMWB@P`)k?$;0x9JSrRI8?s3rlgH(o@`OAo zn{f*gZ#t2u6K??hx|aElOM`Xd0t+SAIUEHvFw%?Wsm$s zUXq{6UU?a>Nc@@Xlb_2k9M1Ctr<#+O?yd}rv z_wu&=_t$!Yngd@N_AUj}T; z#*Ce|%XZr_sQcsWcsl{pCnnj+c8ZNIMmx<;w=-g$Q>BU;9k;w|zQ;4!W32Xg2Cd?{ zvmO3kuKQ^Hv;o>6ZHP8ZJ2`4~Bx?N;cf<0fi=!*G^^WzbTF3e$b&d^qqB{>nqLG81 zs94bBh%|Vj+hLu=!8(b9brJ>ZBns9^6s(gdSVyP9qnu2_I{Sg8j-rloG6{d`De5We zDe5WeY3ga}Y3ga}Y3ga}Y3ga}Y3ga}d8y~6o|k%F>UpW>rJk31Ug~+N=cS&HdOqs; zsOO`ek9t1p`Kafko{xGy>iMbXr=FjBxZMYc8a#gL`Kjlpo}YSt>iMY`pk9DF0qO*( z6QE9jIsxhgs1u-0kUBx8D@eT{^@7w3QZGooAoYUO3sNscy%6<6)C*BBM7L`dk$Xk%6}eZQXgo#!75P`>Uy*-B{uTLGUy*-B{uTLGUy*-B{uTLG))v8{5gt_uj9!t5)^yb-JtjRGrhi zYInOUNJxNyf_yKX01)K=WP|Si>HqEj|B{eUl?MR<)%<1&{(~)D+NPwKxWqT-@~snp zg9KCz1VTZDiS?UH`PRk1VPM{29cgT9=D?!Wc_@}qzggFv;gb@2cJQAYWWtpEZ7?y@jSVqjx${B5UV@SO|wH<<0; z{><1KdVI%Ki}>~<`46C0AggwUwx-|QcU;iiZ{NZu`ur>hd*|Hb(|6veERqxu=b@5Bab=rqptGxd{QJg!4*-i_$sES~)AB46}Fjg|ea#e@?J}z%CUJ zOsLWRQR1#ng^sD)A4FDuY!iUhzlgfJh(J@BRqd&P#v2B`+saBx>m+M&q7vk-75$NH%T5pi%m z5FX?`2-5l53=a&GkC9^NZCLpN5(DMKMwwab$FDIs?q>4!!xBS}75gX_5;(luk;3Vl zLCLd5a_8`Iyz}K}+#RMwu6DVk3O_-}n>aE!4NaD*sQn`GxY?cHe!Bl9n?u&g6?aKm z-P8z&;Q3gr;h`YIxX%z^o&GZZg1=>_+hP2$$-DnL_?7?3^!WAsY4I7|@K;aL<>OTK zByfjl2PA$T83*LM9(;espx-qB%wv7H2i6CFsfAg<9V>Pj*OpwX)l?^mQfr$*OPPS$ z=`mzTYs{*(UW^ij1U8UfXjNoY7GK*+YHht(2oKE&tfZuvAyoN(;_OF>-J6AMmS5fB z^sY6wea&&${+!}@R1f$5oC-2J>J-A${@r(dRzc`wnK>a7~8{Y-scc|ETOI8 zjtNY%Y2!PI;8-@a=O}+{ap1Ewk0@T`C`q!|=KceX9gK8wtOtIC96}-^7)v23Mu;MH zhKyLGOQMujfRG$p(s`(2*nP4EH7*J57^=|%t(#PwCcW7U%e=8Jb>p6~>RAlY4a*ts=pl}_J{->@kKzxH|8XQ5{t=E zV&o`$D#ZHdv&iZWFa)(~oBh-Osl{~CS0hfM7?PyWUWsr5oYlsyC1cwULoQ4|Y5RHA2*rN+EnFPnu z`Y_&Yz*#550YJwDy@brZU>0pWV^RxRjL221@2ABq)AtA%Cz?+FG(}Yh?^v)1Lnh%D zeM{{3&-4#F9rZhS@DT0E(WRkrG!jC#5?OFjZv*xQjUP~XsaxL2rqRKvPW$zHqHr8Urp2Z)L z+)EvQeoeJ8c6A#Iy9>3lxiH3=@86uiTbnnJJJoypZ7gco_*HvKOH97B? zWiwp>+r}*Zf9b3ImxwvjL~h~j<<3shN8$k-$V1p|96I!=N6VBqmb==Bec|*;HUg?) z4!5#R*(#Fe)w%+RH#y{8&%%!|fQ5JcFzUE;-yVYR^&Ek55AXb{^w|@j|&G z|6C-+*On%j;W|f8mj?;679?!qY86c{(s1-PI2Wahoclf%1*8%JAvRh1(0)5Vu37Iz z`JY?RW@qKr+FMmBC{TC7k@}fv-k8t6iO}4K-i3WkF!Lc=D`nuD)v#Na zA|R*no51fkUN3^rmI;tty#IK284*2Zu!kG13!$OlxJAt@zLU`kvsazO25TpJLbK&;M8kw*0)*14kpf*)3;GiDh;C(F}$- z1;!=OBkW#ctacN=je*Pr)lnGzX=OwgNZjTpVbFxqb;8kTc@X&L2XR0A7oc!Mf2?u9 zcctQLCCr+tYipa_k=;1ETIpHt!Jeo;iy^xqBES^Ct6-+wHi%2g&)?7N^Yy zUrMIu){Jk)luDa@7We5U!$$3XFNbyRT!YPIbMKj5$IEpTX1IOtVP~(UPO2-+9ZFi6 z-$3<|{Xb#@tABt0M0s1TVCWKwveDy^S!!@4$s|DAqhsEv--Z}Dl)t%0G>U#ycJ7cy z^8%;|pg32=7~MJmqlC-x07Sd!2YX^|2D`?y;-$a!rZ3R5ia{v1QI_^>gi(HSS_e%2 zUbdg^zjMBBiLr8eSI^BqXM6HKKg#@-w`a**w(}RMe%XWl3MipvBODo*hi?+ykYq)z ziqy4goZw0@VIUY65+L7DaM5q=KWFd$;W3S!Zi>sOzpEF#(*3V-27N;^pDRoMh~(ZD zJLZXIam0lM7U#)119Hm947W)p3$%V`0Tv+*n=&ybF&}h~FA}7hEpA&1Y!BiYIb~~D z$TSo9#3ee02e^%*@4|*+=Nq6&JG5>zX4k5f?)z*#pI-G(+j|jye%13CUdcSP;rNlY z#Q!X%zHf|V)GWIcEz-=fW6AahfxI~y7w7i|PK6H@@twdgH>D_R@>&OtKl}%MuAQ7I zcpFmV^~w~8$4@zzh~P~+?B~%L@EM3x(^KXJSgc6I=;)B6 zpRco2LKIlURPE*XUmZ^|1vb?w*ZfF}EXvY13I4af+()bAI5V?BRbFp`Sb{8GRJHd* z4S2s%4A)6Uc=PK%4@PbJ<{1R6+2THMk0c+kif**#ZGE)w6WsqH z`r^DL&r8|OEAumm^qyrryd(HQ9olv$ltnVGB{aY?_76Uk%6p;e)2DTvF(;t=Q+|8b zqfT(u5@BP);6;jmRAEV057E*2d^wx@*aL1GqWU|$6h5%O@cQtVtC^isd%gD7PZ_Io z_BDP5w(2*)Mu&JxS@X%%ByH_@+l>y07jIc~!@;Raw)q_;9oy@*U#mCnc7%t85qa4? z%_Vr5tkN^}(^>`EFhag;!MpRh!&bKnveQZAJ4)gEJo1@wHtT$Gs6IpznN$Lk-$NcM z3ReVC&qcXvfGX$I0nfkS$a|Pm%x+lq{WweNc;K>a1M@EAVWs2IBcQPiEJNt}+Ea8~WiapASoMvo(&PdUO}AfC~>ZGzqWjd)4no( ziLi#e3lOU~sI*XPH&n&J0cWfoh*}eWEEZW%vX?YK!$?w}htY|GALx3;YZoo=JCF4@ zdiaA-uq!*L5;Yg)z-_`MciiIwDAAR3-snC4V+KA>&V%Ak;p{1u>{Lw$NFj)Yn0Ms2*kxUZ)OTddbiJM}PK!DM}Ot zczn?EZXhx3wyu6i{QMz_Ht%b?K&-@5r;8b076YDir`KXF0&2i9NQ~#JYaq*}Ylb}^ z<{{6xy&;dQ;|@k_(31PDr!}}W$zF7Jv@f%um0M$#=8ygpu%j(VU-d5JtQwT714#f0z+Cm$F9JjGr_G!~NS@L9P;C1? z;Ij2YVYuv}tzU+HugU=f9b1Wbx3418+xj$RKD;$gf$0j_A&c;-OhoF*z@DhEW@d9o zbQBjqEQnn2aG?N9{bmD^A#Um6SDKsm0g{g_<4^dJjg_l_HXdDMk!p`oFv8+@_v_9> zq;#WkQ!GNGfLT7f8m60H@$tu?p;o_It#TApmE`xnZr|_|cb3XXE)N^buLE`9R=Qbg zXJu}6r07me2HU<)S7m?@GzrQDTE3UH?FXM7V+-lT#l}P(U>Fvnyw8T7RTeP`R579m zj=Y>qDw1h-;|mX-)cSXCc$?hr;43LQt)7z$1QG^pyclQ1Bd!jbzsVEgIg~u9b38;> zfsRa%U`l%did6HzPRd;TK{_EW;n^Ivp-%pu0%9G-z@Au{Ry+EqEcqW=z-#6;-!{WA z;l+xC6Zke>dl+(R1q7B^Hu~HmrG~Kt575mzve>x*cL-shl+zqp6yuGX)DDGm`cid! znlnZY=+a5*xQ=$qM}5$N+o!^(TqTFHDdyCcL8NM4VY@2gnNXF|D?5a558Lb*Yfm4) z_;0%2EF7k{)i(tTvS`l5he^KvW%l&-suPwpIlWB_Za1Hfa$@J!emrcyPpTKKM@NqL z?X_SqHt#DucWm<3Lp}W|&YyQE27zbGP55=HtZmB(k*WZA79f##?TweCt{%5yuc+Kx zgfSrIZI*Y57FOD9l@H0nzqOu|Bhrm&^m_RK6^Z<^N($=DDxyyPLA z+J)E(gs9AfaO`5qk$IGGY+_*tEk0n_wrM}n4G#So>8Dw6#K7tx@g;U`8hN_R;^Uw9JLRUgOQ?PTMr4YD5H7=ryv)bPtl=<&4&% z*w6k|D-%Tg*F~sh0Ns(h&mOQ_Qf{`#_XU44(VDY8b})RFpLykg10uxUztD>gswTH} z&&xgt>zc(+=GdM2gIQ%3V4AGxPFW0*l0YsbA|nFZpN~ih4u-P!{39d@_MN)DC%d1w z7>SaUs-g@Hp7xqZ3Tn)e z7x^sC`xJ{V<3YrmbB{h9i5rdancCEyL=9ZOJXoVHo@$$-%ZaNm-75Z-Ry9Z%!^+STWyv~To>{^T&MW0-;$3yc9L2mhq z;ZbQ5LGNM+aN628)Cs16>p55^T^*8$Dw&ss_~4G5Go63gW^CY+0+Z07f2WB4Dh0^q z-|6QgV8__5>~&z1gq0FxDWr`OzmR}3aJmCA^d_eufde7;d|OCrKdnaM>4(M%4V`PxpCJc~UhEuddx9)@)9qe_|i z)0EA%&P@_&9&o#9eqZCUCbh?`j!zgih5sJ%c4(7_#|Xt#r7MVL&Q+^PQEg3MBW;4T zG^4-*8L%s|A}R%*eGdx&i}B1He(mLygTmIAc^G(9Si zK7e{Ngoq>r-r-zhyygK)*9cj8_%g z)`>ANlipCdzw(raeqP-+ldhyUv_VOht+!w*>Sh+Z7(7(l=9~_Vk ztsM|g1xW`?)?|@m2jyAgC_IB`Mtz(O`mwgP15`lPb2V+VihV#29>y=H6ujE#rdnK` zH`EaHzABs~teIrh`ScxMz}FC**_Ii?^EbL(n90b(F0r0PMQ70UkL}tv;*4~bKCiYm zqngRuGy`^c_*M6{*_~%7FmOMquOEZXAg1^kM`)0ZrFqgC>C%RJvQSo_OAA(WF3{euE}GaeA?tu5kF@#62mM$a051I zNhE>u>!gFE8g#Jj95BqHQS%|>DOj71MZ?EYfM+MiJcX?>*}vKfGaBfQFZ3f^Q-R1# znhyK1*RvO@nHb|^i4Ep_0s{lZwCNa;Ix<{E5cUReguJf+72QRZIc%`9-Vy)D zWKhb?FbluyDTgT^naN%l2|rm}oO6D0=3kfXO2L{tqj(kDqjbl(pYz9DykeZlk4iW5 zER`)vqJxx(NOa;so@buE!389-YLbEi@6rZG0#GBsC+Z0fzT6+d7deYVU;dy!rPXiE zmu73@Jr&~K{-9MVQD}&`)e>yLNWr>Yh8CXae9XqfvVQ&eC_;#zpoaMxZ0GpZz7xjx z`t_Q-F?u=vrRPaj3r<9&t6K=+egimiJ8D4gh-rUYvaVy zG($v+3zk5sMuOhjxkH7bQ}(5{PD3Mg?!@8PkK&w>n7tO8FmAmoF30_#^B~c(Q_`4L zYWOoDVSnK|1=p{+@`Fk^Qb81Xf89_S`RSTzv(a4ID%71nll%{Wad$!CKfeTKkyC?n zCkMKHU#*nz_(tO$M)UP&ZfJ#*q(0Gr!E(l5(ce<3xut+_i8XrK8?Xr7_oeHz(bZ?~8q5q~$Rah{5@@7SMN zx9PnJ-5?^xeW2m?yC_7A#WK*B@oIy*Y@iC1n7lYKj&m7vV;KP4TVll=II)$39dOJ^czLRU>L> z68P*PFMN+WXxdAu=Hyt3g$l(GTeTVOZYw3KY|W0Fk-$S_`@9`K=60)bEy?Z%tT+Iq z7f>%M9P)FGg3EY$ood+v$pdsXvG? zd2q3abeu-}LfAQWY@=*+#`CX8RChoA`=1!hS1x5dOF)rGjX4KFg!iPHZE2E=rv|A} zro(8h38LLFljl^>?nJkc+wdY&MOOlVa@6>vBki#gKhNVv+%Add{g6#-@Z$k*ps}0Y zQ=8$)+Nm||)mVz^aa4b-Vpg=1daRaOU)8@BY4jS>=5n#6abG@(F2`=k-eQ9@u# zxfNFHv=z2w@{p1dzSOgHokX1AUGT0DY4jQI@YMw)EWQ~q5wmR$KQ}Y;(HPMSQCwzu zdli|G?bj(>++CP)yQ4s6YfpDc3KqPmquQSxg%*EnTWumWugbDW5ef%8j-rT#3rJu? z)5n;4b2c*;2LIW%LmvUu6t1~di~}0&Svy}QX#ER|hDFZwl!~zUP&}B1oKAxIzt~so zb!GaJYOb#&qRUjEI1xe_`@7qv_-LggQ$JE8+{ryT4%ldwC5ete+{G3C#g@^oxfY3#F zcLlj(l2G8>tC<5XWV|6_DZQZ7ow?MD8EZ9mM2oV~WoV-uoExmbwpzc6eMV}%J_{3l zW(4t2a-o}XRlU|NSiYn!*nR(Sc>*@TuU*(S77gfCi7+WR%2b;4#RiyxWR3(u5BIdf zo@#g4wQjtG3T$PqdX$2z8Zi|QP~I^*9iC+(!;?qkyk&Q7v>DLJGjS44q|%yBz}}>i z&Ve%^6>xY<=Pi9WlwpWB%K10Iz`*#gS^YqMeV9$4qFchMFO}(%y}xs2Hn_E}s4=*3 z+lAeCKtS}9E{l(P=PBI;rsYVG-gw}-_x;KwUefIB@V%RLA&}WU2XCL_?hZHoR<7ED zY}4#P_MmX(_G_lqfp=+iX|!*)RdLCr-1w`4rB_@bI&Uz# z!>9C3&LdoB$r+O#n);WTPi;V52OhNeKfW6_NLnw zpFTuLC^@aPy~ZGUPZr;)=-p|b$-R8htO)JXy{ecE5a|b{{&0O%H2rN&9(VHxmvNly zbY?sVk}@^{aw)%#J}|UW=ucLWs%%j)^n7S%8D1Woi$UT}VuU6@Sd6zc2+t_2IMBxd zb4R#ykMr8s5gKy=v+opw6;4R&&46$V+OOpDZwp3iR0Osqpjx))joB*iX+diVl?E~Q zc|$qmb#T#7Kcal042LUNAoPTPUxF-iGFw>ZFnUqU@y$&s8%h-HGD`EoNBbe#S>Y-4 zlkeAP>62k~-N zHQqXXyN67hGD6CxQIq_zoepU&j0 zYO&}<4cS^2sp!;5))(aAD!KmUED#QGr48DVlwbyft31WlS2yU<1>#VMp?>D1BCFfB z_JJ-kxTB{OLI}5XcPHXUo}x~->VP%of!G_N-(3Snvq`*gX3u0GR&}*fFwHo3-vIw0 zeiWskq3ZT9hTg^je{sC^@+z3FAd}KNhbpE5RO+lsLgv$;1igG7pRwI|;BO7o($2>mS(E z$CO@qYf5i=Zh6-xB=U8@mR7Yjk%OUp;_MMBfe_v1A(Hqk6!D})x%JNl838^ZA13Xu zz}LyD@X2;5o1P61Rc$%jcUnJ>`;6r{h5yrEbnbM$$ntA@P2IS1PyW^RyG0$S2tUlh z8?E(McS?7}X3nAAJs2u_n{^05)*D7 zW{Y>o99!I9&KQdzgtG(k@BT|J*;{Pt*b|?A_})e98pXCbMWbhBZ$t&YbNQOwN^=F) z_yIb_az2Pyya2530n@Y@s>s>n?L79;U-O9oPY$==~f1gXro5Y z*3~JaenSl_I}1*&dpYD?i8s<7w%~sEojqq~iFnaYyLgM#so%_ZZ^WTV0`R*H@{m2+ zja4MX^|#>xS9YQo{@F1I)!%RhM{4ZUapHTKgLZLcn$ehRq(emb8 z9<&Nx*RLcS#)SdTxcURrJhxPM2IBP%I zf1bWu&uRf{60-?Gclb5(IFI*!%tU*7d`i!l@>TaHzYQqH4_Y*6!Wy0d-B#Lz7Rg3l zqKsvXUk9@6iKV6#!bDy5n&j9MYpcKm!vG7z*2&4G*Yl}iccl*@WqKZWQSJCgQSj+d ze&}E1mAs^hP}>`{BJ6lv*>0-ft<;P@`u&VFI~P3qRtufE11+|#Y6|RJccqo27Wzr}Tp|DH z`G4^v)_8}R24X3}=6X&@Uqu;hKEQV^-)VKnBzI*|Iskecw~l?+R|WKO*~(1LrpdJ? z0!JKnCe<|m*WR>m+Qm+NKNH<_yefIml z+x32qzkNRrhR^IhT#yCiYU{3oq196nC3ePkB)f%7X1G^Ibog$ZnYu4(HyHUiFB`6x zo$ty-8pknmO|B9|(5TzoHG|%>s#7)CM(i=M7Nl=@GyDi-*ng6ahK(&-_4h(lyUN-oOa$` zo+P;C4d@m^p9J4c~rbi$rq9nhGxayFjhg+Rqa{l#`Y z!(P6K7fK3T;y!VZhGiC#)|pl$QX?a)a9$(4l(usVSH>2&5pIu5ALn*CqBt)9$yAl; z-{fOmgu><7YJ5k>*0Q~>lq72!XFX6P5Z{vW&zLsraKq5H%Z26}$OKDMv=sim;K?vsoVs(JNbgTU8-M%+ zN(+7Xl}`BDl=KDkUHM9fLlV)gN&PqbyX)$86!Wv!y+r*~kAyjFUKPDWL3A)m$@ir9 zjJ;uQV9#3$*`Dqo1Cy5*;^8DQcid^Td=CivAP+D;gl4b7*xa9IQ-R|lY5tIpiM~9- z%Hm9*vDV@_1FfiR|Kqh_5Ml0sm?abD>@peo(cnhiSWs$uy&$RYcd+m`6%X9FN%?w}s~Q=3!pJzbN~iJ}bbM*PPi@!E0eN zhKcuT=kAsz8TQo76CMO+FW#hr6da({mqpGK2K4T|xv9SNIXZ}a=4_K5pbz1HE6T}9 zbApW~m0C`q)S^F}B9Kw5!eT)Bj_h9vlCX8%VRvMOg8PJ*>PU>%yt-hyGOhjg!2pZR4{ z=VR_*?Hw|aai##~+^H>3p$W@6Zi`o4^iO2Iy=FPdEAI58Ebc~*%1#sh8KzUKOVHs( z<3$LMSCFP|!>fmF^oESZR|c|2JI3|gucuLq4R(||_!8L@gHU8hUQZKn2S#z@EVf3? zTroZd&}JK(mJLe>#x8xL)jfx$6`okcHP?8i%dW?F%nZh=VJ)32CmY;^y5C1^?V0;M z<3!e8GZcPej-h&-Osc>6PU2f4x=XhA*<_K*D6U6R)4xbEx~{3*ldB#N+7QEXD^v=I z+i^L+V7_2ld}O2b-(#bmv*PyZI4|U#Q5|22a(-VLOTZc3!9ns1RI-? zA<~h|tPH0y*bO1#EMrsWN>4yJM7vqFZr?uw$H8*PhiHRQg1U9YoscX-G|gck+SSRX!(e7@~eeUEw+POsT;=W9J&=EV`cUc{PIg_#TQVGnZsQbCs7#Q-)v#BicxLw#Fb?#)8TYbu zN)5R=MI1i7FHhF|X}xEl=sW~`-kf;fOR^h1yjthSw?%#F{HqrY2$q>7!nbw~nZ8q9 zh{vY! z%i=H!!P&wh z7_E%pB7l5)*VU>_O-S~d5Z!+;f{pQ4e86*&);?G<9*Q$JEJ!ZxY;Oj5&@^eg0Zs!iLCAR`2K?MSFzjX;kHD6)^`&=EZOIdW>L#O`J zf~$M4}JiV}v6B-e{NUBGFgj-*H%NG zfY0X(@|S8?V)drF;2OQcpDl2LV=~=%gGx?_$fbSsi@%J~taHcMTLLpjNF8FkjnjyM zW;4sSf6RHaa~LijL#EJ0W2m!BmQP(f=%Km_N@hsBFw%q#7{Er?y1V~UEPEih87B`~ zv$jE%>Ug9&=o+sZVZL7^+sp)PSrS;ZIJac4S-M>#V;T--4FXZ*>CI7w%583<{>tb6 zOZ8gZ#B0jplyTbzto2VOs)s9U%trre`m=RlKf{I_Nwdxn(xNG%zaVNurEYiMV3*g| z``3;{j7`UyfFrjlEbIJN{0db|r>|LA@=vX9CHFZYiexnkn$b%8Rvw0TZOQIXa;oTI zv@j;ZP+#~|!J(aBz9S{wL7W%Dr1H)G-XUNt9-lP?ijJ-XEj1e*CI~-Xz@4(Xg;UoG z{uzBf-U+(SHe}6oG%;A*93Zb=oE>uTb^%qsL>|bQf?7_6=KIiPU`I|r;YcZ!YG7y~ zQu@UldAwz$^|uoz3mz1;An-WVBtefSh-pv<`n&TU3oM!hrEI?l@v8A4#^$4t&~T32 zl*J=1q~h+60sNc43>0aVvhzyfjshgPYZoQ(OOh>LbUIoblb@1z~zp?))n?^)q6WGuDh}gMUaA9|X z3qq-XlcNldy5==T4rq*~g@XVY!9sYZjo#R7 zr{n)r5^S{9+$+8l7IVB*3_k5%-TBY@C%`P@&tZf>82sm#nfw7L%92>nN$663yW!yt zhS>EfLcE_Z)gv-Y^h1;xj(<4nD4GY{C-nWUgQc9cMmH{qpa!uEznrGF^?bbJHApScQ$j>$JZHAX80DdXu z--AMgrA0$Otdd#N9#!cg2Z~N8&lj1d+wDh+^ZObWJ$J)_h(&2#msu>q0B$DEERy{1 zCJN{7M@%#E@8pda`@u!v@{gcT3bA*>g*xYLXlbb&o@1vX*x+l}Voys6o~^_7>#GB| z*r!R%kA9k%J`?m>1tMHB9x$ZRe0$r~ui}X}jOC)9LH=Po*2SLdtf3^4?VKnu2ox&mV~0oDgi` z;9d}P$g~9%ThTK8s}5ow2V4?(-lU*ed8ro|}mU}pk% z;bqB0bx3AOk<0Joeh}Vl@_7Po&C`Cg>>gff>e7fu41U3Ic{JQu1W%+!Gvz3GDO2ixKd;KF6UEw8F_cDAh08gB>@ zaRH2Q96sBJ>`4aXvrF0xPtIWoA1pPsRQtU~xDtnEfTJnl{A9u5pR^K8=UdNq%T8F$)FbN> zgK+_(BF#D>R>kK!M#OT~=@@}3yAYqm33?{Bv?2iBr|-aRK0@uapzuXI)wE0=R@m^7 zQ`wLBn(M*wg!mgmQT1d!@3<2z>~rmDW)KG0*B4>_R6LjiI0^9QT8gtDDT|Lclxppm z+OeL6H3QpearJAB%1ellZ6d*)wBQ(hPbE=%?y6i^uf%`RXm*JW*WQ%>&J+=V(=qf{ zri~yItvTZbII+7S0>4Q0U9@>HnMP$X>8TqAfD(vAh};2P{QK)ik`a6$W$nG<{bR2Ufd!^iE z#1K58$gW!xpeYHeehuhQCXZ9p%N8m zB+l~T_u-Ycr!U>!?xu!!*6rNxq37{`DhMMfY6NpD3Jw zkYQDstvt30Hc_SaZuuMP2YrdW@HsPMbf^Y9lI<9$bnMil2X7`Ba-DGLbzgqP>mxwe zf1&JkDH54D3nLar2KjJ3z`*R+rUABq4;>>4Kjc2iQEj7pVLcZYZ~pteAG4rm1{>PQy=!QiV5G|tVk)53 zP?Azw+N)Yq3zZ`dW7Q9Bq@Y*jSK0<1f`HM;_>GH57pf_S%Ounz_yhTY8lplQSM`xx zU{r-Deqs+*I~sLI$Oq`>i`J1kJ(+yNOYy$_>R3Jfi680<|^u#J@aY%Q>O zqfI~sCbk#3--^zMkV&Yj0D(R^rK}+_npgPr_4^kYuG=pO%$C_7v{s@-{M-P@RL3^<`kO@b=YdKMuccfO1ZW# zeRYE%D~CMAgPlo?T!O6?b|pOZv{iMWb;sN=jF%=?$Iz_5zH?K;aFGU^8l7u%zHgiy z%)~y|k;Es-7YX69AMj^epGX#&^c@pp+lc}kKc`5CjPN4Z$$e58$Yn*J?81%`0~A)D zPg-db*pj-t4-G9>ImW4IMi*v#9z^9VD9h@9t;3jMAUVxt=oor+16yHf{lT|G4 zya6{4#BxFw!!~UTRwXXawKU4iz$$GMY6=Z8VM{2@0{=5A0+A#p6$aT3ubRyWMWPq9 zCEH5(Il0v4e4=Yxg(tDglfYAy!UpC>&^4=x7#6_S&Ktds)a8^`^tp6RnRd{KImB^o z2n=t#>iKx<*evmvoE{+fH#@WXGWs$)Uxrtf?r>AaxV0?kf0o@oDboJ6z0cgP@A$;k>SK1UqC?Q_ zk_I?j74;}uNXhOf_5ZxQSgB4otDEb9JJrX1kq`-o%T>g%M5~xXf!2_4P~K64tKgXq z&KHZ0@!cPvUJG4kw-0;tPo$zJrU-Nop>Uo65Pm|yaNvKjhi7V1g98;^N1~V3% zTR>yWa+X2FJ_wpPwz3i^6AGwOa_VMS-&`*KoKgF2&oR10Jn6{!pvVG@n=Jk@vjNuY zL~P7aDGhg~O9G^!bHi$8?G9v9Gp0cmekYkK;(q=47;~gI>h-kx-ceM{ml$#8KI$4ltyjaqP zki^cyDERloAb)dcDBU4na9C(pfD{P@eBGA}0|Rb)p{ISqi60=^FUEdF!ok{Gs;vb) zfj9(#1QA64w*ud^YsN5&PeiI>c`VioE8h)e}W%S9NMA55Gs zrWL6l+@3CKd@8(UQLTwe12SGWMqRn+j)QZRj*g)Xua)%ayzpqs{pD(WWESJYL3{M$ z%qkpM`jFoqLYVv6{IbCkL?fEiJj$VG=$taup&RL9e{s(Sgse2xVJlw0h74EXJKt2eX|dxz{->0)3W`JN7Bv!rLvRZc z0tAOZ2yVe4g9iq826qXAg`f!*+}(o1;1FDb>kKexumFS40KvK0yH1_@Z=LgWZ+}(Y zwYsa;OLz6tTA%gS=>8$=Z7pLh>|K2QElL)E=Q*(n*H`8R`8={-@4mTD-SWBOYRxV? zmF(-rJB8^Wlp?319rTrh^?QEP?|Msxrv?WbJ-+id+V#F2Y4(JPJ6U9bv+U1cIIH^W z)lg$_=g^Ma>2~Pyd_YOAv29Cb-U6DJO?NxnW7~QP*SmYi*vdUVuW#LWQ_u0`hymZi zaQS3Nb^4`ro$>0G%zbXmr5|D|iq0R<;S@?kr0j5Ruq87-Z1>crx%EzVZ9#U;{?}ti zW2W%*9MQg3Nbh%Ti6LhDd|-aFSgXoPG`mHlUU1iCHr>ru>DX?W_#13(`u*!Plu2OP z6jk=2>BC0l)aw;HCmxoYD1i4b%m$1`DYC_^L~ zIEAnFcHvad=-aO3(_MI=9#`z6-9*_!&$?<%meb5;jGd5Qp=MGf z6BD{%`L#TAOq%z%@*ib95Ey7NbUF=BlszVk3Iu3imD&*91N-ij%hW?W@~2TtdHTfP z#n0@Xd7X8Dyu36n{k#PwQ~T~X7mAO^cNV+z<HO@3X-# z_@rAn$k~(l@kciCC;&Qd*fWRI>=;fL{UPlciNDWyj$bX<#r^(r;EE8wwUVQm&7~QY zCXRj!**r^xybAEPq>h3W$uvI1j=yNIyzkE_D7fpGw)OV{U*Uwm{xB;mEg2(|y|ICd zMdQVqzMb-=XM6|E-a9kNh)^9lY`-DjhhHD1w5lufRcy+QLgJ47!fFne86#F; zX{ufroVBEZJOY?rDo!;Te6aOZ^1SO!dYRxQ*2njyA~dCWawn)>!*k7~>8Ikt&e*0>>V5ZbO|*1+2LFOqVe zXHb!aMk03^h%&9L8GMy7UDI2Kev>V@(R}*Iu6x+!Hn4~D@wj`P%#Hdbf(lK{+DD7f zJ&(v*mhn_e(R$^5L#bM^^Q@-!*b!l|+Xrb(q*MRFJYnrE7*xko!SJOy9LngR2|q5k zY`Ioiu+YBfzF{Labszk-E#*BYQk>$()=xWEGZRKwY)*UxP}0dGuPLZOkNJDI9Hy zFjfwiK6RjhH#rHW#B0(MW}i%V`943<6@Z*Nd^JEP5uZonXm=u%AM>{H^U@&Jy*i0s za_Da^xI6pMtXzHc{e~_ZcnKP*;=YL2Z^RmzDl{dJTk7*}E_h*NvgnhnxVKB59Duh~ zqouS_WoOR*{UvUw_K#OWz;gMracr%8>QQ&V*jv!8)ho;U8}9~8EU{N<=Z_gR%IpMT zbkePUG_afm=#|iIfFmdqkpLMGxY5D$`?I}&T7>TexU@v zkBx09kG)O;09ckj#(_Uov6vv{{HOcr-%H#DUQ@*GzF8Zh{iSM13%fuB%>wjdU@3Nf zlnYE!GTyNrqes|;nLFXfWU*Wg-9wmr=NBd$nCk+H?iwNvcd0Wab^3CT9a`>3V~oWI z9=_H+N-Q=MQ(io4u4mpdQ;k&5FXnKV5M7R`@WJ9h(GrAirO#XXOU{qQpk^B^Vd=Dt{wiqT zg-#j9J~@o%H2;W9mg)o6@*Vo;BSs2*4HAHpDk02mndAsov08R_48zJZ@J)s7+hyCo zy*0L#y)?AqZt-wX%+_Vx`8*A95OLHvs1$k~{h-_N_vov_gHJE=`X>L?5K+ zD?u59=mjtImMvd1GsDytuYp{IyUkW&?h zF>$#`n$~bZ)KN0B$XGeMYh&`;g8 zo_2-koaO6+8O!+L>SpIQbG(i;QW9UJi{Ecewlo?s&D!^>i$|#jaW}#HJuxt|W48=? zb^Y&O$a1s5ddr8DIt!sD!t=y1g(d4GR(s;s-HfV$GXl&m;+sAAxB^rk(3_NjE$p#L z*t4em?tA0d+XwRxN^OQwzbDZMuSE0J1)Ky{mq)^t4bnSl*)s>zNM@mMdtd78&ebHN z`!(|lE5q-p+TsRaNnMXwALaN5QIZ2IUi^Z22tsN5>nvIO+YU}Q*xh6}ee6@rR~<&1 z(PB4z>9ZBUMXZwSMmd9-aKKsmJeJq^G|#JclOh*xf0?^e0(`40nsg1z)(48;4}B_( zGwPI)yo|{oX{dVDL-5-aMGr;~vU1cPtJP5JM(sswz&Q`e<@0?y{YhsO9YK8EYJA;L z>7oG_Mts+(wCBC*Md82#XdKw&J*IizR?9k^rf1r{Ot-&>V^ke{9nI9zavlcNkIJtN z7T>?o|4rENk-?|lewZ(EfdR;%BUrzKJ^UkCpsM)EA9QHBVV8trT&*O(9?FO{MLTFL z=5P0H+T6C^jAuX0k4U;~GM!x`!X2N~3_n?qXY$HI>x@(DHEy&Q3ucT1R6fj28wX!I zC=&d$@bJ_v^%?W2Ngl}e8ww`b%BrN-PzGH;$@B2Ky1?%GMkm#~Okj(-Admyy;qya| zOi73kr_pwt?5Nj3p=&H>81!w#>Agj z(QXx{j0r=pTl>micAI_5vUw<3`Sht?Z}-j2Wx~F8DKCUQrsXl2?W8hur42(F_ zsSJ)_36&x6A|YkY6c<2a94SXbv~d>4CC4nkDPvf9Z5Fys^6^5r0j5=E>Cgy_Dk@tS z%?c}9!qB?t6t8(XMH%le8UeNWp@Nsma~Ql+^3Bo%_npMryeQJz4V=BAqE~T?dejng z3ge{fjCHoNAfYBvsfq;G%VL|j7t z`X0sy1EEgpyD;)tS1x+fnv-?C@glP0{RCW}Ma?3qpoq_&IJAYOy3G#s`rsh5=3>`K zkj``=;|*x5HSjZC zXNvPLh372q;=+6ja|SC!R-`JcL}}wwskajjTUGTpL(1zkN-p?BA2lmf+J3WsB7!k`0Brx8^cLTF9h)r+LZ$vsZo}`OpOs)?c6$hclR!R#MAeh|_DY|9r zy+_3c%IO9h9X?ksp?an&>Lw;QeQ`T-Ku6HaK~H?E9-Z5$cZu{YU;1+-6B$|JD;%!^ zt(4l>F8}a-UkC4YtOxFHckhl4VKr6P$P_O*U!)IDory%}Wz`YeFx6TO{y2Y${SBm?H9cTWV=WWJ z`_*CGso!ZN>l@~_jkeXtV}fczfA{TUkyeD>)i3|NFGcCsBmK3HXp&ol_@GVs7PIpfULy!hi zs+%KYgS%(n7_z_}6)hblk~W#LZ@&2)fwm6xkFP%&Ju|MFWbNiTwy{{g-pV1RK`L&=RE2D z4|g;~vd8xd|teYS%w!IlT4W$&FTrk-hcTADX!P?*f1YWEIRwq$Ys%^(Z9w&HT$>} zsMD#6Df=uJrX!JHP7<>Or;e_Cf=}`!`qR=i8fBj)$6Lxx{HRzd8Tnzd0p>kSps{OG zKJkml>bUj8$u|F=``l(-aMxWBC@CGZ#FXClQZ<4|&%jN}Tkg#q8z)=>Ly{$i0`rjU zvt|QddO&i=91e?h3>s~i;+6{ z8X4i6a1wDLrSuE#W(zhan+U*Zq+8p3a))JFVF4ffaV51K^YgTso~3;Y*NmM; zx8T?y-N0uyWY(8=me-HUC9xtABvX5~%yg+Cp&XF$Bq=OcK6T*D7eZ2EmIoCFWm{$S z1PNw8HDpe5hHeCusN8kdeb&f2#=3M^A~7YwJ7FRrhq*)PG9x?JIAaC{MV}5}g#7R$-Ly%)4=IUkRCGOR|XTMjn&okRmFjaO^YF5^* z@)#MCBOBezD)*xQNxydlUyN?dW{fS(s-T`gv*0BEnk}`BdmrbmPO8q8y(X$AA}*RH%I7Av!~84pudHb&%Q5-j zt?=6x(iR?<^_7X0v6Ys#VAL}dKk^hcjI=|EY;kPcZ_w<*H`_*|N7SacaM1ERD@6ab zg`!iTm7$URV+lpW_{V$ruR&A>jrX68k4x2wo$45}&wf7o<|o(@B!u-L@bKyQBAGwy z4#}UrRAu>^>Vb6k2-th^>WjvP;Nl|i3WrjWv3ISkj{m{eAcQIW^_ndxSX@|8T(ASJ z?_$fcP2u*6uOBk-{d>^ z0vWlfGQMvysI%R=iE|A+!!Nw?C917EU*_$`;;)px?s83CRd3i_jBN)k#nR5t$dJ(+ z_sP;wG@Ad)^(3LRj7q}0b2O(b`|i0~5SYb%Sjk^*5ISZ-Ab+}DGu$-X1n^TF1Ndw_ zF|e*1)cI2%`TR&AW~XpqpFb!=3cHbS>np9hYD_Mr5}y5Y`SY^r7isA2Q4(z zazRQEqWDKT2zIEbjSYdCPi1ZOGz80Nsl}gxO^DWMY0AV<2K&OL{&^6#@L1?lXu#6xSMh%3^5c*}oM6DQGY#(a^@z<&D zF(43I9e&5`h|A$5!+UFuOH0>F3$shBV4`0#M4RSB8=6F0ZgIbq<2LQ$Hh^(kAJu=! zt8ZGXTacD{(3W{V1$j_{Jc)Ka7t6u}ho`4kF+4@t_0!mCBn z)}o%eA}L)_L?=jw6BIfll7tb3n}?*yLt&XADa=rW>qz=_6s9ziOd5sXjil>FVFx3r zf>Feewk0v#W9>Gp4GacTRr>Sd2T6dWi-{YX`v!D)kCWzG5xQB=?es5ON(%nkwUhNl zV>@xkWWWv*N+{e$(SrExvN6BXzU(Hxlx27{VYHf+LpIbTO+Yu(ltMk<;)3A(LU@ytVYFkYvTa79idMtUFhfxx?P!)2F`prNWW#Fub#l>N2s@nh&n_ zA4{#}|AIs9|A4P0ZF%fy=hDN!t#ifH<)4u2kirK~JUpjQ-J+~cXOZI&dIts;P}UeXslP6zKvpEKSN-$y>kJ^nw2tC9bv zo(|lT@?vZ!{_l|d^8Yh)eEBh*5ABh+Lzjw+?V)o z#P-W7361>E(Y4;@`sv;VKn G`u_lkUM?>H literal 0 HcmV?d00001 diff --git a/freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.woff2 b/freescout-dist/public/fonts/glyphicons/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 zcmV(~K+nH-Pew8T0RR9107h&84*&oF0I^&E07eM_0Rl|`00000000000000000000 z0000#Mn+Uk92y`7U;vDA2m}!b3WBL5f#qcZHUcCAhI9*rFaQJ~1&1OBl~F%;WnyLq z8)b|&?3j;$^FW}&KmNW53flIFARDZ7_Wz%hpoWaWlgHTHEHf()GI0&dMi#DFPaEt6 zCO)z0v0~C~q&0zBj^;=tv8q{$8JxX)>_`b}WQGgXi46R*CHJ}6r+;}OrvwA{_SY+o zK)H-vy{l!P`+NG*`*x6^PGgHH4!dsolgU4RKj@I8Xz~F6o?quCX&=VQ$Q{w01;M0? zKe|5r<_7CD z=eO3*x!r$aX2iFh3;}xNfx0v;SwBfGG+@Z;->HhvqfF4r__4$mU>Dl_1w;-9`~5rF~@!3;r~xP-hZvOfOx)A z#>8O3N{L{naf215f>m=bzbp7_(ssu&cx)Qo-{)!)Yz3A@Z0uZaM2yJ8#OGlzm?JO5gbrj~@)NB4@?>KE(K-$w}{};@dKY#K3+Vi64S<@!Z{(I{7l=!p9 z&kjG^P~0f46i13(w!hEDJga;*Eb z`!n|++@H8VaKG<9>VDh(y89J#=;Z$ei=GnD5TesW#|Wf)^D+9NKN4J3H5PF_t=V+Z zdeo8*h9+8&Zfc?>>1|E4B7MAx)^uy$L>szyXre7W|81fjy+RZ1>Gd}@@${~PCOXo) z$#HZd3)V3@lNGG%(3PyIbvyJTOJAWcN@Uh!FqUkx^&BuAvc)G}0~SKI`8ZZXw$*xP zum-ZdtPciTAUn$XWb6vrS=JX~f5?M%9S(=QsdYP?K%Odn0S0-Ad<-tBtS3W06I^FK z8}d2eR_n!(uK~APZ-#tl@SycxkRJ@5wmypdWV{MFtYBUY#g-Vv?5AEBj1 z`$T^tRKca*sn7gt%s@XUD-t>bij-4q-ilku9^;QJ3Mpc`HJ_EX4TGGQ-Og)`c~qm51<|gp7D@ zp#>Grssv^#A)&M8>ulnDM_5t#Al`#jaFpZ<#YJ@>!a$w@kEZ1<@PGs#L~kxOSz7jj zEhb?;W)eS}0IQQuk4~JT30>4rFJ3!b+77}>$_>v#2FFEnN^%(ls*o80pv0Q>#t#%H z@`Yy-FXQ9ULKh{Up&oA_A4B!(x^9&>i`+T|eD!&QOLVd(_avv-bFX~4^>o{%mzzrg_i~SBnr%DeE|i+^}|8?kaV(Z32{`vA^l!sp15>Z72z52FgXf z^8ZITvJ9eXBT1~iQjW|Q`Fac^ak$^N-vI^*geh5|*CdMz;n16gV_zk|Z7q8tFfCvU zJK^Pptnn0Rc~egGIAK}uv99VZm2WLPezQQ5K<`f zg{8Ll|GioPYfNheMj-7-S87=w4N0WxHP`1V6Y)0M&SkYzVrwp>yfsEF7wj&T0!}dB z)R~gGfP9pOR;GY_e0~K^^oJ-3AT+m~?Al!{>>5gNe17?OWz)$)sMH*xuQiB>FT2{i zQ>6U_8}Ay~r4li;jzG+$&?S12{)+<*k9 z<^SX#xY|jvlvTxt(m~C7{y{3g>7TX#o2q$xQO|fc<%8rE@A3=UW(o?gVg?gDV!0q6O!{MlX$6-Bu_m&0ms66 znWS&zr{O_4O&{2uCLQvA?xC5vGZ}KV1v6)#oTewgIMSnBur0PtM0&{R5t#UEy3I9) z`LVP?3f;o}sz*7g5qdTxJl^gk3>;8%SOPH@B)rmFOJ)m6?PlYa$y=RX%;}KId{m9R#2=LNwosF@OTivgMqxpRGe}5=LtAn?VVl6VWCFLD z7l#^^H8jY~42hR)OoVF#YDW(md!g(&pJ;yMj|UBAQa}UH?ED@%ci=*(q~Opn>kE2Q z_4Kgf|0kEA6ary41A;)^Ku(*nirvP!Y>{FZYBLXLP6QL~vRL+uMlZ?jWukMV*(dsn zL~~KA@jU)(UeoOz^4Gkw{fJsYQ%|UA7i79qO5=DOPBcWlv%pK!A+)*F`3WJ}t9FU3 zXhC4xMV7Z%5RjDs0=&vC4WdvD?Zi5tg4@xg8-GLUI>N$N&3aS4bHrp%3_1u9wqL)i z)XQLsI&{Hd&bQE!3m&D0vd!4D`l1$rt_{3NS?~lj#|$GN5RmvP(j3hzJOk=+0B*2v z)Bw133RMUM%wu_+$vbzOy?yk#kvR?xGsg-ipX4wKyXqd zROKp5))>tNy$HByaEHK%$mqd>-{Yoj`oSBK;w>+eZ&TVcj^DyXjo{DDbZ>vS2cCWB z(6&~GZ}kUdN(*2-nI!hvbnVy@z2E#F394OZD&Jb04}`Tgaj?MoY?1`{ejE2iud51% zQ~J0sijw(hqr_Ckbj@pm$FAVASKY(D4BS0GYPkSMqSDONRaFH+O2+jL{hIltJSJT~e)TNDr(}=Xt7|UhcU9eoXl&QZRR<9WomW%&m)FT~j zTgGd3-j}Uk%CRD;$@X)NNV9+RJbifYu>yr{FkO;p>_&njI> zyBHh_72bW;8}oGeY0gpHOxiV597j7mY<#?WMmkf5x~Kfk*re(&tG_mX<3&2cON*2u%V29tsXUv{#-ijs2>EuNH-x3) zPBpi+V6gI=wn}u164_j8xi-y(B?Au2o;UO=r6&)i5S3Mx*)*{_;u}~i4dh$`VgUS- zMG6t*?DXDYX0D2Oj31MI!HF>|aG8rjrOPnxHu4wZl;!=NGjjDoBpXf?ntrwt^dqxm zs(lE@*QB3NH)!`rH)5kks-D89g@UX&@DU9jvrsY)aI=9b4nPy3bfdX_U;#?zsan{G>DKob2LnhCJv8o}duQK)qP{7iaaf2=K`a-VNcfC582d4a z>sBJA*%S|NEazDxXcGPW_uZ&d7xG`~JB!U>U(}acUSn=FqOA~(pn^!aMXRnqiL0;? zebEZYouRv}-0r;Dq&z9>s#Rt1HL`0p4bB)A&sMyn|rE_9nh z?NO*RrjET8D4s(-`nS{MrdYtv*kyCnJKbsftG2D#ia@;42!8xd?a3P(&Y?vCf9na< zQ&Ni*1Qel&Xq{Z?=%f0SRqQt5m|Myg+8T=GDc)@^};=tM>9IDr7hdvE9-M@@<0pqv45xZTeNecbL- zWFQt4t`9>j8~X%lz}%We>Kzh_=`XO}!;4!OWH?=p*DOs#Nt({k^IvtBEL~Qafn)I^ zm*k{y7_bIs9YE}0B6%r`EIUH8US+MGY!KQA1fi-jCx9*}oz2k1nBsXp;4K<_&SN}}w<)!EylI_)v7}3&c)V;Cfuj*eJ2yc8LK=vugqTL><#65r6%#2e| zdYzZ)9Uq7)A$ol&ynM!|RDHc_7?FlWqjW>8TIHc`jExt)f5W|;D%GC#$u!%B*S%Z0 zsj&;bIU2jrt_7%$=!h4Q29n*A^^AI8R|stsW%O@?i+pN0YOU`z;TVuPy!N#~F8Z29 zzZh1`FU(q31wa>kmw{$q=MY>XBprL<1)Py~5TW4mgY%rg$S=4C^0qr+*A^T)Q)Q-U zGgRb9%MdE-&i#X3xW=I`%xDzAG95!RG9)s?v_5+qx`7NdkQ)If5}BoEp~h}XoeK>kweAMxJ8tehagx~;Nr_WP?jXa zJ&j7%Ef3w*XWf?V*nR)|IOMrX;$*$e23m?QN` zk>sC^GE=h6?*Cr~596s_QE@>Nnr?{EU+_^G=LZr#V&0fEXQ3IWtrM{=t^qJ62Sp=e zrrc>bzX^6yFV!^v7;>J9>j;`qHDQ4uc92eVe6nO@c>H=ouLQot``E~KLNqMqJ7(G+?GWO9Ol+q$w z!^kMv!n{vF?RqLnxVk{a_Ar;^sw0@=+~6!4&;SCh^utT=I zo&$CwvhNOjQpenw2`5*a6Gos6cs~*TD`8H9P4=#jOU_`%L!W;$57NjN%4 z39(61ZC#s7^tv`_4j}wMRT9rgDo*XtZwN-L;Qc$6v8kKkhmRrxSDkUAzGPgJ?}~_t zkwoGS4=6lsD`=RL|8L3O9L()N)lmEn-M15fRC{dhZ}7eYV%O-R^gsAp{q4 z!C1}_T8gy^v@SZ5R&Li5JMJy+K8iZw3LOGA0pN1~y@w7RRl#F()ii6Y5mr~Mdy@Kz z@FT4cm^I&#Fu_9IX(HAFP{XLbRALqm&)>m_we>a`hfv?eE|t z?YdDp2yAhj-~vuw^wzVDuj%w?exOcOT(ls(F*ceCe(C5HlN{lcQ;}|mRPqFDqLEzw zR7ldY+M6xe$$qLwekmk{Z&5cME$gpC?-8)f0m$rqaS|mj9ATNJvvyCgs(f2{r;2E!oy$k5{jik#(;S>do<#m0wVcU<}>)VtYmF9O0%(C>GDzPgh6X z9OkQLMR~y7=|MtaU!LDPPY7O)L{X#SC+M|v^X2CZ?$GS>U_|aC(VA(mIvCNk+biD| zSpj>gd(v>_Cbq>~-x^Y3o|?eHmuC?E&z>;Ij`%{$Pm$hI}bl0Kd`9KD~AchY+goL1?igDxf$qxL9< z4sW@sD)nwWr`T>e2B8MQN|p*DVTT8)3(%AZ&D|@Zh6`cJFT4G^y6`(UdPLY-&bJYJ z*L06f2~BX9qX}u)nrpmHPG#La#tiZ23<>`R@u8k;ueM6 znuSTY7>XEc+I-(VvL?Y>)adHo(cZ;1I7QP^q%hu#M{BEd8&mG_!EWR7ZV_&EGO;d(hGGJzX|tqyYEg2-m0zLT}a{COi$9!?9yK zGN7&yP$a|0gL`dPUt=4d^}?zrLN?HfKP0_gdRvb}1D73Hx!tXq>7{DWPV;^X{-)cm zFa^H5oBDL3uLkaFDWgFF@HL6Bt+_^g~*o*t`Hgy3M?nHhWvTp^|AQDc9_H< zg>IaSMzd7c(Sey;1SespO=8YUUArZaCc~}}tZZX80w%)fNpMExki-qB+;8xVX@dr; z#L52S6*aM-_$P9xFuIui;dN#qZ_MYy^C^hrY;YAMg;K`!ZpKKFc z9feHsool)`tFSS}Su|cL0%F;h!lpR+ym|P>kE-O`3QnHbJ%gJ$dQ_HPTT~>6WNX41 zoDEUpX-g&Hh&GP3koF4##?q*MX1K`@=W6(Gxm1=2Tb{hn8{sJyhQBoq}S>bZT zisRz-xDBYoYxt6--g2M1yh{#QWFCISux}4==r|7+fYdS$%DZ zXVQu{yPO<)Hn=TK`E@;l!09aY{!TMbT)H-l!(l{0j=SEj@JwW0a_h-2F0MZNpyucb zPPb+4&j?a!6ZnPTB>$t`(XSf-}`&+#rI#`GB> zl=$3HORwccTnA2%>$Nmz)u7j%_ywoGri1UXVNRxSf(<@vDLKKxFo;5pTI$R~a|-sQ zd5Rfwj+$k1t0{J`qOL^q>vZUHc7a^`cKKVa{66z?wMuQAfdZBaVVv@-wamPmes$d! z>gv^xx<0jXOz;7HIQS z4RBIFD?7{o^IQ=sNQ-k!ao*+V*|-^I2=UF?{d>bE9avsWbAs{sRE-y`7r zxVAKA9amvo4T}ZAHSF-{y1GqUHlDp4DO9I3mz5h8n|}P-9nKD|$r9AS3gbF1AX=2B zyaK3TbKYqv%~JHKQH8v+%zQ8UVEGDZY|mb>Oe3JD_Z{+Pq%HB+J1s*y6JOlk`6~H) zKt)YMZ*RkbU!GPHzJltmW-=6zqO=5;S)jz{ zFSx?ryqSMxgx|Nhv3z#kFBTuTBHsViaOHs5e&vXZ@l@mVI37<+^KvTE51!pB4Tggq zz!NlRY2ZLno0&6bA|KHPYOMY;;LZG&_lzuLy{@i$&B(}_*~Zk2 z>bkQ7u&Ww%CFh{aqkT{HCbPbRX&EvPRp=}WKmyHc>S_-qbwAr0<20vEoJ(!?-ucjE zKQ+nSlRL^VnOX0h+WcjGb6WI(8;7bsMaHXDb6ynPoOXMlf9nLKre;w*#E_whR#5!! z!^%_+X3eJVKc$fMZP;+xP$~e(CIP1R&{2m+iTQhDoC8Yl@kLM=Wily_cu>7C1wjVU z-^~I0P06ZSNVaN~A`#cSBH2L&tk6R%dU1(u1XdAx;g+5S^Hn9-L$v@p7CCF&PqV{Z?R$}4EJi36+u2JP7l(@fYfP!=e#76LGy^f>~vs0%s*x@X8`|5 zGd6JOHsQ=feES4Vo8%1P_7F5qjiIm#oRT0kO1(?Z_Dk6oX&j=Xd8Klk(;gk3S(ZFnc^8Gc=d;8O-R9tlGyp=2I@1teAZpGWUi;}`n zbJOS_Z2L16nVtDnPpMn{+wR9&yU9~C<-ncppPee`>@1k7hTl5Fn_3_KzQ)u{iJPp3 z)df?Xo%9ta%(dp@DhKuQj4D8=_!*ra#Ib&OXKrsYvAG%H7Kq|43WbayvsbeeimSa= z8~{7ya9ZUAIgLLPeuNmSB&#-`Je0Lja)M$}I41KHb7dQq$wgwX+EElNxBgyyLbA2* z=c1VJR%EPJEw(7!UE?4w@94{pI3E%(acEYd8*Wmr^R7|IM2RZ-RVXSkXy-8$!(iB* zQA`qh2Ze!EY6}Zs7vRz&nr|L60NlIgnO3L*Yz2k2Ivfen?drnVzzu3)1V&-t5S~S? zw#=Sdh>K@2vA25su*@>npw&7A%|Uh9T1jR$mV*H@)pU0&2#Se`7iJlOr$mp79`DKM z5vr*XLrg7w6lc4&S{So1KGKBqcuJ!E|HVFB?vTOjQHi)g+FwJqX@Y3q(qa#6T@3{q zhc@2T-W}XD9x4u+LCdce$*}x!Sc#+rH-sCz6j}0EE`Tk*irUq)y^za`}^1gFnF)C!yf_l_}I<6qfbT$Gc&Eyr?!QwJR~RE4!gKVmqjbI+I^*^ z&hz^7r-dgm@Mbfc#{JTH&^6sJCZt-NTpChB^fzQ}?etydyf~+)!d%V$0faN(f`rJb zm_YaJZ@>Fg>Ay2&bzTx3w^u-lsulc{mX4-nH*A(32O&b^EWmSuk{#HJk}_ULC}SB(L7`YAs>opp9o5UcnB^kVB*rmW6{s0&~_>J!_#+cEWib@v-Ms`?!&=3fDot`oH9v&$f<52>{n2l* z1FRzJ#yQbTHO}}wt0!y8Eh-0*|Um3vjX-nWH>`JN5tWB_gnW%; zUJ0V?_a#+!=>ahhrbGvmvObe8=v1uI8#gNHJ#>RwxL>E^pT05Br8+$@a9aDC1~$@* zicSQCbQcr=DCHM*?G7Hsovk|{$3oIwvymi#YoXeVfWj{Gd#XmnDgzQPRUKNAAI44y z{1WG&rhIR4ipmvBmq$BZ*5tmPIZmhhWgq|TcuR{6lA)+vhj(cH`0;+B^72{&a7ff* zkrIo|pd-Yxm+VVptC@QNCDk0=Re%Sz%ta7y{5Dn9(EapBS0r zLbDKeZepar5%cAcb<^;m>1{QhMzRmRem=+0I3ERot-)gb`i|sII^A#^Gz+x>TW5A& z3PQcpM$lDy`zb%1yf!e8&_>D02RN950KzW>GN6n@2so&Wu09x@PB=&IkIf|zZ1W}P zAKf*&Mo5@@G=w&290aG1@3=IMCB^|G4L7*xn;r3v&HBrD4D)Zg+)f~Ls$7*P-^i#B z4X7ac=0&58j^@2EBZCs}YPe3rqgLAA1L3Y}o?}$%u~)7Rk=LLFbAdSy@-Uw6lv?0K z&P@@M`o2Rll3GoYjotf@WNNjHbe|R?IKVn*?Rzf9v9QoFMq)ODF~>L}26@z`KA82t z43e!^z&WGqAk$Ww8j6bc3$I|;5^BHwt`?e)zf|&+l#!8uJV_Cwy-n1yS0^Q{W*a8B zTzTYL>tt&I&9vzGQUrO?YIm6C1r>eyh|qw~-&;7s7u1achP$K3VnXd8sV8J7ZTxTh z5+^*J5%_#X)XL2@>h(Gmv$@)fZ@ikR$v(2Rax89xscFEi!3_;ORI0dBxw)S{r50qf zg&_a*>2Xe{s@)7OX9O!C?^6fD8tc3bQTq9}fxhbx2@QeaO9Ej+2m!u~+u%Q6?Tgz{ zjYS}bleKcVhW~1$?t*AO^p!=Xkkgwx6OTik*R3~yg^L`wUU9Dq#$Z*iW%?s6pO_f8 zJ8w#u#Eaw7=8n{zJ}C>w{enA6XYHfUf7h)!Qaev)?V=yW{b@-z`hAz;I7^|DoFChP z1aYQnkGauh*ps6x*_S77@z1wwGmF8ky9fMbM$dr*`vsot4uvqWn)0vTRwJqH#&D%g zL3(0dP>%Oj&vm5Re%>*4x|h1J2X*mK5BH1?Nx_#7( zepgF`+n)rHXj!RiipusEq!X81;QQBXlTvLDj=Qub(ha&D=BDx3@-V*d!D9PeXUY?l zwZ0<4=iY!sUj4G>zTS+eYX7knN-8Oynl=NdwHS*nSz_5}*5LQ@=?Yr?uj$`C1m2OR zK`f5SD2|;=BhU#AmaTKe9QaSHQ_DUj1*cUPa*JICFt1<&S3P3zsrs^yUE;tx=x^cmW!Jq!+hohv_B> zPDMT0D&08dC4x@cTD$o1$x%So1Ir(G3_AVQMvQ13un~sP(cEWi$2%5q93E7t{3VJf%K? zuwSyDke~7KuB2?*#DV8YzJw z&}SCDexnUPD!%4|y~7}VzvJ4ch)WT4%sw@ItwoNt(C*RP)h?&~^g##vnhR0!HvIYx z0td2yz9=>t3JNySl*TszmfH6`Ir;ft@RdWs3}!J88UE|gj_GMQ6$ZYphUL2~4OY7} zB*33_bjkRf_@l;Y!7MIdb~bVe;-m78Pz|pdy=O*3kjak63UnLt!{^!!Ljg0rJD3a~ z1Q;y5Z^MF<=Hr}rdoz>yRczx+p3RxxgJE2GX&Si)14B@2t21j4hnnP#U?T3g#+{W+Zb z5s^@>->~-}4|_*!5pIzMCEp|3+i1XKcfUxW`8|ezAh>y{WiRcjSG*asw6;Ef(k#>V ztguN?EGkV_mGFdq!n#W)<7E}1#EZN8O$O|}qdoE|7K?F4zo1jL-v}E8v?9qz(d$&2 zMwyK&xlC9rXo_2xw7Qe0caC?o?Pc*-QAOE!+UvRuKjG+;dk|jQhDDBe?`XT7Y5lte zqSu0t5`;>Wv%|nhj|ZiE^IqA_lZu7OWh!2Y(627zb=r7Ends}wVk7Q5o09a@ojhH7 zU0m&h*8+j4e|OqWyJ&B`V`y=>MVO;K9=hk^6EsmVAGkLT{oUtR{JqSRY{Qi{kKw1k z6s;0SMPJOLp!som|A`*q3t0wIj-=bG8a#MC)MHcMSQU98Juv$?$CvYX)(n`P^!`5| zv3q@@|G@6wMqh;d;m4qvdibx2Yjml}vG9mDv&!0ne02M#D`Bo}xIB0VWh8>>WtNZQ z$&ISlJX;*ORQIO;k62qA{^6P%3!Z=Y1EbmY02{w^yB$`;%!{kur&XTGDiO2cjA)lr zsY^XZWy^DSAaz;kZ_VG?uWnJR7qdN18$~)>(kOoybY0~QYu9||K#|$Mby{3GduV~N zk9H7$7=RSo+?CUYF502`b76ytBy}sFak&|HIwRvB=0D|S`c#QCJPq zP)uOWI)#(n&{6|C4A^G~%B~BY21aOMoz9RuuM`Ip%oBz+NoAlb7?#`E^}7xXo!4S? zFg8I~G%!@nXi8&aJSGFcZAxQf;0m}942=i#p-&teLvE{AKm7Sl2f}Io?!IqbC|J;h z`=5LFOnU5?^w~SV@YwNZx$k_(kLNxZDE z3cf08^-rIT_>A$}B%IJBPcN^)4;90BQtiEi!gT#+EqyAUZ|}*b_}R>SGloq&6?opL zuT_+lwQMgg6!Cso$BwUA;k-1NcrzyE>(_X$B0HocjY~=Pk~Q08+N}(|%HjO_i+*=o z%G6C6A30Ch<0UlG;Zdj@ed!rfUY_i9mYwK8(aYuzcUzlTJ1yPz|Bb-9b33A9zRhGl>Ny-Q#JAq-+qtI@B@&w z$;PJbyiW=!py@g2hAi0)U1v=;avka`gd@8LC4=BEbNqL&K^UAQ5%r95#x%^qRB%KLaqMnG|6xKAm}sx!Qwo}J=2C;NROi$mfADui4)y(3wVA3k~{j^_5%H)C6K zlYAm1eY**HZOj($)xfKIQFtIVw$4&yvz9>(Crs>Gh{ zya6-FG7Dgi92#K)64=9Csj5?Zqe~_9TwSI!2quAwa1w-*uC5!}xY`?tltb0Hq740< zsq2QelPveZ4chr$=~U3!+c&>xyfvA1`)owOqj=i4wjY=A1577Gwg&Ko7;?il9r|_* z8P&IDV_g2D{in5OLFxsO!kx3AhO$5aKeoM|!q|VokqMlYM@HtsRuMtBY%I35#5$+G zpp|JOeoj^U=95HLemB04Yqv{a8X<^K9G2`&ShM_6&Bi1n?o?@MXsDj9Z*A3>#XK%J zRc*&SlFl>l)9DyRQ{*%Z+^e1XpH?0@vhpXrnPPU*d%vOhKkimm-u3c%Q^v3RKp9kx@A2dS?QfS=iigGr7m><)YkV=%LA5h@Uj@9=~ABPMJ z1UE;F&;Ttg5Kc^Qy!1SuvbNEqdgu3*l`=>s5_}dUv$B%BJbMiWrrMm7OXOdi=GOmh zZBvXXK7VqO&zojI2Om9};zCB5i|<210I{iwiGznGCx=FT89=Ef)5!lB1cZ6lbzgDn07*he}G&w7m!;|E(L-?+cz@0<9ZI~LqYQE7>HnPA436}oeN2Y(VfG6 zxNZuMK3Crm^Z_AFeHc~CVRrSl0W^?+Gbteu1g8NGYa3(8f*P{(ZT>%!jtSl6WbYVv zmE(37t0C8vJ6O-5+o*lL9XRcFbd~GSBGbGh3~R!67g&l)7n!kJlWd)~TUyXus#!&G6sR%(l(h1$xyrR5j_jM1zj#giA&@(Xl26@n<9>folx!92bQ z24h570+<)4!$!IQ(5yOU|4_E6aN@4v0+{Kx~Z z;q7fp%0cHziuI%!kB~w}g9@V+1wDz0wFlzX2UOvOy|&;e;t!lAR8tV2KQHgtfk8Uf zw;rs!(4JPODERk4ckd5I2Vq|0rd@@Mwd8MID%0^fITjYIQom^q;qhP8@|eJx{?5xX zc1@Fj*kDknlk{c-rnCloQ3hGh7OU+@efO3>fkRMcM>J?AeVP& zlfzX%cdp=N+4S#E*%^=BQ+N`A7C}|k%$|QUn0yI6S3$MS-NjO!4hm55uyju)Q6e!} z*OVO@A#-mfC9Pha6ng((Xl^V7{d+&u+yx)_B1{~t7d5e8L^i4J>;x<7@5;+l7-Gge zf#9diXJ$&v^rbN5V(ee%q0xBMEgS6%qZm7hNUP%G;^J44I!BmI@M*+FWz0!+s;+iQ zU4CuI+27bvNK8v>?7PZnVxB=heJ&_ymE0nN^W#-rqB%+JXkYGDuRw>JM_LdtLkiq* z6%%3&^BX$jnM@2bjiGc-DymKly)wVkA-pq;jSWL#7_*moZZ4I|-N}o8SK?sIv)p|c zu~9-B%tMc=!)YMFp*SiC0>kfnH8+X5>;+FFVN{~a9YVdIg1uGkZ~kegFy{^PU(4{( z`CbY`XmVA3esai686Yw8djCEyF7`bfB^F1)nwv+AqYLZ&Zy=eFhYT2uMd@{sP_qS4 zbJ&>PxajjZt?&c<1^!T|pLHfX=E^FJ>-l_XCZzvRV%x}@u(FtF(mS+Umw$e+IA74e>gCdTqi;6&=euAIpxd=Y3I5xWR zBhGoT+T`V1@91OlQ}2YO*~P4ukd*TBBdt?Plt)_ou6Y@Db`ss+Q~A-48s>?eaJYA2 zRGOa8^~Em}EFTmKIVVbMb|ob)hJJ7ITg>yHAn2i|{2ZJU!cwt9YNDT0=*WO7Bq#Xj zg@FjEaKoolrF8%c;49|`IT&25?O$dq8kp3#la9&6aH z6G|{>^C(>yP7#Dr$aeFyS0Ai_$ILhL43#*mgEl(c*4?Ae;tRL&S7Vc}Szl>B`mBuI zB9Y%xp%CZwlH!3V(`6W4-ZuETssvI&B~_O;CbULfl)X1V%(H7VSPf`_Ka9ak@8A=z z1l|B1QKT}NLI`WVTRd;2En5u{0CRqy9PTi$ja^inu){LJ&E&6W%JJPw#&PaTxpt?k zpC~gjN*22Q8tpGHR|tg~ye#9a8N<%odhZJnk7Oh=(PKfhYfzLAxdE36r<6a?A;rO&ELp_Y?8Pdw(PT^Fxn!eG_|LEbSYoBrsBA|6Fgr zt5LntyusI{Q2fdy=>ditS;}^B;I2MD4=(>7fWt0Jp~y=?VvfvzHvQhj6dyIef46J$ zl4Xu7U9v_NJV?uBBC0!kcTS0UcrV7+@~is?Fi+jrr@l3XwD|uG zr26jUWiv>Ju48Y^#qn7r9mwIH-Pv6Y|V|V-GZ&+&gQ?S?-`&ts{@5GXPqbmyZjUACC&oVXfNwUX0}ba(v978 zp8z!v9~8Zx8qB@7>oFPDm^iR@+yw`79YF)w^OHB_N;&&x7c3l^3!)IY#)}x)@D(iNaOm9 zC=^*!{`7={3*S=%iU=KsPXh=DDZcc``Ss>057i{pdW8M@4q+Ba@Tt%OytH!4>rbIbQw^-pR zGGYNPzw@n=PV@)b7yVbFr;glF*Qq3>F9oBN5PUXt!?2mdGcpv^o1?Thp`jP10G2Yi z(c93td3F3SW!Le5DUwdub!aDKoVLU6g!O?Ret21l$qOC;kdd@L#M&baVu&JZGt&<6 z!VCkvgRaav6QDW2x}tUy4~Y5(B+#Ej-8vM?DM-1?J_*&PntI3E96M!`WL#<&Z5n2u zo`P!~vBT$YOT~gU9#PB)%JZ zcd_u=m^LYzC!pH#W`yA1!(fA;D~b zG#73@l)NNd;n#XrKXZEfab;@kQRnOFU2Th-1m<4mJzlj9b3pv-GF$elX7ib9!uILM_$ke zHIGB*&=5=;ynQA{y7H93%i^d)T}y@(p>8vVhJ4L)M{0Q*@D^+SPp`EW+G6E%+`Z;u zS3goV@Dic7vc5`?!pCN44Ts@*{)zwy)9?B||AM{zKlN4T}qQRL2 zgv+{K8bv7w)#xge16;kI1fU87!W4pX)N&|cq8&i^1r`W|Hg4366r(?-ecEJ9u&Eaw zrhyikXQB>C9d>cpPGiu=VU3Z-u4|0V_iap!_J3o+K_R5EXk@sfu~zHwwYkpncVh!R zqNe7Cmf_|Wmeq4#(mIO&(wCK@b4(x0?W1Qtk(`$?+$uCJCGZm_%k?l32vuShgDFMa ztc`{$8DhB9)&?~(m&EUc=LzI1=qo#zjy#2{hLT_*aj<618qQ7mD#k2ZFGou&69;=2 z1j7=Su8k}{L*h&mfs7jg^PN&9C1Z@U!p6gXk&-7xM~{X`nqH#aGO`;Xy_zbz^rYacIq0AH%4!Oh93TzJ820%ur)8OyeS@K?sF1V(iFO z37Nnqj1z#1{|v7=_CX`lQA|$<1gtuNMHGNJYp1D_k;WQk-b+T6VmUK(x=bWviOZ~T z|4e%SpuaWLWD?qN2%`S*`P;BQBw(B__wTD6epvGdJ+>DBq2oVlf&F*lz+#avb4)3P1c^Mf#olQheVvZ|Z5 z>xXfgmv!5Z^SYn+_x}K5B%G^sRwiez&z9|f!E!#oJlT2kCOV0000$L_|bHBqAarB4TD{W@grX1CUr72@caw0faEd7-K|4L_|cawbojjHdpd6 zI6~Iv5J?-Q4*&oF000000FV;^004t70Z6Qk1Xl{X9oJ{sRC2(cs?- literal 0 HcmV?d00001 diff --git a/freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.eot b/freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..49db2eed077e7da0c072f96d63cfb55bce61ea3d GIT binary patch literal 36962 zcmbSU2|!fU`akC`GsDgR!+>lv42#O*0*JdKE-0WPB8w{sD7cHexkPTMnPpj7YG%Ej zndxX{X6Bkp&(>EyD_bnJ)okze6fXbYckT?JS-tlc@7y!zp7lH5`Sx>X+&zrlzm74^ zBu4)vAEP4CKZUb{A}cSy(VJapoX7I*q6A}yIV)rA-=CbS8ByoXYH^EZd8~$wV^yq{ zm9tq$Ph(S=jSa)Msca@nnphU{CbLSUBUzCsIkTx;N0!Rk<1dwU!1LCB(VDGATPV(P z1>-|u?fdsH98{YWJcO}z)2XBEzWw^ML~dnlWhA~QWakeVcKGr?-oW?Mj2WNI9yUD7 zJ0|o8eD90r%Z3bV*HPMZx)&PVkMCs_Q_81b8n=8mW9qxeyH+u0mTlHsnJ+LF8iwaT zkDoqa%AmNr7ox4V81tJlp?oIw8;JHoa5qhuJb(PJqko*oSlIWBjh$6pRbD9{d*v6j z)g8|}RU^ay4J8NPXW@HX^^{rjuHEhY7Hfj`<%5%_Rg^y*`0SI6S$$FF(vt`>=IPUB&MLb7hi;gPg8G5eYpbTmPyA>jV-_dMpPgGZ zZhZN7r4Ir=cj3E1zz!>3H=^d$*nsZ8uox4v89P0&I)UzIUk;k8t<<`hm@x@uO>Tri z_n2JY`m?mIc)k*~Xcb!i`&Q)q!19^O24QU^zKb7WYcQ%j?qV{Y%E~b5Af77f!)h8D zh|ury+ibiPWKx^4@&fa2%xCi&(Y&3y=+JvuA7*2I*CoSvZ9I)cPAX&k@F)KU5_DDT zP{hU*=?bGYWl|USwSyg%&#;xsOyM1sYf5E0mJJ9D2CdpCEpzLHtsIvy& z^ToXj3qkpzxax3a<7$U123Id!xwuMj^~RNrIwx=)r#5g67x#;7jPZ3g8+}oKt8f*H zdl6fuTx6>ZUG(qhYZcmvp=Tn0hbXH*M_ErfwTN}Yb1TZ-hP1zNEt7B$!sS4I5A;71 zZCP*+NB=3x%*HE7XQA!xcuw=r$Gwp5$fr7mxYnT#uEq<<7iqv}r6_+m4qu#=5( zUlTCqTEL0m{<{k}t02D+a9h>#0u0{6n5%Jr8rM0LG2s&Mr!{DK(fYdA-+ZCZBj~>t_h)duhc;-v2>x`x@d8|s$8jyW;i9$B`wm1~_hPPv zz>|E!5x|PpBT?GX_@lf5lq(5)(tVdIun+%%3uKrsb{Y3+xL1ifgfHU4n0um4+FNVf z*J`Z&gPu!zo%P1^PpK~IjKVdV7ilZ0ZKP@cdE@yar85@cqQF5KS1~JYyvza`-$c{0 z`-*f^mY)U9Am*X4_wcME?Qs^w{McgF$UE?1e4cbt`at^7=4}hHwX#LqVr>bwcDAmz zzP7QpS+2gCI-q_gqOXJ1HV~zJV&S)H@ zv4(FOu3UKa!Ydb!UN~~$D>T&TU!?UR!qe5-!t9y7Z7U;c3epzW@} zi4W`Sxw#2Eloi!rGh>@d4j~QE5 z&Q`2kwdTHuwmiP=i6{T|_otrTvGbYTyIgyoeg3(9`(HTt(#wZfZDm!>C-WYgKIzLj z6WN^)6LX(1R}69TLbm-tUBy)TGG)<)ad#|#@c1j|-uv*Q_dj@%9X)aBi_b28{SBLc z{$sZ6uBB^Nue;~oyEi<*?%%Zekz*%M&tUA-0?~|%CEHwVcuBufn=N-g^BDAgw-*A-2v=-l%Cfvkc`>{r>i($dQEQWsAuEp@n9eo0knX^Kluw)L~Q zl=yPYMeUPc;!+)1E`uWrGe^;~6ql0hz)WnFd(?4RHhMyHjL^qSpDtrrzY3S!){cjL zY^!an(ZQbfYCOO)w4^LQqP(EA#8GN5wYi+bO7JX#fZ^^x#ib^@jD3>!0viQv4fyKF za$t=dS>-Ni+;|tSz$jd5+Z2~E*+xV4#at9N4n;6nXIUu~DeEhSYD(T~^kseeWwo_8 zt(I5vjjQLax5AS!gg%&enXTVyM>(yefG~@oRdCrNFi6iRv7V0dzIqove^A{OhgzuI zG_{tMdN1-v06N+v)dv;sBa+%h6Ik3w=)Tg}yxQsl#)_pbF>~do01X7LuF#<<_>};&|joTD7g(v9@Na|Bo>2S$? z$}2(Qq(0>lNSBp@F5FzF976-ucl0ms9N|C@{V`A6L@#LX7M)-qM2Hm-4=jcXgahBW zX(VW|9F^n6An*ykMijKUFCa@-4=ijLftuikg`+#b)UD}}%LF1~v-NjmQ>V0w-9695 zWOPWoSYb)3tvh%(jb73-(P-0BxD4_5HV}`R!~}g2e~)=(4)xQQV5&1Vfb>8sQQ@3S%H`nN$ z?CPA?5X3Xq=!!zTD;Q$kL}y=gwG&q6bxyb`4JyizL1DQ^Uo6RD9@PNYUrIguJoefB}IG1Mm|#!{b@ zD5E|pQBGz0BQcK3P@;m$P@WK%_^kgq2rxVbrn9GT8Gr((+q%yU7lFG~!W#|EwnT5*T znp&MLzKI%h^d!}ot0$@WJhanIZ*sn#qyh`{Bo(*~WxF@Ew@`c&?Jd%iRD7|Xq~c3Z zv%B8j?Rt_5)agkoa0klvXlie%_$Jz0rYEWRay?1KSDqc1{_bRVQh(}e zQf=crEnja_s%xy#@|&K!q@Xy)^Gs?-Aa+yg&5*)hE?wjL%}9BR=2zcJ!U-`!By%ezW`@@_WPYSN~4_ z6aClwAM(Ex;2Y2}U{t`ufNg{+yhkAz=g)R;~5PHR$Y8_?WWqmiy7FHQHIqacuQ+QeU!B$GEtX79weH}45(mQf+ zWp zddB6%6~&E1-;KYJ&@&+?p(tTo!qkKX2`dtg zBxWTJP8^XqKCw1&apGr*S6iFfgtgh%=2)Ar+a|P~_y>RU+OBAOe_L1E*V_J=6q3|A zX;#wqq>q!O?f?Up!!%T>4u?g| z$Wo!$wd=musLcDe;~sa zwL1LT(|wiKfjJU83YPH&@Q~V#cg7-*j|T4;kjo@V9<6X$9w_H2ieglZLFPc!+nU53 zvYn$ze(G}@PtZQ#HoikXcWnvx*M3BGw7F8gL+HfeM>Fi(z@rfxh07$s7>qrx=;ZINskNF;$je zFymD|+{Uml}9w?SLV z7tytW=iSfiv?X-iuRX`}$ch=!SgjPP-@~cZj&)*BJ1ybSZ}TXx%Tcn@Z65;z2A@@di(m>XP zqJEeo0?_9ZM%gQ@} zcO$@+R;sI59Nem1+3mTm5WpokEu~c`_CSE_#T8qGgk6&p@%vm=$|=<=Sb-yA;sbJ3 z>{)57jJ-8P0^ID)Pk@~62f0jDo63|ry;9>Gj?_ehcdMjeAdVX{mJok#47GO3v>J?n zMy7=zlt>>QxLA8iGIDfajo9YOdFA=-#|N)_y5ipIPH*>JR5Yr0dS+hHGub=d{z8*| z^Lh^&+Now_hk_?3?!04q?;2jW@|}VQ>NY*Kb=9DS6Z3aJw&nHyU%fOi@JwjWZgpqn z&0Pi*WfxCO>Dj;jl^2f;-B>ZNJxCIp0)n$h(5YZ2{Ht!t2O1S9d57v0YTWr&Jdo%T zl^?GaOWW1g;7bU1hN65YTs$SzM^&|wA=SD+70dm@+&3UL+NIewywR0nq9MIi&p$K zKg@%8FhB9xMXjfHMf+Ood5LDj4AM$D0`v1{QO;JrKHgp?qd`?bH!<35lmI!7;xy(= ztI=vqG$v*yW?JPme&w6K*ADDozu~~4Ykj{9I;TvlKGEas{QCOa-sy3ong@xti`ZjI zsj>(AFxlCL$r6)?g9aN^G%oQfCd+Izk-|YJ?8AHn`89fnCvmgGY|pTp!ILb~sn4_- zeBEb!z0^gk<_}#U-gE)uUZWl7F8HFnbvSdj3>#9grGY%p92_izQpJN(IePL8G{;?O zU0&>#Q^)OD^Y#zP-jOHVSZF7oZ5!t=Yq6SCbEvgVxUBm6XgGQb!&O#c`N%2@inYGV#g=63OB3nlg zRE%)?icXhNCeah`7EM`s8V}))tJ-I6)L*Xo;0gHOP{^M#>H`2CIG{6<05BFa0Br(j z-DD{tJ}y@9(m=^!Gp7fE1_cpKcEtKaqmAZi9eZ*m=9gcz$cgXny7r~^c|u|S@Z$Ky z;Y0I_6Q!ftBicRE*#_=>NP9rraQwY7W6vEw`u>=T4`@DhBnbm|yjdG(0+0sYd!B=9 z0B|0F8{?J?FjjBoZ4NP$WRQl1RWnG`u+;I<+Adzsy?LLv<)iO>boqMz+bGKk?-V=c z70E_F4+bZ5@IiloD+;65F}^2Y0lMt3810tq%rg()KljBEWBs+uVZXd{>F2=9xJTK)dL>UE^0u8;rX(>*4Gg&n4Mt040@j7+|BQ z7?upYm!%0Hk%qC%V8Hl+W$uNF4T`hJ#l;%EBfu{dM{HbTJn@8#^n@gyp(n&JJH?4% zf(J))rHxkq{YC9(E02zNt!nz6E5?nQzo2|{@g$S>MJSiv`shE8tlzequRQtQ+b6=$ zR8AOEd9h+taYb24$i6pDxt8sSRDyQ{k75AtYO#(%EY+C=c|hVhBo!!FN12ZYs3CPg z-ar5j3o?t<3^JRI#M*%3_6&~AYT+Q|1|zr2JGGx1mPo^R*MnMTqtvRe_9EZJW3*3s z%scYB`e|=VTRJTc>O4^cujZ^5aF}o?l3*4F>Oe4)0W%WaWWE@|D3WXd%v4oKIv7X` zn1N=*#k*m~h+RM=@xaPx54J;E1Hy+YUA3=%dbcr}hlfrdUbAS<#MwF8)eiEI2A6gG z##evfXD?{~KK#6OM8S&1)0U5v!{636jkrc@DzRLQkJi)*X*1o~!OvHcr6AxWjSztX zvNEB?wo_HlYR1vOSmJOzL~_J3b0EVwtg2x40@KtMj9+O}|M7pcUwAIi*%$8Yiz#ttk2nP%hkmXDX^^#~;{y793u}Mj82oO385iu2;jTQvr z58>GLZ9WoR*pXUk1-niXx5a4wY9W<*%*6b^ZNO6 zNp0Gd*o~^#jq+Gf%o}!Ni}efC5sNlsrauRgWu#*Z2HmiJiQ3ir1MehrQ{vh=OQeKn zDi56J{F{b%yOLP74V3M2<&kt|o&v`ChRrWN6|V6m~p&}^{0G6&VF@G11SJI9{RUz z9u6EXLgYs&bWQ^^JPRt&9%JVgFK#JPec+^$IF4-e1mP}zO_*8Vv&`jw2IQ|^1_vvb#b>zcO2W@`tHMd~hC)`qaJ zoxZely*hS~Kvlt1t-!KGs54k;g963Rz>EfC1xT%FEHoo*2rn;PY|r%~{gPCuq(3Vz z%fM8s1GLnuv_+Lv&~h(H_FDXBRa?|@CVMI%ltYzF>V)4{Y%VTLe|b6m`Xt09B*X{D zCpvscgAb8NbI3@~bh!5{Nj}!J%rsdiGsO0#I7h4^Swe!8G+VsYKvYV~2m~MQsOa09 zL?rC{o|3U>$%Q*cRTvB%PfXbSOx@+XkM}bqZkiq6E+&8d@QvDq!{2M`-+q+aj=`#z z*mJvf_^B(}##bBoAs+G*uY9Rt{@gKRHViAT%vv-qLpr-pd-ti4L*t`sR-D=QIFEjO z*F&0NXWx!(*L-!?Ztl1KpW3Wnk8AHg_{f5>1&4XfS3H9MqyBkq-^XL;ExJ6vc3I)0 zXLS5SbfkJOXuc78Zi3SRjUQS&)7dqdK6G)^&ld%Z;FJase^@VMJGXmrqd!M7QJJ!+ z;fqDu`w$&RNvxs3;2pVzZ&;JWtF?#Jx@$|6?V)j_v~<3KbjMZbZzB5h2RDpi?VQQJ zLO8<{VuVDCJa!>4LZVv`80-#vTf2u1v;j`r6e9jQ?T73@q@S`oc2Xy57aCbZFKIcL zm)G(;pMF+bF@LV+;suLl6lj;U)x7TRrF{L7f2wuQ?w9dT2ZGlOz{?BqXm0u3Z8y_Z9|4~@yFf_m>ExN zr~c7!l54z@uh7n2Isf*xLl>koA80S_RO_^j+MbKw)n`+580JoW8PJyxtqW+A45LOB ziAuB%5Sm($AY`;uw@A%G$KqP06xP2Zecuo$7pZlZv@MskwU# z*0gCE6K1~GP1`PwpRR2@p>5ah=CdG9eZNxsKKbC1x8D2Yoj&QuKWMl%6WTr>!$q@^AeR(cx8~Opu43jid!pl^4G(MY^F(Pp zu2Bt7sC5l*N*%5((P4nK<1V!A(IrWL^0eqtKS`Hd#S>{yit)rWUXjTrYo_%keVqh8 z5(WStw~S2a4#O1Jd9r~)3?NKbo5f_IW``Ig-}B!j^7x=}MI|G(FQi`ALJqx~G^J+F zRO#Xu^*uiP)xG}eb7KCWG~O^3f_=SDGgA~s5iX4oa> ze;Y4pcv4y;tz6!q0SI-{l7^l2@3`>`V1f0Hc8Wk*NH}B2t^}JcGE}CT%?fNT1PQwZ zu#kRM>#kQcVtxl6 zTqviB4kSU}E@H3vg2Mfgz7N5*nV#s>%m$JI9fTMiQI(6kw1e+y&mrn|9v_0g`Pw`0 z9Y6WrM~6?IlTLl8?cKvy@}xwt?%+Z{TL_oOVrnlXqFLOg?_+zh-M_ z{i_c?)-vad{tC4zD$O~R#j;YT4-p5&i-R&id`X3H5yi07vI42AiOi!OF9Ymhbc=Ad zYI;ss%qyFU$$6zO#Kk2#;>9Qf8CVjGjz*Fi65|9HxEXHRH~)^kZCbbP(&AZ<|Eo8& zq!Twn@WUenLFiAbI#!f;|08nWI}sozU2X>D&j6(k$IXDFAG5GDXM0}`ff|Ma3{pi@ zwW7G8X=q~OAwlKoI@llaR12#)bo=GfZEFcO(5}y*U zo038lyA9?E0L+#_=KWL!DiIjFV45~z_K%iTgZ4U>_6O~?WF8f@eFM+Pu6tq2!pi=M zJO-vn12-mUpM|bmto__&`qO82kMG1Ec=P;`3jc8{{Lv{zyo4F->i=LBLVeFu|#x-cqN-zla)#)8zK?IM3 z@Kc)>b11xV;b;|RbCUPCJ=oe4dVg?ea4*~pG1|7njMV`m7;0MZ|@PnWrLG8SV z6X^dHe{Bo?PV+Cd-n=umNEhBoE97UT4q%IyrA?YY|FNO6Ap-rt$ERkX9}|Op+JLQo zGvtDTV4j6l0}CQrmwRa+Xx>oU3V0FEkg^&MNm+8QhMiKOz?~kzo$Y8($T3D>4w<+h zZ8YOhvuqY*4ibLIHfW7-2l>tN4F&SA4F=iqw*2ZBuh&Bg$2e!gK7TK4ySj%I zD?oBrj;^<3OUZ*|Vq(pB3AJ2ETr;I;?FtXkijA`mT{9gPZCPL^PEcRRT>ob~u?jme zPt!iT${lUgU$2n~nX_<=Ewjbg#BRaZAWjMI?eE408H(hna2~3Cjqw#9R)0J6+ZxeU ziXx{%yfy?NHF9r57obsasWCn8(@#;MYy6}=5z+U-b?Ev(4@RlsT z3Rp@q=92n4YP0c5qd{5TScv&1h&KP!4o^)Qu^1uB#%qHIi!o&>k4nANk3l`%^&yvG zJUKM?r6i)zm>@p{ksV(QiD%e@E&Ks(Czq}(k0K^dyAXpi*j3$OFS=bB%o5-={HLz+ z`ou-RXcP$wO6(mDACqQRPN53JR%pXs!u*KO1ZC0%*vw z=y61XY6`-KUm;;LHX4!4Z;*@5i~8+;x7~Pa~Oh+{ty;O@de_q+Uz?2)>k=&g9mzP=Oey3 z^ZLha+S{VTTenV$o>=8=m{WSsxPqkYZdp@$hCKbqc9*1NPMnZk;J@YZe;m-}jO=H4 z$lz^Is;kbz1fVzubRU#EU`aOMyb^F;2b>ILc2>I2Y>*(Ofu)mI1okIzcQP!Ius@WX zhioj+Ou)|>bMsTk8+s9}pYX$aK?VvA)pbsIJ!neck@ikRvyEvm1Hb`7vlmlT)}1>& zb9;*9rE1@qc(|gBy?*3%?fm2!wR30Jej?dH!OvBWaV#*8eporHjdQ&PivR2Wy@#LM zc?A8ze}s8t0$;*dH)m&<#-MkrSSm$UVF8k5p>ARdutSX{{YA**5X1tpDqgzwNvez5 zY#}Cv#T-b+M%7=E3Vzjo=KjAOzGl-d`<9iw|55&AKi(>!?w=t%9z4{SCmngpe^`aK zL0er_={Ic`ShbK7l~HckPH_6joM0OOGtWRBz~m_s0QQVdU?>UkgrQE`O^*#^Uc3>+ zF!b4?Bf_zIp_aIKA&wxe?E={s@rzHZ~A#nc-NYi`}BI2WrY<&5R_fO6IJIDwR zH)$7V*Up&w(X<5t^IN~d6S)`nW6a8SGFIL(RRoB}up!3qsc z5e`o5AQG4Wa;eG)MbtAH@_o5M9fI?}*3jVaknjLM9|Cs}M+ieRxP#0VJK=dIG&tDZ z?5WT@e})Y1{qtA9Hss8nya7X+sco&8B=aXsQ$s+;EC9JpJEQ%}wB@lntM-AsXZ6CR zOMwUAQ_?QL+`_szJCR+)k4p;piW`e_a>?opIYkA>fsxE3s92aK*x`V_Y$8_yK!$J= zVIsF$2yjsxewFvNFSl&j^yrX^Hf;uUJ1Z}&UnVa+G~>Rz1E2TmGGMsd7FnnbRz@g8 zu$z+DT&J(q7k*X}jcBEXozISKBtx_jJIY`XCJ_-p!p0hbn1g8L<1t(ejtc5K@0R6C zofgj8wx+O<(2ziXlYv37Ah7Z#&9Vp2Y(@ra8Wb(N0aQkj2^|D>ONA$Yh~(h`75TF# zOVa4Zm*4u=Ygb09UMlxCXx9SfPW|+g>2tNg58h?(opaCKT_&9((FzD+JMNelJo)bW zPrtY#fBg5Cw1>1uU!r}u4t%{*z#sAurXg6bkX(o6l4vKq!m+`zLfUszJ&15b1^{)S zs*yj@W#`&W^JV$<;Eo7ZKf>-Ces2PQRMKqTQ)qTN6SZ!G*Ga09iKyK zk4s~P$eWvMGOAc*;YDu+@|Ej07U}5T^iX&x&{RTfa9l!2aI6=ct<6Y~o{(sd=9V-2LPt!WK$Bpz0h|qpaQfiJMUIFABg_rL4D@M%Lg-{K!bxFI zm<>2QMWYP$JSY|6E|3@;iNgGqW^O`P2@?|h&SGBY#@05@lB`blzUeiy1L zSTVY-#G%$`S&7jaFMjfE;=e5uZl5!`xccgo#owJj`c2e-e8-HhtQ<6Q$%(nyy!+;7 z*KUX(-P>gbwaUz+lh4q*8m~ep?2H}N%h^2`$R9%S zR&Yp2@=_w{60|RYzXk&%2m?hLz;G|;f*1={<~tu7?uD|qY#lE3Yz1O_45eX!GfG%nSi)u zZYyhq)9T3;!Hpn2W!U~Ox9cVe8xgKy=z;b^$R1*L+kf560jNOs5KD;J(rgbg3UdgV zLC`^FuwAul=XUx!(s#jV^(8m$^$*{{Q=asXh}^^#>Hd^G7aFE2UF%Qe%$625EK6Ur zOnQO(g)fX~de>XnL4L}U3yWUViv3QT5=v^6cA7fn)3M+1 z#_Ma}+KMq002%gS3|{1K5cVsOb>K2!Kt3(8-36}<>5b`M!&lj_dwFR34 z{V7j@Ki%Ba<$O7N;Oo>EhNXybZq|MXh*xHM~;wtwvwu?|BesK4?6qF2HW zlO6=Oy%cQH{c$o3WyQ2E@T^P_QUP#IWucq+^-0t`N1XVl3fVAY*c_*CaG<2h5PT)! zTCihf@>fxAC97arXK)`*U zsf>yF=YtJ7<8u%0{nxus9ZUYoQjj$=pGRw-6;$$t)sxJ8($2%LcfF@()Q;a?eRK83 zc7yuQT=eOV5fj#Tu0SLW*0~GYp>$DpBAPXkD1on!7py6;`L+tTVz-mNlY>}&HXtlW z;$%Ytp{VePr$gnuxYo%DB%TJRQ;aep9%7aeYDRI(21hK#E+=G?m`6dCK+YB2mv3HL zHFy5Fm0RxY-gU*6<(2anj9a;RW!KLZ6c4{`K~d3d($0BR%Qr3W)@|jMl@;^mR4m`R zvRju`TUJ#pD7tOo@S@um=x`Ri40seux<7cl7tb*P$k@t^kH@f~5Ry0ETbx1xxDl)x zY6(UJrT#Ba1gsDw#UHt_GjRzvA!)D;@8Sj8F711SLgY*M5~AJDiHZktyY%e(ZTDTd za^JS~(i?6QH{hbvs$F8g;58{kt(s%Wgg&RojKS_p4zlK0GN_fqfz{%vBoF%YfHER! z*SJA?LiVoz_4)?cr~Vi8ozWPf*wp)AJ?+VQI=f}2BJ@?^(NPu|Fn~D(s)3?M!pc%5 zs2v*%d}M(y{1#e((R88pIZF<^{+npA@SRg+iqL1xc!6|-*|fJgc0Mu;qT0S z=4hwNJCCdPtz918vro6)ZMq~yB@`?u9=$GN$ezFNS+gNNKeu;&`|c^p9X3rLeY+#! z&cY2()St-Cvs>?Ili4=L8}klSc1w?_s|Cz);0l)UMB>p1C&vvu5&<$GpnT9RtO9~y z;`EFi7eDrX!|Z=4yU*hvkV|H^#_!dW*uw#!Qe9ZKXt{fOT4Dl@TM4r}wlQKD5xJ!p zV0KnulU8u91%wmCT36jbqvPY^;}a4R9JHN9cNA?x(N5_>6lxY^Y#9sHF)k=xY?DkY zcE0D}id#rlPaf5FA3dQx^U^V%fAkn1dSv<&+NH-gYhOP7I1hi~2_E@){XgbR%o-oL zwd<~hN8fqv@EyC`Z)#QEZ^|_3I8}X>4|@41&p&ot+k1>^Y|<`2^|*G4YW{8I{W<;G zEt>xEKhC^6eRi83eea_EC9xFgtn{n81`ue&jyip!tOybb15GB=R)iqb!kSLTcazFZ z6F|jPGPtX(0@y6<@YNvH`eCXRkEI@7SfC`u4K-B)%PpqLZiBtbhD31P$!5YH3;T2uW_=5qp`9Vrvzvyc6=anW$4>fWHp~%}9v4hyCG}DC*?}@y+aI zM&KAc4#5;e8V^T6nnmUoyC)$hA|9Dt)i`U&)-*0%L250p$>!7Iwe{MAjuuI*A!4&I zj$%0+Ya)ED;Ihk*%lL5GBk+}R$aTU=qX&7!3kQ@uxBeqVUdwl0XqW}Lz-vs+ZzK}S~fu;yUcIFn#SLO|PJZxyZI-r0M3&c=tz2A@5j+i&?jS$+O; zm;9S|(N4+j>Sm0Y6Rx?WZe6546#i>!g4c&^+QWyN?j_cc}OewnK#eK_*)(?B}j{ark{rXjmqj5{D0~Rw~`4u`;0PF0` zfKrUtJd&6xmIwks6%I=nM| zzMi6drQ_PueE284P}})QLyu2BA$Z}vf%~K->Uww->YC1(5It#yXEL2LkrCh~E9Epu z3Tdqt{yP#4$uo@-#K?YM#A$DO3YQ9m06-8j7S83$;;@RW(7{SSv>MLBt+D3T244s* zAO-HTC~m3%!G!{YEg4eO#F53$p~Jn8`8~97>HQlg3<~5ErG4{z)O71SFmdEKwPOCX zibdnHdfHc`P4t~C?UWW{Uh(Yd=VKvzkj{^niaBxaQ^kAYKqsNPVR{rEsmSnE(jrts zTs4k`2w06E*Y*MD)BzxvvJYw z>jn-PRw6H{m|k7&JGyN2;&w@i$)&zyrcM~{92qEijRIVX;U^yczxauRGjN227uR!1 z+o?Pn!);006|}QLD^l#xMg-Ic5CV~>MUy)Oo9GPKm^N#RHsep2`;WAQx zB!a@5O{{LaW~mca1+*R+>@bJIyB40*WMWBl2Y8`f567YgV6PYb0 zQRNO1&ZwWY;dv+LPaQk4OPa0xc+H?*yJnCNy}0A4 z!{j2tJ2->+Gjd<-l)pM#hk2~!HWb&7%Asx_QYGHYo@N9jYR3QcT>WjQY+eKPF0m#W$b3x~WL%3+5n)L%4*9mZ^k*!dxQs17l_y zg(?5M>AsZCjt;9jXC?UeTxTCM@|!VbhB?N5aCPo1P+=BX)n^}lXs>I_)?KH&4IJ3p*(cYjbl7%a{}bEx@7+GJyrO#I_$sWB1opjF z32P~1K;SV_$Uc|%HsDOc9!c8Ykn2iIVpO)V(U5mT z*@L3&Ecx|@fmD|7Zy4w2^B~w}awYUX{V+%_jx64I7$lP-Gr$Jyp!@El2Z#a?=Mu;@Tw6SMP@g_B z_c2sgxX*&{s0i)L3*Tx#NH1ugboLwtd8}{l^!}csAj)CwM-F;|d5iM`Mr%+4i-UE= z$gc3BTjqbuKW~wr)KvZ|U)v%-rYZmXmidOWP5Iw(kb$Ot{q=mT$tiw_ZDS^^iEA5y znQ#bROd}%AKy5$y{VQ#z=e zYUqP_p%7Aa1}w~;*i8eZSzOk@{7v$X&Ui#%olzjI){MyB2pf3UckQ+`Kut{ z8U*}XyWyX4%lvQot`_+Of4%%wKC?Mrz**1#PS0<|`)~PK(SEY9o?WTa$5Raek8hFx zExiy1Z?AnC`F9zqeZBlu@aU6vrflHu4#|&jz$Oc?2rTfHA?chmmj)(j#E}J-=JPhRzdQ6m#w9hVg5+%>Pz`;e*ynlqVSI<*)J+ zE%K>-J^y>)Y*Tpw8$JI!PC%oxYul9~We4c7;aMM*&aQd!OgKRyw0a>Bu~OT*g_rTN zE!tM`p>5**ag?NNleUe%Z_&1G<>R!63C=Rc^n!XxJqkN)6lA#m&OSaSNo96hco=9$ z2+UxLoP(eoNrnmqP6NSBxVm7#BVQ@d9dBY_EG{;(6`HUl28jS2Yi5Et+F}J;3k|Xe z13;pYrzdhq5>WW995ylOzK7qZpyrdC3Xa}Ay>^%O@(1tUKT<9)YPdLZ!KrigCkv&B zQL|6JH}HcKdUXa%ky)AsRMKN?#d_eK&VuF) z6mS%=w=MF&<#;zzQ+Zksz5G?ara520UC;l{z1|MsL566jyJ-Jg{ybpV9H;IFt6ZWp z4p7z4D5+R1im(K;wbhxur`f zV152_>C+W&)M}RzuzvBqsSCCECA5BsMS;(a;y606N?j0aA-qI5-CrP2d*sAWj7C@~ zu$GWt5D6e$fn=L&ejsA7=qb8@pRn`=M{v71J)A|CTbkUO^ga!tN?<`nbPJtV6(`%F znps1Yr(Qj+-?(b;*sY_z&64fB^R^vHZ&>5{ zbxAMnF=bujpLEQ5p87G?t?zHN z&V86p!*0%>!)-0f|EH<^Np`A5{wq!Srx)vRWO!L%Y;9uUuQ5W%?f7PKg9kqOA#TU|qs8AF3`&$(7SQF?E+O?|k^l-Kvr~xhiLf|K=wqO`_01$LM{AxBe<)%uvF827o?pO01t=dlm?Cg+fXMH|KDuT89G=@EpKwqw zf0Ds2(9{m$nw~$AK@$NnJA%#qvS|O9-hSg^QN9)gXL?hfQkhQ;XzSr~lW%pS`!(s7K@nvkewWFo97)EaN0*gFxagHX&8-VISW4cD~~ zxt&WjU7bbRhfkL5K=7rL`&Lre&ceBiRwzGeN3=^sg8~o(8XX0it12*QQM|bRCKGypk{;D0!hxE@n{!=(AQ5C< z;>&I>gLx6{f#wU=zqGjskip%GUILrmD;``w$Q=R=bVxKh@R}tc27F>*|El2jHl6vq z1GkPWoAJU4P-iZmT3WS#|BU)eX8vIJ$*}VN8mQIu91nRzL~)faJh%P|g>en5edF$* zMrHHY`)T)%>8qtJ5MEi3%XaM%kI?x1)Cl6sUtgJcaS;Uc%11tum#Fi1NF~Sv8#OwibFv*C+84=DX zKokyN_~1p!o1TkEve3A=KoNrF2_(0;?qF!s%F6+T$@}8YfzVP_td&Io;`O&=f`AuX$U_6}Ni;o?`2d>9X=KjGM z(Rpff#!Z_#G* ztxNu{O=t3rpafnG#=#GF?8?mz)7whvpEH89n5b}T2$6Z1v&l^a_ckJTe87xh1aP=d zqgz{Oq(Y{ngPfW2g@j3+w7uFYK9}cbFOwf@sFKDsY?EH856&(>{jc*!XLZ8ss+Qm! zQ~{sq2oJb{r41S`Uc98=32 zSU2fS5Dt$Y1=K04X>Z?odx#kH;Wvf?w)j}Gn z|C=W~m0$NrBac4n2~yp-v4tEWb=2ckr5~DP5TSPneWbgfL*H@mAM8^G8}p~?e=uu@ zuv>;1Vf}*zh$5P#rI6p*te{|VVokVDERIBn(E;b0^y#B#ly|mr>9X=tQbrUGjB1_YztT8E zc{HJ2Lh7IyGy34(A!qZQ=&JesZ0v?Nuo0~Pzt4K|BTBlo2J7-yc4_#Lddk1@BWKQt zwdF_fQ)mB0Ju~W=m|hLzTqF15yVN?^hdpQ8X|L#UbEK)`fp>5SotFzH)eSJRq?y;G zcYdK~za?_72O@4X8W@4XW*-037Ds6Z*t#|Lklu=wB&tAPRfJWcf8#O8V0o0C$XPF?n4&JAfzb6>bm zpL+`+H23QTNed7q=LLS)P6X7_@tOeyh!DU+*XecZ< zN9VtoPvAMS=-C1tOf*03xLhsZBx2(Aey9h1b~tTd4i3S)2C1JG?-~>xAqo*LVYxcJ z%l>XTo~|%A9+Nfi#liFfLgm=PXY;YJ`kM6Si=;L3tJvpZPTg-Sd>PR8IU5oeD_%C) zVoytJcFmu=^XYjDo{^rIzw4O=3!Z+O)RoHL*uAaTQ~MDrUI zDTshhUN!|0@G-ZF7|`2=FF_jA+%B95f7WgYwcF%4Xz~}dbQ3VHtpu{ZiTx)tdWWZY zaj}qYba!|X4$QhH zPbh(9+3%k^qv1xEmU^CYF4G%#Wj=;lk@;W<0s&wlVt1J7wR+@|K}4V(T!CJzXT%$# z(lVruFKF9&_je{}pZm0mT>B3>>#n=*=Pqr)UHm|BySB%Up>uEOT0;;kl*lgYdPHZ2 zfCn#_#X+^<&-Ug9AIN|PwF(E+{6r)cZ0laa>pM6X(cw%gBav9d%8-oKrA2*UKVIPU zUpL_Fs@FA;^O*dL|3hU!#DNH!_&A5%784!WDvV4O5Rrt0WI*_2B2NqekD(X7s1%~a zWJhLNVkSO<5QX86pETO>XT?X5w0=O5H@C!Ge)%)4KB}(uAW53ty0{lVx$jbx_N8}W zPDM=s&yLdGhNG-7T6-(<)Tn~MgD=BVvbWYo#y-Ij0u|-fU z6<|y#d_6d;72=N&69j%jxE3Bj16yVUoq*F}INiBnv=^i#ZWv^>TBEJeW^-t$*d#A&+tvsb+2`>!XlsiS+&j;*Z7qt!N-+x`RhISzh7H@U$4BR)9SD7;?j(ip{ z-!C+Ywz<;z`f0j|c0i`V`?U3QZBcBq@G9G97Qo$@QUwY~sA(AC)h4#<}TJlqo7QA}kbw zg*Xq}m;ha#u+-)=lX4gRgk8hR8_rHPAGo{4DN5}9rum1nfzJMcFjbhj zLMJwGKtseS=Hz;lqe&S}2Tq}adFc))G8-u23SK215)|O4pWOgFWrX5e$f@*4qwLDV zZWpA zoBF{SnRbh+$2_RlVzi_m36B~tqyLlmO-L_#f5jilw4hradG@47o;6sb9()iXZiI>w z3qU&$8_ohc`1y$cK1P!4E_SlPoITMn`k0?2ec_%JC%`q3%orbTEXSmZcOXn2BLFE3k9=hcj>(i^i4E1wE~antW>XHA=2$yjgvGG_%wJr$6npXIi( z6kLDNXz!U7RZ}agY7raxyY~OQs;#;In`+wh@%-seeiv2M&IDL&9aG!4PwjB)xqE&~ zJmpqB5eQQBj&4298i2zl&}SBaQ7(Wo1@~Gu3Hj6TGQ~ezWE;v=36z`(bR%%;NLeF( z<$O5MU>GhN%NOWDKqsW6-##bQExbjKL(t=RATTXab4mJEE6`kTc^cByBG1Mqve|e> z^QaW{JioF&laR0l%xh-a%5Af1%PXsrHW@SwUA@WT0{d-Ki z^=@NL*wtbuFhGtVI!C}^kbob3pNTb@iifkR=2Y1RmCu@0HFFxiXQ9AMJSMQ}KQZ51 z0A&ik(XN~ea1l_eMViVI<)G$>R!nu*90%$_KTiM09n+HN2s0HWX)&gvhdE++HDa*T z8==8?@i|lUI~9G{P>&X#YSTz*$q2bxj>0CEmFN()O(mzGBq1h^y&N=avKS#zx+$Pe zI>j32re$+ciqOIa$WslJx5ogUFxA2bUE*qE$Yx} zxorpqCJR{83_NYq8qy3XPv5rx*WS6tMpZ-s_}tx%ysT{nB`C$kRD!16whLIOh)~xR zv|7VUz#6&T-F8>@<=t(Y;`@ij5VZu2@JEBun21pc)<+QahnnCEG=c(a{N)ctB$_B8 zF|mGU?%mP?@`FSY6StYpo!2>MX3jZt?mX^j%PRY%2`eVYq-iLJaBJUbuXGVoEvT&* zH_+;#{&elDIaPpNtU1bPo5W2!6-;cL95L#qz2=I#JF@8bG`i)q#kAGka(QUfzI18H z5R3S<+66=r<+sR5WpZ`FzGNzv4EEWDU_4Psb!SQJ><^9>gdnMQ7WvKQdKpl96LpAn zmy;+YQ`zod0c990B%D;22O?NZ+QlG>A(JRNshFKk_cKmq@)Yf4VCqX1lZxF=Zwt9p zpf$Qs$-z`6@8o(7dG&>ulSpL2W5?~zR611zM$&fd7`g<7l8PxwaZn|Kc{^KwLywco zCm{0n8{1A~ge+Ij6msd_M8V|FCKB<2>Zv%go~96p)4AL_tx8wUfxLJzSwDi_u3WZA z(Ol4u$Eh<7m5cRcRD&_T7R$uhF(*e_K5Z9)l2J{iwn7meY2hxO1~@_gLiEV!ky5!r zZH17>9}N^N7V|9)4SjumA=?`wV~pk@h|<)v1Y1PNm0?G!2A6$924)6!IV0*~e?H+c ziBkwAiy89k@aK;EWj)ygk17h~{<0J;(?uV`?S=rA%&5op!IL1Uo;!K$ZlY~;lxBR$ zD)aUjn?ZYi*^;(kdmc84(nr`3CPQ7YJia&S;6A#!Tn&_h4wbkNPBl-+^g%bh)DC4# zoq9b06Q=UTLa2~RhjLDLLwl4oeoTGo@6WxuTjVsq-*~lQ5(X=ije~lKial=Qu>K~n zyP|@#-Ibg)spgwQ6IoZhhuNe;KVXT`uR%v6eOa8O*%Q#NND$ z)s#B)TnIYV?hLc@a<4ip>P#3rqt)!kur=$dB@oNSXn*w=83otUr6C+xm?4ILGejqt>0 zaK&!5AqqahvZ>7B1dH>eY2h0qTiJR?AJKRmqW%| z6ZlX?1xFk!ttzXUgN_reNo)+hSa!%8@|L_UAIYG+DIc@z;1z4Kye}WfE^f`4!W!}8 zd@XybRbx%#E0aNMItxMF>Hd7OX-rQx)!5h=as5S&p6`WQ!)~~Bmgmn?KTHA7H{}VN zeE23G{%kLOj^|(P`OTg`*YnHp7P;Xj4{lS__^woU4@Q?=NV-YksO!)1{N~vK)tM%8 zPV{>9qpgjG9{liFQ@wDwvHZ|fei%-gRgRg>8=%Ene5H94JRf<`*(;yM(yJ`*5Tg=f zdCq+r4E(R7K(ZXp8A-zoLqpimuwWR+NPMH8-P$Rg8t+?|aI$iNTEgH| zJW7>I8AmyQ=W2s-q)tVi=OUZS;qwjhG`W=K$BZ99;&vb*hrQ5o__Wb#b3=R*yw}>q zUS2H`uy$DotlxcACN$PJ&DY}V@IBz$$}NmP_|G%373}bu>5ur=`tS2^@egt%6RoQ)^6!*8 z6D?<(cCPDiCVb~Q&5y+D=>qNflY7wZ#s5x@bKQ3T<2$|F`wU0<;3!tG_$eQabgDI! z=l(f|WqoHfFtGM9#ke-YOYv8bcP0KR+!|a5t^z0#4@v|m z13(!t*dq9gaU)Bw0=0HHQ|p^_ZcIDMi`aF!h8Kh`jLR`PVn z#OblJN!YhnV$IINifuL)?0oFh6~-3rV29tKVa}0#wOe*-X!nxj4E6WFcOf(6t2f(6 GMg9h)ubbEa literal 0 HcmV?d00001 diff --git a/freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.svg b/freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.svg new file mode 100644 index 0000000..f9d2e14 --- /dev/null +++ b/freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.svg @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.ttf b/freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fbd41a4dac5d4f80bcbe750ea64cb35127de54df GIT binary patch literal 36756 zcmbSU2|!fU`akC`GsDgR!+>lv42#O*0*JdKE-0WPA&V;rD7Yf-<`QX|npu{WrDoRa znVF7OW@fIr^lW|Qv$DlfTg~=fPvP?aedo>qn$>%M@p8{S_pIOf&bOZnEh%x~(%@|o0cAleJT-86B^{0YB~`Eed&Vc#=0ZdP?wd8K^xm0!?SPrUC^ zjST-clpH+I!gE~p)LHYc-tGLBF$wL<2c}G~D1SKc*(VvZ`l8Imspa!(4NDcY&mTg0 z+qCkjRrhq8d6uzxm5ljps+~S_R?%%g^uSaU)DNt!tE!Ek^w9>!EKZa^Gq-B|g!1o8 z9|U~v!n1)9>?FqOOKMJz3+VX^i!mXCu~UPq6X<^C<)CTW3ay)o8Iw@fL}kT4)HC&O zID_8|?^mD}wbc6Gw<6~Umd{i+1ZyMlo%}FcjZx)s7nAW;R&J3F;H{!QtfrxX2>mX< z%_c}eCROq=DiZxu&NJ_(d^WEM&D*JqPJM6b$85~+nq-)uO`ws;No9;5`sCk0g6?WP zirAPUU1qeVOzP&owzDJhX|_U{$sD*Mj8QC2J;_G$FW6LRJNn$tBsq#1lpU-Hbynj! zU);N~5R@N|s~%T2t`4|jaP`5Ji>m}zUtHO!a~#((Y6I5@algRE8eeC#(HHf%5?7(P z7qOMf1-8=AO@BsTE73*_y%YJ{MOpnl%6iJFMXU$jTT$*-r2UO+n1p)}E(h{^q5qj^ z%Yu72`cF}2HeE(K3vKtrdzybf?uB$mKGiA2wH9@7HJwMkNCQ4AMEMJ%Jf#Z(AF4k{ zw0(gsLE2>K)^rtje_Vk;x&oB3OTE}e+>_A%aJ0)%{wUt3;j-ZBhRZ}{aL>TIK=l%{ zNx#xq*=W=$L|#7SDU+MNlhfEprIK|*UK*}sT*b;pwj5;%-fM7`;VQs-Q~fc99c--o znusyi0Zs(>-(A321^I=5+sf7#VDKKsT#ftFxXz-C373FBtwHOH*4Mo*x1vnTg|(nI z39cAZ`pquDm)0cz#tVHOM*nrVKZEN%v_b1d@TdFr7vO?Cj%&$v7p;ZfcOcrj7jrEH zp5zmb09LdfiPH9_ALaF+TuIoI?z>chefSSto%D-c!hJe6QI)7e_#!Thxi{LRy|vnX zt-{(r=((iVSzo;Wlk+y=`Mw<4YH{LH&x?&M72pptw6|>@|ODv%2O*AdL zuShp#`B~5m&~(s}_wcSW?Qs^w{McgF#5?g@_&n)^^nvuD&D$1YYh#PH#o7{V9c-*f({^IOg@KVNsg$0sK~_*VVOJ!W+Ezx?9{K-*n` z6Q%V~TTdf+DJ!bMX!7#*@%8f$2s8%;hgd?bVc~5eBBP>XZ1z}3Tzo=e+ji}fl2cMU zbnMhQEj^=4X4h`rd-UwpyN|PPRzGmW>_LNba`T1^9hN`5;FiJ>Ma3nhBS(!MGj?2A zIa|JB5-!+PR(HKoVLj zvc#1h;c8o2Ho>-PM2Sm^FMrVl-cwQG7$0G`yI84<^>g&!1Mu}L%Sv(aWS6aMLW)aD zwpH3(hx1)Z!pJ>sxlg|V6$4y`0VQ^q9A8>6s>JTFN31Hbx$^Uo>F%th^&U*b|7SuTSk3o}R2vJ{t+?7&QHmAlpPSvGn@bBxf(Out>mvH=w?xxF1P z``K36R-uF49o2Y%Wq3(henfdeX^ErMUTSkWZz;jM2m*$?{}h*+>@xOC+5>D9ur=V( zk>$V|IkL)K()bB3UV%}#)b=SZW3r8g>WjH3Y&?o!u+FklDpJ;84Aqpp$LP!Y4ajP5 zZ(c30*&?=stfRgQ96NdaLNL95`hMPQJgQDQwE<^A<8eEy)iD-N|# zxp``>EBO-AIkNWnc*{VK2#3A2y*MYsO#?G9A)q=60eo~mH;(A!eXBpc(;M(*`X^#=>M8)CKtAnxa4z4vg= z1vUgHv)xP@P*~y$bY$5ExO{xM`g-Y?AV>DXxs+Omld0JMWwt;%*}gYcAq z$AHj+I&|UO5&{T!1;q+=nNdsu5seqcc6+ldmsh`}s#U2Do2}<6wAtZGrZkJ_s|lnU{bK}2-950dVFYS|8y1e908@|VS1uEXh|M<8kxiY_D)#ie z6O+*)?P7%`skWZr-86bh&qSlmOW`ua<7qHnHH!)QBK{up${g<1!gy0$y*#}M=)0Gz zjAV%$r|enc-k=|;1jT_abH9@O2yjwc&(hT0sXPSxw$F8M3nKEbd+WUJt*375_HL}v zH`&!SsdWKQB~OQ0$*yimtAKulW2>;!|7dJj&{S6{hS*O`nb0^vhkUuq+mWSDozU5V zotBE7r%$Lq2orb=VZXrq{|%+HZ-!w1A1o$#6CLa6=o(>fg)jC}_c#ZD0C!FDKxH7F zx+U3Z%?K{;*))S78z8Xg+a59rc6e~AD;>Lh&>!RvMsqwQ*p-1YImxa}+;Rzu0|0K@ zKrmkqwDOV(p3OA8j#J)9DkoB-sGLZRrg9=RhWhM>Vq>XKN{pjE zDN#m!Qlgy73`Al)m7zohm7zo>m7zqH7*`fj6U4YEHBpRb|qHNFR_7;jK(cU6GNyQiINh-bs zHGAsq-KHn0K)s%%0=J`VujclaiYL+DGCfJf@6eM}d^u|N(%W01C#k@ldXfsPOn%l& zkvtM@R+7t9<&xv_=Xuz83UO8_S*HphKk~c5GA-P44V@Y?@)LVW^?6oNkC$MYexdU$3ac;fF1U&4&Y=K2GCdW@KI{ zZ@~-ZyzWQ5kXirTas1BV&=>A3*)3Q0-$!|bqH58Fg{#lCw-__>qaN&Ts> zNp(&0w0ylyslKU3%Wr<~lJ**ya*=<{4yrEx5z;^LbL?eULiC^hsu{f|yMKz<2>UQ<35wO z={K*AUblIj^3uFhy+?ZQ^#0KMSD#d$u|A7^4*PuX+u3)L@4x)o_|5Wr$nOomU;VrI zPx4>qf6)J8fNwzOfYAX91GWYh1@1R1=4s}GW-VxO&~riOgQJ7MQC*oc42jWk~zZ-u(p?5+~LQ%r_glP#25|$?% zPRvRinm96XLSkLw;>6Dque3F_3v0Kx-O+Ymw@+w4=?{M9wO`);{`Ri+ueJX%DI}?D z(yXLyNgpRm$zAX>KKZWXt;t(cSjyg1?^Ijrmeea9{5zy}DCn@D!+r)VhiRx%9RZ7& zk)=Yh>(FzLQJME`=iLVN!=8I&2}y?Sf^3zt_ZSWH8+z{Hl%Hm{o8#?fdw617 zWIIQb{N(30o}hieZG5|Y_UaPuulpyWHR1rqvlDacWcs_Zh_U%w}_-;vJ^9 zf|lgOa}hjP>xSQE`F7r= z!Hie=a4)YxxeAx0!BSo%Tt1O8kulLxs2I^EJj@yj){Z{)e?i_+NnB2|IAlk#9hV~` zO4SL2KzXe8+o_9a5*Ot(AzxEu@BP(V^Q?*j0e$U#n4y+4%+G**~&S8sD4RX4_x0hlx_;F^G zb8=l#`6bRcW{{1t!MIojr~!4O5vFYbY;Z1CK^emoC5Pw1aFl?uc28MYLAk=qZ>+-d zBAjho%E(+9fg%Pb7c(Q;>a>~zJ9kWOpAct@wps!s%n|0GxL6;;;}C-*HUTh7>m14*JT1O6FwULD z(-R!A099yO=Ps?&hA*Dm>Dsen|8v`~^?gHr=VGGeen6k56^#kp|MV_5B%)o55Bnc;f0SHVjjJ7)ut^=@01Q~x^u6IGiaHJ+0yxSxN199Atv4r?@W2m)Drqy5s zG%_s&p+x%Sfs3`bBqK)$*2t}{oL8RTc5LX{rz`HQ?(%m3MMb0grf23AJ(Iot?JqRh zH?QxI;azG*bt-sr(vI6}`_}OK74H;0P`~l1Eh~pCoRq)ovCXd!{OYB_fu}=z_ozQ3 zZ|XLvD7$!4O7DRUue^A8_=bvk9YK=VR1lm+f=&fH;a_!AKG3K@$vaf1P~*?F;ekY# zsQd)2SlXt(246zBGZf`>awPz02yJ@g`Gv~fmL#4anFE6|?FPD8`Pxl)-L;8_Z(e)% zqgwH2{3s8@wmI_YMXjfHMf*fjo3-Mv z`5_*}gZc5#E@-{A%i7mk?~61WW{_6M5tyGpi*mN{_3`#H84aoex{1+dqXfut6sIv~ zT8&m?qA@WuG1Dri@yp-zzq)_ly7l`HUhV%~&{<`A_3>V3<~KCl`cALo)jUYFUBn(! zN|oK%hsn-%OqQ5D0yNm5qH&2=FPJX7% z;A=nQ>!fa4HGk+l@uu?__iF7JcflX!t;3nKW!R8{Ee+;*=HOr%lqw#S%F&x=pgHbJ z>-J)goO*80nzwIQ_Vzq-Wzqce`39*(T7zLkJ0p<6*$B!XgH>akMkX`aOp6wrVYlQ< zCcfe4pQ#=^)_eyVk=^SoYH;-3stPgH)!fcIY2>-$ETrkI6puBOS;t&gIQE?{vULPO z#R#Xb=yVxn513DuK0OK$N&?bP^ zO_mbk<6;Fb4U`-L_t6yrLCluz7D2`7Y zF+9IGQ97bMqTM5%Y2?m_v?d0X$oA-NLKJv~-m#*c%jk28ZPO)QN zk!;NKU~n=AAM^*fqVNuV)Y9a~VYXtT$`}>&c(4NaMeFb7#Wycd>h!~lNER6$8`RbS zx}28Ln`Z()(m{cZ@x1{H&}DzcXt!i%o__fLxi5|!=dWD~`{kXBKM!57{;rvk(%r}A zf4*qm#@v17<$;>lvK)LfQ_PJ zSTgWlmL`Hk8pbh$0pkajxfd!nD9#=i7i;v60KZTiv2ls<#1k^o6Owp_o)E+A5+{ZU z9vsb;c3Q*t7qp+PJUZgFs@gl3j~_jMLHU^C$tLZKP%gdo(SIIUw{;g^apJwVkB6VG zoH(}fLdEFfin5ZBy>Fa!E!!Tc1n&YK#Q@&bVjY86sxt}lfW&i1Dp0VFGM@lYL+XIM z!GPUCX0e(>OZHngMp|vAw7R{M7J4d_HNgmedvQF6W z>JR+PdF|hap0|!HSiX4r9i!y%x3!HUuhN=IEEnUWHMK(8Om}wj^Oa;N2slY2M4*7I zOl-C7RMoSZar7^iI2;d=9I?zC$S@A8Dww^%G_@7uSK2jv{2%QX9?ECQv0_%;)n(o1ctd{n_Wa`QQI*-jccFZ+&b9;8=n+ z3)YKJKZ-gSB&`cV24jFCS61KA?TB zUC`dy<^nAn!SjIGGV2Q%qyq-wfI%wj=u8Rql4w`{IRqTBNl9)95IPGHF%_GQ76jrC z;jduqmJqC1FzrE|u`B7?)t`RQ{=IQ&-Ce(&zx2!M*>^so9T+`k@4nHa>>o~;I%WFQ ziG0n8a~~XE@nWbF?wa|`sbkO0+-X%ppXI++jydv=u^Rhm*3^Y{b+mrj7<*#N`uTE6 zZQhmGjjGs<@;Fe;>vm(S^$XMyi#B7XKL?U!q+<&PUAKOT+Lea=?<8|m;+i>2q=aWG z_n+hZo5p`@XJ?KZGkMauk#nW9+Dh%NM|Z^E_u0p}BO2fP_^S5UBTFAyF?aTwTWOrf zu^BEY-Tk6ULGG*yBnM)VELYdGNS24D72&jCv9ZL^Y_PmC2%yL>bp}ByW2A-| zR5T=;4aB!d4a0Su+qCoCCVkJ_XrDI*@^B4nE3sqXqhE-A{aJzDZv=Lnw2b6BPv?pv zj{$^4-{Imd#B5Mz8bHfCC<~1F`}z8qjIDbQCxyYyhEH-^TDN`90rkxTDF8bj{ojH}sFWbxg|4@ZuTVTqHXi4lDAa z3%oxca8aAD9Yptl-B`3e6}Z)gMYAr>G@uj1qJaA-oGA-Ir#yJ2DC!skSJlC4UPMGh zbVPJ8IFMDyIw6JtSci;uHzy+cV2mdkuh60?Q;X;3_3tNZzZfP~PA-`_scNOKb|Hd? zo&S~xG!Eu=MJj)IL}sVTo)fkV%$YHuWWtAjEh=*M?H zq#1Vf@7#X%S9k5=e(V0J&HDA2_Wpy9EErdCh}V3@BltfWp4aw%JZ|2iOY`fN6+U`e z$3H|zs`rBC8=>bWI33XVp|vxeU6biU7f1bkQNRdJX#nwu^+L9DyB9b5b0ia$sf!xF zSfsrV(Q&lI8Vd~Gk(>GY)k(Zsdq}Onx2NVLdf7Xl+Bx&?v3?y$GFd)Pob;IvI4;;++w$PPsMDZ6tQb&__zi8c0-?f~=h zy5r8LpVgMnpR2ie!J-)j+C^;@ufKaKUw8PQYW=gj=KU=yWEVd(E?;}RSo1oeO__-O zTL|5DE%sv=>*P!g43H%S(hInh?3Na>Et!yzhw(f}<3WBtMwNx}Fx5LGDc%;W0t0Ns zsj>cK#}FJTNRHq>)tuj=9XhY=(AMw?yzn>PwU5?dKeF`Hn`hqTzU8l-# zJd!{KANpS!kHU_2$v5Gj%jHk7e z|7bkHHD1Y=YiBQ?d;99a^U~=Lw3l|M_1Xq)_l55ovMD+YbEm!x=*x%J1++5sSHm@Vr zw0Rj5X1>=$+a^t@)ixa0wrO|sSrDhbU!i@Ud|=62?|t%4zw~1tG+vzvZJ&?jqxnqj z-h#=~8ov2XyXKj{+DpJU(HRGfXIy4gWKkTHOA4)9^J@)PF>{(d(ecp6hqd>4qBH^5 z=*B11`o=eYz7uxpdlB7R*TJ)%&q)V>kiL@uhcw(9^%Vd)^(|VJ>P68hZ z1AvcPMkaKJVT$WK*}xzM5T>gwVzN-PLkyDdxo;AAe9-u!l2O_hQlG0K2j5MaS~F*w zbm5DJULXGIUVrsDF@I1RZK`u>=g6A zm6tR=DXo@P+|j532=&sE#vKjsxbX{Mf%T4dia=RNIAh7K1e+}~RHmBE3T!R}3A+Wb zkbYL{uT`|*r@@Z;R)+Nn_Et#M%Awk@P(cs%jL*uf7PG}l*l9OIVUdio!-t1jsLkLQ zo(WAxjo@C{vD!bi$-dfEzPcf_V;?T_m2y#3_Ybw7CN*4 zD5r@IBthRUV6XUs!u^rH55cvCp6Jxf29g3Dgcu!Bl?ywy1Mg|iA?kJ>ABLa#+B@$Z zJMrE}hfbW8PJXEE+09q*Ld4B3)Na#uU*xjZ^!b<2O~718<{>&0DDWVdIh-~(&mm)u zN7oRT3$kx^sASB6RgdTzD7>P2D1G3O0M_&$s8ioQ4Qbkm? zqPU@HXlCOfLFNFI_Op}XP20;2RN|&y+znY}xb~vAPPbfpAxT| zl0p=_4dw{|%$7ms{Zs`i5g5B*nl@qfkCs(~_BxjK2ko_F9u>81JC?L=bm0%YdG7GB4z*PWdJavGk5B1UG%NSe=_g)J95s4d z=D<$zNrTH*U?3Q?6%zLAfSr-qozX=5xCi$<+gulTgeQtn57NG@YVT{^l)cKPYZc0- zi=Y~u-HN&ApdB;qSK)giHpa%aBAnhP@D~r^w6SnNU6|@=#|AR#9ql9SG*5i=;mzB5 zqV{|UkK`tKM#GbTf9Cn8-3jz&Zpoga=j4?bJ1DMkHQKWh41#@idIwk#!Q&wO z)TYH83U6FET7}u1Io2RB2Ew)K!BEG+s2HsgAp^jd0U#xp!;kEL^X z#&(`0cerQM(v=P1ObvA(u5Oe*YYZyX-kbHg9Ekp5;Z^&ie;+3Jf$}H(ASg&sJ1^n{ z`hUe=+l-%6{7bDb?}{zbjd#%s`5CDb*y1H=qvp?lY^-dIKtJ&DsTt_U#9*H`V5{E< zxu77JXJOUAf{51TKH3MGH`KNQUc@t`tj2>+RT5@(`JrSPNc4Ef*5kOetEs%tN$d<-wyt^TC|lS zNy-wy&%hFQ8!G!dBQaIrGJ4@lfqjh4LNO@t<{;wLyAgEZB{_h-^26GG=^-(_t^>o*VT!!)F z(A<}jh(cq6{18NTd@&@RVGp+O2echrx~4pem^|%#49Z|v^@P3XHf1PFfYmkYmx~ zhy>LV=FPt<@9dzr3`?krqYWw;9jchF>jJuZOAqkbhI7_2wOwMYtxXtwdj61-0U4g) ztqXs!!1aOht6Lg`DbgZnTBgNcCg$fb1RMP!ERf<0#9z6|b?&XNatenI_R`Kpd~^Es zkK1*$MTfU-n-V>#%G)rf^q%nrN!dNJruGhb`jKreNy(fvF}uKj^W*>6ugw`X!0?d4 z+n`ieoq-8JaSZA?BzMq~Y`}R1;Jg+%8OrRebg$VUK}rKlC$9+XPvGukSR!G6I5`j5 zIG~w;pEKshw~#mVB3M7+hxLLC6dbDSobY@_Zaz-2PdJ7c)*L`~qJ-6d9 z`h))n^T-6ggs~pZt}u;3?^dx?imbu{B+EkG#1vqM8b|tzkjEj21!7gabnTN=7q!Jg zObUxRkc^G0za$m>s{PFUe>-&5rd{$aD|!E;{3(9CO+fuWLwG!Rs4q`C{FMJK720}j zRaK?m^qpYULQYghyJ0)g=_7N3Z2-(XgLMFtr$_+UGdh8xB*YVjI&C++HjsJodJx0V zXN!&q$LfVz;^Kuo0@JPo!V|eP$*nl2b`d`=Dc~z^EY8U#t25*j6&wdfGLN8QVU}Qr1NyRwTm=9b!cByU z+-f1fMRE95-q*g|ym{lJ!z$Xf8`R^Bys%-Jyzt^5ZEmhp7hX+*T&z>Sl zW13!m>tC;39;JGz+}ogC4VXLa(@$#WYC|8q%icHVp1ZqEK1re#5X5%OF)w(^-3_09 zaasQO?=NW&X^*}{`*1DzdZmCrOfT^ zf1=xtxjXi0UmlsTNc#fVDUV-R`|fLv1=3g^wQFVL%j#>|^ARt`)Xl)=OA zrGEc2((8oMr$U)?ZQ$C6-SC@@zI|AGn1qFXue*>0Aanm($A`T7$$Xj>zn!OQ%lHQV zb)hy%eXRlKyS0&xQ}jOEaBCeK@q3>+z@xr)HC@nGi2R1l!0QB7v^O+Ruyq}uLurpo zoykY=-&KNcqq_RLTqqcLP&6|7o4pvNRXb8XpiQWG{O$6 z_$~Bv!V#lc_I+Z+WbF&|;wyVA>5okLEyIxc@Z8N$YF|B3P=n|e9<_5-;{kb4@$}^2 zzr`%9y}WqZ*;gBf(~C#%YTQLQin$=Zau|3;-t&Q2QDS&xf)A$E7a=r29{61Kd053f z@XEazJ#Tb3b{Gqz8R&)#4;r_g0md%p2~>8ESztE}2e{m7oWStmZu)v3=&KJXSS#BC zSQpV#i~_8|0hgv8OTg@cVcBhDtwdY_h^S4uPy0~Q8p+8771E1?IxfcH{6%x%l!?~< zqn+W&;8-e8(mv6CII>f__xYF6?+UG0S*r{uJ%){S`UDAHMa96Y$O#sKz3hRF5Cmu_ zIMpp?F68z3$UhBrzcFvNlF82QEHM8ZOrE^n*UWl$6 zlM?Vv!1lj4eoK345afXvjI}MGo`rt%9F+4ojdYP)PH=(PN=LLGHS{3x!Jturf1iz zk00Xf=}hls=`g&0>?6-SaCe)mzG*!>WCpd#%%hXf(7T$hKqu^q9o5I#GZ@GpLh)8` zNJ#QhBIy#eFM+=X11lpYETqGrK)6udaahd4ExN!08zDr4%rpxkQ{Cdafj_Zu`N{`N zT(7_0vyZ)3b!dKVYK>^oA&sJZ|5mb`bR`=flOMl`+qP3$1QtK+M9^=??Y6h=72bCX!n6yEUet>HPMH!)YLpI|I`z|W-|(hu zYu?&|F%$q9_F@cPoTUqG;j@f<=qg-QvR2BZ-3DH6{Yvy8O`n*;qR zSdlrngH$L8pBY3^5YvTwmmb_AWIJen2s9xL7dcYO^2esloj+yMs^i^94WBuBTLPck@AYGo)kcj2V2;x(x@bYqWR9tqN8`SB}x%nKTi!owJQvH@;QKX0XXPK>B`|;g}L|U zt|-c$+jqw1nw85cQGDv2XxaZx#2NL*QWuL;qULaX4Kig3o>P)@vMQh9TE6$k-MG$P!J$XrP3GK=9c z0%wJm&ncWf0WtJJB8Hwf`<_Fkypp;8JRuU|K`K3Pkj)neI8I-vhcP$c;VDa z-wmfNhhDbuz;C|fZNm5M3x4TA|4|kDp4hoppC5$@jfHJLf%SBDBj2i$z>N^C=wt!( zPy^?X^U2vD#1Vvh7>ypceVjQs-eI=Hn!F>EKrkG3Y^z?}qnvs;W)z1JlN=Cm-)Aag zWB&PIW6p%!1AG4U?o&sTzp@l$jmqcI+Ghood|~xuGoQTU(CgjrsTsZfw^!d>wV}h1 zfio9O(aU->*ED$3T(b@!mZftr0?V)R-X+D3z9h5 zkU%IZJmTqaIWMklasr8`!RZvEOo)e=WrUhh+_J$DOR>udnIz^>P$iIadCxmGEv=e6 zfBcHgclPYQeDfWZ^B0U?v1vv3&lVJqxOG8M(XG;sc~y68yrV~t6`NO7%$rkj$Ced6 zx~<&2vSLBetqVsK-MT=Bv*2aGqfpZQ!Q;JnjtM}s-y;+vU&2F(c0VU79>nd^v+K6rclq*t zTh~c%xJ}%Ei%zR{iv5Dmq!6`gi6s;IoE|dt&yYU(k0(Q-oqu?}PQUH|y=}k(r9nSA|DMS!BQf<`Ad`iXI6oOO>E@ zY#i{B4W)pL-o0XN5#cZ=r13NZQS?@l8BUT!!zPK70r=HgiX-w?7{C%*W`)6*u79RCe}XYMmc zx>VkIOucW-9pSzE_2}EKTT)a)!Ghv3Ya@p3{`>CL>*Mot`{sA-nUdUTW9^vR90_+8 zu79H8cy^xMdV9Of_A%a=cc8LMdPH3%V2%SINDe9~U2=kdWY@?JT;ZXcLNdN)Mt?vmj&ZSg6i%LGfanWLmNF zJqK6ZLb7_|h_>swNGdlAK#>X`SjyF{D~)c~yY$rK+C{4Qw-xv24Ct_^ z_Tzt?ez$gZyI%e8qWvYY6zPogtGXHxXvdB?eWI)g5(xuMCev1gAk@N|PR4hW%1skN z#Z@x6tE>XpEbQ>rAk_L{suYi<9$r|WB*hIiR|3l|rpj)Ey~>6~i0C!lRR3R9fcZVj zZsRN=&eqP}E+RbC0#mP#w+SK{h`x`nE~uo(XHwL;F;Q5eqBy|qKJVf%BzEr7i^8)k zA)J@bFgr?${Z7nT=*^6sx=g^0t|%Q(%C<4+YIgh`NXU;if3+}5va z7(5Qa6hs;iM?jiI<`%mrAtxdpnO)I1Ys}U(E?q`y4X?@O)8n;u+JlZ(Nvt7avoMZg zIUH*ue68TJ%aF_X2-+j?m2t>*!bzhCdBh6`lsvcLBSl`rcbspW1-Za|Rs(VY?9V{; z)_Ws`bz#L~cjP5=L2I=aK|A+fkOiOI`gnh4ocQN0wzEL^`yY&OK%ceJJ0QeZKo z-QolD(0{XARM|mCR`jsuVAwd5U`0Ye+h1=LZNQ${dw9-@_sW{(@+zh9YBbo+~Y?(=2VF}|H1T7i|8}oc2dHdo-vA-WG=%*1g zQIxNAOnaJ-_=Fc~J3eXb^~om$FMKy}ue3y62aiI1^EnfuCvEUfrgJ7T0^DS!oCZlD zt<}PRN1`Elrcr_z+3$-u?agoDQh^Wv2tvlfxm;NsR*@AtSm}pW!&$gB*4)!Mk<*UKvqfMK%W(&9gaJ;hoT*7A-urzvEV^y& z;9<9v$V)0}tE+v-l#N;3At^Dr)OYN(i6fk&0wu4}fJ-s_#3TL}KXGsdj*#%;1}N4bqmX7aJdcVEIFjN?8s2Zb#G);Yhajo6vW+2Q8ha@9(&}eqsMnjvy~sO8q}*-4f4Skw?B1= zTqO7gXE1+8?vI`FS7+-mk2Tze;`&iJ)GdQArg*e4l;=y4jbcJH6vyP4JXAN2x(~|vT5;Ym&1}rbOvA~lf_=|C zw`)6gJb!n}g4vBv&Byj3ndnu>L_V;b_twRrAbbJ#rF-Efk5xc= zS|3loj)cGZ?4u9uac$nR^Hh()gZn!Bw|k2+0_k8C=dtgNGFcBRw&OGwljzP3#;Jm$3=8HIOU#w0P~_ zY$0NJl8_C+#0>-$~H9_@~$g; zK$M*&zuq{Q%JO}UgWY8(igwp|TEtznXg09fJs>9S=AL!|hdS*Er5<#F&VwMP4_plq z=s-W9sl;&}1lvrmg#M==2Fb;d#p@4)WKv`X*nk~$-+lA|Q2^px0=b6kisugL*H7j? zhUyCUSr8r-p?!J&TkQww1?`iro}(a-_0O#x=s60a9MXQ|peLBOI4@wd1|_gKSXYef zGB3Jm{-pd5`Azu#Ek7gLPZrj*%k}zrssZ2$t@6L6 z55nN;8p8wq`gzq{9KQbOZC!Z@yH7}kCCn$tgF9af1YFjq*GG4Y> z+ai8x8~H#SB`MpeZKdbU+SV<6y!J4`S;m-NP%o-SV26!@3^&l(&&MRG%x()01MLWb z8BCFL5R@ayP@%wSAh-!v7YumhD+RjaOAL&~#YVP46PCmv5ujtuOb|y~tYB-QK^9>E zNHp^FL=H&;3cr=ZCMMnY@cR_hd}3q4k-KZ_c4{ww@b3MiL8m z`^0;LKRC{3P+)V;?9*el3(ry5_>g)z;Pk?VhD@%0_xciVB}wB|75( zRsD>Tip8P`Ye5qP^{@*p)sO0ur4y7E6o8`D6X*eYT;Tk#wote_yrl&i57f>sT}lD# z^Y4&8UH(R$b_oIN7v7t;P>Wwe>xWnr_}nOtqZ6yt1;JLrON7(?0D0OYCx&7)!b*X) zg#3a?0O1NG+g!^F5rai<(FOd3r7t*wJH+YXEV|s%?AE03X$VyU3o4>p=)9^p*$&mr z8mc_?>i%`JId*bskePaSBd@Hhurb6?^9%o$c+GY~x+GZclo{8aJR@ zdTFnz8@i0`++}!5-=Ry^H2BEhef`sOk8~N)ze`HHknohTi^Te*W6tx`kFjq3f1`Eo z$8;KYWBwd&YgPU~&E-$9ldbY!Y0f{zT&?m~HRn%api;mW(Y|55o-cR{zDA()mUcRC zky%#LWy2|VOlNl5^r zy(EgU^!Z z*9VA_ExlE!Ts+)~WTB!MQFqv!a!LEg!aKAtAA9!W55CVY8Igxr(Kg?{_5Q5e-=avm zIg>>s-L{7DR&2#DEXZ0?su`$zTmn-+`mbs#vk&G`m42La4f{u$)oty23`-oQ>E zgYv7YdoDNM)WQ|f~1fcydhrypct@NqMHEXzp8F-B`bqMDJ3u%=LJyoF-#M5GQvF-v$4MBy}E(>~;O zF4c5*7HJujij)zqQIISnBzcIGrAoL3_8urnDU?BX}|*Mj=89hYUzR1=i8&NCig=M+kWw zLE4Hs1Rr;T7;5q{A^f7%DS9s`C-?-1I5aNq`>!;vROH6xSL7pKeWlGie_osM)mP|= zH@&KSpiE`KSV22m?hFnJKn!Sf6lkuhz@$a-;`*0N==(`}RFen?Y6)-7bp?V%ka>wO zyRi)BMYIQ+FIfN5mLfm~cPn}cYnGw(#Cn(L>&5 zaQMOpFG}9EAS5G+q1xy2n{PDU9Kf~5zQBY=?t$q&8J98M;kn_fF(4x-2q zMA~`VN$raUtx3D22b0OWcRcyR~#6Qi=or6*|>j5CU!7<@62(lG`}qkb#8wYXk6Gs|*G@1N15nHV(=qBP-C+8;lMi zGcW{_t>q>qhyK1KVeU;z-lR?9xHl+(dA1BXY+F%os)hhx;_T zwUtIHWI8&?nJJ%7nA}C%qpjp~d4BdX`LV_-X>8+G>6M1y?DA9pI(KAN7ksX23C=+k z@R>dyE%n~u|6IJJH9-HBiC-FXAs4lQJs^oasUJ-V@P$t|JPghg7^y(8BAm!Ewa$Tc zlfDGu@c2=nolu-OcogUm;tS}s2H}q&Rw>P;xXI)x1ZCWlyuN&?GbSOPvvzIclM<5R zVq>Bpq?jpoA3{nS-o}@Nlvr}+>jyM%5KL$X>anP9u|%!Dvh<#-o{&_wYWY{Kq>+Zd zdBRiqHIFp%=%bz>)eRe3$stl_JziD%p;-nIdWXbEDfO`L8r}8l9IivT$%z+Z6v5#u>_^ z2^|trhs>DK5BE+vo9;wcE#GHj*L{JFVEzAn*P9-zh02E08w&Y;1Ao0fLc0UGl&2Y0$AueJ@8M!0_J^pO0G`pzV8k|MJwpsLkG{1 z`7h=Zcn&Xmwm=6H%}+ZfR|`0an0UP(>Or3!P8*nmL-4IZ>ZjGW21Q4RLWE0Lu1@c^ zuSbrjE6k0@WX*eVD1CrXIlA!Kd@QWKCVlxLX|?<+_Ia37_uC3z2DE+7hQ-B-k4?7P z)6(jl^XKk(dftL(q$lR@d}hIdr=O-Zk=duPqlSs^5|Z4D_(J@#JhU;!q~Z&*2s*}~ z|3u6c#t$-p;79$9+ZsxEbi*y&Mt&L3Mg4G!E0FSp%^&gCBA|erGoT$HaU-Zi%NGt4d^J2V&3;Y=zckyymakc`%?RefMTKH&6U zH{k59*ENvynEZ?XLuEk3fe4!TIEUR96CK$mj7${}k%WX~K=@=LPYeK$p%1>O6r#jr zM`l`LCVmAW3d0>gX|&_VieEv}xA;+VyB}WPHEq52D&eWb7D2I8 zfH9%)_28^lh(AJ15cmns0#1h!bmxXKUXYTwVTjdgjkZRc&7q-Yi#gES z4}NclIZcG1r@^tBo*BSphnyH{iN_O=z@j*w{B3n;lJlOQ{$`3!eR%2mAx4cq_mi|0 zQboP?PN(TxcuM0EUKqGBcdE9J58AgjYAepa|GIiL?LrzXzUtl;xNjz}GG|a6`7B_* zUuY6-bEWIGQ*;sSfJ}q$Y3t|OqSz+kRon|pF>P$vEkn2o!YO1yvRhJ|gu~>6sp;nj zb8`K>5jZ7{@iIshl&;`IiTVfQQpE9OI^K|z>q7_G#CgL%Dqrf1bJM3NQ&eO`SSSPw zaUPt^ABf(odW}5sxWhf zPHf$mV;NrC+-ZN- z#ybS?N0c1KyaSh>=9r7>PQ%y7Gu&yJrE`kmpz;c9%h$WpD!#k*t~+gD2^7*G${5+t zQZIMf#M;U`-DxitDSzuudrSXR+PKp`tgETSo%Us;O}pG_KQ_s$r#tP>(!D-r{aFoS zscP_vi5j*5KAcKs!&Q#wa-=HQbXLpevpP|#8o4(3O)KCHu)&Ad5f;IYNM)ncbhI)B z^=2&j=?4WZU3r6=*jPgB*`;>qM9OfNks+JWZoNx|vrqzDjS*c1!s*WMp9?QvfEGHGK+x z_eG%!RG)?{YQ)A;aQ#K2eP>owO{=V`Lu};l+W+&awwC^HsA<#3QzS+)(+zoADSTlT zKdI!`Evl-U39#5Yr*`a^iszg4<(YZP&3Ymbr05;ptga2+&cyTyeP#)W5>Te%xsFXn z-gJCS@y{07hH_N`C1;{Ofm27y8i3aZF*p_Ll8RKmSStcLAtkL8p>E+#dK`uxCty^x zL@g!hsSb6^@rGKSj&!xiv$08RHr~-ZDn&gH63;}>t+AnIrmfsItFFAVYHE4iWZU!! z*CUp#u4-b<%vn`+z_yxcwh^hfq}uY!XH`v`WhbLL{X->YmNu?psn=B z9n+HN2t$3)VoXC1bHwm!#9*m6LW2q7_e|06H1uIZJ$;K(ik7oRAXn>A5E534wmgVA z6(tEVY3$|L##6)yiPB94bW3gj@T9F$j+Bo6gocEG6}Y#cua2xXG2&^UHvgUFxA2bUE*)E$Yx}y=@2urik%UAD*^p4QU3Hr*B(=EX`^Po~frg@s6IRV{_Bm z(W*?q6H!5eUZrR)1*5+ie5oXDV7=Grs6#NR6mXg;aw}1y0&}BP)3>_LS!m;44>^Ju zL0zx+f9;)XY!p=#fY0o1q=2=ppaiA3m`c!;ZM(DnmZoVoX$bI*Ov%|N5Jr9(@SSj4B<%pr;>zZF?4nXdK^#;r))KWOIs(OAyvO_A6= z(suAm+Y%CYIQoa5h%Fv&SWvw0;gg+lQ^L`XVGM3L;5i^k( zqMu4;DB4ZOG-&1Hirq|X3piAuHF{9Vek+;DrUz{D7Um+^SS$q|Gir8Q2`dkbxS2I0 z=n@o)6;YC+po;l3W@_Pe{n>OT29dX3-+DYFWI1{!mre}Ca(3=iEEdhFo{A#t2?~KY zkxuvNtMsI^kQdFz7fztJC!NYuH0?K|QQnz~N=N#Ws=?@A^F`v!NH$GcCSm4*l2lEl zwn82rY2q%P8aP4!0<_5Fo>I9&ZH0i#A2k%r=QB+;HG_kL0n_awBlPA0h|<(E1gpZO z3_DRZIP4p+VJ2ah)1uA~WnwOqWOIRdK1qJ<{+xcls3-g3QAMHLUzDQNy68i=%_cx4 zGwgD`{{#rC=T7Xq>!=&;rRg71%Dnxy&7dv6dS$D>Ed!f`X(Mb1J43a<*uU3l=RUc* z91Ubahe})krF)_vc>SEpm$A zZwzgigu%*WxK(0q9h_Gsw=%dTHQ$p#^S`8+o^zTIPOL>wc1WkJliQeoyPZ9+cgT7% znLvD=Y2b&r^X+N*K^~STxSiw~c}ZTto_|zE8S+2L+{6@lTsCu~+z0X<1NmoVoBYAP zhNJAnd__KH_sy&5sEBNYCq9KMcFQNSS3Z-^Wsm%bX8KCLkT2ymbmp(DMSd;&WIrwT zll;t1ygO-|KD1r}j@n8K-9@XZw(Y0w29Tvg+&Q-aroLP57c5aJ)M-H%G;~-xSnPyDqLlvbQaV#^+jS3Dr&M;=OG5A8+A+O7u@|JukBl3oP z#IA#vjal-Zyf3@BHD@+!#E^VlIF_*7Q`i*%k1ob9{GV!`8{VA)qwl?JWD{5We z4L1j!aPuP9U!s1H0EvtloZ% zE;AQ*l7eB!Z*cv_dY|e{JF+2st@`2STALpHV2S;D!C-Cip|1Eap0ubKQ_l#{U@gAP z9toa{Jm~C|M`LMKmUoCzi7{N~JWYcSyTlh6$|mHZ-fXN-s^YQktOU@YDR@~+=?Xtn zASXR3q~ozhB$CV^VMPo+Bu;UcAb*Ov94X_&&g1FDe7Zo=4(CjyVZKd6(57M8IF5<< zCO8q-*d>+XMi zrsFhYI zBS!^JpQsa@AqV-7HB6qhJdN6MdaP_F_U+|Zvx~4|8*K}ADR$}_+ZOF&hu`Qp=g7XQ ZEju-|du4pS`upEKpBeJb&E_v6e*^XGd_VvI literal 0 HcmV?d00001 diff --git a/freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.woff b/freescout-dist/public/fonts/liberation-sans/LiberationSans-Bold-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..556b1102bb7caea88a700d0613b5c1564d0ef47e GIT binary patch literal 21632 zcmY&dUb2O1PPF<}u=06-z}Cr|tXasUMo zhM2sZ5&)ox3IG8A=pc%Fj>f0OlvD(Nc&$Gi^&bR56QJM8DKRkra0q|$djA19bCloE zTHg);01X2GfYN{P@6aH)#?Zy-hXW`7!wLPtqtooK785&DYXAV8^anqDIBOZw7%x+O z$Dg!9)DH*p{{hj|%H0G2KxhO2xDo&WU8iU5dp9# z$>aZk6n+|1(#+b)?Wcc!KL7&&Ksx`${JyfXHT>bht^RN~esGwM*idY(@AlI!@J}%S z;eP-R1Vyybw>Aa~z}4+`?&}4h3IN#xg*bpy{1p7p_IChz z02n|9015yGss`!>ME;Cc8mJZk@}mU-RRZq((Sm}dgR%V3asC&B%mGY*ks-l=VIV+( z{|lhI0O)UdfZO*^eMCP1FbOCL{mcj4J21G(*Uxw$g+XZa06;xEFF*F>0`vc&5CTBm z)~)}_SfVJPhU{#nk@tzf86d^yCIm-PD;o6Ws%U_M{aQdqp|VE$C?!-hvr`e?#5AB9 z3T{1P2z%mx^@o(SFOxEcv;hu)!Jx;@aWoBRV!OS~ZacY{=0sUp0lRZ-imKKDBUpsj zEbq+|RA0(-O`q~!(DrKS1v5SxkYNkcJ>tCASn$~|*(1^l`fGEU%MU zwgoeLC`?0r_$~(TQ_uDH2$#egH(ahg6Q?j86U}d89eVU;(>Dg( z&5@0+;Tm#oD_lDgMlS}c&9R2n86S|@lEDh*x!3K9o(P6i=Xp}6GC3%4xz}w5|8_K{ z+$OJc;OsQh2fvpb!gB)Ov<_T1EZ%Lai+EpeLQB*IFM9+!{wPfqmUNAc@N>RxZT&2T z|JJFVo{66x7Pu%l;w$hWQ@|T7>@UAYq(I;}vccX7`H|b4xj3fOx_~px1-fExXf0@B zXhmoTXh~=b=zq|v(DcxE&}7h-s7cD-A14342})BiN?n2E_7mvuA0M9X8ylJ$m>6Loq$H&!rY5H+q9UUsp`obB zEiJ6fFE6gnD=Vrhs3@`3Gc&R>urRUFH#fF6v^2fhJw3eKKR>?SJ3G2MxHus|#6-nK z#zx0S!a~DC!NJIh8=YO=UgLEi?i`9trqyisIyzUeT&~t_(LXq@(rmt3?KXYc_J$Rq zDocBzK}R?0p`0){O&yY-?2XfbchQ!G8;H=39&;yjvAGH?EM7hfjn(S0Ke?XL)> z_rCv_JNQ$BMQyudXm)0PeRZD0SZ90A8balGJk95HDNF=1a7Udh3W(H9f&R}nRjTH$ zD8Epb+$01?McFlqi9+ z^##y2T`0@udKU!`CDTn=2}GEiE(=8rw>+hdIraYFfQs)_Z#TuyH-*vNoEF-ke(b(_jz5~IP4);pY|jKN|diVypZ;$hAjX3U+;8jYc;?BHrP$kCVenI~Wr;mVhVN|$z z6;cTvzw>EhlwmPu5)HN4xz2LM9flrZM)^KITe~I53$N`aLswdUb>pR zQCy_hgR_w7Xo4Cp@709{HIu=x^xM&E6RDv8Wy79p`%gYM*$beb6>5iE5?|(QQJ*uK zqhYgRfp%iVDc$g`RrZ1Q<*YHbsA9n&CiMAm%d39NuBm*Q;1;9 zZtZJidDV&L_zjV zySXscbFAN?H9lVb%*M#1;hi|={Q{uT#LT^jY~ymf%NE@>vIaqkGDs(~Mrf|ji9$+m zH~D$h#|tGbwDL!4v@Rfo%qJ5ZxB%?LxxWRuzVLIoSyZp@fXnQA zyF6q`dtF@W)QpQk9yR2`*)}p<>JyoYVNvFgBty_;R5WY&guc4!txLmGmc*Egv!Z~v zNEE%*mvpVfX7xJvnxfXdDZ4td(*oK<%U~eAtCAP`XThX`Rz9!5s@+|}xTLb|#5C#k zRU2Iejg8^Nd4IDfLDNBJ@!Na>>c4c^8p#*2yPMOOl+~-218v ztz77plBf*R8C_w+ew>A^dj_fYXI~Yj_d)N<{MmB$B`KowRdo@NYzF6o`k=WnMFHpi z`MY9_0Bk`<;~L&>0lNW;aVON4k2kzyhFuO*hkzI$-9;zh{Aqm)AtpGRApoD(NVUA56520EkuKf9_PC*Ibg zAz#tK(%;L1#lE3gc>OQv%zF?pX}2V;rXDAsMnBqyAMKD}@pr_J*7is13X^6hW_vkq z+^*Z`6Ar#i$-U6J<(=Qv17tT5mOqFkQnM7f)8im!s5b?f#G1SX5n&R zL8E|RO)jlrHTHV>GZ(YQFv9`!q>#jAjlwTfXbkzQA?c}i&uK~ zI|P~o34nOTYsFhdV60`a111ypM}~!`M5OJT+^ts#t|MVI2ow?<6g@^wiMAlzrjG#! z7ex<^IgEphOAsdntflm!_Z|0{j8u*k_l{2&GzH%IUVtZWM@z%fXQ(T!VrpptI@}l! z|F0iIh`?CRRL+E}$I-h!+S_X|)O*X2>Ae2^i`M6R=L1G8*^n0`IT|nqyhiv2Dat3x z7a|9nlj;i$hphkg_6-*ZiO#zx?$M7G1ELQh1R)zm0|g)z{Qmf#KQ-&$&lmz7Vi`pzEht@)3eVJK zm~8N@M{k%t)EWH-{D6QClZ=`SQpsGYP+6h&U3XUNT(@PIVj!niqo>>39T*sS08s*D z3UUH9?W^UXrJ!}udTr-@uzV16P<_DH`{_RtMiF^}U4v>1bpyNY*tzpM`C9)LuTAl@Nio#IK7ODsfcSg!bYv0$=* zsw}&xO9*b{CTJVoy}>>Hz4^W8VGq?RAq z)DqP0)GE}x<-FxA$|>NSf=?0WAZ*-h3{8wpq)j|c)J^P7Am_5@KIaPO z=I0{k&gZdla46e6kRi^)WrUwM9Gn@b>u~o>A#olFjKjeUjJ~lt3Re8!yQe9~Q&v4kW(d;^};U zd85&pOP$laL8I^?sT1c`*B|+EzR1~h^$g75nt@yF6goB+TwKfQmXJg<#Kzq|S_0)F z5rYW4k|;T6+fIQtk(&rObcZTZEQ3v06rzqZR;Rd_EH)c5n*l931Y9D^`Xm8kU80El zU(L6r2P2O85a-WOp8=Ic_Q-XNNcy`{13gPZOOq=#T172+^u->wBO;(LWBX{-qp;Wui;cr(k zE=c)Q#0*geMq}dprOU(e3?=Ge31Z#)Ma6yg%Vf|ChR#8iG5u}cTr zBGKGWRERU$!x5Y9T}VYitR^KCTOirfe)}e`i70;z5W9uCm9Ku_$vm%^aM^t7rqnr> znT$RBwpz}m#1bcZjn1?S>goY=$l;4*WXhowugr9f-s1hIrmL~T<7RqAXWge_!E^pE z!8X_XesxNP>&;dft8LCsN!Q56fD%sd4wuvYv1`z|TzC8ZSrG&Mdogv=oUYH;SW`ug z&Q~S7`$FKvFDso#Lf+cFzVc|C3Vh*i95q*=GR5S0*7>MLBHss9&3JlrSJgmF9uS2D zpu6YGXyF0JcYE-z# z@#$>e^i$qG!Cl9v=}ucJ-k%X_n5a5)m1er>M&K=UUN>8_5R{#;VUko#p$TfVzCVtQ zc~7D*8cx!pOFFcDyTl&LQsJ#>W+ZVmPM$7wnCUBoTGoH*@@~3nuU(o@CX5QBay$fQ z*1P2gCLkCqv$74FAzKUvgCkBIA=mJyC(T-qcaR1Qx*OE`bf*yed)y1f8Y3|!%q2r% zEuvaaJC}aM;;CA@o#w$Iw%9KXWT=g$*r6KSt>V7zLtBp6)ZeI&Ty*M2e7d|5d}?Uq zzZTJp!G0GMWc=al@6fI@Afb5!hxf;#wfmTus08Wg>(txzg==Zu(`>?q$nIAxF=@ z{dJkSzWzSb$gcx$GUv*FAI0zIHff%g-Ru%0sv!9YjzA!PeKU~{)Ny}?RAQ-TL;>!C4~#|+lx@+u3;$ao}@DvM=qzQZj^Bqx7dO` z0@?heCSK?WB~NSoD&sZ^J-GHHOdAhVh?6nIFSS1Rzus%&Z>}<_#X|4PTXBDrM&jK{ zfq0=Z0Ydg*lo@f2K6Q!l*~!#S=B&=esr6HC^OIWS+)5<=FlMfi@PriK?>n^mn_?Z7 z-6p!ms83Y%60T@3S4#W&Vyg?6NFK`&*%WIBR#$bgp|HHJm!oe)#yPi7%K2Z`ft`k= z(L7kAtjO@$41Pcu1@nmf%)Q~5;d4f8_C~wI(JBrci?u3(R2y&ueUL9clz5yEYfGBm ze5g)>-GA{~t3eh2F;jCu$s>pow;|tQ#VGV)@T2~rL8nfkri}F*74IRvYHQrRMgw;v zz7+tL2$N#_r5sOwoe3Wo`4JyIFOsqi#7abZf!loDR+8;uwOj!bDq3Gr_s!>cl%nIf z!pBPI&*x<|{pZ27=H;4zkLTai3m=DkbDM?9(4y=LtB!?+tF}$hgN^vQ=XxPXk`uM0 zV!B)$dwsw{Lfj1*7r00vS7NCJ13fVaPLhSJV_{LzdDLAggORC4I!#t&Tjphy^ypl7 zn0d1_%7QRnCa436>idB4b0l8E{tlkX-R?Y6;K?MOjs~;NO!W}pbGKn9N2kjeK{@O7 zb>P6hL^KAA`QBk|H=$ntv+8}DTfNK8Gr(#s&?_%MU_ulDsa%#78y0DsBjm(=T)hoN`R8cMx5@jl zy~Q`EA!XGUzRQY0t<|aTkSwsU6=Ch4KifLi!~Vx>_BxWaiD$Z4kWzShJjEL-%kp|yTtLz zsRDSP@$Ph<&PIdb*}Q|@M+|35!(7ng-jC>+qMFxx8rvL#FE-D;N~9NlrC_JaSWnDQ zrzpe(09y@5cq6OPjkGFGN<=Pp*;M=%fR@tes5Nhf;k1t)cH4VtMTCpk);-gEh`xbk zqg;U$O{kBJK7fk|u09^zUObSzFpYv{`1f1cha)tH5(~>G>h2{ip#ciysYhRrVBJQg zIFOVJFH^nx)C_<9ccm`tQ=YGG`@b7B7JRRCJWQ>2+wWb07&$SyOh)&8Of~oYeu|L_ zo#^<3@{%i$i&d!wmyM%TjJ=ikBC&0({!!Bfis=llq#Iq=%#I45?|y&kKBe#y1*8%M z>ODzjDG;T_P9G666BC zk7K~uUt$xyV4lxe8f=OymPX^mQpC`!3WAG4Cn0~Y+V4~KTP03<0YA-v+xeklA>kLV z1jpb*JXySw7Pe~Myf{_C$`&Fn+3)nYhI0609Fyp9M2qQoBfXSjHG^jWS$!InJ;yDE zkI!P2SJ$qr`A_Uyp$95mGyFnk6@n9{OwpRZ^{5x<+ky;;z`a$Xm1^-SZMAH3d3nkm^A4-K!B}Mg{fp$~ z&UtG5!MhyJFAH^!4qSw~lo*Uw4Fmd=H>bC{||kIYr7MB$r}3t^^&y&x*#)Udx0_&c|;NFGBN+0#SZp zvaVjU*}W%Z$fxM%U7Z(x<@286BOlPTs~yX|x(kJPkL0s|aaIQ#tA+Hkc!8Ci6gb_R6E?17=*?%v!X|FsyVB z>`KZDJjBGw4~5|XZ1J7`jh z{#YQlj*-;Kq`{Irhp!}2M}M-=(Lki$)1EJN62o=c=Zo+KJ_mOrf@gCOmTWDQ;q3={ zo^=QGj3KT0wK*~#nL=9FnS>7`r6UE(ALXt*z+5XQv2;nCT9``I^5i3GWB_hlKO5UM z|BE5yolWvSQ6=s!pUWxo#+Yiq8Rb7X4QHdK7?Q|skMBz{M81!$zw%HZ`iRo}?uGBI zZ#~}Mj#;Y&vNcrV3?Tc|{CqD9Vl3XE-t)M!{xem|$B*Jfy+}FXmZ?({x^mt!M}_LYp?3>?j*O6I6{F#Vky!n1hC z=)W3pzh;=*N}pjr`!ML9l&EWJqyMaq(~!>gd=#p|q%;OWUJdv+#eMaotp?s#!wu>o zPm=c%-Jtmjv3<_2Bxa=)9Ku!ZM%T(bE;@n%1(!W2c|!-@QUk`6Z^C%(mz zY#k9f5(P^s?IPRkUd42DYK_OKAO%8p@`6ziII$aGYK?6z>9d|peYV$Zj*h9;yNGG9 zzSK{Ru{#f?eik|af#H9B-B0sui1psDNDEo!gZ1xYjONwg@KL;K*=f)e_}7f_=Q)RhU>+OB6o5KZzP!XJx>r@_^(}|DvORe>WE$UG98VeiqBIKyKQ^Us$rv*^ z**s$;A}-<@!vbew-!37;a+pg{*zcR=P5S&BjbO@Fo6laXE*7Vkw#ZO7RdRV^-}H-& zW%A#;W`}lJWtW28tyG!yC?7O_IH5UyXm(CqhdgC~u$qi&wBUMcoIj3!0{we7^MRlD znGs8=!+Z3AMeOcB9Z!SoLv#>%dsX@abto~Ymz_)RpsJ_)L0@S3!d$q1G`tk*6xRk2 zf`$||v`JBcRh;8m76Jn9yF1fR*~|h8eq&EvKWfT=cRl=f9M3&y@yhFpnmP7(3Hf3X z7`|G>5{jW3$5s{!Jxj=GkvdxkYT(`6FSO9y;7U+Z@5fHdj8*hSE=MmtgA+A4PXdh@ zZCX6fTU{2+XP?i%GwRp4)>BsbYL2iHWFS2HS{$R`hc@uK>5w@9nk$w3q!O8;V}f^T3L2n#al@>mmA-O+x%_T-0gb*8otki zROjzDs9w+XWd#~@lA9;;FnNTcEN6W7JIfa6qnv?Mt5lc(_L@BJ!(TQ4t{7iYh24< zSdLz+tMTWOqsi1s%I)J5CN!3<3e!1PyXNxovGVz|*b^aZ-D8%`?6!H+W@>@O6Sii4 zIt|Zw$lTu(H=C6H-KNFyt_9ppm{gL35|>><|3ux=J*RU|HLY`R9Bq5%v-R1FNa(IJ zVio0&VGERO0sfQJZ@c0+j>7iZ*Vpi9@<-VBs4U=>;u3Zn2n^3PXsFzj;zhofSZxM= zhNrTvQ6K%2CQh+^pA#qosZI)XHg$PcGshSv5yy>AB!DECr`QLHvx%wqx4)Hmw5^4s zBLnexhWh8rv!{f{FdGTILvWm{IEYj$`Ar;pCTuR4hkW~>u;aB2sA5Ru0k56fCEo0H z%b*(9J^@>rHRoYev<#Pjs6Cxg_Xa7EDaPSVQ&ZT)8ho1Kf*3okhH7MbnagllkW zYV8@~J&GN1U7nbOL3;-~T;3PC3gr0f!pt?RgpYSxj})$Yxm2X7X2DYk&c=otxOMgO z(+!EpLB2+@?KSjXCay!=>;3$^?gS4j6ZSL_+D(jZzd2HRh#|pWmOae(; zU2g3Q^iNqsvPXp@ZQr29*pB-Pl0x31jko!IF|7qNIz+X2@DWt;ulG|x|LZQv8{Qm zWTF&@sJ7bXgco(!0@1>380n^E7tp7F4a)n8?4o%-f? z%5!A7LB|SpqpY(R`vP+hz~K;slbaFCA4zB&`LaNGuHjIN>njxy*C|?V}N@D*S zFP+A{9S!TBytd|+yVTpddu+C#K8e9u=wfs@Xt?PdHi17c=PScjY4ciCZ#*?N)#=FI z?hM{?{~$Q)gmz~R;TZMvCQl-koLFh))j5#u(X6gHRu1~D$Nn~5;zlX>83V!tKKMHs zv*#fuf-En_2{Nt9(Gn;w_eNI7`MSz{qrOx?f9HZoHaT>g%PUMvgg&o2wvfnR-K;9-7``a&=L5t>va1clSQ5W1Yb z_?*KlgJ+q+PV9dph?jFp$Q#`X2?x{C3R1uV|22Jd_6t2%X zTZqRpIc@fcTQ)fMko&9yA1*JVaaL^4wU?{JaUcP%P3k(ZwsPc{u7&|y9oPJlv4UtT#A z1(le*jQ6ptd$WyMh>6NrUP*4TT*yGLI%&|A?KV0`^&KCo8OuMGhsG>E&U9im5fp0K z)9NDh?}MPGfnu$A<&9G-*u%Rpmw%rq8>v8mvrtC22K1}cuAEFI*Slv?2y!G%DYZ-$ z&&s;Xae4al@`9w`q``7DYd*{!jiBp-rcj8`eujBR;7FU@$AEJldnCY`IHuwt{wlXe zJ?iX<+CQkYqa)kCR%P=&rhoY5>%E1vZK?{z0O5kZ4%toT5ofAEpDWG?O@x2ykDw?b zchIdDCSN|UIncfmGLp^g#GGU!rZ@&BlW$fzdBl9r!`s@sUsO+`XX9 zI_YfpijDG;kH-vu!_at79{Q>76+!VYkxw}sg1hq}PA`>P0IPnaKAxA-;4szA@^#37 zRjzFSdq|E=9w)5tqa&evqN`^Mb?#FA1^2Iyx-2$d%Uy*@7ZAG=OlMF#!2!ezx1{!ELq#}sDl=F^?K8MATSX-(yJ6CbNx>}8123W7f%|L zle8BKrb>hu?QTI_fXI->Nzx;RFW>6t3n|v@-ZGpACJ*Z6h%sNm$UD`$PU6S|rGj9a z6G#VH6(R^0Qa>kVe@pM=dRmxBQF(~4Zqc+AU zLksk2IPD!q3+OWomtVFEN~vG`B-R6B^jH2fa^d&s4MK-dGi@_}EwDa3v+3Fb&u3YZ)T?1tzyp8DYcf!QeLicZhz@Q=B>gfwH%P!OjbsN#MdMJ-$wJmFJ`f}?M_Z|DuljNXTXxTCT4(dXn zIM%q!ZtRzYjglviFL>68W@eWGlQ-G!ia^@-F})AsjqhDx5Y#V`&e^nUjSz=BB2_S% z8gHAXe1 z>l~Ddr9TW#TjX6Wk03Ja;e$tE-|gVR@of>`!L;B(nTfkvjsh(82^+>LjlJHoD4GRt z>xL8J%UB;sw_MJxcXk&7Lf};eeFe34Nv(O>>@}{T$^3G?qzStSbt9?D{Z)#K)uiX* zKjIK6IGnLxC-;!aAfi>3Tqc_JQ0|4LSohHBlV>Z7W~9^EO#4HQMmAv2xvg(!Bf10E zYZKrQgtix{9#ff(p%1HcUPRy2Tue?KB5@lk6SJ^>9V6x(cr0T#gF3uqw!9g6fO|3f zM1i}$8o7ZFd&sL>Zl&*#|9BzxUDA~p=4A-O@=$U`P*2m8MXjgVk^)@mRO(b(&l^K| z52o{}A7%ReJ}n!>Z`4cDqgLAMp5t)>3!W*>eBE9Hg`AG$*412JQ>pfGY;NvRR_g4q z^*=jnoekTDE|UtUXo;N3`yU(B_t}*EH<}}Rcd`%4H4_kM&q$rf5sBWvgH5HInEud4 z*)}UUVN}#SK}W5D!J5GReQ^UaSbORtZxSNgVqGeq?K9`B#s{m$kzyuvS?6g2N1oWM zQCZnfP3u>Q$rUYWeZR)U4}@kZk4JT#SFyJwGBrIC}wq4)uHCzu+uzN(~y zBU%QH6wp3TKCPipWpJ^&uM@9k1#pCY-`QBhHhOmM|DnVOjz8OO zb;)TG$f028^ThbIWRA_Z`eA&7A>P&#?X_+cK{&I**1* z>m}iEt6UfA<$yDr6y{lk$M>__6+9#w`}XQj;a~UEyXoQc_%b`VXzrg5{P!FAz21&= zl7_%G;Eo$1%jRKpwQs}M^ZoPaBK`T2-TDh-10qI!tK2GV^<>$oV^d?B%Hdf7IVcnh zHM|_z9MN=SVfre|t3X;pr!o!rg(xqV5~g>dTTwt`U>HklMzh{MhS{`JtAl>nZ3EssNb5&u zv7;A6U5@753R;5&h3PG#rVQXWIl|GQXfhM_LqlsF??@IwjrEK>^5U*c_-idrh3w>we5VWj%eppU|7JA1}SQNMp0u4Yt^s zELW4ziSfBft2a1EXoY%ixL7Q5u`m_b*+`s~ziu;p90a3TF1)*OKbbGnSJ*#1p%wW$ zn((MKx>#P_U4y~DgG4*rNU-m>7aZ|Enj5#}y2B9hYHNvsP%T%pQz1vV1M}Vp$`di( z9dh4-h|Rp4d`N9O0)0cFB`CfGIsnhup-Pr57j~SsX*X{q5lwLIxr`V}kK8s={`@1+ zPRfDUQz_V5yF3j(e)`z>_!t!hOTj;Fz=4lwYwd)O<2(-+b{@FncnpaO&A=K+|!53I6;PV@hOmp(^gy{G63`z!fXIx@q##Nn*Oq z``DXpa)AST!)4FRJDkxC$BTgP&nFgJ*Cpm2`E+^yFjG&{d9+i3KXJ3}OGPAxx+`k&ff%!}#>i~ycf_XXN)tN>NDoolP$II!%! z!?^a%2eXQ5ZhaIjZI2H`r=Z73e3xs;@tc~YF>jXL3gP5@G^F=$eJhEfPiRTGc@0C?tO^I*)HN<@& z;K*_+<~U2k<)%X1y=x-{_Ik%Z8;|>gH+Y-HGE9GPwN0tpa#<_Q?0U{;Enh!tzihD* zEZdklwKDN)UN%vIoIWeRfOx=d9u|z`g?}?i1P(mNmOPM(7+3R*E>mQI2x5~R$||N8 zV@xG9sCCTmwVeh05!^=;%CKYL(5X3{cMtZh{OE0$N3xKEG(M~;-$zUOwRGr94J*u& zu#Ys>3CSgwlu`I^wueegAogM z-r_c7IIQt>P^)Rc=+sW(nSa~C{{}Od6ji`iO!{+&=?5Qi#`dh&9WaJR`Sn-x4TkWSz=h z7B47Lhvr)jE)-sXsbSFe%5{_ZE?;3!cDdkl5>D|99s9(0bKXhtskrfc^0yfXalO)D z7v$cVvS(>H(jD^dQq9I?jd<$g7BpLyFhbQBZC3<2b>ejb+Y9R)DCjRBCJ8uG9p<)y zyL;4WKSIucjnxTQ;+aUnH{{NJB}T2w!tlOg&{@1C*b>8_u8_bjgWYD?yDt>j%?>Zr zE92p@%PQ0wyut0yLTNR3NTJ3TyQM-4cMumCR2%Z|piG!#P<068iWyu}l7U;Y1Es6p z)kf`6{=M6e5@^lIYQLF_EVpBDFc(H~Lg!(%m6|=r7;~Mm;A6F+%BvM9N@?s!o-#!+ z*A{5l79xhdkmUqaO_O!$#)(!3q5CHxjH^7NvXqiy&IyDbsErwaz*J&PJgA~Q4-#l1 zsh9d@?tB8q4dgHg%y+(j;SwRqSy;h&Jaq#*$u4w`5ouNcJE$mq9=8nMv7C>m&sS$!9|n?^a`cIJHyexb@vC8abcL1BP9x;uY9L9qYBuP#AXD|LR4alO39r78%mx>7x^zoB0V z>eFYds!0Dl9<~e(Y;F_oQ85tTIu;b#au~5&L`{Do8N?twXzY%QVwpRvbGuUx>o#QM z!X+XecGKLCCEqH=+CAa?zWVRsaY&mHrulczwIq~<;OFs_q}PhAD9>) ziN+^8bfr3fUX_nRnq~@l{KE(iZ9j;q8pGt^_0HdhK_C=KECHxD(5 zuhtkPp-H={vLRYT(_jHt#|rj~E(K?nM0ip!E{87SkuDn=Pe11mRE4wOShbJgE7pIdaiGWWSWnGHDMBRz^}j%*k2xY2e7Z)V;a>gVfdqW zx-8#Z8wLK?Ph12lIr!vnKOP)fj7Iall252_?H6kYes}J1!>1v};2mw2-_<~ukQ#8a z2rCWlB(Q<{IBO<%kyW8KjMyPnrkbzcwnm!l1CY*Jwu(4neEp#0IT4*`5QB zW)@tE`vwXwYDgTTirwQ1%La6OHwYdPktV>w&&OB~=4DWDZa(1?w8&1EZCLvjk$6iQ zQETMV*eUS!+I|M$a6a6*hb5NTTr+>u<7C z&b{`r$a-xa{5@a{reHsr;b05W_6aYEeyL2%mz}DpngTm|Uw|RoHyM@Gh^fl;r1|!F zM0DzG_~3r!E#zTQ^FdKvOo3@{xnI!Uu#G36{%3x2b8@tCzepk;^*tQoB1RRbkC#wR z->Te^hdQd#5^m-E4Ms-hG5s{fqm8E}KT4RMrP&xpQ8}Y7*rWDuTiHdWtkdW1K;l8I z{Tsixyd8&m=DdG|&ON23}9!WJMa z=UkBAFJC|@)K+|b5zQ)}Z3u`E`UpI%q`0YUBgEx;CR8$%hHME-iaZ{XaWrapR zwzpY)cqN)wRJ{=T?at!!t6o9YMF=BKwQbGXVV%|d(&3jAH{MwIoxMxO5H8-B_azS} zs|IOnhw_LvvTz#%FR2QB7g#Kq8E5`iBj+2nr|CBG^Aiih&zH#v-v2N#Fjs@Ycf9R< z{(%tOO1GySh>&kp;c1t+denF7CjW7Oa5%5l*?L=a^hD)!?Jxbl<^A(&~npsi@R zy=?#G$s3n)B!^S96E4l&ed4VKHPbf8dvm-}BU_bu0tu}%L4u9orv&KuF-v8+)7-b#8Buces~}JdA%}0dJaSFCx$n}ABi)gPlRA*4NT*#US+)^hGAM0 zc9E53PkF+kC!_c^a1>TovQhZEST-_Q(bsH(o_UAvnTo?N#`@rFgwMPB+eMt<*#9=3 z$J%te@=fX}XH}nF$E0PILa+0gt&|EOd1~wb1cnoN?BA{X*k(W|5d$9ZjkdX2>gXM3L-$Kfl2g}Dn zf;jWd$#yv#^)dIyhw+S21cZSwhfa&fphi&SVu8o?pvX#3y`FpL^)NN@I5NBiANu=vh8B$xrs6G-Z-kUMJNp*N(X);9;zckAS$QTz8#sl8%?C)!P@Bk*f98{1)YAd73j zymPB>@HDRbroQ*^&x58|Y2AzfH@p4bnOZzA@!zyA!LziatSm>p97d$9ZjsSPP# z9xNXR8eS8*5Tc*K{(icNv$Kllk)h7Dl4LThKId8FkvWCCz@I%SOk=P1Mk*rSP2~@S z@wdy!3dm!$ns+=GnPf3(Y0)ADE09QT3rQ&pNh)8ZC6XU{FPKh7FHEkOGIQ3XFO+v# zi)|;_wOUrku4>6-(hEDEd*#_@UkF*>^5FW$ksk%7cocta7cuZSe}dKBb0;6gp|ylk zo(KLb+EfoA2uy)$k1+;mk^(xF>pUZpBo)Dss#8?7s{)*s=jBdTWJjZxxe~AWTJBP_ z*B@&}|K&c-f4NWVU+&ZXm-~$S%Y8Qfi+y?;-(E@FoYU7|j=sJ60RKNvJdotXIJ=dL z`6zx#*Ql2%PooG!W$ZvK>c5R{53xu!9y6mNmM&$CZ^`zuWiSi+bYCnz;~PbX__okD zYc1V}zxA&RN2a%=dRPdB_%kgmV5L54`TfdOR%_*4gR)cSMO@SX?ea|WoOU%ODjZZA z7fX327#CRayhI;bGC-^748&2;I^lWZnxxW+>nmcZLRe)5Yk^)L6ledf{s2#MMmms2 z4bw9-GSgC%6A`4Cc+{~4D_`$Jsj4?aETn(^b;tQ|Xc?ZO@{-TC?MoP1@{*tNW1?O1nyUQVk#k<(T8 z6Rv|PZ<1KjT8!F6yws!w?H1w3;)z*g7*)CBrVo%9+@{EI1CMh0Z?tGxKustIkxriA znMAoa%N+?cx$%G^Z&IVo$YuKGgA1|-7WGJMm=#&2n<8(@XqwTa=aeb;;@{@^8y>vv z{cPm6H?WcaAJ00#VL6+w_-pTc>mdCKhfkdndkcs4iK&U z>(}Y1|4Q#|*?J$Y+2;{AJ>3L3n|JS%4D$&f$KGb5LTpxX43~~p#;B4 zTl;_~J5{`F zGUQCt6}#um+PP!)++B3*oZY+T&fT#?^BsLbh%Q#Vmr$p@3qd;};-vfN zIuXk;kvaRq6p{b#t+oiz&L=VX_7~%6i6ybZi=(l}oliYUJlAPa;3U{ssSs4m@qNw(5fadt;=C6G%pSn$wYzoRrWYmb)qtB1LG4 zG!k)vE6V@E7lnsKiR8?6yK?cD*%7!yi2wk9?D)$}SLgLJf-U*#!7o{DVr9dgl+I{a z(h*MVyOPMhHWuZVjg5ltiR?pEt3^rdgM^cV3!@Jl1S9Czx?8QK(t#b(`GpTZwYHUe zEqhy&ee>~v$s*@3iQm<-Mp6}l15fW45kQqJ)}fqGRVe0vK`k}td5xZSyFJOCWHQCX zm~5tKV?;tGI8APmg6=jsP1(6oAUP#hj4ho9T2YV4&gXtx5tHe8jk7`z9V$27-no zOh?X-h{@!~TyAyiE&k!MLwldLcCRgwY!Fe!eKxCzk;i+CR?s6jMJ&h>AP+b?^>^nx8URa-e;7qRx%~6HFU| zXHNw1tZGku;)z7CYUd^j`*}(|D|qalzn&u*%#S>K+&AN>Qsd8$gu3^!zYDr#?VVr$ zM~r%Rd!+MQM1ZX7ifK9i(vHf_TNy{al?kU*)Fo&3;B zr~;v+|I${ey8YpzH{Kb~j+@!>Xzblp@)$_G145`g0`Gvh;EFeZ3&aEP`PiFm(Q$izW8Uvm+8jpw-H~Vv~xA$t8}~beZ=c@eeD_| zNme=-{m4;yoM0~(B3`1^x(nPw%YP-?HB?hdYrJy4wOycR zF^Wg5y$SRf`WY9ncS zwYz7e3ESB{^1!JNwYfS7zqC$|gZz_EEAe2G9 z#*{ZAsEhYMjPBV?)wya#SG;Hcd{uGk|BR*x@w{YJd!o3D+->7YkzsnNTI=O^)9vka zXR)uDr|u$8-cdI=S{$e57(IO)ulS^l@ZZuQ<_s0*9@%3rVjx+ne~K_Hm(FW-i%vq zbDeo_HeJND#npDouDMDeWX@-7!g+YY;(ly{O5dr85>KRa%6j2_2-ukXaIkxvlo3Y$ zz@|u-Pfp2L@}cK!&R2IGH3z77ajs(#b!8+^jmJPX?8fVCMp6{~wd}Ws4*@(Y3CsD= zOs@GT1Rtwpk_2LouASh`sguOj+MwRm|io z^yS_{iy>R{Wn;O_C!Dua+cig<^PD-!te2{yr?BIS`~Ts|sT9n{kiDF(b^j&51?R>^ zuZAPa@q}{@j55$2^QnbW4_tTeWlnN_P1I|!;(di!Mxk`~r9~;CaQ9k0GMi#r>Z}w~ zX+^HA9@bbK9Eo=eL10y))a%+zP(~BAiLx$QWlLQTh4o$6s#X`#gm9&FzA=oHYjq~8 z>InDLsDbJL7t>U`(tDTH4{3-3U86ut7JAgQX2RihZPqGP!;j46Kv&wqMk!mFp_ibc z=*)7jK#LHKth@Ket!Wg3eD$z*Du}Q`pRuNzD9h5dQn~egD#sQpD8X@M%%R^(VOoTj zyRthMZ((W&RU>4cW6wyG$;Kn!!O&IQEI66925U_v9juIerz*L|8@fYVc&7ZMTDwM+|da|F7a>)IVPDw3qN zY3W8)xc-9MS)Uxwb>>+W^>MZJ_!??r?LUaD7iTQ^K6m!+-U+vM5BEdu$vft*?$5IS zxF-%8(BuKmNJN26F}vS){N?^`xwEC2v_+HKKCY?DC{hT(S-$8ntAd!={(zwJ1oJH_@_Fm@_e(a9qnof!C!xxdBy+g2S|v75{iK^ zjD%w%0yB|B5lsxS#1T&di6oIs3aO-#P6nA|kxdS{n7+S7rKbfPm|=t?)b z(*p|{I#z7hDa1h$#q^{Xz3D?=`q3XJC6rP|IW7h;kU|rA}c`qjR zag<;DkaFJV_<2<+7z%fbXIybl~X_77(Y?VyOl5EM5 zT*;GsDKNTgn;L4ga9^FrVzHD3bXt_EFVU4&WpS%|%K@dP)Rk7HO=(vaDjmuqWpJJ| zpr+<(nz`EJ_Iax+D;jG8OS&tdUFonH%6;B?e`9x*w(2Sg+^0p|%dv%tIb6J$Vi7?J)?%{~L@a}arHE`P z7FJflPIGH%C1NF#%Jt3M2^XX35)_NKGynhh{>=ZgGY|j=FoPACtGB}?j9v?yEgT7& z3n9jr=5H=z3Wgk{DJ=K*bJ^lot!B84!5i~o3kBMO48IvSQ_$q@Lm&FFpV31&j&qpi z4zA-q9+G9V-E*VCBfP*mK6R~4DadK5bu4MfEm@UkUF)U1m#-@4tc)tEvhvkkwWeOH zkGju!2lPRGRD1f0UeS+rtl#R-CSywGyzyOJnYy{d{c&zqxl6$iF=*>@RasbXfyfaf zJ^oHiF*f3&*cRIj-EPG8x`v%}dmYml#wd#JoaYzJ6Nr=l&p4yX*M^m^8d-FlZepK-g5*vE){ZsV})VB}V9 zqsCLnl94Cuj6ER)8~pO_x@v7NV|RZw|DF<#;S|o`EG96CGAg)$Sk000310002gB$&DY0002UNoXbj0002hy%KH!0a=$}?*IT-5bNOp literal 0 HcmV?d00001 diff --git a/freescout-dist/public/fonts/liberation-sans/LiberationSans-BoldItalic-webfont.eot b/freescout-dist/public/fonts/liberation-sans/LiberationSans-BoldItalic-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..be693ab313fbc9f6cb711c9cd410c4fab513cecd GIT binary patch literal 37114 zcmbS!2S8Lu{{PG?yL4CK1yHe|UMK2mnQIpFhUZU>f|M|{a5RJLJ-(P&}?7XSp`Ode`aQ!~ULhx`VG5RO@ z8x={Cd?khRqav%kqRx+U;zKvs(Q8I!RA@3eG6uF&Iq88~CbT%hYYh_9Y!y3J+&&*+zU%?pV>SXLsL`DU2<5 zF=pJ=v1etGUqZw`@%|{@FYeYeD_eTx%po+2ht&)jTRY*x;3dy7roNB7>qDkZu}(Q# za2Rt9!~0){PZ%+_OVZjoXzOjp0>_T1ojd{gA!u(kK7B^qGjn+Qv@?4di~NqUL2r*7 zRy$OF8~m|$3-^hSCM%3H_PjvW@zc?sq^;|0e2|2AR#wBK-pDRN}Fr-Z^}R;PY#IhT$26XC0mxJWWxj z8jl5S%)v8Te4b~AjPJ0`>g#o%qQ4|OL&axrmZY3#Nro2scl4EnHlpaA$loQ(>hDpu zzMR^dMdAJZC^sGH-NrR&yLa6Vr1vQ2>)yva-b7pD@!13YW88JirCl6jXMU(>$7fr7 zwxmy*H|qRB`KW`Zj>aI;fRA02KTqZBK0>+`;6wE-sMj0c2`1w(t|92-Nj$q1R(B0$ z%u*Y6T#l<7hW@9aU54@mt8$ci2cMnrq@c_ReE$^hmZ{&eLg^YCDc}aHq;i`CvZ1Y*tK!~>#C5BnCM1kT9d6=)|S@x)v9628JXbuI&%JT()pM_$ zJ8|yVxg+QHpL^!qW9OEhn{=+#+0$QKQNQ<&8D0Hf{&52!?XAFxKkID!RugzBE2_ch z9Q58Hf-7Y_>P@V zKJnBuyLat*_PM?8ea|0w;ozYckG^#5Wj1N(uu*4cY@aaZyJ@4@@`s4&kC-loc+VX6 z^pOQa#?hCtbI%Q4xa5ITuYUIVSAYN87l+v^uU)wG&G{dGWHZ0~nk`T{GOeva-r8 zIcg94fbR?$Vjmo1v$&PU}#s`$UCXN$$HpmHQW$ z3@LFNN~&yb*-_o2UzN>ni&_?u6;(CJvC=y~O65~3zb2-py1F_Bqjmcg4{@^|Rc=;Bg;A=>+A$OB(0%IQA}hV2ImYN?rr&O3P00|qoMywzV(UulN_4QVspway z#MJhvuCiC#s;zEU&nmo&Az*m>&v2_wx3M^VKd@22)_||}B0JW|UR3Lr1`l`hAsB^Q zP0Mf_omLuZ0Oq2w!6<^kx@xMaNKJb&R3GPlV*o2IDN3_7td_6y_SN&#TjA*#LNVrD zV=Y-}uceg~5N0v73T|r*23bE!tf#%Uz21fYUsQJ|p%yAPOs#RH075!@(SCnF8R!vX zw^gUvGTedAeUen-9$MQz!yV+r1gutfU~wtU3kiEswL6Hu^}x3ve9LeLqnQu^5-T7u z1jBX*7uQ%<)>z%a09uAS#97v>YM(N+eRYyMaF~5YhTG&U>t0pXQ_qaCA>S}G}gIp+c!%ucqCNhQ6i+#&WNYl%Am z$meed!WCI-(EWkXP|ku`QPIkpeW8YQ_loqGM1Uy_b2F!BxWk?MIDJL{&h%+= z(z#Ee&nV|Ul|G}L`wa9Mhr8QQ0|?u#nQlISHb{m$ ztXVn0nW3Xs>NXwF?nOZhAUY7PFiFSIw`m(9azuDO5*CUDm}HP|Gz%`@D2&P+?| z40ry2D++`jg5m{OA{ODWW?DNEApmGQuUy&D-VubS3Oojc7Sti1^YAc0xH%|RgxiE- z3W%sf6x-*=irl`%>BClL+O5{sE74|)+lyH<_4eF~y{Nvl)m=lh#MQm(dC4kUW1g3i z<>=}nqOU$6)#x81uy?@D#%kZbO+iz5y3T;4;+mm$w_IF16eLb6u8l#urW$nN&N{Ui z8mPX#Lv8aIJ9_AVdE!I#g7)sx2?j!hSOM|CVyHkk@Qpi0f);C0*&zmj-{5ORL2va1 zWNBFs3oAyTCVOFFZw)ZDYIx=L0THoUJJ>r?r?iT#>)(mV=#X}^UR9aa*5KVVdP&bj zqYX>pHaPIDGhQ`_3Hl=b8S`rF-qpf*Gu&{i75_e9yeu)b~KQal59o(kk zs)`tJQfuq#%zc?W4Ewh2ZEt(TRNVH~b=%wex{cervqn3oyLo!!0`-;ZJ1la#Tcoc9 z`Vo$;#7_UKv0*_o-I*9-v6wQUak38iTDPCQNS`{PvmHAv6FX0zPK1L@z2siAv}5poQ&_>Wvf^sXorIpUjWZ?2xC;`8(*6`zlqt@ZZq)ss|Ufu5uS3sJUB zLwk$FH__f=JxRrv=t(NR6gAuE?Jd)jRA9NDqyj6P&-*G;y+m7-?)DkxmXj)G)U)vn z;1Yw9b*2a8N4^$JPFJfY>|@3vmoJ-G+np7p7+IS59x_cD?$g_+gHLPK=@V}>`gpTv zzyLJR(ACgR&NVnxksVaj&NuEv)QRvDrW3&~f0ywDGcsS45627Uy6s22kXieENql*C z=nKoM_Q^xr?@Oj{M||#OTyZTQ0-+g2piWi0LT0d$hAKlRL!q2$u&Ty@v_rgZiCb9% z2HyU8b*O>07e+st+yBQup2}0%33ftVK$b+tv_9(Qy30z}y31Oh#_!a(Z@opji+CRn z>%}kZ7x@o#947x!cbSF#hdQE7_Bwu+v*pyE`nEKwZid!{dPI6*-Ds^#!+W=Mz`&FX z{0H{3>gHb}{R{t!y$nl;{?lGHp;xE(r#G9yKIN;W(Q=5KD)*4*$h+jv<(o>hlB@Jr zb|~+wk?IWfpux{D+OXLu8G9Qy8oxGP^NIJ#_gU-P!S`O@_x-~BD*P7ux&00P&Ha1% zuk}CS|4l$zKuy4|fbRlp0(S)|L1{rFgYFA@KbQrl23G{n2;LfeI{4?1kdW+4q-evwFEIzDd*#2;z@b=-aM&w5edI4u z0a4?k_D3Cwx)|L%+8q-R(>rEij27#R-5)nJ?#Z|_aqq`h#ZQZWA^zut4hee`-nTlf z1FdtcC#{;T)V9{P({{x6eqwTBc4A>-X=4Axaf$aPewX;Go!JBIk@hruK4^bedyU;~ zKWTs0{)PRb{r4olq^3zHlip4GBI#n%?+(S`a=h$#)A6a}8^=}0_2hu$y~#(CPbYtv z{B`mV$=6b(l%SO8lzAyDQ#PmUNZFfmB;|A}OI?||Idw;1BXB*Rugwo2LijfR;w zTkq$TpJTF_95$1!y=2vr_!e!XT6ukUd*vNqjl{a)*D~NBwd-&tB99LM-{@4vBuO5i za9Qpwmn({5RE(jf5Y^9;&h2I!H``?X)Wyemvgb@Lx90L)@@LoQ^C0aPYV%8Zp?uEU zro|N|flKiLGB{5sR&FwxLKMG9y%n@1*L>NDn?0Z6w@F^e>om?`s*_kd?7|5uZ0jt6 zmAg6z_(?vd;E+JY$b5{$gA5W=WeLVIKFpu{`gST)xFmI!%41_;DNKk>h>t_XnCPfT zO9a?D`qcl0`o*PlIVapMQ_ps@JugQ*IkL^1gM1r4<=rKpUF-2kh4xOxgWVoJ+Tp#c zy&mkrb9z0}Yo}*?C!W_q9;m&rNn6C{(zA(|Z{!QK`SfhmUf|_0)xl~wQdy$@%xo-` zWwA4^K&K-iCOXj1*GD$;(lU2YMU^WFOq}eis#;p+%Z)~5kbx^or!pTvoDCw7b!O!- z7R8~g9cB6A|5`Lz6jc>Y(7 zE=*C4_JYLZ6h}mkIg{t*7V?||i!nRGqGoct9M9F<Ot2Ck}XLft$PKb$7zyO&MwyTB2a>QszJ3y8z;B~b9puARwVO(CwgGtm zxk}eNtE8by)_?2;ReXFp>-DgF)RQac&ovlfus%3HEc~Bq(Q2e*X2i#tLO5&Fx>d{E z?2HzfEm9nD&UmLKJS5f>>*vFQd2oU+WG`OQwQp|kZne>|a^_vLZ+!lmQuw;MX4|G7H7i~i`1Gdg zO1}Al8Gn?nU9f0umsQ%Fzj)zsfH+`Lnt|IDsVi6tI9VZERpt%@M9p~_>9LXFVWGj2 zA6IQ0ySy~!&$)_JTCG=z1Z3GDAVa4zgCcP*4U)0W-6X&|@zyKQH1CTt+Gq`hs$HQ? zQromj&2EyNoMerUiApj0g{PZC9E9KbIRwifZj9%a`~nLS5joifMuR;uMZi}DRFhK( zz7agkQYhu+CfgGY&G@e1uy0@Awy)prchk%BmJE9-V{^~4v2B~C6m{OasN=|$z2Eqh zA8k7)V@~;;+L2riTRSwTxMk%CKA7iiY&tJ@&-=?}^;LV~nR2BR z8+Zvbz90owlyKke+OHne=o32lwG!n6MGqD}bsmdRr=uOJrZiVpAx4K*56 zg=khF>xd#?DRG>}3PNNtrWjKSQVJ|`j#T(#`|C#zt>1LyWmtUi%j68>zOJ!F*hnuLsUH8Wu z@bOB>(1X;?SgmN5;YuT*4pIb>Q?X0=Ak3&6FAomlEZiIv9UN^`Ss)KI5FN9c!i;CXFP7GKSSF7jgMCHcWmfBE*t@Jpz`>&~I=ftY7J;ZkrA7!BIS z3z{GZdLv;9x$!JM%8^Fx+Y1UM{HK{2jE?-IdV&K|V>4PE&F+2Rz{Y!8?WnpZM!S~B z*_jXj-s_oTiyw7LYn`Vm*Djbh@}3EW>#Ay6J-_R@JvDnDpWk3g_BS5Wos5* ze()Pl2#@w3Iez5Suf{m#Hd97->Uk)a4?Vc!k(Yy&@JHv)d29TUeaU?*Ck#Gv9`IpO z-PeF;g;*;St(MAVBJND)!!cZNZy_Hs791oNE7W8%l28fx%~rr|a*mN3jNE3#!o)4;nUWk1hbhG97n9C`G;*OV*3_Uh zh7>{4##_tX(mi|ogf=Ve+VrWn)@Z*!`}$9@JSKF|z`5&&^qt*JyQ(g@r0ox}Mu&X! z^7h{r@7t3xpzOie)Jhdm)K+#DR}V_?xdnig11l=4DC3IBvJ2W*6JM=LEEAkK;9#h_{TeR$z1o_^?s z_d>O6L8V2LcctV#K4Ryw)qiLw2KO5?e^AfjNv<&N_fHf<9r(c!9}@Mums#G}0CfYHYOSM@t2^ql(eF zL-SnB)jqYac`Coit?SK$AU=`Ye=QNj7{FzMx`{=xB!rojxQfgM3CL4w z3g!gB1!N7Vq z#k@viy`z|eHFae~gi8=?OCba^S!E-DgZ0!#mc_1y43uO~N_Hd~{G!vj*sDTj&khX_ z^Wz4AkXTx?kXkT&3|wy3ZsW-9C)VEBa^`C7-pltL{5pn*#%=h1&d@HC7T5Hgv`|{p zrSqrmO@FM?>OSA2&DXwPvu9h=N9B}0lb&8%IqESn&T0&{Nb$o;7+HClI|5w9g4jbY z*0)S2hS<=gS;n{!I)-LFs@7)_d`QXkKakBEgweKzAD;rSdkSD6oe+rSLZ8zkN|-E89F zzC3)6BHuhNw?kZJ5npyedr3P=#7|;Xyktajyca3 z&hgAvDL7p$q*fhXo_n6R=Oq`kncBEwDYLQ5%q|LOIz<&So4k!a9<3o5LgR?;s&qGs6nT()4EI^&^f)RO<_vgp0A|xdDlnl)&?5yX6*~(bjU0&5Vv`*fCxmo`erwi z3`~NuZ2|VB_;CP_85A}Wi(oVuhhW$H41#h7sn^$6R|dNK0vpr+Q%P9{@=|et0HuK+ z<)tOX$w|q{4znZ0?k{v?iS`u~FS~cYh34mETXG6=WFinb*@Yk(Qj$GUffy62|cE7OP-n22M*lSvvz3F+`)O$zYc02Khv+f zBYxDI! z9yDX_w=*X#?zQbLfgg1crqmqJZ6l;|NIRgZgdV_1>>!;zlW|5DG=U06W`yi)fV2ZK zO16P5gE=FWtOOT$E}hc8kj=dRRjoyM7l?Jmp?svaMO|=xzVftdqc(}Zx(uDlpyW)Q zjXr~z1z4HoavH&x(JNT7BB}Mly3UI0#q1VhY>uXxU8d1CrUU&)>O zkE-Z&GjG_`zBL#hXG5XOo)xqi(n^>ak^*EE@L`=2gUz6jSJ-u6g@p+W_+T|#AmU>A zbBXsVI`3ut{@vTS-YCQEurUoS97leqjE-+*cK%HZz4uiXC=6 z@Bql!$?07B8k*bS)7tXyJumanb{8M}_mYoJyVV8So7z{Gwap)>3vLWq!KK~DPK)_d zzbnwMKdlYMA;bSL4B}-yPgfKbltNWIt6pueL7mDGViJi;He(VuDZQLGKPr)ac6vfO z$-Ohw1sAlf7qm4O0AuPSSj>&6A^=KULk0N})LfV>VFSbqU*=2A8+4J7T1FpI(@N

0TbMmx{aZk?Q_Vc~R_ulh% zr%Y{EiZp!GBib1=Z^PrR^Ti*$d${YwIh(c1FYJFS*LmT6&zUiO8esi{fOU#1344Rg zcf-j73JVCv?bYz0aW}viVllyh#A7ZWo#t6Pjex*Y#!8dM@>uPv)ZepHUEq07YIc1- zy3k=Q^+zA|x+5t`^)1({oLD)I72_!+sEUf5ML|Z{jCv-*1JcvQCIw>$CSrv+*VR8E zg$4x%_(R1oaj04(feOPSC}aVIc$*x=`MHvzPWTW`O?SwSPjJj92{Akir1_pLHz8LD+*8J&50xdjU}Ppj1F2;onVNK* zQ<-WqDX_HwDmo_fly9Y9fxtJ00K>d(7~Vx2kqql*_Q%!)aHxe&Wh#`<`hi9=m|Y_* zguT#UU~V#UyDX&;&E&QiUdRD<<=~#^rX}6%+RzUNCiun%@6oRFP`;`vDyfj~<14hQ z@|1-q9@yLN=KFFEx7Ykyb`zWP=4I`2#X`Avqcv6ukAhf|%jQG7=`LUkVnN}iFdc0A z5O_g8R*1_}w3U|Ny`tlRd}e@u=w5Ent}0`8bh)`0vtHGsXFqwW-j_u5(O|*;3W8Jt zI{+#F^@adoMm=c^fv|504~`9q1#t@ELAo74Cr(}}X-TZ#R79ub$GC&{_()s&uUEA5 zn?B;xzTI(Q&I{YOUzqzG%mHs`zy4br^|$+Y;0r(U2R{3Lv-aW6Lx(r>)Mwl$y|l6# z^Y;U-6t)QxafArSAcuNsCRUfoWuQMSPb%}{eqv0-PFNF;6*1Sd5V^-SZqnY->a=Bp zX?N}5wuc|ScZPOdU2yL7KeTIF2r#P0l*Plx5S<>XO%U*iW9e-8!C*fL5(=ah1f9AP zKy|pR@OcK|3-F3sz_uC?qAq0DL@=~o0*HwnHndSe8DdFzLOkj4@#zVWG@-)8@i-xi zfjGf1EQ1Srt(eIvnGncAlkNu7u$akD|81eUUv))?j83($ePWq*V0PhM06e;8+1Y2t zh7Ya^Iu&*FE%UGkUY-0`us$qs&BB_y@&Y-eekf!L_Jx8Fb5)fFV0Iv2UdbUOm=4+m zTS6>AnIN0V<)ZfF2<>k?R9dy|{ItIv;h|d7`!~&>tZnQr zMRKXeF?Hj;ba``H^ z+O%^K8>Y=!n>%#lwk`ddYA=Kxd$rm4;jdQ=$Y?hD!SVY(fA-1ZzGDV-&njw`>g-Z8 zr~Bum8*p|%c;IBf(a3DBc%rY|3pl*h>6|WZ(S`+j5+yat^Dk$#wkrQt>k`U zcjD7LWp{ygQ2SE*T{|1>vyrDf7`pzWE&Ju6H}BUjY5)51bLVV_Y`FPppUM%4`Q~gl z=J751_G2-wNNC~>q(`4egh^=sw8P9179x4lKeT7h^JbGm3|TGsw>%f*Z2E@jqi4y_ z3Ak*=Ss=21M-W^>0~JAA0UyFCj!6MFVG*VXcrc^V(P|ddVZje0!rLNY&JX1_b-T7B zM&Yg7YP+;Yt9fLSu|+u_!ds`sjKDDiyX^d`*JE36euIubczXYRhh^7|>7BtzaK;7p zza5y*QS|T6tgbkJ9AQ#GPD`ohPGw#u5gHmQT%TqLz~DT@fB08C?;KyxTX>%4HNI6aJZ}pC?HQ^IVqB5xn-G{h!gk4~un0sbpTPr`0;Z&^DQrn{d5tE`6+h zF+p1k{w(t^c(JFKR3x|cJT48m`HC3t`)JRO_Iw3DyM;x%(VM^(YSM?>gu|i+EaEn$ z<&Dj$@;1+?4Eb8>&4F^z2l5-2-np4W^M4cmguc+hbT2aIO|~3pSM`R^E;8}pTeykI z!vbbA=JQEXyhFRJea6EaS|8)om#_OA$9%`b4jrbx1Nq~x?aVm+4E85CPxFU&VtwD})x{nVrps{Rb+!RuYSQhhAWHtDMRHOVKUei$Lm zB9I4hx)8!iC^-qlSU7CjMyZ$f4p{yvgR%`Fi`uya@FYLTZUstLmcg2{*UQ`qWc`n{ zUJaREFefAT>-G1QSa4GN@;>|iq4%hUUOT6)ehTM|;*W>VAK2fd z{gL?7>GxiV?C28~9c_%a2Br1>X2;ZJ-HLlIkMZkO;)q#T`_!Vt$5eSx(!HHa67Ji) zev@N{wk;zjGCDvuJ{$t$kJP_chi(hVcP%kH;Qci4GMY7EqYwCV2!Rj*$mtRXK$ZfA zF!KnbZFga&tfA$Ew)c}3wIapVg{eGFrCK`uHW_=E793o1`R{6sdfcNM;gyWVL`Uacz~pP2d33-?}noa4Bj6cEshhhGiz z)h-2J?LMVP(}4qrFKGYRfU385ALrwapAM@iiY=Jfl4~CBs>Np8oD&$1O&5(^#5;m;HR*A?+#v&6U6XIe}&*Df*B7uwR$Uv@d zVbv0#M|43-fv_1H`EcWaejAE@EBpF|Pkz3S`|kbVeB@<$NJY(}SpHRV&ghBHlx~}I z>Am-O!IPi8I%LXA&BqL0axcIH|1*p_m4JT)YvIZV_~k$jmI*78?uG*LW3xcEAw>aJ zH$g@4JAy@+lkCuIeaNH@;lN&yQ$SnUV$8%4VW7`3N+El-@AhtWY>kLNwx@HCB!5?U zaqGa4PaN``n~UW+FHd}Usp){^<7!u_+dJ$2(E2DRlrFF@WV7k6fM|dCfJwBXg%;Lp z#LHk$LpqeP5e)_bJnS}*{Xs_H1-#}lFtsQO21*5i5`R?=!Z~L|rxM~KBTT`-?<5XL zV^eyyUxaDEbf^b!km)c&;0v4}upS6QeGo8(%!vHC*FTNoF)`c6%}NaOm)Z`R-sR!L z^Uv;{wkt+LI41Ws$3Ky!{jz=J!15s_8P8U)@22%xd4K%;X&Y<0R(DN1v-1r;@wo{# zaa(+PuD^NU`!|(7Bd5F8ec$=@r9$7e!v?$t>k~zq5kLW9hAb_Dql;*Z?htG04FXXt z%A6G8(CsZoAh_=Gg2HO{l3UrvrRr`ea@m2YTOJBB!f!Awddsjq5!1AbJRSiBV-AiS zx_)KU*m%!mspp&~3wBQT>_G3JkwEDFNRcfZw#Eh3NKD?7Pk#O%cCyGJx5*t(bs%bDH)(l zyV2pk2eAhT#%C}$f3ormy9F6bAW4weRQ4CP3;CAqLi+sZ>Pqc9Zsk!=@eg>BmICST zpJf{4u$wq>ul4olboUkv8^>kb5eL9++)gcNXVtf_=ZZawdWJLD7fx2|@{54R76;JK zo`?aNjF5=8BWw+#E)d^Hs)&dXh!z_`yj1}>RfRC%eJS{KO0q2>CdwQd9OUl{fI4+P z%|Xl?qYDflHt2XdER7mh%6D8;l zBzTI30w+qcJM6ll8MfU9&Z$EFftV9QL`2#Cxc14>FSKnu`RI?=v@hPDw54sPaM z_r9=pQ&L$`hoanq_>67y9^18MLtIJW44czxYqACK>s|M=^t0L=J9F3p3pWr!r{mwQ zc*K}V@;Kr!5}YzZurU~P83NlihQxkJUi4S*h3o*E7@5sQ9308UBs=Ijju3@%j5$WT z*VPEiF`qGKdC2gMjvXu9P2aS?GtxgR)mm=t9y0Zwj=iOzmo@EW&-cx3nHhlxBQfsE zx+}o#mR|ZtEMF(}WhA}CxrJLu`!Ykm(fwH1u@Rxh z0vK3$kW^J$o}8N1b$YAVkGZ|0Awe>&g1O1*nKWkE2CYx%tG@k5HdPX}-~{apUk42J zzuj2#)be&>EZ2ZAzhErBtkJ0h45Hp%gxCy}gCh@SD4Mdp-Uwy%OO}Rdol<3<;@K(% zr@k&d{ni_v{_kM@%&BVtRJ#^Ah-lKO! zYxC+_URtfT^DSy#0fW}e;m#I3*KT_V8pc`PWBtaL2Tai39JnM*F%K(?vuOL%67zD8 zW2zYcahx=IO~|^9oTxpwWgTYD^9L;nlg%Sa<1D;O z25je;ImbOgin3Vlgj=NA3fom~s4qfI%oMapp=_MVeZYB1Akzc4fTS|(2J>`q0O2SO zUlERyZHu%h!C3Ny>I9T62nX++S$2Qfvfdr1x0|@N;=$6TU6)RBO_h|i=&baNPkK3{ zv-*GOOcFQ5&^oXX1DYpZAEq6a%7LT*w>TUNe<*|11KCDKy^EVXK+wq+ z-?fYa3Ji%UHag54YNBvyW`muTR7|X=N?2?!5G$I`+Cp1~t3qusr8tBsCHIosy2JX! zfzakW;H!ik*0sC*>n$q06HF@4?DBtVZ}jKSjVk?>tNssdw;Wm5$hN}k-T^(^48Mwl zJUxh=loSVciv-mQ!GzpMvDIERA8`?||B{#6O;Bb z-!f==Bp>tqp?8v(jGDcREAJiKwx($>L~v-o?wWV1b6>=)AdY;O(n;CFVh~G-ofH7w zM3xLPi;l9GO(CTJmHNS?-zhRw;vESaB|fBH)b1{qC#5=*Nvtu$rc+=6@>vj%PMZob z=>{AZfW;)aK!gH7@Blg4)OyLIi-t{~Ie6LD<*i#T-MVDx%vpn%J-V#rH?#Uw&Ysn~ z_iSm;jA2V2S<h69&`9VLt- zqN5b{Q6q>S0qls>iU7$IkpcJ>B7&9(qT;6jW^eidl?Q8sl`vM@ zYvn!gJ5&2PQ*+1i?pE!&SRQtc$3DcD@?`1x^*c6PzPw?_dg)!y9;uhuHzz>HPXCq8 zu7~)65Zy&*C*sEOoB)Bl&JEHz>9Qy6#&qe52P&9U_i0^>GDqFOvRG@1OG`tLo5BlX zV?wce$aDnWqaZ#Ft{+(%LhMWrQ{$j{%@EKTWr6!Hi)ZN$TrWNesqa>75DbhB0%9gl zfuBo12x2C~Ft!SXZzW6bzWt@~*MJEuo1@o^#Q${aen={}M z?KltaSbA;bh*{10OdCIath!wJJJ z!ySy%0C$0?Ac_S!MjIZfdHUbdJ=4-YO;?`#693S)%nIv%Rxe<$2E$g^f>nstOA7MS znjr8}XC>IRU?s5ItA-KGh+r?-NCC8{+{e2CK#XnH-0Vzeio@E%)*?AM+3t(IAo`}T zBCqEj@lBz|#=&pdNueOv`Y{{t?)F;WVQ*9~yrMn#%4^!QFP-ESubkxFPfXaUU3mOa z?YrHN^QfIWdFpetAy#_5A|ny+ zsyCa2XgOI@WCUn86vjsGbIWU@vTo!7X;ch;6Tn|(#y)5hWre1x_s~uEfHccWZ<1su z4@htz6lHj-eFKDLG4Gbd-B$8i<1MXxC_DkE(q=G3lY%d zYYGfNc^@-{06XLYFfDFIP?t9$eRv{>7yo3?UOcLLy535!;-fn9@lCY#+P0KNN$}q? zwnphHM*)mUE*J?c?~1&J?>Xn00$IU(E(fv# zf`qY5jrUOo%V&MWPAYeD1r*uOD8ZnNlNa;QHh3U5l!5v98hl1T!i51rf#dA<{baS~ z_<9rZC!2{y0zYzicQ~w$t$>aYn;vmMM}i9D>~cu$#1!{Zsez(g(NHbp7PZ zvXUkD6&3&2o%CIfZkij%=i6O#H>8o6W zP8m$n3*>0{@=5d*O9X*n7#sk`0d!clv#~rNfU$t!0I1}spoiP}-El&ULb9bZ=d|(s zet7yRGFv*O?dFwdc`t3xSx=j@X9-@o-|~?3sk$Dzch`e~0p#EJ3W8<^VV=Ujsn{T3 z=t!~@L=iYKE)Wpdrz`8#CQ0s6ElzB~{1}zgL=C_pGe}YRFT@UG{iY96!0$UorrtZQ ze;22{Dtbr4%Gr|_XLqUT-!*~{h>{M?XgjhnyD}#(r8aR$zhS*wSC`~BYZi|K8#LA$ z$t}j3#D=&+Fjy|ZILxgH5(HN7sH16oh+HDhp{U9b8X);aMpF2a6<8r*PB?y4UkKr$ zM9|22tSa5AUB348B)iRK2!NL-SJy?zhT1Rz?@0_2t%PFAe;(gHvUOp3&%RNQN3UKy zZSk-k+|_m0{nug+nEGY5lQ&e(%g*RIFmmv~>b@O^8>N6QZHLWyEow*}&8JS;CY$~z z-#K~iU}&f_{F{gSXv>vt3EY~lT?W2?0Nr{rbOi^4$IT8V8Gs#+n`j~QQrI{d`dAHQ zUl?Xd$%Qk4vqWobOk@O(LI%TM4gJ__%V~o{fx1$jCDwAZ_Pf#p@?$Vd2fG0$<1(8^V#PucN~#PXwS$u`j>{gBNNlV> z0!eg6=iat(`MpO@J^akc*Y-+Nm0zwK)a%y`^2>*JJ#!ow!*NHbS&Ed~W7Yr58Aei_ zj}R+3p9M?~E!_-GId0h>%GK?&Lf+_GCB3QmILCnUalbmD!8h?C&QNkt67*7Wy< zk{HIrNbVy#QE$wk18YXeMIxH)WOnnowAAjU1+SourpS$X_RE(#UR~=V0B4IGMf$UJ%wG6HZo>1vWs%A2M0EAm*XK%nL8)FzHIxul$|1v z^K_=N{E(-!w=C?7Xm>r@of`BX+XXD5wVg@~_CKAOK?FI@%;=^VU?>NMLJvcsp(F{4 z12bjvUmcjyIT#%awc*fA5TXdlih(P!>DIY^D z^4g@{c3?(Zh;Z0nIBe&*)mEI}FouLC!{g0DjO;QFOx!L13cu7SKfR&+RX(>Nzu1_d z=L6nn_)EN>4ny8ghoRn=;gg0kqxt9DyC&SbCf@O|hBB}7V{9Yy!MeFOQZvA{x^0N> zqcbPLpae6@+}YT5BppL`!r>2yi3VRbjPA~m^e|E|X+8jAm)9GS$$Y>keJ0#l1M49T z#2$iSrE!I77s97-Z%7CcJ+G=v0vN(6i6BGv)E{mq3vfvXt?(D^jQpDNUWTW*M4F%O zSR!6Y@Q~nixpiw@pjb8pLR-mE0fIITBHlz7&0e3DPs)swZDNPZ5Ifd2h@)Cer z?@J!uDE~^cTlrm$)R$iV>Zx1#<)Sa-Q(r9P_P&7n7R+l6)-Mni7T8xnEy)@rOT&Z# zh(fx&YXAZQ2NGu#ZVS#BBzzqPIutIBVNwh;om3%j2iOIc0<7`RCsroko3>!eem=~p zJ=*b4M^~gCUrN0^d%o1xdk?U9;roNYWlUD!@>c?vIX_g7%X$pEfJ?S_-^>^A60z-# z@~=qk8|4$c^zv8vLyhuj-yom*0xP*=-{^e_nCfLlgPz>BZ$MG!v8QoPa2NQ3;dy_R z&K3Id0$*-{aO%tF@uAvdTX_wy*{VGzerb>J4tzMTc|_Ym-?wTz9^->G42kRj_PVzW zv(zK7!N#)`_K?er7*mBM+Tvm%kc9+F5;R}^=9(xyEDh!v@9|mP^+y+Ol(|EM2270c zR`42(d@HpIpsgKR=84b6fdR2@B3@dob93+d7u_}g3Lr!K zNH0(8ub02db$k)|Vr`Kx_T~q-?-$hFgZ=We=&QBp>k5af2>XcWLnii709Fd8NRR_J zoH`tY4brAeC$6xMggG?{r{7??M4S{Icr(KKB91H+z;H^+fQ^??z&uZJUaLR1u+(|$ z$ihF)(V2zw`f&x|8pSF;4x3(6HrDI)x4;b?0Xw7c`s-itXGZFM!WNQz24N&3n1v4# z&O#Bp7<20lMHC^9$}7W&s6}un`r{xO=}DP(Ix?7J_FjshTPNr~Cn5CT?!>KEaNwq~ zpTT9ZKOXpCUGJ9po#y@h;9Jw$cdf~;=-Ir;)vBax*Y=$|Oj#2Bnj&BM>Y=4ijB&Xf zsqOLx?s)2Siz7+ly*lM|YnC~pefzO3TMcfR*ZSVYH)X7ah>KOf#yVnOL*p+Vi9!A; z-^ix%ghu&)c*`5vX(srnXs4I@YD4}R_I9K4D;x4h(@ppF?He}f`M{Ic**EZQ3BK4w z=Zi8+!?~Kz;HyuE&7qL3cA1h8k&qA%xj4|r2*(BOS~`I32=td^p8(0oWFxG>(6_v? zIldsh92ZV`BTLh8zn1J%Nu>k$e=c0@iY+K$te~)4^@zJt-+Eh^{|q z6g+6vFU-jcvE_K}PawLLk!p@lFr)`u2qpVj8f-7tOiQ&L>xINSlN z-$c0K&D8A^?=5ME2wYH=J0cIitQh2cY7w7TAC5b5B1Iua_V3xL%YxB2^pD%|sB5R=s_|U?NfOJJN=%Xk9K5(O6}|Uqu)e6C__t( zXQTQh>ZOuQ8Xbkh9N@%Il_a_Xq?0&U4(|Y=v+xnt2j9lU+HJ9^aj8jZp|l5ia*8d& z;t1vuq2VD6Ch|P|hC?PfMJ>QRU%CPz4C9;T4CanbB|OD?|DfJDM)ARy*KuCW)Mlx} zIzao;wtnT);|BD(zx!x4YG%vi=40LYv$eYI;6&3dy$**;fg^hLobk_VIiI_S&z$_m zw8|O`4D-hPl;_00=`P@OhNp;pg6nnxrye@|=tc?eIyBCo#_wsAPwS(XKg|)+(@>uF zrJjF=2Q_mC&8l0k~sK zQM>r~kagb2-v_SdMzDOU-9g7G8CiZ(r zDV_0>ZH5%rMrAN#Z$k~d85U0m%*+@|cyeg65g208xiGoC8u4ax7#K1In>lo*T*%WF zjiXSrZIfCbTRMP3&sL6ZIoF53o?V5&Gbvor+BYjj_*wFnNyjPlEWhNDQF|y7tywYu zBs*OScFy>1nl;vQGjZZYtec2e5$j*&UH=tg-R{hv=9R7Wd|H3K{AuoJRG!vZ&mYa> zC|{445bZadv>_#(V)IGckPWv*u)Snx9Kse9+8xH9i9D)j=wC*#^&PC$8mDTN{b^3e zF!nFhWteOKP+eC4sm=qP4SGSHM=%u;2Pp*!zqcNTPz+H;otyPMEyi3u$LBM(UqIYYR)ZdKT;R?yGD{hi((17oR zu&eq1P~JGej||)OFbB57FHg7cihr)is7S^TxsK>WIwU}vacHtFfHY%+9hzBgC<$u` zjWlFOb^&gR1Pu@|iksSQ)V|S9P5qX)KGdyMTls*~Gps}!nC{upS$Z|&#^&K2Uq~1n zKL6C}(mj6oJ3SG*c&jUORfABSS6uo!Oy1__+jG}%^LOqObQt{2I0&+jjb%dm*{;*K z;AmX>NJk!2icutY9ddjc=fF`%w>{c%bEqFznXd}Nfx%E8-%kPkjpU(>TcvPaNPQt# z3+j`%m9KV1XJs*#m7kTLn~jTv>=>I3QMgG-0pu2FBoN^SL4ZZ*E)?5PF9+$dxxymH z8ziAs4U*8mr?>xdr5>(3cW;L+mq`A}qM+T8qb4mZm43Wa0NPU4MGxXFE?rBa&k+jc zowID}537an0~!jBseUAMk=EyRU1Y@Vx(N6|Iqc)v>K`z(*n|x>0%l@8wn|zA7_%v% zW?VsM(fw0KJIvOw>E)ox<|mckpHaE%TjizL6DMMMA(joa;=D3JI%~KGywmdE{X4K* zHu#e7uw8mZM(ON^F=IAt8Z~Ou{Lc9~rKLG}rRum*ns7u+ZZr?gte-QG#Bpkm(pPQXzEe8^&Wq!BGH>a55!kN)!%;JAiVs z>D*X;K@M)kn&9kS);>P1AZmHasu3AION-*tn@287S*2`C&PvYdv~?Linip?fCdP5= zPCs_Xt$mH}`}@DWbMg~Pk+ceu!hf-`Q;$xoe9g*Fy!95X2d}%rPxzSsm%6ai8qLW* zETy3?e06Q5?kb(*t-lw60z}0PEM?*@4w$;lVRQ~sIHCY6V~%_*IkKwbi{-3@@ThrE-6N+%6IBkDG>ZYF*6Nhwj`Y$D2N1ie&O8rBTV2 zKII3++&wwB!C~s*?g9>t0CKB;y3Yj=CI<~$O#SLdqu`n`g@yqx;+C5RxTp;&*>-4W zf`CwDwdUUdtzL55oey#4#Jq!D32G;19)MhPO*;jrI>9XbR$nmFd1opB7Y5krePK%a zBB;0S!;<OS1xyC9nJVAtUR`WFDT`>rAa>cQ?^case{%%)QUfnzeg3ngQ0sbZPiQ+;vGZIpSMk zi$EI#d=Q>tKtweL{~ttfx&>1hck#KT72Sq8R{S0%e&c3La!2VdDU7Q#TP3X!ehMsrE80wU+0iDNqhMxC0q!Gw!a}cyCDb?C z;5y;k|H4Z`@YN8JCDR=jGNXH8Z&`VF1?FKR_K{R8&xqc#%NE(@>O5<8WF4IzFBB;SmmPCt2c7DR<>>( zPmjnYy$m6b_)CPx5X+Rxs$7-H4y;S434s@g+~XXFG(echMF7Jj{8GaL09*e-%->&_ zCk=zZ(z_X8cTP&g6=rdCcX&itNDu|u(_uggrU|4NN|1gSoy9Ms@XUy#k&+4KXb`1> z9GsA}nsFVrY#%c(iKoVC9|fsAKTdmJt}0*F;$Jym?fmB4nuPhje)FtLOL@uZGwNXcR&_9`41mGsFC190yrN_2^gq=TI%Qv@zRz?BYx2(Pr8a&WIgTio{qFK%uD zG!46)5)tmeH#-6rltEAazA7T!(&WB>J>e6d`OuqCq-jxwoMELt^^VI7J|4#prx_J_H zSK)7F3f}Y=G7!+WUjA97ZrZE-4Ed+vTZ1L;fd>#yNn^nEuY8;iA*6e=z1}qbU>fvM z0XghNe_ZaXrxAn7*Ll+li{amR(<%#+>iD=eRrt%T~TxEE0KrVT7xO82IX>{n@~H|@hxtso;5}ScSq;x-x}}F;WDm6>%Im-z*-bhI zN`L1L!!F~)v}9>)72pr%Tbom|<+n=CH(Fwz%{25E50tD35lI>wEflW{QWr{;C; zCR6EV6YHKR8m)U~cN90H*hzyZEO+d_yjsN^1jSw_?KJyB7rfUvy?Y=S@iJs)$O#jX ztDNQC8`Z03U+UBYS7aAQfVwm#M4WmqxREZxH7M;$TraG8F&o>iAi6ZDxf)ZK;V}!C zT=MAgBVL>!KvOAqcb9qUw-^Jh6VAIXkza7S`5bW-T$j>r$!9Ta1~p5NlB!;6)kkI5 zARo&~u0?!4aVZ5c)bp%5C%~)JGldT51;gy4%!%w}QU|PRY2T6MWWpltuy)ZRSST%<(0S?w`f$Q0et{0~cm7~;0h3*}TgQOa`Y6JJ89}A5&2#!BowPR(Fz9YDwGB} zwde`Nr9$;>TI;IUQFQ8O!W?o0&tsM(tX+~RjMC7~2%#`;*$XVQN=?u$zvWh| zesi+ORJvyAD<>+UCit}tbJFXAL)Y@JDx0;(fm4T_Jpt5|j!H`9sixwP%G6)gP!-U% z?QAJ}rmV(t9O9Plv?{B<*#lN79L1*(EQ&Fc)_k*36{uT&wFNEQ+K1W#y_T7+N>*D{ zC$E%ZTc)`P%%|5$Fh$(qt~h8!oo2!hT;EJMiK%)?Fd9-gGHDhX zf}zw2vEMKnXD}M~Ix*{7>?M8>j+z9QXc8|DYB>s%`cA5jq2Z-*;5zmCBx78o3Frue z+C-50O5LeHR?fJK zkKbAprhrDqsa9z-f{NU+hI%l@=Cs9}7n>SBPOq88`Bz2hylwnFsUnE*JE>+n1t|3$3vc=gzU6v%B zPBy(G>K&1D$W&s{#6_aGY` zw;3NoRo*c>wa}g)+BaYZo2W@YeZ*A5R_HXX5x+-=`-S4lJWv21YH?+PdY-cFgHd{E z5Y7fJi2)@HnrTu_f_gcMM>h}lQ^p@l4zm|U{`Iu{#^x{o`5U(p-1#y?<(UXKY@BAr z-9!f~7R%TfbtBu&R`OoNYSwzzum-c1JH1!%7Wq1!;I5Yq?990tDcmBr%B$E1@@k$w zy_VVPCV4#%-aGMgW%$%NYlZh$w#Zi2#J92YWCwcoM&6iu6Z-?+B6l!S^)gfK!z=dV z1^3beckxEV0Pj59O)C$`+nAvaFzJp8qsm1pU(=jE^R zA*LV!9#z#clt<~IchYO>ZO7=lact>1&cHi`Qomc?!?)MJEuWM3vRCSUd7u16F35N2 zo3C?v^>^82@mo2^%0Nfq25z{QbHBHuu#ykKuI4i;YxwlrS~)G>kSF9@@6^wiN750vC8Tqy7 zE6Z5?Um)=CTdl*E7KpP{2Rb(_ZSG`Tmvy7DcTzU`-bgHEyk&^qDwM7Su$XW~O0mpc zEx2x@i8Zw-v|(LJyB%9o$}6#`0@lv*qC~AdCCo+Gk|t(7|4Q zEkE;VFHC1GRi5>F@uU}ra-+_Gp|3QR!;DKl$_4+K7r36%_1N)tZ1NELo$CHqY8T*4 z<2EeqG?wvPo_hiP>@Eysxx$IUINy+bJkOnBhwa7UN}Fph-c;OK94_8he6;xe;vY)9 zbp$rH_LSHWOKN$kzcf<1uk`8C6QyTL7uq`7Uengwc2C=>wug}!HQFB1yJ-L z+A0ltRVB)r;uTV5jfgVcjjqqj=V1POa7*#>V#{9YHY`Q&r7kOBz3Z@F?fJ`lFzdzt zof=EaPFq=|l`LWN*V67*teSl78dul#rD^{kZZZFGb(s|kXRXIy%}i%IvzcSe6YiIX zm;?NgG5BENL5}_UQsFfJXA1WF-m}wk`|J$8x@J}n{wR^(MrsFP=j;?^zMLm6$%@$x zvWl>goYtGk>rKt8=R;dDtoZi}vWl<~=o0m2s54{ftOAeE$y-z4@?`_zCW6+hP@@mn ze!^bD0QrN&hl!66A0s|NJWN;)mOijdgQX8F(_oplSQJABJ$(sUA%_VHrL+}FX}6%J zpgRN93^XqQHDhVEpjOznX7v1=-W2qv7Ns`?);vC4S6pW)eIC)KQ?$+w1x&(;DWpo0z@c#;kRlow44|y!5c0dk)Jvu(qsI&Tjma ZJ~@=9?tb77jh}sT^@tRXKjQpe + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/freescout-dist/public/fonts/liberation-sans/LiberationSans-BoldItalic-webfont.ttf b/freescout-dist/public/fonts/liberation-sans/LiberationSans-BoldItalic-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5d3c89059a4586950a5ee47b9481a70a2e73dac9 GIT binary patch literal 36880 zcmbS!2Vhi1{{PG?yXkF{YEGd*Egc?Yw3B5?~U3v$li46p?0pbP) z0YOj{vG;Q7DJKXPK+jW7fqnUZzVkLAAl}{YFTUN`c~ighoo}BBQ-N zrnJl&#Mp*Q$c^b-Qrd|n^DxF%Y{u`T&ON&K?&&=`nX#2l#*BM9_pU1Pi4XrL-XFvJ zrQLgHW=Rj7Im{Tx!)k|(ubXs!=(1-RQ{O}0wP7=+S*CrRe}u8{P`v+jL)r{QoS(6Nll)g