diff --git a/.gitignore b/.gitignore index d7b83310b6c..3baf1885e8d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,18 +19,3 @@ web/portal/client/.history doc/html/* doc/doctrees/ - -kamailio/users/config/kamailio-selfsigned.key -kamailio/trunks/config/kamailio-selfsigned.key - -kamailio/users/config/listeners.cfg -kamailio/trunks/config/listeners.cfg - -kamailio/users/config/pushservers.cfg -kamailio/users/config/geoip.cfg -kamailio/users/config/multisocket.cfg -kamailio/users/config/apiban.cfg -kamailio/users/config/siptrace.cfg -kamailio/trunks/config/custom_settings.cfg -kamailio/trunks/config/apiban.cfg -kamailio/trunks/config/siptrace.cfg diff --git a/ChangeLog b/ChangeLog index 2c9b4ef159f..65b4720e34f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,42 @@ +Fri, 13 Sep 2024 09:17:20 +0200 IvozProvider Team + + * IvozProvider 4.2.0 released + + * Portals: + - Fixed a bug that forced portal language to english #2706 + - Fixed a bug that prevented image preview in edit screens #2744 + - Fixed a bug while filtering dates with zero seconds #2757 + - Inactive administrators can now have empty password #2741 + - Client: Calendar Periods are now known as Special Schedules #2705 + - Client: fixed Holiday dates importer for Calendars #2729 + - User: fixed gs wave QR code not being displayed #2703 + - User: added a new section to display on-demand user recordings #2725 + + * Microservices: + - Recordings: fixed a bug that prevented recordings on call-ids with special characters #2708 + - Recorgings: added DDI or User that caused the recording to start #2713 + + * Proxies: + - kamailio: restore support for standalone installs with one single IP address #2748 + + * Application Server: + - Fixed a bug that prevented on-demand recordings on incoming external calls #2716 + - Fixed a bug that prevented on-demand recordings on simple huntgroups #2759 + - Fixed a bug while processing outgoing faxes #2723 + - Fixed a bug generating BLF hints with unused terminals data #2755 + - Reorder how endpoints are identified to support single IP address environments #2756 + + * Schema: + - CDRs: StartTime fields are now mandatory for UserCdrs and BillableCalls #2739 + + * Other: + - Added Cypress tests in platform, brand, client and user portals + + * Security: + - build(deps): bump braces from 3.0.2 to 3.0.3 in /web/portal #2682 + - build(deps): bump axios from 0.21.4 to 0.28.0 in /web/portal #2734 + - build(deps): bump micromatch from 4.0.5 to 4.0.8 in /web/portal #2737 + Thu, 27 Jun 2024 13:00:34 +0200 IvozProvider Team * IvozProvider 4.1.0 released diff --git a/Jenkinsfile b/Jenkinsfile index 0719271cc9d..effad60a3ec 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,7 +25,7 @@ pipeline { environment { SYMFONY_PHPUNIT_DIR = "/opt/phpunit/" SYMFONY_PHPUNIT_VERSION = "9.5.3" - DOCKER_IMAGE_TAG = getDockerImageTag() + DOCKER_TAG = getDockerTag() BASE_BRANCH = getBaseBranch() JIRA_TICKET = getJiraTicket() } @@ -36,9 +36,12 @@ pipeline { // -------------------------------------------------------------------- stage('Image') { steps { - dir('tests/docker/'){ + script{ + docker.build("ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}", "-f tests/docker/Dockerfile .") + } + dir('tests/httpd/'){ script{ - docker.build("ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}") + docker.build("ivozprovider-testing-httpd") } } } @@ -49,13 +52,14 @@ pipeline { stage('Generic') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } steps { sh "/opt/irontec/ivozprovider/library/bin/test-commit-tags origin/${env.BASE_BRANCH}" + sh '/opt/irontec/ivozprovider/tests/docker/bin/prepare-composer-deps' } } // -------------------------------------------------------------------- @@ -68,8 +72,8 @@ pipeline { expression { hasLabel("ci-force-tests") } expression { hasCommitTag("core:") } expression { hasCommitTag("schema:") } - expression { hasCommitTag("microservice:") } - expression { hasCommitTag("rest") } + expression { hasCommitTag("microservices/") } + expression { hasCommitTag("rest/") } branch "bleeding" branch "tempest" } @@ -78,13 +82,12 @@ pipeline { stage('prepare-backend') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } steps { - sh '/opt/irontec/ivozprovider/tests/docker/bin/prepare-composer-deps' sh '/opt/irontec/ivozprovider/tests/docker/bin/prepare-fixtures' sh '/opt/irontec/ivozprovider/web/rest/platform/bin/generate-keys --test' } @@ -94,8 +97,8 @@ pipeline { stage('app-generic') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -111,8 +114,8 @@ pipeline { stage('static analysis') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -129,8 +132,8 @@ pipeline { stage('codestyle') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -146,8 +149,8 @@ pipeline { stage('i18n') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -162,8 +165,8 @@ pipeline { stage('phpspec') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -178,8 +181,8 @@ pipeline { stage('api-platform') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -195,8 +198,8 @@ pipeline { stage('api-brand') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -212,8 +215,8 @@ pipeline { stage('api-client') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -229,8 +232,8 @@ pipeline { stage('api-user') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -246,8 +249,8 @@ pipeline { stage('microservice-provision') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -262,8 +265,8 @@ pipeline { stage ('microservice-realtime') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -280,8 +283,8 @@ pipeline { stage('orm') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -296,8 +299,8 @@ pipeline { stage('generators') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' } } steps { @@ -327,7 +330,7 @@ pipeline { // Wait until mysql service is up sh 'while ! mysqladmin ping -hdata.ivozprovider.local --silent; do sleep 1; done' } - docker.image("ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}") + docker.image("ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}") .inside("--env MYSQL_PWD=changeme --volume ${WORKSPACE}:/opt/irontec/ivozprovider --link ${c.id}:data.ivozprovider.local") { sh '/opt/irontec/ivozprovider/schema/bin/test-schema' sh '/opt/irontec/ivozprovider/schema/bin/test-duplicate-keys' @@ -354,6 +357,7 @@ pipeline { expression { hasLabel("ci-force-tests-front") } expression { hasLabel("ci-force-tests") } expression { hasCommitTag("portal") } + expression { hasCommitTag("rest/") } branch "bleeding" branch "tempest" } @@ -362,8 +366,8 @@ pipeline { stage('prepare-frontend') { agent { docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' reuseNode true } } @@ -380,25 +384,48 @@ pipeline { expression { hasLabel("ci-force-tests") } expression { hasCommitTag("portal:") } expression { hasCommitTag("portal/platform:") } + expression { hasCommitTag("rest/platform:") } branch "bleeding" branch "tempest" } } - agent { - docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' - reuseNode true + stages { + stage('web-platform-build') { + agent { + docker { + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' + reuseNode true + } + } + steps { + sh '/opt/irontec/ivozprovider/web/portal/platform/bin/test-lint' + sh '/opt/irontec/ivozprovider/web/portal/platform/bin/test-i18n' + sh '/opt/irontec/ivozprovider/web/portal/platform/bin/test-build' + } + post { + success { notifySuccessGithub() } + failure { notifyFailureGithub() } + } + } + stage('web-platform-cypress') { + steps { + script { + docker.image('ivozprovider-testing-httpd').withRun('-v "${WORKSPACE}":/opt/irontec/ivozprovider') { c -> + docker.image("ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}") + .inside("--env CYPRESS_APP_DOMAIN='http://server/platform/' --volume ${WORKSPACE}:/opt/irontec/ivozprovider --link ${c.id}:server") { + sh '/opt/irontec/ivozprovider/web/portal/platform/bin/test-sync-api-spec platform' + sh '/opt/irontec/ivozprovider/web/portal/platform/bin/test-pact' + } + } + } + } + post { + success { notifySuccessGithub() } + failure { notifyFailureGithub() } + always { archiveArtifacts artifacts: "web/portal/platform/cypress/screenshots/**/*.png", allowEmptyArchive: true } + } } - } - steps { - sh '/opt/irontec/ivozprovider/web/portal/platform/bin/test-lint' - sh '/opt/irontec/ivozprovider/web/portal/platform/bin/test-i18n' - sh '/opt/irontec/ivozprovider/web/portal/platform/bin/test-build' - } - post { - success { notifySuccessGithub() } - failure { notifyFailureGithub() } } } stage('web-brand') { @@ -408,25 +435,48 @@ pipeline { expression { hasLabel("ci-force-tests") } expression { hasCommitTag("portal:") } expression { hasCommitTag("portal/brand:") } + expression { hasCommitTag("rest/brand:") } branch "bleeding" branch "tempest" } } - agent { - docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' - reuseNode true + stages { + stage('web-brand-build') { + agent { + docker { + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' + reuseNode true + } + } + steps { + sh '/opt/irontec/ivozprovider/web/portal/brand/bin/test-lint' + sh '/opt/irontec/ivozprovider/web/portal/brand/bin/test-i18n' + sh '/opt/irontec/ivozprovider/web/portal/brand/bin/test-build' + } + post { + success { notifySuccessGithub() } + failure { notifyFailureGithub() } + } + } + stage('web-brand-cypress') { + steps { + script { + docker.image('ivozprovider-testing-httpd').withRun('-v "${WORKSPACE}":/opt/irontec/ivozprovider') { c -> + docker.image("ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}") + .inside("--env CYPRESS_APP_DOMAIN='http://server/brand/' --volume ${WORKSPACE}:/opt/irontec/ivozprovider --link ${c.id}:server") { + sh '/opt/irontec/ivozprovider/web/portal/brand/bin/test-sync-api-spec brand' + sh '/opt/irontec/ivozprovider/web/portal/brand/bin/test-pact' + } + } + } + } + post { + success { notifySuccessGithub() } + failure { notifyFailureGithub() } + always { archiveArtifacts artifacts: "web/portal/brand/cypress/screenshots/**/*.png", allowEmptyArchive: true } + } } - } - steps { - sh '/opt/irontec/ivozprovider/web/portal/brand/bin/test-lint' - sh '/opt/irontec/ivozprovider/web/portal/brand/bin/test-i18n' - sh '/opt/irontec/ivozprovider/web/portal/brand/bin/test-build' - } - post { - success { notifySuccessGithub() } - failure { notifyFailureGithub() } } } stage('web-client') { @@ -436,25 +486,48 @@ pipeline { expression { hasLabel("ci-force-tests") } expression { hasCommitTag("portal:") } expression { hasCommitTag("portal/client:") } + expression { hasCommitTag("rest/client:") } branch "bleeding" branch "tempest" } } - agent { - docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' - reuseNode true + stages { + stage('web-client-build') { + agent { + docker { + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' + reuseNode true + } + } + steps { + sh '/opt/irontec/ivozprovider/web/portal/client/bin/test-lint' + sh '/opt/irontec/ivozprovider/web/portal/client/bin/test-i18n' + sh '/opt/irontec/ivozprovider/web/portal/client/bin/test-build' + } + post { + success { notifySuccessGithub() } + failure { notifyFailureGithub() } + } + } + stage('web-client-cypress') { + steps { + script { + docker.image('ivozprovider-testing-httpd').withRun('-v "${WORKSPACE}":/opt/irontec/ivozprovider') { c -> + docker.image("ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}") + .inside("--env CYPRESS_APP_DOMAIN='http://server/client/' --volume ${WORKSPACE}:/opt/irontec/ivozprovider --link ${c.id}:server") { + sh '/opt/irontec/ivozprovider/web/portal/client/bin/test-sync-api-spec client' + sh '/opt/irontec/ivozprovider/web/portal/client/bin/test-pact' + } + } + } + } + post { + success { notifySuccessGithub() } + failure { notifyFailureGithub() } + always { archiveArtifacts artifacts: "web/portal/client/cypress/screenshots/**/*.png", allowEmptyArchive: true } + } } - } - steps { - sh '/opt/irontec/ivozprovider/web/portal/client/bin/test-lint' - sh '/opt/irontec/ivozprovider/web/portal/client/bin/test-i18n' - sh '/opt/irontec/ivozprovider/web/portal/client/bin/test-build' - } - post { - success { notifySuccessGithub() } - failure { notifyFailureGithub() } } } stage('web-user') { @@ -464,25 +537,48 @@ pipeline { expression { hasLabel("ci-force-tests") } expression { hasCommitTag("portal:") } expression { hasCommitTag("portal/user:") } + expression { hasCommitTag("rest/user:") } branch "bleeding" branch "tempest" } } - agent { - docker { - image "ironartemis/ivozprovider-testing-base:${env.DOCKER_IMAGE_TAG}" - args '--user jenkins --volume ${WORKSPACE}:/opt/irontec/ivozprovider' - reuseNode true + stages { + stage('web-user-build') { + agent { + docker { + image "ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}" + args '--volume ${WORKSPACE}:/opt/irontec/ivozprovider' + reuseNode true + } + } + steps { + sh '/opt/irontec/ivozprovider/web/portal/user/bin/test-lint' + sh '/opt/irontec/ivozprovider/web/portal/user/bin/test-i18n' + sh '/opt/irontec/ivozprovider/web/portal/user/bin/test-build' + } + post { + success { notifySuccessGithub() } + failure { notifyFailureGithub() } + } + } + stage('web-user-cypress') { + steps { + script { + docker.image('ivozprovider-testing-httpd').withRun('-v "${WORKSPACE}":/opt/irontec/ivozprovider') { c -> + docker.image("ironartemis/ivozprovider-testing-base:${env.DOCKER_TAG}") + .inside("--env CYPRESS_APP_DOMAIN='http://server/user/' --volume ${WORKSPACE}:/opt/irontec/ivozprovider --link ${c.id}:server") { + sh '/opt/irontec/ivozprovider/web/portal/user/bin/test-sync-api-spec user' + sh '/opt/irontec/ivozprovider/web/portal/user/bin/test-pact' + } + } + } + } + post { + success { notifySuccessGithub() } + failure { notifyFailureGithub() } + always { archiveArtifacts artifacts: "web/portal/user/cypress/screenshots/**/*.png", allowEmptyArchive: true } + } } - } - steps { - sh '/opt/irontec/ivozprovider/web/portal/user/bin/test-lint' - sh '/opt/irontec/ivozprovider/web/portal/user/bin/test-i18n' - sh '/opt/irontec/ivozprovider/web/portal/user/bin/test-build' - } - post { - success { notifySuccessGithub() } - failure { notifyFailureGithub() } } } } @@ -602,7 +698,7 @@ pipeline { failure { notifyFailureMattermost() } fixed { notifyFixedMattermost() } unstable { script { currentBuild.result = 'ABORTED' } } - cleanup { cleanWs() } + always { cleanWs() } } } @@ -633,8 +729,8 @@ void getBaseBranch() { return env.CHANGE_TARGET ?: env.GIT_BRANCH } -void getDockerImageTag() { - return env.CHANGE_TARGET ?: env.GIT_BRANCH +void getDockerTag() { + return env.CHANGE_ID ?: env.GIT_BRANCH } void notifySuccessGithub() { diff --git a/README.md b/README.md index dd6b9b958ec..3d79222792e 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ If you want to test an [standalone](https://irontec.github.io/ivozprovider/en/ar | oldoldstable (oasis 1.7) | [![iso http](doc/images/iso-http-green.png)](https://packages.irontec.com/isos/ivozprovider-1.7.1-oasis-amd64.iso) | [![iso http](doc/images/iso-http-green.png)](https://packages.irontec.com/isos/ivozprovider-1.7.1-oasis-i386.iso)| | oldstable (artemis 2.23.0) | [![iso http](doc/images/iso-http-green.png)](https://packages.irontec.com/isos/ivozprovider-2.23~2.23.0-artemis-amd64.iso) | | | stable (halliday 3.4.1) | [![iso http](doc/images/iso-http-green.png)](https://packages.irontec.com/isos/ivozprovider-3.4~3.4.1-halliday-amd64.iso) | | -| testing (tempest 4.1.0) | [![iso http](doc/images/iso-http-green.png)](https://packages.irontec.com/isos/ivozprovider-4.1~4.1.0-tempest-amd64.iso) | | +| testing (tempest 4.2.0) | [![iso http](doc/images/iso-http-green.png)](https://packages.irontec.com/isos/ivozprovider-4.2~4.2.0-tempest-amd64.iso) | | You can read about differences between releases [here](https://github.com/irontec/ivozprovider/blob/bleeding/FAQ.md#what-release-should-i-use). diff --git a/asterisk/agi/src/Agi/Action/HuntGroupCallAction.php b/asterisk/agi/src/Agi/Action/HuntGroupCallAction.php index 64aeef83e35..1b77aac09d2 100644 --- a/asterisk/agi/src/Agi/Action/HuntGroupCallAction.php +++ b/asterisk/agi/src/Agi/Action/HuntGroupCallAction.php @@ -64,6 +64,11 @@ public function process() $options .= "c"; } + // For record asterisk builtin feature code (FIXME Dont use both X's) + if ($huntGroup->getCompany()->getOnDemandRecord() == 2) { + $options .= "xX"; + } + // Call the PSJIP endpoint $this->agi->setVariable("DIAL_DST", $endpointName); $this->agi->setVariable("DIAL_TIMEOUT", $timeout); diff --git a/asterisk/agi/src/Dialplan/Trunks.php b/asterisk/agi/src/Dialplan/Trunks.php index c918ae70bc0..30b53d0e6d5 100644 --- a/asterisk/agi/src/Dialplan/Trunks.php +++ b/asterisk/agi/src/Dialplan/Trunks.php @@ -94,14 +94,10 @@ public function process() $this->agi->setVariable("__COMPANYID", $company->getId()); $this->agi->setVariable("__COMPANYTYPE", $company->getType()); $this->agi->setVariable("__BRANDID", $brand->getId()); + $this->agi->setVariable("__ONDEMANDCODE", $company->getOnDemandRecordDTMFs()); $this->agi->setVariable("CHANNEL(musicclass)", $company->getMusicClass()); $this->agi->setVariable("CHANNEL(language)", $ddi->getLanguageCode()); - // Check company On demand record code - if ($company->getOnDemandRecord()) { - $this->agi->setVariable("FEATUREMAP(automixmon)", $company->getOnDemandRecordDTMFs()); - } - // Set DDI as the caller $this->channelInfo->setChannelCaller(new DdiAgent($this->agi, $ddi)); diff --git a/asterisk/agi/src/Dialplan/Users.php b/asterisk/agi/src/Dialplan/Users.php index 4908a90ad06..f6e4dbe516f 100644 --- a/asterisk/agi/src/Dialplan/Users.php +++ b/asterisk/agi/src/Dialplan/Users.php @@ -160,7 +160,7 @@ public function process() $this->agi->setVariable("__COMPANYID", $company->getId()); $this->agi->setVariable("__COMPANYTYPE", $company->getType()); $this->agi->setVariable("__BRANDID", $brand->getId()); - $this->agi->setVariable("__ONDEMANDCODE", $company->getOnDemandRecordCode()); + $this->agi->setVariable("__ONDEMANDCODE", $company->getOnDemandRecordDTMFs()); // Mark this call as generated from user $this->agi->setVariable("__CALL_TYPE", "internal"); diff --git a/asterisk/config/.gitignore b/asterisk/config/.gitignore new file mode 100644 index 00000000000..b4a23755536 --- /dev/null +++ b/asterisk/config/.gitignore @@ -0,0 +1 @@ +pjsip.conf diff --git a/asterisk/config/dialplan/faxes.conf b/asterisk/config/dialplan/faxes.conf index d00099360bb..799448bc113 100644 --- a/asterisk/config/dialplan/faxes.conf +++ b/asterisk/config/dialplan/faxes.conf @@ -9,7 +9,7 @@ exten => _[+*0-9]!,1,NoOp(Procesing outgoing Fax to ${EXTEN}) ;; Context for Calling an external number through a fax [faxes-call-world] exten => _[+*0-9]!,1,NoOp(Calling external number) - same => n,Dial(${DIAL_DST},${DIAL_TIMEOUT},${DIAL_OPTS}b(add-headers^${EXTEN}^1)) + same => n,Dial(${DIAL_DST},${DIAL_TIMEOUT},${DIAL_OPTS}b(add-headers-trunks^${EXTEN}^1)) exten => h,1,NoOp(Sending faxfile call ended with status ${DIALSTATUS}) same => n,AGI(agi://${FASTAGI_SERVER}/fastagi-runner.php?command=Dialplan/FaxDialStatus) diff --git a/asterisk/config/pjsip.conf b/asterisk/config/pjsip.conf.in similarity index 88% rename from asterisk/config/pjsip.conf rename to asterisk/config/pjsip.conf.in index 16a18c2bc90..360d2e2c03d 100644 --- a/asterisk/config/pjsip.conf +++ b/asterisk/config/pjsip.conf.in @@ -3,8 +3,8 @@ ;; [global] type=global -user_agent=Irontec IvozProvider v4.1 -endpoint_identifier_order=ip,header,username,anonymous +user_agent=Irontec IvozProvider v4.2 +endpoint_identifier_order=header,ip mwi_disable_initial_unsolicited=yes ;; diff --git a/debian/changelog b/debian/changelog index 24967800be5..576bddf1539 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,10 @@ -ivozprovider (4.1~4.1.0) UNRELEASED; urgency=medium +ivozprovider (4.2~4.2.0) UNRELEASED; urgency=medium + + * Version bump to 4.2.0 + + -- Irontec IvozProvider Team Thu, 12 Sep 2024 13:45:11 +0200 + +ivozprovider (4.1~4.1.0) unstable; urgency=medium * Version bump to 4.1.0 diff --git a/debian/control b/debian/control index 6ff0808a362..97923af9d84 100644 --- a/debian/control +++ b/debian/control @@ -2,6 +2,7 @@ Source: ivozprovider Section: php Priority: optional Build-Depends: debhelper (>=9.2), + dh-exec, composer, curl, gettext, diff --git a/debian/ivozprovider-asterisk-config.install b/debian/ivozprovider-asterisk-config.install old mode 100644 new mode 100755 index a150b0e621f..76572ac832c --- a/debian/ivozprovider-asterisk-config.install +++ b/debian/ivozprovider-asterisk-config.install @@ -1 +1,5 @@ +#!/usr/bin/dh-exec + asterisk/config/* /etc/asterisk/ + +asterisk/config/pjsip.conf.in => /etc/asterisk/pjsip.conf \ No newline at end of file diff --git a/debian/ivozprovider.postinst b/debian/ivozprovider.postinst index cb7d05d0260..22d897459e9 100755 --- a/debian/ivozprovider.postinst +++ b/debian/ivozprovider.postinst @@ -98,7 +98,6 @@ function setup_proxies() sed -i -e '/#!define SIPS_PORT/c\#!define SIPS_PORT 7061' /etc/kamailio/proxytrunks/kamailio.cfg sed -i -e '/modparam("dmq", "server_address"/c\modparam("dmq", "server_address", "sip:trunks.ivozprovider.local:7060")' /etc/kamailio/proxytrunks/kamailio.cfg sed -i -e '/contact=sip:trunks.ivozprovider.local/c\contact=sip:trunks.ivozprovider.local:7060' /etc/asterisk/pjsip.conf - sed -i -e '/endpoint_identifier_order=ip,contact,username,anonymous/c\endpoint_identifier_order=contact,ip,username,anonymous' /etc/asterisk/pjsip.conf else sed -i -e '/#!define TRUNKS_SIP_PORT/c\#!define TRUNKS_SIP_PORT 5060' /etc/kamailio/proxyusers/kamailio.cfg sed -i -e '/modparam("dmq", "notification_address"/c\modparam("dmq", "notification_address", "sip:trunks.ivozprovider.local:5060")' /etc/kamailio/proxyusers/kamailio.cfg @@ -106,7 +105,6 @@ function setup_proxies() sed -i -e '/#!define SIPS_PORT/c\#!define SIPS_PORT 5061' /etc/kamailio/proxytrunks/kamailio.cfg sed -i -e '/modparam("dmq", "server_address"/c\modparam("dmq", "server_address", "sip:trunks.ivozprovider.local:5060")' /etc/kamailio/proxytrunks/kamailio.cfg sed -i -e '/contact=sip:trunks.ivozprovider.local/c\contact=sip:trunks.ivozprovider.local' /etc/asterisk/pjsip.conf - sed -i -e '/endpoint_identifier_order=contact,ip,username,anonymous/c\endpoint_identifier_order=ip,contact,username,anonymous' /etc/asterisk/pjsip.conf fi } diff --git a/doc/sphinx/administration_portal/client/vpbx/routing_endpoints/conditional_routes.rst b/doc/sphinx/administration_portal/client/vpbx/routing_endpoints/conditional_routes.rst index 46728807f1c..8911221110c 100644 --- a/doc/sphinx/administration_portal/client/vpbx/routing_endpoints/conditional_routes.rst +++ b/doc/sphinx/administration_portal/client/vpbx/routing_endpoints/conditional_routes.rst @@ -82,7 +82,7 @@ This is how each criteria is evaluated: Lock criteria If one of selected route locks is open, this criteria is considered fulfilled. -.. warning:: :ref:`Calendar Periods` linked to selected calendars are not taken into account. +.. warning:: :ref:`Special Schedules` linked to selected calendars are not taken into account. DDI routed to a conditional route ================================= diff --git a/doc/sphinx/administration_portal/client/vpbx/routing_tools/calendars.rst b/doc/sphinx/administration_portal/client/vpbx/routing_tools/calendars.rst index 78ce0c94270..162e229d884 100644 --- a/doc/sphinx/administration_portal/client/vpbx/routing_tools/calendars.rst +++ b/doc/sphinx/administration_portal/client/vpbx/routing_tools/calendars.rst @@ -47,15 +47,15 @@ days will be holidays using the buttons in its row: is called *Import from CSV*. Also a range of dates may be added with *Add Holiday date range* menu option. -.. _calendar periods: +.. _special schedules: -Calendar Periods ----------------- +Special schedules +----------------- Calendars can also be used to override some time periods with a different schedule. This can be handy if vPBX has a summer schedule or other types of schedule based events. -Calendar periods can define a custom Scheduler and override External Call filters configurations: +Special schedules can define a custom Scheduler and override External Call filters configurations: Start Date Since when the schedules will override the filters configuration @@ -73,8 +73,8 @@ Calendar periods can define a custom Scheduler and override External Call filter Route options Override default external call filter Out of schedule options -Difference between non-whole day event and calendar period ----------------------------------------------------------- +Difference between non-whole day event and special schedules +------------------------------------------------------------ In order to understand the difference between these two features it is important to know the order of call filter logic: @@ -88,10 +88,10 @@ This is where non-whole day event applies, making the answer to the question abo **2. Is current time marked as out-of-schedule?** -This is where calendar period applies, overriding schedules of External Call Filter with the one defined in calendar +This is where special schedules applies, overriding schedules of External Call Filter with the one defined in calendar period. - - Yes: apply out-of-schedule logic defined in the calendar period or in External Call Filter. + - Yes: apply out-of-schedule logic defined in the special schedules or in External Call Filter. - No: proceed with standard logic. @@ -101,11 +101,11 @@ Configuration of a given day: - Non-whole day event: 8:00-15:00 - - Calendar period: 13:00-17:00 + - Special schedule: 13:00-17:00 Cases: - - Call at 7:00: out of schedule due to calendar period. + - Call at 7:00: out of schedule due to special schedule. - Call at 9:00: holiday logic due to non-whole day event. @@ -113,5 +113,5 @@ Cases: - Call at 16:00: normal logic. - - Call at 18:00: out of schedule due to calendar period. + - Call at 18:00: out of schedule due to special schedule. diff --git a/doc/sphinx/basic_concepts/installation/requirements.rst b/doc/sphinx/basic_concepts/installation/requirements.rst index b96c5152e34..0f0f1fbf942 100644 --- a/doc/sphinx/basic_concepts/installation/requirements.rst +++ b/doc/sphinx/basic_concepts/installation/requirements.rst @@ -29,6 +29,6 @@ For a StandAlone installation, we recommend at least: If you're not using a :ref:`Automatic ISO CD image` you will also need: - * Debian Bullseye 11.0 base install + * Debian Bookworm 12.0 base install * Internet access diff --git a/doc/sphinx/conf.py b/doc/sphinx/conf.py index 8e44de113db..ae6369e52a3 100644 --- a/doc/sphinx/conf.py +++ b/doc/sphinx/conf.py @@ -73,7 +73,7 @@ # built documents. # # The short X.Y version. -version = "4.1" +version = "4.2" # The full version, including alpha/beta/rc tags. release = 'tempest' @@ -277,7 +277,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'IvozProvider.tex', 'IvozProvider 4.1 Documentation', + (master_doc, 'IvozProvider.tex', 'IvozProvider 4.2 Documentation', 'Irontec', 'manual'), ] diff --git a/kamailio/.gitignore b/kamailio/.gitignore index 48d35271b59..ca75cc091e9 100644 --- a/kamailio/.gitignore +++ b/kamailio/.gitignore @@ -1,4 +1,13 @@ +*.crt +*.key +apiban.cfg custom-defines.cfg custom-global-params.cfg -*.key -*.crt +custom_settings.cfg +geoip.cfg +listeners.cfg +multisocket.cfg +ports.cfg +ports.cfg.dev +pushservers.cfg +siptrace.cfg diff --git a/kamailio/trunks/config/kamailio.cfg b/kamailio/trunks/config/kamailio.cfg index 8577e8218ba..6c1657fb654 100644 --- a/kamailio/trunks/config/kamailio.cfg +++ b/kamailio/trunks/config/kamailio.cfg @@ -9,10 +9,7 @@ ####### Defines ######### -#!define SIP_PORT 5060 -#!define SIPS_PORT 5061 -#!define RPC_PORT 8001 -#!define XMLRPC_PORT 8002 +include_file "ports.cfg" # - flags # FLT_ - per transaction (message) flags @@ -133,8 +130,8 @@ log_facility=LOG_LOCAL0 onsend_route_reply=yes # Add custom Server header -server_header="Server: Irontec IvozProvider v4.1" -user_agent_header="User-Agent: Irontec IvozProvider v4.1" +server_header="Server: Irontec IvozProvider v4.2" +user_agent_header="User-Agent: Irontec IvozProvider v4.2" ####### Modules Section ######## @@ -201,8 +198,8 @@ modparam("ndb_redis", "init_without_redis", 1) #!endif # DMQ -modparam("dmq", "server_address", "sip:trunks.ivozprovider.local:5060") -modparam("dmq", "notification_address", "sip:users.ivozprovider.local:5060") +modparam("dmq", "server_address", TRUNKS_DMQ_SERVER) +modparam("dmq", "notification_address", USERS_DMQ_SERVER) modparam("dmq", "ping_interval", 3600) # HTTP_CLIENT @@ -1590,7 +1587,7 @@ route[WITHINDLG] { route[DISPATCH] { if ($Ri != $var(trunksAddress)) { # force_send_socket to main address - $fs = "udp:" + $var(trunksAddress) + ":5060"; + $fs = "udp:" + $var(trunksAddress) + ":" + SIP_PORT; } if ($dlg_var(type) == 'retail') { @@ -2109,7 +2106,7 @@ failure_route[MANAGE_FAILURE_AS] { xinfo("[$dlg_var(cidhash)] MANAGE-FAILURE-AS: going to <$ru> via <$du>\n"); # Reset force_socket again - $fs = "udp:" + $var(trunksAddress) + ":5060"; + $fs = "udp:" + $var(trunksAddress) + ":" + SIP_PORT; t_on_failure("MANAGE_FAILURE_AS"); route(RELAY); @@ -2215,6 +2212,19 @@ event_route[tm:local-request] { $ru = $dlg_var(contact); } } + + # REGISTER: fix Contact and socket port if wrong (for mono-IP environments) + if (is_method("REGISTER") && SIP_PORT != 5060) { + $var(contactUsername) = $(ct{s.rm,<}{s.rm,>}{uri.user}); # Extract Contact Username + + sql_xquery("cb", "SELECT socket FROM kam_trunks_uacreg WHERE l_uuid='$var(contactUsername)'", "uacreg"); + if ($xavp(uacreg=>socket) != $null && $(xavp(uacreg=>socket){s.len}) > 0) { + $var(socketIP) = $(xavp(uacreg=>socket){s.select,1,:}); + $fs = "udp:" + $var(socketIP) + ":" + SIP_PORT; + } + + subst_hf("Contact", "/:5060/:" + SIP_PORT + "/", "a"); + } } onsend_route { @@ -2424,13 +2434,13 @@ route[IS_WITHIN_COUNTRY] { route[FORCE_CARRIER_SOCKET] { if ($var(carrierSocketTransport) == '2') { $avp(carrierSocketTransport) = 'tcp'; - $avp(carrierSocketPort) = '5060'; + $avp(carrierSocketPort) = SIP_PORT; } else if ($var(carrierSocketTransport) == '3') { $avp(carrierSocketTransport) = 'tls'; - $avp(carrierSocketPort) = '5061'; + $avp(carrierSocketPort) = SIPS_PORT; } else { $avp(carrierSocketTransport) = 'udp'; - $avp(carrierSocketPort) = '5060'; + $avp(carrierSocketPort) = SIP_PORT; } $fs = $avp(carrierSocketTransport) + ":" + $avp(carrierSocketIp) + ":" + $avp(carrierSocketPort); diff --git a/kamailio/users/config/kamailio.cfg b/kamailio/users/config/kamailio.cfg index 6f33db09701..95eb60de0b6 100644 --- a/kamailio/users/config/kamailio.cfg +++ b/kamailio/users/config/kamailio.cfg @@ -9,12 +9,7 @@ ####### Defines ######### -#!define TRUNKS_SIP_PORT 5060 -#!define SIP_PORT 5060 -#!define SIPS_PORT 5061 -#!define RPC_PORT 8000 -#!define WS_PORT 10080 -#!define WSS_PORT 10081 +include_file "ports.cfg" # # - flags @@ -145,8 +140,8 @@ log_facility=LOG_LOCAL0 onsend_route_reply=yes # Add custom Server header -server_header="Server: Irontec IvozProvider v4.1" -user_agent_header="User-Agent: Irontec IvozProvider v4.1" +server_header="Server: Irontec IvozProvider v4.2" +user_agent_header="User-Agent: Irontec IvozProvider v4.2" ####### Modules Section ######## @@ -214,8 +209,8 @@ import_file "geoip.cfg" import_file "siptrace.cfg" # DMQ -modparam("dmq", "server_address", "sip:users.ivozprovider.local:5060") -modparam("dmq", "notification_address", "sip:trunks.ivozprovider.local:5060") +modparam("dmq", "server_address", USERS_DMQ_SERVER) +modparam("dmq", "notification_address", TRUNKS_DMQ_SERVER) modparam("dmq", "ping_interval", 3600) # RTPENGINE @@ -1330,7 +1325,7 @@ route[STATIC_LOCATION] { xinfo("[$dlg_var(cidhash)] STATIC-LOCATION: Inter-vPBX call\n"); $rd = $fd; $du = "sip:users.ivozprovider.local"; - $fs = "udp:" + $var(usersAddress) + ":5060"; + $fs = "udp:" + $var(usersAddress) + ":" + SIP_PORT; uac_replace_from("sip:" + $xavp(rb=>name) + "@" + $fd); $avp(intervpbx) = 'yes'; # used in SET_PAI return; @@ -1339,9 +1334,9 @@ route[STATIC_LOCATION] { # From now on, logic for directConnectivity == 'yes' objects if ($xavp(rb=>proxyusersIP) != $null && $(xavp(rb=>proxyusersIP){s.len}) > 0) { - $fs = "udp:" + $xavp(rb=>proxyusersIP) + ":5060"; + $fs = "udp:" + $xavp(rb=>proxyusersIP) + ":" + SIP_PORT; } else { - $fs = "udp:" + $var(usersAddress) + ":5060"; + $fs = "udp:" + $var(usersAddress) + ":" + SIP_PORT; } if ($xavp(rb=>ruri_domain) == $null || $(xavp(rb=>ruri_domain){s.len}) == 0) { @@ -2944,7 +2939,7 @@ route[TRANSPORT_DETECT] { route[FORCE_MAIN_SOCKET] { if (!$var(is_from_inside) && $Ri != $var(usersAddress)) { # UAC talking to non-main address, force_send_socket to main address - $fs = "udp:" + $var(usersAddress) + ":5060"; + $fs = "udp:" + $var(usersAddress) + ":" + SIP_PORT; } } diff --git a/library/DataFixtures/ORM/ProviderAdministrator.php b/library/DataFixtures/ORM/ProviderAdministrator.php index d6bb75ef50b..ca9ac104eff 100644 --- a/library/DataFixtures/ORM/ProviderAdministrator.php +++ b/library/DataFixtures/ORM/ProviderAdministrator.php @@ -209,7 +209,7 @@ public function load(ObjectManager $manager) $this->setActive(true); $this->setName("Admin Name"); $this->setLastname("Admin Lastname"); - $this->setBrand($fixture->getReference('_reference_ProviderBrand2')); + $this->setBrand($fixture->getReference('_reference_ProviderBrand1')); $this->setCompany($fixture->getReference('_reference_ProviderCompany5')); $this->setTimezone($fixture->getReference('_reference_ProviderTimezone145')); })->call($item12); @@ -252,6 +252,25 @@ public function load(ObjectManager $manager) $this->sanitizeEntityValues($item14); $manager->persist($item14); + $item15 = $this->createEntityInstance(Administrator::class); + (function () use ($fixture) { + $this->setUsername("disabledCompanyAdmin"); + $this->setPass("changeme"); + $this->setEmail("disabledCompanyAdmin@irontec.com"); + $this->setActive(false); + $this->setRestricted(true); + $this->setCanImpersonate(true); + $this->setName("DisabledCompanyAdmin"); + $this->setLastname("Lastname"); + $this->setBrand($fixture->getReference('_reference_ProviderBrand1')); + $this->setCompany($fixture->getReference('_reference_ProviderCompany1')); + $this->setTimezone($fixture->getReference('_reference_ProviderTimezone145')); + })->call($item15); + + $this->addReference('_reference_ProviderAdministrator15', $item15); + $this->sanitizeEntityValues($item15); + $manager->persist($item15); + $manager->flush(); } diff --git a/library/DataFixtures/ORM/ProviderCompany.php b/library/DataFixtures/ORM/ProviderCompany.php index 070acae0863..687f9d497dd 100644 --- a/library/DataFixtures/ORM/ProviderCompany.php +++ b/library/DataFixtures/ORM/ProviderCompany.php @@ -183,7 +183,7 @@ public function load(ObjectManager $manager) $this->setLanguage($fixture->getReference('_reference_ProviderLanguage1')); $this->setMediaRelaySets($fixture->getReference('_reference_ProviderMediaRelaySet')); $this->setDefaultTimezone($fixture->getReference('_reference_ProviderTimezone145')); - $this->setBrand($fixture->getReference('_reference_ProviderBrand2')); + $this->setBrand($fixture->getReference('_reference_ProviderBrand1')); $this->setCountry($fixture->getReference('_reference_ProviderCountry70')); $this->setTransformationRuleSet($fixture->getReference('_reference_ProviderTransformationRuleSet70')); $this->setVoicemailNotificationTemplate($fixture->getReference('_reference_ProviderNotificationTemplate1')); @@ -194,6 +194,36 @@ public function load(ObjectManager $manager) $manager->persist($item5); $manager->flush(); + + $item6 = $this->createEntityInstance(Company::class); + (function () use ($fixture) { + + $invoicing = new Invoicing( + nif: '12345699-Z', + ); + + $this->setName("Wholesale Argentina"); + $this->setType("wholesale"); + $this->setDomainUsers("argentina.irontec.com"); + $this->setMaxCalls(0); + $this->setIpfilter(false); + $this->setOnDemandRecord(0); + $this->setOnDemandRecordCode(""); + $this->invoicing = $invoicing; + $this->setLanguage($fixture->getReference('_reference_ProviderLanguage1')); + $this->setMediaRelaySets($fixture->getReference('_reference_ProviderMediaRelaySet')); + $this->setDefaultTimezone($fixture->getReference('_reference_ProviderTimezone18')); + $this->setBrand($fixture->getReference('_reference_ProviderBrand2')); + $this->setCountry($fixture->getReference('_reference_ProviderCountry10')); + $this->setTransformationRuleSet($fixture->getReference('_reference_ProviderTransformationRuleSet70')); + $this->setVoicemailNotificationTemplate($fixture->getReference('_reference_ProviderNotificationTemplate4')); + })->call($item6); + + $this->addReference('_reference_ProviderCompany6', $item6); + $this->sanitizeEntityValues($item6); + $manager->persist($item6); + + $manager->flush(); } public function getDependencies() diff --git a/library/DataFixtures/ORM/ProviderWebPortal.php b/library/DataFixtures/ORM/ProviderWebPortal.php index 63d2f39621e..05cf4eb0f6f 100644 --- a/library/DataFixtures/ORM/ProviderWebPortal.php +++ b/library/DataFixtures/ORM/ProviderWebPortal.php @@ -87,13 +87,28 @@ public function load(ObjectManager $manager) $this->sanitizeEntityValues($item5); $manager->persist($item5); + $item6 = $this->createEntityInstance(WebPortal::class); + (function () use ($fixture) { + $this->setUrl("https://users2-ivozprovider.irontec.com"); + $this->setUrlType("user"); + $this->setName("Irontec Ivozprovider User Admin Portal"); + $this->logo = new Logo(10, 'image/jpeg', 'user-logo.jpeg'); + $this->setBrand($fixture->getReference('_reference_ProviderBrand1')); + $this->setCompany($fixture->getReference('_reference_ProviderCompany1')); + })->call($item6); + + $this->addReference('_reference_ProviderWebPortal6', $item6); + $this->sanitizeEntityValues($item6); + $manager->persist($item6); + $manager->flush(); } public function getDependencies() { return array( - ProviderBrand::class + ProviderBrand::class, + ProviderCompany::class ); } } diff --git a/library/DataFixtures/Stub/Provider/RecordingStub.php b/library/DataFixtures/Stub/Provider/RecordingStub.php index e27aa611e8a..40c586db94b 100644 --- a/library/DataFixtures/Stub/Provider/RecordingStub.php +++ b/library/DataFixtures/Stub/Provider/RecordingStub.php @@ -30,5 +30,35 @@ protected function load() ->setCompanyId(1); $this->append($dto); + + $dto = (new RecordingDto(2)) + ->setCallid('fb504426-4e3c-11ef-af02-fc5cee56dc74') + ->setCalldate(new \DateTime('2017-01-05 00:15:15', new \DateTimeZone('UTC'))) + ->setType('ondemand') + ->setDuration(5) + ->setCaller('34946002020') + ->setCallee('34946002021') + ->setRecordedFileFileSize(3276) + ->setRecordedFileMimeType('audio/mpeg; charset=binary') + ->setRecordedFileBaseName("fb504426-4e3c-11ef-af02-fc5cee56dc74.0.mp3") + ->setCompanyId(1) + ->setUserId(1); + + $this->append($dto); + + $dto = (new RecordingDto(3)) + ->setCallid('032f4836-4e3d-11ef-951b-fc5cee56dc74') + ->setCalldate(new \DateTime('2017-01-05 00:15:15', new \DateTimeZone('UTC'))) + ->setType('ddi') + ->setDuration(2) + ->setCaller('34946002020') + ->setCallee('34946002021') + ->setRecordedFileFileSize(4234) + ->setRecordedFileMimeType('audio/mpeg; charset=binary') + ->setRecordedFileBaseName("032f4836-4e3d-11ef-951b-fc5cee56dc74.0.mp3") + ->setCompanyId(1) + ->setDdiId(1); + + $this->append($dto); } } diff --git a/library/Ivoz/Ast/Domain/Service/PsEndpoint/UpdateByUserDeleted.php b/library/Ivoz/Ast/Domain/Service/PsEndpoint/UpdateByUserDeleted.php new file mode 100755 index 00000000000..facd19b8e0a --- /dev/null +++ b/library/Ivoz/Ast/Domain/Service/PsEndpoint/UpdateByUserDeleted.php @@ -0,0 +1,51 @@ + */ + public static function getSubscribedEvents(): array + { + return [ + self::EVENT_POST_REMOVE => self::PRIORITY_NORMAL + ]; + } + + /** + * @return void + */ + public function execute(UserInterface $user) + { + $endpoint = $user->getEndpoint(); + if (!$endpoint) { + return; + } + + /** @var PsEndpointDto $endpointDto */ + $endpointDto = $this + ->entityTools + ->entityToDto($endpoint); + + $endpointDto + ->setCallerid(null) + ->setMailboxes(null) + ->setHintExtension(null) + ->setNamedPickupGroup(null) + ->setExtension(null); + + $this + ->entityTools + ->persistDto($endpointDto, $endpoint, false); + } +} diff --git a/library/Ivoz/Ast/Domain/Service/PsEndpoint/UpdateByUserTerminalUnassignment.php b/library/Ivoz/Ast/Domain/Service/PsEndpoint/UpdateByUserTerminalUnassignment.php new file mode 100755 index 00000000000..8988fb138f4 --- /dev/null +++ b/library/Ivoz/Ast/Domain/Service/PsEndpoint/UpdateByUserTerminalUnassignment.php @@ -0,0 +1,66 @@ + */ + public static function getSubscribedEvents(): array + { + return [ + self::EVENT_POST_PERSIST => self::PRIORITY_NORMAL + ]; + } + + /** + * @return void + */ + public function execute(UserInterface $user) + { + $terminalChanged = $user->hasChanged('terminalId'); + if (!$terminalChanged) { + return; + } + + $originalTerminalId = $user->getInitialValue('terminalId'); + if (!$originalTerminalId) { + return; + } + + $endpoint = $this->psEndpointRepository->findOneByTerminalId( + (int) $originalTerminalId + ); + + if (!$endpoint) { + return; + } + + /** @var PsEndpointDto $endpointDto */ + $endpointDto = $this + ->entityTools + ->entityToDto($endpoint); + + $endpointDto + ->setCallerid(null) + ->setMailboxes(null) + ->setHintExtension(null) + ->setNamedPickupGroup(null) + ->setExtension(null); + + $this + ->entityTools + ->persistDto($endpointDto, $endpoint, false); + } +} diff --git a/library/Ivoz/Provider/Application/Service/HolidayDate/SyncFromCsv.php b/library/Ivoz/Provider/Application/Service/HolidayDate/SyncFromCsv.php index 16e45a68df1..1177d6e995e 100644 --- a/library/Ivoz/Provider/Application/Service/HolidayDate/SyncFromCsv.php +++ b/library/Ivoz/Provider/Application/Service/HolidayDate/SyncFromCsv.php @@ -4,11 +4,15 @@ namespace Ivoz\Provider\Application\Service\HolidayDate; +use Ivoz\Core\Domain\Assert\Assertion; use Ivoz\Core\Domain\Service\EntityTools; use Ivoz\Provider\Domain\Service\HolidayDate\HolidayDateFactory; class SyncFromCsv { + /** @var array{'line': int, 'msg': string}[] */ + private array $errors = []; + public function __construct( private HolidayDateFactory $holidayDateFactory, private EntityTools $entityTools @@ -17,56 +21,103 @@ public function __construct( public function execute( string $calendarId, - string $csv + string $csv, + string $importerParamsJson ): void { $csv = trim($csv); - $rows = explode( - PHP_EOL, - $csv + $rows = array_filter( + explode(PHP_EOL, $csv), + fn ($row) => !empty(trim($row)) ); - /** @var array $rows */ - foreach ($rows as $k => $row) { - $row = trim($row); + $importerParams = $this->initializeImporterParams($importerParamsJson); - if ($row == '') { - unset($rows[$k]); - continue; - } - - $rows[$k] = str_getcsv(trim($row)); + if ($importerParams['ignoreFirst']) { + array_shift($rows); } - $errors = []; - foreach ($rows as $k => $fields) { - try { - $eventName = $fields[0]; - $eventDate = $fields[1]; - - $holidayDate = $this->holidayDateFactory->fromMassProvisioningCsv( - $calendarId, - (string) $eventName, - (string) $eventDate - ); - $this->entityTools->persist($holidayDate); - } catch (\Exception $e) { - $errors[$k + 1] = $e->getMessage(); + $rowsWithColumns = array_map( + fn($row) => str_getcsv( + $row, + $importerParams['delimiter'], + $importerParams['enclosure'], + $importerParams['escape'] + ), + $rows + ); + + foreach ($rowsWithColumns as $k => $row) { + $columnCountMatches = is_array($row) && count($row) === count($importerParams['columns']); + if (!$columnCountMatches) { + $this->addError($k + 1, 'Wrong column count or wrong separator'); continue; } + + $row = array_combine($importerParams['columns'], $row); + $this->persistHolidayDateItem( + $row, + $k, + $calendarId + ); } + } - if (count($errors) > 0) { - $errorMsgs = []; - foreach ($errors as $key => $val) { - $errorMsgs[] = $key . ' => ' . $val; - } - $errorMsg = implode("\n", $errorMsgs); - throw new \Exception( - $errorMsg, - count($errors) + /** @return array{'line': int, 'msg': string}[] */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @return array{ + * 'delimiter': string, + * 'enclosure': string, + * 'escape': string, + * 'columns': string[], + * 'ignoreFirst': boolean + * } + */ + private function initializeImporterParams(string $importerParamsJson): array + { + $importerParams = json_decode($importerParamsJson, true) ?? []; + + return [ + 'delimiter' => $importerParams['delimiter'] ?? ',', + 'enclosure' => $importerParams['enclosure'] ?? '"', + 'escape' => $importerParams['escape'] ?? '\\', + 'columns' => $importerParams['columns'] ?? ['name', 'eventDate'], + 'ignoreFirst' => (bool)($importerParams['ignoreFirst'] ?? false) + ]; + } + + /** @param array $row */ + private function persistHolidayDateItem(array $row, int $line, string $calendarId): void + { + $eventName = $row['name']; + $eventDate = $row['eventDate']; + + try { + $holidayDate = $this->holidayDateFactory->fromMassProvisioningCsv( + $calendarId, + (string)$eventName, + (string)$eventDate ); + + $this->entityTools->persist($holidayDate); + } catch (\DomainException $e) { + $this->addError($line + 1, $e->getMessage()); + } catch (\Exception $e) { + $this->addError($line + 1, 'Unknown error'); } } + + private function addError(int $line, string $msg): void + { + $this->errors[] = [ + 'line' => $line, + 'msg' => $msg + ]; + } } diff --git a/library/Ivoz/Provider/Domain/Model/Administrator/Administrator.php b/library/Ivoz/Provider/Domain/Model/Administrator/Administrator.php index 3a6bb4afd24..9a2ec9ac9b0 100644 --- a/library/Ivoz/Provider/Domain/Model/Administrator/Administrator.php +++ b/library/Ivoz/Provider/Domain/Model/Administrator/Administrator.php @@ -8,7 +8,11 @@ /** * Administrator */ -class Administrator extends AdministratorAbstract implements AdministratorInterface, UserInterface, LegacyPasswordAuthenticatedUserInterface, \Serializable +class Administrator extends AdministratorAbstract implements + AdministratorInterface, + UserInterface, + LegacyPasswordAuthenticatedUserInterface, + \Serializable { const ROLE_SUPER_ADMIN = 'ROLE_SUPER_ADMIN'; const ROLE_BRAND_ADMIN = 'ROLE_BRAND_ADMIN'; @@ -75,7 +79,11 @@ public function setPass(string $pass = null): static return $this; } - $salt = substr(md5((string) random_int(0, mt_getrandmax()), false), 0, 22); + if (empty($pass)) { + return parent::setPass(null); + } + + $salt = substr(md5((string)random_int(0, mt_getrandmax()), false), 0, 22); $cryptPass = crypt( $pass, '$2a$08$' . $salt . '$' . $salt . '$' @@ -169,24 +177,61 @@ public function brandHasFeature(string $iden): bool public function serialize(): string { - return serialize(array( - $this->id, - $this->username, - $this->pass, - $this->email, - $this->active - )); + return serialize($this->__serialize()); } public function unserialize($serialized) { - list ( - $this->id, - $this->username, - $this->pass, - $this->email, - $this->active - ) = unserialize($serialized); + /** @var array{ + * id: int|null, + * username: string, + * pass: string|null, + * email: string, + * active: bool + * } $data */ + $data = unserialize($serialized); + + $this->__unserialize($data); + } + + /** + * @return array{ + * id: int|null, + * username: string, + * pass: string|null, + * email: string, + * active: bool + * } + */ + + public function __serialize(): array + { + return [ + "id" => $this->id, + "username" => $this->username, + "pass" => $this->pass, + "email" => $this->email, + "active" => $this->active + ]; + } + + /** + * @param array{ + * id: int|null, + * username: string, + * pass: string|null, + * email: string, + * active: bool + * } $data + * @return void + */ + public function __unserialize(array $data): void + { + $this->id = $data['id']; + $this->username = $data["username"]; + $this->pass = $data['pass']; + $this->email = $data['email']; + $this->active = $data['active']; } protected function sanitizeValues(): void @@ -194,5 +239,23 @@ protected function sanitizeValues(): void if ($this->getRestricted() === false) { $this->setCanImpersonate(true); } + + $this->sanitizePassword(); + } + + protected function sanitizePassword(): void + { + $isNew = $this->isNew(); + $emptyPass = empty($this->getPass()); + + if ($isNew && $emptyPass) { + $this->setActive(false); + return; + } + + $isActive = $this->getActive(); + if ($emptyPass && $isActive) { + throw new \DomainException('Password cannot be empty for an active user'); + } } } diff --git a/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorAbstract.php b/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorAbstract.php index e607a17de47..fb107a25e1c 100644 --- a/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorAbstract.php @@ -30,10 +30,10 @@ abstract class AdministratorAbstract protected $username; /** - * @var string + * @var ?string * comment: password */ - protected $pass; + protected $pass = ''; /** * @var string @@ -90,7 +90,6 @@ abstract class AdministratorAbstract */ protected function __construct( string $username, - string $pass, string $email, bool $active, bool $internal, @@ -98,7 +97,6 @@ protected function __construct( bool $canImpersonate ) { $this->setUsername($username); - $this->setPass($pass); $this->setEmail($email); $this->setActive($active); $this->setInternal($internal); @@ -169,8 +167,6 @@ public static function fromDto( Assertion::isInstanceOf($dto, AdministratorDto::class); $username = $dto->getUsername(); Assertion::notNull($username, 'getUsername value is null, but non null value was expected.'); - $pass = $dto->getPass(); - Assertion::notNull($pass, 'getPass value is null, but non null value was expected.'); $email = $dto->getEmail(); Assertion::notNull($email, 'getEmail value is null, but non null value was expected.'); $active = $dto->getActive(); @@ -184,7 +180,6 @@ public static function fromDto( $self = new static( $username, - $pass, $email, $active, $internal, @@ -193,6 +188,7 @@ public static function fromDto( ); $self + ->setPass($dto->getPass()) ->setName($dto->getName()) ->setLastname($dto->getLastname()) ->setBrand($fkTransformer->transform($dto->getBrand())) @@ -216,8 +212,6 @@ public function updateFromDto( $username = $dto->getUsername(); Assertion::notNull($username, 'getUsername value is null, but non null value was expected.'); - $pass = $dto->getPass(); - Assertion::notNull($pass, 'getPass value is null, but non null value was expected.'); $email = $dto->getEmail(); Assertion::notNull($email, 'getEmail value is null, but non null value was expected.'); $active = $dto->getActive(); @@ -231,7 +225,7 @@ public function updateFromDto( $this ->setUsername($username) - ->setPass($pass) + ->setPass($dto->getPass()) ->setEmail($email) ->setActive($active) ->setInternal($internal) @@ -301,16 +295,18 @@ public function getUsername(): string return $this->username; } - protected function setPass(string $pass): static + protected function setPass(?string $pass = null): static { - Assertion::maxLength($pass, 80, 'pass value "%s" is too long, it should have no more than %d characters, but has %d characters.'); + if (!is_null($pass)) { + Assertion::maxLength($pass, 80, 'pass value "%s" is too long, it should have no more than %d characters, but has %d characters.'); + } $this->pass = $pass; return $this; } - public function getPass(): string + public function getPass(): ?string { return $this->pass; } diff --git a/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorDto.php b/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorDto.php index d2e3fbf3a17..1db17f70300 100644 --- a/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorDto.php +++ b/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorDto.php @@ -24,6 +24,10 @@ public static function getPropertyMap(string $context = '', string $role = null) 'lastname' => 'lastname', 'email' => 'email' ]; + + if ($role === 'ROLE_BRAND_ADMIN') { + $response['company'] = 'company'; + } } else { $response = parent::getPropertyMap(...func_get_args()); unset($response['internal']); @@ -48,4 +52,24 @@ public function denormalize(array $data, string $context, string $role = ''): vo $data ); } + + /** + * @throws \Exception + */ + public function toArray(bool $hideSensitiveData = false): array + { + if (is_null($this->getPass())) { + if (is_array($this->sensitiveFields)) { + /** @var array $filteredSensitiveFields */ + $filteredSensitiveFields = array_diff( + $this->sensitiveFields, + ["pass"] + ); + + $this->sensitiveFields = $filteredSensitiveFields; + } + } + + return parent::toArray($hideSensitiveData); + } } diff --git a/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorDtoAbstract.php b/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorDtoAbstract.php index d95659bb64d..96be63ff0de 100644 --- a/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorDtoAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorDtoAbstract.php @@ -25,7 +25,7 @@ abstract class AdministratorDtoAbstract implements DataTransferObjectInterface /** * @var string|null */ - private $pass = null; + private $pass = ''; /** * @var string|null @@ -166,7 +166,7 @@ public function getUsername(): ?string return $this->username; } - public function setPass(string $pass): static + public function setPass(?string $pass): static { $this->pass = $pass; diff --git a/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorInterface.php b/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorInterface.php index 0b6b9f63774..f14e06fb8e4 100644 --- a/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorInterface.php +++ b/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorInterface.php @@ -66,6 +66,29 @@ public function serialize(): string; public function unserialize($serialized); + /** + * @return array{ + * id: int|null, + * username: string, + * pass: string|null, + * email: string, + * active: bool + * } + */ + public function __serialize(): array; + + /** + * @param array{ + * id: int|null, + * username: string, + * pass: string|null, + * email: string, + * active: bool + * } $data + * @return void + */ + public function __unserialize(array $data): void; + /** * @param int | null $id */ @@ -91,7 +114,7 @@ public function toDto(int $depth = 0): AdministratorDto; public function getUsername(): string; - public function getPass(): string; + public function getPass(): ?string; public function getEmail(): string; diff --git a/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorSecurityTrait.php b/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorSecurityTrait.php index 3b7bd8b2905..b211948bad0 100755 --- a/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorSecurityTrait.php +++ b/library/Ivoz/Provider/Domain/Model/Administrator/AdministratorSecurityTrait.php @@ -24,9 +24,9 @@ abstract public function getUsername(): string; abstract public function getEmail(): string; /** - * @return string + * @return string | null */ - abstract public function getPass(): string; + abstract public function getPass(): ?string; /** * @return boolean @@ -134,7 +134,7 @@ abstract public function getRelPublicEntities(Criteria $criteria = null): array; */ public function getPassword(): string { - return $this->getPass(); + return $this->getPass() ?? ''; } public function isEnabled(): bool diff --git a/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallAbstract.php b/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallAbstract.php index 97121088e44..9001c27d181 100644 --- a/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallAbstract.php @@ -43,9 +43,9 @@ abstract class BillableCallAbstract protected $callid = null; /** - * @var ?\DateTime + * @var \DateTime */ - protected $startTime = null; + protected $startTime; /** * @var float @@ -163,9 +163,11 @@ abstract class BillableCallAbstract * Constructor */ protected function __construct( + \DateTimeInterface|string $startTime, float $duration, string $direction ) { + $this->setStartTime($startTime); $this->setDuration($duration); $this->setDirection($direction); } @@ -231,19 +233,21 @@ public static function fromDto( ForeignKeyTransformerInterface $fkTransformer ): static { Assertion::isInstanceOf($dto, BillableCallDto::class); + $startTime = $dto->getStartTime(); + Assertion::notNull($startTime, 'getStartTime value is null, but non null value was expected.'); $duration = $dto->getDuration(); Assertion::notNull($duration, 'getDuration value is null, but non null value was expected.'); $direction = $dto->getDirection(); Assertion::notNull($direction, 'getDirection value is null, but non null value was expected.'); $self = new static( + $startTime, $duration, $direction ); $self ->setCallid($dto->getCallid()) - ->setStartTime($dto->getStartTime()) ->setCaller($dto->getCaller()) ->setCallee($dto->getCallee()) ->setCost($dto->getCost()) @@ -280,6 +284,8 @@ public function updateFromDto( ): static { Assertion::isInstanceOf($dto, BillableCallDto::class); + $startTime = $dto->getStartTime(); + Assertion::notNull($startTime, 'getStartTime value is null, but non null value was expected.'); $duration = $dto->getDuration(); Assertion::notNull($duration, 'getDuration value is null, but non null value was expected.'); $direction = $dto->getDirection(); @@ -287,7 +293,7 @@ public function updateFromDto( $this ->setCallid($dto->getCallid()) - ->setStartTime($dto->getStartTime()) + ->setStartTime($startTime) ->setDuration($duration) ->setCaller($dto->getCaller()) ->setCallee($dto->getCallee()) @@ -395,19 +401,17 @@ public function getCallid(): ?string return $this->callid; } - protected function setStartTime(string|\DateTimeInterface|null $startTime = null): static + protected function setStartTime(string|\DateTimeInterface $startTime): static { - if (!is_null($startTime)) { - /** @var ?\DateTime */ - $startTime = DateTimeHelper::createOrFix( - $startTime, - null - ); + /** @var \DateTime */ + $startTime = DateTimeHelper::createOrFix( + $startTime, + '2000-01-01 00:00:00' + ); - if ($this->isInitialized() && $this->startTime == $startTime) { - return $this; - } + if ($this->isInitialized() && $this->startTime == $startTime) { + return $this; } $this->startTime = $startTime; @@ -415,9 +419,9 @@ protected function setStartTime(string|\DateTimeInterface|null $startTime = null return $this; } - public function getStartTime(): ?\DateTime + public function getStartTime(): \DateTime { - return !is_null($this->startTime) ? clone $this->startTime : null; + return clone $this->startTime; } protected function setDuration(float $duration): static diff --git a/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallDtoAbstract.php b/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallDtoAbstract.php index ef5ec2d37ec..994566c320a 100644 --- a/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallDtoAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallDtoAbstract.php @@ -30,7 +30,7 @@ abstract class BillableCallDtoAbstract implements DataTransferObjectInterface /** * @var \DateTimeInterface|string|null */ - private $startTime = null; + private $startTime = '2000-01-01 00:00:00'; /** * @var float|null @@ -249,7 +249,7 @@ public function getCallid(): ?string return $this->callid; } - public function setStartTime(null|\DateTimeInterface|string $startTime): static + public function setStartTime(\DateTimeInterface|string $startTime): static { $this->startTime = $startTime; diff --git a/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallInterface.php b/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallInterface.php index c63579bebaa..e00f43c3fdf 100644 --- a/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallInterface.php +++ b/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallInterface.php @@ -75,7 +75,7 @@ public function toDto(int $depth = 0): BillableCallDto; public function getCallid(): ?string; - public function getStartTime(): ?\DateTime; + public function getStartTime(): \DateTime; public function getDuration(): float; diff --git a/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallRepository.php b/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallRepository.php index f6257580a87..bbe5d6df11e 100644 --- a/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallRepository.php +++ b/library/Ivoz/Provider/Domain/Model/BillableCall/BillableCallRepository.php @@ -15,6 +15,11 @@ interface BillableCallRepository extends ObjectRepository, Selectable */ public function findOutboundByCallid(string $callid, int $brandId = null); + /** + * @return BillableCallInterface[] + */ + public function findByCallid(string $callid); + /** * @param int $id * @return BillableCallInterface diff --git a/library/Ivoz/Provider/Domain/Model/Calendar/CalendarRepository.php b/library/Ivoz/Provider/Domain/Model/Calendar/CalendarRepository.php index 43082c0d8cb..ba41759c276 100644 --- a/library/Ivoz/Provider/Domain/Model/Calendar/CalendarRepository.php +++ b/library/Ivoz/Provider/Domain/Model/Calendar/CalendarRepository.php @@ -7,4 +7,5 @@ interface CalendarRepository extends ObjectRepository, Selectable { + public function findCompanyCalendar(int $companyId, int $calendarId): ?CalendarInterface; } diff --git a/library/Ivoz/Provider/Domain/Model/Ddi/DdiDtoAbstract.php b/library/Ivoz/Provider/Domain/Model/Ddi/DdiDtoAbstract.php index e66cf787d59..80cc220152e 100644 --- a/library/Ivoz/Provider/Domain/Model/Ddi/DdiDtoAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/Ddi/DdiDtoAbstract.php @@ -19,6 +19,7 @@ use Ivoz\Provider\Domain\Model\ResidentialDevice\ResidentialDeviceDto; use Ivoz\Provider\Domain\Model\ConditionalRoute\ConditionalRouteDto; use Ivoz\Provider\Domain\Model\RetailAccount\RetailAccountDto; +use Ivoz\Provider\Domain\Model\Recording\RecordingDto; /** * DdiDtoAbstract @@ -148,6 +149,11 @@ abstract class DdiDtoAbstract implements DataTransferObjectInterface */ private $retailAccount = null; + /** + * @var RecordingDto[] | null + */ + private $recordings = null; + public function __construct(?int $id = null) { $this->setId($id); @@ -219,7 +225,8 @@ public function toArray(bool $hideSensitiveData = false): array 'country' => $this->getCountry(), 'residentialDevice' => $this->getResidentialDevice(), 'conditionalRoute' => $this->getConditionalRoute(), - 'retailAccount' => $this->getRetailAccount() + 'retailAccount' => $this->getRetailAccount(), + 'recordings' => $this->getRecordings() ]; if (!$hideSensitiveData) { @@ -796,4 +803,22 @@ public function getRetailAccountId(): ?int return null; } + + /** + * @param RecordingDto[] | null $recordings + */ + public function setRecordings(?array $recordings): static + { + $this->recordings = $recordings; + + return $this; + } + + /** + * @return RecordingDto[] | null + */ + public function getRecordings(): ?array + { + return $this->recordings; + } } diff --git a/library/Ivoz/Provider/Domain/Model/Ddi/DdiInterface.php b/library/Ivoz/Provider/Domain/Model/Ddi/DdiInterface.php index c0b7667f19a..41ebccec251 100644 --- a/library/Ivoz/Provider/Domain/Model/Ddi/DdiInterface.php +++ b/library/Ivoz/Provider/Domain/Model/Ddi/DdiInterface.php @@ -22,6 +22,9 @@ use Ivoz\Provider\Domain\Model\ResidentialDevice\ResidentialDeviceInterface; use Ivoz\Provider\Domain\Model\ConditionalRoute\ConditionalRouteInterface; use Ivoz\Provider\Domain\Model\RetailAccount\RetailAccountInterface; +use Ivoz\Provider\Domain\Model\Recording\RecordingInterface; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; /** * DdiInterface @@ -162,6 +165,20 @@ public function setRetailAccount(?RetailAccountInterface $retailAccount = null): public function getRetailAccount(): ?RetailAccountInterface; + public function addRecording(RecordingInterface $recording): DdiInterface; + + public function removeRecording(RecordingInterface $recording): DdiInterface; + + /** + * @param Collection $recordings + */ + public function replaceRecordings(Collection $recordings): DdiInterface; + + /** + * @return array + */ + public function getRecordings(?Criteria $criteria = null): array; + /** * @param string $prefix * @return null|string diff --git a/library/Ivoz/Provider/Domain/Model/Ddi/DdiTrait.php b/library/Ivoz/Provider/Domain/Model/Ddi/DdiTrait.php index 543fc31b88c..be2859f67f8 100644 --- a/library/Ivoz/Provider/Domain/Model/Ddi/DdiTrait.php +++ b/library/Ivoz/Provider/Domain/Model/Ddi/DdiTrait.php @@ -6,6 +6,11 @@ use Ivoz\Core\Domain\DataTransferObjectInterface; use Ivoz\Core\Domain\ForeignKeyTransformerInterface; +use Ivoz\Provider\Domain\Model\Recording\RecordingInterface; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Selectable; +use Doctrine\Common\Collections\Criteria; /** * @codeCoverageIgnore @@ -17,12 +22,19 @@ trait DdiTrait */ protected $id = null; + /** + * @var Collection & Selectable + * RecordingInterface mappedBy ddi + */ + protected $recordings; + /** * Constructor */ protected function __construct() { parent::__construct(...func_get_args()); + $this->recordings = new ArrayCollection(); } abstract protected function sanitizeValues(): void; @@ -38,6 +50,15 @@ public static function fromDto( ): static { /** @var static $self */ $self = parent::fromDto($dto, $fkTransformer); + $recordings = $dto->getRecordings(); + if (!is_null($recordings)) { + + /** @var Collection $replacement */ + $replacement = $fkTransformer->transformCollection( + $recordings + ); + $self->replaceRecordings($replacement); + } $self->sanitizeValues(); if ($dto->getId()) { @@ -57,7 +78,15 @@ public function updateFromDto( ForeignKeyTransformerInterface $fkTransformer ): static { parent::updateFromDto($dto, $fkTransformer); + $recordings = $dto->getRecordings(); + if (!is_null($recordings)) { + /** @var Collection $replacement */ + $replacement = $fkTransformer->transformCollection( + $recordings + ); + $this->replaceRecordings($replacement); + } $this->sanitizeValues(); return $this; @@ -82,4 +111,88 @@ protected function __toArray(): array 'id' => self::getId() ]; } + + public function addRecording(RecordingInterface $recording): DdiInterface + { + $this->recordings->add($recording); + + return $this; + } + + public function removeRecording(RecordingInterface $recording): DdiInterface + { + $this->recordings->removeElement($recording); + + return $this; + } + + /** + * @param Collection $recordings + */ + public function replaceRecordings(Collection $recordings): DdiInterface + { + foreach ($recordings as $entity) { + $entity->setDdi($this); + } + + $toStringCallable = fn(mixed $val): \Stringable|string => $val instanceof \Stringable ? $val : serialize($val); + foreach ($this->recordings as $key => $entity) { + /** + * @psalm-suppress MixedArgument + */ + $currentValue = array_map( + $toStringCallable, + (function (): array { + return $this->__toArray(); /** @phpstan-ignore-line */ + })->call($entity) + ); + + $match = false; + foreach ($recordings as $newKey => $newEntity) { + /** + * @psalm-suppress MixedArgument + */ + $newValue = array_map( + $toStringCallable, + (function (): array { + return $this->__toArray(); /** @phpstan-ignore-line */ + })->call($newEntity) + ); + + $diff = array_diff_assoc( + $currentValue, + $newValue + ); + unset($diff['id']); + + if (empty($diff)) { + unset($recordings[$newKey]); + $match = true; + break; + } + } + + if (!$match) { + $this->recordings->remove($key); + } + } + + foreach ($recordings as $entity) { + $this->addRecording($entity); + } + + return $this; + } + + /** + * @return array + */ + public function getRecordings(Criteria $criteria = null): array + { + if (!is_null($criteria)) { + return $this->recordings->matching($criteria)->toArray(); + } + + return $this->recordings->toArray(); + } } diff --git a/library/Ivoz/Provider/Domain/Model/Recording/RecordingAbstract.php b/library/Ivoz/Provider/Domain/Model/Recording/RecordingAbstract.php index 55b9d7384f6..d6ade509e26 100644 --- a/library/Ivoz/Provider/Domain/Model/Recording/RecordingAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/Recording/RecordingAbstract.php @@ -12,7 +12,11 @@ use Ivoz\Core\Domain\Model\Helper\DateTimeHelper; use Ivoz\Provider\Domain\Model\Recording\RecordedFile; use Ivoz\Provider\Domain\Model\Company\CompanyInterface; +use Ivoz\Provider\Domain\Model\Ddi\DdiInterface; +use Ivoz\Provider\Domain\Model\User\UserInterface; use Ivoz\Provider\Domain\Model\Company\Company; +use Ivoz\Provider\Domain\Model\Ddi\Ddi; +use Ivoz\Provider\Domain\Model\User\User; /** * RecordingAbstract @@ -69,6 +73,18 @@ abstract class RecordingAbstract */ protected $company; + /** + * @var ?DdiInterface + * inversedBy recordings + */ + protected $ddi = null; + + /** + * @var ?UserInterface + * inversedBy recordings + */ + protected $user = null; + /** * Constructor */ @@ -172,7 +188,9 @@ public static function fromDto( ->setCaller($dto->getCaller()) ->setCallee($dto->getCallee()) ->setRecorder($dto->getRecorder()) - ->setCompany($fkTransformer->transform($company)); + ->setCompany($fkTransformer->transform($company)) + ->setDdi($fkTransformer->transform($dto->getDdi())) + ->setUser($fkTransformer->transform($dto->getUser())); $self->initChangelog(); @@ -213,7 +231,9 @@ public function updateFromDto( ->setCallee($dto->getCallee()) ->setRecorder($dto->getRecorder()) ->setRecordedFile($recordedFile) - ->setCompany($fkTransformer->transform($company)); + ->setCompany($fkTransformer->transform($company)) + ->setDdi($fkTransformer->transform($dto->getDdi())) + ->setUser($fkTransformer->transform($dto->getUser())); return $this; } @@ -234,7 +254,9 @@ public function toDto(int $depth = 0): RecordingDto ->setRecordedFileFileSize(self::getRecordedFile()->getFileSize()) ->setRecordedFileMimeType(self::getRecordedFile()->getMimeType()) ->setRecordedFileBaseName(self::getRecordedFile()->getBaseName()) - ->setCompany(Company::entityToDto(self::getCompany(), $depth)); + ->setCompany(Company::entityToDto(self::getCompany(), $depth)) + ->setDdi(Ddi::entityToDto(self::getDdi(), $depth)) + ->setUser(User::entityToDto(self::getUser(), $depth)); } /** @@ -253,7 +275,9 @@ protected function __toArray(): array 'recordedFileFileSize' => self::getRecordedFile()->getFileSize(), 'recordedFileMimeType' => self::getRecordedFile()->getMimeType(), 'recordedFileBaseName' => self::getRecordedFile()->getBaseName(), - 'companyId' => self::getCompany()->getId() + 'companyId' => self::getCompany()->getId(), + 'ddiId' => self::getDdi()?->getId(), + 'userId' => self::getUser()?->getId() ]; } @@ -405,4 +429,28 @@ public function getCompany(): CompanyInterface { return $this->company; } + + public function setDdi(?DdiInterface $ddi = null): static + { + $this->ddi = $ddi; + + return $this; + } + + public function getDdi(): ?DdiInterface + { + return $this->ddi; + } + + public function setUser(?UserInterface $user = null): static + { + $this->user = $user; + + return $this; + } + + public function getUser(): ?UserInterface + { + return $this->user; + } } diff --git a/library/Ivoz/Provider/Domain/Model/Recording/RecordingDtoAbstract.php b/library/Ivoz/Provider/Domain/Model/Recording/RecordingDtoAbstract.php index 85c987aac4d..6ddd927937d 100644 --- a/library/Ivoz/Provider/Domain/Model/Recording/RecordingDtoAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/Recording/RecordingDtoAbstract.php @@ -5,6 +5,8 @@ use Ivoz\Core\Domain\DataTransferObjectInterface; use Ivoz\Core\Domain\Model\DtoNormalizer; use Ivoz\Provider\Domain\Model\Company\CompanyDto; +use Ivoz\Provider\Domain\Model\Ddi\DdiDto; +use Ivoz\Provider\Domain\Model\User\UserDto; /** * RecordingDtoAbstract @@ -74,6 +76,16 @@ abstract class RecordingDtoAbstract implements DataTransferObjectInterface */ private $company = null; + /** + * @var DdiDto | null + */ + private $ddi = null; + + /** + * @var UserDto | null + */ + private $user = null; + public function __construct(?int $id = null) { $this->setId($id); @@ -102,7 +114,9 @@ public static function getPropertyMap(string $context = '', string $role = null) 'mimeType', 'baseName', ], - 'companyId' => 'company' + 'companyId' => 'company', + 'ddiId' => 'ddi', + 'userId' => 'user' ]; } @@ -125,7 +139,9 @@ public function toArray(bool $hideSensitiveData = false): array 'mimeType' => $this->getRecordedFileMimeType(), 'baseName' => $this->getRecordedFileBaseName(), ], - 'company' => $this->getCompany() + 'company' => $this->getCompany(), + 'ddi' => $this->getDdi(), + 'user' => $this->getUser() ]; if (!$hideSensitiveData) { @@ -306,4 +322,64 @@ public function getCompanyId(): ?int return null; } + + public function setDdi(?DdiDto $ddi): static + { + $this->ddi = $ddi; + + return $this; + } + + public function getDdi(): ?DdiDto + { + return $this->ddi; + } + + public function setDdiId(?int $id): static + { + $value = !is_null($id) + ? new DdiDto($id) + : null; + + return $this->setDdi($value); + } + + public function getDdiId(): ?int + { + if ($dto = $this->getDdi()) { + return $dto->getId(); + } + + return null; + } + + public function setUser(?UserDto $user): static + { + $this->user = $user; + + return $this; + } + + public function getUser(): ?UserDto + { + return $this->user; + } + + public function setUserId(?int $id): static + { + $value = !is_null($id) + ? new UserDto($id) + : null; + + return $this->setUser($value); + } + + public function getUserId(): ?int + { + if ($dto = $this->getUser()) { + return $dto->getId(); + } + + return null; + } } diff --git a/library/Ivoz/Provider/Domain/Model/Recording/RecordingInterface.php b/library/Ivoz/Provider/Domain/Model/Recording/RecordingInterface.php index 3ae651cb757..a7b2820459c 100644 --- a/library/Ivoz/Provider/Domain/Model/Recording/RecordingInterface.php +++ b/library/Ivoz/Provider/Domain/Model/Recording/RecordingInterface.php @@ -6,6 +6,8 @@ use Ivoz\Core\Domain\DataTransferObjectInterface; use Ivoz\Core\Domain\ForeignKeyTransformerInterface; use Ivoz\Provider\Domain\Model\Company\CompanyInterface; +use Ivoz\Provider\Domain\Model\Ddi\DdiInterface; +use Ivoz\Provider\Domain\Model\User\UserInterface; use Ivoz\Core\Domain\Service\TempFile; use Ivoz\Core\Domain\Service\FileContainerInterface; @@ -73,6 +75,14 @@ public function setCompany(CompanyInterface $company): static; public function getCompany(): CompanyInterface; + public function setDdi(?DdiInterface $ddi = null): static; + + public function getDdi(): ?DdiInterface; + + public function setUser(?UserInterface $user = null): static; + + public function getUser(): ?UserInterface; + /** * @return void */ diff --git a/library/Ivoz/Provider/Domain/Model/User/UserDtoAbstract.php b/library/Ivoz/Provider/Domain/Model/User/UserDtoAbstract.php index 841f99397a1..43b00812e92 100644 --- a/library/Ivoz/Provider/Domain/Model/User/UserDtoAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/User/UserDtoAbstract.php @@ -22,6 +22,7 @@ use Ivoz\Provider\Domain\Model\QueueMember\QueueMemberDto; use Ivoz\Provider\Domain\Model\CallForwardSetting\CallForwardSettingDto; use Ivoz\Provider\Domain\Model\FaxesRelUser\FaxesRelUserDto; +use Ivoz\Provider\Domain\Model\Recording\RecordingDto; /** * UserDtoAbstract @@ -191,6 +192,11 @@ abstract class UserDtoAbstract implements DataTransferObjectInterface */ private $faxesRelUsers = null; + /** + * @var RecordingDto[] | null + */ + private $recordings = null; + public function __construct(?int $id = null) { $this->setId($id); @@ -273,7 +279,8 @@ public function toArray(bool $hideSensitiveData = false): array 'pickUpRelUsers' => $this->getPickUpRelUsers(), 'queueMembers' => $this->getQueueMembers(), 'callForwardSettings' => $this->getCallForwardSettings(), - 'faxesRelUsers' => $this->getFaxesRelUsers() + 'faxesRelUsers' => $this->getFaxesRelUsers(), + 'recordings' => $this->getRecordings() ]; if (!$hideSensitiveData) { @@ -958,4 +965,22 @@ public function getFaxesRelUsers(): ?array { return $this->faxesRelUsers; } + + /** + * @param RecordingDto[] | null $recordings + */ + public function setRecordings(?array $recordings): static + { + $this->recordings = $recordings; + + return $this; + } + + /** + * @return RecordingDto[] | null + */ + public function getRecordings(): ?array + { + return $this->recordings; + } } diff --git a/library/Ivoz/Provider/Domain/Model/User/UserInterface.php b/library/Ivoz/Provider/Domain/Model/User/UserInterface.php index 1e5ff579962..ab3eca70731 100644 --- a/library/Ivoz/Provider/Domain/Model/User/UserInterface.php +++ b/library/Ivoz/Provider/Domain/Model/User/UserInterface.php @@ -26,6 +26,7 @@ use Ivoz\Provider\Domain\Model\QueueMember\QueueMemberInterface; use Ivoz\Provider\Domain\Model\CallForwardSetting\CallForwardSettingInterface; use Ivoz\Provider\Domain\Model\FaxesRelUser\FaxesRelUserInterface; +use Ivoz\Provider\Domain\Model\Recording\RecordingInterface; /** * UserInterface @@ -307,6 +308,20 @@ public function replaceFaxesRelUsers(Collection $faxesRelUsers): UserInterface; */ public function getFaxesRelUsers(?Criteria $criteria = null): array; + public function addRecording(RecordingInterface $recording): UserInterface; + + public function removeRecording(RecordingInterface $recording): UserInterface; + + /** + * @param Collection $recordings + */ + public function replaceRecordings(Collection $recordings): UserInterface; + + /** + * @return array + */ + public function getRecordings(?Criteria $criteria = null): array; + /** * @see UserInterface::getRoles() */ diff --git a/library/Ivoz/Provider/Domain/Model/User/UserTrait.php b/library/Ivoz/Provider/Domain/Model/User/UserTrait.php index f56de16eac9..a89faf5a918 100644 --- a/library/Ivoz/Provider/Domain/Model/User/UserTrait.php +++ b/library/Ivoz/Provider/Domain/Model/User/UserTrait.php @@ -17,6 +17,7 @@ use Ivoz\Provider\Domain\Model\QueueMember\QueueMemberInterface; use Ivoz\Provider\Domain\Model\CallForwardSetting\CallForwardSettingInterface; use Ivoz\Provider\Domain\Model\FaxesRelUser\FaxesRelUserInterface; +use Ivoz\Provider\Domain\Model\Recording\RecordingInterface; /** * @codeCoverageIgnore @@ -71,6 +72,12 @@ trait UserTrait */ protected $faxesRelUsers; + /** + * @var Collection & Selectable + * RecordingInterface mappedBy user + */ + protected $recordings; + /** * Constructor */ @@ -82,6 +89,7 @@ protected function __construct() $this->queueMembers = new ArrayCollection(); $this->callForwardSettings = new ArrayCollection(); $this->faxesRelUsers = new ArrayCollection(); + $this->recordings = new ArrayCollection(); } abstract protected function sanitizeValues(): void; @@ -163,6 +171,16 @@ public static function fromDto( $self->replaceFaxesRelUsers($replacement); } + $recordings = $dto->getRecordings(); + if (!is_null($recordings)) { + + /** @var Collection $replacement */ + $replacement = $fkTransformer->transformCollection( + $recordings + ); + $self->replaceRecordings($replacement); + } + $self->sanitizeValues(); if ($dto->getId()) { $self->id = $dto->getId(); @@ -246,6 +264,16 @@ public function updateFromDto( ); $this->replaceFaxesRelUsers($replacement); } + + $recordings = $dto->getRecordings(); + if (!is_null($recordings)) { + + /** @var Collection $replacement */ + $replacement = $fkTransformer->transformCollection( + $recordings + ); + $this->replaceRecordings($replacement); + } $this->sanitizeValues(); return $this; @@ -714,4 +742,88 @@ public function getFaxesRelUsers(Criteria $criteria = null): array return $this->faxesRelUsers->toArray(); } + + public function addRecording(RecordingInterface $recording): UserInterface + { + $this->recordings->add($recording); + + return $this; + } + + public function removeRecording(RecordingInterface $recording): UserInterface + { + $this->recordings->removeElement($recording); + + return $this; + } + + /** + * @param Collection $recordings + */ + public function replaceRecordings(Collection $recordings): UserInterface + { + foreach ($recordings as $entity) { + $entity->setUser($this); + } + + $toStringCallable = fn(mixed $val): \Stringable|string => $val instanceof \Stringable ? $val : serialize($val); + foreach ($this->recordings as $key => $entity) { + /** + * @psalm-suppress MixedArgument + */ + $currentValue = array_map( + $toStringCallable, + (function (): array { + return $this->__toArray(); /** @phpstan-ignore-line */ + })->call($entity) + ); + + $match = false; + foreach ($recordings as $newKey => $newEntity) { + /** + * @psalm-suppress MixedArgument + */ + $newValue = array_map( + $toStringCallable, + (function (): array { + return $this->__toArray(); /** @phpstan-ignore-line */ + })->call($newEntity) + ); + + $diff = array_diff_assoc( + $currentValue, + $newValue + ); + unset($diff['id']); + + if (empty($diff)) { + unset($recordings[$newKey]); + $match = true; + break; + } + } + + if (!$match) { + $this->recordings->remove($key); + } + } + + foreach ($recordings as $entity) { + $this->addRecording($entity); + } + + return $this; + } + + /** + * @return array + */ + public function getRecordings(Criteria $criteria = null): array + { + if (!is_null($criteria)) { + return $this->recordings->matching($criteria)->toArray(); + } + + return $this->recordings->toArray(); + } } diff --git a/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrAbstract.php b/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrAbstract.php index 2be35b77b55..9bb711b4c26 100644 --- a/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrAbstract.php @@ -29,9 +29,9 @@ abstract class UsersCdrAbstract use ChangelogTrait; /** - * @var ?\DateTime + * @var \DateTime */ - protected $startTime = null; + protected $startTime; /** * @var float @@ -104,8 +104,10 @@ abstract class UsersCdrAbstract * Constructor */ protected function __construct( + \DateTimeInterface|string $startTime, float $duration ) { + $this->setStartTime($startTime); $this->setDuration($duration); } @@ -170,15 +172,17 @@ public static function fromDto( ForeignKeyTransformerInterface $fkTransformer ): static { Assertion::isInstanceOf($dto, UsersCdrDto::class); + $startTime = $dto->getStartTime(); + Assertion::notNull($startTime, 'getStartTime value is null, but non null value was expected.'); $duration = $dto->getDuration(); Assertion::notNull($duration, 'getDuration value is null, but non null value was expected.'); $self = new static( + $startTime, $duration ); $self - ->setStartTime($dto->getStartTime()) ->setDirection($dto->getDirection()) ->setCaller($dto->getCaller()) ->setCallee($dto->getCallee()) @@ -207,11 +211,13 @@ public function updateFromDto( ): static { Assertion::isInstanceOf($dto, UsersCdrDto::class); + $startTime = $dto->getStartTime(); + Assertion::notNull($startTime, 'getStartTime value is null, but non null value was expected.'); $duration = $dto->getDuration(); Assertion::notNull($duration, 'getDuration value is null, but non null value was expected.'); $this - ->setStartTime($dto->getStartTime()) + ->setStartTime($startTime) ->setDuration($duration) ->setDirection($dto->getDirection()) ->setCaller($dto->getCaller()) @@ -274,19 +280,17 @@ protected function __toArray(): array ]; } - protected function setStartTime(string|\DateTimeInterface|null $startTime = null): static + protected function setStartTime(string|\DateTimeInterface $startTime): static { - if (!is_null($startTime)) { - /** @var ?\DateTime */ - $startTime = DateTimeHelper::createOrFix( - $startTime, - null - ); + /** @var \DateTime */ + $startTime = DateTimeHelper::createOrFix( + $startTime, + '2000-01-01 00:00:00' + ); - if ($this->isInitialized() && $this->startTime == $startTime) { - return $this; - } + if ($this->isInitialized() && $this->startTime == $startTime) { + return $this; } $this->startTime = $startTime; @@ -294,9 +298,9 @@ protected function setStartTime(string|\DateTimeInterface|null $startTime = null return $this; } - public function getStartTime(): ?\DateTime + public function getStartTime(): \DateTime { - return !is_null($this->startTime) ? clone $this->startTime : null; + return clone $this->startTime; } protected function setDuration(float $duration): static diff --git a/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrDtoAbstract.php b/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrDtoAbstract.php index d27e04c8b1a..cc5a2e590bb 100644 --- a/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrDtoAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrDtoAbstract.php @@ -21,7 +21,7 @@ abstract class UsersCdrDtoAbstract implements DataTransferObjectInterface /** * @var \DateTimeInterface|string|null */ - private $startTime = null; + private $startTime = '2000-01-01 00:00:00'; /** * @var float|null @@ -163,7 +163,7 @@ public function toArray(bool $hideSensitiveData = false): array return $response; } - public function setStartTime(null|\DateTimeInterface|string $startTime): static + public function setStartTime(\DateTimeInterface|string $startTime): static { $this->startTime = $startTime; diff --git a/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrInterface.php b/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrInterface.php index f973621b32f..9e2e96d4fea 100644 --- a/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrInterface.php +++ b/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrInterface.php @@ -64,7 +64,7 @@ public static function fromDto(DataTransferObjectInterface $dto, ForeignKeyTrans */ public function toDto(int $depth = 0): UsersCdrDto; - public function getStartTime(): ?\DateTime; + public function getStartTime(): \DateTime; public function getDuration(): float; diff --git a/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrRepository.php b/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrRepository.php index 88ac0ff51bd..953c7b1b15f 100644 --- a/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrRepository.php +++ b/library/Ivoz/Provider/Domain/Model/UsersCdr/UsersCdrRepository.php @@ -10,4 +10,9 @@ interface UsersCdrRepository extends RepositoryInterface { public function findByKamUsersCdrId(int $id): ?UsersCdrInterface; + + /** + * @return UsersCdrInterface[] + */ + public function findByCallid(string $callid); } diff --git a/library/Ivoz/Provider/Domain/Model/Voicemail/Voicemail.php b/library/Ivoz/Provider/Domain/Model/Voicemail/Voicemail.php index b1c3c833506..1aaaa705417 100644 --- a/library/Ivoz/Provider/Domain/Model/Voicemail/Voicemail.php +++ b/library/Ivoz/Provider/Domain/Model/Voicemail/Voicemail.php @@ -2,6 +2,7 @@ namespace Ivoz\Provider\Domain\Model\Voicemail; +use Ivoz\Core\Domain\Assert\Assertion; use Ivoz\Provider\Domain\Model\Language\LanguageInterface; /** @@ -105,4 +106,13 @@ public function getLanguage(): ?LanguageInterface return $this->getCompany()->getLanguage(); } + + protected function setEmail(?string $email = null): static + { + if (!empty($email)) { + Assertion::email($email, 'email is not a valid email address'); + } + + return parent::setEmail($email); + } } diff --git a/library/Ivoz/Provider/Domain/Model/Voicemail/VoicemailInterface.php b/library/Ivoz/Provider/Domain/Model/Voicemail/VoicemailInterface.php index ce4239d8db7..b862f1d0c25 100644 --- a/library/Ivoz/Provider/Domain/Model/Voicemail/VoicemailInterface.php +++ b/library/Ivoz/Provider/Domain/Model/Voicemail/VoicemailInterface.php @@ -3,7 +3,6 @@ namespace Ivoz\Provider\Domain\Model\Voicemail; use Ivoz\Core\Domain\Model\LoggableEntityInterface; -use Ivoz\Core\Domain\Model\SelfManagedInterface; use Ivoz\Provider\Domain\Model\Language\LanguageInterface; use Ivoz\Core\Domain\Model\EntityInterface; use Ivoz\Core\Domain\DataTransferObjectInterface; @@ -19,7 +18,7 @@ /** * VoicemailInterface */ -interface VoicemailInterface extends LoggableEntityInterface, SelfManagedInterface +interface VoicemailInterface extends LoggableEntityInterface { /** * @codeCoverageIgnore diff --git a/library/Ivoz/Provider/Domain/Model/Voicemail/VoicemailTrait.php b/library/Ivoz/Provider/Domain/Model/Voicemail/VoicemailTrait.php index c2cb9213e23..02a6f303984 100644 --- a/library/Ivoz/Provider/Domain/Model/Voicemail/VoicemailTrait.php +++ b/library/Ivoz/Provider/Domain/Model/Voicemail/VoicemailTrait.php @@ -32,6 +32,7 @@ trait VoicemailTrait /** * @var Collection & Selectable * VoicemailRelUserInterface mappedBy voicemail + * orphanRemoval */ protected $voicemailRelUsers; diff --git a/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortal.php b/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortal.php index b6b1d4bfea7..813ecee15a0 100644 --- a/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortal.php +++ b/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortal.php @@ -5,6 +5,8 @@ use Assert\Assertion; use Ivoz\Core\Domain\Model\TempFileContainnerTrait; use Ivoz\Core\Domain\Service\FileContainerInterface; +use Ivoz\Provider\Domain\Model\Company\Company; +use Ivoz\Provider\Domain\Model\Company\CompanyInterface; /** * WebPortal @@ -36,6 +38,27 @@ protected function sanitizeValues(): void ); throw new \DomainException($errorMsg); } + + $this->sanitizeCompany(); + } + + protected function sanitizeCompany(): void + { + $isGeneric = is_null($this->getCompany()); + if ($isGeneric) { + return; + } + + $isUserUrlType = $this->getUrlType() === self::URLTYPE_USER; + if ($isUserUrlType && !$this->getCompany()?->isVpbx()) { + throw new \DomainException("Company must be vpbx in user urls"); + } + + $companyBrandId = $this->getCompany()?->getBrand()->getId(); + $brandId = $this->getBrand()?->getId(); + if ($companyBrandId !== $brandId) { + throw new \DomainException("Company brand must be identical to Brand"); + } } /** diff --git a/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalAbstract.php b/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalAbstract.php index 553e68046eb..c2417076b6f 100644 --- a/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalAbstract.php @@ -11,7 +11,9 @@ use Ivoz\Core\Domain\ForeignKeyTransformerInterface; use Ivoz\Provider\Domain\Model\WebPortal\Logo; use Ivoz\Provider\Domain\Model\Brand\BrandInterface; +use Ivoz\Provider\Domain\Model\Company\CompanyInterface; use Ivoz\Provider\Domain\Model\Brand\Brand; +use Ivoz\Provider\Domain\Model\Company\Company; /** * WebPortalAbstract @@ -53,6 +55,11 @@ abstract class WebPortalAbstract */ protected $brand = null; + /** + * @var ?CompanyInterface + */ + protected $company = null; + /** * Constructor */ @@ -151,7 +158,8 @@ public static function fromDto( $self ->setName($dto->getName()) - ->setBrand($fkTransformer->transform($dto->getBrand())); + ->setBrand($fkTransformer->transform($dto->getBrand())) + ->setCompany($fkTransformer->transform($dto->getCompany())); $self->initChangelog(); @@ -187,7 +195,8 @@ public function updateFromDto( ->setName($dto->getName()) ->setColor($color) ->setLogo($logo) - ->setBrand($fkTransformer->transform($dto->getBrand())); + ->setBrand($fkTransformer->transform($dto->getBrand())) + ->setCompany($fkTransformer->transform($dto->getCompany())); return $this; } @@ -205,7 +214,8 @@ public function toDto(int $depth = 0): WebPortalDto ->setLogoFileSize(self::getLogo()->getFileSize()) ->setLogoMimeType(self::getLogo()->getMimeType()) ->setLogoBaseName(self::getLogo()->getBaseName()) - ->setBrand(Brand::entityToDto(self::getBrand(), $depth)); + ->setBrand(Brand::entityToDto(self::getBrand(), $depth)) + ->setCompany(Company::entityToDto(self::getCompany(), $depth)); } /** @@ -221,7 +231,8 @@ protected function __toArray(): array 'logoFileSize' => self::getLogo()->getFileSize(), 'logoMimeType' => self::getLogo()->getMimeType(), 'logoBaseName' => self::getLogo()->getBaseName(), - 'brandId' => self::getBrand()?->getId() + 'brandId' => self::getBrand()?->getId(), + 'companyId' => self::getCompany()?->getId() ]; } @@ -320,4 +331,16 @@ public function getBrand(): ?BrandInterface { return $this->brand; } + + protected function setCompany(?CompanyInterface $company = null): static + { + $this->company = $company; + + return $this; + } + + public function getCompany(): ?CompanyInterface + { + return $this->company; + } } diff --git a/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalDto.php b/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalDto.php index e7a4e46dcd3..a3ccc6485a5 100644 --- a/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalDto.php +++ b/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalDto.php @@ -25,12 +25,17 @@ public static function getPropertyMap(string $context = '', string $role = null) 'baseName', ], ]; + + if ($role === 'ROLE_BRAND_ADMIN' || $role === 'ROLE_SUPER_ADMIN') { + $response['brand'] = 'brand'; + } } else { $response = parent::getPropertyMap(...func_get_args()); } if ($role === 'ROLE_BRAND_ADMIN') { unset($response['brandId']); + $response['companyId'] = 'company'; } return $response; diff --git a/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalDtoAbstract.php b/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalDtoAbstract.php index acc7b840738..58278252657 100644 --- a/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalDtoAbstract.php +++ b/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalDtoAbstract.php @@ -5,6 +5,7 @@ use Ivoz\Core\Domain\DataTransferObjectInterface; use Ivoz\Core\Domain\Model\DtoNormalizer; use Ivoz\Provider\Domain\Model\Brand\BrandDto; +use Ivoz\Provider\Domain\Model\Company\CompanyDto; /** * WebPortalDtoAbstract @@ -59,6 +60,11 @@ abstract class WebPortalDtoAbstract implements DataTransferObjectInterface */ private $brand = null; + /** + * @var CompanyDto | null + */ + private $company = null; + public function __construct(?int $id = null) { $this->setId($id); @@ -84,7 +90,8 @@ public static function getPropertyMap(string $context = '', string $role = null) 'mimeType', 'baseName', ], - 'brandId' => 'brand' + 'brandId' => 'brand', + 'companyId' => 'company' ]; } @@ -104,7 +111,8 @@ public function toArray(bool $hideSensitiveData = false): array 'mimeType' => $this->getLogoMimeType(), 'baseName' => $this->getLogoBaseName(), ], - 'brand' => $this->getBrand() + 'brand' => $this->getBrand(), + 'company' => $this->getCompany() ]; if (!$hideSensitiveData) { @@ -249,4 +257,34 @@ public function getBrandId(): ?int return null; } + + public function setCompany(?CompanyDto $company): static + { + $this->company = $company; + + return $this; + } + + public function getCompany(): ?CompanyDto + { + return $this->company; + } + + public function setCompanyId(?int $id): static + { + $value = !is_null($id) + ? new CompanyDto($id) + : null; + + return $this->setCompany($value); + } + + public function getCompanyId(): ?int + { + if ($dto = $this->getCompany()) { + return $dto->getId(); + } + + return null; + } } diff --git a/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalInterface.php b/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalInterface.php index be71269d763..830b15a479c 100644 --- a/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalInterface.php +++ b/library/Ivoz/Provider/Domain/Model/WebPortal/WebPortalInterface.php @@ -8,6 +8,7 @@ use Ivoz\Core\Domain\DataTransferObjectInterface; use Ivoz\Core\Domain\ForeignKeyTransformerInterface; use Ivoz\Provider\Domain\Model\Brand\BrandInterface; +use Ivoz\Provider\Domain\Model\Company\CompanyInterface; use Ivoz\Core\Domain\Service\TempFile; /** @@ -83,6 +84,8 @@ public function setBrand(?BrandInterface $brand = null): static; public function getBrand(): ?BrandInterface; + public function getCompany(): ?CompanyInterface; + /** * @return void */ diff --git a/library/Ivoz/Provider/Domain/Service/Administrator/AdminLoginChecker.php b/library/Ivoz/Provider/Domain/Service/Administrator/AdminLoginChecker.php index c6a6dacd363..b7ec53aee3e 100644 --- a/library/Ivoz/Provider/Domain/Service/Administrator/AdminLoginChecker.php +++ b/library/Ivoz/Provider/Domain/Service/Administrator/AdminLoginChecker.php @@ -2,13 +2,20 @@ namespace Ivoz\Provider\Domain\Service\Administrator; +use Ivoz\Provider\Domain\Model\Administrator\AdministratorInterface; +use Ivoz\Provider\Domain\Service\HostnameGetter; use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface as SymfonyUserInterface; -use Ivoz\Provider\Domain\Model\Administrator\AdministratorInterface; class AdminLoginChecker implements UserCheckerInterface { + public function __construct( + private HostnameGetter $hostnameGetter, + private AssertWebPortalAccessible $assertWebPortalAccesible + ) { + } + /** * @return void */ @@ -29,6 +36,12 @@ public function checkPreAuth(SymfonyUserInterface $admin) 'Your admin account is disabled.' ); } + + $hostName = $this->hostnameGetter->__invoke() ?? ''; + $this->assertWebPortalAccesible->execute( + $admin, + $hostName + ); } /** diff --git a/library/Ivoz/Provider/Domain/Service/Administrator/AssertAdministratorCanImpersonation.php b/library/Ivoz/Provider/Domain/Service/Administrator/AssertAdministratorCanImpersonation.php index 0b2bacb5ee8..eb8c03f1273 100644 --- a/library/Ivoz/Provider/Domain/Service/Administrator/AssertAdministratorCanImpersonation.php +++ b/library/Ivoz/Provider/Domain/Service/Administrator/AssertAdministratorCanImpersonation.php @@ -12,10 +12,16 @@ public function __construct( ) { } - public function execute(int $id): void + public function execute(int $originatorAdminId, int $targetAdminId): void + { + $this->validateOriginatorAdmin($originatorAdminId); + $this->validateTargetAdmin($targetAdminId); + } + + private function validateTargetAdmin(int $targetAdminId): void { /** @var AdministratorInterface|null $administrator */ - $administrator = $this->administratorRepository->find($id); + $administrator = $this->administratorRepository->find($targetAdminId); if ($administrator === null) { throw new \DomainException( @@ -23,6 +29,33 @@ public function execute(int $id): void 404 ); } + + if ($administrator->getInternal()) { + return; + } + + if ($administrator->isEnabled()) { + return; + } + + throw new \DomainException( + 'Admin cannot be impersonated', + 401 + ); + } + + public function validateOriginatorAdmin(int $originatorAdminId): void + { + /** @var AdministratorInterface|null $administrator */ + $administrator = $this->administratorRepository->find($originatorAdminId); + + if ($administrator === null) { + throw new \DomainException( + 'Admin not found', + 404 + ); + } + if (!$administrator->getRestricted()) { return; } diff --git a/library/Ivoz/Provider/Domain/Service/Administrator/AssertWebPortalAccessible.php b/library/Ivoz/Provider/Domain/Service/Administrator/AssertWebPortalAccessible.php new file mode 100644 index 00000000000..0bbe56b13ce --- /dev/null +++ b/library/Ivoz/Provider/Domain/Service/Administrator/AssertWebPortalAccessible.php @@ -0,0 +1,74 @@ +isSuperAdmin()) { + return; + } + + $webPortal = $this->webPortalRepository->findByServerName($domain); + + if (is_null($webPortal)) { + return; + } + + if ($webPortal->getUrlType() === WebPortalInterface::URLTYPE_GOD) { + return; + } + + if (is_null($webPortal->getCompany())) { + $this->assertBrandsMatch($user, $webPortal); + return; + } + + $this->assertCompaniesMatch($user, $webPortal); + } + + private function assertBrandsMatch(AdministratorInterface $user, WebPortalInterface $webPortal): void + { + $userBrandId = $user->getBrand()?->getId(); + $webPortalBrandId = $webPortal->getBrand()?->getId(); + + $brandMustMatch = !(is_null($userBrandId) || is_null($webPortalBrandId)); + if ($brandMustMatch && $userBrandId === $webPortalBrandId) { + return; + } + + $this->throwAccessDeniedException(); + } + + private function assertCompaniesMatch(AdministratorInterface $user, WebPortalInterface $webPortal): void + { + $userCompanyId = $user->getCompany()?->getId(); + $webPortalCompanyId = $webPortal->getCompany()?->getId(); + + $companyMustMatch = !(is_null($userCompanyId) || is_null($webPortalCompanyId)); + if ($companyMustMatch && $userCompanyId === $webPortalCompanyId) { + return; + } + + $this->throwAccessDeniedException(); + } + + private function throwAccessDeniedException(): void + { + throw new \DomainException( + "Access denied", + 401 + ); + } +} diff --git a/library/Ivoz/Provider/Domain/Service/BillableCall/CreateOrUpdateByTrunksCdr.php b/library/Ivoz/Provider/Domain/Service/BillableCall/CreateOrUpdateByTrunksCdr.php index 1d681da8d58..8692c9bf944 100755 --- a/library/Ivoz/Provider/Domain/Service/BillableCall/CreateOrUpdateByTrunksCdr.php +++ b/library/Ivoz/Provider/Domain/Service/BillableCall/CreateOrUpdateByTrunksCdr.php @@ -95,7 +95,7 @@ public function execute( $isNew = is_null($billableCall); if ($isNew) { - $durantion = round( + $durantion = ceil( $trunksCdrDto->getDuration() ); diff --git a/library/Ivoz/Provider/Domain/Service/HolidayDate/HolidayDateFactory.php b/library/Ivoz/Provider/Domain/Service/HolidayDate/HolidayDateFactory.php index a3ea3aef84f..cf665350b61 100644 --- a/library/Ivoz/Provider/Domain/Service/HolidayDate/HolidayDateFactory.php +++ b/library/Ivoz/Provider/Domain/Service/HolidayDate/HolidayDateFactory.php @@ -24,7 +24,13 @@ public function fromMassProvisioningCsv( $holidayDateDto = new HolidayDateDto(); - $eventDate = new \DateTime($eventDate); + try { + $eventDate = new \DateTime($eventDate); + } catch (\Exception $e) { + throw new \DomainException( + $e->getMessage(), + ); + } $holidayDateDto ->setName($eventName) ->setEventDate($eventDate) diff --git a/library/Ivoz/Provider/Domain/Service/HostnameGetter.php b/library/Ivoz/Provider/Domain/Service/HostnameGetter.php new file mode 100644 index 00000000000..794378c60c0 --- /dev/null +++ b/library/Ivoz/Provider/Domain/Service/HostnameGetter.php @@ -0,0 +1,8 @@ +webPortalRepository->findByServerName($domain); + + if (is_null($webPortal)) { + return; + } + + if ($webPortal->getUrlType() === WebPortalInterface::URLTYPE_GOD) { + return; + } + + $webPortalCompanyId = $webPortal->getCompany()?->getId(); + $userCompanyId = $user->getCompany()->getId(); + if ($userCompanyId === $webPortalCompanyId) { + return; + } + + $webPortalBrandId = $webPortal->getBrand()?->getId(); + $userBrandId = $user->getCompany()->getBrand()->getId(); + if (is_null($webPortalCompanyId) && $userBrandId === $webPortalBrandId) { + return; + } + + throw new \DomainException( + "Access denied", + 401 + ); + } +} diff --git a/library/Ivoz/Provider/Domain/Service/User/UserLifecycleServiceCollection.php b/library/Ivoz/Provider/Domain/Service/User/UserLifecycleServiceCollection.php index 2c0f81d996f..f7ad64de074 100755 --- a/library/Ivoz/Provider/Domain/Service/User/UserLifecycleServiceCollection.php +++ b/library/Ivoz/Provider/Domain/Service/User/UserLifecycleServiceCollection.php @@ -23,12 +23,14 @@ class UserLifecycleServiceCollection implements LifecycleServiceCollectionInterf \Ivoz\Provider\Domain\Service\User\UnsetBossAssistant::class => 30, \Ivoz\Ast\Domain\Service\PsEndpoint\UpdateByUser::class => 40, \Ivoz\Provider\Domain\Service\Voicemail\UpdateByUser::class => 100, + \Ivoz\Ast\Domain\Service\PsEndpoint\UpdateByUserTerminalUnassignment::class => 200, \Ivoz\Ast\Domain\Service\QueueMember\UpdateByUser::class => 200, \Ivoz\Provider\Domain\Service\Contact\UpdateByUser::class => 200, ], "post_remove" => [ \Ivoz\Provider\Domain\Service\Extension\UpdateByUser::class => 10, + \Ivoz\Ast\Domain\Service\PsEndpoint\UpdateByUserDeleted::class => 200, ], "error_handler" => [ diff --git a/library/Ivoz/Provider/Domain/Service/User/UserLoginChecker.php b/library/Ivoz/Provider/Domain/Service/User/UserLoginChecker.php index 214feed57f2..c1ac3f83444 100644 --- a/library/Ivoz/Provider/Domain/Service/User/UserLoginChecker.php +++ b/library/Ivoz/Provider/Domain/Service/User/UserLoginChecker.php @@ -2,33 +2,41 @@ namespace Ivoz\Provider\Domain\Service\User; -use Ivoz\Provider\Domain\Model\Administrator\AdministratorInterface; +use Ivoz\Provider\Domain\Model\User\UserInterface; +use Ivoz\Provider\Domain\Service\HostnameGetter; use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface as SymfonyUserInterface; -use Ivoz\Provider\Domain\Model\User\UserInterface; class UserLoginChecker implements UserCheckerInterface { + public function __construct( + private HostnameGetter $hostnameGetter, + private AssertWebPortalAccessible $assertWebPortalAccesible + ) { + } + /** * @return void */ public function checkPreAuth(SymfonyUserInterface $admin) { - $isTargetClass = - $admin instanceof UserInterface - || $admin instanceof AdministratorInterface; - - if (!$isTargetClass) { + if (!$admin instanceof UserInterface) { return; } - /** @var AdministratorInterface|UserInterface $admin */ + /** @var UserInterface $admin */ if (!$admin->isEnabled()) { throw new CustomUserMessageAccountStatusException( 'Your user account was disabled.' ); } + + $hostName = $this->hostnameGetter->__invoke() ?? ''; + $this->assertWebPortalAccesible->execute( + $admin, + $hostName + ); } /** diff --git a/library/Ivoz/Provider/Domain/Service/UsersCdr/MigrateFromKamUsersCdr.php b/library/Ivoz/Provider/Domain/Service/UsersCdr/MigrateFromKamUsersCdr.php index 5df51c78d90..21ab2d3fe5b 100644 --- a/library/Ivoz/Provider/Domain/Service/UsersCdr/MigrateFromKamUsersCdr.php +++ b/library/Ivoz/Provider/Domain/Service/UsersCdr/MigrateFromKamUsersCdr.php @@ -50,7 +50,7 @@ public function updateDtoByKamUsersCdr(UsersCdrDto $providerUserCdrDto, KamUsers ->setStartTime( $kamUsersCdr->getStartTime() )->setDuration( - $kamUsersCdr->getDuration() + ceil($kamUsersCdr->getDuration()) )->setDirection( $kamUsersCdr->getDirection() )->setCaller( diff --git a/library/Ivoz/Provider/Infrastructure/Api/Jwt/JWTCreatedListener.php b/library/Ivoz/Provider/Infrastructure/Api/Jwt/JWTCreatedListener.php index 028479a9134..57ef92a1876 100644 --- a/library/Ivoz/Provider/Infrastructure/Api/Jwt/JWTCreatedListener.php +++ b/library/Ivoz/Provider/Infrastructure/Api/Jwt/JWTCreatedListener.php @@ -32,6 +32,10 @@ public function onJWTCreated(JWTCreatedEvent $event): void $this ->administratorImpersonationChecker - ->execute(end($onBehalfOfIds)); + ->execute( + end($onBehalfOfIds), + $user->getId() ?? -1 + ) + ; } } diff --git a/library/Ivoz/Provider/Infrastructure/Hostname/GetHostname.php b/library/Ivoz/Provider/Infrastructure/Hostname/GetHostname.php new file mode 100644 index 00000000000..bdde23da4e1 --- /dev/null +++ b/library/Ivoz/Provider/Infrastructure/Hostname/GetHostname.php @@ -0,0 +1,19 @@ +requestStack->getCurrentRequest()?->getHost(); + } +} diff --git a/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/BillableCallDoctrineRepository.php b/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/BillableCallDoctrineRepository.php index 4f70aa0f564..6fba0f3b79b 100755 --- a/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/BillableCallDoctrineRepository.php +++ b/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/BillableCallDoctrineRepository.php @@ -59,6 +59,19 @@ public function findOutboundByCallid(string $callid, int $brandId = null) return $response; } + /** + * @return BillableCall[] + */ + public function findByCallid(string $callid) + { + /** @var BillableCall[] $response */ + $response = $this->findBy([ + 'callid' => $callid + ]); + + return $response; + } + /** * @param int $id * @return BillableCallInterface diff --git a/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/CalendarDoctrineRepository.php b/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/CalendarDoctrineRepository.php index 49052452ddd..c585bbb4424 100755 --- a/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/CalendarDoctrineRepository.php +++ b/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/CalendarDoctrineRepository.php @@ -4,6 +4,7 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Ivoz\Provider\Domain\Model\Calendar\Calendar; +use Ivoz\Provider\Domain\Model\Calendar\CalendarInterface; use Ivoz\Provider\Domain\Model\Calendar\CalendarRepository; use Doctrine\Persistence\ManagerRegistry; @@ -21,4 +22,15 @@ public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Calendar::class); } + + public function findCompanyCalendar(int $companyId, int $calendarId): ?CalendarInterface + { + /** @var CalendarInterface | null $response */ + $response = $this->findOneBy([ + 'company' => $companyId, + 'id' => $calendarId + ]); + + return $response; + } } diff --git a/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/Mapping/Administrator.AdministratorAbstract.orm.xml b/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/Mapping/Administrator.AdministratorAbstract.orm.xml index 7fa72d13094..8a5c1695c85 100644 --- a/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/Mapping/Administrator.AdministratorAbstract.orm.xml +++ b/library/Ivoz/Provider/Infrastructure/Persistence/Doctrine/Mapping/Administrator.AdministratorAbstract.orm.xml @@ -9,10 +9,11 @@