├── .code-generation └── config.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ └── test.yaml ├── .gitignore ├── .htaccess ├── CHANGELOG.md ├── CONTRIBUTING.md ├── DEVELOPER_GUIDELINE.md ├── LICENSE ├── README.md ├── composer.json ├── docs ├── CloudinaryFilter.php ├── Makefile ├── sami_config.php └── themes │ └── cloudinary │ ├── class.twig │ ├── css │ └── cloudinary.css │ ├── index.twig │ ├── layout │ ├── base.twig │ └── layout.twig │ ├── macros.twig │ ├── manifest.yml │ └── sami.js.twig ├── phpcs.xml ├── phpstan.neon ├── phpunit.xml ├── samples ├── SamplePage │ ├── Sample │ │ ├── Sample.php │ │ ├── Tabs │ │ │ ├── BaseTab.php │ │ │ ├── CodeTab.php │ │ │ ├── TagTab.php │ │ │ └── TransformationTab.php │ │ ├── TagSample.php │ │ └── TransformationSample.php │ ├── SamplePage.php │ └── SamplePageUtils.php ├── edit-me.php ├── styles.css ├── tag-samples.php └── transformation-samples.php ├── src ├── Api │ ├── Admin │ │ ├── AdminApi.php │ │ ├── AnalysisTrait.php │ │ ├── ApiEndPoint.php │ │ ├── AssetsTrait.php │ │ ├── FoldersTrait.php │ │ ├── MetadataFieldsTrait.php │ │ ├── MiscTrait.php │ │ ├── StreamingProfilesTrait.php │ │ ├── TransformationsTrait.php │ │ ├── UploadMappingsTrait.php │ │ └── UploadPresetsTrait.php │ ├── ApiClient.php │ ├── ApiResponse.php │ ├── BaseApiClient.php │ ├── Exception │ │ ├── AlreadyExists.php │ │ ├── ApiError.php │ │ ├── AuthorizationRequired.php │ │ ├── BadRequest.php │ │ ├── GeneralError.php │ │ ├── NotAllowed.php │ │ ├── NotFound.php │ │ └── RateLimited.php │ ├── Metadata │ │ ├── DateMetadataField.php │ │ ├── EnumMetadataField.php │ │ ├── IntMetadataField.php │ │ ├── Metadata.php │ │ ├── MetadataDataEntry.php │ │ ├── MetadataDataSource.php │ │ ├── MetadataField.php │ │ ├── MetadataFieldList.php │ │ ├── MetadataFieldType.php │ │ ├── SetMetadataField.php │ │ ├── StringMetadataField.php │ │ └── Validators │ │ │ ├── AndValidator.php │ │ │ ├── ComparisonRule.php │ │ │ ├── DateGreaterThan.php │ │ │ ├── DateLessThan.php │ │ │ ├── IntGreaterThan.php │ │ │ ├── IntLessThan.php │ │ │ ├── MetadataValidation.php │ │ │ └── StringLength.php │ ├── Provisioning │ │ ├── AccountApi.php │ │ ├── AccountApiClient.php │ │ ├── AccountEndPoint.php │ │ └── UserRole.php │ ├── Search │ │ ├── SearchApi.php │ │ ├── SearchFoldersApi.php │ │ ├── SearchQueryInterface.php │ │ └── SearchQueryTrait.php │ ├── Upload │ │ ├── ArchiveTrait.php │ │ ├── ContextCommand.php │ │ ├── ContextTrait.php │ │ ├── CreativeTrait.php │ │ ├── EditTrait.php │ │ ├── TagCommand.php │ │ ├── TagTrait.php │ │ ├── UploadApi.php │ │ ├── UploadEndPoint.php │ │ └── UploadTrait.php │ ├── UploadApiClient.php │ └── Utils │ │ ├── ApiUtils.php │ │ ├── HttpMethod.php │ │ └── HttpStatusCode.php ├── Asset │ ├── AccessControl │ │ ├── AccessControlRule.php │ │ └── AccessType.php │ ├── Analytics │ │ └── Analytics.php │ ├── AssetFinalizerTrait.php │ ├── AssetInterface.php │ ├── AssetQualifiers.php │ ├── AuthToken.php │ ├── BaseAsset.php │ ├── BaseMediaAsset.php │ ├── DeliveryTypeTrait.php │ ├── Descriptor │ │ ├── AssetDescriptor.php │ │ ├── AssetDescriptorTrait.php │ │ ├── AssetTransformation.php │ │ ├── AssetType.php │ │ └── DeliveryType.php │ ├── File.php │ ├── Image.php │ ├── Media.php │ ├── MediaAssetFinalizerTrait.php │ ├── Moderation │ │ ├── ModerationStatus.php │ │ └── ModerationType.php │ ├── SearchAsset.php │ ├── SearchAssetTrait.php │ └── Video.php ├── Cloudinary.php ├── Configuration │ ├── ApiConfig.php │ ├── AssetConfigTrait.php │ ├── AuthTokenConfig.php │ ├── BaseConfigSection.php │ ├── CloudConfig.php │ ├── CloudConfigTrait.php │ ├── ConfigUtils.php │ ├── ConfigurableInterface.php │ ├── Configuration.php │ ├── LoggingConfig.php │ ├── Provisioning │ │ ├── ProvisioningAccountConfig.php │ │ ├── ProvisioningConfigUtils.php │ │ └── ProvisioningConfiguration.php │ ├── ResponsiveBreakpointsConfig.php │ ├── TagConfig.php │ ├── TagConfigTrait.php │ ├── UrlConfig.php │ └── UrlConfigTrait.php ├── Exception │ ├── ConfigurationException.php │ └── Error.php ├── HttpClient │ └── HttpClient.php ├── Log │ ├── Logger.php │ ├── LoggerDecorator.php │ ├── LoggerDecoratorTrait.php │ ├── LoggerTrait.php │ └── LoggersList.php ├── Tag │ ├── Attribute │ │ ├── AudioSourceType.php │ │ ├── Media.php │ │ ├── Sizes.php │ │ ├── SourceType.php │ │ ├── SrcSet.php │ │ └── VideoSourceType.php │ ├── BaseConfigurableApiTag.php │ ├── BaseImageTag.php │ ├── BaseTag.php │ ├── ClientHintsMetaTag.php │ ├── FormInputTag.php │ ├── FormTag.php │ ├── ImageTag.php │ ├── ImageTagDeliveryTypeTrait.php │ ├── JsConfigTag.php │ ├── PictureSourceTag.php │ ├── PictureTag.php │ ├── SpriteTag.php │ ├── Tag.php │ ├── TagBuilder.php │ ├── TagUtils.php │ ├── UploadTag.php │ ├── VideoSourceTag.php │ ├── VideoTag.php │ ├── VideoTagDeliveryTypeTrait.php │ └── VideoThumbnailTag.php └── Utils │ ├── FileUtils.php │ ├── SignatureVerifier.php │ └── Utils.php ├── tests ├── CloudinaryTestCase.php ├── Helpers │ ├── Addon.php │ ├── Feature.php │ ├── MockAccountApi.php │ ├── MockAccountApiClient.php │ ├── MockAdminApi.php │ ├── MockAnalytics.php │ ├── MockApiClient.php │ ├── MockApiClientTrait.php │ ├── MockApiTrait.php │ ├── MockSearchApi.php │ ├── MockSearchFoldersApi.php │ ├── MockUploadApi.php │ ├── MockUploadApiClient.php │ └── RequestAssertionsTrait.php ├── Integration │ ├── Admin │ │ ├── AdaptiveStreamingProfilesTest.php │ │ ├── AssetTest.php │ │ ├── Assets │ │ │ ├── AssetsMetadataTest.php │ │ │ ├── DeleteAssetsTest.php │ │ │ ├── ListAssetsTest.php │ │ │ └── RestoreAssetsTest.php │ │ ├── FoldersTest.php │ │ ├── MetadataFieldsTest.php │ │ ├── MiscTest.php │ │ ├── OAuthTest.php │ │ ├── TagsTest.php │ │ ├── TransformationsTest.php │ │ ├── UploadMappingsTest.php │ │ ├── UploadPresetsTest.php │ │ └── UsageTest.php │ ├── IntegrationTestCase.php │ ├── Provisioning │ │ ├── ProvisioningAccountTest.php │ │ ├── ProvisioningIntegrationTestCase.php │ │ ├── ProvisioningSubAccountTest.php │ │ └── ProvisioningUserGroupTest.php │ ├── Search │ │ └── SearchApiTest.php │ └── Upload │ │ ├── ArchiveTest.php │ │ ├── ContextTest.php │ │ ├── CreativeTest.php │ │ ├── EditTest.php │ │ ├── TagTest.php │ │ └── UploadApiTest.php ├── Unit │ ├── Admin │ │ ├── AdminApiTest.php │ │ ├── AnalysisTest.php │ │ ├── AssetsTest.php │ │ ├── FoldersTest.php │ │ ├── MetadataFieldsTest.php │ │ └── OAuthTest.php │ ├── ApiClientTest.php │ ├── Archive │ │ └── ArchiveTest.php │ ├── Asset │ │ ├── AnalyticsTest.php │ │ ├── AssetAuthTokenTest.php │ │ ├── AssetTestCase.php │ │ ├── AssetTransformationTest.php │ │ ├── AuthTokenTest.php │ │ ├── AuthTokenTestCase.php │ │ ├── DistributionTest.php │ │ ├── FileTest.php │ │ ├── ImageTest.php │ │ ├── MediaFromParamsTest.php │ │ ├── MediaTest.php │ │ ├── SearchAssetTest.php │ │ └── VideoTest.php │ ├── Cloudinary │ │ ├── CloudinaryAssetTest.php │ │ ├── CloudinaryTagTest.php │ │ └── CloudinaryTest.php │ ├── Configuration │ │ ├── CloudConfigTest.php │ │ ├── ConfigUtilsTest.php │ │ ├── ConfigurationTest.php │ │ ├── Provisioning │ │ │ └── ConfigurationAccountTest.php │ │ └── SensitiveKeysSerializationTest.php │ ├── HttpClientTest.php │ ├── Logger │ │ └── LoggerTest.php │ ├── Provisioning │ │ ├── AccessKeysTest.php │ │ └── ProvisioningUnitTestCase.php │ ├── Search │ │ └── SearchApiTest.php │ ├── Tag │ │ ├── ClientHintsMetaTagTest.php │ │ ├── FormInputTagTest.php │ │ ├── FormTagTest.php │ │ ├── ImageTagTest.php │ │ ├── ImageTagTestCase.php │ │ ├── JsConfigTagTest.php │ │ ├── Patterns │ │ │ └── FormTagPatterns.php │ │ ├── PictureSourceTagTest.php │ │ ├── PictureTagTest.php │ │ ├── Responsive │ │ │ ├── GetBreakpointsTest.php │ │ │ └── SrcSetAttributeTest.php │ │ ├── SpriteTagTest.php │ │ ├── TagFromParamsTest.php │ │ ├── TagTest.php │ │ ├── TagTestCase.php │ │ ├── TestTag.php │ │ ├── UploadTagTest.php │ │ ├── VideoTagTest.php │ │ └── VideoThumbnailTagTest.php │ ├── TestHelpers │ │ ├── TestClassA.php │ │ ├── TestClassB.php │ │ └── TestLogger.php │ ├── UnitTestCase.php │ ├── Upload │ │ ├── CreateSlideshowTest.php │ │ ├── OAuthTest.php │ │ └── UploadApiTest.php │ └── Utils │ │ ├── ApiUtilsTest.php │ │ ├── FileUtilsTest.php │ │ ├── SignatureVerifierTest.php │ │ └── UtilsTest.php └── assets │ ├── sample.docx │ ├── sample.gif │ ├── sample.ico │ ├── sample.mp4 │ └── sample.png └── tools ├── allocate_test_cloud.sh ├── get_test_cloud.sh └── update_version.sh /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Bug report for Cloudinary PHP SDK 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug report for Cloudinary PHP SDK 11 | Before proceeding, please update to latest version and test if the issue persists 12 | 13 | ## Describe the bug in a sentence or two. 14 | … 15 | 16 | ## Issue Type (Can be multiple) 17 | - [ ] Build - Can’t install or import the SDK 18 | - [ ] Performance - Performance issues 19 | - [ ] Behaviour - Functions aren’t working as expected (such as generate URL) 20 | - [ ] Documentation - Inconsistency between the docs and behaviour 21 | - [ ] Other (Specify) 22 | 23 | ## Steps to reproduce 24 | … if applicable 25 | 26 | ## Error screenshots or Stack Trace (if applicable) 27 | … 28 | 29 | ## Operating System 30 | - [ ] Linux 31 | - [ ] Windows 32 | - [ ] macOS 33 | - [ ] All 34 | 35 | ## Environment and Frameworks (fill in the version numbers) 36 | 37 | - PHP Cloudinary SDK version - 0.0.0 38 | - PHP Version - 0.0.0 39 | - Framework (Laravel, Symphony, etc) - 0.0.0 40 | 41 | ## Repository 42 | 43 | If possible, please provide a link to a reproducible repository that showcases the problem 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Feature request for Cloudinary PHP SDK 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Feature request for Cloudinary PHP SDK 11 | …(If your feature is for other SDKs, please request them there) 12 | 13 | 14 | ## Explain your use case 15 | … (A high level explanation of why you need this feature) 16 | 17 | ## Describe the problem you’re trying to solve 18 | … (A more technical view of what you’d like to accomplish, and how this feature will help you achieve it) 19 | 20 | ## Do you have a proposed solution? 21 | … (yes, no? Please elaborate if needed) 22 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Brief Summary of Changes 2 | 3 | 4 | #### What does this PR address? 5 | - [ ] GitHub issue (Add reference - #XX) 6 | - [ ] Refactoring 7 | - [ ] New feature 8 | - [ ] Bug fix 9 | - [ ] Adds more tests 10 | 11 | #### Are tests included? 12 | - [ ] Yes 13 | - [ ] No 14 | 15 | #### Reviewer, please note: 16 | 23 | 24 | #### Checklist: 25 | 26 | 27 | - [ ] My code follows the code style of this project. 28 | - [ ] My change requires a change to the documentation. 29 | - [ ] I ran the full test suite before pushing the changes and all of the tests pass. 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push, pull_request] 3 | jobs: 4 | php: 5 | name: PHP ${{ matrix.php-versions }} 6 | runs-on: ubuntu-latest 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | php-versions: 11 | - '8.0' 12 | - '8.1' 13 | - '8.2' 14 | - '8.3' 15 | - '8.4' 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | - name: Setup PHP, with composer and extensions 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: ${{ matrix.php-versions }} 23 | extensions: json 24 | coverage: xdebug 25 | - name: Install Composer dependencies 26 | run: composer update -n 27 | - name: Run Tests 28 | run: | 29 | export CLOUDINARY_URL=$(bash tools/get_test_cloud.sh); 30 | echo cloud_name: "$(echo $CLOUDINARY_URL | cut -d'@' -f2)" 31 | vendor/bin/simple-phpunit 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | tests/coverage 4 | output/ 5 | .idea 6 | composer.phar 7 | docs/sami.phar 8 | docs/cache/ 9 | docs/build/ 10 | tools/dev/sanity/node_modules 11 | tools/dev/sanity/package-lock.json 12 | tools/dev/sanity/results.json 13 | tools/dev/sanity/TransformationSanityTest.php 14 | /.phpunit.result.cache 15 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | deny from all 3 | 4 | -------------------------------------------------------------------------------- /DEVELOPER_GUIDELINE.md: -------------------------------------------------------------------------------- 1 | # Developing Cloudinary PHP 2 | 3 | ## Code style 4 | 5 | ### Recommended Standards 6 | All code should follow the following standards: 7 | - [PSR-1](http://www.php-fig.org/psr/psr-1/) 8 | - [PSR-2](http://www.php-fig.org/psr/psr-2/) 9 | - [PSR-3 _Recommended_](http://www.php-fig.org/psr/psr-3/) 10 | - [PSR-5 _Recommended_](https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md) 11 | - [PSR-12](https://www.php-fig.org/psr/psr-12/) 12 | 13 | ### PHP Code Size Control 14 | All code should meet default configuration of [PHPMD](https://phpmd.org/rules/codesize.html) 15 | 16 | ## Tests Coverage 17 | All code must be covered by tests using [PHPUnit](https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html) 18 | For functional tests unique IDs should be used and after test is done all data from remote server should be removed. 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cloudinary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudinary/cloudinary_php", 3 | "version": "3.1.0", 4 | "description": "Cloudinary PHP SDK", 5 | "keywords": [ 6 | "cloudinary", 7 | "sdk", 8 | "cloud", 9 | "image management", 10 | "cdn" 11 | ], 12 | "type": "library", 13 | "homepage": "https://github.com/cloudinary/cloudinary_php", 14 | "minimum-stability": "stable", 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "Cloudinary", 19 | "homepage": "https://github.com/cloudinary/cloudinary_php/graphs/contributors" 20 | } 21 | ], 22 | "support": { 23 | "email": "info@cloudinary.com", 24 | "issues": "https://github.com/cloudinary/cloudinary_php/issues" 25 | }, 26 | "require": { 27 | "php": ">=8.0.0", 28 | "cloudinary/transformation-builder-sdk": "^2", 29 | "guzzlehttp/guzzle": "^7.4.5", 30 | "guzzlehttp/promises": "^1.5.3|^2.0", 31 | "guzzlehttp/psr7": "^2.7", 32 | "ext-json": "*", 33 | "monolog/monolog": "^2|^3", 34 | "psr/log" : "^2|^3" 35 | }, 36 | "require-dev": { 37 | "symfony/phpunit-bridge": "^7.2", 38 | "phpmd/phpmd": "*", 39 | "squizlabs/php_codesniffer": "*", 40 | "friendsofphp/php-cs-fixer": "*", 41 | "ext-dom": "*", 42 | "ext-libxml": "*", 43 | "ext-zip": "*" 44 | }, 45 | "autoload": { 46 | "classmap": [ 47 | "src" 48 | ] 49 | }, 50 | "autoload-dev": { 51 | "psr-4": { 52 | "Cloudinary\\Test\\": "tests" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/CloudinaryFilter.php: -------------------------------------------------------------------------------- 1 | getTags(self::API); 35 | } 36 | 37 | /** 38 | * Accept only public methods that are not tagged '@internal'. 39 | * 40 | * @param MethodReflection $method The method reflection object. 41 | * 42 | * @return bool 43 | */ 44 | public function acceptMethod(MethodReflection $method) 45 | { 46 | return $method->isPublic() && ! $method->getTags(self::INTERNAL); 47 | } 48 | 49 | /** 50 | * Accept only public properties that are not tagged '@internal'. 51 | * 52 | * @param PropertyReflection $property The property reflection object. 53 | * 54 | * @return bool 55 | */ 56 | public function acceptProperty(PropertyReflection $property) 57 | { 58 | return $property->isPublic() && ! $property->getTags(self::INTERNAL); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | CACHE_DIR = cache 2 | BUILD_DIR = build 3 | 4 | DOCS_TOOL = sami.phar 5 | 6 | PHP?=php 7 | 8 | CONFIG=sami_config.php 9 | 10 | .PHONY: all clean update 11 | 12 | all: update 13 | 14 | update: $(DOCS_TOOL) 15 | $(PHP) $(DOCS_TOOL) update $(CONFIG) 16 | 17 | $(DOCS_TOOL): 18 | curl -O https://cloudinary-res.cloudinary.com/raw/upload/docsite/sami.phar 19 | 20 | clean: 21 | @rm -rf $(CACHE_DIR) $(BUILD_DIR) 22 | -------------------------------------------------------------------------------- /docs/sami_config.php: -------------------------------------------------------------------------------- 1 | 'cloudinary', 15 | 'template_dirs' => [$docsDir . 'themes'], 16 | 'title' => 'Cloudinary PHP SDK', 17 | 'version' => '3.1.0', 18 | 'build_dir' => $docsDir . 'build', 19 | 'cache_dir' => $docsDir . 'cache', 20 | 'default_opened_level' => 1, 21 | 'filter' => new CloudinaryFilter(), 22 | ] 23 | ); 24 | -------------------------------------------------------------------------------- /docs/themes/cloudinary/index.twig: -------------------------------------------------------------------------------- 1 | {% extends "layout/layout.twig" %} 2 | 3 | {% block body_class 'index' %} 4 | 5 | {% block page_content %} 6 | 9 |
10 |

This reference provides details on all PHP SDK asset management namespaces and classes.

11 |

For details on the PHP SDK transformation namespaces and classes, 12 | see the PHP SDK Transformation Reference.

13 |
14 | 17 | {% if namespaces %} 18 |
19 | {% set last_name = '' %} 20 | {% for namespace in namespaces %} 21 | {% set top_level = namespace|split('\\')|first %} 22 | {% if top_level != last_name %} 23 | {% if last_name %}
{% endif %} 24 |
25 |

{{ top_level|raw }}

26 | 32 |
33 | 34 | {% endif %} 35 | 36 | {% endblock %} 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/themes/cloudinary/layout/base.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% block title project.config('title') %} 7 | 8 | {% block head %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% endblock %} 21 | 22 | 23 | 24 | {% if project.config('base_url') %} 25 | {%- for version in project.versions -%} 26 | 30 | {% endfor -%} 31 | {% endif %} 32 | 33 | 34 | {% block html %} 35 | 36 | {% block content '' %} 37 | 38 | {% endblock %} 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/themes/cloudinary/manifest.yml: -------------------------------------------------------------------------------- 1 | name: cloudinary 2 | parent: default 3 | static: 4 | 'css/cloudinary.css': 'css/cloudinary.css' 5 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | cloudinary_php coding standard 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | src 20 | tests 21 | 22 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 1 3 | paths: 4 | - src 5 | ignoreErrors: 6 | - '#Unsafe usage of new static\(\)#' 7 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./src 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | tests/Unit 15 | 16 | 17 | tests/Integration 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/SamplePage/Sample/Tabs/BaseTab.php: -------------------------------------------------------------------------------- 1 | title = $title; 30 | $this->text = $text; 31 | } 32 | 33 | public function getTabPill() 34 | { 35 | $class = 'url-tab nav-link'; 36 | if ($this->isFirst) { 37 | $class .= ' active'; 38 | } 39 | return ' 40 | 49 | '; 50 | } 51 | 52 | public function getTabContent() 53 | { 54 | return ''; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /samples/SamplePage/Sample/Tabs/CodeTab.php: -------------------------------------------------------------------------------- 1 | text = $code; 39 | $this->isFirst = true; 40 | $this->keepSpaces = $keepSpaces; 41 | } 42 | 43 | public function getTabContent() 44 | { 45 | return ' 46 |
47 |
51 | ' . getColoredPhp($this->text, $this->keepSpaces) . ' 52 |
53 | '; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /samples/SamplePage/Sample/Tabs/TagTab.php: -------------------------------------------------------------------------------- 1 | html = $html; 37 | } 38 | 39 | public function getTabContent() 40 | { 41 | // TODO: add getFormattedHtml() function to SamplePageUtils 42 | 43 | return ' 44 |
' . 49 | getFormattedHtml($this->html) . 50 | '
51 |
52 | '; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /samples/SamplePage/Sample/Tabs/TransformationTab.php: -------------------------------------------------------------------------------- 1 | transformation = $transformation; 51 | $this->publicId = $publicId; 52 | $this->text = $code; 53 | $this->type = $type; 54 | } 55 | 56 | public function getTabContent() 57 | { 58 | $url = $this->calculateUrl(); 59 | return ' 60 |
65 | ' . 66 | getFormattedUrl( 67 | $url, 68 | $this->transformation 69 | ) . 70 | ' 71 |
72 | 73 | '; 74 | } 75 | 76 | private function calculateUrl() 77 | { 78 | $tag = $this->type === 'video' ? Video::upload($this->publicId) : Image::upload($this->publicId); 79 | return $tag->toUrl($this->transformation); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /samples/SamplePage/Sample/TagSample.php: -------------------------------------------------------------------------------- 1 | tag = $tag; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | protected function createTabs() 46 | { 47 | return [ 48 | $this->createCodeTab(), 49 | new TagTab( 50 | $this->code, 51 | $this->tag 52 | ) 53 | ]; 54 | } 55 | 56 | protected function getExample() 57 | { 58 | return $this->getExampleHtml($this->getUrl(), $this->tag); 59 | } 60 | 61 | protected function getUrl() 62 | { 63 | // TODO: return actual url from tag 64 | return 'http://cloudinary.com'; 65 | } 66 | 67 | /** 68 | * @param string $url 69 | * @param BaseTag $tag 70 | * 71 | * @return string 72 | */ 73 | protected function getExampleHtml($url = '', $tag = null) 74 | { 75 | return ' 76 |
' . 77 | $tag->addClass('z - depth - 1')->setAttribute('style', 'margin:auto; max-width: 900px;') . ' 78 |
79 | 80 | '; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Api/Admin/AdminApi.php: -------------------------------------------------------------------------------- 1 | 21 | * Admin API Reference 22 | * 23 | * @api 24 | */ 25 | class AdminApi 26 | { 27 | use AssetsTrait; 28 | use FoldersTrait; 29 | use TransformationsTrait; 30 | use StreamingProfilesTrait; 31 | use UploadPresetsTrait; 32 | use UploadMappingsTrait; 33 | use MiscTrait; 34 | use MetadataFieldsTrait; 35 | use AnalysisTrait; 36 | 37 | /** 38 | * @var ApiClient $apiClient The API client instance. 39 | */ 40 | protected $apiClient; 41 | 42 | /** 43 | * @var ApiClient $apiV2Client The API v2 client instance. 44 | */ 45 | protected $apiV2Client; 46 | 47 | /** 48 | * AdminApi constructor. 49 | * 50 | * @param mixed|null $configuration 51 | * 52 | * @noinspection UnusedConstructorDependenciesInspection*/ 53 | public function __construct(mixed $configuration = null) 54 | { 55 | $this->apiClient = new ApiClient($configuration); 56 | 57 | $apiV2Configuration = new Configuration($configuration); 58 | $apiV2Configuration->api->apiVersion = '2'; 59 | 60 | $this->apiV2Client = new ApiClient($apiV2Configuration); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Api/Admin/AnalysisTrait.php: -------------------------------------------------------------------------------- 1 | analyzeAsync($inputType, $analysisType, $uri, $parameters)->wait(); 47 | } 48 | 49 | /** 50 | * Analyzes an asset with the requested analysis type asynchronously. 51 | * 52 | * @param string $inputType The type of input for the asset to analyze ('uri'). 53 | * @param string $analysisType The type of analysis to run ('google_tagging', 'captioning', 'fashion'). 54 | * @param string|null $uri The URI of the asset to analyze. 55 | * @param array|null $parameters Additional parameters. 56 | * 57 | * @see https://cloudinary.com/documentation/media_analyzer_api_reference 58 | */ 59 | public function analyzeAsync( 60 | string $inputType, 61 | string $analysisType, 62 | ?string $uri = null, 63 | ?array $parameters = null 64 | ): PromiseInterface { 65 | $endPoint = [ApiEndPoint::ANALYSIS, 'analyze', $inputType]; 66 | 67 | $params = ['analysis_type' => $analysisType, 'uri' => $uri, 'parameters' => $parameters]; 68 | 69 | return $this->apiV2Client->postJsonAsync($endPoint, $params); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Api/Admin/ApiEndPoint.php: -------------------------------------------------------------------------------- 1 | headers = $headers; 58 | // According to RFC 2616, header names are case-insensitive. 59 | $lcHeaders = array_change_key_case($headers); 60 | 61 | $this->rateLimitResetAt = strtotime(ArrayUtils::get($lcHeaders, ['x-featureratelimit-reset', 0], 0)); 62 | $this->rateLimitAllowed = (int)ArrayUtils::get($lcHeaders, ['x-featureratelimit-limit', 0], 0); 63 | $this->rateLimitRemaining = (int)ArrayUtils::get($lcHeaders, ['x-featureratelimit-remaining', 0], 0); 64 | 65 | parent::__construct($responseJson); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Api/Exception/AlreadyExists.php: -------------------------------------------------------------------------------- 1 | type = MetadataFieldType::DATE; 32 | } 33 | 34 | /** 35 | * Sets the default date for this field. 36 | * 37 | * @param mixed $defaultValue The date to set. 38 | */ 39 | public function setDefaultValue(mixed $defaultValue): void 40 | { 41 | $this->defaultValue = Utils::toISO8601DateOnly($defaultValue); 42 | } 43 | 44 | /** 45 | * Gets the default date of this field. 46 | * 47 | * @throws Exception When the underlying value is malformed. 48 | */ 49 | public function getDefaultValue(): ?DateTime 50 | { 51 | return $this->defaultValue ? new DateTime($this->defaultValue) : null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Api/Metadata/EnumMetadataField.php: -------------------------------------------------------------------------------- 1 | type = MetadataFieldType::ENUM; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Api/Metadata/IntMetadataField.php: -------------------------------------------------------------------------------- 1 | type = MetadataFieldType::INTEGER; 28 | } 29 | 30 | /** 31 | * Sets the default value for this field. 32 | * 33 | */ 34 | public function setDefaultValue(mixed $defaultValue): void 35 | { 36 | $this->defaultValue = (int)$defaultValue; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Api/Metadata/Metadata.php: -------------------------------------------------------------------------------- 1 | getPropertyKeys(); 39 | 40 | $snakeCaseProperties = []; 41 | foreach ($propertyKeys as $key) { 42 | $value = $this->{$key} ?? null; 43 | if ($value === null) { 44 | continue; 45 | } 46 | if (is_object($value)) { 47 | if (method_exists($value, 'jsonSerialize')) { 48 | $snakeCaseProperties[StringUtils::camelCaseToSnakeCase($key)] = $value->jsonSerialize(); 49 | } 50 | } elseif (is_array($value) && !ArrayUtils::isAssoc($value)) { 51 | $subArray = []; 52 | foreach ($value as $subArrayValue) { 53 | if (method_exists($subArrayValue, 'jsonSerialize')) { 54 | $subArray[] = $subArrayValue->jsonSerialize(); 55 | } else { 56 | $subArray[] = $subArrayValue; 57 | } 58 | } 59 | $snakeCaseProperties[StringUtils::camelCaseToSnakeCase($key)] = $subArray; 60 | } else { 61 | $snakeCaseProperties[StringUtils::camelCaseToSnakeCase($key)] = $value; 62 | } 63 | } 64 | 65 | return $snakeCaseProperties; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Api/Metadata/MetadataDataEntry.php: -------------------------------------------------------------------------------- 1 | setValue($value); 33 | $this->setExternalId($externalId); 34 | } 35 | 36 | /** 37 | * Gets the keys for all the properties of this object. 38 | * 39 | */ 40 | public function getPropertyKeys(): array 41 | { 42 | return ['externalId', 'value']; 43 | } 44 | 45 | /** 46 | * Gets the value of the entry. 47 | * 48 | */ 49 | public function getValue(): string 50 | { 51 | return $this->value; 52 | } 53 | 54 | /** 55 | * Sets the value of the entry. 56 | * 57 | */ 58 | public function setValue(string $value): void 59 | { 60 | if (is_null($value)) { 61 | throw new InvalidArgumentException('Metadata data entry value is not valid'); 62 | } 63 | $this->value = $value; 64 | } 65 | 66 | /** 67 | * Gets the ID of the entry. 68 | * 69 | */ 70 | public function getExternalId(): string 71 | { 72 | return $this->externalId; 73 | } 74 | 75 | /** 76 | * Sets the ID of the entry. Will be auto-generated if left blank. 77 | * 78 | * @param ?string $externalId The external ID. 79 | */ 80 | public function setExternalId(?string $externalId): void 81 | { 82 | $this->externalId = $externalId; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Api/Metadata/MetadataDataSource.php: -------------------------------------------------------------------------------- 1 | setValues($values); 37 | } 38 | 39 | /** 40 | * Gets the keys for all the properties of this object. 41 | * 42 | */ 43 | public function getPropertyKeys(): array 44 | { 45 | return ['values']; 46 | } 47 | 48 | /** 49 | * Sets entities for this data source. 50 | * 51 | * @param MetadataDataEntry[]|array[] $values 52 | * 53 | * @throws InvalidArgumentException 54 | */ 55 | public function setValues(array $values): void 56 | { 57 | $this->values = []; 58 | foreach ($values as $value) { 59 | if ($value instanceof MetadataDataEntry) { 60 | $this->values[] = $value; 61 | } elseif (is_array($value) && isset($value['value'])) { 62 | $this->values[] = new MetadataDataEntry( 63 | $value['value'], 64 | $value['external_id'] ?? null 65 | ); 66 | } else { 67 | throw new InvalidArgumentException('The specified metadata datasource values are not valid.'); 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * Gets entities of this data source. 74 | * 75 | * @return MetadataDataEntry[] 76 | */ 77 | public function getValues(): array 78 | { 79 | return $this->values; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Api/Metadata/MetadataFieldType.php: -------------------------------------------------------------------------------- 1 | type = MetadataFieldType::SET; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Api/Metadata/StringMetadataField.php: -------------------------------------------------------------------------------- 1 | type = MetadataFieldType::STRING; 27 | } 28 | 29 | /** 30 | * Sets the default value. 31 | * 32 | */ 33 | public function setDefaultValue(mixed $defaultValue): void 34 | { 35 | $this->defaultValue = (string)$defaultValue; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Api/Metadata/Validators/AndValidator.php: -------------------------------------------------------------------------------- 1 | type = self::RULE_AND; 35 | $this->rules = $rules; 36 | } 37 | 38 | /** 39 | * Gets the keys for all the properties of this object. 40 | * 41 | * @return string[] 42 | */ 43 | public function getPropertyKeys(): array 44 | { 45 | return array_merge(parent::getPropertyKeys(), ['rules']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Api/Metadata/Validators/ComparisonRule.php: -------------------------------------------------------------------------------- 1 | type = $type; 30 | $this->setValue($value); 31 | $this->equals = $equals; 32 | } 33 | 34 | protected function setValue(mixed $value): void 35 | { 36 | $this->value = $value; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Api/Metadata/Validators/DateGreaterThan.php: -------------------------------------------------------------------------------- 1 | value = Utils::toISO8601DateOnly($value); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Api/Metadata/Validators/DateLessThan.php: -------------------------------------------------------------------------------- 1 | value = Utils::toISO8601DateOnly($value); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Api/Metadata/Validators/IntGreaterThan.php: -------------------------------------------------------------------------------- 1 | type = self::STRLEN; 31 | $this->min = $min; 32 | $this->max = $max; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Api/Provisioning/AccountEndPoint.php: -------------------------------------------------------------------------------- 1 | endpoint(self::FOLDERS); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Api/Search/SearchQueryInterface.php: -------------------------------------------------------------------------------- 1 | accessType = $accessType; 60 | $this->start = $start; 61 | $this->end = $end; 62 | } 63 | 64 | 65 | /** 66 | * Serializes to JSON 67 | */ 68 | public function jsonSerialize(): array 69 | { 70 | return ArrayUtils::safeFilter( 71 | [ 72 | 'access_type' => $this->accessType, 73 | 'start' => Utils::formatDate($this->start), 74 | 'end' => Utils::formatDate($this->end), 75 | ] 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Asset/AccessControl/AccessType.php: -------------------------------------------------------------------------------- 1 | [DeliveryType::UPLOAD => 'files'], 30 | ]; 31 | } 32 | -------------------------------------------------------------------------------- /src/Asset/Image.php: -------------------------------------------------------------------------------- 1 | [ 34 | DeliveryType::UPLOAD => 'images', 35 | DeliveryType::PRIVATE_DELIVERY => 'private_images', 36 | DeliveryType::AUTHENTICATED => 'authenticated_images', 37 | ], 38 | ]; 39 | 40 | /** 41 | * Gets the transformation. 42 | * 43 | */ 44 | public function getTransformation(): CommonTransformation 45 | { 46 | if (! isset($this->transformation)) { 47 | $this->transformation = new ImageTransformation(); 48 | } 49 | 50 | return $this->transformation; 51 | } 52 | 53 | /** 54 | * Finalizes the asset type. 55 | * 56 | */ 57 | protected function finalizeAssetType(): ?string 58 | { 59 | return $this->finalizeShorten(parent::finalizeAssetType()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Asset/Media.php: -------------------------------------------------------------------------------- 1 | transformation)) { 52 | $this->transformation = new Transformation(); 53 | } 54 | 55 | return $this->transformation; 56 | } 57 | 58 | /** 59 | * Internal getter for a list of the delivery types that support SEO suffix. 60 | * 61 | * 62 | * @internal 63 | */ 64 | public static function getSuffixSupportedDeliveryTypes(): array 65 | { 66 | if (empty(self::$suffixSupportedDeliveryTypes)) { 67 | self::$suffixSupportedDeliveryTypes = ArrayUtils::mergeNonEmpty( 68 | Image::getSuffixSupportedDeliveryTypes(), 69 | Video::getSuffixSupportedDeliveryTypes(), 70 | File::getSuffixSupportedDeliveryTypes() 71 | ); 72 | } 73 | 74 | return self::$suffixSupportedDeliveryTypes; 75 | } 76 | 77 | /** 78 | * Finalizes the asset type. 79 | * 80 | */ 81 | protected function finalizeAssetType(): ?string 82 | { 83 | return $this->finalizeShorten(parent::finalizeAssetType()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Asset/Moderation/ModerationStatus.php: -------------------------------------------------------------------------------- 1 | ttl = $ttl; 24 | 25 | return $this; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Asset/Video.php: -------------------------------------------------------------------------------- 1 | [DeliveryType::UPLOAD => 'videos'], 32 | ]; 33 | 34 | /** 35 | * Gets the transformation. 36 | * 37 | */ 38 | public function getTransformation(): CommonTransformation|VideoTransformation 39 | { 40 | if (! isset($this->transformation)) { 41 | $this->transformation = new VideoTransformation(); 42 | } 43 | 44 | return $this->transformation; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Configuration/AssetConfigTrait.php: -------------------------------------------------------------------------------- 1 | Delivering token based authenticated media assets 16 | * 17 | * @api 18 | */ 19 | class AuthTokenConfig extends BaseConfigSection 20 | { 21 | public const CONFIG_NAME = 'auth_token'; 22 | 23 | // Supported parameters 24 | public const KEY = 'key'; 25 | public const IP = 'ip'; 26 | public const ACL = 'acl'; 27 | public const START_TIME = 'start_time'; 28 | public const EXPIRATION = 'expiration'; 29 | public const DURATION = 'duration'; 30 | 31 | /** 32 | * (Required) – the token must be signed with the encryption key received from Cloudinary. 33 | * 34 | * @var ?string 35 | */ 36 | public ?string $key = null; 37 | 38 | /** 39 | * (Optional) – only this IP address can access the resource. 40 | * 41 | * @var ?string 42 | */ 43 | public ?string $ip = null; 44 | 45 | /** 46 | * (Optional) – an Access Control List for limiting the allowed URL path to a specified pattern (e.g., 47 | * /video/authenticated/*). 48 | * 49 | * The pattern can include any of Cloudinary's transformations to also apply to the 50 | * delivered assets. Note that if you add an overlay (e.g., for a watermark), you should also include the 51 | * fl_layer_apply flag to ensure the layer cannot be modified. This parameter is useful for generating a token 52 | * that can be added to a number of different URLs that share a common transformation. Without this parameter, 53 | * the pattern defaults to the full URL path of the requested asset. 54 | * 55 | * @var string|array|null 56 | */ 57 | public string|array|null $acl = null; 58 | 59 | /** 60 | * (Optional) – timestamp of the UNIX time when the URL becomes valid. Default value: the current time. 61 | * 62 | * @var ?int 63 | */ 64 | public ?int $startTime = null; 65 | 66 | /** 67 | * (Optional) – timestamp of the UNIX time when the URL expires. 68 | * 69 | * @var ?int 70 | */ 71 | public ?int $expiration = null; 72 | 73 | /** 74 | * (Optional) – the duration that the URL is valid in seconds (counted from start_time). 75 | * 76 | * @var ?int 77 | */ 78 | public ?int $duration = null; 79 | } 80 | -------------------------------------------------------------------------------- /src/Configuration/CloudConfigTrait.php: -------------------------------------------------------------------------------- 1 | setCloudConfig(CloudConfig::CLOUD_NAME, $cloudName); 32 | } 33 | 34 | /** 35 | * Sets the OAuth2.0 token. 36 | * 37 | * @param ?string $oauthToken Used instead of API key and API secret 38 | * 39 | * @return $this 40 | * 41 | * @api 42 | */ 43 | public function oauthToken(?string $oauthToken): static 44 | { 45 | return $this->setCloudConfig(CloudConfig::OAUTH_TOKEN, $oauthToken); 46 | } 47 | 48 | /** 49 | * Sets the signature algorithm. 50 | * 51 | * @param string $signatureAlgorithm The algorithm to use. (Can be SHA1 or SHA256). 52 | * 53 | * @return $this 54 | * 55 | * @api 56 | */ 57 | public function signatureAlgorithm(string $signatureAlgorithm): static 58 | { 59 | return $this->setCloudConfig(CloudConfig::SIGNATURE_ALGORITHM, $signatureAlgorithm); 60 | } 61 | 62 | /** 63 | * Sets the Cloud configuration key with the specified value. 64 | * 65 | * @param string $configKey The configuration key. 66 | * @param mixed $configValue THe configuration value. 67 | * 68 | * @return $this 69 | * 70 | * @internal 71 | */ 72 | abstract public function setCloudConfig(string $configKey, mixed $configValue): static; 73 | } 74 | -------------------------------------------------------------------------------- /src/Configuration/ConfigurableInterface.php: -------------------------------------------------------------------------------- 1 | :@]" expected' 63 | ); 64 | } 65 | 66 | $config = [ProvisioningAccountConfig::ACCOUNT_ID => $uri->getHost()]; 67 | 68 | $userPass = explode(':', $uri->getUserInfo(), 2); 69 | 70 | ArrayUtils::addNonEmpty( 71 | $config, 72 | ProvisioningAccountConfig::PROVISIONING_API_KEY, 73 | ArrayUtils::get($userPass, 0) 74 | ); 75 | ArrayUtils::addNonEmpty( 76 | $config, 77 | ProvisioningAccountConfig::PROVISIONING_API_SECRET, 78 | ArrayUtils::get($userPass, 1) 79 | ); 80 | 81 | return [ProvisioningAccountConfig::CONFIG_NAME => $config]; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Configuration/ResponsiveBreakpointsConfig.php: -------------------------------------------------------------------------------- 1 | Responsive breakpoints 17 | * 18 | * @property ?int $minWidth The minimum width needed for the image. Default: 375. 19 | * @property ?int $maxWidth The maximum width needed for the image. Default 3840. 20 | * @property ?int $maxImages The maximal number of breakpoints. 21 | * 22 | * @api 23 | */ 24 | class ResponsiveBreakpointsConfig extends BaseConfigSection 25 | { 26 | public const CONFIG_NAME = 'responsive_breakpoints'; 27 | 28 | public const DEFAULT_MIN_WIDTH = 375; 29 | public const DEFAULT_MAX_WIDTH = 3840; 30 | public const DEFAULT_MAX_IMAGES = 5; 31 | 32 | // Supported parameters 33 | public const BREAKPOINTS = 'breakpoints'; 34 | 35 | public const MIN_WIDTH = 'min_width'; 36 | public const MAX_WIDTH = 'max_width'; 37 | public const MAX_IMAGES = 'max_images'; 38 | 39 | public const AUTO_OPTIMAL_BREAKPOINTS = 'auto_optimal_breakpoints'; 40 | 41 | /** 42 | * An array of static breakpoints to use (overrides Cloudinary-optimized breakpoints). 43 | * 44 | * @var array|null 45 | */ 46 | public ?array $breakpoints = null; 47 | 48 | /** 49 | * The minimum width needed for the image. Default: 375. 50 | * 51 | * @var ?int 52 | */ 53 | protected ?int $minWidth; 54 | 55 | /** 56 | * The maximum width needed for the image. If specifying a width bigger than the original image, 57 | * the width of the original image is used instead. Default: 3840. 58 | * 59 | * @var ?int 60 | */ 61 | protected ?int $maxWidth; 62 | 63 | /** 64 | * The number of breakpoints. Default: 5. 65 | * 66 | * @var ?int 67 | */ 68 | protected ?int $maxImages; 69 | 70 | /** 71 | * Defines whether to use auto optimal breakpoints. 72 | * 73 | * @var ?bool 74 | */ 75 | public ?bool $autoOptimalBreakpoints = null; 76 | } 77 | -------------------------------------------------------------------------------- /src/Configuration/TagConfigTrait.php: -------------------------------------------------------------------------------- 1 | setTagConfig(TagConfig::VIDEO_POSTER_FORMAT, $format); 31 | } 32 | 33 | 34 | /** 35 | * Use fetch format transformation ("f_") instead of file extension. 36 | * 37 | * @param bool $useFetchFormat 38 | * 39 | * @return $this 40 | */ 41 | public function useFetchFormat($useFetchFormat = true): static 42 | { 43 | return $this->setTagConfig(TagConfig::USE_FETCH_FORMAT, $useFetchFormat); 44 | } 45 | 46 | /** 47 | * Sets the Tag configuration key with the specified value. 48 | * 49 | * @param string $configKey The configuration key. 50 | * @param mixed $configValue THe configuration value. 51 | * 52 | * @return $this 53 | * 54 | * @internal 55 | */ 56 | abstract public function setTagConfig($configKey, $configValue): static; 57 | } 58 | -------------------------------------------------------------------------------- /src/Exception/ConfigurationException.php: -------------------------------------------------------------------------------- 1 | httpClient = new Client(); 42 | 43 | if ($configuration === null) { 44 | $configuration = Configuration::instance(); 45 | } 46 | 47 | $this->logging = $configuration->logging; 48 | } 49 | 50 | /** 51 | * Retrieves JSON from url. 52 | * 53 | * @param string $url The url 54 | * 55 | * 56 | * @throws Error 57 | * @throws \GuzzleHttp\Exception\GuzzleException 58 | */ 59 | public function getJson(string $url): mixed 60 | { 61 | try { 62 | return self::parseJsonResponse($this->httpClient->get($url)); 63 | } catch (Error $e) { 64 | $this->getLogger()->critical( 65 | 'Error parsing JSON server response', 66 | [ 67 | 'exception' => $e->getMessage(), 68 | 'class' => self::class, 69 | 'url' => $url, 70 | ] 71 | ); 72 | throw $e; 73 | } 74 | } 75 | 76 | /** 77 | * Parses JSON response. 78 | * 79 | * @param ResponseInterface $response Response from HTTP request to Cloudinary server 80 | * 81 | * 82 | * @throws Error 83 | */ 84 | private static function parseJsonResponse(ResponseInterface $response): mixed 85 | { 86 | try { 87 | $responseJson = JsonUtils::decode($response->getBody()); 88 | } catch (InvalidArgumentException $iae) { 89 | $message = sprintf( 90 | 'Error parsing server response (%s) - %s. Got - %s', 91 | $response->getStatusCode(), 92 | $response->getBody(), 93 | $iae 94 | ); 95 | throw new Error($message); 96 | } 97 | 98 | return $responseJson; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Log/LoggerDecorator.php: -------------------------------------------------------------------------------- 1 | config = $config; 36 | } 37 | 38 | /** 39 | * Get the TestHandler (if one has been defined) 40 | * 41 | */ 42 | public function getTestHandler(): ?TestHandler 43 | { 44 | return $this->logger?->getTestHandler(); 45 | 46 | } 47 | 48 | /** 49 | * Get all Monolog handlers used by this logger 50 | * 51 | * @return HandlerInterface[] 52 | */ 53 | public function getHandlers(): array 54 | { 55 | if ($this->logger !== null) { 56 | return $this->logger->getHandlers(); 57 | } 58 | 59 | return []; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Log/LoggersList.php: -------------------------------------------------------------------------------- 1 | enabled === false) { 55 | return null; 56 | } 57 | 58 | $configHash = crc32(serialize($config)); 59 | 60 | if (!isset(self::$loggerInstances[$configHash])) { 61 | self::$loggerInstances[$configHash] = new Logger($config); 62 | } 63 | 64 | return self::$loggerInstances[$configHash]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Tag/Attribute/AudioSourceType.php: -------------------------------------------------------------------------------- 1 | minWidth = $minWidth; 45 | $this->maxWidth = $maxWidth; 46 | 47 | $this->logging = $configuration->logging; 48 | } 49 | 50 | /** 51 | * Serializes to string. 52 | * 53 | * @return string 54 | */ 55 | public function __toString() 56 | { 57 | if ($this->minWidth === null && $this->maxWidth === null) { 58 | $message = 'either minWidth or maxWidth is required'; 59 | $this->getLogger()->critical($message); 60 | 61 | return ''; 62 | } 63 | 64 | if (is_int($this->minWidth) && is_int($this->maxWidth) && $this->minWidth > $this->maxWidth) { 65 | $message = 'minWidth must be less than maxWidth'; 66 | $this->getLogger()->critical($message); 67 | 68 | return ''; 69 | } 70 | 71 | $mediaQueryConditions = []; 72 | 73 | if (! empty($this->minWidth)) { 74 | $mediaQueryConditions[] = "(min-width: {$this->minWidth}px)"; 75 | } 76 | 77 | if (! empty($this->maxWidth)) { 78 | $mediaQueryConditions[] = "(max-width: {$this->maxWidth}px)"; 79 | } 80 | 81 | return implode(' and ', $mediaQueryConditions); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Tag/Attribute/Sizes.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 35 | } 36 | 37 | /** 38 | * Serializes to string. 39 | * 40 | * @return string 41 | */ 42 | public function __toString() 43 | { 44 | return (int)($this->configuration->tag->relativeWidth * 100) . 'vw'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Tag/Attribute/SourceType.php: -------------------------------------------------------------------------------- 1 | VideoSourceType::OGG]; 44 | 45 | /** 46 | * SourceType constructor. 47 | * 48 | * @param string|null $mediaType The media type. Can be self::MEDIA_TYPE_VIDEO or self::MEDIA_TYPE_AUDIO. 49 | * @param string|null $type The type(format) of the source. 50 | * @param array|string|null $codecs The codecs. 51 | */ 52 | public function __construct(?string $mediaType = null, ?string $type = null, array|string|null $codecs = null) 53 | { 54 | $this->mediaType = $mediaType; 55 | $this->type = $type; 56 | $this->codecs = ArrayUtils::build($codecs); 57 | } 58 | 59 | /** 60 | * Serializes to string. 61 | * 62 | * @return string 63 | */ 64 | public function __toString() 65 | { 66 | if ($this->type === null) { 67 | return ''; 68 | } 69 | 70 | $codecsStr = ! empty($this->codecs) ? 'codecs=' . ArrayUtils::implodeFiltered(', ', $this->codecs) : ''; 71 | 72 | $typeStr = ArrayUtils::get(self::$typeOverrides, $this->type, $this->type); 73 | 74 | return ArrayUtils::implodeFiltered('; ', ["{$this->mediaType}/{$typeStr}", $codecsStr]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Tag/Attribute/VideoSourceType.php: -------------------------------------------------------------------------------- 1 | ` tag to indicate support for Client Hints: 15 | * 16 | * ``` 17 | * 18 | * ``` 19 | * 20 | * @api 21 | */ 22 | class ClientHintsMetaTag extends BaseTag 23 | { 24 | /** 25 | * @var string NAME Mandatory. The name of the tag. 26 | */ 27 | public const NAME = 'meta'; 28 | 29 | /** 30 | * @var bool IS_VOID Indicates whether the tag is a void (self-closed, without body) tag. 31 | */ 32 | protected const IS_VOID = true; 33 | 34 | /** 35 | * @var array $attributes An array of tag attributes. 36 | */ 37 | protected array $attributes = [ 38 | 'http-equiv' => 'Accept-CH', 39 | 'content' => 'DPR, Viewport-Width, Width', 40 | ]; 41 | } 42 | -------------------------------------------------------------------------------- /src/Tag/FormInputTag.php: -------------------------------------------------------------------------------- 1 | 17 | * 18 | * @internal 19 | */ 20 | class FormInputTag extends BaseTag 21 | { 22 | public const NAME = 'input'; 23 | protected const IS_VOID = true; 24 | 25 | /** 26 | * @var array $attributes An array of tag attributes. 27 | */ 28 | protected array $attributes = ['type' => 'hidden']; 29 | 30 | /** 31 | * FormInputTag constructor. 32 | * 33 | * @param string $name The name of the input tag. 34 | * @param mixed $value The value of the input tag. 35 | */ 36 | public function __construct($name, mixed $value) 37 | { 38 | parent::__construct(); 39 | 40 | $this->setAttribute('name', $name); 41 | $this->setAttribute('value', $value); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Tag/FormTag.php: -------------------------------------------------------------------------------- 1 | ` tag, for example: 18 | * 19 | * 20 | * ``` 21 | *
22 | * 23 | * 24 | * 25 | * 26 | *
27 | * ``` 28 | * 29 | * @api 30 | * 31 | */ 32 | class FormTag extends BaseConfigurableApiTag 33 | { 34 | public const NAME = 'form'; 35 | 36 | /** 37 | * @var array $attributes An array of tag attributes. 38 | */ 39 | protected array $attributes = [ 40 | 'enctype' => 'multipart/form-data', 41 | 'method' => HttpMethod::POST, 42 | ]; 43 | 44 | /** 45 | * Serializes the tag attributes. 46 | * 47 | * @param array $attributes Optional. Additional attributes to add without affecting the tag state. 48 | * 49 | * @internal 50 | */ 51 | public function serializeAttributes(array $attributes = []): string 52 | { 53 | $attributes['action'] = $this->uploadApi->getUploadUrl($this->assetType); 54 | 55 | return parent::serializeAttributes($attributes); 56 | } 57 | 58 | /** 59 | * Returns input tags that are appended to the form tag. 60 | * 61 | * For example: 62 | * ``` 63 | * 64 | * 65 | * 66 | * 67 | * ``` 68 | * 69 | * @param array $additionalContent The additional content. 70 | * @param bool $prependAdditionalContent Whether to prepend additional content (instead of append). 71 | * 72 | * 73 | * @internal 74 | */ 75 | public function serializeContent(array $additionalContent = [], bool $prependAdditionalContent = false): string 76 | { 77 | $inputTags = []; 78 | $uploadParams = $this->getUploadParams(); 79 | 80 | foreach ($uploadParams as $key => $value) { 81 | $inputTags[] = (string)new FormInputTag($key, $value); 82 | } 83 | 84 | return parent::serializeContent( 85 | ArrayUtils::mergeNonEmpty($inputTags, $additionalContent), 86 | $prependAdditionalContent 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Tag/ImageTag.php: -------------------------------------------------------------------------------- 1 | ` tag with the `src` attribute set to the transformation URL, optional `srcset` and other 17 | * specified attributes. 18 | * 19 | * For more information, see the [PHP SDK guide](https://cloudinary.com/documentation/php_image_manipulation#deliver_and_transform_images). 20 | * 21 | * @api 22 | */ 23 | class ImageTag extends BaseImageTag 24 | { 25 | public const NAME = 'img'; 26 | public const IS_VOID = true; 27 | 28 | public const BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; 29 | protected const RESPONSIVE_CLASS = 'cld-responsive'; 30 | protected const HI_DPI_CLASS = 'cld-hidpi'; 31 | 32 | /** 33 | * Serializes the tag attributes. 34 | * 35 | * @param array $attributes Optional. Additional attributes to add without affecting the tag state. 36 | * 37 | */ 38 | public function serializeAttributes(array $attributes = []): string 39 | { 40 | if (($this->config->tag->responsive || $this->config->tag->hidpi) && ! $this->config->tag->clientHints) { 41 | $attributes['data-src'] = $this->image; 42 | 43 | $this->addClass($this->config->tag->responsive ? self::RESPONSIVE_CLASS : self::HI_DPI_CLASS); 44 | 45 | $src = $this->config->tag->responsivePlaceholder; 46 | if ($src === 'blank') { 47 | $src = self::BLANK; 48 | } 49 | } else { 50 | $src = $this->image->toUrl($this->additionalTransformation); 51 | } 52 | 53 | ArrayUtils::addNonEmpty($attributes, 'src', $src); 54 | 55 | if (! empty($this->srcset->getBreakpoints())) { // TODO: improve performance here 56 | $attributes['srcset'] = $this->srcset; 57 | 58 | if (! array_key_exists('sizes', $this->attributes)) { 59 | if (is_bool($this->config->tag->sizes)) { 60 | if ($this->config->tag->sizes) { 61 | $attributes['sizes'] = new Sizes($this->config); 62 | } 63 | } elseif (is_string($this->config->tag->sizes)) { 64 | $attributes['sizes'] = $this->config->tag->sizes; 65 | } 66 | } 67 | } 68 | 69 | return parent::serializeAttributes($attributes); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Tag/JsConfigTag.php: -------------------------------------------------------------------------------- 1 | ` tag for JavaScript: 18 | * 19 | * ``` 20 | * 23 | * ``` 24 | * 25 | * @api 26 | */ 27 | class JsConfigTag extends BaseTag 28 | { 29 | public const NAME = 'script'; 30 | 31 | /** 32 | * @var array $attributes An array of tag attributes. 33 | */ 34 | protected array $attributes = [ 35 | 'type' => 'text/javascript', 36 | ]; 37 | 38 | /** 39 | * JsConfigTag constructor. 40 | * 41 | * @param Configuration $configuration The configuration instance. 42 | */ 43 | public function __construct($configuration = null) 44 | { 45 | parent::__construct(); 46 | 47 | if ($configuration === null) { 48 | $configuration = Configuration::instance(); 49 | } 50 | 51 | $params = [ 52 | 'api_key' => $configuration->cloud->apiKey, 53 | 'cloud_name' => $configuration->cloud->cloudName, 54 | 'private_cdn' => $configuration->url->privateCdn, 55 | 'secure_distribution' => $configuration->url->secureCname, // secure_distribution is still used in v1 56 | 'cdn_subdomain' => $configuration->url->cdnSubdomain, 57 | ]; 58 | 59 | $this->setContent('$.cloudinary.config('.json_encode(ArrayUtils::safeFilter($params)).');'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Tag/TagUtils.php: -------------------------------------------------------------------------------- 1 | url->responsiveWidth); 30 | $useResponsiveBreakpoints = array_key_exists('responsive_breakpoints', $params); 31 | 32 | $noHtmlSizes = $hasLayer || $hasAngle || $cropMode === 'fit' || $cropMode === 'limit' || $responsiveWidth 33 | || $useResponsiveBreakpoints; 34 | 35 | ArrayUtils::addNonEmpty($params, 'width', ArrayUtils::pop($params, 'html_width')); 36 | ArrayUtils::addNonEmpty($params, 'height', ArrayUtils::pop($params, 'html_height')); 37 | 38 | $width = ArrayUtils::get($params, 'width'); 39 | if (! (is_null($width) || strlen($width) == 0 || $width && (StringUtils::startsWith($width, 'auto') 40 | || (float)$width < 1 || $noHtmlSizes))) { 41 | ArrayUtils::addNonEmptyFromOther($tagAttributes, 'width', $params); 42 | } 43 | 44 | $height = ArrayUtils::get($params, 'height'); 45 | if (! (is_null($height) || strlen($height) == 0 || (float)$height < 1 || $noHtmlSizes)) { 46 | ArrayUtils::addNonEmptyFromOther($tagAttributes, 'height', $params); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Tag/VideoTagDeliveryTypeTrait.php: -------------------------------------------------------------------------------- 1 | ` tag based on a captured frame from the specified video source. 21 | * 22 | * @api 23 | */ 24 | class VideoThumbnailTag extends ImageTag 25 | { 26 | /** 27 | * Sets the image of the tag. 28 | * 29 | * @param mixed $image The public ID of the video. 30 | * @param Configuration|null $configuration The Configuration source. 31 | * 32 | */ 33 | public function image(mixed $image, ?Configuration $configuration = null): static 34 | { 35 | parent::image(new Video($image, $configuration), $configuration); 36 | 37 | $this->image->setFormat($configuration->tag->videoPosterFormat, $configuration->tag->useFetchFormat); 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * Creates a video poster image tag for a video from the provided source and an array of parameters. 44 | * 45 | * @param string $source The public ID of the asset. 46 | * @param array $params The asset parameters. 47 | * 48 | */ 49 | public static function fromParams(string $source, array $params = []): VideoThumbnailTag 50 | { 51 | $configuration = self::fromParamsDefaultConfig(); 52 | 53 | ArrayUtils::setDefaultValue($params, 'resource_type', AssetType::VIDEO); 54 | ArrayUtils::setDefaultValue($params, 'format', $configuration->tag->videoPosterFormat); 55 | 56 | return parent::fromParams($source, $params); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Helpers/Addon.php: -------------------------------------------------------------------------------- 1 | accountApiClient = new MockAccountApiClient($configuration); 32 | } 33 | 34 | /** 35 | * Returns a mock api client. 36 | * 37 | * @return MockAccountApiClient 38 | */ 39 | public function getApiClient() 40 | { 41 | return $this->accountApiClient; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Helpers/MockAccountApiClient.php: -------------------------------------------------------------------------------- 1 | apiClient = new MockApiClient($configuration); 34 | 35 | $apiV2Configuration = new Configuration($configuration); 36 | $apiV2Configuration->api->apiVersion = '2'; 37 | 38 | $this->apiV2Client = new MockApiClient($apiV2Configuration); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Helpers/MockAnalytics.php: -------------------------------------------------------------------------------- 1 | getApiClient()->mockHandler; 28 | } 29 | 30 | /** 31 | * Returns mock handler. 32 | * 33 | * @return MockHandler 34 | */ 35 | public function getV2MockHandler() 36 | { 37 | return $this->getApiV2Client()->mockHandler; 38 | } 39 | 40 | /** 41 | * Returns a mock api client. 42 | * 43 | * @return \Cloudinary\Api\ApiClient|\Cloudinary\Api\UploadApiClient|MockApiClient 44 | */ 45 | public function getApiClient() 46 | { 47 | return $this->apiClient; 48 | } 49 | 50 | /** 51 | * Returns a mock api client. 52 | * 53 | * @return \Cloudinary\Api\ApiClient|\Cloudinary\Api\UploadApiClient|MockApiClient 54 | */ 55 | public function getApiV2Client() 56 | { 57 | return $this->apiV2Client; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Helpers/MockSearchApi.php: -------------------------------------------------------------------------------- 1 | apiClient = new MockApiClient($configuration); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Helpers/MockSearchFoldersApi.php: -------------------------------------------------------------------------------- 1 | apiClient = new MockApiClient($configuration); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Helpers/MockUploadApi.php: -------------------------------------------------------------------------------- 1 | apiClient = new MockUploadApiClient($configuration); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Helpers/MockUploadApiClient.php: -------------------------------------------------------------------------------- 1 | ping(); 24 | 25 | self::assertEquals('ok', $result['status']); 26 | } 27 | 28 | public function testPingAsync() 29 | { 30 | $result = self::$adminApi->pingAsync()->wait(); 31 | 32 | self::assertEquals('ok', $result['status']); 33 | } 34 | 35 | public function testConfig() 36 | { 37 | $result = self::$adminApi->config(); 38 | 39 | self::assertEquals(Configuration::instance()->cloud->cloudName, $result['cloud_name']); 40 | self::assertArrayNotHasKey('settings', $result); 41 | 42 | $resultWithSettings = self::$adminApi->config(['settings' => 'true']); 43 | 44 | self::assertArrayHasKey('settings', $resultWithSettings); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Integration/Admin/OAuthTest.php: -------------------------------------------------------------------------------- 1 | cloud->oauthToken(self::FAKE_OAUTH_TOKEN); 38 | $adminApi = new AdminApi($config); 39 | 40 | $this->expectExceptionMessage('Invalid token'); 41 | 42 | $adminApi->asset(self::$UNIQUE_IMAGE_PUBLIC_ID); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Integration/Admin/TagsTest.php: -------------------------------------------------------------------------------- 1 | ['tags' => [self::$TAG_WITH_PREFIX]], 39 | ], 40 | ] 41 | ); 42 | } 43 | 44 | public static function tearDownAfterClass() 45 | { 46 | self::cleanupTestAssets(); 47 | 48 | parent::tearDownAfterClass(); 49 | } 50 | 51 | /** 52 | * List tags of images 53 | * 54 | * @throws ApiError 55 | */ 56 | public function testListTagsOfImages() 57 | { 58 | $result = self::$adminApi->tags(['max_results' => 2]); 59 | 60 | self::assertObjectStructure($result, ['tags' => IsType::TYPE_ARRAY]); 61 | self::assertCount(2, $result['tags']); 62 | self::assertIsString($result['tags'][0]); 63 | } 64 | 65 | /** 66 | * List all tags with a given prefix 67 | */ 68 | public function testListTagsWithPrefix() 69 | { 70 | $result = self::$adminApi->tags(['prefix' => self::$PREFIX_TAG]); 71 | 72 | self::assertObjectStructure($result, ['tags' => IsType::TYPE_ARRAY]); 73 | self::assertCount(1, $result['tags']); 74 | self::assertContains(self::$TAG_WITH_PREFIX, $result['tags']); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Unit/Admin/AdminApiTest.php: -------------------------------------------------------------------------------- 1 | asset(self::$UNIQUE_TEST_ID, ['accessibility_analysis' => true]); 31 | $lastRequest = $mockAdminApi->getMockHandler()->getLastRequest(); 32 | 33 | self::assertRequestQueryStringSubset($lastRequest, ['accessibility_analysis' => '1']); 34 | } 35 | 36 | /** 37 | * Should allow the user to pass accessibility_analysis in the createUploadPreset function. 38 | */ 39 | public function testAccessibilityAnalysisUploadPreset() 40 | { 41 | $mockAdminApi = new MockAdminApi(); 42 | $mockAdminApi->createUploadPreset(['accessibility_analysis' => true]); 43 | $lastRequest = $mockAdminApi->getMockHandler()->getLastRequest(); 44 | 45 | self::assertRequestBodySubset($lastRequest, ['accessibility_analysis' => '1']); 46 | } 47 | 48 | /** 49 | * Should allow listing related assets in the asset function. 50 | */ 51 | public function testAssetRelatedAssets() 52 | { 53 | $mockAdminApi = new MockAdminApi(); 54 | $mockAdminApi->asset(self::$UNIQUE_TEST_ID, ['related' => true, 'related_next_cursor' => self::NEXT_CURSOR]); 55 | $lastRequest = $mockAdminApi->getMockHandler()->getLastRequest(); 56 | 57 | self::assertRequestQueryStringSubset( 58 | $lastRequest, 59 | ['related' => '1', 'related_next_cursor' => self::NEXT_CURSOR] 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Unit/Admin/AnalysisTest.php: -------------------------------------------------------------------------------- 1 | analyze( 32 | "uri", 33 | "captioning", 34 | "https://res.cloudinary.com/demo/image/upload/dog", 35 | ["custom" => ["model_name" => "my_model", "model_version" => 1]] 36 | ); 37 | 38 | $lastRequest = $mockAdminApi->getV2MockHandler()->getLastRequest(); 39 | $apiV2Configuration = new Configuration(); 40 | $apiV2Configuration->api->apiVersion = '2'; 41 | 42 | self::assertRequestUrl($lastRequest, '/analysis/analyze/uri', "", $apiV2Configuration); 43 | self::assertRequestJsonBodySubset( 44 | $lastRequest, 45 | [ 46 | "analysis_type" => "captioning", 47 | "uri" => "https://res.cloudinary.com/demo/image/upload/dog", 48 | "parameters" => ["custom" => ["model_name" => "my_model", "model_version" => 1]] 49 | ] 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Unit/Admin/FoldersTest.php: -------------------------------------------------------------------------------- 1 | renameFolder(self::API_TEST_ASSET_ID, self::API_TEST_ASSET_ID2); 35 | 36 | $lastRequest = $mockAdminApi->getMockHandler()->getLastRequest(); 37 | 38 | self::assertRequestPut($lastRequest); 39 | self::assertRequestUrl($lastRequest, '/folders/' . self::API_TEST_ASSET_ID); 40 | self::assertRequestBodySubset( 41 | $lastRequest, 42 | [ 43 | "to_folder" => self::API_TEST_ASSET_ID2, 44 | ] 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Unit/Admin/OAuthTest.php: -------------------------------------------------------------------------------- 1 | cloud->oauthToken(self::FAKE_OAUTH_TOKEN); 35 | 36 | $adminApi = new MockAdminApi($config); 37 | $adminApi->ping(); 38 | $lastRequest = $adminApi->getMockHandler()->getLastRequest(); 39 | 40 | self::assertRequestHeaderSubset( 41 | $lastRequest, 42 | [ 43 | 'Authorization' => ['Bearer ' . self::FAKE_OAUTH_TOKEN] 44 | ] 45 | ); 46 | } 47 | 48 | /** 49 | * Should make a request using `apiKey` and `apiSecret` if an Oauth Token is absent. 50 | */ 51 | public function testKeyAndSecretAdminApi() 52 | { 53 | $config = new Configuration(Configuration::instance()); 54 | $config->cloud->oauthToken(null); 55 | 56 | $adminApi = new MockAdminApi($config); 57 | $adminApi->ping(); 58 | $lastRequest = $adminApi->getMockHandler()->getLastRequest(); 59 | 60 | self::assertRequestHeaderSubset( 61 | $lastRequest, 62 | [ 63 | 'Authorization' => ['Basic a2V5OnNlY3JldA=='] 64 | ] 65 | ); 66 | } 67 | 68 | /** 69 | * Should be thrown an exception if `apiKey` and `apiSecret` or an Oauth Token are absent. 70 | */ 71 | public function testMissingCredentialsAdminApi() 72 | { 73 | $config = new Configuration(Configuration::instance()); 74 | $config->cloud->oauthToken(null); 75 | $config->cloud->apiKey = null; 76 | $config->cloud->apiSecret = null; 77 | 78 | $this->expectException(InvalidArgumentException::class); 79 | $this->expectExceptionMessage('Must supply apiKey'); 80 | 81 | $adminApi = new MockAdminApi($config); 82 | $adminApi->ping(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/Unit/ApiClientTest.php: -------------------------------------------------------------------------------- 1 | postFileAsync('/', '', []); 41 | } catch (Exception $e) { 42 | $message = $e->getMessage(); 43 | } 44 | 45 | self::assertStringStartsWith($expectedExceptionMessage, $message); 46 | self::assertObjectLoggedMessage($apiClient, $expectedLogMessage, Monolog::CRITICAL); 47 | } 48 | 49 | 50 | /** 51 | * @throws ReflectionException 52 | */ 53 | public function testInvalidApiClientParseJsonResponse() 54 | { 55 | $apiClient = new ApiClient(); 56 | $reflectionMethod = new ReflectionMethod(ApiClient::class, 'parseJsonResponse'); 57 | $reflectionMethod->setAccessible(true); 58 | 59 | $message = null; 60 | $expectedExceptionMessage = 'Error parsing server response'; 61 | try { 62 | $reflectionMethod->invoke($apiClient, new Response(200, [], '{NOT_A_JSON}')); 63 | } catch (Exception $e) { 64 | $message = $e->getMessage(); 65 | } 66 | 67 | self::assertStringStartsWith($expectedExceptionMessage, $message); 68 | self::assertObjectLoggedMessage($apiClient, $expectedExceptionMessage, Monolog::ERROR); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Unit/Asset/AnalyticsTest.php: -------------------------------------------------------------------------------- 1 | expectException(OutOfRangeException::class); 45 | self::invokeNonPublicMethod(MockAnalytics::class, 'encodeVersion', '44.45.46'); 46 | } 47 | 48 | public function testSdkAnalyticsSignature() 49 | { 50 | self::assertEquals( 51 | 'BAAJ1uAI', 52 | MockAnalytics::sdkAnalyticsSignature() 53 | ); 54 | } 55 | 56 | public function testTechVersion() 57 | { 58 | MockAnalytics::techVersion('12.0'); 59 | 60 | self::assertEquals( 61 | 'BAAJ1uAM', 62 | MockAnalytics::sdkAnalyticsSignature() 63 | ); 64 | 65 | MockAnalytics::techVersion('12.0.0'); 66 | 67 | self::assertEquals( 68 | 'BAAJ1uAM', 69 | MockAnalytics::sdkAnalyticsSignature() 70 | ); 71 | 72 | MockAnalytics::techVersion('12'); 73 | 74 | self::assertEquals( 75 | 'BAAJ1uM', 76 | MockAnalytics::sdkAnalyticsSignature() 77 | ); 78 | } 79 | 80 | public function testSdkAnalyticsSignatureWithIntegration() 81 | { 82 | MockAnalytics::product('B'); // Integrations 83 | MockAnalytics::sdkCode('B'); // Laravel 84 | MockAnalytics::sdkVersion('2.0.0'); // Laravel SDK version 85 | MockAnalytics::techVersion('9.5'); // Laravel version 86 | 87 | self::assertEquals( 88 | 'BBBAACH9', 89 | MockAnalytics::sdkAnalyticsSignature() 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/Unit/Asset/AssetTransformationTest.php: -------------------------------------------------------------------------------- 1 | resize(Resize::fill(300, 400)); 33 | } 34 | 35 | public function testAssetTransformation() 36 | { 37 | self::assertEquals( 38 | self::$expectedTStr, 39 | (string)(new AssetTransformation(self::$t)) 40 | ); 41 | } 42 | 43 | public function testAssetTransformationWithExt() 44 | { 45 | self::assertEquals( 46 | self::$expectedTStr . '/' . Format::JPG, 47 | (string)(new AssetTransformation(self::$t, Format::JPG)) 48 | ); 49 | } 50 | 51 | public function testAssetTransformationWithEmptyExt() 52 | { 53 | self::assertEquals( 54 | self::$expectedTStr . '/', 55 | (string)(new AssetTransformation(self::$t, '')) 56 | ); 57 | } 58 | 59 | public function testAssetTransformationFromParams() 60 | { 61 | $params = [ 62 | 'crop' => 'fill', 63 | 'width' => 300, 64 | 'height' => 400, 65 | ]; 66 | 67 | self::assertEquals( 68 | self::$expectedTStr, 69 | (string)(new AssetTransformation($params)) 70 | ); 71 | 72 | $params['format'] = Format::JPG; 73 | 74 | self::assertEquals( 75 | self::$expectedTStr . '/' . Format::JPG, 76 | (string)(new AssetTransformation($params)) 77 | ); 78 | 79 | self::assertEquals( 80 | self::$expectedTStr . '/' . Format::JPG, 81 | (string)AssetTransformation::fromParams($params) 82 | ); 83 | 84 | $params['format'] = ''; 85 | 86 | self::assertEquals( 87 | self::$expectedTStr . '/', 88 | (string)(new AssetTransformation($params)) 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/Unit/Asset/AuthTokenTestCase.php: -------------------------------------------------------------------------------- 1 | importCloudinaryUrl( 35 | $this->cloudinaryUrl. 36 | '?auth_token[duration]='.self::DURATION. 37 | '&auth_token[start_time]='.static::START_TIME. 38 | '&auth_token[key]='.self::AUTH_TOKEN_KEY. 39 | '&url[sign_url]=true'. 40 | '&url[private_cdn]=true' 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Unit/Asset/FileTest.php: -------------------------------------------------------------------------------- 1 | asset->suffix = 'my_favorite_sample'; 39 | 40 | self::assertAssetUrl( 41 | 'files/sample/my_favorite_sample.bin', 42 | (string)$f 43 | ); 44 | 45 | $fNoFormat = new File(pathinfo(self::FILE_NAME, PATHINFO_FILENAME)); 46 | 47 | $fNoFormat->asset->suffix = 'my_favorite_sample'; 48 | 49 | self::assertAssetUrl( 50 | 'files/sample/my_favorite_sample', 51 | (string)$fNoFormat 52 | ); 53 | 54 | $fNoFormat->deliveryType(DeliveryType::FETCH); 55 | 56 | self::assertErrorThrowing( 57 | static function () use ($fNoFormat) { 58 | return $fNoFormat->toUrl(); 59 | } 60 | ); 61 | } 62 | 63 | /** 64 | * @throws ReflectionException 65 | */ 66 | public function testInvalidFileImportJson() 67 | { 68 | $file = new File(self::FILE_NAME, self::TEST_LOGGING); 69 | 70 | $message = null; 71 | $expectedLogMessage = 'Error importing JSON'; 72 | $expectedExceptionMessage = 'JsonException :'; 73 | try { 74 | $file->importJson('{NOT_A_JSON}'); 75 | } catch (InvalidArgumentException $e) { 76 | $message = $e->getMessage(); 77 | } 78 | 79 | self::assertStringStartsWith($expectedExceptionMessage, $message); 80 | self::assertObjectLoggedMessage($file, $expectedLogMessage, Monolog::CRITICAL); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/Unit/Asset/MediaTest.php: -------------------------------------------------------------------------------- 1 | media = new Media(self::IMAGE_NAME); 34 | } 35 | 36 | public function testSimpleMedia() 37 | { 38 | self::assertImageUrl( 39 | self::IMAGE_NAME, 40 | $this->media 41 | ); 42 | 43 | self::assertImageUrl( 44 | self::IMAGE_NAME, 45 | $this->media->toUrl() 46 | ); 47 | } 48 | 49 | public function testFetchMedia() 50 | { 51 | $image = Media::fetch(self::FETCH_IMAGE_URL); 52 | 53 | self::assertImageUrl(self::FETCH_IMAGE_URL, $image, ['delivery_type' => DeliveryType::FETCH]); 54 | } 55 | 56 | public function testMediaAssetProperties() 57 | { 58 | self::assertAssetUrl( 59 | 'images/' . self::TEST_ASSET_VERSION_STR . '/' . self::URL_SUFFIXED_ASSET_ID . '.' . self::IMG_EXT_GIF, 60 | $this->media 61 | ->version(self::TEST_ASSET_VERSION) 62 | ->suffix(self::URL_SUFFIX) 63 | ->extension(self::IMG_EXT_GIF) 64 | ); 65 | } 66 | 67 | public function testMediaAssetSuffixValidation() 68 | { 69 | $this->expectException(UnexpectedValueException::class); 70 | $this->media->suffix('../illegal_suffix.//'); 71 | } 72 | 73 | public function testMediaMultipleTypes() 74 | { 75 | self::assertImageUrl( 76 | 'c_fill,g_auto,h_160,w_80/ac_vorbis/' . self::IMAGE_NAME, 77 | $this->media->fill(80, 160, Gravity::auto())->transcode(AudioCodec::vorbis()) 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/Unit/Asset/SearchAssetTest.php: -------------------------------------------------------------------------------- 1 | expression("resource_type:image AND tags=kitten AND uploaded_at>1d AND bytes>1m") 26 | ->sortBy("public_id", "desc") 27 | ->maxResults(30); 28 | 29 | $b64Query = "eyJleHByZXNzaW9uIjoicmVzb3VyY2VfdHlwZTppbWFnZSBBTkQgdGFncz1raXR0ZW4gQU5EIHVwbG9hZGVkX2F0" . 30 | "PjFkIEFORCBieXRlcz4xbSIsIm1heF9yZXN1bHRzIjozMCwic29ydF9ieSI6W3sicHVibGljX2lkIjoiZGVzYyJ9XX0="; 31 | 32 | $ttl300Sig = "431454b74cefa342e2f03e2d589b2e901babb8db6e6b149abf25bc0dd7ab20b7"; 33 | $ttl1000Sig = "25b91426a37d4f633a9b34383c63889ff8952e7ffecef29a17d600eeb3db0db7"; 34 | 35 | $nextCursor = self::NEXT_CURSOR; 36 | 37 | # default usage 38 | self::assertAssetUrl( 39 | "search/{$ttl300Sig}/300/{$b64Query}", 40 | $s 41 | ); 42 | 43 | # same signature with next cursor 44 | self::assertAssetUrl( 45 | "search/{$ttl300Sig}/300/{$b64Query}/{$nextCursor}", 46 | $s->toUrl(null, self::NEXT_CURSOR) 47 | ); 48 | 49 | # with custom ttl and next cursor 50 | self::assertAssetUrl( 51 | "search/{$ttl1000Sig}/1000/{$b64Query}/{$nextCursor}", 52 | $s->toUrl(1000, self::NEXT_CURSOR) 53 | ); 54 | 55 | # ttl and cursor are set from the class 56 | self::assertAssetUrl( 57 | "search/{$ttl1000Sig}/1000/{$b64Query}/{$nextCursor}", 58 | $s->ttl(1000)->nextCursor(self::NEXT_CURSOR) 59 | ); 60 | 61 | # private cdn 62 | self::assertAssetUrl( 63 | "search/{$ttl1000Sig}/1000/{$b64Query}/{$nextCursor}", 64 | $s->privateCdn(), 65 | ['private_cdn' => true] 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Unit/Asset/VideoTest.php: -------------------------------------------------------------------------------- 1 | resize(Scale::scale(new Width(100), 200)->aspectRatio(AspectRatio::ignoreInitialAspectRatio())); 28 | 29 | $t_expected = 'c_scale,fl_ignore_aspect_ratio,h_200,w_100'; 30 | 31 | self::assertEquals( 32 | $t_expected, 33 | (string)$v->getTransformation() 34 | ); 35 | 36 | self::assertVideoUrl( 37 | "{$t_expected}/" . self::VIDEO_NAME, 38 | (string)$v 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Unit/Cloudinary/CloudinaryTest.php: -------------------------------------------------------------------------------- 1 | configuration->cloud->cloudName); 28 | self::assertNotNull($c->configuration->cloud->apiKey); 29 | self::assertNotNull($c->configuration->cloud->apiSecret); 30 | } 31 | 32 | public function testCloudinaryUrlNotSet() 33 | { 34 | self::clearEnvironment(); 35 | 36 | $this->expectException(InvalidArgumentException::class); 37 | 38 | new Cloudinary(); // Boom! 39 | } 40 | 41 | public function testCloudinaryFromOptions() 42 | { 43 | $c = new Cloudinary( 44 | [ 45 | 'cloud' => [ 46 | 'cloud_name' => self::CLOUD_NAME, 47 | 'api_key' => self::API_KEY, 48 | 'api_secret' => self::API_SECRET, 49 | ], 50 | ] 51 | ); 52 | 53 | self::assertEquals(self::CLOUD_NAME, $c->configuration->cloud->cloudName); 54 | self::assertEquals(self::API_KEY, $c->configuration->cloud->apiKey); 55 | self::assertEquals(self::API_SECRET, $c->configuration->cloud->apiSecret); 56 | } 57 | 58 | public function testCloudinaryFromUrl() 59 | { 60 | $c = new Cloudinary($this->cloudinaryUrl); 61 | 62 | self::assertEquals(self::CLOUD_NAME, $c->configuration->cloud->cloudName); 63 | self::assertEquals(self::API_KEY, $c->configuration->cloud->apiKey); 64 | self::assertEquals(self::API_SECRET, $c->configuration->cloud->apiSecret); 65 | } 66 | 67 | public function testCloudinaryFromConfiguration() 68 | { 69 | self::clearEnvironment(); 70 | 71 | $config = new Configuration($this->cloudinaryUrl); 72 | 73 | $c = new Cloudinary($config); 74 | 75 | self::assertEquals(self::CLOUD_NAME, $c->configuration->cloud->cloudName); 76 | self::assertEquals(self::API_KEY, $c->configuration->cloud->apiKey); 77 | self::assertEquals(self::API_SECRET, $c->configuration->cloud->apiSecret); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Unit/Configuration/CloudConfigTest.php: -------------------------------------------------------------------------------- 1 | cloudinaryUrl); 24 | 25 | self::assertEquals(self::CLOUD_NAME, $cloud->cloudName); 26 | 27 | self::assertEquals( 28 | '', 29 | (string)$cloud 30 | ); 31 | 32 | self::assertEquals( 33 | '{"cloud":{"cloud_name":"' . self::CLOUD_NAME . '","api_key":"' . self::API_KEY . '","api_secret":"' . 34 | self::API_SECRET . '"}}', 35 | json_encode($cloud) 36 | ); 37 | 38 | self::assertEquals( 39 | '{"cloud":{"cloud_name":"' . self::CLOUD_NAME . '"}}', 40 | json_encode($cloud->jsonSerialize(false)) // exclude sensitive (passwords, etc) keys 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Unit/Configuration/Provisioning/ConfigurationAccountTest.php: -------------------------------------------------------------------------------- 1 | accountUrl); 24 | 25 | self::assertEquals(self::ACCOUNT_ID, $config->provisioningAccount->accountId); 26 | self::assertEquals(self::ACCOUNT_API_KEY, $config->provisioningAccount->provisioningApiKey); 27 | self::assertEquals(self::ACCOUNT_API_SECRET, $config->provisioningAccount->provisioningApiSecret); 28 | } 29 | 30 | public function testAccountConfigFromArray() 31 | { 32 | $config = new ProvisioningConfiguration( 33 | [ 34 | 'account_id' => self::ACCOUNT_ID, 35 | 'provisioning_api_key' => self::ACCOUNT_API_KEY, 36 | 'provisioning_api_secret' => self::ACCOUNT_API_SECRET, 37 | ] 38 | ); 39 | 40 | self::assertEquals(self::ACCOUNT_ID, $config->provisioningAccount->accountId); 41 | self::assertEquals(self::ACCOUNT_API_KEY, $config->provisioningAccount->provisioningApiKey); 42 | self::assertEquals(self::ACCOUNT_API_SECRET, $config->provisioningAccount->provisioningApiSecret); 43 | } 44 | 45 | public function testAccountConfigFromObject() 46 | { 47 | $config = new ProvisioningConfiguration($this->accountUrl); 48 | $config = new ProvisioningConfiguration($config); 49 | 50 | self::assertEquals(self::ACCOUNT_ID, $config->provisioningAccount->accountId); 51 | self::assertEquals(self::ACCOUNT_API_KEY, $config->provisioningAccount->provisioningApiKey); 52 | self::assertEquals(self::ACCOUNT_API_SECRET, $config->provisioningAccount->provisioningApiSecret); 53 | } 54 | 55 | public function testAccountConfigJsonSerialize() 56 | { 57 | $config = new ProvisioningConfiguration($this->accountUrl); 58 | 59 | $jsonConfig = json_encode($config->jsonSerialize()); 60 | $expectedJsonConfig = '{"provisioning_account":{"account_id":"' . self::ACCOUNT_ID . 61 | '","provisioning_api_key":"' . self::ACCOUNT_API_KEY . '","provisioning_api_secret":"' . 62 | self::ACCOUNT_API_SECRET . '"}}'; 63 | 64 | self::assertEquals($expectedJsonConfig, $jsonConfig); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Unit/Configuration/SensitiveKeysSerializationTest.php: -------------------------------------------------------------------------------- 1 | cloud = CloudConfig::fromCloudinaryUrl($this->cloudinaryUrl); 28 | } 29 | 30 | public function testExcludedKeysAreNotSerialized() 31 | { 32 | self::assertStrEquals( 33 | 'cloud[cloud_name]=test123&cloud[api_key]=' . self::API_KEY, 34 | $this->cloud->toString([CloudConfig::API_SECRET]) 35 | ); 36 | 37 | self::assertStrEquals( 38 | 'cloud[cloud_name]=test123&cloud[api_key]=' . self::API_KEY . '&cloud[api_secret]=' . self::API_SECRET, 39 | $this->cloud->toString() 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Unit/HttpClientTest.php: -------------------------------------------------------------------------------- 1 | getJson('https://cloudinary.com/'); 35 | } catch (Error $e) { 36 | $message = $e->getMessage(); 37 | } 38 | 39 | self::assertStringStartsWith($expectedExceptionMessage, $message); 40 | self::assertObjectLoggedMessage($httpClient, $expectedLogMessage, Monolog::CRITICAL); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Unit/Provisioning/AccessKeysTest.php: -------------------------------------------------------------------------------- 1 | accessKeys( 26 | self::SUB_ACCOUNT_ID, 27 | ['page_size' => 2, 'page' => 1, 'sort_by' => 'name', 'sort_order' => 'asc'] 28 | ); 29 | 30 | $lastRequest = $mockAccApi->getMockHandler()->getLastRequest(); 31 | 32 | self::assertAccountRequestUrl($lastRequest, '/sub_accounts/' . self::SUB_ACCOUNT_ID . '/access_keys'); 33 | 34 | self::assertRequestGet($lastRequest); 35 | 36 | self::assertRequestQueryStringSubset($lastRequest, ['page_size' => '2']); 37 | self::assertRequestQueryStringSubset($lastRequest, ['page' => '1']); 38 | self::assertRequestQueryStringSubset($lastRequest, ['sort_by' => 'name']); 39 | self::assertRequestQueryStringSubset($lastRequest, ['sort_order' => 'asc']); 40 | } 41 | 42 | /** 43 | * Should allow generating access keys. 44 | */ 45 | public function testGenerateAccessKey() 46 | { 47 | $mockAccApi = new MockAccountApi(); 48 | 49 | $mockAccApi->generateAccessKey( 50 | self::SUB_ACCOUNT_ID, 51 | ['enabled' => true, 'name' => 'test_key'] 52 | ); 53 | 54 | $lastRequest = $mockAccApi->getMockHandler()->getLastRequest(); 55 | 56 | self::assertAccountRequestUrl($lastRequest, '/sub_accounts/' . self::SUB_ACCOUNT_ID . '/access_keys'); 57 | self::assertRequestPost($lastRequest); 58 | 59 | self::assertRequestJsonBodySubset($lastRequest, ['enabled' => true, 'name' => 'test_key']); 60 | } 61 | 62 | /** 63 | * Should allow updating access keys. 64 | */ 65 | public function testUpdateAccessKey() 66 | { 67 | $mockAccApi = new MockAccountApi(); 68 | 69 | $mockAccApi->updateAccessKey( 70 | self::SUB_ACCOUNT_ID, 71 | self::API_KEY, 72 | ['enabled' => false, 'name' => 'updated_key'] 73 | ); 74 | 75 | $lastRequest = $mockAccApi->getMockHandler()->getLastRequest(); 76 | 77 | self::assertAccountRequestUrl( 78 | $lastRequest, 79 | '/sub_accounts/' . self::SUB_ACCOUNT_ID . '/access_keys/' . self::API_KEY 80 | ); 81 | self::assertRequestPut($lastRequest); 82 | 83 | self::assertRequestJsonBodySubset($lastRequest, ['enabled' => false, 'name' => 'updated_key']); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/Unit/Provisioning/ProvisioningUnitTestCase.php: -------------------------------------------------------------------------------- 1 | accountUrlEnvBackup = getenv(ProvisioningConfiguration::CLOUDINARY_ACCOUNT_URL_ENV_VAR); 35 | 36 | $this->accountUrl = 'account://' . $this::ACCOUNT_API_KEY . ':' . $this::ACCOUNT_API_SECRET . '@' 37 | . $this::ACCOUNT_ID; 38 | 39 | putenv(ProvisioningConfiguration::CLOUDINARY_ACCOUNT_URL_ENV_VAR . '=' . $this->accountUrl); 40 | 41 | ProvisioningConfiguration::instance()->init(); 42 | } 43 | 44 | public function tearDown() 45 | { 46 | parent::tearDown(); 47 | 48 | putenv( 49 | ProvisioningConfiguration::CLOUDINARY_ACCOUNT_URL_ENV_VAR . 50 | (! empty($this->accountUrlEnvBackup) ? '=' . $this->accountUrlEnvBackup : "") 51 | ); 52 | try { 53 | ProvisioningConfiguration::instance()->init(); 54 | } catch (ConfigurationException $ce) { 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/Unit/Tag/ClientHintsMetaTagTest.php: -------------------------------------------------------------------------------- 1 | ", 28 | (string)$tag 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Unit/Tag/FormTagTest.php: -------------------------------------------------------------------------------- 1 | configuration = new Configuration(); 36 | $this->uploadParams = ['public_id' => self::IMAGE_NAME]; 37 | } 38 | 39 | public function testFormTag() 40 | { 41 | $tag = new FormTag($this->configuration, $this->uploadParams); 42 | $tag->addClass('uploader'); 43 | 44 | self::assertMatchesRegularExpression(FormTagPatterns::getFormTagPattern(), (string)$tag); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Unit/Tag/JsConfigTagTest.php: -------------------------------------------------------------------------------- 1 | ', 29 | '$.cloudinary.config({"api_key":"' . self::API_KEY . '","cloud_name":"' . self::CLOUD_NAME . '"});', 30 | '', 31 | ] 32 | ), 33 | (string)$tag 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Unit/Tag/Patterns/FormTagPatterns.php: -------------------------------------------------------------------------------- 1 | \R* 29 | \R* 30 | \R* 31 | \R* 32 | \R* 33 | <\/form># 34 | TAG; 35 | return str_replace(PHP_EOL, '', $regExp); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Unit/Tag/Responsive/SrcSetAttributeTest.php: -------------------------------------------------------------------------------- 1 | responsiveBreakpoints->autoOptimalBreakpoints = true; 37 | 38 | $srcset = new SrcSet(self::IMAGE_NAME, $c); 39 | 40 | self::assertStrEquals( 41 | "{$this->prefix}828/sample.png 828w, " . 42 | "{$this->prefix}1366/sample.png 1366w, " . 43 | "{$this->prefix}1536/sample.png 1536w, " . 44 | "{$this->prefix}1920/sample.png 1920w, " . 45 | "{$this->prefix}3840/sample.png 3840w", 46 | $srcset 47 | ); 48 | } 49 | 50 | public function testSrcSetAttributeWithCustomBreakpoints() 51 | { 52 | $c = new Configuration(Configuration::instance()); 53 | 54 | $c->responsiveBreakpoints->breakpoints = [500, 1000, 1500]; 55 | 56 | $srcset = new SrcSet(self::IMAGE_NAME, $c); 57 | 58 | self::assertStrEquals( 59 | "{$this->prefix}500/sample.png 500w, " . 60 | "{$this->prefix}1000/sample.png 1000w, " . 61 | "{$this->prefix}1500/sample.png 1500w", 62 | $srcset 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Unit/Tag/SpriteTagTest.php: -------------------------------------------------------------------------------- 1 | ', 27 | (string)$tag 28 | ); 29 | } 30 | 31 | public function testSpriteTagAttributes() 32 | { 33 | $tag = new SpriteTag(self::IMAGE_NAME); 34 | $image = Image::sprite(self::IMAGE_NAME); 35 | 36 | self::assertTagAttributeEquals('text/css', $tag, 'type'); 37 | self::assertTagAttributeEquals('stylesheet', $tag, 'rel'); 38 | self::assertTagAttributeEquals((string)$image, $tag, 'href'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Unit/Tag/TagTestCase.php: -------------------------------------------------------------------------------- 1 | loadHTML($actualTag); 26 | $actualElement = $doc->getElementsByTagName($actualTag::NAME)->item(0); 27 | /** @noinspection PhpPossiblePolymorphicInvocationInspection */ 28 | self::assertEquals( 29 | (string)$expectedValue, 30 | $actualElement->getAttribute($attributeName), 31 | "Should contain attribute '$attributeName'" 32 | ); 33 | } 34 | 35 | /** 36 | * @param $expectedSrcValue 37 | * @param $actualTag 38 | */ 39 | protected static function assertTagSrcEquals($expectedSrcValue, $actualTag) 40 | { 41 | self::assertTagAttributeEquals($expectedSrcValue, $actualTag, 'src'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Unit/Tag/TestTag.php: -------------------------------------------------------------------------------- 1 | '; 24 | 25 | public function testSimpleVideoThumbnailTag() 26 | { 27 | $tag = new VideoThumbnailTag(self::VIDEO_NAME); 28 | 29 | self::assertEquals( 30 | 'args = $args; 28 | } 29 | 30 | /** 31 | * @return string 32 | */ 33 | public function __toString() 34 | { 35 | return substr(strrchr(static::class, '\\'), 1) . '(' . implode(', ', $this->args) . ')'; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Unit/TestHelpers/TestClassB.php: -------------------------------------------------------------------------------- 1 | logging); 32 | } 33 | 34 | /** 35 | * Generates log entries 36 | */ 37 | public function generateLog() 38 | { 39 | $this->info('This is an info level message'); 40 | $this->debug('This is a debug level message'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Unit/UnitTestCase.php: -------------------------------------------------------------------------------- 1 | ['test' => ['level' => 'debug']]]; 31 | 32 | const API_TEST_ASSET_ID = '4af5a0d1d4047808528b5425d166c101'; 33 | const API_TEST_ASSET_ID2 = '4af5a0d1d4047808528b5425d166c102'; 34 | const API_TEST_ASSET_ID3 = '4af5a0d1d4047808528b5425d166c103'; 35 | 36 | const NEXT_CURSOR = '8c452e112d4c88ac7c9ffb3a2a41c41bef24'; 37 | 38 | protected $cloudinaryUrl; 39 | 40 | private $cldUrlEnvBackup; 41 | 42 | public function setUp() 43 | { 44 | parent::setUp(); 45 | 46 | $this->cldUrlEnvBackup = getenv(Configuration::CLOUDINARY_URL_ENV_VAR); 47 | 48 | self::assertNotEmpty($this->cldUrlEnvBackup, 'Please set up CLOUDINARY_URL before running tests!'); 49 | 50 | $this->cloudinaryUrl = 'cloudinary://' . $this::API_KEY . ':' . $this::API_SECRET . '@' . $this::CLOUD_NAME; 51 | 52 | putenv(Configuration::CLOUDINARY_URL_ENV_VAR . '=' . $this->cloudinaryUrl); 53 | 54 | $config = ConfigUtils::parseCloudinaryUrl(getenv(Configuration::CLOUDINARY_URL_ENV_VAR)); 55 | $config = array_merge($config, self::TEST_LOGGING); 56 | Configuration::instance()->init($config); 57 | 58 | Configuration::instance()->url->analytics(false); // disable analytics for all unit tests 59 | } 60 | 61 | public function tearDown() 62 | { 63 | parent::tearDown(); 64 | 65 | putenv(Configuration::CLOUDINARY_URL_ENV_VAR . '=' . $this->cldUrlEnvBackup); 66 | } 67 | 68 | protected static function clearEnvironment() 69 | { 70 | putenv(Configuration::CLOUDINARY_URL_ENV_VAR); // unset CLOUDINARY_URL 71 | 72 | Configuration::instance()->init(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Unit/Utils/FileUtilsTest.php: -------------------------------------------------------------------------------- 1 | payload; printf("cloudinary://%s:%s@%s", $c->cloudApiKey, $c->cloudApiSecret, $c->cloudName);' 10 | -------------------------------------------------------------------------------- /tools/get_test_cloud.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 4 | 5 | PHP_VER=$(php -v | head -n 1 | cut -d ' ' -f 2); 6 | SDK_VER=$(php -r "echo json_decode(file_get_contents('composer.json'))->version;") 7 | 8 | 9 | bash ${DIR}/allocate_test_cloud.sh "PHP ${PHP_VER} SDK ${SDK_VER}" 10 | --------------------------------------------------------------------------------