| @@ -0,0 +1,10 @@ | |||||
| name: phpmyadmin | |||||
| repository: ddev/ddev-phpmyadmin | |||||
| version: v0.3.8 | |||||
| install_date: "2024-08-29T14:51:22+02:00" | |||||
| project_files: | |||||
| - docker-compose.phpmyadmin.yaml | |||||
| - docker-compose.phpmyadmin_norouter.yaml | |||||
| - commands/host/phpmyadmin | |||||
| global_files: [] | |||||
| removal_actions: [] | |||||
| @@ -0,0 +1,14 @@ | |||||
| #!/bin/bash | |||||
| ## #ddev-generated: If you want to edit and own this file, remove this line. | |||||
| ## Description: Launch a browser with PhpMyAdmin | |||||
| ## Usage: phpmyadmin | |||||
| ## Example: "ddev phpmyadmin" | |||||
| DDEV_PHPMYADMIN_PORT=8036 | |||||
| DDEV_PHPMYADMIN_HTTPS_PORT=8037 | |||||
| if [ ${DDEV_PRIMARY_URL%://*} = "http" ] || [ -n "${GITPOD_WORKSPACE_ID:-}" ] || [ "${CODESPACES:-}" = "true" ]; then | |||||
| ddev launch :$DDEV_PHPMYADMIN_PORT | |||||
| else | |||||
| ddev launch :$DDEV_PHPMYADMIN_HTTPS_PORT | |||||
| fi | |||||
| @@ -0,0 +1,279 @@ | |||||
| name: probuddy-master | |||||
| type: php | |||||
| docroot: /src/client | |||||
| #docroot: /src/client/manager #manager console | |||||
| #docroot: /src/client/app #app | |||||
| php_version: "8.1" | |||||
| webserver_type: nginx-fpm | |||||
| xdebug_enabled: false | |||||
| additional_hostnames: [] | |||||
| additional_fqdns: [] | |||||
| database: | |||||
| type: mariadb | |||||
| version: "10.4" | |||||
| use_dns_when_possible: true | |||||
| composer_version: "2" | |||||
| web_environment: [] | |||||
| router_http_port: 8091 | |||||
| router_https_port: 8463 | |||||
| # Key features of DDEV's config.yaml: | |||||
| # name: <projectname> # Name of the project, automatically provides | |||||
| # http://projectname.ddev.site and https://projectname.ddev.site | |||||
| # type: <projecttype> # backdrop, craftcms, django4, drupal6/7/8/9/10, laravel, magento, magento2, php, python, shopware6, silverstripe, typo3, wordpress | |||||
| # See https://ddev.readthedocs.io/en/latest/users/quickstart/ for more | |||||
| # information on the different project types | |||||
| # docroot: <relative_path> # Relative path to the directory containing index.php. | |||||
| # php_version: "8.1" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3" | |||||
| # You can explicitly specify the webimage but this | |||||
| # is not recommended, as the images are often closely tied to DDEV's' behavior, | |||||
| # so this can break upgrades. | |||||
| # webimage: <docker_image> # nginx/php docker image. | |||||
| # database: | |||||
| # type: <dbtype> # mysql, mariadb, postgres | |||||
| # version: <version> # database version, like "10.4" or "8.0" | |||||
| # MariaDB versions can be 5.5-10.8 and 10.11, MySQL versions can be 5.5-8.0 | |||||
| # PostgreSQL versions can be 9-16. | |||||
| # router_http_port: <port> # Port to be used for http (defaults to global configuration, usually 80) | |||||
| # router_https_port: <port> # Port for https (defaults to global configuration, usually 443) | |||||
| # xdebug_enabled: false # Set to true to enable Xdebug and "ddev start" or "ddev restart" | |||||
| # Note that for most people the commands | |||||
| # "ddev xdebug" to enable Xdebug and "ddev xdebug off" to disable it work better, | |||||
| # as leaving Xdebug enabled all the time is a big performance hit. | |||||
| # xhprof_enabled: false # Set to true to enable Xhprof and "ddev start" or "ddev restart" | |||||
| # Note that for most people the commands | |||||
| # "ddev xhprof" to enable Xhprof and "ddev xhprof off" to disable it work better, | |||||
| # as leaving Xhprof enabled all the time is a big performance hit. | |||||
| # webserver_type: nginx-fpm, apache-fpm, or nginx-gunicorn | |||||
| # timezone: Europe/Berlin | |||||
| # This is the timezone used in the containers and by PHP; | |||||
| # it can be set to any valid timezone, | |||||
| # see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones | |||||
| # For example Europe/Dublin or MST7MDT | |||||
| # composer_root: <relative_path> | |||||
| # Relative path to the Composer root directory from the project root. This is | |||||
| # the directory which contains the composer.json and where all Composer related | |||||
| # commands are executed. | |||||
| # composer_version: "2" | |||||
| # You can set it to "" or "2" (default) for Composer v2 or "1" for Composer v1 | |||||
| # to use the latest major version available at the time your container is built. | |||||
| # It is also possible to use each other Composer version channel. This includes: | |||||
| # - 2.2 (latest Composer LTS version) | |||||
| # - stable | |||||
| # - preview | |||||
| # - snapshot | |||||
| # Alternatively, an explicit Composer version may be specified, for example "2.2.18". | |||||
| # To reinstall Composer after the image was built, run "ddev debug refresh". | |||||
| # nodejs_version: "18" | |||||
| # change from the default system Node.js version to any other version. | |||||
| # Numeric version numbers can be complete (i.e. 18.15.0) or | |||||
| # incomplete (18, 17.2, 16). 'lts' and 'latest' can be used as well along with | |||||
| # other named releases. | |||||
| # see https://www.npmjs.com/package/n#specifying-nodejs-versions | |||||
| # Note that you can continue using 'ddev nvm' or nvm inside the web container | |||||
| # to change the project's installed node version if you need to. | |||||
| # additional_hostnames: | |||||
| # - somename | |||||
| # - someothername | |||||
| # would provide http and https URLs for "somename.ddev.site" | |||||
| # and "someothername.ddev.site". | |||||
| # additional_fqdns: | |||||
| # - example.com | |||||
| # - sub1.example.com | |||||
| # would provide http and https URLs for "example.com" and "sub1.example.com" | |||||
| # Please take care with this because it can cause great confusion. | |||||
| # upload_dirs: "custom/upload/dir" | |||||
| # | |||||
| # upload_dirs: | |||||
| # - custom/upload/dir | |||||
| # - ../private | |||||
| # | |||||
| # would set the destination paths for ddev import-files to <docroot>/custom/upload/dir | |||||
| # When Mutagen is enabled this path is bind-mounted so that all the files | |||||
| # in the upload_dirs don't have to be synced into Mutagen. | |||||
| # disable_upload_dirs_warning: false | |||||
| # If true, turns off the normal warning that says | |||||
| # "You have Mutagen enabled and your 'php' project type doesn't have upload_dirs set" | |||||
| # ddev_version_constraint: "" | |||||
| # Example: | |||||
| # ddev_version_constraint: ">= 1.22.4" | |||||
| # This will enforce that the running ddev version is within this constraint. | |||||
| # See https://github.com/Masterminds/semver#checking-version-constraints for | |||||
| # supported constraint formats | |||||
| # working_dir: | |||||
| # web: /var/www/html | |||||
| # db: /home | |||||
| # would set the default working directory for the web and db services. | |||||
| # These values specify the destination directory for ddev ssh and the | |||||
| # directory in which commands passed into ddev exec are run. | |||||
| # omit_containers: [db, ddev-ssh-agent] | |||||
| # Currently only these containers are supported. Some containers can also be | |||||
| # omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit | |||||
| # the "db" container, several standard features of DDEV that access the | |||||
| # database container will be unusable. In the global configuration it is also | |||||
| # possible to omit ddev-router, but not here. | |||||
| # performance_mode: "global" | |||||
| # DDEV offers performance optimization strategies to improve the filesystem | |||||
| # performance depending on your host system. Should be configured globally. | |||||
| # | |||||
| # If set, will override the global config. Possible values are: | |||||
| # - "global": uses the value from the global config. | |||||
| # - "none": disables performance optimization for this project. | |||||
| # - "mutagen": enables Mutagen for this project. | |||||
| # - "nfs": enables NFS for this project. | |||||
| # | |||||
| # See https://ddev.readthedocs.io/en/latest/users/install/performance/#nfs | |||||
| # See https://ddev.readthedocs.io/en/latest/users/install/performance/#mutagen | |||||
| # fail_on_hook_fail: False | |||||
| # Decide whether 'ddev start' should be interrupted by a failing hook | |||||
| # host_https_port: "59002" | |||||
| # The host port binding for https can be explicitly specified. It is | |||||
| # dynamic unless otherwise specified. | |||||
| # This is not used by most people, most people use the *router* instead | |||||
| # of the localhost port. | |||||
| # host_webserver_port: "59001" | |||||
| # The host port binding for the ddev-webserver can be explicitly specified. It is | |||||
| # dynamic unless otherwise specified. | |||||
| # This is not used by most people, most people use the *router* instead | |||||
| # of the localhost port. | |||||
| # host_db_port: "59002" | |||||
| # The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic | |||||
| # unless explicitly specified. | |||||
| # mailpit_http_port: "8025" | |||||
| # mailpit_https_port: "8026" | |||||
| # The Mailpit ports can be changed from the default 8025 and 8026 | |||||
| # host_mailpit_port: "8025" | |||||
| # The mailpit port is not normally bound on the host at all, instead being routed | |||||
| # through ddev-router, but it can be bound directly to localhost if specified here. | |||||
| # webimage_extra_packages: [php7.4-tidy, php-bcmath] | |||||
| # Extra Debian packages that are needed in the webimage can be added here | |||||
| # dbimage_extra_packages: [telnet,netcat] | |||||
| # Extra Debian packages that are needed in the dbimage can be added here | |||||
| # use_dns_when_possible: true | |||||
| # If the host has internet access and the domain configured can | |||||
| # successfully be looked up, DNS will be used for hostname resolution | |||||
| # instead of editing /etc/hosts | |||||
| # Defaults to true | |||||
| # project_tld: ddev.site | |||||
| # The top-level domain used for project URLs | |||||
| # The default "ddev.site" allows DNS lookup via a wildcard | |||||
| # If you prefer you can change this to "ddev.local" to preserve | |||||
| # pre-v1.9 behavior. | |||||
| # ngrok_args: --basic-auth username:pass1234 | |||||
| # Provide extra flags to the "ngrok http" command, see | |||||
| # https://ngrok.com/docs/ngrok-agent/config or run "ngrok http -h" | |||||
| # disable_settings_management: false | |||||
| # If true, DDEV will not create CMS-specific settings files like | |||||
| # Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php | |||||
| # In this case the user must provide all such settings. | |||||
| # You can inject environment variables into the web container with: | |||||
| # web_environment: | |||||
| # - SOMEENV=somevalue | |||||
| # - SOMEOTHERENV=someothervalue | |||||
| # no_project_mount: false | |||||
| # (Experimental) If true, DDEV will not mount the project into the web container; | |||||
| # the user is responsible for mounting it manually or via a script. | |||||
| # This is to enable experimentation with alternate file mounting strategies. | |||||
| # For advanced users only! | |||||
| # bind_all_interfaces: false | |||||
| # If true, host ports will be bound on all network interfaces, | |||||
| # not the localhost interface only. This means that ports | |||||
| # will be available on the local network if the host firewall | |||||
| # allows it. | |||||
| # default_container_timeout: 120 | |||||
| # The default time that DDEV waits for all containers to become ready can be increased from | |||||
| # the default 120. This helps in importing huge databases, for example. | |||||
| #web_extra_exposed_ports: | |||||
| #- name: nodejs | |||||
| # container_port: 3000 | |||||
| # http_port: 2999 | |||||
| # https_port: 3000 | |||||
| #- name: something | |||||
| # container_port: 4000 | |||||
| # https_port: 4000 | |||||
| # http_port: 3999 | |||||
| # Allows a set of extra ports to be exposed via ddev-router | |||||
| # Fill in all three fields even if you don’t intend to use the https_port! | |||||
| # If you don’t add https_port, then it defaults to 0 and ddev-router will fail to start. | |||||
| # | |||||
| # The port behavior on the ddev-webserver must be arranged separately, for example | |||||
| # using web_extra_daemons. | |||||
| # For example, with a web app on port 3000 inside the container, this config would | |||||
| # expose that web app on https://<project>.ddev.site:9999 and http://<project>.ddev.site:9998 | |||||
| # web_extra_exposed_ports: | |||||
| # - name: myapp | |||||
| # container_port: 3000 | |||||
| # http_port: 9998 | |||||
| # https_port: 9999 | |||||
| #web_extra_daemons: | |||||
| #- name: "http-1" | |||||
| # command: "/var/www/html/node_modules/.bin/http-server -p 3000" | |||||
| # directory: /var/www/html | |||||
| #- name: "http-2" | |||||
| # command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000" | |||||
| # directory: /var/www/html | |||||
| # override_config: false | |||||
| # By default, config.*.yaml files are *merged* into the configuration | |||||
| # But this means that some things can't be overridden | |||||
| # For example, if you have 'use_dns_when_possible: true'' you can't override it with a merge | |||||
| # and you can't erase existing hooks or all environment variables. | |||||
| # However, with "override_config: true" in a particular config.*.yaml file, | |||||
| # 'use_dns_when_possible: false' can override the existing values, and | |||||
| # hooks: | |||||
| # post-start: [] | |||||
| # or | |||||
| # web_environment: [] | |||||
| # or | |||||
| # additional_hostnames: [] | |||||
| # can have their intended affect. 'override_config' affects only behavior of the | |||||
| # config.*.yaml file it exists in. | |||||
| # Many DDEV commands can be extended to run tasks before or after the | |||||
| # DDEV command is executed, for example "post-start", "post-import-db", | |||||
| # "pre-composer", "post-composer" | |||||
| # See https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/ for more | |||||
| # information on the commands that can be extended and the tasks you can define | |||||
| # for them. Example: | |||||
| #hooks: | |||||
| @@ -0,0 +1,30 @@ | |||||
| #ddev-generated | |||||
| services: | |||||
| phpmyadmin: | |||||
| container_name: ddev-${DDEV_SITENAME}-phpmyadmin | |||||
| image: phpmyadmin:5.2.0 | |||||
| working_dir: "/root" | |||||
| restart: "no" | |||||
| labels: | |||||
| com.ddev.site-name: ${DDEV_SITENAME} | |||||
| com.ddev.approot: $DDEV_APPROOT | |||||
| volumes: | |||||
| - ".:/mnt/ddev_config" | |||||
| - "ddev-global-cache:/mnt/ddev-global-cache" | |||||
| expose: | |||||
| - "80" | |||||
| environment: | |||||
| - PMA_USER=root | |||||
| - PMA_PASSWORD=root | |||||
| - PMA_HOST=db | |||||
| - PMA_PORT=3306 | |||||
| - VIRTUAL_HOST=$DDEV_HOSTNAME | |||||
| - UPLOAD_LIMIT=4000M | |||||
| - HTTP_EXPOSE=8036:80 | |||||
| - HTTPS_EXPOSE=8037:80 | |||||
| healthcheck: | |||||
| interval: 120s | |||||
| timeout: 2s | |||||
| retries: 1 | |||||
| depends_on: | |||||
| - db | |||||
| @@ -0,0 +1,4 @@ | |||||
| #ddev-generated | |||||
| # If omit_containers[ddev-router] then this file will be replaced | |||||
| # with another with a `ports` statement to directly expose port 80 to 8036 | |||||
| services: {} | |||||
| @@ -0,0 +1,8 @@ | |||||
| ;xdebug.log = /home/danielknudsen/xdebug.log | |||||
| ;xdebug.client_port = 9003 | |||||
| ;xdebug.client_host=host.docker.internal | |||||
| xdebug.client_host=docker.for.mac.localhost | |||||
| xdebug.mode = debug | |||||
| xdebug.start_with_request = yes | |||||
| xdebug.log = /tmp/xdebug.log | |||||
| xdebug.log_level = 7 | |||||
| @@ -28,6 +28,9 @@ cp -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/git_repositories/beta- | |||||
| rm -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/httpdocs/src/server/dependencies | rm -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/httpdocs/src/server/dependencies | ||||
| cp -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/git_repositories/beta-probuddy/src/server/dependencies /var/www/vhosts/spawntree.de/probuddy.spawntree.de/httpdocs/src/server | cp -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/git_repositories/beta-probuddy/src/server/dependencies /var/www/vhosts/spawntree.de/probuddy.spawntree.de/httpdocs/src/server | ||||
| rm -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/httpdocs/src/server/patches | |||||
| cp -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/git_repositories/beta-probuddy/src/server/patches /var/www/vhosts/spawntree.de/probuddy.spawntree.de/httpdocs/src/server | |||||
| rm -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/httpdocs/src/server/server/cli | rm -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/httpdocs/src/server/server/cli | ||||
| cp -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/git_repositories/beta-probuddy/src/server/server/cli /var/www/vhosts/spawntree.de/probuddy.spawntree.de/httpdocs/src/server/server | cp -rf /var/www/vhosts/spawntree.de/probuddy.spawntree.de/git_repositories/beta-probuddy/src/server/server/cli /var/www/vhosts/spawntree.de/probuddy.spawntree.de/httpdocs/src/server/server | ||||
| @@ -0,0 +1,55 @@ | |||||
| #!/bin/bash | |||||
| cd /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master | |||||
| git pull | |||||
| echo "$(tput setab 2)pro-buddy has been PULLED$(tput sgr 0)" | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/client | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/client /www/htdocs/v034011/projects/probuddy | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/admin/AHDMN | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/admin/AHDMN /www/htdocs/v034011/projects/probuddy/server/admin | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/admin/libs | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/admin/libs /www/htdocs/v034011/projects/probuddy/server/admin | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/admin/services | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/admin/services /www/htdocs/v034011/projects/probuddy/server/admin | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/admin/boot.php | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/admin/boot.php /www/htdocs/v034011/projects/probuddy/server/admin | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/dependencies | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/dependencies /www/htdocs/v034011/projects/probuddy/server | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/patches | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/patches /www/htdocs/v034011/projects/probuddy/server | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/server/cli | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/server/cli /www/htdocs/v034011/projects/probuddy/server/server | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/server/control | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/server/control /www/htdocs/v034011/projects/probuddy/server/server | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/server/core | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/server/core /www/htdocs/v034011/projects/probuddy/server/server | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/server/job | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/server/job /www/htdocs/v034011/projects/probuddy/server/server | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/server/template | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/server/template /www/htdocs/v034011/projects/probuddy/server/server | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/server/utils | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/server/utils /www/htdocs/v034011/projects/probuddy/server/server | |||||
| rm -rf /www/htdocs/v034011/projects/probuddy/server/shared | |||||
| cp -rf /www/htdocs/v034011/projects/probuddy/git_repository/probuddy-master/src/server/shared /www/htdocs/v034011/projects/probuddy/server | |||||
| echo "$(tput setab 2)Files have been copied$(tput sgr 0)" | |||||
| echo "$(tput setab 7)$(tput setaf 1)THINK ABOUT POSSIBLE PATCHES!" | |||||
| echo "You have updated probuddy live!$(tput sgr 0)" | |||||
| @@ -1,3 +1,8 @@ | |||||
| DDEV | |||||
| - Um zwischen app und manager zu wechseln in die .ddev/config.yaml gucken, dort dann entsprechend einstellen :) | |||||
| DOCKER | |||||
| - Client: http://localhost:8097/client/app/#/auth/start | - Client: http://localhost:8097/client/app/#/auth/start | ||||
| - Database: http://localhost:8096 | - Database: http://localhost:8096 | ||||
| - Template-Engine: https://github.com/cho45/micro-template.js | - Template-Engine: https://github.com/cho45/micro-template.js | ||||
| @@ -28,3 +33,5 @@ Neuinstallation: | |||||
| - php pw_gen.php - Erzeugt Passwort "test" | - php pw_gen.php - Erzeugt Passwort "test" | ||||
| - In phpmyadmin pb_core - account: SQL Statement: UPDATE `account` SET `pass`='aa47377bfef0917b6ff2e73ece5a6952d7763664' WHERE 1 | - In phpmyadmin pb_core - account: SQL Statement: UPDATE `account` SET `pass`='aa47377bfef0917b6ff2e73ece5a6952d7763664' WHERE 1 | ||||
| Update client: | |||||
| - um Client Caching zu umgehen bei Frontend Changes -> versions nummer erhöhen in src/client/app/index.php | |||||
| @@ -0,0 +1,5 @@ | |||||
| { | |||||
| "require": { | |||||
| "ramsey/uuid": "^4.7" | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,260 @@ | |||||
| { | |||||
| "_readme": [ | |||||
| "This file locks the dependencies of your project to a known state", | |||||
| "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | |||||
| "This file is @generated automatically" | |||||
| ], | |||||
| "content-hash": "311c7a785a2af4ab4dae0f24542d289d", | |||||
| "packages": [ | |||||
| { | |||||
| "name": "brick/math", | |||||
| "version": "0.12.1", | |||||
| "source": { | |||||
| "type": "git", | |||||
| "url": "https://github.com/brick/math.git", | |||||
| "reference": "f510c0a40911935b77b86859eb5223d58d660df1" | |||||
| }, | |||||
| "dist": { | |||||
| "type": "zip", | |||||
| "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", | |||||
| "reference": "f510c0a40911935b77b86859eb5223d58d660df1", | |||||
| "shasum": "" | |||||
| }, | |||||
| "require": { | |||||
| "php": "^8.1" | |||||
| }, | |||||
| "require-dev": { | |||||
| "php-coveralls/php-coveralls": "^2.2", | |||||
| "phpunit/phpunit": "^10.1", | |||||
| "vimeo/psalm": "5.16.0" | |||||
| }, | |||||
| "type": "library", | |||||
| "autoload": { | |||||
| "psr-4": { | |||||
| "Brick\\Math\\": "src/" | |||||
| } | |||||
| }, | |||||
| "notification-url": "https://packagist.org/downloads/", | |||||
| "license": [ | |||||
| "MIT" | |||||
| ], | |||||
| "description": "Arbitrary-precision arithmetic library", | |||||
| "keywords": [ | |||||
| "Arbitrary-precision", | |||||
| "BigInteger", | |||||
| "BigRational", | |||||
| "arithmetic", | |||||
| "bigdecimal", | |||||
| "bignum", | |||||
| "bignumber", | |||||
| "brick", | |||||
| "decimal", | |||||
| "integer", | |||||
| "math", | |||||
| "mathematics", | |||||
| "rational" | |||||
| ], | |||||
| "support": { | |||||
| "issues": "https://github.com/brick/math/issues", | |||||
| "source": "https://github.com/brick/math/tree/0.12.1" | |||||
| }, | |||||
| "funding": [ | |||||
| { | |||||
| "url": "https://github.com/BenMorel", | |||||
| "type": "github" | |||||
| } | |||||
| ], | |||||
| "time": "2023-11-29T23:19:16+00:00" | |||||
| }, | |||||
| { | |||||
| "name": "ramsey/collection", | |||||
| "version": "2.0.0", | |||||
| "source": { | |||||
| "type": "git", | |||||
| "url": "https://github.com/ramsey/collection.git", | |||||
| "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" | |||||
| }, | |||||
| "dist": { | |||||
| "type": "zip", | |||||
| "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", | |||||
| "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", | |||||
| "shasum": "" | |||||
| }, | |||||
| "require": { | |||||
| "php": "^8.1" | |||||
| }, | |||||
| "require-dev": { | |||||
| "captainhook/plugin-composer": "^5.3", | |||||
| "ergebnis/composer-normalize": "^2.28.3", | |||||
| "fakerphp/faker": "^1.21", | |||||
| "hamcrest/hamcrest-php": "^2.0", | |||||
| "jangregor/phpstan-prophecy": "^1.0", | |||||
| "mockery/mockery": "^1.5", | |||||
| "php-parallel-lint/php-console-highlighter": "^1.0", | |||||
| "php-parallel-lint/php-parallel-lint": "^1.3", | |||||
| "phpcsstandards/phpcsutils": "^1.0.0-rc1", | |||||
| "phpspec/prophecy-phpunit": "^2.0", | |||||
| "phpstan/extension-installer": "^1.2", | |||||
| "phpstan/phpstan": "^1.9", | |||||
| "phpstan/phpstan-mockery": "^1.1", | |||||
| "phpstan/phpstan-phpunit": "^1.3", | |||||
| "phpunit/phpunit": "^9.5", | |||||
| "psalm/plugin-mockery": "^1.1", | |||||
| "psalm/plugin-phpunit": "^0.18.4", | |||||
| "ramsey/coding-standard": "^2.0.3", | |||||
| "ramsey/conventional-commits": "^1.3", | |||||
| "vimeo/psalm": "^5.4" | |||||
| }, | |||||
| "type": "library", | |||||
| "extra": { | |||||
| "captainhook": { | |||||
| "force-install": true | |||||
| }, | |||||
| "ramsey/conventional-commits": { | |||||
| "configFile": "conventional-commits.json" | |||||
| } | |||||
| }, | |||||
| "autoload": { | |||||
| "psr-4": { | |||||
| "Ramsey\\Collection\\": "src/" | |||||
| } | |||||
| }, | |||||
| "notification-url": "https://packagist.org/downloads/", | |||||
| "license": [ | |||||
| "MIT" | |||||
| ], | |||||
| "authors": [ | |||||
| { | |||||
| "name": "Ben Ramsey", | |||||
| "email": "ben@benramsey.com", | |||||
| "homepage": "https://benramsey.com" | |||||
| } | |||||
| ], | |||||
| "description": "A PHP library for representing and manipulating collections.", | |||||
| "keywords": [ | |||||
| "array", | |||||
| "collection", | |||||
| "hash", | |||||
| "map", | |||||
| "queue", | |||||
| "set" | |||||
| ], | |||||
| "support": { | |||||
| "issues": "https://github.com/ramsey/collection/issues", | |||||
| "source": "https://github.com/ramsey/collection/tree/2.0.0" | |||||
| }, | |||||
| "funding": [ | |||||
| { | |||||
| "url": "https://github.com/ramsey", | |||||
| "type": "github" | |||||
| }, | |||||
| { | |||||
| "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", | |||||
| "type": "tidelift" | |||||
| } | |||||
| ], | |||||
| "time": "2022-12-31T21:50:55+00:00" | |||||
| }, | |||||
| { | |||||
| "name": "ramsey/uuid", | |||||
| "version": "4.7.6", | |||||
| "source": { | |||||
| "type": "git", | |||||
| "url": "https://github.com/ramsey/uuid.git", | |||||
| "reference": "91039bc1faa45ba123c4328958e620d382ec7088" | |||||
| }, | |||||
| "dist": { | |||||
| "type": "zip", | |||||
| "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", | |||||
| "reference": "91039bc1faa45ba123c4328958e620d382ec7088", | |||||
| "shasum": "" | |||||
| }, | |||||
| "require": { | |||||
| "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", | |||||
| "ext-json": "*", | |||||
| "php": "^8.0", | |||||
| "ramsey/collection": "^1.2 || ^2.0" | |||||
| }, | |||||
| "replace": { | |||||
| "rhumsaa/uuid": "self.version" | |||||
| }, | |||||
| "require-dev": { | |||||
| "captainhook/captainhook": "^5.10", | |||||
| "captainhook/plugin-composer": "^5.3", | |||||
| "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", | |||||
| "doctrine/annotations": "^1.8", | |||||
| "ergebnis/composer-normalize": "^2.15", | |||||
| "mockery/mockery": "^1.3", | |||||
| "paragonie/random-lib": "^2", | |||||
| "php-mock/php-mock": "^2.2", | |||||
| "php-mock/php-mock-mockery": "^1.3", | |||||
| "php-parallel-lint/php-parallel-lint": "^1.1", | |||||
| "phpbench/phpbench": "^1.0", | |||||
| "phpstan/extension-installer": "^1.1", | |||||
| "phpstan/phpstan": "^1.8", | |||||
| "phpstan/phpstan-mockery": "^1.1", | |||||
| "phpstan/phpstan-phpunit": "^1.1", | |||||
| "phpunit/phpunit": "^8.5 || ^9", | |||||
| "ramsey/composer-repl": "^1.4", | |||||
| "slevomat/coding-standard": "^8.4", | |||||
| "squizlabs/php_codesniffer": "^3.5", | |||||
| "vimeo/psalm": "^4.9" | |||||
| }, | |||||
| "suggest": { | |||||
| "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", | |||||
| "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", | |||||
| "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", | |||||
| "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", | |||||
| "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." | |||||
| }, | |||||
| "type": "library", | |||||
| "extra": { | |||||
| "captainhook": { | |||||
| "force-install": true | |||||
| } | |||||
| }, | |||||
| "autoload": { | |||||
| "files": [ | |||||
| "src/functions.php" | |||||
| ], | |||||
| "psr-4": { | |||||
| "Ramsey\\Uuid\\": "src/" | |||||
| } | |||||
| }, | |||||
| "notification-url": "https://packagist.org/downloads/", | |||||
| "license": [ | |||||
| "MIT" | |||||
| ], | |||||
| "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", | |||||
| "keywords": [ | |||||
| "guid", | |||||
| "identifier", | |||||
| "uuid" | |||||
| ], | |||||
| "support": { | |||||
| "issues": "https://github.com/ramsey/uuid/issues", | |||||
| "source": "https://github.com/ramsey/uuid/tree/4.7.6" | |||||
| }, | |||||
| "funding": [ | |||||
| { | |||||
| "url": "https://github.com/ramsey", | |||||
| "type": "github" | |||||
| }, | |||||
| { | |||||
| "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", | |||||
| "type": "tidelift" | |||||
| } | |||||
| ], | |||||
| "time": "2024-04-27T21:32:50+00:00" | |||||
| } | |||||
| ], | |||||
| "packages-dev": [], | |||||
| "aliases": [], | |||||
| "minimum-stability": "stable", | |||||
| "stability-flags": [], | |||||
| "prefer-stable": false, | |||||
| "prefer-lowest": false, | |||||
| "platform": [], | |||||
| "platform-dev": [], | |||||
| "plugin-api-version": "2.6.0" | |||||
| } | |||||
| @@ -81,7 +81,13 @@ body.body-content { | |||||
| } | } | ||||
| .content { | .content { | ||||
| margin-top: 72px; | |||||
| /*margin-top: 72px;*/ | |||||
| margin-top: 20px; | |||||
| margin-bottom: 20px; | |||||
| } | |||||
| .dropdown-menu { | |||||
| top: auto; | |||||
| bottom: 100%; | |||||
| } | } | ||||
| .c-offcanvas--right { | .c-offcanvas--right { | ||||
| @@ -92,6 +98,8 @@ body.body-content { | |||||
| { | { | ||||
| position: absolute; | position: absolute; | ||||
| min-width: 280px; | min-width: 280px; | ||||
| max-height: calc(100vh - 60px); | |||||
| overflow: auto; | |||||
| } | } | ||||
| .ul-offcanvas-nav | .ul-offcanvas-nav | ||||
| @@ -457,7 +465,8 @@ body.body-auth .action-button { | |||||
| position: fixed; | position: fixed; | ||||
| -webkit-transform: translate3d(0,0,0); | -webkit-transform: translate3d(0,0,0); | ||||
| transform: translate3d(0,0,0); | transform: translate3d(0,0,0); | ||||
| bottom: 20px; | |||||
| /*bottom: 20px;*/ | |||||
| bottom: 76px; | |||||
| right: 20px; | right: 20px; | ||||
| text-align: center; | text-align: center; | ||||
| font-size: 20px; | font-size: 20px; | ||||
| @@ -471,6 +480,8 @@ body.body-auth .action-button { | |||||
| overflow: hidden; | overflow: hidden; | ||||
| z-index: 100; | z-index: 100; | ||||
| box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12); | box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12); | ||||
| overflow: auto; | |||||
| max-height: calc(100% - 85px); | |||||
| } | } | ||||
| .action-list { | .action-list { | ||||
| @@ -482,7 +493,7 @@ body.body-auth .action-button { | |||||
| bottom: 0; | bottom: 0; | ||||
| right: 0; | right: 0; | ||||
| opacity: 0; | opacity: 0; | ||||
| background-color: white; | |||||
| background-color: #fff; | |||||
| } | } | ||||
| .action-list-item { | .action-list-item { | ||||
| @@ -1125,6 +1136,44 @@ h6.in-card { | |||||
| font-size: 0.75rem; | font-size: 0.75rem; | ||||
| } | } | ||||
| .search-box { | |||||
| display: flex; | |||||
| position: relative; | |||||
| margin-right: 20px; | |||||
| } | |||||
| .search-box .fa { | |||||
| font-size: 16px; | |||||
| color: #ccc; | |||||
| position: absolute; | |||||
| right: 8px; | |||||
| top: 50%; | |||||
| transform: translate(0,-50%); | |||||
| } | |||||
| .search-box input { | |||||
| width: 200px; | |||||
| padding-right: 25px; | |||||
| } | |||||
| .right-content { | |||||
| display: flex; | |||||
| justify-content: right; | |||||
| align-items: center; | |||||
| } | |||||
| @media only screen and (max-width: 500px) { | |||||
| .float-right.search-content { | |||||
| float: none !important; | |||||
| margin-right: 0 !important; | |||||
| } | |||||
| .right-content { | |||||
| justify-content: left; | |||||
| padding-top: 5px; | |||||
| } | |||||
| } | |||||
| h6.calendar-week { | h6.calendar-week { | ||||
| margin-top: 0; | margin-top: 0; | ||||
| margin-bottom: 0; | margin-bottom: 0; | ||||
| @@ -1,8 +1,7 @@ | |||||
| <?php | <?php | ||||
| require_once __DIR__ . '/../../server/server/config/boot_global.php'; | require_once __DIR__ . '/../../server/server/config/boot_global.php'; | ||||
| require_once __DIR__ . '/../../server/server/config/boot_local.php'; | require_once __DIR__ . '/../../server/server/config/boot_local.php'; | ||||
| $version = 'v=2.0.1';//time();//Francis_Utils_Config::get( 'version' ); | |||||
| $version = 'v=2.1.2';//time();//Francis_Utils_Config::get( 'version' ); | |||||
| ?> | ?> | ||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||
| <html lang="en"> | <html lang="en"> | ||||
| @@ -111,6 +111,7 @@ app.core.Dict = { | |||||
| "CLOSE" : "Schließen", | "CLOSE" : "Schließen", | ||||
| "CREATE_APPOINTMENT" : "Termin erstellen", | "CREATE_APPOINTMENT" : "Termin erstellen", | ||||
| "APPOINTMENT_SUBJECT" : "Terminname", | "APPOINTMENT_SUBJECT" : "Terminname", | ||||
| "APPOINTMENT_ICON" : "Icon", | |||||
| "APPOINTMENT_START_DATE" : "Startdatum", | "APPOINTMENT_START_DATE" : "Startdatum", | ||||
| "APPOINTMENT_START_TIME" : "Startzeit", | "APPOINTMENT_START_TIME" : "Startzeit", | ||||
| "APPOINTMENT_END_DATE" : "Enddatum", | "APPOINTMENT_END_DATE" : "Enddatum", | ||||
| @@ -431,6 +432,8 @@ app.core.Dict = { | |||||
| "ACCOUNT_NOT_VALIDATED_DESCRIPTION" : "Bitte validiere zunächst deine Email Adresse. Wir haben dir gerade einen Validierungslink per Mail zugeschickt. Bitte schau in deinem Postfach nach (ggf. auch im Spam-Ordner) und klicke auf den Button.", | "ACCOUNT_NOT_VALIDATED_DESCRIPTION" : "Bitte validiere zunächst deine Email Adresse. Wir haben dir gerade einen Validierungslink per Mail zugeschickt. Bitte schau in deinem Postfach nach (ggf. auch im Spam-Ordner) und klicke auf den Button.", | ||||
| "LOGIN_FIRSTNAME" : "Vorname", | "LOGIN_FIRSTNAME" : "Vorname", | ||||
| "LOGIN_LASTNAME" : "Nachname", | "LOGIN_LASTNAME" : "Nachname", | ||||
| "DELETION_NOT_POSSIBLE_INFO_HEADLINE" : "Informationen zur Löschung deines Profils", | |||||
| "DELETION_NOT_POSSIBLE_INFO" : "Du hast noch <strong>{0}</strong> Teilnahme(n) für zukünftige Termine, bei denen die Absagefrist noch nicht verstrichen ist. Bitte sage alle Teilnahmen ab, bevor Du Deinen Account löschen kannst.", | |||||
| "DELETION_GROUP_OWNER_INFO_HEADLINE" : "Informationen zur Löschung deines Profils", | "DELETION_GROUP_OWNER_INFO_HEADLINE" : "Informationen zur Löschung deines Profils", | ||||
| "DELETION_GROUP_OWNER_INFO" : "Bitte schreibe eine Mail an support@probuddy.de um die Löschung deines Profile bei Probuddy zu veranlassen. Da du Gruppeninhaber mindestens einer Gruppe bei ProBuddy bist, wird diese Gruppe/Gruppen automatisch mitgelöscht.", | "DELETION_GROUP_OWNER_INFO" : "Bitte schreibe eine Mail an support@probuddy.de um die Löschung deines Profile bei Probuddy zu veranlassen. Da du Gruppeninhaber mindestens einer Gruppe bei ProBuddy bist, wird diese Gruppe/Gruppen automatisch mitgelöscht.", | ||||
| "BTN_BACK" : "Zurück", | "BTN_BACK" : "Zurück", | ||||
| @@ -571,6 +574,7 @@ app.core.Dict = { | |||||
| "GROUP_MANAGEMENT_MEMBERS_ACTIVE" : "Aktiv", | "GROUP_MANAGEMENT_MEMBERS_ACTIVE" : "Aktiv", | ||||
| "GROUP_MANAGEMENT_MEMBERS_INACTIVE" : "Inaktiv", | "GROUP_MANAGEMENT_MEMBERS_INACTIVE" : "Inaktiv", | ||||
| "GROUP_MANAGEMENT_MEMBERS_NOT_APPROVED" : "Unbestätigt", | "GROUP_MANAGEMENT_MEMBERS_NOT_APPROVED" : "Unbestätigt", | ||||
| "GROUP_MANAGEMENT_MEMBERS_DELETED" : "Gelöscht", | |||||
| "GROUP_MANAGEMENT_MEMBERS_CHANGE_STATUS" : "Gruppenstatus ändern", | "GROUP_MANAGEMENT_MEMBERS_CHANGE_STATUS" : "Gruppenstatus ändern", | ||||
| "GROUP_MANAGEMENT_MEMBERS_CHANGE_STATUS_INFO" : "Du kannst deinen eigenen Gruppenstatus nicht verändern.", | "GROUP_MANAGEMENT_MEMBERS_CHANGE_STATUS_INFO" : "Du kannst deinen eigenen Gruppenstatus nicht verändern.", | ||||
| "BTN_GROUP_MANAGEMENT_MEMBERS_SAVE_STATUS" : "Status speichern", | "BTN_GROUP_MANAGEMENT_MEMBERS_SAVE_STATUS" : "Status speichern", | ||||
| @@ -15,6 +15,7 @@ app.model.Appointment = function( appData, attendees, attendeeProfiles ) | |||||
| attendees = attendees || [], | attendees = attendees || [], | ||||
| attendeeProfiles = attendeeProfiles || [], | attendeeProfiles = attendeeProfiles || [], | ||||
| id = data.id, | id = data.id, | ||||
| icon = data.icon, | |||||
| category = data.category || null, | category = data.category || null, | ||||
| categoryIds = typeof( data.category_ids_js ) === 'string' ? JSON.parse( data.category_ids_js ) : data.category_ids_js, | categoryIds = typeof( data.category_ids_js ) === 'string' ? JSON.parse( data.category_ids_js ) : data.category_ids_js, | ||||
| visibility = data.visibility, | visibility = data.visibility, | ||||
| @@ -65,6 +66,11 @@ app.model.Appointment = function( appData, attendees, attendeeProfiles ) | |||||
| return teamId; | return teamId; | ||||
| }; | }; | ||||
| this.getIcon = function() | |||||
| { | |||||
| return icon; | |||||
| }; | |||||
| this.getSubject = function() | this.getSubject = function() | ||||
| { | { | ||||
| return subject; | return subject; | ||||
| @@ -223,6 +223,11 @@ app.model.Profile = function( data, groupsData ) | |||||
| teamData = gd; | teamData = gd; | ||||
| }; | }; | ||||
| this.getGroupStatus = function( groupId ) | |||||
| { | |||||
| return this.getGroupData(groupId).status; | |||||
| } | |||||
| this.isOwn = function() | this.isOwn = function() | ||||
| { | { | ||||
| return ( this.getId() == app.model.SessionUser.getUserProfile().getId() ); | return ( this.getId() == app.model.SessionUser.getUserProfile().getId() ); | ||||
| @@ -184,6 +184,7 @@ app.state.AppointmentCreate = function() | |||||
| var $form = $content.find( '[data-id="form-appointment"]' ).first(), | var $form = $content.find( '[data-id="form-appointment"]' ).first(), | ||||
| teamId, | teamId, | ||||
| icon, | |||||
| appointmentState = $form.find( '[data-id="checkbox-appointment-state"]' ).first().is( ':checked' ) ? 'open' : null, | appointmentState = $form.find( '[data-id="checkbox-appointment-state"]' ).first().is( ':checked' ) ? 'open' : null, | ||||
| isValid = app.util.Form.bootstrapValidate( $form ); | isValid = app.util.Form.bootstrapValidate( $form ); | ||||
| @@ -191,11 +192,15 @@ app.state.AppointmentCreate = function() | |||||
| { | { | ||||
| app.gui.PageLoader.show(); | app.gui.PageLoader.show(); | ||||
| teamId = $form.find( '[data-id="select-team-id"]' ).first().val(); | teamId = $form.find( '[data-id="select-team-id"]' ).first().val(); | ||||
| icon = $('#input-subject-icon option:selected').val(); | |||||
| //console.log(icon); | |||||
| app.core.Rpc.call( | app.core.Rpc.call( | ||||
| 'Appointment', | 'Appointment', | ||||
| 'create', | 'create', | ||||
| { | { | ||||
| teamId : teamId, | teamId : teamId, | ||||
| icon: icon, | |||||
| categoryIds : $form.find( '[data-id="select-category-' + teamId + '"]' ).first().val(), | categoryIds : $form.find( '[data-id="select-category-' + teamId + '"]' ).first().val(), | ||||
| visibility : ( 'visibility-category-only' === $form.find( '[name="input-visibility"]:checked' ).first().val() ) ? app.model.Appointment.ENUM_VISIBLE_FOR_CATEGORIES : app.model.Appointment.ENUM_VISIBLE_FOR_ALL, | visibility : ( 'visibility-category-only' === $form.find( '[name="input-visibility"]:checked' ).first().val() ) ? app.model.Appointment.ENUM_VISIBLE_FOR_CATEGORIES : app.model.Appointment.ENUM_VISIBLE_FOR_ALL, | ||||
| subject : $form.find( '[data-id="input-subject"]' ).first().val(), | subject : $form.find( '[data-id="input-subject"]' ).first().val(), | ||||
| @@ -168,6 +168,7 @@ app.state.AppointmentEdit= function() | |||||
| 'Appointment', | 'Appointment', | ||||
| 'update', | 'update', | ||||
| { | { | ||||
| icon: $('#input-subject-icon option:selected').val(), | |||||
| processSerial : isSerial, | processSerial : isSerial, | ||||
| appointmentId : appointmentId, | appointmentId : appointmentId, | ||||
| categoryIds : $form.find( '[data-id="select-category-' + appointment.getTeamId() + '"]' ).first().val(), | categoryIds : $form.find( '[data-id="select-category-' + appointment.getTeamId() + '"]' ).first().val(), | ||||
| @@ -31,7 +31,7 @@ app.state.AppointmentEditAttendee = function() | |||||
| app.core.Rpc.call( | app.core.Rpc.call( | ||||
| 'Team', | 'Team', | ||||
| 'getMembers', | 'getMembers', | ||||
| { teamId : appointment.getTeamId() }, | |||||
| { teamId : appointment.getTeamId(), activeOnly: true }, | |||||
| function( res2 ) | function( res2 ) | ||||
| { | { | ||||
| let childs, m, notDecided = []; | let childs, m, notDecided = []; | ||||
| @@ -40,7 +40,7 @@ app.state.ConfigurationAttendanceLog = function() | |||||
| { | { | ||||
| $content.find( '[data-id="container-appointment-log"]' ).first().html( | $content.find( '[data-id="container-appointment-log"]' ).first().html( | ||||
| app.core.View.getTemplate( | app.core.View.getTemplate( | ||||
| 'group-member-management-body-appointment-log', | |||||
| 'group-member-management-member-body-appointment-log', | |||||
| { | { | ||||
| logs : res.appointmentLog | logs : res.appointmentLog | ||||
| } | } | ||||
| @@ -18,15 +18,18 @@ app.state.ConfigurationProfileDelete = function() | |||||
| app.core.Rpc.call( | app.core.Rpc.call( | ||||
| 'Account', | 'Account', | ||||
| 'getAccountRelatedData', | 'getAccountRelatedData', | ||||
| null, | |||||
| { | |||||
| includeNumFutureAttendances : true | |||||
| }, | |||||
| function( res ) { | function( res ) { | ||||
| app.gui.PageLoader.hide(); | app.gui.PageLoader.hide(); | ||||
| console.log(res); | |||||
| app.core.View.setContent( | app.core.View.setContent( | ||||
| app.core.View.getTemplate( | app.core.View.getTemplate( | ||||
| 'configuration-profile-delete', | 'configuration-profile-delete', | ||||
| { | { | ||||
| profile: new app.model.Profile(res.profile) | |||||
| profile: new app.model.Profile(res.profile), | |||||
| numFutureAttendances: res.numFutureAttendances | |||||
| } | } | ||||
| ) | ) | ||||
| ); | ); | ||||
| @@ -30,7 +30,9 @@ app.state.CourseCategories = function() | |||||
| 'Team', | 'Team', | ||||
| 'getDetails', | 'getDetails', | ||||
| { | { | ||||
| teamId : groupId | |||||
| teamId : groupId, | |||||
| includeMembers: true, | |||||
| activeOnly: true | |||||
| }, | }, | ||||
| function( res ) | function( res ) | ||||
| { | { | ||||
| @@ -13,11 +13,13 @@ app.state.GroupMemberManagement = function() | |||||
| { | { | ||||
| let $content = app.core.View.getContent(), | let $content = app.core.View.getContent(), | ||||
| group = null, | group = null, | ||||
| currentProfile = null, | |||||
| memberToEdit = null, | memberToEdit = null, | ||||
| members = [], | members = [], | ||||
| membersActive = [], | membersActive = [], | ||||
| membersInactive = [], | membersInactive = [], | ||||
| membersNotApproved = [], | membersNotApproved = [], | ||||
| membersDeleted = [], | |||||
| groupId = p.groupId, | groupId = p.groupId, | ||||
| memberId = p.hasOwnProperty( 'memberId' ) ? p.memberId : null, | memberId = p.hasOwnProperty( 'memberId' ) ? p.memberId : null, | ||||
| activeTab = 'active'; | activeTab = 'active'; | ||||
| @@ -65,6 +67,10 @@ app.state.GroupMemberManagement = function() | |||||
| case 'not_approved': | case 'not_approved': | ||||
| membersNotApproved.push(member); | membersNotApproved.push(member); | ||||
| break; | break; | ||||
| case 'deleted': | |||||
| membersDeleted.push(member); | |||||
| console.log(member.isAccessible()) | |||||
| break; | |||||
| } | } | ||||
| } | } | ||||
| @@ -76,6 +82,7 @@ app.state.GroupMemberManagement = function() | |||||
| membersActive: membersActive, | membersActive: membersActive, | ||||
| membersInactive: membersInactive, | membersInactive: membersInactive, | ||||
| membersNotApproved: membersNotApproved, | membersNotApproved: membersNotApproved, | ||||
| membersDeleted: membersDeleted, | |||||
| group : group, | group : group, | ||||
| groups : app.model.SessionUser.getAdminGroups(), | groups : app.model.SessionUser.getAdminGroups(), | ||||
| currentProfile : currentProfile, | currentProfile : currentProfile, | ||||
| @@ -11,7 +11,6 @@ app.state.GroupMemberManagementMember = function() | |||||
| state.onEnter = function( p ) | state.onEnter = function( p ) | ||||
| { | { | ||||
| console.log(p); | |||||
| let $content = app.core.View.getContent(), | let $content = app.core.View.getContent(), | ||||
| fnRenderMemberForm = null, | fnRenderMemberForm = null, | ||||
| fnGetMemberById = null, | fnGetMemberById = null, | ||||
| @@ -27,6 +26,11 @@ app.state.GroupMemberManagementMember = function() | |||||
| fnRenderMemberForm = function( profile ) | fnRenderMemberForm = function( profile ) | ||||
| { | { | ||||
| let status = ''; | |||||
| if (profile) { | |||||
| status = profile.getGroupData(groupId).status; | |||||
| } | |||||
| $memberContainer = $content.find( '[data-id="member-container"]' ).first(); | $memberContainer = $content.find( '[data-id="member-container"]' ).first(); | ||||
| $memberContainer.html( | $memberContainer.html( | ||||
| app.core.View.getTemplate( | app.core.View.getTemplate( | ||||
| @@ -34,6 +38,7 @@ app.state.GroupMemberManagementMember = function() | |||||
| { | { | ||||
| p : profile, | p : profile, | ||||
| g : group, | g : group, | ||||
| status: status | |||||
| } | } | ||||
| ) | ) | ||||
| ); | ); | ||||
| @@ -452,34 +452,6 @@ app.state.Home = function() | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * Render appointments according to pager setting | |||||
| * @param pageNo | |||||
| */ | |||||
| function updatePaging( pageNo ) | |||||
| { | |||||
| var pager = self.createPager( | |||||
| appointments, | |||||
| +pageNo | |||||
| ), | |||||
| $content = app.core.View.getContent(); | |||||
| $content.html( | |||||
| app.core.View.getTemplate( | |||||
| 'home', | |||||
| { | |||||
| appointments : pager.pageElements, | |||||
| pager : pager, | |||||
| filter : filter, | |||||
| groupsNotActiveString: groupsNotActiveString, | |||||
| } | |||||
| ) | |||||
| ); | |||||
| // Animate scroll to top | |||||
| $("html, body").animate({ scrollTop: 0 }); | |||||
| } | |||||
| // Note | // Note | ||||
| // This needs to be called once at the beginning to trigger correct handlers and body classes | // This needs to be called once at the beginning to trigger correct handlers and body classes | ||||
| app.core.View.setContent( 'Loading...' ); | app.core.View.setContent( 'Loading...' ); | ||||
| @@ -511,14 +483,45 @@ app.state.Home = function() | |||||
| ) | ) | ||||
| ); | ); | ||||
| } | } | ||||
| self.appointments = appointments; | self.appointments = appointments; | ||||
| updatePaging(); | |||||
| $content = app.core.View.getContent(); | |||||
| $content.html( | |||||
| app.core.View.getTemplate( | |||||
| 'home', | |||||
| { | |||||
| appointments: appointments, | |||||
| filter : filter, | |||||
| groupsNotActiveString: groupsNotActiveString, | |||||
| } | |||||
| ) | |||||
| ); | |||||
| // Animate scroll to top | |||||
| $("html, body").animate({ scrollTop: 0 }); | |||||
| $content.on( 'change', '[data-id="pager"]', function( e ) | |||||
| $content.on('input', '[data-id="appointment-search-filter"]', function(e) | |||||
| { | { | ||||
| updatePaging( +$( this ).val() ); | |||||
| var searchTerm = $(this).val().toLowerCase(); | |||||
| // Filter elements | |||||
| $('[data-type="appointment-item-container"]').each(function() { | |||||
| var $div = $(this); | |||||
| let appCat = $div.find('.appointment-category').text().toLowerCase(); | |||||
| let appSub = $div.find('.appointment-subject').text().toLowerCase(); | |||||
| let appDate = $div.find('.appointment-datetime').text().toLowerCase(); | |||||
| if (appCat.includes(searchTerm) || | |||||
| appSub.includes(searchTerm) || | |||||
| appDate.includes(searchTerm) || | |||||
| searchTerm === '') { | |||||
| $div.show(); | |||||
| } else { | |||||
| $div.hide(); | |||||
| } | |||||
| }); | |||||
| }); | }); | ||||
| $content.on( 'click', '[data-type="appointment-short-info"]', function( e ) | $content.on( 'click', '[data-type="appointment-short-info"]', function( e ) | ||||
| @@ -104,7 +104,8 @@ app.state.StatsExport = function() | |||||
| 'Team', | 'Team', | ||||
| 'getDetails', | 'getDetails', | ||||
| { | { | ||||
| teamId : groupId | |||||
| teamId : groupId, | |||||
| includeMembers: true | |||||
| }, | }, | ||||
| function( res2 ) | function( res2 ) | ||||
| { | { | ||||
| @@ -44,7 +44,7 @@ | |||||
| <% } %> | <% } %> | ||||
| </div> | </div> | ||||
| <div class="appointment-detail-appointment-name"> | <div class="appointment-detail-appointment-name"> | ||||
| <%= a.getSubject() %> | |||||
| <%= a.getIcon() %> <%= a.getSubject() %> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -24,22 +24,46 @@ | |||||
| value="<%= mTeam.getId() %>" /> | value="<%= mTeam.getId() %>" /> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label for="input-subject"> | |||||
| <%= _lc( 'APPOINTMENT_SUBJECT' ) %> | |||||
| </label> | |||||
| <input type="text" | |||||
| minlength="3" | |||||
| maxlength="255" | |||||
| name="subject" | |||||
| data-id="input-subject" | |||||
| id="input-subject" | |||||
| class="form-control" | |||||
| value="<%= a.getSubject() %>" | |||||
| autocomplete="off" | |||||
| required /> | |||||
| <div class="invalid-feedback"> | |||||
| <%= _lc( 'VALIDATION_INPUT_REQUIRED' ) %> | |||||
| <div class="form-row"> | |||||
| <div class="col col-2"> | |||||
| <label for="input-subject-icon"> | |||||
| <%= _lc( 'APPOINTMENT_ICON' ) %> | |||||
| </label> | |||||
| <select class="form-control" name="categoryicon" id="input-subject-icon"> | |||||
| <option value="" <%= a.getIcon() === "" ? 'selected' : '' %>>- kein -</option> | |||||
| <option value="⚘" <%= a.getIcon() === "❀" ? 'selected' : '' %>>⚘</option> | |||||
| <option value="⚑" <%= a.getIcon() === "⚑" ? 'selected' : '' %>>⚑</option> | |||||
| <option value="✗" <%= a.getIcon() === "✗" ? 'selected' : '' %>>✗</option> | |||||
| <option value="✩" <%= a.getIcon() === "✩" ? 'selected' : '' %>>✩</option> | |||||
| <option value="✽" <%= a.getIcon() === "✽" ? 'selected' : '' %>>✽</option> | |||||
| <option value="❤" <%= a.getIcon() === "❤" ? 'selected' : '' %>>❤</option> | |||||
| <option value="➔" <%= a.getIcon() === "➔" ? 'selected' : '' %>>➔</option> | |||||
| <option value="⚙" <%= a.getIcon() === "⚙" ? 'selected' : '' %>>⚙</option> | |||||
| <option value="⚇" <%= a.getIcon() === "⚇" ? 'selected' : '' %>>⚇</option> | |||||
| <option value="☼" <%= a.getIcon() === "☼" ? 'selected' : '' %>>☼</option> | |||||
| <option value="☻" <%= a.getIcon() === "☻" ? 'selected' : '' %>>☻</option> | |||||
| </select> | |||||
| </div> | |||||
| <div class="col col-10"> | |||||
| <label for="input-subject"> | |||||
| <%= _lc( 'APPOINTMENT_SUBJECT' ) %> | |||||
| </label> | |||||
| <input type="text" | |||||
| minlength="3" | |||||
| maxlength="255" | |||||
| name="subject" | |||||
| data-id="input-subject" | |||||
| id="input-subject" | |||||
| class="form-control" | |||||
| value="<%= a.getSubject() %>" | |||||
| autocomplete="off" | |||||
| required /> | |||||
| <div class="invalid-feedback"> | |||||
| <%= _lc( 'VALIDATION_INPUT_REQUIRED' ) %> | |||||
| </div> | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <% var categories = mTeam.getAdminCourseCategoriesForProfile( currentProfile ); %> | <% var categories = mTeam.getAdminCourseCategoriesForProfile( currentProfile ); %> | ||||
| @@ -38,20 +38,44 @@ | |||||
| </div> | </div> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label for="input-subject"> | |||||
| <%= _lc( 'APPOINTMENT_SUBJECT' ) %> | |||||
| </label> | |||||
| <input type="text" | |||||
| minlength="3" | |||||
| maxlength="255" | |||||
| name="subject" | |||||
| data-id="input-subject" | |||||
| id="input-subject" | |||||
| class="form-control" | |||||
| autocomplete="off" | |||||
| required /> | |||||
| <div class="invalid-feedback"> | |||||
| <%= _lc( 'VALIDATION_INPUT_REQUIRED' ) %> | |||||
| <div class="form-row"> | |||||
| <div class="col col-2"> | |||||
| <label for="input-subject-icon"> | |||||
| <%= _lc( 'APPOINTMENT_ICON' ) %> | |||||
| </label> | |||||
| <select class="form-control" | |||||
| name="categoryicon" id="input-subject-icon"> | |||||
| <option value="">- kein -</option> | |||||
| <option value="⚘">⚘</option> | |||||
| <option value="⚑">⚑</option> | |||||
| <option value="✗">✗</option> | |||||
| <option value="✩">✩</option> | |||||
| <option value="✽">✽</option> | |||||
| <option value="❤">❤</option> | |||||
| <option value="➔">➔</option> | |||||
| <option value="⚙">⚙</option> | |||||
| <option value="⚇">⚇</option> | |||||
| <option value="☼">☼</option> | |||||
| <option value="☻">☻</option> | |||||
| </select> | |||||
| </div> | |||||
| <div class="col col-10"> | |||||
| <label for="input-subject"> | |||||
| <%= _lc( 'APPOINTMENT_SUBJECT' ) %> | |||||
| </label> | |||||
| <input type="text" | |||||
| minlength="3" | |||||
| maxlength="255" | |||||
| name="subject" | |||||
| data-id="input-subject" | |||||
| id="input-subject" | |||||
| class="form-control" | |||||
| autocomplete="off" | |||||
| required /> | |||||
| <div class="invalid-feedback"> | |||||
| <%= _lc( 'VALIDATION_INPUT_REQUIRED' ) %> | |||||
| </div> | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -19,6 +19,23 @@ | |||||
| </a> | </a> | ||||
| </div> | </div> | ||||
| <% } else if (numFutureAttendances > 0) { %> | |||||
| <div class="card-header text-white bg-danger"> | |||||
| <i class="fas fa-info-circle"></i> <%= _lc( 'DELETION_NOT_POSSIBLE_INFO_HEADLINE' ) %> | |||||
| </div> | |||||
| <div class="card-body"> | |||||
| <p> | |||||
| <strong><%=raw _lc( 'DELETION_NOT_POSSIBLE_INFO', [numFutureAttendances] ) %></strong> | |||||
| </p> | |||||
| </div> | |||||
| <div class="card-footer"> | |||||
| <a href="#/configuration/profile" | |||||
| class="btn btn-sm btn-secondary"> | |||||
| <%= _lc( 'BTN_BACK' ) %> | |||||
| </a> | |||||
| </div> | |||||
| <% } else { %> | <% } else { %> | ||||
| <form data-id="form-profile-deletion" | <form data-id="form-profile-deletion" | ||||
| @@ -1,289 +1,288 @@ | |||||
| <% if ( p ) { %> | <% if ( p ) { %> | ||||
| <% var currentUser = app.model.SessionUser.getUserProfile(); %> | |||||
| <div class="row"> | |||||
| <div class="col text-center"> | |||||
| <img class="img-fluid rounded-circle profile-image-big img-thumbnail" | |||||
| style="margin-bottom: 12px; padding: 6px" | |||||
| src="<%= p.getProfileImg() %>" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col text-center"> | |||||
| <div style="font-size: 1.4rem"> | |||||
| <%=raw p.getName() %> | |||||
| </div> | |||||
| <% if ( p.isInGroupCategory( 'DOGSCHOOL' ) ) { %> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'PROFILE_DOGNAME' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <%= p.getCustomGroupProperty( 'DOGSCHOOL', 'dogname' ) ? p.getCustomGroupProperty( 'DOGSCHOOL', 'dogname' ) : '---' %> | |||||
| </div> | |||||
| <% } %> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'PROFILE_STATUS' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <%= p.getStatus() %> | |||||
| <% var currentUser = app.model.SessionUser.getUserProfile(); %> | |||||
| <div class="row"> | |||||
| <div class="col text-center"> | |||||
| <img class="img-fluid rounded-circle profile-image-big img-thumbnail" | |||||
| style="margin-bottom: 12px; padding: 6px" | |||||
| src="<%= p.getProfileImg() %>" /> | |||||
| </div> | </div> | ||||
| <div class="profile-header"> | |||||
| <%= _lc( 'ADDRESS' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <%= p.getStreet() ? p.getStreet() : '---' %><br /> | |||||
| <%= p.getZipCode() ? p.getZipCode() : '' %> <%= p.getCity() ? p.getCity() : '' %> | |||||
| </div> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'PHONE' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <% if ( p.getMobile() ) { %> | |||||
| <a href="tel:<%= p.getMobile() %>"><%= p.getMobile() %></a> | |||||
| <% } else { %> | |||||
| --- | |||||
| <% } %> | |||||
| <br /> | |||||
| <% if ( p.getPhone() ) { %> | |||||
| <a href="tel:<%= p.getPhone() %>"><%= p.getPhone() %></a> | |||||
| <% } else { %> | |||||
| --- | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col text-center"> | |||||
| <div style="font-size: 1.4rem"> | |||||
| <%=raw p.getName() %> | |||||
| </div> | |||||
| <% if ( p.isInGroupCategory( 'DOGSCHOOL' ) ) { %> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'PROFILE_DOGNAME' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <%= p.getCustomGroupProperty( 'DOGSCHOOL', 'dogname' ) ? p.getCustomGroupProperty( 'DOGSCHOOL', 'dogname' ) : '---' %> | |||||
| </div> | |||||
| <% } %> | <% } %> | ||||
| </div> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'EMAIL' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <% if ( p.getEmail() ) { %> | |||||
| <a href="mailto:<%= p.getEmail() %>"><%= p.getEmail() %></a> | |||||
| <% if ( p.getEmailValidated() === true ) { %> | |||||
| <div> ( <i class="far fa-check-circle text-success"></i> <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_EMAIL_VALIDATED' ) %> )</div> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'PROFILE_STATUS' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <%= p.getStatus() %> | |||||
| </div> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'ADDRESS' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <%= p.getStreet() ? p.getStreet() : '---' %><br /> | |||||
| <%= p.getZipCode() ? p.getZipCode() : '' %> <%= p.getCity() ? p.getCity() : '' %> | |||||
| </div> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'PHONE' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <% if ( p.getMobile() ) { %> | |||||
| <a href="tel:<%= p.getMobile() %>"><%= p.getMobile() %></a> | |||||
| <% } else { %> | |||||
| --- | |||||
| <% } %> | |||||
| <br /> | |||||
| <% if ( p.getPhone() ) { %> | |||||
| <a href="tel:<%= p.getPhone() %>"><%= p.getPhone() %></a> | |||||
| <% } else { %> | |||||
| --- | |||||
| <% } %> | |||||
| </div> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'EMAIL' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <% if ( p.getEmail() ) { %> | |||||
| <a href="mailto:<%= p.getEmail() %>"><%= p.getEmail() %></a> | |||||
| <% if ( p.getEmailValidated() === true ) { %> | |||||
| <div> ( <i class="far fa-check-circle text-success"></i> <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_EMAIL_VALIDATED' ) %> )</div> | |||||
| <% } else { %> | |||||
| <div> ( <i class="far fa-window-close text-danger"></i> <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_EMAIL_NOT_VALIDATED' ) %> )</div> | |||||
| <% } %> | |||||
| <% } else { %> | <% } else { %> | ||||
| <div> ( <i class="far fa-window-close text-danger"></i> <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_EMAIL_NOT_VALIDATED' ) %> )</div> | |||||
| --- | |||||
| <% } %> | <% } %> | ||||
| <% } else { %> | |||||
| --- | |||||
| <% } %> | |||||
| </div> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'BIRTHDAY' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <%= ( null != p.getMomentBirthday() ) ? p.getMomentBirthday().format( 'DD.MM.YYYY' ) : '---' %> | |||||
| </div> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'JOIN_DT' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <% var momentJoin = p.getMomentJoinInGroup( g.getId() ); %> | |||||
| <%= ( null != momentJoin ) ? momentJoin.format( 'DD.MM.YYYY' ) : '---' %> | |||||
| </div> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'BIRTHDAY' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <%= ( null != p.getMomentBirthday() ) ? p.getMomentBirthday().format( 'DD.MM.YYYY' ) : '---' %> | |||||
| </div> | |||||
| <div class="profile-header"> | |||||
| <%= _lc( 'JOIN_DT' ) %> | |||||
| </div> | |||||
| <div class="profile-content"> | |||||
| <% var momentJoin = p.getMomentJoinInGroup( g.getId() ); %> | |||||
| <%= ( null != momentJoin ) ? momentJoin.format( 'DD.MM.YYYY' ) : '---' %> | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'CHANGE_ROLE' ) %></strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col-6"> | |||||
| <select class="form-control form-control-sm" | |||||
| <%= ( 'trainer' === p.getRoleInGroup( g.getId() ) ) ? 'disabled="disabled"' : '' %> | |||||
| data-id="select-member-role"> | |||||
| <option value="player" <%= ( 'player' === p.getRoleInGroup( g.getId() ) ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_MEMBER' ) %> | |||||
| </option> | |||||
| <option value="cotrainer" <%= ( 'cotrainer' === p.getRoleInGroup( g.getId() ) ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_ADMIN' ) %> | |||||
| </option> | |||||
| <% if ( 'trainer' === p.getRoleInGroup( g.getId() ) ) { %> | |||||
| <option value="trainer" <%= ( 'trainer' === p.getRoleInGroup( g.getId() ) ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_OWNER' ) %> | |||||
| </option> | |||||
| <% } %> | |||||
| </select> | |||||
| </div> | |||||
| <div class="col"> | |||||
| <button class="btn btn-primary btn-sm" | |||||
| data-id="btn-update-role"> | |||||
| <%= _lc( 'BTN_SAVE_NEW_ROLE' ) %> | |||||
| </button> | |||||
| </div> | |||||
| <% if ( 'trainer' === p.getRoleInGroup( g.getId() ) ) { %> | |||||
| <div class="col-sm-12"> | |||||
| <small><i><%= _lc( 'CANNOT_CHANGE_GROUP_OWNER_ROLE_INFO' ) %></i></small> | |||||
| </div> | |||||
| <% } %> | |||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'GROUP_MANAGEMENT_MEMBERS_CHANGE_STATUS' ) %></strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col-6"> | |||||
| <select class="form-control form-control-sm" data-id="select-member-status" | |||||
| <%= ( p.getId() === currentUser.getId() ) ? 'disabled="disabled"' : '' %> | |||||
| > | |||||
| <option value="active" <%= ( 'active' === p.getGroupData( g.getId() ).status ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_ACTIVE' ) %> | |||||
| </option> | |||||
| <option value="inactive" <%= ( 'inactive' === p.getGroupData( g.getId() ).status ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_INACTIVE' ) %> | |||||
| </option> | |||||
| <option value="not_approved" <%= ( 'not_approved' === p.getGroupData( g.getId() ).status ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_NOT_APPROVED' ) %> | |||||
| </option> | |||||
| </select> | |||||
| </div> | |||||
| <div class="col"> | |||||
| <button class="btn btn-primary btn-sm" data-id="btn-update-status"> | |||||
| <%= _lc( 'BTN_GROUP_MANAGEMENT_MEMBERS_SAVE_STATUS' ) %> | |||||
| </button> | |||||
| </div> | |||||
| <% if ( p.getId() === currentUser.getId() ) { %> | |||||
| <div class="col-sm-12"> | |||||
| <small><i><%= _lc( 'GROUP_MANAGEMENT_MEMBERS_CHANGE_STATUS_INFO' ) %></i></small> | |||||
| </div> | |||||
| <% } %> | |||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'ASSIGNED_MEMBER_GROUP_CATEGORIES' ) %></strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <% var cgs = g.getCourseCategoriesForProfile( p ); %> | |||||
| <% for ( var cgsi = 0; cgsi < cgs.length; cgsi++ ) { %> | |||||
| <% if ( cgsi > 0 ) { %> | |||||
| <%= ' ' %> | |||||
| <% } %> | |||||
| <span class="badge badge-pill badge-primary"><%= cgs[ cgsi ].name %></span> | |||||
| <hr /> | |||||
| <% if ( status !== 'deleted' ) { %> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'CHANGE_ROLE' ) %></strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col-6"> | |||||
| <select class="form-control form-control-sm" | |||||
| <%= ( 'trainer' === p.getRoleInGroup( g.getId() ) ) ? 'disabled="disabled"' : '' %> | |||||
| data-id="select-member-role"> | |||||
| <option value="player" <%= ( 'player' === p.getRoleInGroup( g.getId() ) ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_MEMBER' ) %> | |||||
| </option> | |||||
| <option value="cotrainer" <%= ( 'cotrainer' === p.getRoleInGroup( g.getId() ) ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_ADMIN' ) %> | |||||
| </option> | |||||
| <% if ( 'trainer' === p.getRoleInGroup( g.getId() ) ) { %> | |||||
| <option value="trainer" <%= ( 'trainer' === p.getRoleInGroup( g.getId() ) ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_OWNER' ) %> | |||||
| </option> | |||||
| <% } %> | |||||
| </select> | |||||
| </div> | |||||
| <div class="col"> | |||||
| <button class="btn btn-primary btn-sm" | |||||
| data-id="btn-update-role"> | |||||
| <%= _lc( 'BTN_SAVE_NEW_ROLE' ) %> | |||||
| </button> | |||||
| </div> | |||||
| <% if ( 'trainer' === p.getRoleInGroup( g.getId() ) ) { %> | |||||
| <div class="col-sm-12"> | |||||
| <small><i><%= _lc( 'CANNOT_CHANGE_GROUP_OWNER_ROLE_INFO' ) %></i></small> | |||||
| </div> | |||||
| <% } %> | |||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'GROUP_MANAGEMENT_MEMBERS_CHANGE_STATUS' ) %></strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col-6"> | |||||
| <select class="form-control form-control-sm" data-id="select-member-status" | |||||
| <%= ( p.getId() === currentUser.getId() ) ? 'disabled="disabled"' : '' %> | |||||
| > | |||||
| <option value="active" <%= ( 'active' === p.getGroupData( g.getId() ).status ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_ACTIVE' ) %> | |||||
| </option> | |||||
| <option value="inactive" <%= ( 'inactive' === p.getGroupData( g.getId() ).status ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_INACTIVE' ) %> | |||||
| </option> | |||||
| <option value="not_approved" <%= ( 'not_approved' === p.getGroupData( g.getId() ).status ) ? 'selected="selected"' : '' %> > | |||||
| <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_NOT_APPROVED' ) %> | |||||
| </option> | |||||
| </select> | |||||
| </div> | |||||
| <div class="col"> | |||||
| <button class="btn btn-primary btn-sm" data-id="btn-update-status"> | |||||
| <%= _lc( 'BTN_GROUP_MANAGEMENT_MEMBERS_SAVE_STATUS' ) %> | |||||
| </button> | |||||
| </div> | |||||
| <% if ( p.getId() === currentUser.getId() ) { %> | |||||
| <div class="col-sm-12"> | |||||
| <small><i><%= _lc( 'GROUP_MANAGEMENT_MEMBERS_CHANGE_STATUS_INFO' ) %></i></small> | |||||
| </div> | |||||
| <% } %> | |||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'ASSIGNED_MEMBER_GROUP_CATEGORIES' ) %></strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <% var cgs = g.getCourseCategoriesForProfile( p ); %> | |||||
| <% for ( var cgsi = 0; cgsi < cgs.length; cgsi++ ) { %> | |||||
| <% if ( cgsi > 0 ) { %> | |||||
| <%= ' ' %> | |||||
| <% } %> | |||||
| <span class="badge badge-pill badge-primary"><%= cgs[ cgsi ].name %></span> | |||||
| <% } %> | |||||
| </div> | |||||
| <div class="col-sm-12"> | |||||
| <small><i><%= _lc( 'ASSIGNED_MEMBER_CATEOGORY_INFO' ) %></i></small> | |||||
| </div> | |||||
| <div class="col"> | |||||
| <a href="#/course/categories/<%= g.getId() %>" | |||||
| class="btn btn-primary btn-sm"> | |||||
| <%= _lc( 'BTN_NAVIGATE_TO_CATEGORY_MANAGEMENT' ) %> | |||||
| </a> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'MEMBER_CONTRACT' ) %></strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col-6"> | |||||
| <div class="form-group" style="margin-bottom:0;"> | |||||
| <label for="appointment-log-until"> | |||||
| <%= _lc( 'CONTRACT_NAME' ) %> | |||||
| </label> | |||||
| <input type="text" | |||||
| class="form-control" | |||||
| maxlength="128" | |||||
| value="<%= p.getContractInGroup( g.getId() ) ? p.getContractInGroup( g.getId() ) : '' %>" | |||||
| data-id="input-contract" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-6"> | |||||
| <div class="form-group" style="margin-bottom:0;"> | |||||
| <label for="contract-date"> | |||||
| <%= _lc( 'CONTRACT_DATE' ) %> | |||||
| </label> | |||||
| <input type="date" | |||||
| id="contract-date" | |||||
| value="<%= p.getContractMomentInGroup( g.getId() ) ? p.getContractMomentInGroup( g.getId() ).format( 'YYYY-MM-DD' ) : '' %>" | |||||
| data-id="input-contract-moment" | |||||
| class="form-control" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col"> | |||||
| <button class="btn btn-primary btn-sm" | |||||
| data-id="btn-update-contract"> | |||||
| <%= _lc( 'BTN_SAVE_MEMBER_CONTRACT' ) %> | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'APPOINTMENT_LOG' ) %></strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col-lg-6 col-md-12"> | |||||
| <div class="form-group" style="margin-bottom:0;"> | |||||
| <label for="appointment-log-from"> | |||||
| <%= _lc( 'FROM' ) %> | |||||
| </label> | |||||
| <input type="date" | |||||
| id="appointment-log-from" | |||||
| value="<%= p.getContractMomentInGroup( g.getId() ) ? p.getContractMomentInGroup( g.getId() ).format( 'YYYY-MM-DD' ) : moment().subtract( 30, 'days' ).format( 'YYYY-MM-DD' ) %>" | |||||
| data-id="input-appointment-log-from" | |||||
| class="form-control" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-lg-6 col-md-12"> | |||||
| <div class="form-group" style="margin-bottom:0;"> | |||||
| <label for="appointment-log-until"> | |||||
| <%= _lc( 'UNTIL' ) %> | |||||
| </label> | |||||
| <input type="date" | |||||
| id="appointment-log-until" | |||||
| value="<%= moment().format( 'YYYY-MM-DD' ) %>" | |||||
| data-id="input-appointment-log-until" | |||||
| class="form-control" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col"> | |||||
| <button class="btn btn-primary btn-sm" | |||||
| data-id="btn-update-appointment-log"> | |||||
| <%= _lc( 'BTN_UPDATE_APPOINTMENT_LOG' ) %> | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col" | |||||
| data-id="container-appointment-log"> | |||||
| <i class="fas fa-spinner fa-spin"></i> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'ADMIN_NOTES' ) %></strong><small> - <%= _lc( 'ADMIN_NOTES_INFO' ) %></small> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col" | |||||
| data-id="admin-note"> | |||||
| <i class="fas fa-spinner fa-spin"></i> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <button class="btn btn-primary btn-sm" | |||||
| data-id="btn-save-admin-note"> | |||||
| <%= _lc( 'BTN_SAVE_ADMIN_NOTE' ) %> | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| <% } %> | <% } %> | ||||
| </div> | |||||
| <div class="col-sm-12"> | |||||
| <small><i><%= _lc( 'ASSIGNED_MEMBER_CATEOGORY_INFO' ) %></i></small> | |||||
| </div> | |||||
| <div class="col"> | |||||
| <a href="#/course/categories/<%= g.getId() %>" | |||||
| class="btn btn-primary btn-sm"> | |||||
| <%= _lc( 'BTN_NAVIGATE_TO_CATEGORY_MANAGEMENT' ) %> | |||||
| </a> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'MEMBER_CONTRACT' ) %></strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col-6"> | |||||
| <div class="form-group" style="margin-bottom:0;"> | |||||
| <label for="appointment-log-until"> | |||||
| <%= _lc( 'CONTRACT_NAME' ) %> | |||||
| </label> | |||||
| <input type="text" | |||||
| class="form-control" | |||||
| maxlength="128" | |||||
| value="<%= p.getContractInGroup( g.getId() ) ? p.getContractInGroup( g.getId() ) : '' %>" | |||||
| data-id="input-contract" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-6"> | |||||
| <div class="form-group" style="margin-bottom:0;"> | |||||
| <label for="contract-date"> | |||||
| <%= _lc( 'CONTRACT_DATE' ) %> | |||||
| </label> | |||||
| <input type="date" | |||||
| id="contract-date" | |||||
| value="<%= p.getContractMomentInGroup( g.getId() ) ? p.getContractMomentInGroup( g.getId() ).format( 'YYYY-MM-DD' ) : '' %>" | |||||
| data-id="input-contract-moment" | |||||
| class="form-control" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col"> | |||||
| <button class="btn btn-primary btn-sm" | |||||
| data-id="btn-update-contract"> | |||||
| <%= _lc( 'BTN_SAVE_MEMBER_CONTRACT' ) %> | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'APPOINTMENT_LOG' ) %></strong> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col-lg-6 col-md-12"> | |||||
| <div class="form-group" style="margin-bottom:0;"> | |||||
| <label for="appointment-log-from"> | |||||
| <%= _lc( 'FROM' ) %> | |||||
| </label> | |||||
| <input type="date" | |||||
| id="appointment-log-from" | |||||
| value="<%= p.getContractMomentInGroup( g.getId() ) ? p.getContractMomentInGroup( g.getId() ).format( 'YYYY-MM-DD' ) : moment().subtract( 30, 'days' ).format( 'YYYY-MM-DD' ) %>" | |||||
| data-id="input-appointment-log-from" | |||||
| class="form-control" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-lg-6 col-md-12"> | |||||
| <div class="form-group" style="margin-bottom:0;"> | |||||
| <label for="appointment-log-until"> | |||||
| <%= _lc( 'UNTIL' ) %> | |||||
| </label> | |||||
| <input type="date" | |||||
| id="appointment-log-until" | |||||
| value="<%= moment().format( 'YYYY-MM-DD' ) %>" | |||||
| data-id="input-appointment-log-until" | |||||
| class="form-control" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col"> | |||||
| <button class="btn btn-primary btn-sm" | |||||
| data-id="btn-update-appointment-log"> | |||||
| <%= _lc( 'BTN_UPDATE_APPOINTMENT_LOG' ) %> | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col" | |||||
| data-id="container-appointment-log"> | |||||
| <i class="fas fa-spinner fa-spin"></i> | |||||
| </div> | |||||
| </div> | |||||
| <hr /> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <strong><%= _lc( 'ADMIN_NOTES' ) %></strong><small> - <%= _lc( 'ADMIN_NOTES_INFO' ) %></small> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col" | |||||
| data-id="admin-note"> | |||||
| <i class="fas fa-spinner fa-spin"></i> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <button class="btn btn-primary btn-sm" | |||||
| data-id="btn-save-admin-note"> | |||||
| <%= _lc( 'BTN_SAVE_ADMIN_NOTE' ) %> | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| <% } else { %> | <% } else { %> | ||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <%= _lc( 'SELECT_MEMBER_TO_EDIT' ) %> | |||||
| <div class="row"> | |||||
| <div class="col"> | |||||
| <%= _lc( 'SELECT_MEMBER_TO_EDIT' ) %> | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | |||||
| <% } %> | <% } %> | ||||
| @@ -46,6 +46,12 @@ | |||||
| <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_NOT_APPROVED' ) %> (<%= membersNotApproved.length %>) | <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_NOT_APPROVED' ) %> (<%= membersNotApproved.length %>) | ||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <li class="nav-item"> | |||||
| <a class="nav-link <%= ( activeTab == 'deleted' ) ? 'active' : '' %>" data-content="group-members-deleted"> | |||||
| <i class="fas fa-users"></i> | |||||
| <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_DELETED' ) %> (<%= membersDeleted.length %>) | |||||
| </a> | |||||
| </li> | |||||
| </ul> | </ul> | ||||
| </div> | </div> | ||||
| <div class="card-body" data-id="member-container"> | <div class="card-body" data-id="member-container"> | ||||
| @@ -86,6 +92,18 @@ | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div id="group-members-deleted" class="group-members-content group-members-tab-content <%= ( activeTab == 'deleted' ) ? 'active' : '' %>"> | |||||
| <div class="table-responsive"> | |||||
| <table class="table table-profiles"> | |||||
| <tbody> | |||||
| <% for ( var mi = 0; mi < membersDeleted.length; mi++ ) { %> | |||||
| <%=raw app.core.View.getTemplate( 'group-member-management-row', { m : membersDeleted[ mi ], g : group } ) %> | |||||
| <% } %> | |||||
| </tbody> | |||||
| </table> | |||||
| </div> | |||||
| </div> | |||||
| </div> | </div> | ||||
| <div class="card-footer"> | <div class="card-footer"> | ||||
| </div> | </div> | ||||
| @@ -1,4 +1,4 @@ | |||||
| <nav class="navbar fixed-top bg-gradient"> | |||||
| <nav class="navbar fixed-bottom bg-gradient"> | |||||
| <div class="container-fluid"> | <div class="container-fluid"> | ||||
| <a class="navbar-brand" href="#/home"> | <a class="navbar-brand" href="#/home"> | ||||
| <!-- | <!-- | ||||
| @@ -46,7 +46,7 @@ | |||||
| <% } %> | <% } %> | ||||
| </div> | </div> | ||||
| <div class="appointment-subject"> | <div class="appointment-subject"> | ||||
| <%= a.getSubject() %> | |||||
| <%= a.getIcon() %> <%= a.getSubject() %> | |||||
| <% if ( a.isDraft() ) { %> | <% if ( a.isDraft() ) { %> | ||||
| <span class="badge badge-warning">Entwurf</span> | <span class="badge badge-warning">Entwurf</span> | ||||
| <% } %> | <% } %> | ||||
| @@ -58,7 +58,8 @@ | |||||
| <% displayedDateStart = mStart.format( 'ddd' ) + ' ' + mStart.format( 'D.M.' ) + ' ' + mStart.format( 'HH:mm' ) %> | <% displayedDateStart = mStart.format( 'ddd' ) + ' ' + mStart.format( 'D.M.' ) + ' ' + mStart.format( 'HH:mm' ) %> | ||||
| <% displayedDateEnd = mEnd.format( 'ddd' ) + ' ' + mEnd.format( 'D.M.' ) + ' ' + mEnd.format( 'HH:mm' ) %> | <% displayedDateEnd = mEnd.format( 'ddd' ) + ' ' + mEnd.format( 'D.M.' ) + ' ' + mEnd.format( 'HH:mm' ) %> | ||||
| <% } %> | <% } %> | ||||
| <i class="far fa-clock"></i> <%= displayedDateStart %> - <%= displayedDateEnd %> | <i class="far fa-thumbs-up"></i> <%= a.getNumAttendeesAccepted() %> <% if ( 0 < a.getMaxAttendees() ) { %> | <%= a.getMaxAttendees() - a.getNumAttendeesAccepted() %> verfügbar <% } %> | <%= group ? group.getName() : '' %> | |||||
| <% var available = a.getMaxAttendees() - a.getNumAttendeesAccepted() >= 0 ? a.getMaxAttendees() - a.getNumAttendeesAccepted() : 0 %> | |||||
| <i class="far fa-clock"></i> <%= displayedDateStart %> - <%= displayedDateEnd %> | <i class="far fa-thumbs-up"></i> <%= a.getNumAttendeesAccepted() %> <% if ( 0 < a.getMaxAttendees() ) { %> | <%= available %> verfügbar <% } %> | <%= group ? group.getName() : '' %> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <% if ( a.getVProfileCanAttend() ) { %> | <% if ( a.getVProfileCanAttend() ) { %> | ||||
| @@ -1,4 +1,4 @@ | |||||
| <form> | |||||
| <form style="overflow: auto; overflow-x: hidden; max-height: 60vh"> | |||||
| <div class="row"> | <div class="row"> | ||||
| <div class="col"> | <div class="col"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| @@ -79,11 +79,17 @@ | |||||
| <h4> | <h4> | ||||
| <%= currentMonth %> | <%= currentMonth %> | ||||
| <% if ( monthHeader === null ) { %> | <% if ( monthHeader === null ) { %> | ||||
| <span class="float-right clickable" style="margin-right: 12px"> | |||||
| <div data-type="btn-filter" | |||||
| class="<%= ( false === filter.isModified ) ? 'text-primary' : 'text-danger' %>" | |||||
| style="font-size: smaller"> | |||||
| <i class="fas fa-filter"></i> | |||||
| <span class="float-right clickable search-content" style="margin-right: 12px"> | |||||
| <div class="right-content"> | |||||
| <div class="search-box"> | |||||
| <i class="fa fa-search"></i> | |||||
| <input data-id="appointment-search-filter" type="text" class="form-control form-control-sm"> | |||||
| </div> | |||||
| <div data-type="btn-filter" | |||||
| class="<%= ( false === filter.isModified ) ? 'text-primary' : 'text-danger' %>" | |||||
| style="font-size: smaller"> | |||||
| <i class="fas fa-filter"></i> | |||||
| </div> | |||||
| </div> | </div> | ||||
| </span> | </span> | ||||
| <% } %> | <% } %> | ||||
| @@ -110,6 +116,4 @@ | |||||
| </div> | </div> | ||||
| <% } %> | <% } %> | ||||
| <%=raw app.core.View.getTemplate( 'home-pager', { pager : pager } ) %> | |||||
| <% } %> | <% } %> | ||||
| @@ -85,8 +85,9 @@ | |||||
| </table> | </table> | ||||
| </div> | </div> | ||||
| /* todo: refactoring into css file */ | |||||
| <style> | <style> | ||||
| /* todo: refactoring into css file */ | |||||
| /* spawntree */ | /* spawntree */ | ||||
| .button-group { | .button-group { | ||||
| display: flex; | display: flex; | ||||
| @@ -9,6 +9,7 @@ | |||||
| <th>Postleitzahl</th> | <th>Postleitzahl</th> | ||||
| <th>Stadt</th> | <th>Stadt</th> | ||||
| <th>Telefon/Mobile</th> | <th>Telefon/Mobile</th> | ||||
| <th>Status</th> | |||||
| <th></th> | <th></th> | ||||
| </tr> | </tr> | ||||
| </thead> | </thead> | ||||
| @@ -22,6 +23,7 @@ | |||||
| <td><%= members[ mi ].zip ? members[ mi ].zip : '' %></td> | <td><%= members[ mi ].zip ? members[ mi ].zip : '' %></td> | ||||
| <td><%= members[ mi ].city ? members[ mi ].city : '' %></td> | <td><%= members[ mi ].city ? members[ mi ].city : '' %></td> | ||||
| <td><%= members[ mi ].phone ? members[ mi ].phone : '---' %> / <%= members[ mi ].mobile ? members[ mi ].mobile : '---' %></td> | <td><%= members[ mi ].phone ? members[ mi ].phone : '---' %> / <%= members[ mi ].mobile ? members[ mi ].mobile : '---' %></td> | ||||
| <td><%= members[ mi ].getGroupStatusText() %></td> | |||||
| <td class="text-center"> | <td class="text-center"> | ||||
| <button type="button" | <button type="button" | ||||
| data-type="btn-edit-memeber" | data-type="btn-edit-memeber" | ||||
| @@ -102,6 +102,30 @@ const UserProfile = { | |||||
| } | } | ||||
| return gd; | return gd; | ||||
| }, | }, | ||||
| getGroupStatus : function( groupId ) | |||||
| { | |||||
| if ( !groupId ) { | |||||
| groupId = this.contextGroupId; | |||||
| } | |||||
| return this.getGroupData(groupId).status; | |||||
| }, | |||||
| getGroupStatusText : function( groupId ) | |||||
| { | |||||
| if ( !groupId ) { | |||||
| groupId = this.contextGroupId; | |||||
| } | |||||
| switch (this.getGroupData(groupId).status) { | |||||
| case 'active': | |||||
| return 'aktiv'; | |||||
| case 'inactive': | |||||
| return 'inaktiv'; | |||||
| case 'not_approved': | |||||
| return 'unbestätigt'; | |||||
| case 'deleted': | |||||
| return 'gelöscht'; | |||||
| } | |||||
| return ''; | |||||
| }, | |||||
| getRoleName : function() | getRoleName : function() | ||||
| { | { | ||||
| var gd = this.getGroupData( this.contextGroupId ), | var gd = this.getGroupData( this.contextGroupId ), | ||||
| @@ -23,7 +23,8 @@ const AppointmentCalendar = { | |||||
| 'Team', | 'Team', | ||||
| 'getDetails', | 'getDetails', | ||||
| { | { | ||||
| teamId : p.groupId | |||||
| teamId : p.groupId, | |||||
| includeMembers: true | |||||
| }, | }, | ||||
| function( res ) | function( res ) | ||||
| { | { | ||||
| @@ -25,7 +25,8 @@ const AppointmentList = { | |||||
| 'Team', | 'Team', | ||||
| 'getDetails', | 'getDetails', | ||||
| { | { | ||||
| teamId : groupId | |||||
| teamId : groupId, | |||||
| includeMembers: true | |||||
| }, | }, | ||||
| function( res ) | function( res ) | ||||
| { | { | ||||
| @@ -40,7 +40,8 @@ const ContractCharging = { | |||||
| 'Team', | 'Team', | ||||
| 'getDetails', | 'getDetails', | ||||
| { | { | ||||
| teamId : groupId | |||||
| teamId : groupId, | |||||
| includeMembers: true | |||||
| }, | }, | ||||
| function( res ) | function( res ) | ||||
| { | { | ||||
| @@ -25,7 +25,8 @@ const ContractCreate = { | |||||
| 'Team', | 'Team', | ||||
| 'getDetails', | 'getDetails', | ||||
| { | { | ||||
| teamId : groupId | |||||
| teamId : groupId, | |||||
| includeMembers: true | |||||
| }, | }, | ||||
| function( res ) | function( res ) | ||||
| { | { | ||||
| @@ -27,7 +27,8 @@ const CoronaSpecial = { | |||||
| 'Team', | 'Team', | ||||
| 'getDetails', | 'getDetails', | ||||
| { | { | ||||
| teamId : groupId | |||||
| teamId : groupId, | |||||
| includeMembers: true | |||||
| }, | }, | ||||
| function( res ) | function( res ) | ||||
| { | { | ||||
| @@ -44,7 +44,8 @@ const EmployeeList = { | |||||
| 'Team', | 'Team', | ||||
| 'getDetails', | 'getDetails', | ||||
| { | { | ||||
| teamId : p.get( 'groupId' ) | |||||
| teamId : p.get( 'groupId' ), | |||||
| includeMembers: true | |||||
| }, | }, | ||||
| function( res ) | function( res ) | ||||
| { | { | ||||
| @@ -22,7 +22,9 @@ const MemberList = { | |||||
| 'Team', | 'Team', | ||||
| 'getDetails', | 'getDetails', | ||||
| { | { | ||||
| teamId : p.get( 'groupId' ) | |||||
| teamId : p.get( 'groupId' ), | |||||
| includeMembers: true | |||||
| }, | }, | ||||
| function( res ) | function( res ) | ||||
| { | { | ||||
| @@ -39,7 +41,7 @@ const MemberList = { | |||||
| this.createComponent( | this.createComponent( | ||||
| 'member-data-table', | 'member-data-table', | ||||
| $container.find( '[f-id="container-member-data-table"]' ).first().get( 0 ), | $container.find( '[f-id="container-member-data-table"]' ).first().get( 0 ), | ||||
| members | |||||
| members, | |||||
| ); | ); | ||||
| } | } | ||||
| $container.find( '.sk-loading' ).toggleClass( 'sk-loading' ); | $container.find( '.sk-loading' ).toggleClass( 'sk-loading' ); | ||||
| @@ -26,7 +26,8 @@ const SettingsAccess = { | |||||
| 'Team', | 'Team', | ||||
| 'getDetails', | 'getDetails', | ||||
| { | { | ||||
| teamId : groupId | |||||
| teamId : groupId, | |||||
| includeMembers: true | |||||
| }, | }, | ||||
| function( res ) | function( res ) | ||||
| { | { | ||||
| @@ -0,0 +1,38 @@ | |||||
| <?php | |||||
| require_once __DIR__ . '/../server/config/boot_global.php'; | |||||
| require_once __DIR__ . '/../server/config/boot_local.php'; | |||||
| function patch_addActiveStateToProfile() | |||||
| { | |||||
| $db = TB_Shared_Db_TeamData::get(); | |||||
| $sql = 'SELECT * FROM profile'; | |||||
| $stmt = $db->query( $sql ); | |||||
| $res = $stmt->fetchAll(); | |||||
| $db->beginTransaction(); | |||||
| foreach( $res as $row ) | |||||
| { | |||||
| if ( is_array( $row ) && isset( $row[ 'id' ] ) ) | |||||
| { | |||||
| $entProfile = TB_Shared_Ent_TeamData_Profile::get( $row[ 'id' ] ); | |||||
| $resTeam = []; | |||||
| foreach ($entProfile->teams_js as $team) { | |||||
| $tmpTeam = $team; | |||||
| $tmpTeam['status'] = 'active'; | |||||
| $resTeam[] = $tmpTeam; | |||||
| } | |||||
| $entProfile->teams_js = $resTeam; | |||||
| $entProfile->save(); | |||||
| unset($team); | |||||
| unset( $entProfile ); | |||||
| } | |||||
| } | |||||
| $db->commit(); | |||||
| echo "DONE..."; | |||||
| } | |||||
| patch_addActiveStateToProfile(); | |||||
| @@ -1,7 +1,7 @@ | |||||
| <?php | <?php | ||||
| require_once __DIR__ . '/../../src/server/server/config/boot_global.php'; | |||||
| require_once __DIR__ . '/../../src/server/server/config/boot_local.php'; | |||||
| require_once __DIR__ . '/../server/config/boot_global.php'; | |||||
| require_once __DIR__ . '/../server/config/boot_local.php'; | |||||
| function patch() | function patch() | ||||
| { | { | ||||
| @@ -1,7 +1,7 @@ | |||||
| <?php | <?php | ||||
| require_once __DIR__ . '/../../src/server/server/config/boot_global.php'; | |||||
| require_once __DIR__ . '/../../src/server/server/config/boot_local.php'; | |||||
| require_once __DIR__ . '/../server/config/boot_global.php'; | |||||
| require_once __DIR__ . '/../server/config/boot_local.php'; | |||||
| function patch_addTermsAcceptedToProfile() | function patch_addTermsAcceptedToProfile() | ||||
| { | { | ||||
| @@ -1,7 +1,7 @@ | |||||
| <?php | <?php | ||||
| require_once __DIR__ . '/../../src/server/server/config/boot_global.php'; | |||||
| require_once __DIR__ . '/../../src/server/server/config/boot_local.php'; | |||||
| require_once __DIR__ . '/../server/config/boot_global.php'; | |||||
| require_once __DIR__ . '/../server/config/boot_local.php'; | |||||
| function patch() | function patch() | ||||
| { | { | ||||
| @@ -9,7 +9,7 @@ function patch() | |||||
| $db = TB_Shared_Db_TeamData::get(); | $db = TB_Shared_Db_TeamData::get(); | ||||
| $sql = "ALTER TABLE team | $sql = "ALTER TABLE team | ||||
| ADD COLUMN new_users_inactive TINYINT(1) NOT NULL DEFAULT 1 AFTER terms_conditions_active;"; | |||||
| ADD COLUMN new_users_inactive TINYINT(1) NOT NULL DEFAULT 0 AFTER terms_conditions_active;"; | |||||
| $stmt = $db->query( $sql ); | $stmt = $db->query( $sql ); | ||||
| $sql = "UPDATE team SET new_users_inactive = 0 WHERE 1"; | $sql = "UPDATE team SET new_users_inactive = 0 WHERE 1"; | ||||
| @@ -0,0 +1,18 @@ | |||||
| <?php | |||||
| require_once __DIR__ . '/../server/config/boot_global.php'; | |||||
| require_once __DIR__ . '/../server/config/boot_local.php'; | |||||
| function patch() | |||||
| { | |||||
| $db = TB_Shared_Db_TeamData::get(); | |||||
| $sql = "ALTER TABLE appointment | |||||
| ADD COLUMN icon varchar(50) DEFAULT '' AFTER visibility;"; | |||||
| $stmt = $db->query( $sql ); | |||||
| echo "DONE..."; | |||||
| } | |||||
| patch(); | |||||
| @@ -42,5 +42,18 @@ function _lc( $txt, array $params = NULL ) { | |||||
| * | * | ||||
| */ | */ | ||||
| function _xss( $txt ) { | function _xss( $txt ) { | ||||
| return ( is_string( $txt ) ) ? filter_var( $txt, FILTER_SANITIZE_STRING ) : $txt; | |||||
| if (!is_string($txt)) { | |||||
| return $txt; | |||||
| } | |||||
| // Entfernt alle HTML- und PHP-Tags | |||||
| $txt = strip_tags($txt); | |||||
| // Konvertiert spezielle Zeichen in HTML-Entitäten | |||||
| $txt = htmlspecialchars($txt, ENT_QUOTES | ENT_HTML5, 'UTF-8'); | |||||
| // Entfernt Steuerzeichen | |||||
| $txt = preg_replace('/[\x00-\x1F\x7F]/u', '', $txt); | |||||
| return $txt; | |||||
| } | } | ||||
| @@ -6,13 +6,24 @@ | |||||
| // General | // General | ||||
| Francis_Utils_Config::set( 'url.client', 'src/client/app' ); | Francis_Utils_Config::set( 'url.client', 'src/client/app' ); | ||||
| // DB settings | |||||
| Francis_Utils_Config::set( 'db.tbcore.host', 'database' ); | |||||
| // DB settings (docker) | |||||
| //Francis_Utils_Config::set( 'db.tbcore.host', 'database' ); | |||||
| //Francis_Utils_Config::set( 'db.tbcore.name', 'pb_core' ); // probudy core | |||||
| //Francis_Utils_Config::set( 'db.tbcore.user', 'root' ); | |||||
| //Francis_Utils_Config::set( 'db.tbcore.pass', 'root' ); | |||||
| // | |||||
| //Francis_Utils_Config::set( 'db.tbteamdata.host', 'database' ); | |||||
| //Francis_Utils_Config::set( 'db.tbteamdata.name', 'pb_teamdata' ); | |||||
| //Francis_Utils_Config::set( 'db.tbteamdata.user', 'root' ); | |||||
| //Francis_Utils_Config::set( 'db.tbteamdata.pass', 'root' ); | |||||
| // DB settings (ddev) | |||||
| Francis_Utils_Config::set( 'db.tbcore.host', 'db' ); | |||||
| Francis_Utils_Config::set( 'db.tbcore.name', 'pb_core' ); // probudy core | Francis_Utils_Config::set( 'db.tbcore.name', 'pb_core' ); // probudy core | ||||
| Francis_Utils_Config::set( 'db.tbcore.user', 'root' ); | Francis_Utils_Config::set( 'db.tbcore.user', 'root' ); | ||||
| Francis_Utils_Config::set( 'db.tbcore.pass', 'root' ); | Francis_Utils_Config::set( 'db.tbcore.pass', 'root' ); | ||||
| Francis_Utils_Config::set( 'db.tbteamdata.host', 'database' ); | |||||
| Francis_Utils_Config::set( 'db.tbteamdata.host', 'db' ); | |||||
| Francis_Utils_Config::set( 'db.tbteamdata.name', 'pb_teamdata' ); | Francis_Utils_Config::set( 'db.tbteamdata.name', 'pb_teamdata' ); | ||||
| Francis_Utils_Config::set( 'db.tbteamdata.user', 'root' ); | Francis_Utils_Config::set( 'db.tbteamdata.user', 'root' ); | ||||
| Francis_Utils_Config::set( 'db.tbteamdata.pass', 'root' ); | Francis_Utils_Config::set( 'db.tbteamdata.pass', 'root' ); | ||||
| @@ -39,4 +50,4 @@ Francis_Utils_Config::set( 'slack.logWebhook', "https://hooks.slack.com/services | |||||
| Francis_Utils_Config::set( 'analytics.trackingId', 'UA-139730172-1' ); | Francis_Utils_Config::set( 'analytics.trackingId', 'UA-139730172-1' ); | ||||
| // Manager console | // Manager console | ||||
| Francis_Utils_Config::set( 'url.manager', 'https://manager.probuddy.de/' ); | |||||
| Francis_Utils_Config::set( 'url.manager', '/manager' ); | |||||
| @@ -3,6 +3,11 @@ | |||||
| * (c)1337 aheadware.com - All rights reserved | * (c)1337 aheadware.com - All rights reserved | ||||
| ********************************************************************************/ | ********************************************************************************/ | ||||
| require Francis_Utils_Config::get( 'path.root' ).'/vendor/autoload.php'; | |||||
| use Ramsey\Uuid\Uuid; | |||||
| /** | /** | ||||
| * Class TB_Server_Control_Account | * Class TB_Server_Control_Account | ||||
| */ | */ | ||||
| @@ -23,63 +28,67 @@ class TB_Server_Control_Account | |||||
| $resp->addData( 'profile', $session->getProfile() ); | $resp->addData( 'profile', $session->getProfile() ); | ||||
| $resp->addData( 'own_teams', TB_Shared_Ent_TeamData_Team::getByProfile( $session->getProfile() ) ); | $resp->addData( 'own_teams', TB_Shared_Ent_TeamData_Team::getByProfile( $session->getProfile() ) ); | ||||
| if ($params->get('includeNumFutureAttendances')) { | |||||
| $appointmentsAccepted = TB_Shared_Ent_TeamData_Appointment::getAcceptedByProfile( | |||||
| $session->getProfile()->id, | |||||
| false, | |||||
| true | |||||
| ); | |||||
| $resp->addData( 'numFutureAttendances', count($appointmentsAccepted) ); | |||||
| } | |||||
| return $resp; | return $resp; | ||||
| } | } | ||||
| public static function delete( TB_Server_Core_RequestData $params ) | public static function delete( TB_Server_Core_RequestData $params ) | ||||
| { | { | ||||
| throw new \Exception( 'Does not work anymore.' ); | |||||
| /* | |||||
| $resp = new TB_Server_Core_Response(); | $resp = new TB_Server_Core_Response(); | ||||
| $session = TB_Server_Core_Session::get(); | $session = TB_Server_Core_Session::get(); | ||||
| if ( is_null( $session ) ) { | |||||
| throw new Exception( "No session found. Deletion failed." ); | |||||
| } | |||||
| $account = $session->getAccount(); | $account = $session->getAccount(); | ||||
| if ( is_null( $account ) ) throw new Exception( "No account in session. Deletion failed." ); | |||||
| if ( is_null( $account ) ) { | |||||
| throw new Exception( "No account in session. Deletion failed." ); | |||||
| } | |||||
| $profile = $session->getProfile(); | $profile = $session->getProfile(); | ||||
| $appointmentsToUpdate = TB_Shared_Ent_TeamData_Appointment::getAcceptedByProfile( $profile->id ); | |||||
| $dbCore = TB_Shared_Db_Core::get(); | |||||
| $dbTeam = TB_Shared_Db_TeamData::get(); | |||||
| if ( is_null( $profile ) ) { | |||||
| throw new Exception( "No profile. Deletion failed." ); | |||||
| } | |||||
| if ( $profile->ownsATeam() ) | if ( $profile->ownsATeam() ) | ||||
| { | { | ||||
| throw new \Exception( 'Cannot delete a profile who owns a team' ); | throw new \Exception( 'Cannot delete a profile who owns a team' ); | ||||
| } | } | ||||
| try | |||||
| { | |||||
| $dbCore->beginTransaction(); | |||||
| $dbTeam->beginTransaction(); | |||||
| $account->delete(); | |||||
| $profile->delete(); | |||||
| $dbCore->commit(); | |||||
| $dbTeam->commit(); | |||||
| } | |||||
| catch( Exception $e ) | |||||
| $appointmentsAccepted = | |||||
| TB_Shared_Ent_TeamData_Appointment::getAcceptedByProfile( | |||||
| $profile->id, false, true | |||||
| ); | |||||
| if ( $appointmentsAccepted && count( $appointmentsAccepted ) > 0 ) | |||||
| { | { | ||||
| $dbCore->rollBack(); | |||||
| $dbTeam->rollBack(); | |||||
| throw $e; | |||||
| throw new \Exception( 'User has accepted appointments in the future' ); | |||||
| } | } | ||||
| if ( $appointmentsToUpdate && count( $appointmentsToUpdate ) > 0 ) | |||||
| { | |||||
| $a = $profile->teams_js; | |||||
| foreach( $appointmentsToUpdate as $app ) | |||||
| { | |||||
| TB_Server_Control_Appointment::processWaitingList( $app ); | |||||
| } | |||||
| $resTeam = []; | |||||
| foreach ($profile->teams_js as $team) { | |||||
| $tmpTeam = $team; | |||||
| $tmpTeam['status'] = TB_Shared_Ent_TeamData_Profile::STATUS_DELETED; | |||||
| $resTeam[] = $tmpTeam; | |||||
| } | } | ||||
| $profile->teams_js = $resTeam; | |||||
| $uuid = \Ramsey\Uuid\Uuid::uuid4()->toString(); | |||||
| $account->email = $account->email . $uuid; | |||||
| $account->save(); | |||||
| $profile->save(); | |||||
| return $resp; | return $resp; | ||||
| */ | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -25,7 +25,7 @@ class TB_Server_Control_Appointment { | |||||
| { | { | ||||
| throw new \Exception( 'No team id specified.' ); | throw new \Exception( 'No team id specified.' ); | ||||
| } | } | ||||
| $icon = _xss( $params->get( 'icon' ) ); | |||||
| $sessionProfile = TB_Server_Core_Session::get()->getProfile(); | $sessionProfile = TB_Server_Core_Session::get()->getProfile(); | ||||
| if ( false === $sessionProfile->isAdminOfTeam( $forTeamId ) ) | if ( false === $sessionProfile->isAdminOfTeam( $forTeamId ) ) | ||||
| { | { | ||||
| @@ -68,11 +68,15 @@ class TB_Server_Control_Appointment { | |||||
| } | } | ||||
| // Link replacements | // Link replacements | ||||
| if ( 0 === mb_strlen( $comment ) ) | |||||
| { | |||||
| $comment = NULL; | |||||
| if (empty($comment) || !is_string($comment)) { | |||||
| $comment = null; | |||||
| } else { | } else { | ||||
| $comment = TB_Server_Utils_Helper::makeUrltoLink($comment); | |||||
| $comment = trim($comment); | |||||
| if ($comment === '') { | |||||
| $comment = null; | |||||
| } else { | |||||
| $comment = TB_Server_Utils_Helper::makeUrltoLink($comment); | |||||
| } | |||||
| } | } | ||||
| $visibility = $params->get( 'visibility', TB_Shared_Ent_TeamData_Appointment::VISIBLE_ALL ); | $visibility = $params->get( 'visibility', TB_Shared_Ent_TeamData_Appointment::VISIBLE_ALL ); | ||||
| @@ -121,6 +125,7 @@ class TB_Server_Control_Appointment { | |||||
| $appointment = new TB_Shared_Ent_TeamData_Appointment(); | $appointment = new TB_Shared_Ent_TeamData_Appointment(); | ||||
| $appointment->team_id = $forTeamId; | $appointment->team_id = $forTeamId; | ||||
| $appointment->icon = $icon; | |||||
| $appointment->category_ids_js = $categoryIds; | $appointment->category_ids_js = $categoryIds; | ||||
| $appointment->visibility = $visibility; | $appointment->visibility = $visibility; | ||||
| $appointment->subject = _xss( $params->get( 'subject' ) ); | $appointment->subject = _xss( $params->get( 'subject' ) ); | ||||
| @@ -409,19 +414,19 @@ class TB_Server_Control_Appointment { | |||||
| $comment = _xss( $params->get( 'comment' ) ); | $comment = _xss( $params->get( 'comment' ) ); | ||||
| if ( 0 === mb_strlen( $comment ) ) | |||||
| { | |||||
| $comment = NULL; | |||||
| } | |||||
| // Link replacements | // Link replacements | ||||
| if ( 0 === mb_strlen( $comment ) ) | |||||
| { | |||||
| $comment = NULL; | |||||
| if (empty($comment) || !is_string($comment)) { | |||||
| $comment = null; | |||||
| } else { | } else { | ||||
| $comment = TB_Server_Utils_Helper::makeUrltoLink($comment); | |||||
| $comment = trim($comment); | |||||
| if ($comment === '') { | |||||
| $comment = null; | |||||
| } else { | |||||
| $comment = TB_Server_Utils_Helper::makeUrltoLink($comment); | |||||
| } | |||||
| } | } | ||||
| $visibility = $params->get( 'visibility', TB_Shared_Ent_TeamData_Appointment::VISIBLE_ALL ); | $visibility = $params->get( 'visibility', TB_Shared_Ent_TeamData_Appointment::VISIBLE_ALL ); | ||||
| if ( $visibility !== TB_Shared_Ent_TeamData_Appointment::VISIBLE_ALL && $visibility !== TB_Shared_Ent_TeamData_Appointment::VISIBLE_CATEGORIES ) | if ( $visibility !== TB_Shared_Ent_TeamData_Appointment::VISIBLE_ALL && $visibility !== TB_Shared_Ent_TeamData_Appointment::VISIBLE_CATEGORIES ) | ||||
| @@ -497,6 +502,7 @@ class TB_Server_Control_Appointment { | |||||
| $appointment->visibility = $visibility; | $appointment->visibility = $visibility; | ||||
| $appointment->category_ids_js = $categoryIds; | $appointment->category_ids_js = $categoryIds; | ||||
| $appointment->subject = _xss( $params->get( 'subject' ) ); | $appointment->subject = _xss( $params->get( 'subject' ) ); | ||||
| $appointment->icon = _xss( $params->get( 'icon' ) ); | |||||
| $appointment->start_dt->setTimezone( $defaultDtz ); | $appointment->start_dt->setTimezone( $defaultDtz ); | ||||
| $appointment->end_dt->setTimezone( $defaultDtz ); | $appointment->end_dt->setTimezone( $defaultDtz ); | ||||
| @@ -862,10 +868,12 @@ class TB_Server_Control_Appointment { | |||||
| throw new \Exception( 'Not visible for current session profile.' ); | throw new \Exception( 'Not visible for current session profile.' ); | ||||
| } | } | ||||
| if ( false === $appointment->isAttendableByProfile( $sessionProfile ) ) | |||||
| $isAttendableByProfile = $appointment->isAttendableByProfile( $sessionProfile ); | |||||
| if ( false === $isAttendableByProfile ) | |||||
| { | { | ||||
| throw new \Exception( 'Not possible to attend by current profile.' ); | throw new \Exception( 'Not possible to attend by current profile.' ); | ||||
| } | } | ||||
| $appointment->v_profile_can_attend = $isAttendableByProfile; | |||||
| //$attendeeData = TB_Shared_Ent_TeamData_Attendee::getWithProfileDataByAppointmentId( $appointment->id ); | //$attendeeData = TB_Shared_Ent_TeamData_Attendee::getWithProfileDataByAppointmentId( $appointment->id ); | ||||
| $attendeeData = TB_Shared_Ent_TeamData_Attendee::getByAppointmentId( $appointment->id ); | $attendeeData = TB_Shared_Ent_TeamData_Attendee::getByAppointmentId( $appointment->id ); | ||||
| @@ -691,7 +691,7 @@ class TB_Server_Control_Auth | |||||
| $team->category = _xss( $teamCategory ); | $team->category = _xss( $teamCategory ); | ||||
| $team->affiliate_id = is_string( $affiliateId ) ? _xss( $affiliateId ) : NULL; | $team->affiliate_id = is_string( $affiliateId ) ? _xss( $affiliateId ) : NULL; | ||||
| $team->terms_conditions_active = 0; | $team->terms_conditions_active = 0; | ||||
| $team->new_users_inactive = 1; | |||||
| $team->new_users_inactive = 0; | |||||
| $team->save(); | $team->save(); | ||||
| // Set default course categories | // Set default course categories | ||||
| @@ -83,6 +83,7 @@ class TB_Server_Control_Team { | |||||
| $includeEmail = $params->get( 'includeEmails' ); | $includeEmail = $params->get( 'includeEmails' ); | ||||
| $adminsOnly = $params->get( 'adminsOnly' ); | $adminsOnly = $params->get( 'adminsOnly' ); | ||||
| $includeMembers = $params->get( 'includeMembers' ); | $includeMembers = $params->get( 'includeMembers' ); | ||||
| $activeOnly = $params->get( 'activeOnly'); | |||||
| $members = array(); | $members = array(); | ||||
| $sessionProfile = TB_Server_Core_Session::get()->getProfile(); | $sessionProfile = TB_Server_Core_Session::get()->getProfile(); | ||||
| @@ -113,7 +114,27 @@ class TB_Server_Control_Team { | |||||
| $filteredMembers[] = $member; | $filteredMembers[] = $member; | ||||
| } | } | ||||
| } else { | } else { | ||||
| $filteredMembers[] = $member; | |||||
| $teamData = $member->getTeamsData($teamId); | |||||
| if ($activeOnly === true) { | |||||
| if ($teamData['status'] === TB_Shared_Ent_TeamData_Profile::STATUS_ACTIVE) { | |||||
| $filteredMembers[] = $member; | |||||
| } | |||||
| } else { | |||||
| if ($teamData['status'] === TB_Shared_Ent_TeamData_Profile::STATUS_DELETED) { | |||||
| $member->street = ""; | |||||
| $member->zip_code = ""; | |||||
| $member->city = ""; | |||||
| $member->country = ""; | |||||
| $member->mobile = ""; | |||||
| $member->phone = ""; | |||||
| $member->gender = ""; | |||||
| $member->pic_url = ""; | |||||
| $member->birthday_dt = ""; | |||||
| $member->misc = ""; | |||||
| $member->status = ""; | |||||
| } | |||||
| $filteredMembers[] = $member; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -127,7 +148,10 @@ class TB_Server_Control_Team { | |||||
| /** @var TB_Shared_Ent_TeamData_Profile $member */ | /** @var TB_Shared_Ent_TeamData_Profile $member */ | ||||
| foreach ( $members as $member ) | foreach ( $members as $member ) | ||||
| { | { | ||||
| array_push( $accountIds, $member->account_id ); | |||||
| $teamData = $member->getTeamsData($teamId); | |||||
| if ($teamData['status'] !== TB_Shared_Ent_TeamData_Profile::STATUS_DELETED) { | |||||
| array_push( $accountIds, $member->account_id ); | |||||
| } | |||||
| } | } | ||||
| $emails = TB_Shared_Ent_Core_Account::getEmailsForAccountIds( $accountIds ); | $emails = TB_Shared_Ent_Core_Account::getEmailsForAccountIds( $accountIds ); | ||||
| @@ -161,6 +185,7 @@ class TB_Server_Control_Team { | |||||
| $resp = new TB_Server_Core_Response(); | $resp = new TB_Server_Core_Response(); | ||||
| $teamId = $params->get( 'teamId' ); | $teamId = $params->get( 'teamId' ); | ||||
| $activeOnly = $params->get( 'activeOnly'); | |||||
| $sessionProfile = TB_Server_Core_Session::get()->getProfile(); | $sessionProfile = TB_Server_Core_Session::get()->getProfile(); | ||||
| if ( false === $sessionProfile->isInTeam( $teamId ) ) | if ( false === $sessionProfile->isInTeam( $teamId ) ) | ||||
| @@ -176,7 +201,7 @@ class TB_Server_Control_Team { | |||||
| return $resp; | return $resp; | ||||
| } | } | ||||
| $resp->addData( 'members', TB_Shared_Ent_TeamData_Profile::getProfilesByTeamId( $team->id ) ); | |||||
| $resp->addData( 'members', TB_Shared_Ent_TeamData_Profile::getProfilesByTeamId( $team->id, $activeOnly ) ); | |||||
| return $resp; | return $resp; | ||||
| } | } | ||||
| @@ -8,6 +8,7 @@ | |||||
| * | * | ||||
| * @property integer $id | * @property integer $id | ||||
| * @property integer $team_id | * @property integer $team_id | ||||
| * @property string $icon | |||||
| * @property string $category_id | * @property string $category_id | ||||
| * @property array $category_ids_js | * @property array $category_ids_js | ||||
| * @property string $visibility | * @property string $visibility | ||||
| @@ -139,8 +140,11 @@ class TB_Shared_Ent_TeamData_Appointment extends Francis_Db_Row | |||||
| * @param $profileId | * @param $profileId | ||||
| * @param bool $includePastAppointments | * @param bool $includePastAppointments | ||||
| */ | */ | ||||
| public static function getAcceptedByProfile( $profileId, $includePastAppointments = false ) | |||||
| { | |||||
| public static function getAcceptedByProfile( | |||||
| $profileId, | |||||
| $includePastAppointments = false, | |||||
| $excludeRejectDeadlineExpired = false | |||||
| ) { | |||||
| $sql = 'SELECT appointment.* FROM appointment '; | $sql = 'SELECT appointment.* FROM appointment '; | ||||
| $sql .= 'LEFT JOIN attendee ON appointment.id = attendee.appointment_id '; | $sql .= 'LEFT JOIN attendee ON appointment.id = attendee.appointment_id '; | ||||
| $sql .= 'WHERE '; | $sql .= 'WHERE '; | ||||
| @@ -149,9 +153,16 @@ class TB_Shared_Ent_TeamData_Appointment extends Francis_Db_Row | |||||
| $sql .= 'attendee.status = "' . TB_Shared_Ent_TeamData_Attendee::STATUS_ACCEPTED . '" '; | $sql .= 'attendee.status = "' . TB_Shared_Ent_TeamData_Attendee::STATUS_ACCEPTED . '" '; | ||||
| if ( false === $includePastAppointments ) | if ( false === $includePastAppointments ) | ||||
| { | { | ||||
| $sql .= 'AND appointment.start_dt < UTC_TIMESTAMP()'; | |||||
| $sql .= 'AND appointment.start_dt > UTC_TIMESTAMP()'; | |||||
| } | } | ||||
| if ( true === $excludeRejectDeadlineExpired ) { | |||||
| // By this we do not include appointments where reject deadline has passed expired | |||||
| $sql .= 'AND appointment.deadline_reject_dt > UTC_TIMESTAMP()'; | |||||
| } | |||||
| //SELECT appointment.* FROM appointment LEFT JOIN attendee ON appointment.id = attendee.appointment_id WHERE appointment.state = "open" AND attendee.profile_id = 16772 AND attendee.status = "accepted"; | |||||
| return self::findMany( $sql, array( ':profile_id' => $profileId ) ); | return self::findMany( $sql, array( ':profile_id' => $profileId ) ); | ||||
| } | } | ||||
| @@ -40,6 +40,7 @@ class TB_Shared_Ent_TeamData_Profile extends Francis_Db_Row { | |||||
| const STATUS_NOT_APPROVED = 'not_approved'; | const STATUS_NOT_APPROVED = 'not_approved'; | ||||
| const STATUS_ACTIVE = 'active'; | const STATUS_ACTIVE = 'active'; | ||||
| const STATUS_INACTIVE = 'inactive'; | const STATUS_INACTIVE = 'inactive'; | ||||
| const STATUS_DELETED = 'deleted'; | |||||
| // Enums | // Enums | ||||
| const GENDER_MALE = 'male'; | const GENDER_MALE = 'male'; | ||||
| @@ -74,7 +75,8 @@ class TB_Shared_Ent_TeamData_Profile extends Francis_Db_Row { | |||||
| return array( | return array( | ||||
| self::STATUS_ACTIVE, | self::STATUS_ACTIVE, | ||||
| self::STATUS_INACTIVE, | self::STATUS_INACTIVE, | ||||
| self::STATUS_NOT_APPROVED | |||||
| self::STATUS_NOT_APPROVED, | |||||
| self::STATUS_DELETED | |||||
| ); | ); | ||||
| } | } | ||||
| @@ -754,9 +756,14 @@ class TB_Shared_Ent_TeamData_Profile extends Francis_Db_Row { | |||||
| * @param $teamId | * @param $teamId | ||||
| * @return array | * @return array | ||||
| */ | */ | ||||
| public static function getProfilesByTeamId( $teamId ) | |||||
| public static function getProfilesByTeamId( $teamId, $activeOnly = null ) | |||||
| { | { | ||||
| $whereJson = json_encode( array( 'team_id' => $teamId ) ); | |||||
| $params = array('team_id' => "$teamId"); | |||||
| if ($activeOnly === true ) { | |||||
| $params['status'] = self::STATUS_ACTIVE; | |||||
| } | |||||
| $whereJson = json_encode( $params ); | |||||
| $sql = 'SELECT * FROM ' . self::getTable() . ' WHERE '; | $sql = 'SELECT * FROM ' . self::getTable() . ' WHERE '; | ||||
| $sql .= 'JSON_CONTAINS( `teams_js`, :where_json ) '; | $sql .= 'JSON_CONTAINS( `teams_js`, :where_json ) '; | ||||
| $sql .= 'ORDER BY last_name ASC'; | $sql .= 'ORDER BY last_name ASC'; | ||||
| @@ -0,0 +1,25 @@ | |||||
| <?php | |||||
| // autoload.php @generated by Composer | |||||
| if (PHP_VERSION_ID < 50600) { | |||||
| if (!headers_sent()) { | |||||
| header('HTTP/1.1 500 Internal Server Error'); | |||||
| } | |||||
| $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; | |||||
| if (!ini_get('display_errors')) { | |||||
| if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { | |||||
| fwrite(STDERR, $err); | |||||
| } elseif (!headers_sent()) { | |||||
| echo $err; | |||||
| } | |||||
| } | |||||
| trigger_error( | |||||
| $err, | |||||
| E_USER_ERROR | |||||
| ); | |||||
| } | |||||
| require_once __DIR__ . '/composer/autoload_real.php'; | |||||
| return ComposerAutoloaderInit7422faf8f33d1f1cdfca8767e0ffc0f4::getLoader(); | |||||
| @@ -0,0 +1,463 @@ | |||||
| # Changelog | |||||
| All notable changes to this project will be documented in this file. | |||||
| ## [0.12.1](https://github.com/brick/math/releases/tag/0.12.1) - 2023-11-29 | |||||
| ⚡️ **Performance improvements** | |||||
| - `BigNumber::of()` is now faster, thanks to [@SebastienDug](https://github.com/SebastienDug) in [#77](https://github.com/brick/math/pull/77). | |||||
| ## [0.12.0](https://github.com/brick/math/releases/tag/0.12.0) - 2023-11-26 | |||||
| 💥 **Breaking changes** | |||||
| - Minimum PHP version is now 8.1 | |||||
| - `RoundingMode` is now an `enum`; if you're type-hinting rounding modes, you need to type-hint against `RoundingMode` instead of `int` now | |||||
| - `BigNumber` classes do not implement the `Serializable` interface anymore (they use the [new custom object serialization mechanism](https://wiki.php.net/rfc/custom_object_serialization)) | |||||
| - The following breaking changes only affect you if you're creating your own `BigNumber` subclasses: | |||||
| - the return type of `BigNumber::of()` is now `static` | |||||
| - `BigNumber` has a new abstract method `from()` | |||||
| - all `public` and `protected` functions of `BigNumber` are now `final` | |||||
| ## [0.11.0](https://github.com/brick/math/releases/tag/0.11.0) - 2023-01-16 | |||||
| 💥 **Breaking changes** | |||||
| - Minimum PHP version is now 8.0 | |||||
| - Methods accepting a union of types are now strongly typed<sup>*</sup> | |||||
| - `MathException` now extends `Exception` instead of `RuntimeException` | |||||
| <sup>* You may now run into type errors if you were passing `Stringable` objects to `of()` or any of the methods | |||||
| internally calling `of()`, with `strict_types` enabled. You can fix this by casting `Stringable` objects to `string` | |||||
| first.</sup> | |||||
| ## [0.10.2](https://github.com/brick/math/releases/tag/0.10.2) - 2022-08-11 | |||||
| 👌 **Improvements** | |||||
| - `BigRational::toFloat()` now simplifies the fraction before performing division (#73) thanks to @olsavmic | |||||
| ## [0.10.1](https://github.com/brick/math/releases/tag/0.10.1) - 2022-08-02 | |||||
| ✨ **New features** | |||||
| - `BigInteger::gcdMultiple()` returns the GCD of multiple `BigInteger` numbers | |||||
| ## [0.10.0](https://github.com/brick/math/releases/tag/0.10.0) - 2022-06-18 | |||||
| 💥 **Breaking changes** | |||||
| - Minimum PHP version is now 7.4 | |||||
| ## [0.9.3](https://github.com/brick/math/releases/tag/0.9.3) - 2021-08-15 | |||||
| 🚀 **Compatibility with PHP 8.1** | |||||
| - Support for custom object serialization; this removes a warning on PHP 8.1 due to the `Serializable` interface being deprecated (#60) thanks @TRowbotham | |||||
| ## [0.9.2](https://github.com/brick/math/releases/tag/0.9.2) - 2021-01-20 | |||||
| 🐛 **Bug fix** | |||||
| - Incorrect results could be returned when using the BCMath calculator, with a default scale set with `bcscale()`, on PHP >= 7.2 (#55). | |||||
| ## [0.9.1](https://github.com/brick/math/releases/tag/0.9.1) - 2020-08-19 | |||||
| ✨ **New features** | |||||
| - `BigInteger::not()` returns the bitwise `NOT` value | |||||
| 🐛 **Bug fixes** | |||||
| - `BigInteger::toBytes()` could return an incorrect binary representation for some numbers | |||||
| - The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available | |||||
| ## [0.9.0](https://github.com/brick/math/releases/tag/0.9.0) - 2020-08-18 | |||||
| 👌 **Improvements** | |||||
| - `BigNumber::of()` now accepts `.123` and `123.` formats, both of which return a `BigDecimal` | |||||
| 💥 **Breaking changes** | |||||
| - Deprecated method `BigInteger::powerMod()` has been removed - use `modPow()` instead | |||||
| - Deprecated method `BigInteger::parse()` has been removed - use `fromBase()` instead | |||||
| ## [0.8.17](https://github.com/brick/math/releases/tag/0.8.17) - 2020-08-19 | |||||
| 🐛 **Bug fix** | |||||
| - `BigInteger::toBytes()` could return an incorrect binary representation for some numbers | |||||
| - The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available | |||||
| ## [0.8.16](https://github.com/brick/math/releases/tag/0.8.16) - 2020-08-18 | |||||
| 🚑 **Critical fix** | |||||
| - This version reintroduces the deprecated `BigInteger::parse()` method, that has been removed by mistake in version `0.8.9` and should have lasted for the whole `0.8` release cycle. | |||||
| ✨ **New features** | |||||
| - `BigInteger::modInverse()` calculates a modular multiplicative inverse | |||||
| - `BigInteger::fromBytes()` creates a `BigInteger` from a byte string | |||||
| - `BigInteger::toBytes()` converts a `BigInteger` to a byte string | |||||
| - `BigInteger::randomBits()` creates a pseudo-random `BigInteger` of a given bit length | |||||
| - `BigInteger::randomRange()` creates a pseudo-random `BigInteger` between two bounds | |||||
| 💩 **Deprecations** | |||||
| - `BigInteger::powerMod()` is now deprecated in favour of `modPow()` | |||||
| ## [0.8.15](https://github.com/brick/math/releases/tag/0.8.15) - 2020-04-15 | |||||
| 🐛 **Fixes** | |||||
| - added missing `ext-json` requirement, due to `BigNumber` implementing `JsonSerializable` | |||||
| ⚡️ **Optimizations** | |||||
| - additional optimization in `BigInteger::remainder()` | |||||
| ## [0.8.14](https://github.com/brick/math/releases/tag/0.8.14) - 2020-02-18 | |||||
| ✨ **New features** | |||||
| - `BigInteger::getLowestSetBit()` returns the index of the rightmost one bit | |||||
| ## [0.8.13](https://github.com/brick/math/releases/tag/0.8.13) - 2020-02-16 | |||||
| ✨ **New features** | |||||
| - `BigInteger::isEven()` tests whether the number is even | |||||
| - `BigInteger::isOdd()` tests whether the number is odd | |||||
| - `BigInteger::testBit()` tests if a bit is set | |||||
| - `BigInteger::getBitLength()` returns the number of bits in the minimal representation of the number | |||||
| ## [0.8.12](https://github.com/brick/math/releases/tag/0.8.12) - 2020-02-03 | |||||
| 🛠️ **Maintenance release** | |||||
| Classes are now annotated for better static analysis with [psalm](https://psalm.dev/). | |||||
| This is a maintenance release: no bug fixes, no new features, no breaking changes. | |||||
| ## [0.8.11](https://github.com/brick/math/releases/tag/0.8.11) - 2020-01-23 | |||||
| ✨ **New feature** | |||||
| `BigInteger::powerMod()` performs a power-with-modulo operation. Useful for crypto. | |||||
| ## [0.8.10](https://github.com/brick/math/releases/tag/0.8.10) - 2020-01-21 | |||||
| ✨ **New feature** | |||||
| `BigInteger::mod()` returns the **modulo** of two numbers. The *modulo* differs from the *remainder* when the signs of the operands are different. | |||||
| ## [0.8.9](https://github.com/brick/math/releases/tag/0.8.9) - 2020-01-08 | |||||
| ⚡️ **Performance improvements** | |||||
| A few additional optimizations in `BigInteger` and `BigDecimal` when one of the operands can be returned as is. Thanks to @tomtomsen in #24. | |||||
| ## [0.8.8](https://github.com/brick/math/releases/tag/0.8.8) - 2019-04-25 | |||||
| 🐛 **Bug fixes** | |||||
| - `BigInteger::toBase()` could return an empty string for zero values (BCMath & Native calculators only, GMP calculator unaffected) | |||||
| ✨ **New features** | |||||
| - `BigInteger::toArbitraryBase()` converts a number to an arbitrary base, using a custom alphabet | |||||
| - `BigInteger::fromArbitraryBase()` converts a string in an arbitrary base, using a custom alphabet, back to a number | |||||
| These methods can be used as the foundation to convert strings between different bases/alphabets, using BigInteger as an intermediate representation. | |||||
| 💩 **Deprecations** | |||||
| - `BigInteger::parse()` is now deprecated in favour of `fromBase()` | |||||
| `BigInteger::fromBase()` works the same way as `parse()`, with 2 minor differences: | |||||
| - the `$base` parameter is required, it does not default to `10` | |||||
| - it throws a `NumberFormatException` instead of an `InvalidArgumentException` when the number is malformed | |||||
| ## [0.8.7](https://github.com/brick/math/releases/tag/0.8.7) - 2019-04-20 | |||||
| **Improvements** | |||||
| - Safer conversion from `float` when using custom locales | |||||
| - **Much faster** `NativeCalculator` implementation 🚀 | |||||
| You can expect **at least a 3x performance improvement** for common arithmetic operations when using the library on systems without GMP or BCMath; it gets exponentially faster on multiplications with a high number of digits. This is due to calculations now being performed on whole blocks of digits (the block size depending on the platform, 32-bit or 64-bit) instead of digit-by-digit as before. | |||||
| ## [0.8.6](https://github.com/brick/math/releases/tag/0.8.6) - 2019-04-11 | |||||
| **New method** | |||||
| `BigNumber::sum()` returns the sum of one or more numbers. | |||||
| ## [0.8.5](https://github.com/brick/math/releases/tag/0.8.5) - 2019-02-12 | |||||
| **Bug fix**: `of()` factory methods could fail when passing a `float` in environments using a `LC_NUMERIC` locale with a decimal separator other than `'.'` (#20). | |||||
| Thanks @manowark 👍 | |||||
| ## [0.8.4](https://github.com/brick/math/releases/tag/0.8.4) - 2018-12-07 | |||||
| **New method** | |||||
| `BigDecimal::sqrt()` calculates the square root of a decimal number, to a given scale. | |||||
| ## [0.8.3](https://github.com/brick/math/releases/tag/0.8.3) - 2018-12-06 | |||||
| **New method** | |||||
| `BigInteger::sqrt()` calculates the square root of a number (thanks @peter279k). | |||||
| **New exception** | |||||
| `NegativeNumberException` is thrown when calling `sqrt()` on a negative number. | |||||
| ## [0.8.2](https://github.com/brick/math/releases/tag/0.8.2) - 2018-11-08 | |||||
| **Performance update** | |||||
| - Further improvement of `toInt()` performance | |||||
| - `NativeCalculator` can now perform some multiplications more efficiently | |||||
| ## [0.8.1](https://github.com/brick/math/releases/tag/0.8.1) - 2018-11-07 | |||||
| Performance optimization of `toInt()` methods. | |||||
| ## [0.8.0](https://github.com/brick/math/releases/tag/0.8.0) - 2018-10-13 | |||||
| **Breaking changes** | |||||
| The following deprecated methods have been removed. Use the new method name instead: | |||||
| | Method removed | Replacement method | | |||||
| | --- | --- | | |||||
| | `BigDecimal::getIntegral()` | `BigDecimal::getIntegralPart()` | | |||||
| | `BigDecimal::getFraction()` | `BigDecimal::getFractionalPart()` | | |||||
| --- | |||||
| **New features** | |||||
| `BigInteger` has been augmented with 5 new methods for bitwise operations: | |||||
| | New method | Description | | |||||
| | --- | --- | | |||||
| | `and()` | performs a bitwise `AND` operation on two numbers | | |||||
| | `or()` | performs a bitwise `OR` operation on two numbers | | |||||
| | `xor()` | performs a bitwise `XOR` operation on two numbers | | |||||
| | `shiftedLeft()` | returns the number shifted left by a number of bits | | |||||
| | `shiftedRight()` | returns the number shifted right by a number of bits | | |||||
| Thanks to @DASPRiD 👍 | |||||
| ## [0.7.3](https://github.com/brick/math/releases/tag/0.7.3) - 2018-08-20 | |||||
| **New method:** `BigDecimal::hasNonZeroFractionalPart()` | |||||
| **Renamed/deprecated methods:** | |||||
| - `BigDecimal::getIntegral()` has been renamed to `getIntegralPart()` and is now deprecated | |||||
| - `BigDecimal::getFraction()` has been renamed to `getFractionalPart()` and is now deprecated | |||||
| ## [0.7.2](https://github.com/brick/math/releases/tag/0.7.2) - 2018-07-21 | |||||
| **Performance update** | |||||
| `BigInteger::parse()` and `toBase()` now use GMP's built-in base conversion features when available. | |||||
| ## [0.7.1](https://github.com/brick/math/releases/tag/0.7.1) - 2018-03-01 | |||||
| This is a maintenance release, no code has been changed. | |||||
| - When installed with `--no-dev`, the autoloader does not autoload tests anymore | |||||
| - Tests and other files unnecessary for production are excluded from the dist package | |||||
| This will help make installations more compact. | |||||
| ## [0.7.0](https://github.com/brick/math/releases/tag/0.7.0) - 2017-10-02 | |||||
| Methods renamed: | |||||
| - `BigNumber:sign()` has been renamed to `getSign()` | |||||
| - `BigDecimal::unscaledValue()` has been renamed to `getUnscaledValue()` | |||||
| - `BigDecimal::scale()` has been renamed to `getScale()` | |||||
| - `BigDecimal::integral()` has been renamed to `getIntegral()` | |||||
| - `BigDecimal::fraction()` has been renamed to `getFraction()` | |||||
| - `BigRational::numerator()` has been renamed to `getNumerator()` | |||||
| - `BigRational::denominator()` has been renamed to `getDenominator()` | |||||
| Classes renamed: | |||||
| - `ArithmeticException` has been renamed to `MathException` | |||||
| ## [0.6.2](https://github.com/brick/math/releases/tag/0.6.2) - 2017-10-02 | |||||
| The base class for all exceptions is now `MathException`. | |||||
| `ArithmeticException` has been deprecated, and will be removed in 0.7.0. | |||||
| ## [0.6.1](https://github.com/brick/math/releases/tag/0.6.1) - 2017-10-02 | |||||
| A number of methods have been renamed: | |||||
| - `BigNumber:sign()` is deprecated; use `getSign()` instead | |||||
| - `BigDecimal::unscaledValue()` is deprecated; use `getUnscaledValue()` instead | |||||
| - `BigDecimal::scale()` is deprecated; use `getScale()` instead | |||||
| - `BigDecimal::integral()` is deprecated; use `getIntegral()` instead | |||||
| - `BigDecimal::fraction()` is deprecated; use `getFraction()` instead | |||||
| - `BigRational::numerator()` is deprecated; use `getNumerator()` instead | |||||
| - `BigRational::denominator()` is deprecated; use `getDenominator()` instead | |||||
| The old methods will be removed in version 0.7.0. | |||||
| ## [0.6.0](https://github.com/brick/math/releases/tag/0.6.0) - 2017-08-25 | |||||
| - Minimum PHP version is now [7.1](https://gophp71.org/); for PHP 5.6 and PHP 7.0 support, use version `0.5` | |||||
| - Deprecated method `BigDecimal::withScale()` has been removed; use `toScale()` instead | |||||
| - Method `BigNumber::toInteger()` has been renamed to `toInt()` | |||||
| ## [0.5.4](https://github.com/brick/math/releases/tag/0.5.4) - 2016-10-17 | |||||
| `BigNumber` classes now implement [JsonSerializable](http://php.net/manual/en/class.jsonserializable.php). | |||||
| The JSON output is always a string. | |||||
| ## [0.5.3](https://github.com/brick/math/releases/tag/0.5.3) - 2016-03-31 | |||||
| This is a bugfix release. Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6. | |||||
| ## [0.5.2](https://github.com/brick/math/releases/tag/0.5.2) - 2015-08-06 | |||||
| The `$scale` parameter of `BigDecimal::dividedBy()` is now optional again. | |||||
| ## [0.5.1](https://github.com/brick/math/releases/tag/0.5.1) - 2015-07-05 | |||||
| **New method: `BigNumber::toScale()`** | |||||
| This allows to convert any `BigNumber` to a `BigDecimal` with a given scale, using rounding if necessary. | |||||
| ## [0.5.0](https://github.com/brick/math/releases/tag/0.5.0) - 2015-07-04 | |||||
| **New features** | |||||
| - Common `BigNumber` interface for all classes, with the following methods: | |||||
| - `sign()` and derived methods (`isZero()`, `isPositive()`, ...) | |||||
| - `compareTo()` and derived methods (`isEqualTo()`, `isGreaterThan()`, ...) that work across different `BigNumber` types | |||||
| - `toBigInteger()`, `toBigDecimal()`, `toBigRational`() conversion methods | |||||
| - `toInteger()` and `toFloat()` conversion methods to native types | |||||
| - Unified `of()` behaviour: every class now accepts any type of number, provided that it can be safely converted to the current type | |||||
| - New method: `BigDecimal::exactlyDividedBy()`; this method automatically computes the scale of the result, provided that the division yields a finite number of digits | |||||
| - New methods: `BigRational::quotient()` and `remainder()` | |||||
| - Fine-grained exceptions: `DivisionByZeroException`, `RoundingNecessaryException`, `NumberFormatException` | |||||
| - Factory methods `zero()`, `one()` and `ten()` available in all classes | |||||
| - Rounding mode reintroduced in `BigInteger::dividedBy()` | |||||
| This release also comes with many performance improvements. | |||||
| --- | |||||
| **Breaking changes** | |||||
| - `BigInteger`: | |||||
| - `getSign()` is renamed to `sign()` | |||||
| - `toString()` is renamed to `toBase()` | |||||
| - `BigInteger::dividedBy()` now throws an exception by default if the remainder is not zero; use `quotient()` to get the previous behaviour | |||||
| - `BigDecimal`: | |||||
| - `getSign()` is renamed to `sign()` | |||||
| - `getUnscaledValue()` is renamed to `unscaledValue()` | |||||
| - `getScale()` is renamed to `scale()` | |||||
| - `getIntegral()` is renamed to `integral()` | |||||
| - `getFraction()` is renamed to `fraction()` | |||||
| - `divideAndRemainder()` is renamed to `quotientAndRemainder()` | |||||
| - `dividedBy()` now takes a **mandatory** `$scale` parameter **before** the rounding mode | |||||
| - `toBigInteger()` does not accept a `$roundingMode` parameter anymore | |||||
| - `toBigRational()` does not simplify the fraction anymore; explicitly add `->simplified()` to get the previous behaviour | |||||
| - `BigRational`: | |||||
| - `getSign()` is renamed to `sign()` | |||||
| - `getNumerator()` is renamed to `numerator()` | |||||
| - `getDenominator()` is renamed to `denominator()` | |||||
| - `of()` is renamed to `nd()`, while `parse()` is renamed to `of()` | |||||
| - Miscellaneous: | |||||
| - `ArithmeticException` is moved to an `Exception\` sub-namespace | |||||
| - `of()` factory methods now throw `NumberFormatException` instead of `InvalidArgumentException` | |||||
| ## [0.4.3](https://github.com/brick/math/releases/tag/0.4.3) - 2016-03-31 | |||||
| Backport of two bug fixes from the 0.5 branch: | |||||
| - `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected | |||||
| - Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6. | |||||
| ## [0.4.2](https://github.com/brick/math/releases/tag/0.4.2) - 2015-06-16 | |||||
| New method: `BigDecimal::stripTrailingZeros()` | |||||
| ## [0.4.1](https://github.com/brick/math/releases/tag/0.4.1) - 2015-06-12 | |||||
| Introducing a `BigRational` class, to perform calculations on fractions of any size. | |||||
| ## [0.4.0](https://github.com/brick/math/releases/tag/0.4.0) - 2015-06-12 | |||||
| Rounding modes have been removed from `BigInteger`, and are now a concept specific to `BigDecimal`. | |||||
| `BigInteger::dividedBy()` now always returns the quotient of the division. | |||||
| ## [0.3.5](https://github.com/brick/math/releases/tag/0.3.5) - 2016-03-31 | |||||
| Backport of two bug fixes from the 0.5 branch: | |||||
| - `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected | |||||
| - Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6. | |||||
| ## [0.3.4](https://github.com/brick/math/releases/tag/0.3.4) - 2015-06-11 | |||||
| New methods: | |||||
| - `BigInteger::remainder()` returns the remainder of a division only | |||||
| - `BigInteger::gcd()` returns the greatest common divisor of two numbers | |||||
| ## [0.3.3](https://github.com/brick/math/releases/tag/0.3.3) - 2015-06-07 | |||||
| Fix `toString()` not handling negative numbers. | |||||
| ## [0.3.2](https://github.com/brick/math/releases/tag/0.3.2) - 2015-06-07 | |||||
| `BigInteger` and `BigDecimal` now have a `getSign()` method that returns: | |||||
| - `-1` if the number is negative | |||||
| - `0` if the number is zero | |||||
| - `1` if the number is positive | |||||
| ## [0.3.1](https://github.com/brick/math/releases/tag/0.3.1) - 2015-06-05 | |||||
| Minor performance improvements | |||||
| ## [0.3.0](https://github.com/brick/math/releases/tag/0.3.0) - 2015-06-04 | |||||
| The `$roundingMode` and `$scale` parameters have been swapped in `BigDecimal::dividedBy()`. | |||||
| ## [0.2.2](https://github.com/brick/math/releases/tag/0.2.2) - 2015-06-04 | |||||
| Stronger immutability guarantee for `BigInteger` and `BigDecimal`. | |||||
| So far, it would have been possible to break immutability of these classes by calling the `unserialize()` internal function. This release fixes that. | |||||
| ## [0.2.1](https://github.com/brick/math/releases/tag/0.2.1) - 2015-06-02 | |||||
| Added `BigDecimal::divideAndRemainder()` | |||||
| ## [0.2.0](https://github.com/brick/math/releases/tag/0.2.0) - 2015-05-22 | |||||
| - `min()` and `max()` do not accept an `array` anymore, but a variable number of parameters | |||||
| - **minimum PHP version is now 5.6** | |||||
| - continuous integration with PHP 7 | |||||
| ## [0.1.1](https://github.com/brick/math/releases/tag/0.1.1) - 2014-09-01 | |||||
| - Added `BigInteger::power()` | |||||
| - Added HHVM support | |||||
| ## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31 | |||||
| First beta release. | |||||
| @@ -0,0 +1,20 @@ | |||||
| The MIT License (MIT) | |||||
| Copyright (c) 2013-present Benjamin Morel | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | |||||
| this software and associated documentation files (the "Software"), to deal in | |||||
| the Software without restriction, including without limitation the rights to | |||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |||||
| the Software, and to permit persons to whom the Software is furnished to do so, | |||||
| subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in all | |||||
| copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |||||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |||||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |||||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||||
| @@ -0,0 +1,39 @@ | |||||
| { | |||||
| "name": "brick/math", | |||||
| "description": "Arbitrary-precision arithmetic library", | |||||
| "type": "library", | |||||
| "keywords": [ | |||||
| "Brick", | |||||
| "Math", | |||||
| "Mathematics", | |||||
| "Arbitrary-precision", | |||||
| "Arithmetic", | |||||
| "BigInteger", | |||||
| "BigDecimal", | |||||
| "BigRational", | |||||
| "BigNumber", | |||||
| "Bignum", | |||||
| "Decimal", | |||||
| "Rational", | |||||
| "Integer" | |||||
| ], | |||||
| "license": "MIT", | |||||
| "require": { | |||||
| "php": "^8.1" | |||||
| }, | |||||
| "require-dev": { | |||||
| "phpunit/phpunit": "^10.1", | |||||
| "php-coveralls/php-coveralls": "^2.2", | |||||
| "vimeo/psalm": "5.16.0" | |||||
| }, | |||||
| "autoload": { | |||||
| "psr-4": { | |||||
| "Brick\\Math\\": "src/" | |||||
| } | |||||
| }, | |||||
| "autoload-dev": { | |||||
| "psr-4": { | |||||
| "Brick\\Math\\Tests\\": "tests/" | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,754 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math; | |||||
| use Brick\Math\Exception\DivisionByZeroException; | |||||
| use Brick\Math\Exception\MathException; | |||||
| use Brick\Math\Exception\NegativeNumberException; | |||||
| use Brick\Math\Internal\Calculator; | |||||
| /** | |||||
| * Immutable, arbitrary-precision signed decimal numbers. | |||||
| * | |||||
| * @psalm-immutable | |||||
| */ | |||||
| final class BigDecimal extends BigNumber | |||||
| { | |||||
| /** | |||||
| * The unscaled value of this decimal number. | |||||
| * | |||||
| * This is a string of digits with an optional leading minus sign. | |||||
| * No leading zero must be present. | |||||
| * No leading minus sign must be present if the value is 0. | |||||
| */ | |||||
| private readonly string $value; | |||||
| /** | |||||
| * The scale (number of digits after the decimal point) of this decimal number. | |||||
| * | |||||
| * This must be zero or more. | |||||
| */ | |||||
| private readonly int $scale; | |||||
| /** | |||||
| * Protected constructor. Use a factory method to obtain an instance. | |||||
| * | |||||
| * @param string $value The unscaled value, validated. | |||||
| * @param int $scale The scale, validated. | |||||
| */ | |||||
| protected function __construct(string $value, int $scale = 0) | |||||
| { | |||||
| $this->value = $value; | |||||
| $this->scale = $scale; | |||||
| } | |||||
| /** | |||||
| * @psalm-pure | |||||
| */ | |||||
| protected static function from(BigNumber $number): static | |||||
| { | |||||
| return $number->toBigDecimal(); | |||||
| } | |||||
| /** | |||||
| * Creates a BigDecimal from an unscaled value and a scale. | |||||
| * | |||||
| * Example: `(12345, 3)` will result in the BigDecimal `12.345`. | |||||
| * | |||||
| * @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger. | |||||
| * @param int $scale The scale of the number, positive or zero. | |||||
| * | |||||
| * @throws \InvalidArgumentException If the scale is negative. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0) : BigDecimal | |||||
| { | |||||
| if ($scale < 0) { | |||||
| throw new \InvalidArgumentException('The scale cannot be negative.'); | |||||
| } | |||||
| return new BigDecimal((string) BigInteger::of($value), $scale); | |||||
| } | |||||
| /** | |||||
| * Returns a BigDecimal representing zero, with a scale of zero. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function zero() : BigDecimal | |||||
| { | |||||
| /** | |||||
| * @psalm-suppress ImpureStaticVariable | |||||
| * @var BigDecimal|null $zero | |||||
| */ | |||||
| static $zero; | |||||
| if ($zero === null) { | |||||
| $zero = new BigDecimal('0'); | |||||
| } | |||||
| return $zero; | |||||
| } | |||||
| /** | |||||
| * Returns a BigDecimal representing one, with a scale of zero. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function one() : BigDecimal | |||||
| { | |||||
| /** | |||||
| * @psalm-suppress ImpureStaticVariable | |||||
| * @var BigDecimal|null $one | |||||
| */ | |||||
| static $one; | |||||
| if ($one === null) { | |||||
| $one = new BigDecimal('1'); | |||||
| } | |||||
| return $one; | |||||
| } | |||||
| /** | |||||
| * Returns a BigDecimal representing ten, with a scale of zero. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function ten() : BigDecimal | |||||
| { | |||||
| /** | |||||
| * @psalm-suppress ImpureStaticVariable | |||||
| * @var BigDecimal|null $ten | |||||
| */ | |||||
| static $ten; | |||||
| if ($ten === null) { | |||||
| $ten = new BigDecimal('10'); | |||||
| } | |||||
| return $ten; | |||||
| } | |||||
| /** | |||||
| * Returns the sum of this number and the given one. | |||||
| * | |||||
| * The result has a scale of `max($this->scale, $that->scale)`. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal. | |||||
| * | |||||
| * @throws MathException If the number is not valid, or is not convertible to a BigDecimal. | |||||
| */ | |||||
| public function plus(BigNumber|int|float|string $that) : BigDecimal | |||||
| { | |||||
| $that = BigDecimal::of($that); | |||||
| if ($that->value === '0' && $that->scale <= $this->scale) { | |||||
| return $this; | |||||
| } | |||||
| if ($this->value === '0' && $this->scale <= $that->scale) { | |||||
| return $that; | |||||
| } | |||||
| [$a, $b] = $this->scaleValues($this, $that); | |||||
| $value = Calculator::get()->add($a, $b); | |||||
| $scale = $this->scale > $that->scale ? $this->scale : $that->scale; | |||||
| return new BigDecimal($value, $scale); | |||||
| } | |||||
| /** | |||||
| * Returns the difference of this number and the given one. | |||||
| * | |||||
| * The result has a scale of `max($this->scale, $that->scale)`. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal. | |||||
| * | |||||
| * @throws MathException If the number is not valid, or is not convertible to a BigDecimal. | |||||
| */ | |||||
| public function minus(BigNumber|int|float|string $that) : BigDecimal | |||||
| { | |||||
| $that = BigDecimal::of($that); | |||||
| if ($that->value === '0' && $that->scale <= $this->scale) { | |||||
| return $this; | |||||
| } | |||||
| [$a, $b] = $this->scaleValues($this, $that); | |||||
| $value = Calculator::get()->sub($a, $b); | |||||
| $scale = $this->scale > $that->scale ? $this->scale : $that->scale; | |||||
| return new BigDecimal($value, $scale); | |||||
| } | |||||
| /** | |||||
| * Returns the product of this number and the given one. | |||||
| * | |||||
| * The result has a scale of `$this->scale + $that->scale`. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal. | |||||
| * | |||||
| * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal. | |||||
| */ | |||||
| public function multipliedBy(BigNumber|int|float|string $that) : BigDecimal | |||||
| { | |||||
| $that = BigDecimal::of($that); | |||||
| if ($that->value === '1' && $that->scale === 0) { | |||||
| return $this; | |||||
| } | |||||
| if ($this->value === '1' && $this->scale === 0) { | |||||
| return $that; | |||||
| } | |||||
| $value = Calculator::get()->mul($this->value, $that->value); | |||||
| $scale = $this->scale + $that->scale; | |||||
| return new BigDecimal($value, $scale); | |||||
| } | |||||
| /** | |||||
| * Returns the result of the division of this number by the given one, at the given scale. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The divisor. | |||||
| * @param int|null $scale The desired scale, or null to use the scale of this number. | |||||
| * @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY. | |||||
| * | |||||
| * @throws \InvalidArgumentException If the scale or rounding mode is invalid. | |||||
| * @throws MathException If the number is invalid, is zero, or rounding was necessary. | |||||
| */ | |||||
| public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal | |||||
| { | |||||
| $that = BigDecimal::of($that); | |||||
| if ($that->isZero()) { | |||||
| throw DivisionByZeroException::divisionByZero(); | |||||
| } | |||||
| if ($scale === null) { | |||||
| $scale = $this->scale; | |||||
| } elseif ($scale < 0) { | |||||
| throw new \InvalidArgumentException('Scale cannot be negative.'); | |||||
| } | |||||
| if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) { | |||||
| return $this; | |||||
| } | |||||
| $p = $this->valueWithMinScale($that->scale + $scale); | |||||
| $q = $that->valueWithMinScale($this->scale - $scale); | |||||
| $result = Calculator::get()->divRound($p, $q, $roundingMode); | |||||
| return new BigDecimal($result, $scale); | |||||
| } | |||||
| /** | |||||
| * Returns the exact result of the division of this number by the given one. | |||||
| * | |||||
| * The scale of the result is automatically calculated to fit all the fraction digits. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. | |||||
| * | |||||
| * @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero, | |||||
| * or the result yields an infinite number of digits. | |||||
| */ | |||||
| public function exactlyDividedBy(BigNumber|int|float|string $that) : BigDecimal | |||||
| { | |||||
| $that = BigDecimal::of($that); | |||||
| if ($that->value === '0') { | |||||
| throw DivisionByZeroException::divisionByZero(); | |||||
| } | |||||
| [, $b] = $this->scaleValues($this, $that); | |||||
| $d = \rtrim($b, '0'); | |||||
| $scale = \strlen($b) - \strlen($d); | |||||
| $calculator = Calculator::get(); | |||||
| foreach ([5, 2] as $prime) { | |||||
| for (;;) { | |||||
| $lastDigit = (int) $d[-1]; | |||||
| if ($lastDigit % $prime !== 0) { | |||||
| break; | |||||
| } | |||||
| $d = $calculator->divQ($d, (string) $prime); | |||||
| $scale++; | |||||
| } | |||||
| } | |||||
| return $this->dividedBy($that, $scale)->stripTrailingZeros(); | |||||
| } | |||||
| /** | |||||
| * Returns this number exponentiated to the given value. | |||||
| * | |||||
| * The result has a scale of `$this->scale * $exponent`. | |||||
| * | |||||
| * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. | |||||
| */ | |||||
| public function power(int $exponent) : BigDecimal | |||||
| { | |||||
| if ($exponent === 0) { | |||||
| return BigDecimal::one(); | |||||
| } | |||||
| if ($exponent === 1) { | |||||
| return $this; | |||||
| } | |||||
| if ($exponent < 0 || $exponent > Calculator::MAX_POWER) { | |||||
| throw new \InvalidArgumentException(\sprintf( | |||||
| 'The exponent %d is not in the range 0 to %d.', | |||||
| $exponent, | |||||
| Calculator::MAX_POWER | |||||
| )); | |||||
| } | |||||
| return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent); | |||||
| } | |||||
| /** | |||||
| * Returns the quotient of the division of this number by the given one. | |||||
| * | |||||
| * The quotient has a scale of `0`. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. | |||||
| * | |||||
| * @throws MathException If the divisor is not a valid decimal number, or is zero. | |||||
| */ | |||||
| public function quotient(BigNumber|int|float|string $that) : BigDecimal | |||||
| { | |||||
| $that = BigDecimal::of($that); | |||||
| if ($that->isZero()) { | |||||
| throw DivisionByZeroException::divisionByZero(); | |||||
| } | |||||
| $p = $this->valueWithMinScale($that->scale); | |||||
| $q = $that->valueWithMinScale($this->scale); | |||||
| $quotient = Calculator::get()->divQ($p, $q); | |||||
| return new BigDecimal($quotient, 0); | |||||
| } | |||||
| /** | |||||
| * Returns the remainder of the division of this number by the given one. | |||||
| * | |||||
| * The remainder has a scale of `max($this->scale, $that->scale)`. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. | |||||
| * | |||||
| * @throws MathException If the divisor is not a valid decimal number, or is zero. | |||||
| */ | |||||
| public function remainder(BigNumber|int|float|string $that) : BigDecimal | |||||
| { | |||||
| $that = BigDecimal::of($that); | |||||
| if ($that->isZero()) { | |||||
| throw DivisionByZeroException::divisionByZero(); | |||||
| } | |||||
| $p = $this->valueWithMinScale($that->scale); | |||||
| $q = $that->valueWithMinScale($this->scale); | |||||
| $remainder = Calculator::get()->divR($p, $q); | |||||
| $scale = $this->scale > $that->scale ? $this->scale : $that->scale; | |||||
| return new BigDecimal($remainder, $scale); | |||||
| } | |||||
| /** | |||||
| * Returns the quotient and remainder of the division of this number by the given one. | |||||
| * | |||||
| * The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. | |||||
| * | |||||
| * @return BigDecimal[] An array containing the quotient and the remainder. | |||||
| * | |||||
| * @psalm-return array{BigDecimal, BigDecimal} | |||||
| * | |||||
| * @throws MathException If the divisor is not a valid decimal number, or is zero. | |||||
| */ | |||||
| public function quotientAndRemainder(BigNumber|int|float|string $that) : array | |||||
| { | |||||
| $that = BigDecimal::of($that); | |||||
| if ($that->isZero()) { | |||||
| throw DivisionByZeroException::divisionByZero(); | |||||
| } | |||||
| $p = $this->valueWithMinScale($that->scale); | |||||
| $q = $that->valueWithMinScale($this->scale); | |||||
| [$quotient, $remainder] = Calculator::get()->divQR($p, $q); | |||||
| $scale = $this->scale > $that->scale ? $this->scale : $that->scale; | |||||
| $quotient = new BigDecimal($quotient, 0); | |||||
| $remainder = new BigDecimal($remainder, $scale); | |||||
| return [$quotient, $remainder]; | |||||
| } | |||||
| /** | |||||
| * Returns the square root of this number, rounded down to the given number of decimals. | |||||
| * | |||||
| * @throws \InvalidArgumentException If the scale is negative. | |||||
| * @throws NegativeNumberException If this number is negative. | |||||
| */ | |||||
| public function sqrt(int $scale) : BigDecimal | |||||
| { | |||||
| if ($scale < 0) { | |||||
| throw new \InvalidArgumentException('Scale cannot be negative.'); | |||||
| } | |||||
| if ($this->value === '0') { | |||||
| return new BigDecimal('0', $scale); | |||||
| } | |||||
| if ($this->value[0] === '-') { | |||||
| throw new NegativeNumberException('Cannot calculate the square root of a negative number.'); | |||||
| } | |||||
| $value = $this->value; | |||||
| $addDigits = 2 * $scale - $this->scale; | |||||
| if ($addDigits > 0) { | |||||
| // add zeros | |||||
| $value .= \str_repeat('0', $addDigits); | |||||
| } elseif ($addDigits < 0) { | |||||
| // trim digits | |||||
| if (-$addDigits >= \strlen($this->value)) { | |||||
| // requesting a scale too low, will always yield a zero result | |||||
| return new BigDecimal('0', $scale); | |||||
| } | |||||
| $value = \substr($value, 0, $addDigits); | |||||
| } | |||||
| $value = Calculator::get()->sqrt($value); | |||||
| return new BigDecimal($value, $scale); | |||||
| } | |||||
| /** | |||||
| * Returns a copy of this BigDecimal with the decimal point moved $n places to the left. | |||||
| */ | |||||
| public function withPointMovedLeft(int $n) : BigDecimal | |||||
| { | |||||
| if ($n === 0) { | |||||
| return $this; | |||||
| } | |||||
| if ($n < 0) { | |||||
| return $this->withPointMovedRight(-$n); | |||||
| } | |||||
| return new BigDecimal($this->value, $this->scale + $n); | |||||
| } | |||||
| /** | |||||
| * Returns a copy of this BigDecimal with the decimal point moved $n places to the right. | |||||
| */ | |||||
| public function withPointMovedRight(int $n) : BigDecimal | |||||
| { | |||||
| if ($n === 0) { | |||||
| return $this; | |||||
| } | |||||
| if ($n < 0) { | |||||
| return $this->withPointMovedLeft(-$n); | |||||
| } | |||||
| $value = $this->value; | |||||
| $scale = $this->scale - $n; | |||||
| if ($scale < 0) { | |||||
| if ($value !== '0') { | |||||
| $value .= \str_repeat('0', -$scale); | |||||
| } | |||||
| $scale = 0; | |||||
| } | |||||
| return new BigDecimal($value, $scale); | |||||
| } | |||||
| /** | |||||
| * Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part. | |||||
| */ | |||||
| public function stripTrailingZeros() : BigDecimal | |||||
| { | |||||
| if ($this->scale === 0) { | |||||
| return $this; | |||||
| } | |||||
| $trimmedValue = \rtrim($this->value, '0'); | |||||
| if ($trimmedValue === '') { | |||||
| return BigDecimal::zero(); | |||||
| } | |||||
| $trimmableZeros = \strlen($this->value) - \strlen($trimmedValue); | |||||
| if ($trimmableZeros === 0) { | |||||
| return $this; | |||||
| } | |||||
| if ($trimmableZeros > $this->scale) { | |||||
| $trimmableZeros = $this->scale; | |||||
| } | |||||
| $value = \substr($this->value, 0, -$trimmableZeros); | |||||
| $scale = $this->scale - $trimmableZeros; | |||||
| return new BigDecimal($value, $scale); | |||||
| } | |||||
| /** | |||||
| * Returns the absolute value of this number. | |||||
| */ | |||||
| public function abs() : BigDecimal | |||||
| { | |||||
| return $this->isNegative() ? $this->negated() : $this; | |||||
| } | |||||
| /** | |||||
| * Returns the negated value of this number. | |||||
| */ | |||||
| public function negated() : BigDecimal | |||||
| { | |||||
| return new BigDecimal(Calculator::get()->neg($this->value), $this->scale); | |||||
| } | |||||
| public function compareTo(BigNumber|int|float|string $that) : int | |||||
| { | |||||
| $that = BigNumber::of($that); | |||||
| if ($that instanceof BigInteger) { | |||||
| $that = $that->toBigDecimal(); | |||||
| } | |||||
| if ($that instanceof BigDecimal) { | |||||
| [$a, $b] = $this->scaleValues($this, $that); | |||||
| return Calculator::get()->cmp($a, $b); | |||||
| } | |||||
| return - $that->compareTo($this); | |||||
| } | |||||
| public function getSign() : int | |||||
| { | |||||
| return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); | |||||
| } | |||||
| public function getUnscaledValue() : BigInteger | |||||
| { | |||||
| return self::newBigInteger($this->value); | |||||
| } | |||||
| public function getScale() : int | |||||
| { | |||||
| return $this->scale; | |||||
| } | |||||
| /** | |||||
| * Returns a string representing the integral part of this decimal number. | |||||
| * | |||||
| * Example: `-123.456` => `-123`. | |||||
| */ | |||||
| public function getIntegralPart() : string | |||||
| { | |||||
| if ($this->scale === 0) { | |||||
| return $this->value; | |||||
| } | |||||
| $value = $this->getUnscaledValueWithLeadingZeros(); | |||||
| return \substr($value, 0, -$this->scale); | |||||
| } | |||||
| /** | |||||
| * Returns a string representing the fractional part of this decimal number. | |||||
| * | |||||
| * If the scale is zero, an empty string is returned. | |||||
| * | |||||
| * Examples: `-123.456` => '456', `123` => ''. | |||||
| */ | |||||
| public function getFractionalPart() : string | |||||
| { | |||||
| if ($this->scale === 0) { | |||||
| return ''; | |||||
| } | |||||
| $value = $this->getUnscaledValueWithLeadingZeros(); | |||||
| return \substr($value, -$this->scale); | |||||
| } | |||||
| /** | |||||
| * Returns whether this decimal number has a non-zero fractional part. | |||||
| */ | |||||
| public function hasNonZeroFractionalPart() : bool | |||||
| { | |||||
| return $this->getFractionalPart() !== \str_repeat('0', $this->scale); | |||||
| } | |||||
| public function toBigInteger() : BigInteger | |||||
| { | |||||
| $zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0); | |||||
| return self::newBigInteger($zeroScaleDecimal->value); | |||||
| } | |||||
| public function toBigDecimal() : BigDecimal | |||||
| { | |||||
| return $this; | |||||
| } | |||||
| public function toBigRational() : BigRational | |||||
| { | |||||
| $numerator = self::newBigInteger($this->value); | |||||
| $denominator = self::newBigInteger('1' . \str_repeat('0', $this->scale)); | |||||
| return self::newBigRational($numerator, $denominator, false); | |||||
| } | |||||
| public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal | |||||
| { | |||||
| if ($scale === $this->scale) { | |||||
| return $this; | |||||
| } | |||||
| return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode); | |||||
| } | |||||
| public function toInt() : int | |||||
| { | |||||
| return $this->toBigInteger()->toInt(); | |||||
| } | |||||
| public function toFloat() : float | |||||
| { | |||||
| return (float) (string) $this; | |||||
| } | |||||
| public function __toString() : string | |||||
| { | |||||
| if ($this->scale === 0) { | |||||
| return $this->value; | |||||
| } | |||||
| $value = $this->getUnscaledValueWithLeadingZeros(); | |||||
| return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale); | |||||
| } | |||||
| /** | |||||
| * This method is required for serializing the object and SHOULD NOT be accessed directly. | |||||
| * | |||||
| * @internal | |||||
| * | |||||
| * @return array{value: string, scale: int} | |||||
| */ | |||||
| public function __serialize(): array | |||||
| { | |||||
| return ['value' => $this->value, 'scale' => $this->scale]; | |||||
| } | |||||
| /** | |||||
| * This method is only here to allow unserializing the object and cannot be accessed directly. | |||||
| * | |||||
| * @internal | |||||
| * @psalm-suppress RedundantPropertyInitializationCheck | |||||
| * | |||||
| * @param array{value: string, scale: int} $data | |||||
| * | |||||
| * @throws \LogicException | |||||
| */ | |||||
| public function __unserialize(array $data): void | |||||
| { | |||||
| if (isset($this->value)) { | |||||
| throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); | |||||
| } | |||||
| $this->value = $data['value']; | |||||
| $this->scale = $data['scale']; | |||||
| } | |||||
| /** | |||||
| * Puts the internal values of the given decimal numbers on the same scale. | |||||
| * | |||||
| * @return array{string, string} The scaled integer values of $x and $y. | |||||
| */ | |||||
| private function scaleValues(BigDecimal $x, BigDecimal $y) : array | |||||
| { | |||||
| $a = $x->value; | |||||
| $b = $y->value; | |||||
| if ($b !== '0' && $x->scale > $y->scale) { | |||||
| $b .= \str_repeat('0', $x->scale - $y->scale); | |||||
| } elseif ($a !== '0' && $x->scale < $y->scale) { | |||||
| $a .= \str_repeat('0', $y->scale - $x->scale); | |||||
| } | |||||
| return [$a, $b]; | |||||
| } | |||||
| private function valueWithMinScale(int $scale) : string | |||||
| { | |||||
| $value = $this->value; | |||||
| if ($this->value !== '0' && $scale > $this->scale) { | |||||
| $value .= \str_repeat('0', $scale - $this->scale); | |||||
| } | |||||
| return $value; | |||||
| } | |||||
| /** | |||||
| * Adds leading zeros if necessary to the unscaled value to represent the full decimal number. | |||||
| */ | |||||
| private function getUnscaledValueWithLeadingZeros() : string | |||||
| { | |||||
| $value = $this->value; | |||||
| $targetLength = $this->scale + 1; | |||||
| $negative = ($value[0] === '-'); | |||||
| $length = \strlen($value); | |||||
| if ($negative) { | |||||
| $length--; | |||||
| } | |||||
| if ($length >= $targetLength) { | |||||
| return $this->value; | |||||
| } | |||||
| if ($negative) { | |||||
| $value = \substr($value, 1); | |||||
| } | |||||
| $value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT); | |||||
| if ($negative) { | |||||
| $value = '-' . $value; | |||||
| } | |||||
| return $value; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,509 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math; | |||||
| use Brick\Math\Exception\DivisionByZeroException; | |||||
| use Brick\Math\Exception\MathException; | |||||
| use Brick\Math\Exception\NumberFormatException; | |||||
| use Brick\Math\Exception\RoundingNecessaryException; | |||||
| /** | |||||
| * Common interface for arbitrary-precision rational numbers. | |||||
| * | |||||
| * @psalm-immutable | |||||
| */ | |||||
| abstract class BigNumber implements \JsonSerializable | |||||
| { | |||||
| /** | |||||
| * The regular expression used to parse integer or decimal numbers. | |||||
| */ | |||||
| private const PARSE_REGEXP_NUMERICAL = | |||||
| '/^' . | |||||
| '(?<sign>[\-\+])?' . | |||||
| '(?<integral>[0-9]+)?' . | |||||
| '(?<point>\.)?' . | |||||
| '(?<fractional>[0-9]+)?' . | |||||
| '(?:[eE](?<exponent>[\-\+]?[0-9]+))?' . | |||||
| '$/'; | |||||
| /** | |||||
| * The regular expression used to parse rational numbers. | |||||
| */ | |||||
| private const PARSE_REGEXP_RATIONAL = | |||||
| '/^' . | |||||
| '(?<sign>[\-\+])?' . | |||||
| '(?<numerator>[0-9]+)' . | |||||
| '\/?' . | |||||
| '(?<denominator>[0-9]+)' . | |||||
| '$/'; | |||||
| /** | |||||
| * Creates a BigNumber of the given value. | |||||
| * | |||||
| * The concrete return type is dependent on the given value, with the following rules: | |||||
| * | |||||
| * - BigNumber instances are returned as is | |||||
| * - integer numbers are returned as BigInteger | |||||
| * - floating point numbers are converted to a string then parsed as such | |||||
| * - strings containing a `/` character are returned as BigRational | |||||
| * - strings containing a `.` character or using an exponential notation are returned as BigDecimal | |||||
| * - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger | |||||
| * | |||||
| * @throws NumberFormatException If the format of the number is not valid. | |||||
| * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| final public static function of(BigNumber|int|float|string $value) : static | |||||
| { | |||||
| $value = self::_of($value); | |||||
| if (static::class === BigNumber::class) { | |||||
| // https://github.com/vimeo/psalm/issues/10309 | |||||
| assert($value instanceof static); | |||||
| return $value; | |||||
| } | |||||
| return static::from($value); | |||||
| } | |||||
| /** | |||||
| * @psalm-pure | |||||
| */ | |||||
| private static function _of(BigNumber|int|float|string $value) : BigNumber | |||||
| { | |||||
| if ($value instanceof BigNumber) { | |||||
| return $value; | |||||
| } | |||||
| if (\is_int($value)) { | |||||
| return new BigInteger((string) $value); | |||||
| } | |||||
| if (is_float($value)) { | |||||
| $value = (string) $value; | |||||
| } | |||||
| if (str_contains($value, '/')) { | |||||
| // Rational number | |||||
| if (\preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { | |||||
| throw NumberFormatException::invalidFormat($value); | |||||
| } | |||||
| $sign = $matches['sign']; | |||||
| $numerator = $matches['numerator']; | |||||
| $denominator = $matches['denominator']; | |||||
| assert($numerator !== null); | |||||
| assert($denominator !== null); | |||||
| $numerator = self::cleanUp($sign, $numerator); | |||||
| $denominator = self::cleanUp(null, $denominator); | |||||
| if ($denominator === '0') { | |||||
| throw DivisionByZeroException::denominatorMustNotBeZero(); | |||||
| } | |||||
| return new BigRational( | |||||
| new BigInteger($numerator), | |||||
| new BigInteger($denominator), | |||||
| false | |||||
| ); | |||||
| } else { | |||||
| // Integer or decimal number | |||||
| if (\preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { | |||||
| throw NumberFormatException::invalidFormat($value); | |||||
| } | |||||
| $sign = $matches['sign']; | |||||
| $point = $matches['point']; | |||||
| $integral = $matches['integral']; | |||||
| $fractional = $matches['fractional']; | |||||
| $exponent = $matches['exponent']; | |||||
| if ($integral === null && $fractional === null) { | |||||
| throw NumberFormatException::invalidFormat($value); | |||||
| } | |||||
| if ($integral === null) { | |||||
| $integral = '0'; | |||||
| } | |||||
| if ($point !== null || $exponent !== null) { | |||||
| $fractional = ($fractional ?? ''); | |||||
| $exponent = ($exponent !== null) ? (int)$exponent : 0; | |||||
| if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) { | |||||
| throw new NumberFormatException('Exponent too large.'); | |||||
| } | |||||
| $unscaledValue = self::cleanUp($sign, $integral . $fractional); | |||||
| $scale = \strlen($fractional) - $exponent; | |||||
| if ($scale < 0) { | |||||
| if ($unscaledValue !== '0') { | |||||
| $unscaledValue .= \str_repeat('0', -$scale); | |||||
| } | |||||
| $scale = 0; | |||||
| } | |||||
| return new BigDecimal($unscaledValue, $scale); | |||||
| } | |||||
| $integral = self::cleanUp($sign, $integral); | |||||
| return new BigInteger($integral); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Overridden by subclasses to convert a BigNumber to an instance of the subclass. | |||||
| * | |||||
| * @throws MathException If the value cannot be converted. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| abstract protected static function from(BigNumber $number): static; | |||||
| /** | |||||
| * Proxy method to access BigInteger's protected constructor from sibling classes. | |||||
| * | |||||
| * @internal | |||||
| * @psalm-pure | |||||
| */ | |||||
| final protected function newBigInteger(string $value) : BigInteger | |||||
| { | |||||
| return new BigInteger($value); | |||||
| } | |||||
| /** | |||||
| * Proxy method to access BigDecimal's protected constructor from sibling classes. | |||||
| * | |||||
| * @internal | |||||
| * @psalm-pure | |||||
| */ | |||||
| final protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal | |||||
| { | |||||
| return new BigDecimal($value, $scale); | |||||
| } | |||||
| /** | |||||
| * Proxy method to access BigRational's protected constructor from sibling classes. | |||||
| * | |||||
| * @internal | |||||
| * @psalm-pure | |||||
| */ | |||||
| final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational | |||||
| { | |||||
| return new BigRational($numerator, $denominator, $checkDenominator); | |||||
| } | |||||
| /** | |||||
| * Returns the minimum of the given values. | |||||
| * | |||||
| * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible | |||||
| * to an instance of the class this method is called on. | |||||
| * | |||||
| * @throws \InvalidArgumentException If no values are given. | |||||
| * @throws MathException If an argument is not valid. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| final public static function min(BigNumber|int|float|string ...$values) : static | |||||
| { | |||||
| $min = null; | |||||
| foreach ($values as $value) { | |||||
| $value = static::of($value); | |||||
| if ($min === null || $value->isLessThan($min)) { | |||||
| $min = $value; | |||||
| } | |||||
| } | |||||
| if ($min === null) { | |||||
| throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); | |||||
| } | |||||
| return $min; | |||||
| } | |||||
| /** | |||||
| * Returns the maximum of the given values. | |||||
| * | |||||
| * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible | |||||
| * to an instance of the class this method is called on. | |||||
| * | |||||
| * @throws \InvalidArgumentException If no values are given. | |||||
| * @throws MathException If an argument is not valid. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| final public static function max(BigNumber|int|float|string ...$values) : static | |||||
| { | |||||
| $max = null; | |||||
| foreach ($values as $value) { | |||||
| $value = static::of($value); | |||||
| if ($max === null || $value->isGreaterThan($max)) { | |||||
| $max = $value; | |||||
| } | |||||
| } | |||||
| if ($max === null) { | |||||
| throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); | |||||
| } | |||||
| return $max; | |||||
| } | |||||
| /** | |||||
| * Returns the sum of the given values. | |||||
| * | |||||
| * @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible | |||||
| * to an instance of the class this method is called on. | |||||
| * | |||||
| * @throws \InvalidArgumentException If no values are given. | |||||
| * @throws MathException If an argument is not valid. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| final public static function sum(BigNumber|int|float|string ...$values) : static | |||||
| { | |||||
| /** @var static|null $sum */ | |||||
| $sum = null; | |||||
| foreach ($values as $value) { | |||||
| $value = static::of($value); | |||||
| $sum = $sum === null ? $value : self::add($sum, $value); | |||||
| } | |||||
| if ($sum === null) { | |||||
| throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); | |||||
| } | |||||
| return $sum; | |||||
| } | |||||
| /** | |||||
| * Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException. | |||||
| * | |||||
| * @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to | |||||
| * concrete classes the responsibility to perform the addition themselves or delegate it to the given number, | |||||
| * depending on their ability to perform the operation. This will also require a version bump because we're | |||||
| * potentially breaking custom BigNumber implementations (if any...) | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| private static function add(BigNumber $a, BigNumber $b) : BigNumber | |||||
| { | |||||
| if ($a instanceof BigRational) { | |||||
| return $a->plus($b); | |||||
| } | |||||
| if ($b instanceof BigRational) { | |||||
| return $b->plus($a); | |||||
| } | |||||
| if ($a instanceof BigDecimal) { | |||||
| return $a->plus($b); | |||||
| } | |||||
| if ($b instanceof BigDecimal) { | |||||
| return $b->plus($a); | |||||
| } | |||||
| /** @var BigInteger $a */ | |||||
| return $a->plus($b); | |||||
| } | |||||
| /** | |||||
| * Removes optional leading zeros and applies sign. | |||||
| * | |||||
| * @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'. | |||||
| * @param string $number The number, validated as a non-empty string of digits. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| private static function cleanUp(string|null $sign, string $number) : string | |||||
| { | |||||
| $number = \ltrim($number, '0'); | |||||
| if ($number === '') { | |||||
| return '0'; | |||||
| } | |||||
| return $sign === '-' ? '-' . $number : $number; | |||||
| } | |||||
| /** | |||||
| * Checks if this number is equal to the given one. | |||||
| */ | |||||
| final public function isEqualTo(BigNumber|int|float|string $that) : bool | |||||
| { | |||||
| return $this->compareTo($that) === 0; | |||||
| } | |||||
| /** | |||||
| * Checks if this number is strictly lower than the given one. | |||||
| */ | |||||
| final public function isLessThan(BigNumber|int|float|string $that) : bool | |||||
| { | |||||
| return $this->compareTo($that) < 0; | |||||
| } | |||||
| /** | |||||
| * Checks if this number is lower than or equal to the given one. | |||||
| */ | |||||
| final public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool | |||||
| { | |||||
| return $this->compareTo($that) <= 0; | |||||
| } | |||||
| /** | |||||
| * Checks if this number is strictly greater than the given one. | |||||
| */ | |||||
| final public function isGreaterThan(BigNumber|int|float|string $that) : bool | |||||
| { | |||||
| return $this->compareTo($that) > 0; | |||||
| } | |||||
| /** | |||||
| * Checks if this number is greater than or equal to the given one. | |||||
| */ | |||||
| final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool | |||||
| { | |||||
| return $this->compareTo($that) >= 0; | |||||
| } | |||||
| /** | |||||
| * Checks if this number equals zero. | |||||
| */ | |||||
| final public function isZero() : bool | |||||
| { | |||||
| return $this->getSign() === 0; | |||||
| } | |||||
| /** | |||||
| * Checks if this number is strictly negative. | |||||
| */ | |||||
| final public function isNegative() : bool | |||||
| { | |||||
| return $this->getSign() < 0; | |||||
| } | |||||
| /** | |||||
| * Checks if this number is negative or zero. | |||||
| */ | |||||
| final public function isNegativeOrZero() : bool | |||||
| { | |||||
| return $this->getSign() <= 0; | |||||
| } | |||||
| /** | |||||
| * Checks if this number is strictly positive. | |||||
| */ | |||||
| final public function isPositive() : bool | |||||
| { | |||||
| return $this->getSign() > 0; | |||||
| } | |||||
| /** | |||||
| * Checks if this number is positive or zero. | |||||
| */ | |||||
| final public function isPositiveOrZero() : bool | |||||
| { | |||||
| return $this->getSign() >= 0; | |||||
| } | |||||
| /** | |||||
| * Returns the sign of this number. | |||||
| * | |||||
| * @psalm-return -1|0|1 | |||||
| * | |||||
| * @return int -1 if the number is negative, 0 if zero, 1 if positive. | |||||
| */ | |||||
| abstract public function getSign() : int; | |||||
| /** | |||||
| * Compares this number to the given one. | |||||
| * | |||||
| * @psalm-return -1|0|1 | |||||
| * | |||||
| * @return int -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`. | |||||
| * | |||||
| * @throws MathException If the number is not valid. | |||||
| */ | |||||
| abstract public function compareTo(BigNumber|int|float|string $that) : int; | |||||
| /** | |||||
| * Converts this number to a BigInteger. | |||||
| * | |||||
| * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding. | |||||
| */ | |||||
| abstract public function toBigInteger() : BigInteger; | |||||
| /** | |||||
| * Converts this number to a BigDecimal. | |||||
| * | |||||
| * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding. | |||||
| */ | |||||
| abstract public function toBigDecimal() : BigDecimal; | |||||
| /** | |||||
| * Converts this number to a BigRational. | |||||
| */ | |||||
| abstract public function toBigRational() : BigRational; | |||||
| /** | |||||
| * Converts this number to a BigDecimal with the given scale, using rounding if necessary. | |||||
| * | |||||
| * @param int $scale The scale of the resulting `BigDecimal`. | |||||
| * @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY. | |||||
| * | |||||
| * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding. | |||||
| * This only applies when RoundingMode::UNNECESSARY is used. | |||||
| */ | |||||
| abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; | |||||
| /** | |||||
| * Returns the exact value of this number as a native integer. | |||||
| * | |||||
| * If this number cannot be converted to a native integer without losing precision, an exception is thrown. | |||||
| * Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit. | |||||
| * | |||||
| * @throws MathException If this number cannot be exactly converted to a native integer. | |||||
| */ | |||||
| abstract public function toInt() : int; | |||||
| /** | |||||
| * Returns an approximation of this number as a floating-point value. | |||||
| * | |||||
| * Note that this method can discard information as the precision of a floating-point value | |||||
| * is inherently limited. | |||||
| * | |||||
| * If the number is greater than the largest representable floating point number, positive infinity is returned. | |||||
| * If the number is less than the smallest representable floating point number, negative infinity is returned. | |||||
| */ | |||||
| abstract public function toFloat() : float; | |||||
| /** | |||||
| * Returns a string representation of this number. | |||||
| * | |||||
| * The output of this method can be parsed by the `of()` factory method; | |||||
| * this will yield an object equal to this one, without any information loss. | |||||
| */ | |||||
| abstract public function __toString() : string; | |||||
| final public function jsonSerialize() : string | |||||
| { | |||||
| return $this->__toString(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,413 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math; | |||||
| use Brick\Math\Exception\DivisionByZeroException; | |||||
| use Brick\Math\Exception\MathException; | |||||
| use Brick\Math\Exception\NumberFormatException; | |||||
| use Brick\Math\Exception\RoundingNecessaryException; | |||||
| /** | |||||
| * An arbitrarily large rational number. | |||||
| * | |||||
| * This class is immutable. | |||||
| * | |||||
| * @psalm-immutable | |||||
| */ | |||||
| final class BigRational extends BigNumber | |||||
| { | |||||
| /** | |||||
| * The numerator. | |||||
| */ | |||||
| private readonly BigInteger $numerator; | |||||
| /** | |||||
| * The denominator. Always strictly positive. | |||||
| */ | |||||
| private readonly BigInteger $denominator; | |||||
| /** | |||||
| * Protected constructor. Use a factory method to obtain an instance. | |||||
| * | |||||
| * @param BigInteger $numerator The numerator. | |||||
| * @param BigInteger $denominator The denominator. | |||||
| * @param bool $checkDenominator Whether to check the denominator for negative and zero. | |||||
| * | |||||
| * @throws DivisionByZeroException If the denominator is zero. | |||||
| */ | |||||
| protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) | |||||
| { | |||||
| if ($checkDenominator) { | |||||
| if ($denominator->isZero()) { | |||||
| throw DivisionByZeroException::denominatorMustNotBeZero(); | |||||
| } | |||||
| if ($denominator->isNegative()) { | |||||
| $numerator = $numerator->negated(); | |||||
| $denominator = $denominator->negated(); | |||||
| } | |||||
| } | |||||
| $this->numerator = $numerator; | |||||
| $this->denominator = $denominator; | |||||
| } | |||||
| /** | |||||
| * @psalm-pure | |||||
| */ | |||||
| protected static function from(BigNumber $number): static | |||||
| { | |||||
| return $number->toBigRational(); | |||||
| } | |||||
| /** | |||||
| * Creates a BigRational out of a numerator and a denominator. | |||||
| * | |||||
| * If the denominator is negative, the signs of both the numerator and the denominator | |||||
| * will be inverted to ensure that the denominator is always positive. | |||||
| * | |||||
| * @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger. | |||||
| * @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger. | |||||
| * | |||||
| * @throws NumberFormatException If an argument does not represent a valid number. | |||||
| * @throws RoundingNecessaryException If an argument represents a non-integer number. | |||||
| * @throws DivisionByZeroException If the denominator is zero. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function nd( | |||||
| BigNumber|int|float|string $numerator, | |||||
| BigNumber|int|float|string $denominator, | |||||
| ) : BigRational { | |||||
| $numerator = BigInteger::of($numerator); | |||||
| $denominator = BigInteger::of($denominator); | |||||
| return new BigRational($numerator, $denominator, true); | |||||
| } | |||||
| /** | |||||
| * Returns a BigRational representing zero. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function zero() : BigRational | |||||
| { | |||||
| /** | |||||
| * @psalm-suppress ImpureStaticVariable | |||||
| * @var BigRational|null $zero | |||||
| */ | |||||
| static $zero; | |||||
| if ($zero === null) { | |||||
| $zero = new BigRational(BigInteger::zero(), BigInteger::one(), false); | |||||
| } | |||||
| return $zero; | |||||
| } | |||||
| /** | |||||
| * Returns a BigRational representing one. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function one() : BigRational | |||||
| { | |||||
| /** | |||||
| * @psalm-suppress ImpureStaticVariable | |||||
| * @var BigRational|null $one | |||||
| */ | |||||
| static $one; | |||||
| if ($one === null) { | |||||
| $one = new BigRational(BigInteger::one(), BigInteger::one(), false); | |||||
| } | |||||
| return $one; | |||||
| } | |||||
| /** | |||||
| * Returns a BigRational representing ten. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function ten() : BigRational | |||||
| { | |||||
| /** | |||||
| * @psalm-suppress ImpureStaticVariable | |||||
| * @var BigRational|null $ten | |||||
| */ | |||||
| static $ten; | |||||
| if ($ten === null) { | |||||
| $ten = new BigRational(BigInteger::ten(), BigInteger::one(), false); | |||||
| } | |||||
| return $ten; | |||||
| } | |||||
| public function getNumerator() : BigInteger | |||||
| { | |||||
| return $this->numerator; | |||||
| } | |||||
| public function getDenominator() : BigInteger | |||||
| { | |||||
| return $this->denominator; | |||||
| } | |||||
| /** | |||||
| * Returns the quotient of the division of the numerator by the denominator. | |||||
| */ | |||||
| public function quotient() : BigInteger | |||||
| { | |||||
| return $this->numerator->quotient($this->denominator); | |||||
| } | |||||
| /** | |||||
| * Returns the remainder of the division of the numerator by the denominator. | |||||
| */ | |||||
| public function remainder() : BigInteger | |||||
| { | |||||
| return $this->numerator->remainder($this->denominator); | |||||
| } | |||||
| /** | |||||
| * Returns the quotient and remainder of the division of the numerator by the denominator. | |||||
| * | |||||
| * @return BigInteger[] | |||||
| * | |||||
| * @psalm-return array{BigInteger, BigInteger} | |||||
| */ | |||||
| public function quotientAndRemainder() : array | |||||
| { | |||||
| return $this->numerator->quotientAndRemainder($this->denominator); | |||||
| } | |||||
| /** | |||||
| * Returns the sum of this number and the given one. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The number to add. | |||||
| * | |||||
| * @throws MathException If the number is not valid. | |||||
| */ | |||||
| public function plus(BigNumber|int|float|string $that) : BigRational | |||||
| { | |||||
| $that = BigRational::of($that); | |||||
| $numerator = $this->numerator->multipliedBy($that->denominator); | |||||
| $numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator)); | |||||
| $denominator = $this->denominator->multipliedBy($that->denominator); | |||||
| return new BigRational($numerator, $denominator, false); | |||||
| } | |||||
| /** | |||||
| * Returns the difference of this number and the given one. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The number to subtract. | |||||
| * | |||||
| * @throws MathException If the number is not valid. | |||||
| */ | |||||
| public function minus(BigNumber|int|float|string $that) : BigRational | |||||
| { | |||||
| $that = BigRational::of($that); | |||||
| $numerator = $this->numerator->multipliedBy($that->denominator); | |||||
| $numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator)); | |||||
| $denominator = $this->denominator->multipliedBy($that->denominator); | |||||
| return new BigRational($numerator, $denominator, false); | |||||
| } | |||||
| /** | |||||
| * Returns the product of this number and the given one. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The multiplier. | |||||
| * | |||||
| * @throws MathException If the multiplier is not a valid number. | |||||
| */ | |||||
| public function multipliedBy(BigNumber|int|float|string $that) : BigRational | |||||
| { | |||||
| $that = BigRational::of($that); | |||||
| $numerator = $this->numerator->multipliedBy($that->numerator); | |||||
| $denominator = $this->denominator->multipliedBy($that->denominator); | |||||
| return new BigRational($numerator, $denominator, false); | |||||
| } | |||||
| /** | |||||
| * Returns the result of the division of this number by the given one. | |||||
| * | |||||
| * @param BigNumber|int|float|string $that The divisor. | |||||
| * | |||||
| * @throws MathException If the divisor is not a valid number, or is zero. | |||||
| */ | |||||
| public function dividedBy(BigNumber|int|float|string $that) : BigRational | |||||
| { | |||||
| $that = BigRational::of($that); | |||||
| $numerator = $this->numerator->multipliedBy($that->denominator); | |||||
| $denominator = $this->denominator->multipliedBy($that->numerator); | |||||
| return new BigRational($numerator, $denominator, true); | |||||
| } | |||||
| /** | |||||
| * Returns this number exponentiated to the given value. | |||||
| * | |||||
| * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. | |||||
| */ | |||||
| public function power(int $exponent) : BigRational | |||||
| { | |||||
| if ($exponent === 0) { | |||||
| $one = BigInteger::one(); | |||||
| return new BigRational($one, $one, false); | |||||
| } | |||||
| if ($exponent === 1) { | |||||
| return $this; | |||||
| } | |||||
| return new BigRational( | |||||
| $this->numerator->power($exponent), | |||||
| $this->denominator->power($exponent), | |||||
| false | |||||
| ); | |||||
| } | |||||
| /** | |||||
| * Returns the reciprocal of this BigRational. | |||||
| * | |||||
| * The reciprocal has the numerator and denominator swapped. | |||||
| * | |||||
| * @throws DivisionByZeroException If the numerator is zero. | |||||
| */ | |||||
| public function reciprocal() : BigRational | |||||
| { | |||||
| return new BigRational($this->denominator, $this->numerator, true); | |||||
| } | |||||
| /** | |||||
| * Returns the absolute value of this BigRational. | |||||
| */ | |||||
| public function abs() : BigRational | |||||
| { | |||||
| return new BigRational($this->numerator->abs(), $this->denominator, false); | |||||
| } | |||||
| /** | |||||
| * Returns the negated value of this BigRational. | |||||
| */ | |||||
| public function negated() : BigRational | |||||
| { | |||||
| return new BigRational($this->numerator->negated(), $this->denominator, false); | |||||
| } | |||||
| /** | |||||
| * Returns the simplified value of this BigRational. | |||||
| */ | |||||
| public function simplified() : BigRational | |||||
| { | |||||
| $gcd = $this->numerator->gcd($this->denominator); | |||||
| $numerator = $this->numerator->quotient($gcd); | |||||
| $denominator = $this->denominator->quotient($gcd); | |||||
| return new BigRational($numerator, $denominator, false); | |||||
| } | |||||
| public function compareTo(BigNumber|int|float|string $that) : int | |||||
| { | |||||
| return $this->minus($that)->getSign(); | |||||
| } | |||||
| public function getSign() : int | |||||
| { | |||||
| return $this->numerator->getSign(); | |||||
| } | |||||
| public function toBigInteger() : BigInteger | |||||
| { | |||||
| $simplified = $this->simplified(); | |||||
| if (! $simplified->denominator->isEqualTo(1)) { | |||||
| throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.'); | |||||
| } | |||||
| return $simplified->numerator; | |||||
| } | |||||
| public function toBigDecimal() : BigDecimal | |||||
| { | |||||
| return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator); | |||||
| } | |||||
| public function toBigRational() : BigRational | |||||
| { | |||||
| return $this; | |||||
| } | |||||
| public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal | |||||
| { | |||||
| return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode); | |||||
| } | |||||
| public function toInt() : int | |||||
| { | |||||
| return $this->toBigInteger()->toInt(); | |||||
| } | |||||
| public function toFloat() : float | |||||
| { | |||||
| $simplified = $this->simplified(); | |||||
| return $simplified->numerator->toFloat() / $simplified->denominator->toFloat(); | |||||
| } | |||||
| public function __toString() : string | |||||
| { | |||||
| $numerator = (string) $this->numerator; | |||||
| $denominator = (string) $this->denominator; | |||||
| if ($denominator === '1') { | |||||
| return $numerator; | |||||
| } | |||||
| return $this->numerator . '/' . $this->denominator; | |||||
| } | |||||
| /** | |||||
| * This method is required for serializing the object and SHOULD NOT be accessed directly. | |||||
| * | |||||
| * @internal | |||||
| * | |||||
| * @return array{numerator: BigInteger, denominator: BigInteger} | |||||
| */ | |||||
| public function __serialize(): array | |||||
| { | |||||
| return ['numerator' => $this->numerator, 'denominator' => $this->denominator]; | |||||
| } | |||||
| /** | |||||
| * This method is only here to allow unserializing the object and cannot be accessed directly. | |||||
| * | |||||
| * @internal | |||||
| * @psalm-suppress RedundantPropertyInitializationCheck | |||||
| * | |||||
| * @param array{numerator: BigInteger, denominator: BigInteger} $data | |||||
| * | |||||
| * @throws \LogicException | |||||
| */ | |||||
| public function __unserialize(array $data): void | |||||
| { | |||||
| if (isset($this->numerator)) { | |||||
| throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); | |||||
| } | |||||
| $this->numerator = $data['numerator']; | |||||
| $this->denominator = $data['denominator']; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math\Exception; | |||||
| /** | |||||
| * Exception thrown when a division by zero occurs. | |||||
| */ | |||||
| class DivisionByZeroException extends MathException | |||||
| { | |||||
| /** | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function divisionByZero() : DivisionByZeroException | |||||
| { | |||||
| return new self('Division by zero.'); | |||||
| } | |||||
| /** | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function modulusMustNotBeZero() : DivisionByZeroException | |||||
| { | |||||
| return new self('The modulus must not be zero.'); | |||||
| } | |||||
| /** | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function denominatorMustNotBeZero() : DivisionByZeroException | |||||
| { | |||||
| return new self('The denominator of a rational number cannot be zero.'); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math\Exception; | |||||
| use Brick\Math\BigInteger; | |||||
| /** | |||||
| * Exception thrown when an integer overflow occurs. | |||||
| */ | |||||
| class IntegerOverflowException extends MathException | |||||
| { | |||||
| /** | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function toIntOverflow(BigInteger $value) : IntegerOverflowException | |||||
| { | |||||
| $message = '%s is out of range %d to %d and cannot be represented as an integer.'; | |||||
| return new self(\sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX)); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,12 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math\Exception; | |||||
| /** | |||||
| * Base class for all math exceptions. | |||||
| */ | |||||
| class MathException extends \Exception | |||||
| { | |||||
| } | |||||
| @@ -0,0 +1,12 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math\Exception; | |||||
| /** | |||||
| * Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number. | |||||
| */ | |||||
| class NegativeNumberException extends MathException | |||||
| { | |||||
| } | |||||
| @@ -0,0 +1,41 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math\Exception; | |||||
| /** | |||||
| * Exception thrown when attempting to create a number from a string with an invalid format. | |||||
| */ | |||||
| class NumberFormatException extends MathException | |||||
| { | |||||
| public static function invalidFormat(string $value) : self | |||||
| { | |||||
| return new self(\sprintf( | |||||
| 'The given value "%s" does not represent a valid number.', | |||||
| $value, | |||||
| )); | |||||
| } | |||||
| /** | |||||
| * @param string $char The failing character. | |||||
| * | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function charNotInAlphabet(string $char) : self | |||||
| { | |||||
| $ord = \ord($char); | |||||
| if ($ord < 32 || $ord > 126) { | |||||
| $char = \strtoupper(\dechex($ord)); | |||||
| if ($ord < 10) { | |||||
| $char = '0' . $char; | |||||
| } | |||||
| } else { | |||||
| $char = '"' . $char . '"'; | |||||
| } | |||||
| return new self(\sprintf('Char %s is not a valid character in the given alphabet.', $char)); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math\Exception; | |||||
| /** | |||||
| * Exception thrown when a number cannot be represented at the requested scale without rounding. | |||||
| */ | |||||
| class RoundingNecessaryException extends MathException | |||||
| { | |||||
| /** | |||||
| * @psalm-pure | |||||
| */ | |||||
| public static function roundingNecessary() : RoundingNecessaryException | |||||
| { | |||||
| return new self('Rounding is necessary to represent the result of the operation at this scale.'); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,668 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math\Internal; | |||||
| use Brick\Math\Exception\RoundingNecessaryException; | |||||
| use Brick\Math\RoundingMode; | |||||
| /** | |||||
| * Performs basic operations on arbitrary size integers. | |||||
| * | |||||
| * Unless otherwise specified, all parameters must be validated as non-empty strings of digits, | |||||
| * without leading zero, and with an optional leading minus sign if the number is not zero. | |||||
| * | |||||
| * Any other parameter format will lead to undefined behaviour. | |||||
| * All methods must return strings respecting this format, unless specified otherwise. | |||||
| * | |||||
| * @internal | |||||
| * | |||||
| * @psalm-immutable | |||||
| */ | |||||
| abstract class Calculator | |||||
| { | |||||
| /** | |||||
| * The maximum exponent value allowed for the pow() method. | |||||
| */ | |||||
| public const MAX_POWER = 1_000_000; | |||||
| /** | |||||
| * The alphabet for converting from and to base 2 to 36, lowercase. | |||||
| */ | |||||
| public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'; | |||||
| /** | |||||
| * The Calculator instance in use. | |||||
| */ | |||||
| private static ?Calculator $instance = null; | |||||
| /** | |||||
| * Sets the Calculator instance to use. | |||||
| * | |||||
| * An instance is typically set only in unit tests: the autodetect is usually the best option. | |||||
| * | |||||
| * @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect. | |||||
| */ | |||||
| final public static function set(?Calculator $calculator) : void | |||||
| { | |||||
| self::$instance = $calculator; | |||||
| } | |||||
| /** | |||||
| * Returns the Calculator instance to use. | |||||
| * | |||||
| * If none has been explicitly set, the fastest available implementation will be returned. | |||||
| * | |||||
| * @psalm-pure | |||||
| * @psalm-suppress ImpureStaticProperty | |||||
| */ | |||||
| final public static function get() : Calculator | |||||
| { | |||||
| if (self::$instance === null) { | |||||
| /** @psalm-suppress ImpureMethodCall */ | |||||
| self::$instance = self::detect(); | |||||
| } | |||||
| return self::$instance; | |||||
| } | |||||
| /** | |||||
| * Returns the fastest available Calculator implementation. | |||||
| * | |||||
| * @codeCoverageIgnore | |||||
| */ | |||||
| private static function detect() : Calculator | |||||
| { | |||||
| if (\extension_loaded('gmp')) { | |||||
| return new Calculator\GmpCalculator(); | |||||
| } | |||||
| if (\extension_loaded('bcmath')) { | |||||
| return new Calculator\BcMathCalculator(); | |||||
| } | |||||
| return new Calculator\NativeCalculator(); | |||||
| } | |||||
| /** | |||||
| * Extracts the sign & digits of the operands. | |||||
| * | |||||
| * @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits. | |||||
| */ | |||||
| final protected function init(string $a, string $b) : array | |||||
| { | |||||
| return [ | |||||
| $aNeg = ($a[0] === '-'), | |||||
| $bNeg = ($b[0] === '-'), | |||||
| $aNeg ? \substr($a, 1) : $a, | |||||
| $bNeg ? \substr($b, 1) : $b, | |||||
| ]; | |||||
| } | |||||
| /** | |||||
| * Returns the absolute value of a number. | |||||
| */ | |||||
| final public function abs(string $n) : string | |||||
| { | |||||
| return ($n[0] === '-') ? \substr($n, 1) : $n; | |||||
| } | |||||
| /** | |||||
| * Negates a number. | |||||
| */ | |||||
| final public function neg(string $n) : string | |||||
| { | |||||
| if ($n === '0') { | |||||
| return '0'; | |||||
| } | |||||
| if ($n[0] === '-') { | |||||
| return \substr($n, 1); | |||||
| } | |||||
| return '-' . $n; | |||||
| } | |||||
| /** | |||||
| * Compares two numbers. | |||||
| * | |||||
| * @psalm-return -1|0|1 | |||||
| * | |||||
| * @return int -1 if the first number is less than, 0 if equal to, 1 if greater than the second number. | |||||
| */ | |||||
| final public function cmp(string $a, string $b) : int | |||||
| { | |||||
| [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); | |||||
| if ($aNeg && ! $bNeg) { | |||||
| return -1; | |||||
| } | |||||
| if ($bNeg && ! $aNeg) { | |||||
| return 1; | |||||
| } | |||||
| $aLen = \strlen($aDig); | |||||
| $bLen = \strlen($bDig); | |||||
| if ($aLen < $bLen) { | |||||
| $result = -1; | |||||
| } elseif ($aLen > $bLen) { | |||||
| $result = 1; | |||||
| } else { | |||||
| $result = $aDig <=> $bDig; | |||||
| } | |||||
| return $aNeg ? -$result : $result; | |||||
| } | |||||
| /** | |||||
| * Adds two numbers. | |||||
| */ | |||||
| abstract public function add(string $a, string $b) : string; | |||||
| /** | |||||
| * Subtracts two numbers. | |||||
| */ | |||||
| abstract public function sub(string $a, string $b) : string; | |||||
| /** | |||||
| * Multiplies two numbers. | |||||
| */ | |||||
| abstract public function mul(string $a, string $b) : string; | |||||
| /** | |||||
| * Returns the quotient of the division of two numbers. | |||||
| * | |||||
| * @param string $a The dividend. | |||||
| * @param string $b The divisor, must not be zero. | |||||
| * | |||||
| * @return string The quotient. | |||||
| */ | |||||
| abstract public function divQ(string $a, string $b) : string; | |||||
| /** | |||||
| * Returns the remainder of the division of two numbers. | |||||
| * | |||||
| * @param string $a The dividend. | |||||
| * @param string $b The divisor, must not be zero. | |||||
| * | |||||
| * @return string The remainder. | |||||
| */ | |||||
| abstract public function divR(string $a, string $b) : string; | |||||
| /** | |||||
| * Returns the quotient and remainder of the division of two numbers. | |||||
| * | |||||
| * @param string $a The dividend. | |||||
| * @param string $b The divisor, must not be zero. | |||||
| * | |||||
| * @return array{string, string} An array containing the quotient and remainder. | |||||
| */ | |||||
| abstract public function divQR(string $a, string $b) : array; | |||||
| /** | |||||
| * Exponentiates a number. | |||||
| * | |||||
| * @param string $a The base number. | |||||
| * @param int $e The exponent, validated as an integer between 0 and MAX_POWER. | |||||
| * | |||||
| * @return string The power. | |||||
| */ | |||||
| abstract public function pow(string $a, int $e) : string; | |||||
| /** | |||||
| * @param string $b The modulus; must not be zero. | |||||
| */ | |||||
| public function mod(string $a, string $b) : string | |||||
| { | |||||
| return $this->divR($this->add($this->divR($a, $b), $b), $b); | |||||
| } | |||||
| /** | |||||
| * Returns the modular multiplicative inverse of $x modulo $m. | |||||
| * | |||||
| * If $x has no multiplicative inverse mod m, this method must return null. | |||||
| * | |||||
| * This method can be overridden by the concrete implementation if the underlying library has built-in support. | |||||
| * | |||||
| * @param string $m The modulus; must not be negative or zero. | |||||
| */ | |||||
| public function modInverse(string $x, string $m) : ?string | |||||
| { | |||||
| if ($m === '1') { | |||||
| return '0'; | |||||
| } | |||||
| $modVal = $x; | |||||
| if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) { | |||||
| $modVal = $this->mod($x, $m); | |||||
| } | |||||
| [$g, $x] = $this->gcdExtended($modVal, $m); | |||||
| if ($g !== '1') { | |||||
| return null; | |||||
| } | |||||
| return $this->mod($this->add($this->mod($x, $m), $m), $m); | |||||
| } | |||||
| /** | |||||
| * Raises a number into power with modulo. | |||||
| * | |||||
| * @param string $base The base number; must be positive or zero. | |||||
| * @param string $exp The exponent; must be positive or zero. | |||||
| * @param string $mod The modulus; must be strictly positive. | |||||
| */ | |||||
| abstract public function modPow(string $base, string $exp, string $mod) : string; | |||||
| /** | |||||
| * Returns the greatest common divisor of the two numbers. | |||||
| * | |||||
| * This method can be overridden by the concrete implementation if the underlying library | |||||
| * has built-in support for GCD calculations. | |||||
| * | |||||
| * @return string The GCD, always positive, or zero if both arguments are zero. | |||||
| */ | |||||
| public function gcd(string $a, string $b) : string | |||||
| { | |||||
| if ($a === '0') { | |||||
| return $this->abs($b); | |||||
| } | |||||
| if ($b === '0') { | |||||
| return $this->abs($a); | |||||
| } | |||||
| return $this->gcd($b, $this->divR($a, $b)); | |||||
| } | |||||
| /** | |||||
| * @return array{string, string, string} GCD, X, Y | |||||
| */ | |||||
| private function gcdExtended(string $a, string $b) : array | |||||
| { | |||||
| if ($a === '0') { | |||||
| return [$b, '0', '1']; | |||||
| } | |||||
| [$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a); | |||||
| $x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1)); | |||||
| $y = $x1; | |||||
| return [$gcd, $x, $y]; | |||||
| } | |||||
| /** | |||||
| * Returns the square root of the given number, rounded down. | |||||
| * | |||||
| * The result is the largest x such that x² ≤ n. | |||||
| * The input MUST NOT be negative. | |||||
| */ | |||||
| abstract public function sqrt(string $n) : string; | |||||
| /** | |||||
| * Converts a number from an arbitrary base. | |||||
| * | |||||
| * This method can be overridden by the concrete implementation if the underlying library | |||||
| * has built-in support for base conversion. | |||||
| * | |||||
| * @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base. | |||||
| * @param int $base The base of the number, validated from 2 to 36. | |||||
| * | |||||
| * @return string The converted number, following the Calculator conventions. | |||||
| */ | |||||
| public function fromBase(string $number, int $base) : string | |||||
| { | |||||
| return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base); | |||||
| } | |||||
| /** | |||||
| * Converts a number to an arbitrary base. | |||||
| * | |||||
| * This method can be overridden by the concrete implementation if the underlying library | |||||
| * has built-in support for base conversion. | |||||
| * | |||||
| * @param string $number The number to convert, following the Calculator conventions. | |||||
| * @param int $base The base to convert to, validated from 2 to 36. | |||||
| * | |||||
| * @return string The converted number, lowercase. | |||||
| */ | |||||
| public function toBase(string $number, int $base) : string | |||||
| { | |||||
| $negative = ($number[0] === '-'); | |||||
| if ($negative) { | |||||
| $number = \substr($number, 1); | |||||
| } | |||||
| $number = $this->toArbitraryBase($number, self::ALPHABET, $base); | |||||
| if ($negative) { | |||||
| return '-' . $number; | |||||
| } | |||||
| return $number; | |||||
| } | |||||
| /** | |||||
| * Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10. | |||||
| * | |||||
| * @param string $number The number to convert, validated as a non-empty string, | |||||
| * containing only chars in the given alphabet/base. | |||||
| * @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. | |||||
| * @param int $base The base of the number, validated from 2 to alphabet length. | |||||
| * | |||||
| * @return string The number in base 10, following the Calculator conventions. | |||||
| */ | |||||
| final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string | |||||
| { | |||||
| // remove leading "zeros" | |||||
| $number = \ltrim($number, $alphabet[0]); | |||||
| if ($number === '') { | |||||
| return '0'; | |||||
| } | |||||
| // optimize for "one" | |||||
| if ($number === $alphabet[1]) { | |||||
| return '1'; | |||||
| } | |||||
| $result = '0'; | |||||
| $power = '1'; | |||||
| $base = (string) $base; | |||||
| for ($i = \strlen($number) - 1; $i >= 0; $i--) { | |||||
| $index = \strpos($alphabet, $number[$i]); | |||||
| if ($index !== 0) { | |||||
| $result = $this->add($result, ($index === 1) | |||||
| ? $power | |||||
| : $this->mul($power, (string) $index) | |||||
| ); | |||||
| } | |||||
| if ($i !== 0) { | |||||
| $power = $this->mul($power, $base); | |||||
| } | |||||
| } | |||||
| return $result; | |||||
| } | |||||
| /** | |||||
| * Converts a non-negative number to an arbitrary base using a custom alphabet. | |||||
| * | |||||
| * @param string $number The number to convert, positive or zero, following the Calculator conventions. | |||||
| * @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. | |||||
| * @param int $base The base to convert to, validated from 2 to alphabet length. | |||||
| * | |||||
| * @return string The converted number in the given alphabet. | |||||
| */ | |||||
| final public function toArbitraryBase(string $number, string $alphabet, int $base) : string | |||||
| { | |||||
| if ($number === '0') { | |||||
| return $alphabet[0]; | |||||
| } | |||||
| $base = (string) $base; | |||||
| $result = ''; | |||||
| while ($number !== '0') { | |||||
| [$number, $remainder] = $this->divQR($number, $base); | |||||
| $remainder = (int) $remainder; | |||||
| $result .= $alphabet[$remainder]; | |||||
| } | |||||
| return \strrev($result); | |||||
| } | |||||
| /** | |||||
| * Performs a rounded division. | |||||
| * | |||||
| * Rounding is performed when the remainder of the division is not zero. | |||||
| * | |||||
| * @param string $a The dividend. | |||||
| * @param string $b The divisor, must not be zero. | |||||
| * @param RoundingMode $roundingMode The rounding mode. | |||||
| * | |||||
| * @throws \InvalidArgumentException If the rounding mode is invalid. | |||||
| * @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary. | |||||
| * | |||||
| * @psalm-suppress ImpureFunctionCall | |||||
| */ | |||||
| final public function divRound(string $a, string $b, RoundingMode $roundingMode) : string | |||||
| { | |||||
| [$quotient, $remainder] = $this->divQR($a, $b); | |||||
| $hasDiscardedFraction = ($remainder !== '0'); | |||||
| $isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-'); | |||||
| $discardedFractionSign = function() use ($remainder, $b) : int { | |||||
| $r = $this->abs($this->mul($remainder, '2')); | |||||
| $b = $this->abs($b); | |||||
| return $this->cmp($r, $b); | |||||
| }; | |||||
| $increment = false; | |||||
| switch ($roundingMode) { | |||||
| case RoundingMode::UNNECESSARY: | |||||
| if ($hasDiscardedFraction) { | |||||
| throw RoundingNecessaryException::roundingNecessary(); | |||||
| } | |||||
| break; | |||||
| case RoundingMode::UP: | |||||
| $increment = $hasDiscardedFraction; | |||||
| break; | |||||
| case RoundingMode::DOWN: | |||||
| break; | |||||
| case RoundingMode::CEILING: | |||||
| $increment = $hasDiscardedFraction && $isPositiveOrZero; | |||||
| break; | |||||
| case RoundingMode::FLOOR: | |||||
| $increment = $hasDiscardedFraction && ! $isPositiveOrZero; | |||||
| break; | |||||
| case RoundingMode::HALF_UP: | |||||
| $increment = $discardedFractionSign() >= 0; | |||||
| break; | |||||
| case RoundingMode::HALF_DOWN: | |||||
| $increment = $discardedFractionSign() > 0; | |||||
| break; | |||||
| case RoundingMode::HALF_CEILING: | |||||
| $increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0; | |||||
| break; | |||||
| case RoundingMode::HALF_FLOOR: | |||||
| $increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; | |||||
| break; | |||||
| case RoundingMode::HALF_EVEN: | |||||
| $lastDigit = (int) $quotient[-1]; | |||||
| $lastDigitIsEven = ($lastDigit % 2 === 0); | |||||
| $increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; | |||||
| break; | |||||
| default: | |||||
| throw new \InvalidArgumentException('Invalid rounding mode.'); | |||||
| } | |||||
| if ($increment) { | |||||
| return $this->add($quotient, $isPositiveOrZero ? '1' : '-1'); | |||||
| } | |||||
| return $quotient; | |||||
| } | |||||
| /** | |||||
| * Calculates bitwise AND of two numbers. | |||||
| * | |||||
| * This method can be overridden by the concrete implementation if the underlying library | |||||
| * has built-in support for bitwise operations. | |||||
| */ | |||||
| public function and(string $a, string $b) : string | |||||
| { | |||||
| return $this->bitwise('and', $a, $b); | |||||
| } | |||||
| /** | |||||
| * Calculates bitwise OR of two numbers. | |||||
| * | |||||
| * This method can be overridden by the concrete implementation if the underlying library | |||||
| * has built-in support for bitwise operations. | |||||
| */ | |||||
| public function or(string $a, string $b) : string | |||||
| { | |||||
| return $this->bitwise('or', $a, $b); | |||||
| } | |||||
| /** | |||||
| * Calculates bitwise XOR of two numbers. | |||||
| * | |||||
| * This method can be overridden by the concrete implementation if the underlying library | |||||
| * has built-in support for bitwise operations. | |||||
| */ | |||||
| public function xor(string $a, string $b) : string | |||||
| { | |||||
| return $this->bitwise('xor', $a, $b); | |||||
| } | |||||
| /** | |||||
| * Performs a bitwise operation on a decimal number. | |||||
| * | |||||
| * @param 'and'|'or'|'xor' $operator The operator to use. | |||||
| * @param string $a The left operand. | |||||
| * @param string $b The right operand. | |||||
| */ | |||||
| private function bitwise(string $operator, string $a, string $b) : string | |||||
| { | |||||
| [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); | |||||
| $aBin = $this->toBinary($aDig); | |||||
| $bBin = $this->toBinary($bDig); | |||||
| $aLen = \strlen($aBin); | |||||
| $bLen = \strlen($bBin); | |||||
| if ($aLen > $bLen) { | |||||
| $bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin; | |||||
| } elseif ($bLen > $aLen) { | |||||
| $aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin; | |||||
| } | |||||
| if ($aNeg) { | |||||
| $aBin = $this->twosComplement($aBin); | |||||
| } | |||||
| if ($bNeg) { | |||||
| $bBin = $this->twosComplement($bBin); | |||||
| } | |||||
| $value = match ($operator) { | |||||
| 'and' => $aBin & $bBin, | |||||
| 'or' => $aBin | $bBin, | |||||
| 'xor' => $aBin ^ $bBin, | |||||
| }; | |||||
| $negative = match ($operator) { | |||||
| 'and' => $aNeg and $bNeg, | |||||
| 'or' => $aNeg or $bNeg, | |||||
| 'xor' => $aNeg xor $bNeg, | |||||
| }; | |||||
| if ($negative) { | |||||
| $value = $this->twosComplement($value); | |||||
| } | |||||
| $result = $this->toDecimal($value); | |||||
| return $negative ? $this->neg($result) : $result; | |||||
| } | |||||
| /** | |||||
| * @param string $number A positive, binary number. | |||||
| */ | |||||
| private function twosComplement(string $number) : string | |||||
| { | |||||
| $xor = \str_repeat("\xff", \strlen($number)); | |||||
| $number ^= $xor; | |||||
| for ($i = \strlen($number) - 1; $i >= 0; $i--) { | |||||
| $byte = \ord($number[$i]); | |||||
| if (++$byte !== 256) { | |||||
| $number[$i] = \chr($byte); | |||||
| break; | |||||
| } | |||||
| $number[$i] = "\x00"; | |||||
| if ($i === 0) { | |||||
| $number = "\x01" . $number; | |||||
| } | |||||
| } | |||||
| return $number; | |||||
| } | |||||
| /** | |||||
| * Converts a decimal number to a binary string. | |||||
| * | |||||
| * @param string $number The number to convert, positive or zero, only digits. | |||||
| */ | |||||
| private function toBinary(string $number) : string | |||||
| { | |||||
| $result = ''; | |||||
| while ($number !== '0') { | |||||
| [$number, $remainder] = $this->divQR($number, '256'); | |||||
| $result .= \chr((int) $remainder); | |||||
| } | |||||
| return \strrev($result); | |||||
| } | |||||
| /** | |||||
| * Returns the positive decimal representation of a binary number. | |||||
| * | |||||
| * @param string $bytes The bytes representing the number. | |||||
| */ | |||||
| private function toDecimal(string $bytes) : string | |||||
| { | |||||
| $result = '0'; | |||||
| $power = '1'; | |||||
| for ($i = \strlen($bytes) - 1; $i >= 0; $i--) { | |||||
| $index = \ord($bytes[$i]); | |||||
| if ($index !== 0) { | |||||
| $result = $this->add($result, ($index === 1) | |||||
| ? $power | |||||
| : $this->mul($power, (string) $index) | |||||
| ); | |||||
| } | |||||
| if ($i !== 0) { | |||||
| $power = $this->mul($power, '256'); | |||||
| } | |||||
| } | |||||
| return $result; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,65 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math\Internal\Calculator; | |||||
| use Brick\Math\Internal\Calculator; | |||||
| /** | |||||
| * Calculator implementation built around the bcmath library. | |||||
| * | |||||
| * @internal | |||||
| * | |||||
| * @psalm-immutable | |||||
| */ | |||||
| class BcMathCalculator extends Calculator | |||||
| { | |||||
| public function add(string $a, string $b) : string | |||||
| { | |||||
| return \bcadd($a, $b, 0); | |||||
| } | |||||
| public function sub(string $a, string $b) : string | |||||
| { | |||||
| return \bcsub($a, $b, 0); | |||||
| } | |||||
| public function mul(string $a, string $b) : string | |||||
| { | |||||
| return \bcmul($a, $b, 0); | |||||
| } | |||||
| public function divQ(string $a, string $b) : string | |||||
| { | |||||
| return \bcdiv($a, $b, 0); | |||||
| } | |||||
| public function divR(string $a, string $b) : string | |||||
| { | |||||
| return \bcmod($a, $b, 0); | |||||
| } | |||||
| public function divQR(string $a, string $b) : array | |||||
| { | |||||
| $q = \bcdiv($a, $b, 0); | |||||
| $r = \bcmod($a, $b, 0); | |||||
| return [$q, $r]; | |||||
| } | |||||
| public function pow(string $a, int $e) : string | |||||
| { | |||||
| return \bcpow($a, (string) $e, 0); | |||||
| } | |||||
| public function modPow(string $base, string $exp, string $mod) : string | |||||
| { | |||||
| return \bcpowmod($base, $exp, $mod, 0); | |||||
| } | |||||
| public function sqrt(string $n) : string | |||||
| { | |||||
| return \bcsqrt($n, 0); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,108 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math\Internal\Calculator; | |||||
| use Brick\Math\Internal\Calculator; | |||||
| /** | |||||
| * Calculator implementation built around the GMP library. | |||||
| * | |||||
| * @internal | |||||
| * | |||||
| * @psalm-immutable | |||||
| */ | |||||
| class GmpCalculator extends Calculator | |||||
| { | |||||
| public function add(string $a, string $b) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_add($a, $b)); | |||||
| } | |||||
| public function sub(string $a, string $b) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_sub($a, $b)); | |||||
| } | |||||
| public function mul(string $a, string $b) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_mul($a, $b)); | |||||
| } | |||||
| public function divQ(string $a, string $b) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_div_q($a, $b)); | |||||
| } | |||||
| public function divR(string $a, string $b) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_div_r($a, $b)); | |||||
| } | |||||
| public function divQR(string $a, string $b) : array | |||||
| { | |||||
| [$q, $r] = \gmp_div_qr($a, $b); | |||||
| return [ | |||||
| \gmp_strval($q), | |||||
| \gmp_strval($r) | |||||
| ]; | |||||
| } | |||||
| public function pow(string $a, int $e) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_pow($a, $e)); | |||||
| } | |||||
| public function modInverse(string $x, string $m) : ?string | |||||
| { | |||||
| $result = \gmp_invert($x, $m); | |||||
| if ($result === false) { | |||||
| return null; | |||||
| } | |||||
| return \gmp_strval($result); | |||||
| } | |||||
| public function modPow(string $base, string $exp, string $mod) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_powm($base, $exp, $mod)); | |||||
| } | |||||
| public function gcd(string $a, string $b) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_gcd($a, $b)); | |||||
| } | |||||
| public function fromBase(string $number, int $base) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_init($number, $base)); | |||||
| } | |||||
| public function toBase(string $number, int $base) : string | |||||
| { | |||||
| return \gmp_strval($number, $base); | |||||
| } | |||||
| public function and(string $a, string $b) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_and($a, $b)); | |||||
| } | |||||
| public function or(string $a, string $b) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_or($a, $b)); | |||||
| } | |||||
| public function xor(string $a, string $b) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_xor($a, $b)); | |||||
| } | |||||
| public function sqrt(string $n) : string | |||||
| { | |||||
| return \gmp_strval(\gmp_sqrt($n)); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,572 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math\Internal\Calculator; | |||||
| use Brick\Math\Internal\Calculator; | |||||
| /** | |||||
| * Calculator implementation using only native PHP code. | |||||
| * | |||||
| * @internal | |||||
| * | |||||
| * @psalm-immutable | |||||
| */ | |||||
| class NativeCalculator extends Calculator | |||||
| { | |||||
| /** | |||||
| * The max number of digits the platform can natively add, subtract, multiply or divide without overflow. | |||||
| * For multiplication, this represents the max sum of the lengths of both operands. | |||||
| * | |||||
| * In addition, it is assumed that an extra digit can hold a carry (1) without overflowing. | |||||
| * Example: 32-bit: max number 1,999,999,999 (9 digits + carry) | |||||
| * 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry) | |||||
| */ | |||||
| private readonly int $maxDigits; | |||||
| /** | |||||
| * @codeCoverageIgnore | |||||
| */ | |||||
| public function __construct() | |||||
| { | |||||
| $this->maxDigits = match (PHP_INT_SIZE) { | |||||
| 4 => 9, | |||||
| 8 => 18, | |||||
| default => throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.') | |||||
| }; | |||||
| } | |||||
| public function add(string $a, string $b) : string | |||||
| { | |||||
| /** | |||||
| * @psalm-var numeric-string $a | |||||
| * @psalm-var numeric-string $b | |||||
| */ | |||||
| $result = $a + $b; | |||||
| if (is_int($result)) { | |||||
| return (string) $result; | |||||
| } | |||||
| if ($a === '0') { | |||||
| return $b; | |||||
| } | |||||
| if ($b === '0') { | |||||
| return $a; | |||||
| } | |||||
| [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); | |||||
| $result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig); | |||||
| if ($aNeg) { | |||||
| $result = $this->neg($result); | |||||
| } | |||||
| return $result; | |||||
| } | |||||
| public function sub(string $a, string $b) : string | |||||
| { | |||||
| return $this->add($a, $this->neg($b)); | |||||
| } | |||||
| public function mul(string $a, string $b) : string | |||||
| { | |||||
| /** | |||||
| * @psalm-var numeric-string $a | |||||
| * @psalm-var numeric-string $b | |||||
| */ | |||||
| $result = $a * $b; | |||||
| if (is_int($result)) { | |||||
| return (string) $result; | |||||
| } | |||||
| if ($a === '0' || $b === '0') { | |||||
| return '0'; | |||||
| } | |||||
| if ($a === '1') { | |||||
| return $b; | |||||
| } | |||||
| if ($b === '1') { | |||||
| return $a; | |||||
| } | |||||
| if ($a === '-1') { | |||||
| return $this->neg($b); | |||||
| } | |||||
| if ($b === '-1') { | |||||
| return $this->neg($a); | |||||
| } | |||||
| [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); | |||||
| $result = $this->doMul($aDig, $bDig); | |||||
| if ($aNeg !== $bNeg) { | |||||
| $result = $this->neg($result); | |||||
| } | |||||
| return $result; | |||||
| } | |||||
| public function divQ(string $a, string $b) : string | |||||
| { | |||||
| return $this->divQR($a, $b)[0]; | |||||
| } | |||||
| public function divR(string $a, string $b): string | |||||
| { | |||||
| return $this->divQR($a, $b)[1]; | |||||
| } | |||||
| public function divQR(string $a, string $b) : array | |||||
| { | |||||
| if ($a === '0') { | |||||
| return ['0', '0']; | |||||
| } | |||||
| if ($a === $b) { | |||||
| return ['1', '0']; | |||||
| } | |||||
| if ($b === '1') { | |||||
| return [$a, '0']; | |||||
| } | |||||
| if ($b === '-1') { | |||||
| return [$this->neg($a), '0']; | |||||
| } | |||||
| /** @psalm-var numeric-string $a */ | |||||
| $na = $a * 1; // cast to number | |||||
| if (is_int($na)) { | |||||
| /** @psalm-var numeric-string $b */ | |||||
| $nb = $b * 1; | |||||
| if (is_int($nb)) { | |||||
| // the only division that may overflow is PHP_INT_MIN / -1, | |||||
| // which cannot happen here as we've already handled a divisor of -1 above. | |||||
| $q = intdiv($na, $nb); | |||||
| $r = $na % $nb; | |||||
| return [ | |||||
| (string) $q, | |||||
| (string) $r | |||||
| ]; | |||||
| } | |||||
| } | |||||
| [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); | |||||
| [$q, $r] = $this->doDiv($aDig, $bDig); | |||||
| if ($aNeg !== $bNeg) { | |||||
| $q = $this->neg($q); | |||||
| } | |||||
| if ($aNeg) { | |||||
| $r = $this->neg($r); | |||||
| } | |||||
| return [$q, $r]; | |||||
| } | |||||
| public function pow(string $a, int $e) : string | |||||
| { | |||||
| if ($e === 0) { | |||||
| return '1'; | |||||
| } | |||||
| if ($e === 1) { | |||||
| return $a; | |||||
| } | |||||
| $odd = $e % 2; | |||||
| $e -= $odd; | |||||
| $aa = $this->mul($a, $a); | |||||
| /** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */ | |||||
| $result = $this->pow($aa, $e / 2); | |||||
| if ($odd === 1) { | |||||
| $result = $this->mul($result, $a); | |||||
| } | |||||
| return $result; | |||||
| } | |||||
| /** | |||||
| * Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/ | |||||
| */ | |||||
| public function modPow(string $base, string $exp, string $mod) : string | |||||
| { | |||||
| // special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0) | |||||
| if ($base === '0' && $exp === '0' && $mod === '1') { | |||||
| return '0'; | |||||
| } | |||||
| // special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0) | |||||
| if ($exp === '0' && $mod === '1') { | |||||
| return '0'; | |||||
| } | |||||
| $x = $base; | |||||
| $res = '1'; | |||||
| // numbers are positive, so we can use remainder instead of modulo | |||||
| $x = $this->divR($x, $mod); | |||||
| while ($exp !== '0') { | |||||
| if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd | |||||
| $res = $this->divR($this->mul($res, $x), $mod); | |||||
| } | |||||
| $exp = $this->divQ($exp, '2'); | |||||
| $x = $this->divR($this->mul($x, $x), $mod); | |||||
| } | |||||
| return $res; | |||||
| } | |||||
| /** | |||||
| * Adapted from https://cp-algorithms.com/num_methods/roots_newton.html | |||||
| */ | |||||
| public function sqrt(string $n) : string | |||||
| { | |||||
| if ($n === '0') { | |||||
| return '0'; | |||||
| } | |||||
| // initial approximation | |||||
| $x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1); | |||||
| $decreased = false; | |||||
| for (;;) { | |||||
| $nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2'); | |||||
| if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) { | |||||
| break; | |||||
| } | |||||
| $decreased = $this->cmp($nx, $x) < 0; | |||||
| $x = $nx; | |||||
| } | |||||
| return $x; | |||||
| } | |||||
| /** | |||||
| * Performs the addition of two non-signed large integers. | |||||
| */ | |||||
| private function doAdd(string $a, string $b) : string | |||||
| { | |||||
| [$a, $b, $length] = $this->pad($a, $b); | |||||
| $carry = 0; | |||||
| $result = ''; | |||||
| for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { | |||||
| $blockLength = $this->maxDigits; | |||||
| if ($i < 0) { | |||||
| $blockLength += $i; | |||||
| /** @psalm-suppress LoopInvalidation */ | |||||
| $i = 0; | |||||
| } | |||||
| /** @psalm-var numeric-string $blockA */ | |||||
| $blockA = \substr($a, $i, $blockLength); | |||||
| /** @psalm-var numeric-string $blockB */ | |||||
| $blockB = \substr($b, $i, $blockLength); | |||||
| $sum = (string) ($blockA + $blockB + $carry); | |||||
| $sumLength = \strlen($sum); | |||||
| if ($sumLength > $blockLength) { | |||||
| $sum = \substr($sum, 1); | |||||
| $carry = 1; | |||||
| } else { | |||||
| if ($sumLength < $blockLength) { | |||||
| $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; | |||||
| } | |||||
| $carry = 0; | |||||
| } | |||||
| $result = $sum . $result; | |||||
| if ($i === 0) { | |||||
| break; | |||||
| } | |||||
| } | |||||
| if ($carry === 1) { | |||||
| $result = '1' . $result; | |||||
| } | |||||
| return $result; | |||||
| } | |||||
| /** | |||||
| * Performs the subtraction of two non-signed large integers. | |||||
| */ | |||||
| private function doSub(string $a, string $b) : string | |||||
| { | |||||
| if ($a === $b) { | |||||
| return '0'; | |||||
| } | |||||
| // Ensure that we always subtract to a positive result: biggest minus smallest. | |||||
| $cmp = $this->doCmp($a, $b); | |||||
| $invert = ($cmp === -1); | |||||
| if ($invert) { | |||||
| $c = $a; | |||||
| $a = $b; | |||||
| $b = $c; | |||||
| } | |||||
| [$a, $b, $length] = $this->pad($a, $b); | |||||
| $carry = 0; | |||||
| $result = ''; | |||||
| $complement = 10 ** $this->maxDigits; | |||||
| for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { | |||||
| $blockLength = $this->maxDigits; | |||||
| if ($i < 0) { | |||||
| $blockLength += $i; | |||||
| /** @psalm-suppress LoopInvalidation */ | |||||
| $i = 0; | |||||
| } | |||||
| /** @psalm-var numeric-string $blockA */ | |||||
| $blockA = \substr($a, $i, $blockLength); | |||||
| /** @psalm-var numeric-string $blockB */ | |||||
| $blockB = \substr($b, $i, $blockLength); | |||||
| $sum = $blockA - $blockB - $carry; | |||||
| if ($sum < 0) { | |||||
| $sum += $complement; | |||||
| $carry = 1; | |||||
| } else { | |||||
| $carry = 0; | |||||
| } | |||||
| $sum = (string) $sum; | |||||
| $sumLength = \strlen($sum); | |||||
| if ($sumLength < $blockLength) { | |||||
| $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; | |||||
| } | |||||
| $result = $sum . $result; | |||||
| if ($i === 0) { | |||||
| break; | |||||
| } | |||||
| } | |||||
| // Carry cannot be 1 when the loop ends, as a > b | |||||
| assert($carry === 0); | |||||
| $result = \ltrim($result, '0'); | |||||
| if ($invert) { | |||||
| $result = $this->neg($result); | |||||
| } | |||||
| return $result; | |||||
| } | |||||
| /** | |||||
| * Performs the multiplication of two non-signed large integers. | |||||
| */ | |||||
| private function doMul(string $a, string $b) : string | |||||
| { | |||||
| $x = \strlen($a); | |||||
| $y = \strlen($b); | |||||
| $maxDigits = \intdiv($this->maxDigits, 2); | |||||
| $complement = 10 ** $maxDigits; | |||||
| $result = '0'; | |||||
| for ($i = $x - $maxDigits;; $i -= $maxDigits) { | |||||
| $blockALength = $maxDigits; | |||||
| if ($i < 0) { | |||||
| $blockALength += $i; | |||||
| /** @psalm-suppress LoopInvalidation */ | |||||
| $i = 0; | |||||
| } | |||||
| $blockA = (int) \substr($a, $i, $blockALength); | |||||
| $line = ''; | |||||
| $carry = 0; | |||||
| for ($j = $y - $maxDigits;; $j -= $maxDigits) { | |||||
| $blockBLength = $maxDigits; | |||||
| if ($j < 0) { | |||||
| $blockBLength += $j; | |||||
| /** @psalm-suppress LoopInvalidation */ | |||||
| $j = 0; | |||||
| } | |||||
| $blockB = (int) \substr($b, $j, $blockBLength); | |||||
| $mul = $blockA * $blockB + $carry; | |||||
| $value = $mul % $complement; | |||||
| $carry = ($mul - $value) / $complement; | |||||
| $value = (string) $value; | |||||
| $value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT); | |||||
| $line = $value . $line; | |||||
| if ($j === 0) { | |||||
| break; | |||||
| } | |||||
| } | |||||
| if ($carry !== 0) { | |||||
| $line = $carry . $line; | |||||
| } | |||||
| $line = \ltrim($line, '0'); | |||||
| if ($line !== '') { | |||||
| $line .= \str_repeat('0', $x - $blockALength - $i); | |||||
| $result = $this->add($result, $line); | |||||
| } | |||||
| if ($i === 0) { | |||||
| break; | |||||
| } | |||||
| } | |||||
| return $result; | |||||
| } | |||||
| /** | |||||
| * Performs the division of two non-signed large integers. | |||||
| * | |||||
| * @return string[] The quotient and remainder. | |||||
| */ | |||||
| private function doDiv(string $a, string $b) : array | |||||
| { | |||||
| $cmp = $this->doCmp($a, $b); | |||||
| if ($cmp === -1) { | |||||
| return ['0', $a]; | |||||
| } | |||||
| $x = \strlen($a); | |||||
| $y = \strlen($b); | |||||
| // we now know that a >= b && x >= y | |||||
| $q = '0'; // quotient | |||||
| $r = $a; // remainder | |||||
| $z = $y; // focus length, always $y or $y+1 | |||||
| for (;;) { | |||||
| $focus = \substr($a, 0, $z); | |||||
| $cmp = $this->doCmp($focus, $b); | |||||
| if ($cmp === -1) { | |||||
| if ($z === $x) { // remainder < dividend | |||||
| break; | |||||
| } | |||||
| $z++; | |||||
| } | |||||
| $zeros = \str_repeat('0', $x - $z); | |||||
| $q = $this->add($q, '1' . $zeros); | |||||
| $a = $this->sub($a, $b . $zeros); | |||||
| $r = $a; | |||||
| if ($r === '0') { // remainder == 0 | |||||
| break; | |||||
| } | |||||
| $x = \strlen($a); | |||||
| if ($x < $y) { // remainder < dividend | |||||
| break; | |||||
| } | |||||
| $z = $y; | |||||
| } | |||||
| return [$q, $r]; | |||||
| } | |||||
| /** | |||||
| * Compares two non-signed large numbers. | |||||
| * | |||||
| * @psalm-return -1|0|1 | |||||
| */ | |||||
| private function doCmp(string $a, string $b) : int | |||||
| { | |||||
| $x = \strlen($a); | |||||
| $y = \strlen($b); | |||||
| $cmp = $x <=> $y; | |||||
| if ($cmp !== 0) { | |||||
| return $cmp; | |||||
| } | |||||
| return \strcmp($a, $b) <=> 0; // enforce -1|0|1 | |||||
| } | |||||
| /** | |||||
| * Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length. | |||||
| * | |||||
| * The numbers must only consist of digits, without leading minus sign. | |||||
| * | |||||
| * @return array{string, string, int} | |||||
| */ | |||||
| private function pad(string $a, string $b) : array | |||||
| { | |||||
| $x = \strlen($a); | |||||
| $y = \strlen($b); | |||||
| if ($x > $y) { | |||||
| $b = \str_repeat('0', $x - $y) . $b; | |||||
| return [$a, $b, $x]; | |||||
| } | |||||
| if ($x < $y) { | |||||
| $a = \str_repeat('0', $y - $x) . $a; | |||||
| return [$a, $b, $y]; | |||||
| } | |||||
| return [$a, $b, $x]; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,98 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace Brick\Math; | |||||
| /** | |||||
| * Specifies a rounding behavior for numerical operations capable of discarding precision. | |||||
| * | |||||
| * Each rounding mode indicates how the least significant returned digit of a rounded result | |||||
| * is to be calculated. If fewer digits are returned than the digits needed to represent the | |||||
| * exact numerical result, the discarded digits will be referred to as the discarded fraction | |||||
| * regardless the digits' contribution to the value of the number. In other words, considered | |||||
| * as a numerical value, the discarded fraction could have an absolute value greater than one. | |||||
| */ | |||||
| enum RoundingMode | |||||
| { | |||||
| /** | |||||
| * Asserts that the requested operation has an exact result, hence no rounding is necessary. | |||||
| * | |||||
| * If this rounding mode is specified on an operation that yields a result that | |||||
| * cannot be represented at the requested scale, a RoundingNecessaryException is thrown. | |||||
| */ | |||||
| case UNNECESSARY; | |||||
| /** | |||||
| * Rounds away from zero. | |||||
| * | |||||
| * Always increments the digit prior to a nonzero discarded fraction. | |||||
| * Note that this rounding mode never decreases the magnitude of the calculated value. | |||||
| */ | |||||
| case UP; | |||||
| /** | |||||
| * Rounds towards zero. | |||||
| * | |||||
| * Never increments the digit prior to a discarded fraction (i.e., truncates). | |||||
| * Note that this rounding mode never increases the magnitude of the calculated value. | |||||
| */ | |||||
| case DOWN; | |||||
| /** | |||||
| * Rounds towards positive infinity. | |||||
| * | |||||
| * If the result is positive, behaves as for UP; if negative, behaves as for DOWN. | |||||
| * Note that this rounding mode never decreases the calculated value. | |||||
| */ | |||||
| case CEILING; | |||||
| /** | |||||
| * Rounds towards negative infinity. | |||||
| * | |||||
| * If the result is positive, behave as for DOWN; if negative, behave as for UP. | |||||
| * Note that this rounding mode never increases the calculated value. | |||||
| */ | |||||
| case FLOOR; | |||||
| /** | |||||
| * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up. | |||||
| * | |||||
| * Behaves as for UP if the discarded fraction is >= 0.5; otherwise, behaves as for DOWN. | |||||
| * Note that this is the rounding mode commonly taught at school. | |||||
| */ | |||||
| case HALF_UP; | |||||
| /** | |||||
| * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down. | |||||
| * | |||||
| * Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN. | |||||
| */ | |||||
| case HALF_DOWN; | |||||
| /** | |||||
| * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity. | |||||
| * | |||||
| * If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN. | |||||
| */ | |||||
| case HALF_CEILING; | |||||
| /** | |||||
| * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity. | |||||
| * | |||||
| * If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP. | |||||
| */ | |||||
| case HALF_FLOOR; | |||||
| /** | |||||
| * Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor. | |||||
| * | |||||
| * Behaves as for HALF_UP if the digit to the left of the discarded fraction is odd; | |||||
| * behaves as for HALF_DOWN if it's even. | |||||
| * | |||||
| * Note that this is the rounding mode that statistically minimizes | |||||
| * cumulative error when applied repeatedly over a sequence of calculations. | |||||
| * It is sometimes known as "Banker's rounding", and is chiefly used in the USA. | |||||
| */ | |||||
| case HALF_EVEN; | |||||
| } | |||||
| @@ -0,0 +1,579 @@ | |||||
| <?php | |||||
| /* | |||||
| * This file is part of Composer. | |||||
| * | |||||
| * (c) Nils Adermann <naderman@naderman.de> | |||||
| * Jordi Boggiano <j.boggiano@seld.be> | |||||
| * | |||||
| * For the full copyright and license information, please view the LICENSE | |||||
| * file that was distributed with this source code. | |||||
| */ | |||||
| namespace Composer\Autoload; | |||||
| /** | |||||
| * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. | |||||
| * | |||||
| * $loader = new \Composer\Autoload\ClassLoader(); | |||||
| * | |||||
| * // register classes with namespaces | |||||
| * $loader->add('Symfony\Component', __DIR__.'/component'); | |||||
| * $loader->add('Symfony', __DIR__.'/framework'); | |||||
| * | |||||
| * // activate the autoloader | |||||
| * $loader->register(); | |||||
| * | |||||
| * // to enable searching the include path (eg. for PEAR packages) | |||||
| * $loader->setUseIncludePath(true); | |||||
| * | |||||
| * In this example, if you try to use a class in the Symfony\Component | |||||
| * namespace or one of its children (Symfony\Component\Console for instance), | |||||
| * the autoloader will first look for the class under the component/ | |||||
| * directory, and it will then fallback to the framework/ directory if not | |||||
| * found before giving up. | |||||
| * | |||||
| * This class is loosely based on the Symfony UniversalClassLoader. | |||||
| * | |||||
| * @author Fabien Potencier <fabien@symfony.com> | |||||
| * @author Jordi Boggiano <j.boggiano@seld.be> | |||||
| * @see https://www.php-fig.org/psr/psr-0/ | |||||
| * @see https://www.php-fig.org/psr/psr-4/ | |||||
| */ | |||||
| class ClassLoader | |||||
| { | |||||
| /** @var \Closure(string):void */ | |||||
| private static $includeFile; | |||||
| /** @var string|null */ | |||||
| private $vendorDir; | |||||
| // PSR-4 | |||||
| /** | |||||
| * @var array<string, array<string, int>> | |||||
| */ | |||||
| private $prefixLengthsPsr4 = array(); | |||||
| /** | |||||
| * @var array<string, list<string>> | |||||
| */ | |||||
| private $prefixDirsPsr4 = array(); | |||||
| /** | |||||
| * @var list<string> | |||||
| */ | |||||
| private $fallbackDirsPsr4 = array(); | |||||
| // PSR-0 | |||||
| /** | |||||
| * List of PSR-0 prefixes | |||||
| * | |||||
| * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) | |||||
| * | |||||
| * @var array<string, array<string, list<string>>> | |||||
| */ | |||||
| private $prefixesPsr0 = array(); | |||||
| /** | |||||
| * @var list<string> | |||||
| */ | |||||
| private $fallbackDirsPsr0 = array(); | |||||
| /** @var bool */ | |||||
| private $useIncludePath = false; | |||||
| /** | |||||
| * @var array<string, string> | |||||
| */ | |||||
| private $classMap = array(); | |||||
| /** @var bool */ | |||||
| private $classMapAuthoritative = false; | |||||
| /** | |||||
| * @var array<string, bool> | |||||
| */ | |||||
| private $missingClasses = array(); | |||||
| /** @var string|null */ | |||||
| private $apcuPrefix; | |||||
| /** | |||||
| * @var array<string, self> | |||||
| */ | |||||
| private static $registeredLoaders = array(); | |||||
| /** | |||||
| * @param string|null $vendorDir | |||||
| */ | |||||
| public function __construct($vendorDir = null) | |||||
| { | |||||
| $this->vendorDir = $vendorDir; | |||||
| self::initializeIncludeClosure(); | |||||
| } | |||||
| /** | |||||
| * @return array<string, list<string>> | |||||
| */ | |||||
| public function getPrefixes() | |||||
| { | |||||
| if (!empty($this->prefixesPsr0)) { | |||||
| return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); | |||||
| } | |||||
| return array(); | |||||
| } | |||||
| /** | |||||
| * @return array<string, list<string>> | |||||
| */ | |||||
| public function getPrefixesPsr4() | |||||
| { | |||||
| return $this->prefixDirsPsr4; | |||||
| } | |||||
| /** | |||||
| * @return list<string> | |||||
| */ | |||||
| public function getFallbackDirs() | |||||
| { | |||||
| return $this->fallbackDirsPsr0; | |||||
| } | |||||
| /** | |||||
| * @return list<string> | |||||
| */ | |||||
| public function getFallbackDirsPsr4() | |||||
| { | |||||
| return $this->fallbackDirsPsr4; | |||||
| } | |||||
| /** | |||||
| * @return array<string, string> Array of classname => path | |||||
| */ | |||||
| public function getClassMap() | |||||
| { | |||||
| return $this->classMap; | |||||
| } | |||||
| /** | |||||
| * @param array<string, string> $classMap Class to filename map | |||||
| * | |||||
| * @return void | |||||
| */ | |||||
| public function addClassMap(array $classMap) | |||||
| { | |||||
| if ($this->classMap) { | |||||
| $this->classMap = array_merge($this->classMap, $classMap); | |||||
| } else { | |||||
| $this->classMap = $classMap; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Registers a set of PSR-0 directories for a given prefix, either | |||||
| * appending or prepending to the ones previously set for this prefix. | |||||
| * | |||||
| * @param string $prefix The prefix | |||||
| * @param list<string>|string $paths The PSR-0 root directories | |||||
| * @param bool $prepend Whether to prepend the directories | |||||
| * | |||||
| * @return void | |||||
| */ | |||||
| public function add($prefix, $paths, $prepend = false) | |||||
| { | |||||
| $paths = (array) $paths; | |||||
| if (!$prefix) { | |||||
| if ($prepend) { | |||||
| $this->fallbackDirsPsr0 = array_merge( | |||||
| $paths, | |||||
| $this->fallbackDirsPsr0 | |||||
| ); | |||||
| } else { | |||||
| $this->fallbackDirsPsr0 = array_merge( | |||||
| $this->fallbackDirsPsr0, | |||||
| $paths | |||||
| ); | |||||
| } | |||||
| return; | |||||
| } | |||||
| $first = $prefix[0]; | |||||
| if (!isset($this->prefixesPsr0[$first][$prefix])) { | |||||
| $this->prefixesPsr0[$first][$prefix] = $paths; | |||||
| return; | |||||
| } | |||||
| if ($prepend) { | |||||
| $this->prefixesPsr0[$first][$prefix] = array_merge( | |||||
| $paths, | |||||
| $this->prefixesPsr0[$first][$prefix] | |||||
| ); | |||||
| } else { | |||||
| $this->prefixesPsr0[$first][$prefix] = array_merge( | |||||
| $this->prefixesPsr0[$first][$prefix], | |||||
| $paths | |||||
| ); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Registers a set of PSR-4 directories for a given namespace, either | |||||
| * appending or prepending to the ones previously set for this namespace. | |||||
| * | |||||
| * @param string $prefix The prefix/namespace, with trailing '\\' | |||||
| * @param list<string>|string $paths The PSR-4 base directories | |||||
| * @param bool $prepend Whether to prepend the directories | |||||
| * | |||||
| * @throws \InvalidArgumentException | |||||
| * | |||||
| * @return void | |||||
| */ | |||||
| public function addPsr4($prefix, $paths, $prepend = false) | |||||
| { | |||||
| $paths = (array) $paths; | |||||
| if (!$prefix) { | |||||
| // Register directories for the root namespace. | |||||
| if ($prepend) { | |||||
| $this->fallbackDirsPsr4 = array_merge( | |||||
| $paths, | |||||
| $this->fallbackDirsPsr4 | |||||
| ); | |||||
| } else { | |||||
| $this->fallbackDirsPsr4 = array_merge( | |||||
| $this->fallbackDirsPsr4, | |||||
| $paths | |||||
| ); | |||||
| } | |||||
| } elseif (!isset($this->prefixDirsPsr4[$prefix])) { | |||||
| // Register directories for a new namespace. | |||||
| $length = strlen($prefix); | |||||
| if ('\\' !== $prefix[$length - 1]) { | |||||
| throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); | |||||
| } | |||||
| $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; | |||||
| $this->prefixDirsPsr4[$prefix] = $paths; | |||||
| } elseif ($prepend) { | |||||
| // Prepend directories for an already registered namespace. | |||||
| $this->prefixDirsPsr4[$prefix] = array_merge( | |||||
| $paths, | |||||
| $this->prefixDirsPsr4[$prefix] | |||||
| ); | |||||
| } else { | |||||
| // Append directories for an already registered namespace. | |||||
| $this->prefixDirsPsr4[$prefix] = array_merge( | |||||
| $this->prefixDirsPsr4[$prefix], | |||||
| $paths | |||||
| ); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Registers a set of PSR-0 directories for a given prefix, | |||||
| * replacing any others previously set for this prefix. | |||||
| * | |||||
| * @param string $prefix The prefix | |||||
| * @param list<string>|string $paths The PSR-0 base directories | |||||
| * | |||||
| * @return void | |||||
| */ | |||||
| public function set($prefix, $paths) | |||||
| { | |||||
| if (!$prefix) { | |||||
| $this->fallbackDirsPsr0 = (array) $paths; | |||||
| } else { | |||||
| $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Registers a set of PSR-4 directories for a given namespace, | |||||
| * replacing any others previously set for this namespace. | |||||
| * | |||||
| * @param string $prefix The prefix/namespace, with trailing '\\' | |||||
| * @param list<string>|string $paths The PSR-4 base directories | |||||
| * | |||||
| * @throws \InvalidArgumentException | |||||
| * | |||||
| * @return void | |||||
| */ | |||||
| public function setPsr4($prefix, $paths) | |||||
| { | |||||
| if (!$prefix) { | |||||
| $this->fallbackDirsPsr4 = (array) $paths; | |||||
| } else { | |||||
| $length = strlen($prefix); | |||||
| if ('\\' !== $prefix[$length - 1]) { | |||||
| throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); | |||||
| } | |||||
| $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; | |||||
| $this->prefixDirsPsr4[$prefix] = (array) $paths; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Turns on searching the include path for class files. | |||||
| * | |||||
| * @param bool $useIncludePath | |||||
| * | |||||
| * @return void | |||||
| */ | |||||
| public function setUseIncludePath($useIncludePath) | |||||
| { | |||||
| $this->useIncludePath = $useIncludePath; | |||||
| } | |||||
| /** | |||||
| * Can be used to check if the autoloader uses the include path to check | |||||
| * for classes. | |||||
| * | |||||
| * @return bool | |||||
| */ | |||||
| public function getUseIncludePath() | |||||
| { | |||||
| return $this->useIncludePath; | |||||
| } | |||||
| /** | |||||
| * Turns off searching the prefix and fallback directories for classes | |||||
| * that have not been registered with the class map. | |||||
| * | |||||
| * @param bool $classMapAuthoritative | |||||
| * | |||||
| * @return void | |||||
| */ | |||||
| public function setClassMapAuthoritative($classMapAuthoritative) | |||||
| { | |||||
| $this->classMapAuthoritative = $classMapAuthoritative; | |||||
| } | |||||
| /** | |||||
| * Should class lookup fail if not found in the current class map? | |||||
| * | |||||
| * @return bool | |||||
| */ | |||||
| public function isClassMapAuthoritative() | |||||
| { | |||||
| return $this->classMapAuthoritative; | |||||
| } | |||||
| /** | |||||
| * APCu prefix to use to cache found/not-found classes, if the extension is enabled. | |||||
| * | |||||
| * @param string|null $apcuPrefix | |||||
| * | |||||
| * @return void | |||||
| */ | |||||
| public function setApcuPrefix($apcuPrefix) | |||||
| { | |||||
| $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; | |||||
| } | |||||
| /** | |||||
| * The APCu prefix in use, or null if APCu caching is not enabled. | |||||
| * | |||||
| * @return string|null | |||||
| */ | |||||
| public function getApcuPrefix() | |||||
| { | |||||
| return $this->apcuPrefix; | |||||
| } | |||||
| /** | |||||
| * Registers this instance as an autoloader. | |||||
| * | |||||
| * @param bool $prepend Whether to prepend the autoloader or not | |||||
| * | |||||
| * @return void | |||||
| */ | |||||
| public function register($prepend = false) | |||||
| { | |||||
| spl_autoload_register(array($this, 'loadClass'), true, $prepend); | |||||
| if (null === $this->vendorDir) { | |||||
| return; | |||||
| } | |||||
| if ($prepend) { | |||||
| self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; | |||||
| } else { | |||||
| unset(self::$registeredLoaders[$this->vendorDir]); | |||||
| self::$registeredLoaders[$this->vendorDir] = $this; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Unregisters this instance as an autoloader. | |||||
| * | |||||
| * @return void | |||||
| */ | |||||
| public function unregister() | |||||
| { | |||||
| spl_autoload_unregister(array($this, 'loadClass')); | |||||
| if (null !== $this->vendorDir) { | |||||
| unset(self::$registeredLoaders[$this->vendorDir]); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Loads the given class or interface. | |||||
| * | |||||
| * @param string $class The name of the class | |||||
| * @return true|null True if loaded, null otherwise | |||||
| */ | |||||
| public function loadClass($class) | |||||
| { | |||||
| if ($file = $this->findFile($class)) { | |||||
| $includeFile = self::$includeFile; | |||||
| $includeFile($file); | |||||
| return true; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| /** | |||||
| * Finds the path to the file where the class is defined. | |||||
| * | |||||
| * @param string $class The name of the class | |||||
| * | |||||
| * @return string|false The path if found, false otherwise | |||||
| */ | |||||
| public function findFile($class) | |||||
| { | |||||
| // class map lookup | |||||
| if (isset($this->classMap[$class])) { | |||||
| return $this->classMap[$class]; | |||||
| } | |||||
| if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { | |||||
| return false; | |||||
| } | |||||
| if (null !== $this->apcuPrefix) { | |||||
| $file = apcu_fetch($this->apcuPrefix.$class, $hit); | |||||
| if ($hit) { | |||||
| return $file; | |||||
| } | |||||
| } | |||||
| $file = $this->findFileWithExtension($class, '.php'); | |||||
| // Search for Hack files if we are running on HHVM | |||||
| if (false === $file && defined('HHVM_VERSION')) { | |||||
| $file = $this->findFileWithExtension($class, '.hh'); | |||||
| } | |||||
| if (null !== $this->apcuPrefix) { | |||||
| apcu_add($this->apcuPrefix.$class, $file); | |||||
| } | |||||
| if (false === $file) { | |||||
| // Remember that this class does not exist. | |||||
| $this->missingClasses[$class] = true; | |||||
| } | |||||
| return $file; | |||||
| } | |||||
| /** | |||||
| * Returns the currently registered loaders keyed by their corresponding vendor directories. | |||||
| * | |||||
| * @return array<string, self> | |||||
| */ | |||||
| public static function getRegisteredLoaders() | |||||
| { | |||||
| return self::$registeredLoaders; | |||||
| } | |||||
| /** | |||||
| * @param string $class | |||||
| * @param string $ext | |||||
| * @return string|false | |||||
| */ | |||||
| private function findFileWithExtension($class, $ext) | |||||
| { | |||||
| // PSR-4 lookup | |||||
| $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; | |||||
| $first = $class[0]; | |||||
| if (isset($this->prefixLengthsPsr4[$first])) { | |||||
| $subPath = $class; | |||||
| while (false !== $lastPos = strrpos($subPath, '\\')) { | |||||
| $subPath = substr($subPath, 0, $lastPos); | |||||
| $search = $subPath . '\\'; | |||||
| if (isset($this->prefixDirsPsr4[$search])) { | |||||
| $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); | |||||
| foreach ($this->prefixDirsPsr4[$search] as $dir) { | |||||
| if (file_exists($file = $dir . $pathEnd)) { | |||||
| return $file; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| // PSR-4 fallback dirs | |||||
| foreach ($this->fallbackDirsPsr4 as $dir) { | |||||
| if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { | |||||
| return $file; | |||||
| } | |||||
| } | |||||
| // PSR-0 lookup | |||||
| if (false !== $pos = strrpos($class, '\\')) { | |||||
| // namespaced class name | |||||
| $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) | |||||
| . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); | |||||
| } else { | |||||
| // PEAR-like class name | |||||
| $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; | |||||
| } | |||||
| if (isset($this->prefixesPsr0[$first])) { | |||||
| foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { | |||||
| if (0 === strpos($class, $prefix)) { | |||||
| foreach ($dirs as $dir) { | |||||
| if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { | |||||
| return $file; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| // PSR-0 fallback dirs | |||||
| foreach ($this->fallbackDirsPsr0 as $dir) { | |||||
| if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { | |||||
| return $file; | |||||
| } | |||||
| } | |||||
| // PSR-0 include paths. | |||||
| if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { | |||||
| return $file; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /** | |||||
| * @return void | |||||
| */ | |||||
| private static function initializeIncludeClosure() | |||||
| { | |||||
| if (self::$includeFile !== null) { | |||||
| return; | |||||
| } | |||||
| /** | |||||
| * Scope isolated include. | |||||
| * | |||||
| * Prevents access to $this/self from included files. | |||||
| * | |||||
| * @param string $file | |||||
| * @return void | |||||
| */ | |||||
| self::$includeFile = \Closure::bind(static function($file) { | |||||
| include $file; | |||||
| }, null, null); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,359 @@ | |||||
| <?php | |||||
| /* | |||||
| * This file is part of Composer. | |||||
| * | |||||
| * (c) Nils Adermann <naderman@naderman.de> | |||||
| * Jordi Boggiano <j.boggiano@seld.be> | |||||
| * | |||||
| * For the full copyright and license information, please view the LICENSE | |||||
| * file that was distributed with this source code. | |||||
| */ | |||||
| namespace Composer; | |||||
| use Composer\Autoload\ClassLoader; | |||||
| use Composer\Semver\VersionParser; | |||||
| /** | |||||
| * This class is copied in every Composer installed project and available to all | |||||
| * | |||||
| * See also https://getcomposer.org/doc/07-runtime.md#installed-versions | |||||
| * | |||||
| * To require its presence, you can require `composer-runtime-api ^2.0` | |||||
| * | |||||
| * @final | |||||
| */ | |||||
| class InstalledVersions | |||||
| { | |||||
| /** | |||||
| * @var mixed[]|null | |||||
| * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null | |||||
| */ | |||||
| private static $installed; | |||||
| /** | |||||
| * @var bool|null | |||||
| */ | |||||
| private static $canGetVendors; | |||||
| /** | |||||
| * @var array[] | |||||
| * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> | |||||
| */ | |||||
| private static $installedByVendor = array(); | |||||
| /** | |||||
| * Returns a list of all package names which are present, either by being installed, replaced or provided | |||||
| * | |||||
| * @return string[] | |||||
| * @psalm-return list<string> | |||||
| */ | |||||
| public static function getInstalledPackages() | |||||
| { | |||||
| $packages = array(); | |||||
| foreach (self::getInstalled() as $installed) { | |||||
| $packages[] = array_keys($installed['versions']); | |||||
| } | |||||
| if (1 === \count($packages)) { | |||||
| return $packages[0]; | |||||
| } | |||||
| return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); | |||||
| } | |||||
| /** | |||||
| * Returns a list of all package names with a specific type e.g. 'library' | |||||
| * | |||||
| * @param string $type | |||||
| * @return string[] | |||||
| * @psalm-return list<string> | |||||
| */ | |||||
| public static function getInstalledPackagesByType($type) | |||||
| { | |||||
| $packagesByType = array(); | |||||
| foreach (self::getInstalled() as $installed) { | |||||
| foreach ($installed['versions'] as $name => $package) { | |||||
| if (isset($package['type']) && $package['type'] === $type) { | |||||
| $packagesByType[] = $name; | |||||
| } | |||||
| } | |||||
| } | |||||
| return $packagesByType; | |||||
| } | |||||
| /** | |||||
| * Checks whether the given package is installed | |||||
| * | |||||
| * This also returns true if the package name is provided or replaced by another package | |||||
| * | |||||
| * @param string $packageName | |||||
| * @param bool $includeDevRequirements | |||||
| * @return bool | |||||
| */ | |||||
| public static function isInstalled($packageName, $includeDevRequirements = true) | |||||
| { | |||||
| foreach (self::getInstalled() as $installed) { | |||||
| if (isset($installed['versions'][$packageName])) { | |||||
| return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /** | |||||
| * Checks whether the given package satisfies a version constraint | |||||
| * | |||||
| * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: | |||||
| * | |||||
| * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') | |||||
| * | |||||
| * @param VersionParser $parser Install composer/semver to have access to this class and functionality | |||||
| * @param string $packageName | |||||
| * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package | |||||
| * @return bool | |||||
| */ | |||||
| public static function satisfies(VersionParser $parser, $packageName, $constraint) | |||||
| { | |||||
| $constraint = $parser->parseConstraints((string) $constraint); | |||||
| $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); | |||||
| return $provided->matches($constraint); | |||||
| } | |||||
| /** | |||||
| * Returns a version constraint representing all the range(s) which are installed for a given package | |||||
| * | |||||
| * It is easier to use this via isInstalled() with the $constraint argument if you need to check | |||||
| * whether a given version of a package is installed, and not just whether it exists | |||||
| * | |||||
| * @param string $packageName | |||||
| * @return string Version constraint usable with composer/semver | |||||
| */ | |||||
| public static function getVersionRanges($packageName) | |||||
| { | |||||
| foreach (self::getInstalled() as $installed) { | |||||
| if (!isset($installed['versions'][$packageName])) { | |||||
| continue; | |||||
| } | |||||
| $ranges = array(); | |||||
| if (isset($installed['versions'][$packageName]['pretty_version'])) { | |||||
| $ranges[] = $installed['versions'][$packageName]['pretty_version']; | |||||
| } | |||||
| if (array_key_exists('aliases', $installed['versions'][$packageName])) { | |||||
| $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); | |||||
| } | |||||
| if (array_key_exists('replaced', $installed['versions'][$packageName])) { | |||||
| $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); | |||||
| } | |||||
| if (array_key_exists('provided', $installed['versions'][$packageName])) { | |||||
| $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); | |||||
| } | |||||
| return implode(' || ', $ranges); | |||||
| } | |||||
| throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); | |||||
| } | |||||
| /** | |||||
| * @param string $packageName | |||||
| * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present | |||||
| */ | |||||
| public static function getVersion($packageName) | |||||
| { | |||||
| foreach (self::getInstalled() as $installed) { | |||||
| if (!isset($installed['versions'][$packageName])) { | |||||
| continue; | |||||
| } | |||||
| if (!isset($installed['versions'][$packageName]['version'])) { | |||||
| return null; | |||||
| } | |||||
| return $installed['versions'][$packageName]['version']; | |||||
| } | |||||
| throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); | |||||
| } | |||||
| /** | |||||
| * @param string $packageName | |||||
| * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present | |||||
| */ | |||||
| public static function getPrettyVersion($packageName) | |||||
| { | |||||
| foreach (self::getInstalled() as $installed) { | |||||
| if (!isset($installed['versions'][$packageName])) { | |||||
| continue; | |||||
| } | |||||
| if (!isset($installed['versions'][$packageName]['pretty_version'])) { | |||||
| return null; | |||||
| } | |||||
| return $installed['versions'][$packageName]['pretty_version']; | |||||
| } | |||||
| throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); | |||||
| } | |||||
| /** | |||||
| * @param string $packageName | |||||
| * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference | |||||
| */ | |||||
| public static function getReference($packageName) | |||||
| { | |||||
| foreach (self::getInstalled() as $installed) { | |||||
| if (!isset($installed['versions'][$packageName])) { | |||||
| continue; | |||||
| } | |||||
| if (!isset($installed['versions'][$packageName]['reference'])) { | |||||
| return null; | |||||
| } | |||||
| return $installed['versions'][$packageName]['reference']; | |||||
| } | |||||
| throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); | |||||
| } | |||||
| /** | |||||
| * @param string $packageName | |||||
| * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. | |||||
| */ | |||||
| public static function getInstallPath($packageName) | |||||
| { | |||||
| foreach (self::getInstalled() as $installed) { | |||||
| if (!isset($installed['versions'][$packageName])) { | |||||
| continue; | |||||
| } | |||||
| return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; | |||||
| } | |||||
| throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); | |||||
| } | |||||
| /** | |||||
| * @return array | |||||
| * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} | |||||
| */ | |||||
| public static function getRootPackage() | |||||
| { | |||||
| $installed = self::getInstalled(); | |||||
| return $installed[0]['root']; | |||||
| } | |||||
| /** | |||||
| * Returns the raw installed.php data for custom implementations | |||||
| * | |||||
| * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. | |||||
| * @return array[] | |||||
| * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} | |||||
| */ | |||||
| public static function getRawData() | |||||
| { | |||||
| @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); | |||||
| if (null === self::$installed) { | |||||
| // only require the installed.php file if this file is loaded from its dumped location, | |||||
| // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 | |||||
| if (substr(__DIR__, -8, 1) !== 'C') { | |||||
| self::$installed = include __DIR__ . '/installed.php'; | |||||
| } else { | |||||
| self::$installed = array(); | |||||
| } | |||||
| } | |||||
| return self::$installed; | |||||
| } | |||||
| /** | |||||
| * Returns the raw data of all installed.php which are currently loaded for custom implementations | |||||
| * | |||||
| * @return array[] | |||||
| * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> | |||||
| */ | |||||
| public static function getAllRawData() | |||||
| { | |||||
| return self::getInstalled(); | |||||
| } | |||||
| /** | |||||
| * Lets you reload the static array from another file | |||||
| * | |||||
| * This is only useful for complex integrations in which a project needs to use | |||||
| * this class but then also needs to execute another project's autoloader in process, | |||||
| * and wants to ensure both projects have access to their version of installed.php. | |||||
| * | |||||
| * A typical case would be PHPUnit, where it would need to make sure it reads all | |||||
| * the data it needs from this class, then call reload() with | |||||
| * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure | |||||
| * the project in which it runs can then also use this class safely, without | |||||
| * interference between PHPUnit's dependencies and the project's dependencies. | |||||
| * | |||||
| * @param array[] $data A vendor/composer/installed.php data set | |||||
| * @return void | |||||
| * | |||||
| * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data | |||||
| */ | |||||
| public static function reload($data) | |||||
| { | |||||
| self::$installed = $data; | |||||
| self::$installedByVendor = array(); | |||||
| } | |||||
| /** | |||||
| * @return array[] | |||||
| * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> | |||||
| */ | |||||
| private static function getInstalled() | |||||
| { | |||||
| if (null === self::$canGetVendors) { | |||||
| self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); | |||||
| } | |||||
| $installed = array(); | |||||
| if (self::$canGetVendors) { | |||||
| foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { | |||||
| if (isset(self::$installedByVendor[$vendorDir])) { | |||||
| $installed[] = self::$installedByVendor[$vendorDir]; | |||||
| } elseif (is_file($vendorDir.'/composer/installed.php')) { | |||||
| /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ | |||||
| $required = require $vendorDir.'/composer/installed.php'; | |||||
| $installed[] = self::$installedByVendor[$vendorDir] = $required; | |||||
| if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { | |||||
| self::$installed = $installed[count($installed) - 1]; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| if (null === self::$installed) { | |||||
| // only require the installed.php file if this file is loaded from its dumped location, | |||||
| // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 | |||||
| if (substr(__DIR__, -8, 1) !== 'C') { | |||||
| /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ | |||||
| $required = require __DIR__ . '/installed.php'; | |||||
| self::$installed = $required; | |||||
| } else { | |||||
| self::$installed = array(); | |||||
| } | |||||
| } | |||||
| if (self::$installed !== array()) { | |||||
| $installed[] = self::$installed; | |||||
| } | |||||
| return $installed; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| Copyright (c) Nils Adermann, Jordi Boggiano | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is furnished | |||||
| to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in all | |||||
| copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
| THE SOFTWARE. | |||||
| @@ -0,0 +1,10 @@ | |||||
| <?php | |||||
| // autoload_classmap.php @generated by Composer | |||||
| $vendorDir = dirname(__DIR__); | |||||
| $baseDir = dirname($vendorDir); | |||||
| return array( | |||||
| 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', | |||||
| ); | |||||
| @@ -0,0 +1,10 @@ | |||||
| <?php | |||||
| // autoload_files.php @generated by Composer | |||||
| $vendorDir = dirname(__DIR__); | |||||
| $baseDir = dirname($vendorDir); | |||||
| return array( | |||||
| 'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php', | |||||
| ); | |||||
| @@ -0,0 +1,9 @@ | |||||
| <?php | |||||
| // autoload_namespaces.php @generated by Composer | |||||
| $vendorDir = dirname(__DIR__); | |||||
| $baseDir = dirname($vendorDir); | |||||
| return array( | |||||
| ); | |||||
| @@ -0,0 +1,12 @@ | |||||
| <?php | |||||
| // autoload_psr4.php @generated by Composer | |||||
| $vendorDir = dirname(__DIR__); | |||||
| $baseDir = dirname($vendorDir); | |||||
| return array( | |||||
| 'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), | |||||
| 'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'), | |||||
| 'Brick\\Math\\' => array($vendorDir . '/brick/math/src'), | |||||
| ); | |||||
| @@ -0,0 +1,50 @@ | |||||
| <?php | |||||
| // autoload_real.php @generated by Composer | |||||
| class ComposerAutoloaderInit7422faf8f33d1f1cdfca8767e0ffc0f4 | |||||
| { | |||||
| private static $loader; | |||||
| public static function loadClassLoader($class) | |||||
| { | |||||
| if ('Composer\Autoload\ClassLoader' === $class) { | |||||
| require __DIR__ . '/ClassLoader.php'; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * @return \Composer\Autoload\ClassLoader | |||||
| */ | |||||
| public static function getLoader() | |||||
| { | |||||
| if (null !== self::$loader) { | |||||
| return self::$loader; | |||||
| } | |||||
| require __DIR__ . '/platform_check.php'; | |||||
| spl_autoload_register(array('ComposerAutoloaderInit7422faf8f33d1f1cdfca8767e0ffc0f4', 'loadClassLoader'), true, true); | |||||
| self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); | |||||
| spl_autoload_unregister(array('ComposerAutoloaderInit7422faf8f33d1f1cdfca8767e0ffc0f4', 'loadClassLoader')); | |||||
| require __DIR__ . '/autoload_static.php'; | |||||
| call_user_func(\Composer\Autoload\ComposerStaticInit7422faf8f33d1f1cdfca8767e0ffc0f4::getInitializer($loader)); | |||||
| $loader->register(true); | |||||
| $filesToLoad = \Composer\Autoload\ComposerStaticInit7422faf8f33d1f1cdfca8767e0ffc0f4::$files; | |||||
| $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { | |||||
| if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { | |||||
| $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; | |||||
| require $file; | |||||
| } | |||||
| }, null, null); | |||||
| foreach ($filesToLoad as $fileIdentifier => $file) { | |||||
| $requireFile($fileIdentifier, $file); | |||||
| } | |||||
| return $loader; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,53 @@ | |||||
| <?php | |||||
| // autoload_static.php @generated by Composer | |||||
| namespace Composer\Autoload; | |||||
| class ComposerStaticInit7422faf8f33d1f1cdfca8767e0ffc0f4 | |||||
| { | |||||
| public static $files = array ( | |||||
| 'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php', | |||||
| ); | |||||
| public static $prefixLengthsPsr4 = array ( | |||||
| 'R' => | |||||
| array ( | |||||
| 'Ramsey\\Uuid\\' => 12, | |||||
| 'Ramsey\\Collection\\' => 18, | |||||
| ), | |||||
| 'B' => | |||||
| array ( | |||||
| 'Brick\\Math\\' => 11, | |||||
| ), | |||||
| ); | |||||
| public static $prefixDirsPsr4 = array ( | |||||
| 'Ramsey\\Uuid\\' => | |||||
| array ( | |||||
| 0 => __DIR__ . '/..' . '/ramsey/uuid/src', | |||||
| ), | |||||
| 'Ramsey\\Collection\\' => | |||||
| array ( | |||||
| 0 => __DIR__ . '/..' . '/ramsey/collection/src', | |||||
| ), | |||||
| 'Brick\\Math\\' => | |||||
| array ( | |||||
| 0 => __DIR__ . '/..' . '/brick/math/src', | |||||
| ), | |||||
| ); | |||||
| public static $classMap = array ( | |||||
| 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', | |||||
| ); | |||||
| public static function getInitializer(ClassLoader $loader) | |||||
| { | |||||
| return \Closure::bind(function () use ($loader) { | |||||
| $loader->prefixLengthsPsr4 = ComposerStaticInit7422faf8f33d1f1cdfca8767e0ffc0f4::$prefixLengthsPsr4; | |||||
| $loader->prefixDirsPsr4 = ComposerStaticInit7422faf8f33d1f1cdfca8767e0ffc0f4::$prefixDirsPsr4; | |||||
| $loader->classMap = ComposerStaticInit7422faf8f33d1f1cdfca8767e0ffc0f4::$classMap; | |||||
| }, null, ClassLoader::class); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,256 @@ | |||||
| { | |||||
| "packages": [ | |||||
| { | |||||
| "name": "brick/math", | |||||
| "version": "0.12.1", | |||||
| "version_normalized": "0.12.1.0", | |||||
| "source": { | |||||
| "type": "git", | |||||
| "url": "https://github.com/brick/math.git", | |||||
| "reference": "f510c0a40911935b77b86859eb5223d58d660df1" | |||||
| }, | |||||
| "dist": { | |||||
| "type": "zip", | |||||
| "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", | |||||
| "reference": "f510c0a40911935b77b86859eb5223d58d660df1", | |||||
| "shasum": "" | |||||
| }, | |||||
| "require": { | |||||
| "php": "^8.1" | |||||
| }, | |||||
| "require-dev": { | |||||
| "php-coveralls/php-coveralls": "^2.2", | |||||
| "phpunit/phpunit": "^10.1", | |||||
| "vimeo/psalm": "5.16.0" | |||||
| }, | |||||
| "time": "2023-11-29T23:19:16+00:00", | |||||
| "type": "library", | |||||
| "installation-source": "dist", | |||||
| "autoload": { | |||||
| "psr-4": { | |||||
| "Brick\\Math\\": "src/" | |||||
| } | |||||
| }, | |||||
| "notification-url": "https://packagist.org/downloads/", | |||||
| "license": [ | |||||
| "MIT" | |||||
| ], | |||||
| "description": "Arbitrary-precision arithmetic library", | |||||
| "keywords": [ | |||||
| "Arbitrary-precision", | |||||
| "BigInteger", | |||||
| "BigRational", | |||||
| "arithmetic", | |||||
| "bigdecimal", | |||||
| "bignum", | |||||
| "bignumber", | |||||
| "brick", | |||||
| "decimal", | |||||
| "integer", | |||||
| "math", | |||||
| "mathematics", | |||||
| "rational" | |||||
| ], | |||||
| "support": { | |||||
| "issues": "https://github.com/brick/math/issues", | |||||
| "source": "https://github.com/brick/math/tree/0.12.1" | |||||
| }, | |||||
| "funding": [ | |||||
| { | |||||
| "url": "https://github.com/BenMorel", | |||||
| "type": "github" | |||||
| } | |||||
| ], | |||||
| "install-path": "../brick/math" | |||||
| }, | |||||
| { | |||||
| "name": "ramsey/collection", | |||||
| "version": "2.0.0", | |||||
| "version_normalized": "2.0.0.0", | |||||
| "source": { | |||||
| "type": "git", | |||||
| "url": "https://github.com/ramsey/collection.git", | |||||
| "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" | |||||
| }, | |||||
| "dist": { | |||||
| "type": "zip", | |||||
| "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", | |||||
| "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", | |||||
| "shasum": "" | |||||
| }, | |||||
| "require": { | |||||
| "php": "^8.1" | |||||
| }, | |||||
| "require-dev": { | |||||
| "captainhook/plugin-composer": "^5.3", | |||||
| "ergebnis/composer-normalize": "^2.28.3", | |||||
| "fakerphp/faker": "^1.21", | |||||
| "hamcrest/hamcrest-php": "^2.0", | |||||
| "jangregor/phpstan-prophecy": "^1.0", | |||||
| "mockery/mockery": "^1.5", | |||||
| "php-parallel-lint/php-console-highlighter": "^1.0", | |||||
| "php-parallel-lint/php-parallel-lint": "^1.3", | |||||
| "phpcsstandards/phpcsutils": "^1.0.0-rc1", | |||||
| "phpspec/prophecy-phpunit": "^2.0", | |||||
| "phpstan/extension-installer": "^1.2", | |||||
| "phpstan/phpstan": "^1.9", | |||||
| "phpstan/phpstan-mockery": "^1.1", | |||||
| "phpstan/phpstan-phpunit": "^1.3", | |||||
| "phpunit/phpunit": "^9.5", | |||||
| "psalm/plugin-mockery": "^1.1", | |||||
| "psalm/plugin-phpunit": "^0.18.4", | |||||
| "ramsey/coding-standard": "^2.0.3", | |||||
| "ramsey/conventional-commits": "^1.3", | |||||
| "vimeo/psalm": "^5.4" | |||||
| }, | |||||
| "time": "2022-12-31T21:50:55+00:00", | |||||
| "type": "library", | |||||
| "extra": { | |||||
| "captainhook": { | |||||
| "force-install": true | |||||
| }, | |||||
| "ramsey/conventional-commits": { | |||||
| "configFile": "conventional-commits.json" | |||||
| } | |||||
| }, | |||||
| "installation-source": "dist", | |||||
| "autoload": { | |||||
| "psr-4": { | |||||
| "Ramsey\\Collection\\": "src/" | |||||
| } | |||||
| }, | |||||
| "notification-url": "https://packagist.org/downloads/", | |||||
| "license": [ | |||||
| "MIT" | |||||
| ], | |||||
| "authors": [ | |||||
| { | |||||
| "name": "Ben Ramsey", | |||||
| "email": "ben@benramsey.com", | |||||
| "homepage": "https://benramsey.com" | |||||
| } | |||||
| ], | |||||
| "description": "A PHP library for representing and manipulating collections.", | |||||
| "keywords": [ | |||||
| "array", | |||||
| "collection", | |||||
| "hash", | |||||
| "map", | |||||
| "queue", | |||||
| "set" | |||||
| ], | |||||
| "support": { | |||||
| "issues": "https://github.com/ramsey/collection/issues", | |||||
| "source": "https://github.com/ramsey/collection/tree/2.0.0" | |||||
| }, | |||||
| "funding": [ | |||||
| { | |||||
| "url": "https://github.com/ramsey", | |||||
| "type": "github" | |||||
| }, | |||||
| { | |||||
| "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", | |||||
| "type": "tidelift" | |||||
| } | |||||
| ], | |||||
| "install-path": "../ramsey/collection" | |||||
| }, | |||||
| { | |||||
| "name": "ramsey/uuid", | |||||
| "version": "4.7.6", | |||||
| "version_normalized": "4.7.6.0", | |||||
| "source": { | |||||
| "type": "git", | |||||
| "url": "https://github.com/ramsey/uuid.git", | |||||
| "reference": "91039bc1faa45ba123c4328958e620d382ec7088" | |||||
| }, | |||||
| "dist": { | |||||
| "type": "zip", | |||||
| "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", | |||||
| "reference": "91039bc1faa45ba123c4328958e620d382ec7088", | |||||
| "shasum": "" | |||||
| }, | |||||
| "require": { | |||||
| "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", | |||||
| "ext-json": "*", | |||||
| "php": "^8.0", | |||||
| "ramsey/collection": "^1.2 || ^2.0" | |||||
| }, | |||||
| "replace": { | |||||
| "rhumsaa/uuid": "self.version" | |||||
| }, | |||||
| "require-dev": { | |||||
| "captainhook/captainhook": "^5.10", | |||||
| "captainhook/plugin-composer": "^5.3", | |||||
| "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", | |||||
| "doctrine/annotations": "^1.8", | |||||
| "ergebnis/composer-normalize": "^2.15", | |||||
| "mockery/mockery": "^1.3", | |||||
| "paragonie/random-lib": "^2", | |||||
| "php-mock/php-mock": "^2.2", | |||||
| "php-mock/php-mock-mockery": "^1.3", | |||||
| "php-parallel-lint/php-parallel-lint": "^1.1", | |||||
| "phpbench/phpbench": "^1.0", | |||||
| "phpstan/extension-installer": "^1.1", | |||||
| "phpstan/phpstan": "^1.8", | |||||
| "phpstan/phpstan-mockery": "^1.1", | |||||
| "phpstan/phpstan-phpunit": "^1.1", | |||||
| "phpunit/phpunit": "^8.5 || ^9", | |||||
| "ramsey/composer-repl": "^1.4", | |||||
| "slevomat/coding-standard": "^8.4", | |||||
| "squizlabs/php_codesniffer": "^3.5", | |||||
| "vimeo/psalm": "^4.9" | |||||
| }, | |||||
| "suggest": { | |||||
| "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", | |||||
| "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", | |||||
| "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", | |||||
| "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", | |||||
| "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." | |||||
| }, | |||||
| "time": "2024-04-27T21:32:50+00:00", | |||||
| "type": "library", | |||||
| "extra": { | |||||
| "captainhook": { | |||||
| "force-install": true | |||||
| } | |||||
| }, | |||||
| "installation-source": "dist", | |||||
| "autoload": { | |||||
| "files": [ | |||||
| "src/functions.php" | |||||
| ], | |||||
| "psr-4": { | |||||
| "Ramsey\\Uuid\\": "src/" | |||||
| } | |||||
| }, | |||||
| "notification-url": "https://packagist.org/downloads/", | |||||
| "license": [ | |||||
| "MIT" | |||||
| ], | |||||
| "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", | |||||
| "keywords": [ | |||||
| "guid", | |||||
| "identifier", | |||||
| "uuid" | |||||
| ], | |||||
| "support": { | |||||
| "issues": "https://github.com/ramsey/uuid/issues", | |||||
| "source": "https://github.com/ramsey/uuid/tree/4.7.6" | |||||
| }, | |||||
| "funding": [ | |||||
| { | |||||
| "url": "https://github.com/ramsey", | |||||
| "type": "github" | |||||
| }, | |||||
| { | |||||
| "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", | |||||
| "type": "tidelift" | |||||
| } | |||||
| ], | |||||
| "install-path": "../ramsey/uuid" | |||||
| } | |||||
| ], | |||||
| "dev": true, | |||||
| "dev-package-names": [] | |||||
| } | |||||
| @@ -0,0 +1,56 @@ | |||||
| <?php return array( | |||||
| 'root' => array( | |||||
| 'name' => '__root__', | |||||
| 'pretty_version' => 'dev-master', | |||||
| 'version' => 'dev-master', | |||||
| 'reference' => '58250a9b460c2aa8b4e02c2feff7f72cb980a184', | |||||
| 'type' => 'library', | |||||
| 'install_path' => __DIR__ . '/../../', | |||||
| 'aliases' => array(), | |||||
| 'dev' => true, | |||||
| ), | |||||
| 'versions' => array( | |||||
| '__root__' => array( | |||||
| 'pretty_version' => 'dev-master', | |||||
| 'version' => 'dev-master', | |||||
| 'reference' => '58250a9b460c2aa8b4e02c2feff7f72cb980a184', | |||||
| 'type' => 'library', | |||||
| 'install_path' => __DIR__ . '/../../', | |||||
| 'aliases' => array(), | |||||
| 'dev_requirement' => false, | |||||
| ), | |||||
| 'brick/math' => array( | |||||
| 'pretty_version' => '0.12.1', | |||||
| 'version' => '0.12.1.0', | |||||
| 'reference' => 'f510c0a40911935b77b86859eb5223d58d660df1', | |||||
| 'type' => 'library', | |||||
| 'install_path' => __DIR__ . '/../brick/math', | |||||
| 'aliases' => array(), | |||||
| 'dev_requirement' => false, | |||||
| ), | |||||
| 'ramsey/collection' => array( | |||||
| 'pretty_version' => '2.0.0', | |||||
| 'version' => '2.0.0.0', | |||||
| 'reference' => 'a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5', | |||||
| 'type' => 'library', | |||||
| 'install_path' => __DIR__ . '/../ramsey/collection', | |||||
| 'aliases' => array(), | |||||
| 'dev_requirement' => false, | |||||
| ), | |||||
| 'ramsey/uuid' => array( | |||||
| 'pretty_version' => '4.7.6', | |||||
| 'version' => '4.7.6.0', | |||||
| 'reference' => '91039bc1faa45ba123c4328958e620d382ec7088', | |||||
| 'type' => 'library', | |||||
| 'install_path' => __DIR__ . '/../ramsey/uuid', | |||||
| 'aliases' => array(), | |||||
| 'dev_requirement' => false, | |||||
| ), | |||||
| 'rhumsaa/uuid' => array( | |||||
| 'dev_requirement' => false, | |||||
| 'replaced' => array( | |||||
| 0 => '4.7.6', | |||||
| ), | |||||
| ), | |||||
| ), | |||||
| ); | |||||
| @@ -0,0 +1,26 @@ | |||||
| <?php | |||||
| // platform_check.php @generated by Composer | |||||
| $issues = array(); | |||||
| if (!(PHP_VERSION_ID >= 80100)) { | |||||
| $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; | |||||
| } | |||||
| if ($issues) { | |||||
| if (!headers_sent()) { | |||||
| header('HTTP/1.1 500 Internal Server Error'); | |||||
| } | |||||
| if (!ini_get('display_errors')) { | |||||
| if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { | |||||
| fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); | |||||
| } elseif (!headers_sent()) { | |||||
| echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; | |||||
| } | |||||
| } | |||||
| trigger_error( | |||||
| 'Composer detected issues in your platform: ' . implode(' ', $issues), | |||||
| E_USER_ERROR | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| Copyright (c) 2015-2022 Ben Ramsey <ben@benramsey.com> | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in | |||||
| all copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
| THE SOFTWARE. | |||||
| @@ -0,0 +1,70 @@ | |||||
| <h1 align="center">ramsey/collection</h1> | |||||
| <p align="center"> | |||||
| <strong>A PHP library for representing and manipulating collections.</strong> | |||||
| </p> | |||||
| <p align="center"> | |||||
| <a href="https://github.com/ramsey/collection"><img src="http://img.shields.io/badge/source-ramsey/collection-blue.svg?style=flat-square" alt="Source Code"></a> | |||||
| <a href="https://packagist.org/packages/ramsey/collection"><img src="https://img.shields.io/packagist/v/ramsey/collection.svg?style=flat-square&label=release" alt="Download Package"></a> | |||||
| <a href="https://php.net"><img src="https://img.shields.io/packagist/php-v/ramsey/collection.svg?style=flat-square&colorB=%238892BF" alt="PHP Programming Language"></a> | |||||
| <a href="https://github.com/ramsey/collection/blob/master/LICENSE"><img src="https://img.shields.io/packagist/l/ramsey/collection.svg?style=flat-square&colorB=darkcyan" alt="Read License"></a> | |||||
| <a href="https://github.com/ramsey/collection/actions/workflows/continuous-integration.yml"><img src="https://img.shields.io/github/actions/workflow/status/ramsey/collection/continuous-integration.yml?branch=main&logo=github&style=flat-square" alt="Build Status"></a> | |||||
| <a href="https://codecov.io/gh/ramsey/collection"><img src="https://img.shields.io/codecov/c/gh/ramsey/collection?label=codecov&logo=codecov&style=flat-square" alt="Codecov Code Coverage"></a> | |||||
| <a href="https://shepherd.dev/github/ramsey/collection"><img src="https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fshepherd.dev%2Fgithub%2Framsey%2Fcollection%2Fcoverage" alt="Psalm Type Coverage"></a> | |||||
| </p> | |||||
| ## About | |||||
| ramsey/collection is a PHP library for representing and manipulating collections. | |||||
| Much inspiration for this library came from the [Java Collections Framework][java]. | |||||
| This project adheres to a [code of conduct](CODE_OF_CONDUCT.md). | |||||
| By participating in this project and its community, you are expected to | |||||
| uphold this code. | |||||
| ## Installation | |||||
| Install this package as a dependency using [Composer](https://getcomposer.org). | |||||
| ``` bash | |||||
| composer require ramsey/collection | |||||
| ``` | |||||
| ## Usage | |||||
| Examples of how to use this library may be found in the | |||||
| [Wiki pages](https://github.com/ramsey/collection/wiki/Examples). | |||||
| ## Contributing | |||||
| Contributions are welcome! To contribute, please familiarize yourself with | |||||
| [CONTRIBUTING.md](CONTRIBUTING.md). | |||||
| ## Coordinated Disclosure | |||||
| Keeping user information safe and secure is a top priority, and we welcome the | |||||
| contribution of external security researchers. If you believe you've found a | |||||
| security issue in software that is maintained in this repository, please read | |||||
| [SECURITY.md][] for instructions on submitting a vulnerability report. | |||||
| ## ramsey/collection for Enterprise | |||||
| Available as part of the Tidelift Subscription. | |||||
| The maintainers of ramsey/collection and thousands of other packages are working | |||||
| with Tidelift to deliver commercial support and maintenance for the open source | |||||
| packages you use to build your applications. Save time, reduce risk, and improve | |||||
| code health, while paying the maintainers of the exact packages you use. | |||||
| [Learn more.](https://tidelift.com/subscription/pkg/packagist-ramsey-collection?utm_source=undefined&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) | |||||
| ## Copyright and License | |||||
| The ramsey/collection library is copyright © [Ben Ramsey](https://benramsey.com) | |||||
| and licensed for use under the terms of the | |||||
| MIT License (MIT). Please see [LICENSE](LICENSE) for more information. | |||||
| [java]: http://docs.oracle.com/javase/8/docs/technotes/guides/collections/index.html | |||||
| [security.md]: https://github.com/ramsey/collection/blob/main/SECURITY.md | |||||