| @@ -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 | |||
| 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 | |||
| 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 | |||
| - Database: http://localhost:8096 | |||
| - Template-Engine: https://github.com/cho45/micro-template.js | |||
| @@ -28,3 +33,5 @@ Neuinstallation: | |||
| - php pw_gen.php - Erzeugt Passwort "test" | |||
| - 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 { | |||
| margin-top: 72px; | |||
| /*margin-top: 72px;*/ | |||
| margin-top: 20px; | |||
| margin-bottom: 20px; | |||
| } | |||
| .dropdown-menu { | |||
| top: auto; | |||
| bottom: 100%; | |||
| } | |||
| .c-offcanvas--right { | |||
| @@ -92,6 +98,8 @@ body.body-content { | |||
| { | |||
| position: absolute; | |||
| min-width: 280px; | |||
| max-height: calc(100vh - 60px); | |||
| overflow: auto; | |||
| } | |||
| .ul-offcanvas-nav | |||
| @@ -457,7 +465,8 @@ body.body-auth .action-button { | |||
| position: fixed; | |||
| -webkit-transform: translate3d(0,0,0); | |||
| transform: translate3d(0,0,0); | |||
| bottom: 20px; | |||
| /*bottom: 20px;*/ | |||
| bottom: 76px; | |||
| right: 20px; | |||
| text-align: center; | |||
| font-size: 20px; | |||
| @@ -471,6 +480,8 @@ body.body-auth .action-button { | |||
| overflow: hidden; | |||
| 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); | |||
| overflow: auto; | |||
| max-height: calc(100% - 85px); | |||
| } | |||
| .action-list { | |||
| @@ -482,7 +493,7 @@ body.body-auth .action-button { | |||
| bottom: 0; | |||
| right: 0; | |||
| opacity: 0; | |||
| background-color: white; | |||
| background-color: #fff; | |||
| } | |||
| .action-list-item { | |||
| @@ -1125,6 +1136,44 @@ h6.in-card { | |||
| 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 { | |||
| margin-top: 0; | |||
| margin-bottom: 0; | |||
| @@ -1,8 +1,7 @@ | |||
| <?php | |||
| require_once __DIR__ . '/../../server/server/config/boot_global.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> | |||
| <html lang="en"> | |||
| @@ -111,6 +111,7 @@ app.core.Dict = { | |||
| "CLOSE" : "Schließen", | |||
| "CREATE_APPOINTMENT" : "Termin erstellen", | |||
| "APPOINTMENT_SUBJECT" : "Terminname", | |||
| "APPOINTMENT_ICON" : "Icon", | |||
| "APPOINTMENT_START_DATE" : "Startdatum", | |||
| "APPOINTMENT_START_TIME" : "Startzeit", | |||
| "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.", | |||
| "LOGIN_FIRSTNAME" : "Vorname", | |||
| "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" : "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", | |||
| @@ -571,6 +574,7 @@ app.core.Dict = { | |||
| "GROUP_MANAGEMENT_MEMBERS_ACTIVE" : "Aktiv", | |||
| "GROUP_MANAGEMENT_MEMBERS_INACTIVE" : "Inaktiv", | |||
| "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_INFO" : "Du kannst deinen eigenen Gruppenstatus nicht verändern.", | |||
| "BTN_GROUP_MANAGEMENT_MEMBERS_SAVE_STATUS" : "Status speichern", | |||
| @@ -15,6 +15,7 @@ app.model.Appointment = function( appData, attendees, attendeeProfiles ) | |||
| attendees = attendees || [], | |||
| attendeeProfiles = attendeeProfiles || [], | |||
| id = data.id, | |||
| icon = data.icon, | |||
| category = data.category || null, | |||
| categoryIds = typeof( data.category_ids_js ) === 'string' ? JSON.parse( data.category_ids_js ) : data.category_ids_js, | |||
| visibility = data.visibility, | |||
| @@ -65,6 +66,11 @@ app.model.Appointment = function( appData, attendees, attendeeProfiles ) | |||
| return teamId; | |||
| }; | |||
| this.getIcon = function() | |||
| { | |||
| return icon; | |||
| }; | |||
| this.getSubject = function() | |||
| { | |||
| return subject; | |||
| @@ -223,6 +223,11 @@ app.model.Profile = function( data, groupsData ) | |||
| teamData = gd; | |||
| }; | |||
| this.getGroupStatus = function( groupId ) | |||
| { | |||
| return this.getGroupData(groupId).status; | |||
| } | |||
| this.isOwn = function() | |||
| { | |||
| 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(), | |||
| teamId, | |||
| icon, | |||
| appointmentState = $form.find( '[data-id="checkbox-appointment-state"]' ).first().is( ':checked' ) ? 'open' : null, | |||
| isValid = app.util.Form.bootstrapValidate( $form ); | |||
| @@ -191,11 +192,15 @@ app.state.AppointmentCreate = function() | |||
| { | |||
| app.gui.PageLoader.show(); | |||
| teamId = $form.find( '[data-id="select-team-id"]' ).first().val(); | |||
| icon = $('#input-subject-icon option:selected').val(); | |||
| //console.log(icon); | |||
| app.core.Rpc.call( | |||
| 'Appointment', | |||
| 'create', | |||
| { | |||
| teamId : teamId, | |||
| icon: icon, | |||
| 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, | |||
| subject : $form.find( '[data-id="input-subject"]' ).first().val(), | |||
| @@ -168,6 +168,7 @@ app.state.AppointmentEdit= function() | |||
| 'Appointment', | |||
| 'update', | |||
| { | |||
| icon: $('#input-subject-icon option:selected').val(), | |||
| processSerial : isSerial, | |||
| appointmentId : appointmentId, | |||
| categoryIds : $form.find( '[data-id="select-category-' + appointment.getTeamId() + '"]' ).first().val(), | |||
| @@ -31,7 +31,7 @@ app.state.AppointmentEditAttendee = function() | |||
| app.core.Rpc.call( | |||
| 'Team', | |||
| 'getMembers', | |||
| { teamId : appointment.getTeamId() }, | |||
| { teamId : appointment.getTeamId(), activeOnly: true }, | |||
| function( res2 ) | |||
| { | |||
| let childs, m, notDecided = []; | |||
| @@ -40,7 +40,7 @@ app.state.ConfigurationAttendanceLog = function() | |||
| { | |||
| $content.find( '[data-id="container-appointment-log"]' ).first().html( | |||
| app.core.View.getTemplate( | |||
| 'group-member-management-body-appointment-log', | |||
| 'group-member-management-member-body-appointment-log', | |||
| { | |||
| logs : res.appointmentLog | |||
| } | |||
| @@ -18,15 +18,18 @@ app.state.ConfigurationProfileDelete = function() | |||
| app.core.Rpc.call( | |||
| 'Account', | |||
| 'getAccountRelatedData', | |||
| null, | |||
| { | |||
| includeNumFutureAttendances : true | |||
| }, | |||
| function( res ) { | |||
| app.gui.PageLoader.hide(); | |||
| console.log(res); | |||
| app.core.View.setContent( | |||
| app.core.View.getTemplate( | |||
| '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', | |||
| 'getDetails', | |||
| { | |||
| teamId : groupId | |||
| teamId : groupId, | |||
| includeMembers: true, | |||
| activeOnly: true | |||
| }, | |||
| function( res ) | |||
| { | |||
| @@ -13,11 +13,13 @@ app.state.GroupMemberManagement = function() | |||
| { | |||
| let $content = app.core.View.getContent(), | |||
| group = null, | |||
| currentProfile = null, | |||
| memberToEdit = null, | |||
| members = [], | |||
| membersActive = [], | |||
| membersInactive = [], | |||
| membersNotApproved = [], | |||
| membersDeleted = [], | |||
| groupId = p.groupId, | |||
| memberId = p.hasOwnProperty( 'memberId' ) ? p.memberId : null, | |||
| activeTab = 'active'; | |||
| @@ -65,6 +67,10 @@ app.state.GroupMemberManagement = function() | |||
| case 'not_approved': | |||
| membersNotApproved.push(member); | |||
| break; | |||
| case 'deleted': | |||
| membersDeleted.push(member); | |||
| console.log(member.isAccessible()) | |||
| break; | |||
| } | |||
| } | |||
| @@ -76,6 +82,7 @@ app.state.GroupMemberManagement = function() | |||
| membersActive: membersActive, | |||
| membersInactive: membersInactive, | |||
| membersNotApproved: membersNotApproved, | |||
| membersDeleted: membersDeleted, | |||
| group : group, | |||
| groups : app.model.SessionUser.getAdminGroups(), | |||
| currentProfile : currentProfile, | |||
| @@ -11,7 +11,6 @@ app.state.GroupMemberManagementMember = function() | |||
| state.onEnter = function( p ) | |||
| { | |||
| console.log(p); | |||
| let $content = app.core.View.getContent(), | |||
| fnRenderMemberForm = null, | |||
| fnGetMemberById = null, | |||
| @@ -27,6 +26,11 @@ app.state.GroupMemberManagementMember = function() | |||
| fnRenderMemberForm = function( profile ) | |||
| { | |||
| let status = ''; | |||
| if (profile) { | |||
| status = profile.getGroupData(groupId).status; | |||
| } | |||
| $memberContainer = $content.find( '[data-id="member-container"]' ).first(); | |||
| $memberContainer.html( | |||
| app.core.View.getTemplate( | |||
| @@ -34,6 +38,7 @@ app.state.GroupMemberManagementMember = function() | |||
| { | |||
| p : profile, | |||
| 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 | |||
| // This needs to be called once at the beginning to trigger correct handlers and body classes | |||
| app.core.View.setContent( 'Loading...' ); | |||
| @@ -511,14 +483,45 @@ app.state.Home = function() | |||
| ) | |||
| ); | |||
| } | |||
| 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 ) | |||
| @@ -104,7 +104,8 @@ app.state.StatsExport = function() | |||
| 'Team', | |||
| 'getDetails', | |||
| { | |||
| teamId : groupId | |||
| teamId : groupId, | |||
| includeMembers: true | |||
| }, | |||
| function( res2 ) | |||
| { | |||
| @@ -44,7 +44,7 @@ | |||
| <% } %> | |||
| </div> | |||
| <div class="appointment-detail-appointment-name"> | |||
| <%= a.getSubject() %> | |||
| <%= a.getIcon() %> <%= a.getSubject() %> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -24,22 +24,46 @@ | |||
| value="<%= mTeam.getId() %>" /> | |||
| <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> | |||
| <% var categories = mTeam.getAdminCourseCategoriesForProfile( currentProfile ); %> | |||
| @@ -38,20 +38,44 @@ | |||
| </div> | |||
| <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> | |||
| @@ -19,6 +19,23 @@ | |||
| </a> | |||
| </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 { %> | |||
| <form data-id="form-profile-deletion" | |||
| @@ -1,289 +1,288 @@ | |||
| <% 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 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 { %> | |||
| <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> | |||
| <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 { %> | |||
| <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> | |||
| <% } %> | |||
| @@ -46,6 +46,12 @@ | |||
| <%= _lc( 'GROUP_MANAGEMENT_MEMBERS_NOT_APPROVED' ) %> (<%= membersNotApproved.length %>) | |||
| </a> | |||
| </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> | |||
| </div> | |||
| <div class="card-body" data-id="member-container"> | |||
| @@ -86,6 +92,18 @@ | |||
| </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 class="card-footer"> | |||
| </div> | |||
| @@ -1,4 +1,4 @@ | |||
| <nav class="navbar fixed-top bg-gradient"> | |||
| <nav class="navbar fixed-bottom bg-gradient"> | |||
| <div class="container-fluid"> | |||
| <a class="navbar-brand" href="#/home"> | |||
| <!-- | |||
| @@ -46,7 +46,7 @@ | |||
| <% } %> | |||
| </div> | |||
| <div class="appointment-subject"> | |||
| <%= a.getSubject() %> | |||
| <%= a.getIcon() %> <%= a.getSubject() %> | |||
| <% if ( a.isDraft() ) { %> | |||
| <span class="badge badge-warning">Entwurf</span> | |||
| <% } %> | |||
| @@ -58,7 +58,8 @@ | |||
| <% displayedDateStart = mStart.format( 'ddd' ) + ' ' + mStart.format( 'D.M.' ) + ' ' + mStart.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> | |||
| <% if ( a.getVProfileCanAttend() ) { %> | |||
| @@ -1,4 +1,4 @@ | |||
| <form> | |||
| <form style="overflow: auto; overflow-x: hidden; max-height: 60vh"> | |||
| <div class="row"> | |||
| <div class="col"> | |||
| <div class="form-group"> | |||
| @@ -79,11 +79,17 @@ | |||
| <h4> | |||
| <%= currentMonth %> | |||
| <% 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> | |||
| </span> | |||
| <% } %> | |||
| @@ -110,6 +116,4 @@ | |||
| </div> | |||
| <% } %> | |||
| <%=raw app.core.View.getTemplate( 'home-pager', { pager : pager } ) %> | |||
| <% } %> | |||
| @@ -85,8 +85,9 @@ | |||
| </table> | |||
| </div> | |||
| /* todo: refactoring into css file */ | |||
| <style> | |||
| /* todo: refactoring into css file */ | |||
| /* spawntree */ | |||
| .button-group { | |||
| display: flex; | |||
| @@ -9,6 +9,7 @@ | |||
| <th>Postleitzahl</th> | |||
| <th>Stadt</th> | |||
| <th>Telefon/Mobile</th> | |||
| <th>Status</th> | |||
| <th></th> | |||
| </tr> | |||
| </thead> | |||
| @@ -22,6 +23,7 @@ | |||
| <td><%= members[ mi ].zip ? members[ mi ].zip : '' %></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 ].getGroupStatusText() %></td> | |||
| <td class="text-center"> | |||
| <button type="button" | |||
| data-type="btn-edit-memeber" | |||
| @@ -102,6 +102,30 @@ const UserProfile = { | |||
| } | |||
| 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() | |||
| { | |||
| var gd = this.getGroupData( this.contextGroupId ), | |||
| @@ -23,7 +23,8 @@ const AppointmentCalendar = { | |||
| 'Team', | |||
| 'getDetails', | |||
| { | |||
| teamId : p.groupId | |||
| teamId : p.groupId, | |||
| includeMembers: true | |||
| }, | |||
| function( res ) | |||
| { | |||
| @@ -25,7 +25,8 @@ const AppointmentList = { | |||
| 'Team', | |||
| 'getDetails', | |||
| { | |||
| teamId : groupId | |||
| teamId : groupId, | |||
| includeMembers: true | |||
| }, | |||
| function( res ) | |||
| { | |||
| @@ -40,7 +40,8 @@ const ContractCharging = { | |||
| 'Team', | |||
| 'getDetails', | |||
| { | |||
| teamId : groupId | |||
| teamId : groupId, | |||
| includeMembers: true | |||
| }, | |||
| function( res ) | |||
| { | |||
| @@ -25,7 +25,8 @@ const ContractCreate = { | |||
| 'Team', | |||
| 'getDetails', | |||
| { | |||
| teamId : groupId | |||
| teamId : groupId, | |||
| includeMembers: true | |||
| }, | |||
| function( res ) | |||
| { | |||
| @@ -27,7 +27,8 @@ const CoronaSpecial = { | |||
| 'Team', | |||
| 'getDetails', | |||
| { | |||
| teamId : groupId | |||
| teamId : groupId, | |||
| includeMembers: true | |||
| }, | |||
| function( res ) | |||
| { | |||
| @@ -44,7 +44,8 @@ const EmployeeList = { | |||
| 'Team', | |||
| 'getDetails', | |||
| { | |||
| teamId : p.get( 'groupId' ) | |||
| teamId : p.get( 'groupId' ), | |||
| includeMembers: true | |||
| }, | |||
| function( res ) | |||
| { | |||
| @@ -22,7 +22,9 @@ const MemberList = { | |||
| 'Team', | |||
| 'getDetails', | |||
| { | |||
| teamId : p.get( 'groupId' ) | |||
| teamId : p.get( 'groupId' ), | |||
| includeMembers: true | |||
| }, | |||
| function( res ) | |||
| { | |||
| @@ -39,7 +41,7 @@ const MemberList = { | |||
| this.createComponent( | |||
| 'member-data-table', | |||
| $container.find( '[f-id="container-member-data-table"]' ).first().get( 0 ), | |||
| members | |||
| members, | |||
| ); | |||
| } | |||
| $container.find( '.sk-loading' ).toggleClass( 'sk-loading' ); | |||
| @@ -26,7 +26,8 @@ const SettingsAccess = { | |||
| 'Team', | |||
| 'getDetails', | |||
| { | |||
| teamId : groupId | |||
| teamId : groupId, | |||
| includeMembers: true | |||
| }, | |||
| 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 | |||
| 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() | |||
| { | |||
| @@ -1,7 +1,7 @@ | |||
| <?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() | |||
| { | |||
| @@ -1,7 +1,7 @@ | |||
| <?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() | |||
| { | |||
| @@ -9,7 +9,7 @@ function patch() | |||
| $db = TB_Shared_Db_TeamData::get(); | |||
| $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 ); | |||
| $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 ) { | |||
| 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 | |||
| 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.user', '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.user', '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' ); | |||
| // 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 | |||
| ********************************************************************************/ | |||
| require Francis_Utils_Config::get( 'path.root' ).'/vendor/autoload.php'; | |||
| use Ramsey\Uuid\Uuid; | |||
| /** | |||
| * Class TB_Server_Control_Account | |||
| */ | |||
| @@ -23,63 +28,67 @@ class TB_Server_Control_Account | |||
| $resp->addData( 'profile', $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; | |||
| } | |||
| public static function delete( TB_Server_Core_RequestData $params ) | |||
| { | |||
| throw new \Exception( 'Does not work anymore.' ); | |||
| /* | |||
| $resp = new TB_Server_Core_Response(); | |||
| $session = TB_Server_Core_Session::get(); | |||
| if ( is_null( $session ) ) { | |||
| throw new Exception( "No session found. Deletion failed." ); | |||
| } | |||
| $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(); | |||
| $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() ) | |||
| { | |||
| 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; | |||
| */ | |||
| } | |||
| /** | |||
| @@ -25,7 +25,7 @@ class TB_Server_Control_Appointment { | |||
| { | |||
| throw new \Exception( 'No team id specified.' ); | |||
| } | |||
| $icon = _xss( $params->get( 'icon' ) ); | |||
| $sessionProfile = TB_Server_Core_Session::get()->getProfile(); | |||
| if ( false === $sessionProfile->isAdminOfTeam( $forTeamId ) ) | |||
| { | |||
| @@ -68,11 +68,15 @@ class TB_Server_Control_Appointment { | |||
| } | |||
| // Link replacements | |||
| if ( 0 === mb_strlen( $comment ) ) | |||
| { | |||
| $comment = NULL; | |||
| if (empty($comment) || !is_string($comment)) { | |||
| $comment = null; | |||
| } 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 ); | |||
| @@ -121,6 +125,7 @@ class TB_Server_Control_Appointment { | |||
| $appointment = new TB_Shared_Ent_TeamData_Appointment(); | |||
| $appointment->team_id = $forTeamId; | |||
| $appointment->icon = $icon; | |||
| $appointment->category_ids_js = $categoryIds; | |||
| $appointment->visibility = $visibility; | |||
| $appointment->subject = _xss( $params->get( 'subject' ) ); | |||
| @@ -409,19 +414,19 @@ class TB_Server_Control_Appointment { | |||
| $comment = _xss( $params->get( 'comment' ) ); | |||
| if ( 0 === mb_strlen( $comment ) ) | |||
| { | |||
| $comment = NULL; | |||
| } | |||
| // Link replacements | |||
| if ( 0 === mb_strlen( $comment ) ) | |||
| { | |||
| $comment = NULL; | |||
| if (empty($comment) || !is_string($comment)) { | |||
| $comment = null; | |||
| } 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 ); | |||
| 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->category_ids_js = $categoryIds; | |||
| $appointment->subject = _xss( $params->get( 'subject' ) ); | |||
| $appointment->icon = _xss( $params->get( 'icon' ) ); | |||
| $appointment->start_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.' ); | |||
| } | |||
| if ( false === $appointment->isAttendableByProfile( $sessionProfile ) ) | |||
| $isAttendableByProfile = $appointment->isAttendableByProfile( $sessionProfile ); | |||
| if ( false === $isAttendableByProfile ) | |||
| { | |||
| 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::getByAppointmentId( $appointment->id ); | |||
| @@ -691,7 +691,7 @@ class TB_Server_Control_Auth | |||
| $team->category = _xss( $teamCategory ); | |||
| $team->affiliate_id = is_string( $affiliateId ) ? _xss( $affiliateId ) : NULL; | |||
| $team->terms_conditions_active = 0; | |||
| $team->new_users_inactive = 1; | |||
| $team->new_users_inactive = 0; | |||
| $team->save(); | |||
| // Set default course categories | |||
| @@ -83,6 +83,7 @@ class TB_Server_Control_Team { | |||
| $includeEmail = $params->get( 'includeEmails' ); | |||
| $adminsOnly = $params->get( 'adminsOnly' ); | |||
| $includeMembers = $params->get( 'includeMembers' ); | |||
| $activeOnly = $params->get( 'activeOnly'); | |||
| $members = array(); | |||
| $sessionProfile = TB_Server_Core_Session::get()->getProfile(); | |||
| @@ -113,7 +114,27 @@ class TB_Server_Control_Team { | |||
| $filteredMembers[] = $member; | |||
| } | |||
| } 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 */ | |||
| 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 ); | |||
| @@ -161,6 +185,7 @@ class TB_Server_Control_Team { | |||
| $resp = new TB_Server_Core_Response(); | |||
| $teamId = $params->get( 'teamId' ); | |||
| $activeOnly = $params->get( 'activeOnly'); | |||
| $sessionProfile = TB_Server_Core_Session::get()->getProfile(); | |||
| if ( false === $sessionProfile->isInTeam( $teamId ) ) | |||
| @@ -176,7 +201,7 @@ class TB_Server_Control_Team { | |||
| 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; | |||
| } | |||
| @@ -8,6 +8,7 @@ | |||
| * | |||
| * @property integer $id | |||
| * @property integer $team_id | |||
| * @property string $icon | |||
| * @property string $category_id | |||
| * @property array $category_ids_js | |||
| * @property string $visibility | |||
| @@ -139,8 +140,11 @@ class TB_Shared_Ent_TeamData_Appointment extends Francis_Db_Row | |||
| * @param $profileId | |||
| * @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 .= 'LEFT JOIN attendee ON appointment.id = attendee.appointment_id '; | |||
| $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 . '" '; | |||
| 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 ) ); | |||
| } | |||
| @@ -40,6 +40,7 @@ class TB_Shared_Ent_TeamData_Profile extends Francis_Db_Row { | |||
| const STATUS_NOT_APPROVED = 'not_approved'; | |||
| const STATUS_ACTIVE = 'active'; | |||
| const STATUS_INACTIVE = 'inactive'; | |||
| const STATUS_DELETED = 'deleted'; | |||
| // Enums | |||
| const GENDER_MALE = 'male'; | |||
| @@ -74,7 +75,8 @@ class TB_Shared_Ent_TeamData_Profile extends Francis_Db_Row { | |||
| return array( | |||
| self::STATUS_ACTIVE, | |||
| 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 | |||
| * @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 .= 'JSON_CONTAINS( `teams_js`, :where_json ) '; | |||
| $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 | |||