From 82a464dca2ee2aa2e425aee5dda453fd869823d4 Mon Sep 17 00:00:00 2001 From: XIGE <710062962@qq.com> Date: Mon, 23 Feb 2026 19:48:13 +0800 Subject: [PATCH] 1.0 --- Plugin.php | 602 ++++++++ composer.json | 5 + composer.lock | 142 ++ php-sdk/.github/workflows/test-ci.yml | 71 + php-sdk/.github/workflows/version-check.yml | 19 + php-sdk/.gitignore | 12 + php-sdk/.scrutinizer.yml | 42 + php-sdk/CHANGELOG.md | 196 +++ php-sdk/CONTRIBUTING.md | 30 + php-sdk/LICENSE | 22 + php-sdk/README.md | 76 + php-sdk/autoload.php | 19 + php-sdk/codecov.yml | 28 + php-sdk/composer.json | 40 + php-sdk/examples/README.md | 10 + php-sdk/examples/bucket_lifecycleRule.php | 42 + php-sdk/examples/cdn_get_bandwidth.php | 41 + php-sdk/examples/cdn_get_flux.php | 35 + php-sdk/examples/cdn_get_log_list.php | 31 + php-sdk/examples/cdn_get_prefetch_list.php | 46 + php-sdk/examples/cdn_get_refresh_list.php | 48 + php-sdk/examples/cdn_refresh_urls_dirs.php | 59 + php-sdk/examples/cdn_timestamp_antileech.php | 20 + php-sdk/examples/censor_image.php | 42 + php-sdk/examples/censor_video.php | 52 + php-sdk/examples/delete_bucket.php | 27 + php-sdk/examples/delete_bucketEvent.php | 28 + .../examples/delete_bucketLifecycleRule.php | 27 + php-sdk/examples/get_bucketEvents.php | 26 + php-sdk/examples/get_bucketLifecycleRules.php | 26 + php-sdk/examples/get_bucketList.php | 26 + php-sdk/examples/get_bucketQuota.php | 26 + php-sdk/examples/get_bucketinfo.php | 25 + php-sdk/examples/get_bucketinfos.php | 26 + php-sdk/examples/get_corsRules.php | 26 + php-sdk/examples/image_url_builder.php | 74 + php-sdk/examples/persistent_fop_init.php | 18 + php-sdk/examples/persistent_fop_status.php | 19 + php-sdk/examples/pfop_mkzip.php | 58 + php-sdk/examples/pfop_vframe.php | 55 + php-sdk/examples/pfop_video_avthumb.php | 55 + php-sdk/examples/pfop_watermark.php | 59 + php-sdk/examples/php-logo.png | Bin 0 -> 65062 bytes php-sdk/examples/prefop.php | 27 + php-sdk/examples/put_bucketAccessMode.php | 27 + .../examples/put_bucketAccessStyleMode.php | 27 + php-sdk/examples/put_bucketEvent.php | 32 + php-sdk/examples/put_bucketMaxAge.php | 27 + php-sdk/examples/put_bucketQuota.php | 29 + php-sdk/examples/put_referAntiLeech.php | 30 + php-sdk/examples/qetag.php | 14 + php-sdk/examples/rs_asynch_fetch.php | 71 + php-sdk/examples/rs_batch_change_mime.php | 32 + php-sdk/examples/rs_batch_change_type.php | 45 + php-sdk/examples/rs_batch_copy.php | 40 + php-sdk/examples/rs_batch_delete.php | 32 + .../examples/rs_batch_delete_after_days.php | 39 + php-sdk/examples/rs_batch_move.php | 40 + php-sdk/examples/rs_batch_restore_ar.php | 41 + php-sdk/examples/rs_batch_stat.php | 32 + php-sdk/examples/rs_bucket_domains.php | 26 + php-sdk/examples/rs_buckets.php | 25 + php-sdk/examples/rs_change_mime.php | 29 + php-sdk/examples/rs_change_status.php | 29 + php-sdk/examples/rs_change_type.php | 36 + php-sdk/examples/rs_copy.php | 33 + php-sdk/examples/rs_delete.php | 27 + php-sdk/examples/rs_delete_after_days.php | 26 + php-sdk/examples/rs_download_urls.php | 19 + php-sdk/examples/rs_fetch.php | 43 + php-sdk/examples/rs_move.php | 29 + php-sdk/examples/rs_prefetch.php | 25 + php-sdk/examples/rs_restore.php | 28 + php-sdk/examples/rs_stat.php | 28 + php-sdk/examples/rsf_list_bucket.php | 47 + php-sdk/examples/rsf_list_files.php | 39 + php-sdk/examples/rsf_v2list_bucket.php | 34 + php-sdk/examples/rtc/README.md | 34 + php-sdk/examples/rtc/rtc_createApp.php | 32 + php-sdk/examples/rtc/rtc_create_roomToken.php | 34 + php-sdk/examples/rtc/rtc_deleteApp.php | 25 + php-sdk/examples/rtc/rtc_getApp.php | 26 + php-sdk/examples/rtc/rtc_rooms_kickUser.php | 31 + .../rtc/rtc_rooms_listActiveRooms.php | 35 + php-sdk/examples/rtc/rtc_rooms_listUser.php | 29 + php-sdk/examples/rtc/rtc_rooms_stopMerge.php | 28 + php-sdk/examples/rtc/rtc_updateApp.php | 40 + php-sdk/examples/saveas.php | 33 + php-sdk/examples/sms/README.md | 45 + php-sdk/examples/sms/sms_create_signature.php | 29 + php-sdk/examples/sms/sms_create_template.php | 33 + php-sdk/examples/sms/sms_delete_signature.php | 25 + php-sdk/examples/sms/sms_delete_template.php | 25 + php-sdk/examples/sms/sms_edit_signature.php | 30 + php-sdk/examples/sms/sms_edit_template.php | 31 + php-sdk/examples/sms/sms_query_send_sms.php | 50 + php-sdk/examples/sms/sms_query_signature.php | 28 + .../sms/sms_query_single_signature.php | 26 + .../sms/sms_query_single_template.php | 26 + php-sdk/examples/sms/sms_query_template.php | 28 + php-sdk/examples/sms/sms_send_message.php | 32 + php-sdk/examples/update_bucketEvent.php | 31 + .../examples/update_bucketLifecycleRule.php | 36 + php-sdk/examples/upload_and_callback.php | 31 + php-sdk/examples/upload_and_pfop.php | 49 + php-sdk/examples/upload_mgr_init.php | 19 + php-sdk/examples/upload_multi_demos.php | 89 ++ php-sdk/examples/upload_simple_file.php | 37 + php-sdk/examples/upload_tokens.php | 82 + php-sdk/examples/upload_verify_callback.php | 34 + php-sdk/examples/upload_with_qvmzone.php | 40 + php-sdk/examples/upload_with_zone.php | 39 + php-sdk/phpunit.xml.dist | 18 + php-sdk/src/Qiniu/Auth.php | 285 ++++ php-sdk/src/Qiniu/Cdn/CdnManager.php | 263 ++++ php-sdk/src/Qiniu/Config.php | 398 +++++ php-sdk/src/Qiniu/Enum/QiniuEnum.php | 53 + php-sdk/src/Qiniu/Enum/SplitUploadVersion.php | 9 + php-sdk/src/Qiniu/Etag.php | 76 + php-sdk/src/Qiniu/Http/Client.php | 198 +++ php-sdk/src/Qiniu/Http/Error.php | 38 + php-sdk/src/Qiniu/Http/Header.php | 291 ++++ .../src/Qiniu/Http/Middleware/Middleware.php | 31 + .../Middleware/RetryDomainsMiddleware.php | 76 + php-sdk/src/Qiniu/Http/Proxy.php | 34 + php-sdk/src/Qiniu/Http/Request.php | 42 + php-sdk/src/Qiniu/Http/RequestOptions.php | 104 ++ php-sdk/src/Qiniu/Http/Response.php | 220 +++ .../src/Qiniu/Processing/ImageUrlBuilder.php | 292 ++++ php-sdk/src/Qiniu/Processing/Operation.php | 69 + .../src/Qiniu/Processing/PersistentFop.php | 135 ++ php-sdk/src/Qiniu/Region.php | 229 +++ php-sdk/src/Qiniu/Rtc/AppClient.php | 236 +++ php-sdk/src/Qiniu/Sms/Sms.php | 382 +++++ php-sdk/src/Qiniu/Storage/ArgusManager.php | 129 ++ php-sdk/src/Qiniu/Storage/BucketManager.php | 1324 +++++++++++++++++ php-sdk/src/Qiniu/Storage/FormUploader.php | 165 ++ php-sdk/src/Qiniu/Storage/ResumeUploader.php | 580 ++++++++ php-sdk/src/Qiniu/Storage/UploadManager.php | 176 +++ php-sdk/src/Qiniu/Zone.php | 58 + php-sdk/src/Qiniu/functions.php | 346 +++++ php-sdk/test-env.sh | 4 + php-sdk/tests/Qiniu/Tests/AuthTest.php | 296 ++++ php-sdk/tests/Qiniu/Tests/Base64Test.php | 16 + php-sdk/tests/Qiniu/Tests/BucketTest.php | 733 +++++++++ php-sdk/tests/Qiniu/Tests/CdnManagerTest.php | 151 ++ php-sdk/tests/Qiniu/Tests/ConfigTest.php | 118 ++ php-sdk/tests/Qiniu/Tests/Crc32Test.php | 23 + php-sdk/tests/Qiniu/Tests/DownloadTest.php | 27 + php-sdk/tests/Qiniu/Tests/EntryTest.php | 88 ++ php-sdk/tests/Qiniu/Tests/EtagTest.php | 54 + php-sdk/tests/Qiniu/Tests/FopTest.php | 39 + php-sdk/tests/Qiniu/Tests/FormUpTest.php | 205 +++ php-sdk/tests/Qiniu/Tests/HeaderTest.php | 184 +++ php-sdk/tests/Qiniu/Tests/HttpTest.php | 163 ++ .../tests/Qiniu/Tests/ImageUrlBuilderTest.php | 263 ++++ php-sdk/tests/Qiniu/Tests/MiddlewareTest.php | 160 ++ php-sdk/tests/Qiniu/Tests/PfopTest.php | 304 ++++ php-sdk/tests/Qiniu/Tests/ResumeUpTest.php | 354 +++++ php-sdk/tests/Qiniu/Tests/ZoneTest.php | 136 ++ php-sdk/tests/bootstrap.php | 61 + php-sdk/tests/mock-server/ok.php | 3 + php-sdk/tests/mock-server/redirect.php | 5 + php-sdk/tests/mock-server/timeout.php | 3 + php-sdk/tests/socks5-server/go.mod | 7 + php-sdk/tests/socks5-server/go.sum | 4 + php-sdk/tests/socks5-server/main.go | 24 + vendor/autoload.php | 7 + vendor/composer/ClassLoader.php | 479 ++++++ vendor/composer/InstalledVersions.php | 317 ++++ vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 11 + vendor/composer/autoload_files.php | 11 + vendor/composer/autoload_namespaces.php | 9 + vendor/composer/autoload_psr4.php | 11 + vendor/composer/autoload_real.php | 75 + vendor/composer/autoload_static.php | 50 + vendor/composer/installed.json | 135 ++ vendor/composer/installed.php | 42 + vendor/composer/platform_check.php | 26 + vendor/myclabs/php-enum/LICENSE | 18 + vendor/myclabs/php-enum/README.md | 196 +++ vendor/myclabs/php-enum/SECURITY.md | 11 + vendor/myclabs/php-enum/composer.json | 36 + vendor/myclabs/php-enum/src/Enum.php | 319 ++++ .../php-enum/src/PHPUnit/Comparator.php | 54 + vendor/myclabs/php-enum/stubs/Stringable.php | 11 + .../php-sdk/.github/workflows/test-ci.yml | 71 + .../.github/workflows/version-check.yml | 19 + vendor/qiniu/php-sdk/.gitignore | 12 + vendor/qiniu/php-sdk/.scrutinizer.yml | 42 + vendor/qiniu/php-sdk/CHANGELOG.md | 196 +++ vendor/qiniu/php-sdk/CONTRIBUTING.md | 30 + vendor/qiniu/php-sdk/LICENSE | 22 + vendor/qiniu/php-sdk/README.md | 76 + vendor/qiniu/php-sdk/autoload.php | 19 + vendor/qiniu/php-sdk/codecov.yml | 28 + vendor/qiniu/php-sdk/composer.json | 40 + vendor/qiniu/php-sdk/examples/README.md | 10 + .../php-sdk/examples/bucket_lifecycleRule.php | 42 + .../php-sdk/examples/cdn_get_bandwidth.php | 41 + .../qiniu/php-sdk/examples/cdn_get_flux.php | 35 + .../php-sdk/examples/cdn_get_log_list.php | 31 + .../examples/cdn_get_prefetch_list.php | 46 + .../php-sdk/examples/cdn_get_refresh_list.php | 48 + .../examples/cdn_refresh_urls_dirs.php | 59 + .../examples/cdn_timestamp_antileech.php | 20 + .../qiniu/php-sdk/examples/censor_image.php | 42 + .../qiniu/php-sdk/examples/censor_video.php | 52 + .../qiniu/php-sdk/examples/delete_bucket.php | 27 + .../php-sdk/examples/delete_bucketEvent.php | 28 + .../examples/delete_bucketLifecycleRule.php | 27 + .../php-sdk/examples/get_bucketEvents.php | 26 + .../examples/get_bucketLifecycleRules.php | 26 + .../qiniu/php-sdk/examples/get_bucketList.php | 26 + .../php-sdk/examples/get_bucketQuota.php | 26 + .../qiniu/php-sdk/examples/get_bucketinfo.php | 25 + .../php-sdk/examples/get_bucketinfos.php | 26 + .../qiniu/php-sdk/examples/get_corsRules.php | 26 + .../php-sdk/examples/image_url_builder.php | 74 + .../php-sdk/examples/persistent_fop_init.php | 18 + .../examples/persistent_fop_status.php | 19 + vendor/qiniu/php-sdk/examples/pfop_mkzip.php | 58 + vendor/qiniu/php-sdk/examples/pfop_vframe.php | 55 + .../php-sdk/examples/pfop_video_avthumb.php | 55 + .../qiniu/php-sdk/examples/pfop_watermark.php | 59 + vendor/qiniu/php-sdk/examples/php-logo.png | Bin 0 -> 65062 bytes vendor/qiniu/php-sdk/examples/prefop.php | 27 + .../php-sdk/examples/put_bucketAccessMode.php | 27 + .../examples/put_bucketAccessStyleMode.php | 27 + .../php-sdk/examples/put_bucketEvent.php | 32 + .../php-sdk/examples/put_bucketMaxAge.php | 27 + .../php-sdk/examples/put_bucketQuota.php | 29 + .../php-sdk/examples/put_referAntiLeech.php | 30 + vendor/qiniu/php-sdk/examples/qetag.php | 14 + .../php-sdk/examples/rs_asynch_fetch.php | 71 + .../php-sdk/examples/rs_batch_change_mime.php | 32 + .../php-sdk/examples/rs_batch_change_type.php | 45 + .../qiniu/php-sdk/examples/rs_batch_copy.php | 40 + .../php-sdk/examples/rs_batch_delete.php | 32 + .../examples/rs_batch_delete_after_days.php | 39 + .../qiniu/php-sdk/examples/rs_batch_move.php | 40 + .../php-sdk/examples/rs_batch_restore_ar.php | 41 + .../qiniu/php-sdk/examples/rs_batch_stat.php | 32 + .../php-sdk/examples/rs_bucket_domains.php | 26 + vendor/qiniu/php-sdk/examples/rs_buckets.php | 25 + .../qiniu/php-sdk/examples/rs_change_mime.php | 29 + .../php-sdk/examples/rs_change_status.php | 29 + .../qiniu/php-sdk/examples/rs_change_type.php | 36 + vendor/qiniu/php-sdk/examples/rs_copy.php | 33 + vendor/qiniu/php-sdk/examples/rs_delete.php | 27 + .../php-sdk/examples/rs_delete_after_days.php | 26 + .../php-sdk/examples/rs_download_urls.php | 19 + vendor/qiniu/php-sdk/examples/rs_fetch.php | 43 + vendor/qiniu/php-sdk/examples/rs_move.php | 29 + vendor/qiniu/php-sdk/examples/rs_prefetch.php | 25 + vendor/qiniu/php-sdk/examples/rs_restore.php | 28 + vendor/qiniu/php-sdk/examples/rs_stat.php | 28 + .../php-sdk/examples/rsf_list_bucket.php | 47 + .../qiniu/php-sdk/examples/rsf_list_files.php | 39 + .../php-sdk/examples/rsf_v2list_bucket.php | 34 + vendor/qiniu/php-sdk/examples/rtc/README.md | 34 + .../php-sdk/examples/rtc/rtc_createApp.php | 32 + .../examples/rtc/rtc_create_roomToken.php | 34 + .../php-sdk/examples/rtc/rtc_deleteApp.php | 25 + .../qiniu/php-sdk/examples/rtc/rtc_getApp.php | 26 + .../examples/rtc/rtc_rooms_kickUser.php | 31 + .../rtc/rtc_rooms_listActiveRooms.php | 35 + .../examples/rtc/rtc_rooms_listUser.php | 29 + .../examples/rtc/rtc_rooms_stopMerge.php | 28 + .../php-sdk/examples/rtc/rtc_updateApp.php | 40 + vendor/qiniu/php-sdk/examples/saveas.php | 33 + vendor/qiniu/php-sdk/examples/sms/README.md | 45 + .../examples/sms/sms_create_signature.php | 29 + .../examples/sms/sms_create_template.php | 33 + .../examples/sms/sms_delete_signature.php | 25 + .../examples/sms/sms_delete_template.php | 25 + .../examples/sms/sms_edit_signature.php | 30 + .../examples/sms/sms_edit_template.php | 31 + .../examples/sms/sms_query_send_sms.php | 50 + .../examples/sms/sms_query_signature.php | 28 + .../sms/sms_query_single_signature.php | 26 + .../sms/sms_query_single_template.php | 26 + .../examples/sms/sms_query_template.php | 28 + .../php-sdk/examples/sms/sms_send_message.php | 32 + .../php-sdk/examples/update_bucketEvent.php | 31 + .../examples/update_bucketLifecycleRule.php | 36 + .../php-sdk/examples/upload_and_callback.php | 31 + .../php-sdk/examples/upload_and_pfop.php | 49 + .../php-sdk/examples/upload_mgr_init.php | 19 + .../php-sdk/examples/upload_multi_demos.php | 89 ++ .../php-sdk/examples/upload_simple_file.php | 37 + .../qiniu/php-sdk/examples/upload_tokens.php | 82 + .../examples/upload_verify_callback.php | 34 + .../php-sdk/examples/upload_with_qvmzone.php | 40 + .../php-sdk/examples/upload_with_zone.php | 39 + vendor/qiniu/php-sdk/phpunit.xml.dist | 18 + vendor/qiniu/php-sdk/src/Qiniu/Auth.php | 285 ++++ .../php-sdk/src/Qiniu/Cdn/CdnManager.php | 263 ++++ vendor/qiniu/php-sdk/src/Qiniu/Config.php | 398 +++++ .../php-sdk/src/Qiniu/Enum/QiniuEnum.php | 53 + .../src/Qiniu/Enum/SplitUploadVersion.php | 9 + vendor/qiniu/php-sdk/src/Qiniu/Etag.php | 76 + .../qiniu/php-sdk/src/Qiniu/Http/Client.php | 198 +++ vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php | 38 + .../qiniu/php-sdk/src/Qiniu/Http/Header.php | 291 ++++ .../src/Qiniu/Http/Middleware/Middleware.php | 31 + .../Middleware/RetryDomainsMiddleware.php | 76 + vendor/qiniu/php-sdk/src/Qiniu/Http/Proxy.php | 34 + .../qiniu/php-sdk/src/Qiniu/Http/Request.php | 42 + .../php-sdk/src/Qiniu/Http/RequestOptions.php | 104 ++ .../qiniu/php-sdk/src/Qiniu/Http/Response.php | 220 +++ .../src/Qiniu/Processing/ImageUrlBuilder.php | 292 ++++ .../src/Qiniu/Processing/Operation.php | 69 + .../src/Qiniu/Processing/PersistentFop.php | 135 ++ vendor/qiniu/php-sdk/src/Qiniu/Region.php | 229 +++ .../qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php | 236 +++ vendor/qiniu/php-sdk/src/Qiniu/Sms/Sms.php | 382 +++++ .../src/Qiniu/Storage/ArgusManager.php | 129 ++ .../src/Qiniu/Storage/BucketManager.php | 1324 +++++++++++++++++ .../src/Qiniu/Storage/FormUploader.php | 165 ++ .../src/Qiniu/Storage/ResumeUploader.php | 580 ++++++++ .../src/Qiniu/Storage/UploadManager.php | 176 +++ vendor/qiniu/php-sdk/src/Qiniu/Zone.php | 58 + vendor/qiniu/php-sdk/src/Qiniu/functions.php | 346 +++++ vendor/qiniu/php-sdk/test-env.sh | 4 + .../php-sdk/tests/Qiniu/Tests/AuthTest.php | 296 ++++ .../php-sdk/tests/Qiniu/Tests/Base64Test.php | 16 + .../php-sdk/tests/Qiniu/Tests/BucketTest.php | 733 +++++++++ .../tests/Qiniu/Tests/CdnManagerTest.php | 151 ++ .../php-sdk/tests/Qiniu/Tests/ConfigTest.php | 118 ++ .../php-sdk/tests/Qiniu/Tests/Crc32Test.php | 23 + .../tests/Qiniu/Tests/DownloadTest.php | 27 + .../php-sdk/tests/Qiniu/Tests/EntryTest.php | 88 ++ .../php-sdk/tests/Qiniu/Tests/EtagTest.php | 54 + .../php-sdk/tests/Qiniu/Tests/FopTest.php | 39 + .../php-sdk/tests/Qiniu/Tests/FormUpTest.php | 205 +++ .../php-sdk/tests/Qiniu/Tests/HeaderTest.php | 184 +++ .../php-sdk/tests/Qiniu/Tests/HttpTest.php | 163 ++ .../tests/Qiniu/Tests/ImageUrlBuilderTest.php | 263 ++++ .../tests/Qiniu/Tests/MiddlewareTest.php | 160 ++ .../php-sdk/tests/Qiniu/Tests/PfopTest.php | 304 ++++ .../tests/Qiniu/Tests/ResumeUpTest.php | 354 +++++ .../php-sdk/tests/Qiniu/Tests/ZoneTest.php | 136 ++ vendor/qiniu/php-sdk/tests/bootstrap.php | 61 + vendor/qiniu/php-sdk/tests/mock-server/ok.php | 3 + .../php-sdk/tests/mock-server/redirect.php | 5 + .../php-sdk/tests/mock-server/timeout.php | 3 + .../qiniu/php-sdk/tests/socks5-server/go.mod | 7 + .../qiniu/php-sdk/tests/socks5-server/go.sum | 4 + .../qiniu/php-sdk/tests/socks5-server/main.go | 24 + 351 files changed, 29816 insertions(+) create mode 100644 Plugin.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 php-sdk/.github/workflows/test-ci.yml create mode 100644 php-sdk/.github/workflows/version-check.yml create mode 100644 php-sdk/.gitignore create mode 100644 php-sdk/.scrutinizer.yml create mode 100644 php-sdk/CHANGELOG.md create mode 100644 php-sdk/CONTRIBUTING.md create mode 100644 php-sdk/LICENSE create mode 100644 php-sdk/README.md create mode 100644 php-sdk/autoload.php create mode 100644 php-sdk/codecov.yml create mode 100644 php-sdk/composer.json create mode 100644 php-sdk/examples/README.md create mode 100644 php-sdk/examples/bucket_lifecycleRule.php create mode 100644 php-sdk/examples/cdn_get_bandwidth.php create mode 100644 php-sdk/examples/cdn_get_flux.php create mode 100644 php-sdk/examples/cdn_get_log_list.php create mode 100644 php-sdk/examples/cdn_get_prefetch_list.php create mode 100644 php-sdk/examples/cdn_get_refresh_list.php create mode 100644 php-sdk/examples/cdn_refresh_urls_dirs.php create mode 100644 php-sdk/examples/cdn_timestamp_antileech.php create mode 100644 php-sdk/examples/censor_image.php create mode 100644 php-sdk/examples/censor_video.php create mode 100644 php-sdk/examples/delete_bucket.php create mode 100644 php-sdk/examples/delete_bucketEvent.php create mode 100644 php-sdk/examples/delete_bucketLifecycleRule.php create mode 100644 php-sdk/examples/get_bucketEvents.php create mode 100644 php-sdk/examples/get_bucketLifecycleRules.php create mode 100644 php-sdk/examples/get_bucketList.php create mode 100644 php-sdk/examples/get_bucketQuota.php create mode 100644 php-sdk/examples/get_bucketinfo.php create mode 100644 php-sdk/examples/get_bucketinfos.php create mode 100644 php-sdk/examples/get_corsRules.php create mode 100644 php-sdk/examples/image_url_builder.php create mode 100644 php-sdk/examples/persistent_fop_init.php create mode 100644 php-sdk/examples/persistent_fop_status.php create mode 100644 php-sdk/examples/pfop_mkzip.php create mode 100644 php-sdk/examples/pfop_vframe.php create mode 100644 php-sdk/examples/pfop_video_avthumb.php create mode 100644 php-sdk/examples/pfop_watermark.php create mode 100644 php-sdk/examples/php-logo.png create mode 100644 php-sdk/examples/prefop.php create mode 100644 php-sdk/examples/put_bucketAccessMode.php create mode 100644 php-sdk/examples/put_bucketAccessStyleMode.php create mode 100644 php-sdk/examples/put_bucketEvent.php create mode 100644 php-sdk/examples/put_bucketMaxAge.php create mode 100644 php-sdk/examples/put_bucketQuota.php create mode 100644 php-sdk/examples/put_referAntiLeech.php create mode 100644 php-sdk/examples/qetag.php create mode 100644 php-sdk/examples/rs_asynch_fetch.php create mode 100644 php-sdk/examples/rs_batch_change_mime.php create mode 100644 php-sdk/examples/rs_batch_change_type.php create mode 100644 php-sdk/examples/rs_batch_copy.php create mode 100644 php-sdk/examples/rs_batch_delete.php create mode 100644 php-sdk/examples/rs_batch_delete_after_days.php create mode 100644 php-sdk/examples/rs_batch_move.php create mode 100644 php-sdk/examples/rs_batch_restore_ar.php create mode 100644 php-sdk/examples/rs_batch_stat.php create mode 100644 php-sdk/examples/rs_bucket_domains.php create mode 100644 php-sdk/examples/rs_buckets.php create mode 100644 php-sdk/examples/rs_change_mime.php create mode 100644 php-sdk/examples/rs_change_status.php create mode 100644 php-sdk/examples/rs_change_type.php create mode 100644 php-sdk/examples/rs_copy.php create mode 100644 php-sdk/examples/rs_delete.php create mode 100644 php-sdk/examples/rs_delete_after_days.php create mode 100644 php-sdk/examples/rs_download_urls.php create mode 100644 php-sdk/examples/rs_fetch.php create mode 100644 php-sdk/examples/rs_move.php create mode 100644 php-sdk/examples/rs_prefetch.php create mode 100644 php-sdk/examples/rs_restore.php create mode 100644 php-sdk/examples/rs_stat.php create mode 100644 php-sdk/examples/rsf_list_bucket.php create mode 100644 php-sdk/examples/rsf_list_files.php create mode 100644 php-sdk/examples/rsf_v2list_bucket.php create mode 100644 php-sdk/examples/rtc/README.md create mode 100644 php-sdk/examples/rtc/rtc_createApp.php create mode 100644 php-sdk/examples/rtc/rtc_create_roomToken.php create mode 100644 php-sdk/examples/rtc/rtc_deleteApp.php create mode 100644 php-sdk/examples/rtc/rtc_getApp.php create mode 100644 php-sdk/examples/rtc/rtc_rooms_kickUser.php create mode 100644 php-sdk/examples/rtc/rtc_rooms_listActiveRooms.php create mode 100644 php-sdk/examples/rtc/rtc_rooms_listUser.php create mode 100644 php-sdk/examples/rtc/rtc_rooms_stopMerge.php create mode 100644 php-sdk/examples/rtc/rtc_updateApp.php create mode 100644 php-sdk/examples/saveas.php create mode 100644 php-sdk/examples/sms/README.md create mode 100644 php-sdk/examples/sms/sms_create_signature.php create mode 100644 php-sdk/examples/sms/sms_create_template.php create mode 100644 php-sdk/examples/sms/sms_delete_signature.php create mode 100644 php-sdk/examples/sms/sms_delete_template.php create mode 100644 php-sdk/examples/sms/sms_edit_signature.php create mode 100644 php-sdk/examples/sms/sms_edit_template.php create mode 100644 php-sdk/examples/sms/sms_query_send_sms.php create mode 100644 php-sdk/examples/sms/sms_query_signature.php create mode 100644 php-sdk/examples/sms/sms_query_single_signature.php create mode 100644 php-sdk/examples/sms/sms_query_single_template.php create mode 100644 php-sdk/examples/sms/sms_query_template.php create mode 100644 php-sdk/examples/sms/sms_send_message.php create mode 100644 php-sdk/examples/update_bucketEvent.php create mode 100644 php-sdk/examples/update_bucketLifecycleRule.php create mode 100644 php-sdk/examples/upload_and_callback.php create mode 100644 php-sdk/examples/upload_and_pfop.php create mode 100644 php-sdk/examples/upload_mgr_init.php create mode 100644 php-sdk/examples/upload_multi_demos.php create mode 100644 php-sdk/examples/upload_simple_file.php create mode 100644 php-sdk/examples/upload_tokens.php create mode 100644 php-sdk/examples/upload_verify_callback.php create mode 100644 php-sdk/examples/upload_with_qvmzone.php create mode 100644 php-sdk/examples/upload_with_zone.php create mode 100644 php-sdk/phpunit.xml.dist create mode 100644 php-sdk/src/Qiniu/Auth.php create mode 100644 php-sdk/src/Qiniu/Cdn/CdnManager.php create mode 100644 php-sdk/src/Qiniu/Config.php create mode 100644 php-sdk/src/Qiniu/Enum/QiniuEnum.php create mode 100644 php-sdk/src/Qiniu/Enum/SplitUploadVersion.php create mode 100644 php-sdk/src/Qiniu/Etag.php create mode 100644 php-sdk/src/Qiniu/Http/Client.php create mode 100644 php-sdk/src/Qiniu/Http/Error.php create mode 100644 php-sdk/src/Qiniu/Http/Header.php create mode 100644 php-sdk/src/Qiniu/Http/Middleware/Middleware.php create mode 100644 php-sdk/src/Qiniu/Http/Middleware/RetryDomainsMiddleware.php create mode 100644 php-sdk/src/Qiniu/Http/Proxy.php create mode 100644 php-sdk/src/Qiniu/Http/Request.php create mode 100644 php-sdk/src/Qiniu/Http/RequestOptions.php create mode 100644 php-sdk/src/Qiniu/Http/Response.php create mode 100644 php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php create mode 100644 php-sdk/src/Qiniu/Processing/Operation.php create mode 100644 php-sdk/src/Qiniu/Processing/PersistentFop.php create mode 100644 php-sdk/src/Qiniu/Region.php create mode 100644 php-sdk/src/Qiniu/Rtc/AppClient.php create mode 100644 php-sdk/src/Qiniu/Sms/Sms.php create mode 100644 php-sdk/src/Qiniu/Storage/ArgusManager.php create mode 100644 php-sdk/src/Qiniu/Storage/BucketManager.php create mode 100644 php-sdk/src/Qiniu/Storage/FormUploader.php create mode 100644 php-sdk/src/Qiniu/Storage/ResumeUploader.php create mode 100644 php-sdk/src/Qiniu/Storage/UploadManager.php create mode 100644 php-sdk/src/Qiniu/Zone.php create mode 100644 php-sdk/src/Qiniu/functions.php create mode 100644 php-sdk/test-env.sh create mode 100644 php-sdk/tests/Qiniu/Tests/AuthTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/Base64Test.php create mode 100644 php-sdk/tests/Qiniu/Tests/BucketTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/CdnManagerTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/ConfigTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/Crc32Test.php create mode 100644 php-sdk/tests/Qiniu/Tests/DownloadTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/EntryTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/EtagTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/FopTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/FormUpTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/HeaderTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/HttpTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/MiddlewareTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/PfopTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/ResumeUpTest.php create mode 100644 php-sdk/tests/Qiniu/Tests/ZoneTest.php create mode 100644 php-sdk/tests/bootstrap.php create mode 100644 php-sdk/tests/mock-server/ok.php create mode 100644 php-sdk/tests/mock-server/redirect.php create mode 100644 php-sdk/tests/mock-server/timeout.php create mode 100644 php-sdk/tests/socks5-server/go.mod create mode 100644 php-sdk/tests/socks5-server/go.sum create mode 100644 php-sdk/tests/socks5-server/main.go create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/InstalledVersions.php create mode 100644 vendor/composer/LICENSE create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_files.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/autoload_static.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/composer/installed.php create mode 100644 vendor/composer/platform_check.php create mode 100644 vendor/myclabs/php-enum/LICENSE create mode 100644 vendor/myclabs/php-enum/README.md create mode 100644 vendor/myclabs/php-enum/SECURITY.md create mode 100644 vendor/myclabs/php-enum/composer.json create mode 100644 vendor/myclabs/php-enum/src/Enum.php create mode 100644 vendor/myclabs/php-enum/src/PHPUnit/Comparator.php create mode 100644 vendor/myclabs/php-enum/stubs/Stringable.php create mode 100644 vendor/qiniu/php-sdk/.github/workflows/test-ci.yml create mode 100644 vendor/qiniu/php-sdk/.github/workflows/version-check.yml create mode 100644 vendor/qiniu/php-sdk/.gitignore create mode 100644 vendor/qiniu/php-sdk/.scrutinizer.yml create mode 100644 vendor/qiniu/php-sdk/CHANGELOG.md create mode 100644 vendor/qiniu/php-sdk/CONTRIBUTING.md create mode 100644 vendor/qiniu/php-sdk/LICENSE create mode 100644 vendor/qiniu/php-sdk/README.md create mode 100644 vendor/qiniu/php-sdk/autoload.php create mode 100644 vendor/qiniu/php-sdk/codecov.yml create mode 100644 vendor/qiniu/php-sdk/composer.json create mode 100644 vendor/qiniu/php-sdk/examples/README.md create mode 100644 vendor/qiniu/php-sdk/examples/bucket_lifecycleRule.php create mode 100644 vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php create mode 100644 vendor/qiniu/php-sdk/examples/cdn_get_flux.php create mode 100644 vendor/qiniu/php-sdk/examples/cdn_get_log_list.php create mode 100644 vendor/qiniu/php-sdk/examples/cdn_get_prefetch_list.php create mode 100644 vendor/qiniu/php-sdk/examples/cdn_get_refresh_list.php create mode 100644 vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php create mode 100644 vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php create mode 100644 vendor/qiniu/php-sdk/examples/censor_image.php create mode 100644 vendor/qiniu/php-sdk/examples/censor_video.php create mode 100644 vendor/qiniu/php-sdk/examples/delete_bucket.php create mode 100644 vendor/qiniu/php-sdk/examples/delete_bucketEvent.php create mode 100644 vendor/qiniu/php-sdk/examples/delete_bucketLifecycleRule.php create mode 100644 vendor/qiniu/php-sdk/examples/get_bucketEvents.php create mode 100644 vendor/qiniu/php-sdk/examples/get_bucketLifecycleRules.php create mode 100644 vendor/qiniu/php-sdk/examples/get_bucketList.php create mode 100644 vendor/qiniu/php-sdk/examples/get_bucketQuota.php create mode 100644 vendor/qiniu/php-sdk/examples/get_bucketinfo.php create mode 100644 vendor/qiniu/php-sdk/examples/get_bucketinfos.php create mode 100644 vendor/qiniu/php-sdk/examples/get_corsRules.php create mode 100644 vendor/qiniu/php-sdk/examples/image_url_builder.php create mode 100644 vendor/qiniu/php-sdk/examples/persistent_fop_init.php create mode 100644 vendor/qiniu/php-sdk/examples/persistent_fop_status.php create mode 100644 vendor/qiniu/php-sdk/examples/pfop_mkzip.php create mode 100644 vendor/qiniu/php-sdk/examples/pfop_vframe.php create mode 100644 vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php create mode 100644 vendor/qiniu/php-sdk/examples/pfop_watermark.php create mode 100644 vendor/qiniu/php-sdk/examples/php-logo.png create mode 100644 vendor/qiniu/php-sdk/examples/prefop.php create mode 100644 vendor/qiniu/php-sdk/examples/put_bucketAccessMode.php create mode 100644 vendor/qiniu/php-sdk/examples/put_bucketAccessStyleMode.php create mode 100644 vendor/qiniu/php-sdk/examples/put_bucketEvent.php create mode 100644 vendor/qiniu/php-sdk/examples/put_bucketMaxAge.php create mode 100644 vendor/qiniu/php-sdk/examples/put_bucketQuota.php create mode 100644 vendor/qiniu/php-sdk/examples/put_referAntiLeech.php create mode 100644 vendor/qiniu/php-sdk/examples/qetag.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_asynch_fetch.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_batch_change_mime.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_batch_change_type.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_batch_copy.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_batch_delete.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_batch_move.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_batch_restore_ar.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_batch_stat.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_bucket_domains.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_buckets.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_change_mime.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_change_status.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_change_type.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_copy.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_delete.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_delete_after_days.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_download_urls.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_fetch.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_move.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_prefetch.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_restore.php create mode 100644 vendor/qiniu/php-sdk/examples/rs_stat.php create mode 100644 vendor/qiniu/php-sdk/examples/rsf_list_bucket.php create mode 100644 vendor/qiniu/php-sdk/examples/rsf_list_files.php create mode 100644 vendor/qiniu/php-sdk/examples/rsf_v2list_bucket.php create mode 100644 vendor/qiniu/php-sdk/examples/rtc/README.md create mode 100644 vendor/qiniu/php-sdk/examples/rtc/rtc_createApp.php create mode 100644 vendor/qiniu/php-sdk/examples/rtc/rtc_create_roomToken.php create mode 100644 vendor/qiniu/php-sdk/examples/rtc/rtc_deleteApp.php create mode 100644 vendor/qiniu/php-sdk/examples/rtc/rtc_getApp.php create mode 100644 vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_kickUser.php create mode 100644 vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_listActiveRooms.php create mode 100644 vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_listUser.php create mode 100644 vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_stopMerge.php create mode 100644 vendor/qiniu/php-sdk/examples/rtc/rtc_updateApp.php create mode 100644 vendor/qiniu/php-sdk/examples/saveas.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/README.md create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_create_signature.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_create_template.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_delete_signature.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_delete_template.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_edit_signature.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_edit_template.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_query_send_sms.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_query_signature.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_query_single_signature.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_query_single_template.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_query_template.php create mode 100644 vendor/qiniu/php-sdk/examples/sms/sms_send_message.php create mode 100644 vendor/qiniu/php-sdk/examples/update_bucketEvent.php create mode 100644 vendor/qiniu/php-sdk/examples/update_bucketLifecycleRule.php create mode 100644 vendor/qiniu/php-sdk/examples/upload_and_callback.php create mode 100644 vendor/qiniu/php-sdk/examples/upload_and_pfop.php create mode 100644 vendor/qiniu/php-sdk/examples/upload_mgr_init.php create mode 100644 vendor/qiniu/php-sdk/examples/upload_multi_demos.php create mode 100644 vendor/qiniu/php-sdk/examples/upload_simple_file.php create mode 100644 vendor/qiniu/php-sdk/examples/upload_tokens.php create mode 100644 vendor/qiniu/php-sdk/examples/upload_verify_callback.php create mode 100644 vendor/qiniu/php-sdk/examples/upload_with_qvmzone.php create mode 100644 vendor/qiniu/php-sdk/examples/upload_with_zone.php create mode 100644 vendor/qiniu/php-sdk/phpunit.xml.dist create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Auth.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Config.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Enum/QiniuEnum.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Enum/SplitUploadVersion.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Etag.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Http/Client.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Http/Header.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Http/Middleware/Middleware.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Http/Middleware/RetryDomainsMiddleware.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Http/Proxy.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Http/RequestOptions.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Region.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Sms/Sms.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/Zone.php create mode 100644 vendor/qiniu/php-sdk/src/Qiniu/functions.php create mode 100644 vendor/qiniu/php-sdk/test-env.sh create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/ConfigTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/EntryTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/HeaderTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/MiddlewareTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php create mode 100644 vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php create mode 100644 vendor/qiniu/php-sdk/tests/bootstrap.php create mode 100644 vendor/qiniu/php-sdk/tests/mock-server/ok.php create mode 100644 vendor/qiniu/php-sdk/tests/mock-server/redirect.php create mode 100644 vendor/qiniu/php-sdk/tests/mock-server/timeout.php create mode 100644 vendor/qiniu/php-sdk/tests/socks5-server/go.mod create mode 100644 vendor/qiniu/php-sdk/tests/socks5-server/go.sum create mode 100644 vendor/qiniu/php-sdk/tests/socks5-server/main.go diff --git a/Plugin.php b/Plugin.php new file mode 100644 index 0000000..431c73e --- /dev/null +++ b/Plugin.php @@ -0,0 +1,602 @@ +uploadHandle = array('Qiniu_Plugin', 'uploadHandle'); + Typecho_Plugin::factory('Widget_Upload')->modifyHandle = array('Qiniu_Plugin', 'modifyHandle'); + Typecho_Plugin::factory('Widget_Upload')->deleteHandle = array('Qiniu_Plugin', 'deleteHandle'); + Typecho_Plugin::factory('Widget_Upload')->attachmentHandle = array('Qiniu_Plugin', 'attachmentHandle'); + + // 添加文章内容输出时的EXIF信息显示 + Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('Qiniu_Plugin', 'parseContent'); + Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('Qiniu_Plugin', 'parseContent'); + + // 使用安全的CSS加载方式 + Typecho_Plugin::factory('Widget_Archive')->footer = array('Qiniu_Plugin', 'footer'); + + return _t('插件已激活,请先配置七牛云信息!'); + } + + // 禁用插件 + public static function deactivate() + { + return _t('插件已禁用'); + } + + // 插件配置面板 + public static function config(Typecho_Widget_Helper_Form $form) + { + $bucket = new Typecho_Widget_Helper_Form_Element_Text('bucket', null, null, _t('空间名称:'), _t('七牛云存储空间名称')); + $bucket->addRule('required', _t('"空间名称"不能为空!')); + $form->addInput($bucket); + + $accesskey = new Typecho_Widget_Helper_Form_Element_Text('accesskey', null, null, _t('AccessKey:'), _t('从七牛云控制台获取')); + $form->addInput($accesskey->addRule('required', _t('AccessKey不能为空!'))); + + $secretkey = new Typecho_Widget_Helper_Form_Element_Text('secretkey', null, null, _t('SecretKey:'), _t('从七牛云控制台获取')); + $form->addInput($secretkey->addRule('required', _t('SecretKey不能为空!'))); + + $domain = new Typecho_Widget_Helper_Form_Element_Text('domain', null, 'https://', _t('绑定域名:'), _t('空间绑定的域名,如:https://cdn.example.com')); + $form->addInput($domain->addRule('required', _t('请填写空间绑定的域名!'))->addRule('url', _t('您输入的域名格式错误!'))); + + $savepath = new Typecho_Widget_Helper_Form_Element_Text('savepath', null, 'typecho/{year}/{month}/', _t('保存路径前缀'), _t('可使用变量:{year}, {month}, {day}, {random}')); + $form->addInput($savepath); + + // WebP转换开关 + $webp = new Typecho_Widget_Helper_Form_Element_Radio('webp', + array('0' => _t('关闭'), '1' => _t('开启')), + '0', + _t('WebP自动转换:'), + _t('开启后上传的JPEG/PNG图片将自动转换为WebP格式')); + $form->addInput($webp); + + // 图片质量设置 + $quality = new Typecho_Widget_Helper_Form_Element_Text('quality', null, '85', _t('图片质量:'), + _t('设置图片压缩质量(1-100),仅对JPEG/PNG/WEBP格式有效。85为推荐值')); + $quality->addRule('required', _t('图片质量不能为空!')) + ->addRule('isInteger', _t('必须输入数字!')) + ->addRule('range', _t('请输入1-100之间的数字!'), array(1, 100)); + $form->addInput($quality); + + // EXIF信息开关 + $exif = new Typecho_Widget_Helper_Form_Element_Radio('exif', + array('0' => _t('关闭'), '1' => _t('开启')), + '0', + _t('EXIF信息显示:'), + _t('开启后文章图片悬停时会显示拍摄信息(相机型号、光圈、快门等)')); + $form->addInput($exif); + } + + // 个人用户配置面板 + public static function personalConfig(Typecho_Widget_Helper_Form $form) + { + } + + // 获得插件配置信息 + public static function getConfig() + { + return Typecho_Widget::widget('Widget_Options')->plugin('Qiniu'); + } + + // 安全的CSS加载方式 + public static function footer() + { + $option = self::getConfig(); + if ($option->exif && !self::$_cssLoaded) { + // 只在文章页面加载CSS + $widget = Typecho_Widget::widget('Widget_Archive'); + if ($widget->is('single')) { + echo ''; + self::$_cssLoaded = true; + } + } + } + + // 输出CSS内容 + private static function echoCss() + { + echo '.qiniu-exif-container { + display: inline-block; + position: relative; + max-width: 100%; +} +.qiniu-exif-info { + position: absolute; + top: 0; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.85); + color: white; + padding: 8px 10px; + font-size: 12px; + line-height: 1.4; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; + z-index: 9999; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + pointer-events: none; +} +.qiniu-exif-container:hover .qiniu-exif-info { + opacity: 1; + visibility: visible; +} +.qiniu-exif-info div { + margin: 3px 0; +} +.qiniu-exif-info strong { + font-weight: 600; + color: #fff; +}'; + } + + /** + * 解析文章内容,为图片添加EXIF容器 + */ + public static function parseContent($content, $widget, $lastResult) + { + $content = $lastResult ? $lastResult : $content; + $option = self::getConfig(); + + if (!$option->exif || !$widget->is('single')) { + return $content; + } + + $pattern = '/]*src=["\']([^"\']+)["\'][^>]*>/i'; + return preg_replace_callback($pattern, function($matches) { + $imgTag = $matches[0]; + $src = $matches[1]; + + $option = self::getConfig(); + $domain = rtrim($option->domain, '/'); + + // 检查是否是七牛云的图片 + if (strpos($src, $domain) === false) { + return $imgTag; + } + + // 从URL中提取文件路径 + $path = str_replace($domain, '', $src); + $path = ltrim(parse_url($path, PHP_URL_PATH), '/'); + + // 去除图片处理参数 + if (strpos($path, '?') !== false) { + $path = substr($path, 0, strpos($path, '?')); + } + + // 从数据库获取EXIF信息 + $db = Typecho_Db::get(); + $row = $db->fetchRow($db->select()->from('table.contents') + ->where('type = ?', 'attachment') + ->where('text LIKE ?', '%"path":"' . $path . '"%') + ->limit(1)); + + if ($row) { + $attachment = unserialize($row['text']); + + if (isset($attachment['exif']) && !empty($attachment['exif'])) { + $exifData = $attachment['exif']; + + // 生成简化的EXIF信息HTML + $exifHtml = '
'; + + if (!empty($exifData['camera'])) { + $exifHtml .= '
' . htmlspecialchars($exifData['camera']) . '
'; + } + + $details = array(); + if (!empty($exifData['exposure'])) { + $details[] = '快门:' . htmlspecialchars($exifData['exposure']); + } + if (!empty($exifData['aperture'])) { + $details[] = '光圈:f/' . htmlspecialchars($exifData['aperture']); + } + if (!empty($exifData['iso'])) { + $details[] = 'ISO:' . htmlspecialchars($exifData['iso']); + } + if (!empty($exifData['focal_length'])) { + $details[] = '焦距:' . htmlspecialchars($exifData['focal_length']) . 'mm'; + } + + if (!empty($details)) { + $exifHtml .= '
' . implode(' ', $details) . '
'; + } + + $exifHtml .= '
'; + + return '
' . $imgTag . $exifHtml . '
'; + } + } + + return $imgTag; + }, $content); + } + + /** + * 提取EXIF信息 + */ + private static function extractExif($filePath) + { + if (!function_exists('exif_read_data')) { + return array(); + } + + $exif = @exif_read_data($filePath); + if (!$exif) { + return array(); + } + + $result = array(); + + // 相机型号 + if (!empty($exif['Make']) || !empty($exif['Model'])) { + $make = !empty($exif['Make']) ? trim($exif['Make']) : ''; + $model = !empty($exif['Model']) ? trim($exif['Model']) : ''; + $result['camera'] = trim($make . ' ' . $model); + } + + // 曝光时间 + if (!empty($exif['ExposureTime'])) { + $result['exposure'] = $exif['ExposureTime']; + } + + // 光圈值 + if (!empty($exif['FNumber'])) { + $fnumber = is_array($exif['FNumber']) ? $exif['FNumber'][0] / $exif['FNumber'][1] : $exif['FNumber']; + $result['aperture'] = number_format($fnumber, 1); + } + + // ISO + if (!empty($exif['ISOSpeedRatings'])) { + $result['iso'] = $exif['ISOSpeedRatings']; + } + + // 焦距 + if (!empty($exif['FocalLength'])) { + if (is_array($exif['FocalLength'])) { + $focal = $exif['FocalLength'][0] / $exif['FocalLength'][1]; + } else { + $focal = $exif['FocalLength']; + } + $result['focal_length'] = number_format($focal, 1); + } + + // 拍摄时间 + if (!empty($exif['DateTimeOriginal'])) { + $result['date'] = date('Y-m-d H:i', strtotime($exif['DateTimeOriginal'])); + } + + return $result; + } + + /** + * 检测是否支持WebP转换 + */ + private static function canConvertWebp() + { + if (!extension_loaded('gd')) { + return false; + } + + $gdInfo = gd_info(); + return isset($gdInfo['WebP Support']) && $gdInfo['WebP Support']; + } + + /** + * 压缩图片并转换格式 + */ + private static function compressAndConvert($sourcePath, $targetExt, $quality) + { + $imageInfo = @getimagesize($sourcePath); + if (!$imageInfo) { + return $sourcePath; + } + + $mime = $imageInfo['mime']; + + // 创建图像资源 + switch ($mime) { + case 'image/jpeg': + $image = imagecreatefromjpeg($sourcePath); + break; + case 'image/png': + $image = imagecreatefrompng($sourcePath); + imagepalettetotruecolor($image); + imagealphablending($image, false); + imagesavealpha($image, true); + break; + case 'image/gif': + $image = imagecreatefromgif($sourcePath); + break; + default: + return $sourcePath; + } + + if (!$image) { + return $sourcePath; + } + + $tempFile = tempnam(sys_get_temp_dir(), 'img_') . '.' . $targetExt; + + $success = false; + switch ($targetExt) { + case 'jpg': + case 'jpeg': + $success = imagejpeg($image, $tempFile, $quality); + break; + case 'png': + $pngQuality = 9 - round(($quality / 100) * 9); + $success = imagepng($image, $tempFile, $pngQuality); + break; + case 'webp': + if (self::canConvertWebp()) { + $success = imagewebp($image, $tempFile, $quality); + } + break; + } + + imagedestroy($image); + + if ($success && file_exists($tempFile)) { + return $tempFile; + } + + return $sourcePath; + } + + /** + * 使用七牛基本图片处理 + */ + private static function applyQiniuStyle($url, $option) + { + $pathInfo = pathinfo($url); + $currentExt = isset($pathInfo['extension']) ? strtolower($pathInfo['extension']) : ''; + + $needQuality = $option->quality && $option->quality != 85; + $needWebP = $option->webp && $currentExt != 'webp'; + + if (!$needQuality && !$needWebP) { + return $url; + } + + $params = array(); + + if ($needQuality) { + $params[] = 'q/' . intval($option->quality); + } + + if ($needWebP) { + $params[] = 'format/webp'; + } + + if (!empty($params)) { + $separator = (strpos($url, '?') === false) ? '?' : '&'; + $url .= $separator . 'imageView2/2/' . implode('/', $params); + } + + return $url; + } + + /** + * 删除文件 + */ + public static function deleteFile($filepath) + { + try { + $option = self::getConfig(); + $auth = new Auth($option->accesskey, $option->secretkey); + $bucketMgr = new BucketManager($auth); + + $err = $bucketMgr->delete($option->bucket, $filepath); + + return $err === null; + } catch (Exception $e) { + return false; + } + } + + /** + * 上传文件到七牛云 - 修复中文文件名问题 + */ + public static function uploadFile($file, $content = null) + { + error_reporting(0); + + if (empty($file['name']) || !isset($file['tmp_name'])) { + return array('error' => 1, 'message' => '上传文件无效'); + } + + // 获取原始文件名 + $originalName = basename($file['name']); + + // 使用pathinfo正确处理中文字符 + $pathInfo = pathinfo($originalName); + $originalExt = isset($pathInfo['extension']) ? strtolower($pathInfo['extension']) : ''; + $baseName = isset($pathInfo['filename']) ? $pathInfo['filename'] : ''; + + // 校验扩展名 + if (!Widget_Upload::checkFileType($originalExt)) { + return array('error' => 1, 'message' => '不允许上传此类型文件'); + } + + $option = self::getConfig(); + + if (empty($option->bucket) || empty($option->accesskey) || empty($option->secretkey) || empty($option->domain)) { + return array('error' => 1, 'message' => '请先正确配置七牛云插件'); + } + + $date = new Typecho_Date(Typecho_Widget::widget('Widget_Options')->gmtTime); + + // 提取EXIF信息 + $exifData = array(); + if ($option->exif && in_array($originalExt, array('jpg', 'jpeg'))) { + $exifData = self::extractExif($file['tmp_name']); + } + + // 处理图片压缩和转换 + $tempFile = null; + $uploadPath = $file['tmp_name']; + $finalExt = $originalExt; + + $supportedImages = array('jpg', 'jpeg', 'png', 'gif'); + if (in_array($originalExt, $supportedImages)) { + $quality = isset($option->quality) ? intval($option->quality) : 85; + $quality = max(1, min(100, $quality)); + + $targetExt = $originalExt; + if ($option->webp && self::canConvertWebp() && $originalExt != 'gif') { + $targetExt = 'webp'; + } + + if ($targetExt != $originalExt || $quality != 85) { + $processedPath = self::compressAndConvert($file['tmp_name'], $targetExt, $quality); + + if ($processedPath != $file['tmp_name']) { + $uploadPath = $processedPath; + $finalExt = $targetExt; + $tempFile = $processedPath; + } + } + } + + // 生成存储路径 - 保持原始文件名,只替换文件系统不允许的字符 + // 处理文件名,保留中文字符,只替换特殊字符 + $safeName = $baseName; + + // 替换文件系统不允许的字符,但保留中文字符 + $safeName = preg_replace('/[<>:"\/\\|?*]/', '_', $safeName); + // 移除首尾空格 + $safeName = trim($safeName); + // 如果名称为空,使用时间戳 + if (empty($safeName)) { + $safeName = 'image_' . time(); + } + + if (isset($content)) { + $filePath = $content['attachment']->path; + self::deleteFile($filePath); + } else { + $savepath = preg_replace(array('/\{year\}/', '/\{month\}/', '/\{day\}/', '/\{random\}/'), + array($date->year, $date->month, $date->day, uniqid()), + $option->savepath); + $filePath = rtrim($savepath, '/') . '/' . $safeName . '.' . $finalExt; + } + + if (!file_exists($uploadPath)) { + return array('error' => 1, 'message' => '上传文件不存在'); + } + + try { + $upManager = new UploadManager(); + $auth = new Auth($option->accesskey, $option->secretkey); + $token = $auth->uploadToken($option->bucket); + + // 七牛云SDK会自动处理文件名编码 + list($ret, $error) = $upManager->putFile($token, $filePath, $uploadPath); + + if ($tempFile && file_exists($tempFile) && $tempFile != $file['tmp_name']) { + @unlink($tempFile); + } + + if ($error == null) { + $rawUrl = Typecho_Common::url($filePath, $option->domain); + $processedUrl = self::applyQiniuStyle($rawUrl, $option); + + $fileSize = @filesize($uploadPath); + + $result = array( + 'name' => $originalName, // 使用原始文件名 + 'path' => $filePath, + 'size' => $fileSize ? (int)$fileSize : 0, + 'type' => $finalExt, + 'mime' => $finalExt == 'webp' ? 'image/webp' : Typecho_Common::mimeContentType($uploadPath), + 'url' => $processedUrl + ); + + if (!empty($exifData)) { + $result['exif'] = $exifData; + } + + return $result; + } else { + return array('error' => 1, 'message' => '七牛云上传失败: ' . $error->message()); + } + } catch (Exception $e) { + if ($tempFile && file_exists($tempFile) && $tempFile != $file['tmp_name']) { + @unlink($tempFile); + } + return array('error' => 1, 'message' => '上传异常: ' . $e->getMessage()); + } + } + + // 上传文件处理函数 + public static function uploadHandle($file) + { + return self::uploadFile($file); + } + + // 修改文件处理函数 + public static function modifyHandle($content, $file) + { + return self::uploadFile($file, $content); + } + + // 删除文件处理函数 + public static function deleteHandle(array $content) + { + if (isset($content['attachment'])) { + self::deleteFile($content['attachment']->path); + } + } + + // 获取实际文件绝对访问路径 + public static function attachmentHandle(array $content) + { + $option = self::getConfig(); + if (isset($content['attachment'])) { + $rawUrl = Typecho_Common::url($content['attachment']->path, $option->domain); + return self::applyQiniuStyle($rawUrl, $option); + } + return ''; + } +} \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..97d16ff --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "qiniu/php-sdk": "^7.14" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..e20d4a2 --- /dev/null +++ b/composer.lock @@ -0,0 +1,142 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e53b7aa650906cf4c75020ac18b97155", + "packages": [ + { + "name": "myclabs/php-enum", + "version": "1.8.5", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/e7be26966b7398204a234f8673fdad5ac6277802", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2 || ^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "https://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2025-01-14T11:49:03+00:00" + }, + { + "name": "qiniu/php-sdk", + "version": "v7.14.0", + "source": { + "type": "git", + "url": "https://github.com/qiniu/php-sdk.git", + "reference": "ee752ffa7263ce99fca0bd7340cf13c486a3516c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/qiniu/php-sdk/zipball/ee752ffa7263ce99fca0bd7340cf13c486a3516c", + "reference": "ee752ffa7263ce99fca0bd7340cf13c486a3516c", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-xml": "*", + "myclabs/php-enum": "~1.5.2 || ~1.6.6 || ~1.7.7 || ~1.8.4", + "php": ">=5.3.3" + }, + "require-dev": { + "paragonie/random_compat": ">=2", + "phpunit/phpunit": "^4.8 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4", + "squizlabs/php_codesniffer": "^2.3 || ~3.6" + }, + "type": "library", + "autoload": { + "files": [ + "src/Qiniu/functions.php", + "src/Qiniu/Http/Middleware/Middleware.php" + ], + "psr-4": { + "Qiniu\\": "src/Qiniu" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Qiniu", + "email": "sdk@qiniu.com", + "homepage": "http://www.qiniu.com" + } + ], + "description": "Qiniu Resource (Cloud) Storage SDK for PHP", + "homepage": "http://developer.qiniu.com/", + "keywords": [ + "cloud", + "qiniu", + "sdk", + "storage" + ], + "support": { + "issues": "https://github.com/qiniu/php-sdk/issues", + "source": "https://github.com/qiniu/php-sdk/tree/v7.14.0" + }, + "time": "2024-10-25T08:39:01+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/php-sdk/.github/workflows/test-ci.yml b/php-sdk/.github/workflows/test-ci.yml new file mode 100644 index 0000000..00f964e --- /dev/null +++ b/php-sdk/.github/workflows/test-ci.yml @@ -0,0 +1,71 @@ +name: PHP CI with Composer + +on: [push, pull_request] + +jobs: + build: + strategy: + fail-fast: false + matrix: + php-versions: ['5.3', '5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup php for mock server + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: '1.21.x' + + - name: Setup build-in server + run: | + nohup php -S localhost:9000 -t ./tests/mock-server/ > phpd.log 2>&1 & + echo $! > mock-server.pid + + cd tests/socks5-server/ + nohup go run main.go > ../../socks5.log 2>&1 & + echo $! > ../../socks-server.pid + + - name: Setup php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + + - name: Install dependencies + run: | + composer self-update + composer install --no-interaction --prefer-source --dev + + - name: Run cases + run: | + ./vendor/bin/phpcs --standard=PSR2 src + ./vendor/bin/phpcs --standard=PSR2 examples + ./vendor/bin/phpcs --standard=PSR2 tests + ./vendor/bin/phpunit --coverage-clover=coverage.xml + cat mock-server.pid | xargs kill + cat socks-server.pid | xargs kill + + env: + QINIU_ACCESS_KEY: ${{ secrets.QINIU_ACCESS_KEY }} + QINIU_SECRET_KEY: ${{ secrets.QINIU_SECRET_KEY }} + QINIU_TEST_BUCKET: ${{ secrets.QINIU_TEST_BUCKET }} + QINIU_TEST_DOMAIN: ${{ secrets.QINIU_TEST_DOMAIN }} + + - name: Print mock server log + if: ${{ failure() }} + run: | + cat phpd.log + + - name: Print socks5 server log + if: ${{ failure() }} + run: | + cat socks5.log + + - name: After_success + run: bash <(curl -s https://codecov.io/bash) diff --git a/php-sdk/.github/workflows/version-check.yml b/php-sdk/.github/workflows/version-check.yml new file mode 100644 index 0000000..983a98f --- /dev/null +++ b/php-sdk/.github/workflows/version-check.yml @@ -0,0 +1,19 @@ +name: PHP SDK Version Check +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" +jobs: + linux: + name: Version Check + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Set env + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV + - name: Check + run: | + set -e + grep -qF "## ${RELEASE_VERSION}" CHANGELOG.md + grep -qF "const SDK_VER = '${RELEASE_VERSION}';" src/Qiniu/Config.php diff --git a/php-sdk/.gitignore b/php-sdk/.gitignore new file mode 100644 index 0000000..4c842c8 --- /dev/null +++ b/php-sdk/.gitignore @@ -0,0 +1,12 @@ +*.phar +*.zip +build/artifacts +phpunit.xml +phpunit.functional.xml +.DS_Store +.swp +.build +composer.lock +vendor +src/package.xml +.idea/ diff --git a/php-sdk/.scrutinizer.yml b/php-sdk/.scrutinizer.yml new file mode 100644 index 0000000..6a2d0d8 --- /dev/null +++ b/php-sdk/.scrutinizer.yml @@ -0,0 +1,42 @@ +filter: + excluded_paths: [tests/*] +checks: + php: + code_rating: true + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true +tools: + external_code_coverage: + timeout: 1200 + runs: 3 + php_analyzer: true + php_code_coverage: false + php_code_sniffer: + config: + standard: PSR2 + filter: + paths: ['src'] + php_loc: + enabled: true + excluded_dirs: [vendor, tests] + php_cpd: + enabled: true + excluded_dirs: [vendor, tests] +build: + nodes: + analysis: + tests: + override: + - php-scrutinizer-run + diff --git a/php-sdk/CHANGELOG.md b/php-sdk/CHANGELOG.md new file mode 100644 index 0000000..6f8da5d --- /dev/null +++ b/php-sdk/CHANGELOG.md @@ -0,0 +1,196 @@ +# Changelog + +## 7.14.0 (2024-10-16) +* 对象存储,持久化处理支持工作流模版 + +## 7.13.0 (2024-09-05) +* 对象存储,验证回调方法新增支持 Qiniu 签名 +* 对象存储,调整查询空间区域域名顺序与默认空间管理域名 +* 支持闲时任务配置 + +## 7.12.1 (2024-02-21) +* 对象存储,添加上传策略部分字段 + +## 7.12.0 (2023-12-11) +* 对象存储,支持归档直读存储 +* 对象存储,批量操作支持自动查询 rs 服务域名 + +## 7.11.0 (2023-09-05) +* 支持代理 + +## 7.10.1 (2023-08-04) +* 修复部分 API 调用中间件合并失败(#417) + +## 7.10.0 (2023-06-20) +* 对象存储,新增请求中间件逻辑,方便拓展请求逻辑 +* 对象存储,新增备用 UC 域名用于查询区域域名 +* 对象存储,修复分片上传初始化失败无法快速失败 +* 对象存储,移除首尔区域 + +## 7.9.0 (2023-03-31) +* 对象存储,修复无法对 key 为空字符串的对象进行操作 +* 修复 301 重定向无法正确获取 header 信息 +* 对象存储,新增查询区域域名过期时间 +* 对象存储,更新获取区域域名的接口 +* 对象存储,更新查询 bucket 域名为 uc 服务 +* 对象存储,新增 uc 服务可配置 + +## 7.8.0 (2022-10-25) +* 移除不推荐域名,并增加区域亚太-首尔和华东-浙江2 +* 对象存储,修复断点上传的文件内容不正确 +* 对象存储,优化分片上传 ctx 超时检测 + +## 7.7.0 (2022-09-02) +* 对象存储,新增支持设置文件级别生命周期 setObjectLifecycle API +* 对象存储,内置增加七牛新建存储区域域名信息 +* 修复当前已知问题 + +## 7.6.0 (2022-06-08) +* 对象存储,管理类 API 发送请求时增加 [X-Qiniu-Date](https://developer.qiniu.com/kodo/3924/common-request-headers) (生成请求的时间) header + + +## 7.5.0 (2022-04-18) +* 对象存储,新增支持 [深度归档存储类型](https://developer.qiniu.com/kodo/3956/kodo-category#deep_archive) + +## 7.4.3 (2022-04-01) +* 优化签名算法逻辑 + +## 7.4.2(2022-03-01) +* 修复已知关于请求 Header 处理不当问题,比如没有处理为大小写不敏感等问题 + +## 7.4.1(2021-09-24) +* 修复了 分片上传 v2 已知问题,明确给出了参数不合理情况下对应的错误提示信息 + +## 7.4.0 (2021-07-19) +* 【对象存储】支持 [分片上传 v2](https://developer.qiniu.com/kodo/7458/multipartupload) 和 断点续传,使用方式见 [开发者文档](https://developer.qiniu.com/kodo/1241/php#resume-upload-file) + +## 7.3.0 (2020-09-24) +### 新增 +* 【对象存储】增加异步抓取方法与demo +* 【融合cdn】增加查询CDN刷新记录、查询CDN预取记录方法与demo +* 【云短信】增加查询短信发送记录的方法 +* 【实时音视频】增加rtc停止房间的合流转推方法 +* 【内容审核】增加图片审核、视频审核方法与demo + +### 修复 +* 【对象存储】修复签算 token 时上传策略中的 forceSaveKey 字段不生效的问题 +* 【对象存储】修复更新空间事件通知规则方法 + +### 优化 +* 【对象存储】创建空间迁移到mkbucketv3 api +* 优化对 http2 返回头的判断 +* 优化 demo 中的文档注释说明 +* docs 目录下的 rtc demo 移动至 examples/rtc 目录下 +* docs 目录下的 sms demo 移动至 examples/sms 目录下 + +## 7.2.10 (2019-10-28) +* 去除云短信类类型指定 +* 修改不传文件名时存在表单上传错误的情况 + +## 7.2.9 (2019-07-09) +* 添加空间管理、云短信接口 +* 去除无效参数 + +## 7.2.7 (2018-11-06) +* 添加 QVM 内网上传到 KODO 的 zone 设置 + +## 7.2.6 (2018-05-18) +* 修复rs,rsf在不同机房默认的https域名 + +## 7.2.5 (2018-05-10) +* 修复表单上传中多余的参数checkCrc导致的fname错位问题 + +## 7.2.4 (2018-05-09) +### 增加 +* 连麦功能 + +## 7.2.3 (2018-01-20) +### 增加 +* 新加坡机房 +### 修正 +* 获取域名的入口域名 +* http回复头部兼容大小写 + +## 7.2.2 (2017-11-06) +### 增加 +* Qiniu算法的鉴权方法 + +## 7.1.4 (2017-06-21) +### 增加 +* cdn 文件/目录 刷新 +* cdn 获取 流量/带宽 +* cdn 获取域名的访问日志列表 +* cdn 对资源链接进行时间戳防盗链签名 + +## 7.1.3 (2016-11-18) +### 增加 +* move, copy操作增加force参数 + +## 7.1.2 (2016-11-12) +### 修正 +* 明确抛出获取各区域域名失败时的报错 + +## 7.1.1 (2016-11-02) +### 修正 +* 多区域配置文件存储目录从home修改到tmp目录 + + +## 7.1.0 (2016-10-22) +### 增加 +* 多存储区域的支持 + +## 7.0.8 (2016-07-19) +### 增加 +* demo +* https url 支持 +* deleteAfterDays 策略 +* 添加图片处理链接统一拼接方法 by @SherlockRen + +## 7.0.7 (2016-01-12) +### 修正 +* PersistentFop参数pipeline和notify_url失效 +* resume 模式 close file inputstream + +## 7.0.6 (2015-12-05) +### 修正 +* php7.0 Json 对空字符串解析单元测试报错 +* 开启安全模式或者设置可操作目录树时,设置CURLOPT_FOLLOWLOCATION报错, by @twocabbages +* fetch 支持不指定key, by @sinkcup + +## 7.0.5 (2015-10-29) +### 增加 +* 增加上传策略最小文件大小限制 fsizeMin +* 增加常见examples + +## 7.0.4 (2015-07-23) +### 修正 +* 一些地方的严格比较检查 +* resumeupload 备用地址失效 + +## 7.0.3 (2015-07-10) +### 修改 +* 多zone 支持 + +## 7.0.2 (2015-04-18) +### 修改 +* fetch 接口返回内容调整 +* pfop 接口调整 + +###修正 +* exception 类调用 + +## 7.0.1 (2015-03-27) +### 增加 +* 增加代码注释 + +## 7.0.0 (2015-02-03) + +### 增加 +* 简化上传接口 +* 自动选择断点续上传还是直传 +* 重构代码,接口和内部结构更清晰 +* 改变mime +* 代码覆盖度报告 +* policy改为array, 便于灵活增加,并加入过期字段检查 +* 文件列表支持目录形式 +* 利用元编程方式支持 fop 和 pfop diff --git a/php-sdk/CONTRIBUTING.md b/php-sdk/CONTRIBUTING.md new file mode 100644 index 0000000..0466bf9 --- /dev/null +++ b/php-sdk/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# 贡献代码指南 + +我们非常欢迎大家来贡献代码,我们会向贡献者致以最诚挚的敬意。 + +一般可以通过在Github上提交[Pull Request](https://github.com/qiniu/php-sdk)来贡献代码。 + +## Pull Request要求 + +- **[PSR-2 编码风格标准](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** 。要通过项目中的code sniffer检查。 + +- **代码格式** 提交前 请按 ./vendor/bin/phpcbf --standard=PSR2 进行格式化。 + +- **必须添加测试!** - 如果没有测试(单元测试、集成测试都可以),那么提交的补丁是不会通过的。 + +- **记得更新文档** - 保证`README.md`以及其他相关文档及时更新,和代码的变更保持一致性。 + +- **考虑我们的发布周期** - 我们的版本号会服从[SemVer v2.0.0](http://semver.org/),我们绝对不会随意变更对外的API。 + +- **创建feature分支** - 最好不要从你的master分支提交 pull request。 + +- **一个feature提交一个pull请求** - 如果你的代码变更了多个操作,那就提交多个pull请求吧。 + +- **清晰的commit历史** - 保证你的pull请求的每次commit操作都是有意义的。如果你开发中需要执行多次的即时commit操作,那么请把它们放到一起再提交pull请求。 + +## 运行测试 + +``` bash +./vendor/bin/phpunit tests/Qiniu/Tests/ + +``` diff --git a/php-sdk/LICENSE b/php-sdk/LICENSE new file mode 100644 index 0000000..ba646be --- /dev/null +++ b/php-sdk/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Qiniu, Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/php-sdk/README.md b/php-sdk/README.md new file mode 100644 index 0000000..784d735 --- /dev/null +++ b/php-sdk/README.md @@ -0,0 +1,76 @@ +# Qiniu Cloud SDK for PHP +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) +[![Build Status](https://travis-ci.org/qiniu/php-sdk.svg)](https://travis-ci.org/qiniu/php-sdk) +[![GitHub release](https://img.shields.io/github/v/tag/qiniu/php-sdk.svg?label=release)](https://github.com/qiniu/php-sdk/releases) +[![Latest Stable Version](https://img.shields.io/packagist/v/qiniu/php-sdk.svg)](https://packagist.org/packages/qiniu/php-sdk) +[![Total Downloads](https://img.shields.io/packagist/dt/qiniu/php-sdk.svg)](https://packagist.org/packages/qiniu/php-sdk) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/qiniu/php-sdk/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/qiniu/php-sdk/?branch=master) +[![Coverage Status](https://codecov.io/gh/qiniu/php-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/qiniu/php-sdk) +[![Join Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/qiniu/php-sdk?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![@qiniu on weibo](http://img.shields.io/badge/weibo-%40qiniutek-blue.svg)](http://weibo.com/qiniutek) + + +## 安装 + +推荐使用 `composer` 进行安装。可以使用 composer.json 声明依赖,或者运行下面的命令。SDK 包已经放到这里 [`qiniu/php-sdk`][install-packagist] 。 + +```bash +$ composer require qiniu/php-sdk +``` + +## 运行环境 + +| Qiniu SDK版本 | PHP 版本 | +|:--------------------:|:-----------------------------------------------:| +| 7.x | cURL extension, 5.3 - 5.6, 7.0 - 7.4, 8.0-8.1 | +| 6.x | cURL extension, 5.2 - 5.6 | + +## 使用方法 + +### 上传 +```php +use Qiniu\Storage\UploadManager; +use Qiniu\Auth; +... + $uploadMgr = new UploadManager(); + $auth = new Auth($accessKey, $secretKey); + $token = $auth->uploadToken($bucket); + list($ret, $error) = $uploadMgr->putFile($token, 'key', 'filePath'); +... +``` + +## 测试 + +``` bash +$ ./vendor/bin/phpunit tests/Qiniu/Tests/ +``` + +## 常见问题 + +- `$error` 保留了请求响应的信息,失败情况下 `ret` 为 `none`, 将 `$error` 可以打印出来,提交给我们。 +- API 的使用 demo 可以参考 [examples](https://github.com/qiniu/php-sdk/tree/master/examples)。 + +## 代码贡献 + +详情参考[代码提交指南](https://github.com/qiniu/php-sdk/blob/master/CONTRIBUTING.md)。 + +## 贡献记录 + +- [所有贡献者](https://github.com/qiniu/php-sdk/contributors) + +## 联系我们 + +- 如果需要帮助,请提交工单(在portal右侧点击咨询和建议提交工单,或者直接向 support@qiniu.com 发送邮件) +- 如果有什么问题,可以到问答社区提问,[问答社区](https://qiniu.segmentfault.com/) +- 更详细的文档,见[官方文档站](https://developer.qiniu.com/) +- 如果发现了 bug, 欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues) +- 如果有功能需求,欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues) +- 如果要提交代码,欢迎提交 pull request +- 欢迎关注我们的[微信](https://www.qiniu.com/#weixin) [微博](https://weibo.com/qiniutek),及时获取动态信息。 + +## 代码许可 + +The MIT License (MIT).详情见 [License文件](https://github.com/qiniu/php-sdk/blob/master/LICENSE). + +[packagist]: http://packagist.org +[install-packagist]: https://packagist.org/packages/qiniu/php-sdk diff --git a/php-sdk/autoload.php b/php-sdk/autoload.php new file mode 100644 index 0000000..9efddd7 --- /dev/null +++ b/php-sdk/autoload.php @@ -0,0 +1,19 @@ +=5.3.3", + "ext-xml": "*", + "ext-curl": "*", + "myclabs/php-enum": "~1.5.2 || ~1.6.6 || ~1.7.7 || ~1.8.4" + }, + "require-dev": { + "paragonie/random_compat": ">=2", + "phpunit/phpunit": "^4.8 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4", + "squizlabs/php_codesniffer": "^2.3 || ~3.6" + }, + "autoload": { + "psr-4": { + "Qiniu\\": "src/Qiniu" + }, + "files": [ + "src/Qiniu/functions.php", + "src/Qiniu/Http/Middleware/Middleware.php" + ] + } +} diff --git a/php-sdk/examples/README.md b/php-sdk/examples/README.md new file mode 100644 index 0000000..b7b4f98 --- /dev/null +++ b/php-sdk/examples/README.md @@ -0,0 +1,10 @@ +# examples + +这些 examples 旨在帮助你快速了解使用七牛的 SDK。这些 demo 都是可以直接运行的, 但是在运行之前需要填上您自己的参数。 + +比如: + +* `$bucket` 需要填上您想操作的 [bucket名字](https://portal.qiniu.com/kodo/bucket)。 +* `$accessKey` 和 `$secretKey` 可以在我们的[管理后台](https://portal.qiniu.com/user/key)找到。 +* 在进行`视频转码`, `压缩文件`等异步操作时 需要使用到的队列名称也可以在我们[管理后台](https://portal.qiniu.com/dora/media-gate/pipeline)新建。 + diff --git a/php-sdk/examples/bucket_lifecycleRule.php b/php-sdk/examples/bucket_lifecycleRule.php new file mode 100644 index 0000000..f51524c --- /dev/null +++ b/php-sdk/examples/bucket_lifecycleRule.php @@ -0,0 +1,42 @@ +bucketLifecycleRule( + $bucket, + $name, + $prefix, + $delete_after_days, + $to_line_after_days, + $to_archive_after_days, + $to_deep_archive_after_days, + $to_archive_ir_after_days +); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/cdn_get_bandwidth.php b/php-sdk/examples/cdn_get_bandwidth.php new file mode 100644 index 0000000..c9de0e6 --- /dev/null +++ b/php-sdk/examples/cdn_get_bandwidth.php @@ -0,0 +1,41 @@ +getBandwidthData( + $domains, + $startDate, + $endDate, + $granularity +); + +if ($getBandwidthErr != null) { + var_dump($getBandwidthErr); +} else { + echo "get bandwidth data success\n"; + print_r($bandwidthData); +} diff --git a/php-sdk/examples/cdn_get_flux.php b/php-sdk/examples/cdn_get_flux.php new file mode 100644 index 0000000..57df808 --- /dev/null +++ b/php-sdk/examples/cdn_get_flux.php @@ -0,0 +1,35 @@ +getFluxData($domains, $startDate, $endDate, $granularity); +if ($getFluxErr != null) { + var_dump($getFluxErr); +} else { + echo "get flux data success\n"; + print_r($fluxData); +} diff --git a/php-sdk/examples/cdn_get_log_list.php b/php-sdk/examples/cdn_get_log_list.php new file mode 100644 index 0000000..2b3f7dd --- /dev/null +++ b/php-sdk/examples/cdn_get_log_list.php @@ -0,0 +1,31 @@ +getCdnLogList($domains, $logDate); +if ($getLogErr != null) { + var_dump($getLogErr); +} else { + echo "get cdn log list success\n"; + print_r($logListData); +} diff --git a/php-sdk/examples/cdn_get_prefetch_list.php b/php-sdk/examples/cdn_get_prefetch_list.php new file mode 100644 index 0000000..958e5eb --- /dev/null +++ b/php-sdk/examples/cdn_get_prefetch_list.php @@ -0,0 +1,46 @@ +getCdnPrefetchList( + $requestId, + $urls, + $state, + $pageNo, + $pageSize, + $startTime, + $endTime +); +echo "\n====> query prefetch list: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/cdn_get_refresh_list.php b/php-sdk/examples/cdn_get_refresh_list.php new file mode 100644 index 0000000..ad4fca2 --- /dev/null +++ b/php-sdk/examples/cdn_get_refresh_list.php @@ -0,0 +1,48 @@ +getCdnRefreshList( + $requestId, + $isDir, + $urls, + $state, + $pageNo, + $pageSize, + $startTime, + $endTime +); +echo "\n====> query refresh list: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/cdn_refresh_urls_dirs.php b/php-sdk/examples/cdn_refresh_urls_dirs.php new file mode 100644 index 0000000..2140378 --- /dev/null +++ b/php-sdk/examples/cdn_refresh_urls_dirs.php @@ -0,0 +1,59 @@ +refreshUrlsAndDirs($urls, $dirs); +if ($refreshErr != null) { + var_dump($refreshErr); +} else { + echo "refresh request sent\n"; + print_r($refreshResult); +} + +//---------------------------------------- demo2 ---------------------------------------- +// 刷新文件 + +list($refreshResult, $refreshErr) = $cdnManager->refreshUrls($urls); +if ($refreshErr != null) { + var_dump($refreshErr); +} else { + echo "refresh urls request sent\n"; + print_r($refreshResult); +} + +//---------------------------------------- demo3 ---------------------------------------- +// 刷新目录 + +list($refreshResult, $refreshErr) = $cdnManager->refreshDirs($dirs); +if ($refreshErr != null) { + var_dump($refreshErr); +} else { + echo "refresh dirs request sent\n"; + print_r($refreshResult); +} diff --git a/php-sdk/examples/cdn_timestamp_antileech.php b/php-sdk/examples/cdn_timestamp_antileech.php new file mode 100644 index 0000000..f2d7855 --- /dev/null +++ b/php-sdk/examples/cdn_timestamp_antileech.php @@ -0,0 +1,20 @@ +censorImage($body); +echo "\n====> Result is: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/censor_video.php b/php-sdk/examples/censor_video.php new file mode 100644 index 0000000..7ac056f --- /dev/null +++ b/php-sdk/examples/censor_video.php @@ -0,0 +1,52 @@ +censorVideo($body); +echo "\n====> Result is: \n"; +if ($err !== null) { + var_dump($err); +} else { + echo "job_id is: $jobid\n"; +} + +// 查询视频审核结果 +list($ret, $err) = $argusManager->censorStatus($jobid); +echo "\n====> job status: \n"; + +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/delete_bucket.php b/php-sdk/examples/delete_bucket.php new file mode 100644 index 0000000..325a47a --- /dev/null +++ b/php-sdk/examples/delete_bucket.php @@ -0,0 +1,27 @@ +deleteBucket($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/delete_bucketEvent.php b/php-sdk/examples/delete_bucketEvent.php new file mode 100644 index 0000000..7eb744d --- /dev/null +++ b/php-sdk/examples/delete_bucketEvent.php @@ -0,0 +1,28 @@ +deleteBucketEvent($bucket, $name); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/delete_bucketLifecycleRule.php b/php-sdk/examples/delete_bucketLifecycleRule.php new file mode 100644 index 0000000..2146b1b --- /dev/null +++ b/php-sdk/examples/delete_bucketLifecycleRule.php @@ -0,0 +1,27 @@ +deleteBucketLifecycleRule($bucket, $name); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/get_bucketEvents.php b/php-sdk/examples/get_bucketEvents.php new file mode 100644 index 0000000..2379584 --- /dev/null +++ b/php-sdk/examples/get_bucketEvents.php @@ -0,0 +1,26 @@ +getBucketEvents($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/get_bucketLifecycleRules.php b/php-sdk/examples/get_bucketLifecycleRules.php new file mode 100644 index 0000000..a35feed --- /dev/null +++ b/php-sdk/examples/get_bucketLifecycleRules.php @@ -0,0 +1,26 @@ +getBucketLifecycleRules($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/get_bucketList.php b/php-sdk/examples/get_bucketList.php new file mode 100644 index 0000000..6a2f7b0 --- /dev/null +++ b/php-sdk/examples/get_bucketList.php @@ -0,0 +1,26 @@ +listbuckets($region); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/get_bucketQuota.php b/php-sdk/examples/get_bucketQuota.php new file mode 100644 index 0000000..93474b5 --- /dev/null +++ b/php-sdk/examples/get_bucketQuota.php @@ -0,0 +1,26 @@ +getBucketQuota($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/get_bucketinfo.php b/php-sdk/examples/get_bucketinfo.php new file mode 100644 index 0000000..98fd9f7 --- /dev/null +++ b/php-sdk/examples/get_bucketinfo.php @@ -0,0 +1,25 @@ +bucketInfo($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/get_bucketinfos.php b/php-sdk/examples/get_bucketinfos.php new file mode 100644 index 0000000..5eec1d8 --- /dev/null +++ b/php-sdk/examples/get_bucketinfos.php @@ -0,0 +1,26 @@ +bucketInfos($region); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/get_corsRules.php b/php-sdk/examples/get_corsRules.php new file mode 100644 index 0000000..58e28be --- /dev/null +++ b/php-sdk/examples/get_corsRules.php @@ -0,0 +1,26 @@ +getCorsRules($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/image_url_builder.php b/php-sdk/examples/image_url_builder.php new file mode 100644 index 0000000..20e2b00 --- /dev/null +++ b/php-sdk/examples/image_url_builder.php @@ -0,0 +1,74 @@ + + */ +$thumbLink = $imageUrlBuilder->thumbnail($url, 1, 100, 100); + +// 函数方式调用 也可拼接多个操作参数 图片+水印 +$thumbLink2 = \Qiniu\thumbnail($url2, 1, 100, 100); +var_dump($thumbLink, $thumbLink2); + +/** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param int $dissolve 透明度 [可选] + * @param string $gravity 水印位置 [可选] + * @param int $dx 横轴边距 [可选] + * @param int $dy 纵轴边距 [可选] + * @param int $watermarkScale 自适应原图的短边比例 [可选] + * @link https://developer.qiniu.com/dora/api/1316/image-watermarking-processing-watermark + * @return string + * @author Sherlock Ren + */ +$waterLink = $imageUrlBuilder->waterImg($url, $waterImage); +// 函数调用方法 +//$waterLink = \Qiniu\waterImg($url, $waterImage); +var_dump($waterLink); + +/** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 [可选] + * @param int $dissolve 透明度 [可选] + * @param string $gravity 水印位置 [可选] + * @param int $dx 横轴边距 [可选] + * @param int $dy 纵轴边距 [可选] + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @return string + * @author Sherlock Ren + */ +$textLink = $imageUrlBuilder->waterText($url, '你瞅啥', '微软雅黑', 300); +// 函数调用方法 +// $textLink = \Qiniu\waterText($url, '你瞅啥', '微软雅黑', 300); +var_dump($textLink); diff --git a/php-sdk/examples/persistent_fop_init.php b/php-sdk/examples/persistent_fop_init.php new file mode 100644 index 0000000..baca846 --- /dev/null +++ b/php-sdk/examples/persistent_fop_init.php @@ -0,0 +1,18 @@ +useHTTPS=true; + +// 初始化 +$pfop = new PersistentFop($auth, $config); diff --git a/php-sdk/examples/persistent_fop_status.php b/php-sdk/examples/persistent_fop_status.php new file mode 100644 index 0000000..73e85a3 --- /dev/null +++ b/php-sdk/examples/persistent_fop_status.php @@ -0,0 +1,19 @@ +status($persistentId); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/pfop_mkzip.php b/php-sdk/examples/pfop_mkzip.php new file mode 100644 index 0000000..fb95cc2 --- /dev/null +++ b/php-sdk/examples/pfop_mkzip.php @@ -0,0 +1,58 @@ +execute($bucket, $key, $fops, $pipeline, $notify_url, $force); + +echo "\n====> pfop mkzip result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +// 查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop mkzip status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/pfop_vframe.php b/php-sdk/examples/pfop_vframe.php new file mode 100644 index 0000000..49fd36d --- /dev/null +++ b/php-sdk/examples/pfop_vframe.php @@ -0,0 +1,55 @@ +useHTTPS = true; +$pfop = new PersistentFop($auth, $config); + +// 视频处理完毕后保存到空间中的名称 +$saveasKey = 'qiniu_480x360.jpg'; + +// 进行视频截帧操作 +$fops = "vframe/jpg/offset/1/w/480/h/360/rotate/90|saveas/" . + \Qiniu\base64_urlSafeEncode("$bucket:$saveasKey"); + +list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force); +echo "\n====> pfop avthumb result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +// 查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/pfop_video_avthumb.php b/php-sdk/examples/pfop_video_avthumb.php new file mode 100644 index 0000000..986aa8c --- /dev/null +++ b/php-sdk/examples/pfop_video_avthumb.php @@ -0,0 +1,55 @@ +useHTTPS=true; + +// 视频处理完毕后保存到空间中的名称 +$saveasKey = 'qiniu_640x360.mp4'; + +$pfop = new PersistentFop($auth, $config); + +// 进行视频转码操作 +$fops = "avthumb/mp4/s/640x360/vb/1.4m|saveas/" . \Qiniu\base64_urlSafeEncode("$bucket:$saveasKey"); + +list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force); +echo "\n====> pfop avthumb result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +// 查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/pfop_watermark.php b/php-sdk/examples/pfop_watermark.php new file mode 100644 index 0000000..ea3d6bc --- /dev/null +++ b/php-sdk/examples/pfop_watermark.php @@ -0,0 +1,59 @@ +useHTTPS=true; +$pfop = new PersistentFop($auth, $config); + +// 图片水印的源路径,也就是给视频打图片水印的图片 +$base64URL = Qiniu\base64_urlSafeEncode('http://test-2.qiniudn.com/logo.png'); + +// 视频处理完毕后保存到空间中的名称 +$saveasKey = 'qiniu_watermark.mp4'; + +// 进行视频打图片水印操作 +$fops = "avthumb/mp4/wmImage/" . $base64URL . "|saveas/" + . \Qiniu\base64_urlSafeEncode("$bucket:$saveasKey"); + +list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force); +echo "\n====> pfop avthumb result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +// 查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/php-logo.png b/php-sdk/examples/php-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..77e051fe413b2754bc121876b5f986d028a8449a GIT binary patch literal 65062 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 NC#5X;17sTf{{R#))Y1R| literal 0 HcmV?d00001 diff --git a/php-sdk/examples/prefop.php b/php-sdk/examples/prefop.php new file mode 100644 index 0000000..1b8950a --- /dev/null +++ b/php-sdk/examples/prefop.php @@ -0,0 +1,27 @@ +useHTTPS=true; + +$pfop = new PersistentFop($auth, $config); + +$id = "z2.01z201c4oyre6q1hgy00murnel0002nh"; + +// 查询持久化处理的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/put_bucketAccessMode.php b/php-sdk/examples/put_bucketAccessMode.php new file mode 100644 index 0000000..638ae3c --- /dev/null +++ b/php-sdk/examples/put_bucketAccessMode.php @@ -0,0 +1,27 @@ +putBucketAccessMode($bucket, $private); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/put_bucketAccessStyleMode.php b/php-sdk/examples/put_bucketAccessStyleMode.php new file mode 100644 index 0000000..3cc2aec --- /dev/null +++ b/php-sdk/examples/put_bucketAccessStyleMode.php @@ -0,0 +1,27 @@ +putBucketAccessStyleMode($bucket, $mode); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/put_bucketEvent.php b/php-sdk/examples/put_bucketEvent.php new file mode 100644 index 0000000..f3c830d --- /dev/null +++ b/php-sdk/examples/put_bucketEvent.php @@ -0,0 +1,32 @@ +putBucketEvent($bucket, $name, $prefix, $suffix, $event, $callbackURL); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/put_bucketMaxAge.php b/php-sdk/examples/put_bucketMaxAge.php new file mode 100644 index 0000000..4890174 --- /dev/null +++ b/php-sdk/examples/put_bucketMaxAge.php @@ -0,0 +1,27 @@ +putBucketMaxAge($bucket, $maxAge); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/put_bucketQuota.php b/php-sdk/examples/put_bucketQuota.php new file mode 100644 index 0000000..b00ec48 --- /dev/null +++ b/php-sdk/examples/put_bucketQuota.php @@ -0,0 +1,29 @@ +putBucketQuota($bucket, $size, $count); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/put_referAntiLeech.php b/php-sdk/examples/put_referAntiLeech.php new file mode 100644 index 0000000..7d56d1e --- /dev/null +++ b/php-sdk/examples/put_referAntiLeech.php @@ -0,0 +1,30 @@ +putReferAntiLeech($bucket, $mode, $norefer, $pattern); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/qetag.php b/php-sdk/examples/qetag.php new file mode 100644 index 0000000..1fe90d1 --- /dev/null +++ b/php-sdk/examples/qetag.php @@ -0,0 +1,14 @@ +useHTTPS = true; // 接口是否使用 HTTPS 协议 + +$bucketManager = new BucketManager($auth, $config); + +// 异步第三方资源抓取 +// 参考文档:https://developer.qiniu.com/kodo/api/4097/asynch-fetch + +// 需要抓取的文件 URL +$url = 'http://devtools.qiniu.com/qiniu.png'; + +//回调 URL(需要可以公网访问,并能够相应 200 OK) +$callbackurl = "http://your.domain.com/upload_verify_callback.php"; + +// 回调Body +$callbackbody = '{"key":"$(key)","hash":"$(etag)","w":"$(imageInfo.width)","h":"$(imageInfo.height)"}'; + + +//---------------------------------------- demo1 ---------------------------------------- +// 指定抓取的文件保存到七牛云空间中的名称 + +$key = time() . '.png'; +list($ret, $err) = $bucketManager->asynchFetch($url, $bucket, null, $key, null, null, $callbackurl, $callbackbody); +echo "=====> asynch fetch $url to bucket: $bucket key: $key\n"; +if ($err !== null) { + var_dump($err); +} else { + $id = $ret['id']; + echo "id is: $id\n"; +} + +//---------------------------------------- demo2 ---------------------------------------- +// 不指定 key 时,以文件内容的 hash 作为文件名 + +$key = null; +list($ret, $err) = $bucketManager->asynchFetch($url, $bucket, null, $key, null, null, $callbackurl, $callbackbody); +echo "=====> asynch fetch $url to bucket: $bucket key: $(etag)\n"; +if ($err !== null) { + var_dump($err); +} else { + $id = $ret['id']; + echo "id is: $id\n"; +} + +// 查询异步抓取的进度和状态 + +// 华东:z0,华北:z1,华南:z2,北美:na0,东南亚:as0 +$zone = 'z2'; + +sleep(10); // 由于异步抓取需要耗时,等待 10 秒后再查询状态 +list($ret, $err) = $bucketManager->asynchFetchStatus($zone, $id); +echo "\n====> asynch fetch status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_batch_change_mime.php b/php-sdk/examples/rs_batch_change_mime.php new file mode 100644 index 0000000..c5bd6b4 --- /dev/null +++ b/php-sdk/examples/rs_batch_change_mime.php @@ -0,0 +1,32 @@ + 'video/x-mp4', + 'qiniu.png' => 'image/x-png', + 'qiniu.jpg' => 'image/x-jpg' +); + +$ops = $bucketManager->buildBatchChangeMime($bucket, $keyMimePairs); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_batch_change_type.php b/php-sdk/examples/rs_batch_change_type.php new file mode 100644 index 0000000..a19d0d4 --- /dev/null +++ b/php-sdk/examples/rs_batch_change_type.php @@ -0,0 +1,45 @@ +batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_batch_copy.php b/php-sdk/examples/rs_batch_copy.php new file mode 100644 index 0000000..66c4d4d --- /dev/null +++ b/php-sdk/examples/rs_batch_copy.php @@ -0,0 +1,40 @@ +buildBatchCopy($srcBucket, $keyPairs, $destBucket, true); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_batch_delete.php b/php-sdk/examples/rs_batch_delete.php new file mode 100644 index 0000000..ebcdbe6 --- /dev/null +++ b/php-sdk/examples/rs_batch_delete.php @@ -0,0 +1,32 @@ +buildBatchDelete($bucket, $keys); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_batch_delete_after_days.php b/php-sdk/examples/rs_batch_delete_after_days.php new file mode 100644 index 0000000..928dd14 --- /dev/null +++ b/php-sdk/examples/rs_batch_delete_after_days.php @@ -0,0 +1,39 @@ +buildBatchDeleteAfterDays($bucket, $keyDayPairs); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_batch_move.php b/php-sdk/examples/rs_batch_move.php new file mode 100644 index 0000000..01d8c91 --- /dev/null +++ b/php-sdk/examples/rs_batch_move.php @@ -0,0 +1,40 @@ +buildBatchMove($srcBucket, $keyPairs, $destBucket, true); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_batch_restore_ar.php b/php-sdk/examples/rs_batch_restore_ar.php new file mode 100644 index 0000000..b2f79d0 --- /dev/null +++ b/php-sdk/examples/rs_batch_restore_ar.php @@ -0,0 +1,41 @@ +batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_batch_stat.php b/php-sdk/examples/rs_batch_stat.php new file mode 100644 index 0000000..88bc32e --- /dev/null +++ b/php-sdk/examples/rs_batch_stat.php @@ -0,0 +1,32 @@ +buildBatchStat($bucket, $keys); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_bucket_domains.php b/php-sdk/examples/rs_bucket_domains.php new file mode 100644 index 0000000..3cc9cb3 --- /dev/null +++ b/php-sdk/examples/rs_bucket_domains.php @@ -0,0 +1,26 @@ +domains($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_buckets.php b/php-sdk/examples/rs_buckets.php new file mode 100644 index 0000000..84263a9 --- /dev/null +++ b/php-sdk/examples/rs_buckets.php @@ -0,0 +1,25 @@ +buckets(true); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_change_mime.php b/php-sdk/examples/rs_change_mime.php new file mode 100644 index 0000000..f4442aa --- /dev/null +++ b/php-sdk/examples/rs_change_mime.php @@ -0,0 +1,29 @@ +changeMime($bucket, $key, $newMime); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_change_status.php b/php-sdk/examples/rs_change_status.php new file mode 100644 index 0000000..bedf61c --- /dev/null +++ b/php-sdk/examples/rs_change_status.php @@ -0,0 +1,29 @@ +changeStatus($bucket, $key, $status); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_change_type.php b/php-sdk/examples/rs_change_type.php new file mode 100644 index 0000000..8b3201f --- /dev/null +++ b/php-sdk/examples/rs_change_type.php @@ -0,0 +1,36 @@ +changeType($bucket, $key, $fileType); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_copy.php b/php-sdk/examples/rs_copy.php new file mode 100644 index 0000000..aae4d96 --- /dev/null +++ b/php-sdk/examples/rs_copy.php @@ -0,0 +1,33 @@ +copy($srcBucket, $srcKey, $destBucket, $destKey, true); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_delete.php b/php-sdk/examples/rs_delete.php new file mode 100644 index 0000000..ad97266 --- /dev/null +++ b/php-sdk/examples/rs_delete.php @@ -0,0 +1,27 @@ +delete($bucket, $key); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_delete_after_days.php b/php-sdk/examples/rs_delete_after_days.php new file mode 100644 index 0000000..96e55de --- /dev/null +++ b/php-sdk/examples/rs_delete_after_days.php @@ -0,0 +1,26 @@ +deleteAfterDays($bucket, $key, $days); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_download_urls.php b/php-sdk/examples/rs_download_urls.php new file mode 100644 index 0000000..e803ddc --- /dev/null +++ b/php-sdk/examples/rs_download_urls.php @@ -0,0 +1,19 @@ +/,一定要带访问协议,也就是 http:// 或者 https:// +$baseUrl = 'http://if-pri.qiniudn.com/qiniu.png?imageView2/1/h/500'; + +// 对链接进行签名,参考文档:https://developer.qiniu.com/kodo/manual/1656/download-private +$signedUrl = $auth->privateDownloadUrl($baseUrl); + +echo $signedUrl; diff --git a/php-sdk/examples/rs_fetch.php b/php-sdk/examples/rs_fetch.php new file mode 100644 index 0000000..5c1a5ab --- /dev/null +++ b/php-sdk/examples/rs_fetch.php @@ -0,0 +1,43 @@ +fetch($url, $bucket, $key); +echo "=====> fetch $url to bucket: $bucket key: $key\n"; +if ($err !== null) { + var_dump($err); +} else { + print_r($ret); +} + +//---------------------------------------- demo2 ---------------------------------------- +// 不指定 key 时,以文件内容的 hash 作为文件名 + +$key = null; +list($ret, $err) = $bucketManager->fetch($url, $bucket, $key); +echo "=====> fetch $url to bucket: $bucket key: $(etag)\n"; +if ($err !== null) { + var_dump($err); +} else { + print_r($ret); +} diff --git a/php-sdk/examples/rs_move.php b/php-sdk/examples/rs_move.php new file mode 100644 index 0000000..a399665 --- /dev/null +++ b/php-sdk/examples/rs_move.php @@ -0,0 +1,29 @@ +move($srcBucket, $srcKey, $destBucket, $destKey, true); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_prefetch.php b/php-sdk/examples/rs_prefetch.php new file mode 100644 index 0000000..28af115 --- /dev/null +++ b/php-sdk/examples/rs_prefetch.php @@ -0,0 +1,25 @@ +prefetch($bucket, $key); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_restore.php b/php-sdk/examples/rs_restore.php new file mode 100644 index 0000000..a3bf070 --- /dev/null +++ b/php-sdk/examples/rs_restore.php @@ -0,0 +1,28 @@ +restoreAr($bucket, $key, 1); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rs_stat.php b/php-sdk/examples/rs_stat.php new file mode 100644 index 0000000..36e863e --- /dev/null +++ b/php-sdk/examples/rs_stat.php @@ -0,0 +1,28 @@ +stat($bucket, $key); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rsf_list_bucket.php b/php-sdk/examples/rsf_list_bucket.php new file mode 100644 index 0000000..97a5838 --- /dev/null +++ b/php-sdk/examples/rsf_list_bucket.php @@ -0,0 +1,47 @@ +listFiles($bucket, $prefix, $marker, $limit, $delimiter); + if ($err !== null) { + echo "\n====> list file err: \n"; + var_dump($err); + } else { + $marker = null; + if (array_key_exists('marker', $ret)) { + $marker = $ret['marker']; + } + echo "Marker: $marker\n"; + echo "\nList Items====>\n"; + //var_dump($ret['items']); + print('items count:' . count($ret['items']) . "\n"); + if (array_key_exists('commonPrefixes', $ret)) { + print_r($ret['commonPrefixes']); + } + } +} while (!empty($marker)); diff --git a/php-sdk/examples/rsf_list_files.php b/php-sdk/examples/rsf_list_files.php new file mode 100644 index 0000000..31c455b --- /dev/null +++ b/php-sdk/examples/rsf_list_files.php @@ -0,0 +1,39 @@ +listFiles($bucket, $prefix, $marker, $limit, $delimiter); +if ($err !== null) { + echo "\n====> list file err: \n"; + var_dump($err); +} else { + if (array_key_exists('marker', $ret)) { + echo "Marker:" . $ret["marker"] . "\n"; + } + echo "\nList Iterms====>\n"; +} diff --git a/php-sdk/examples/rsf_v2list_bucket.php b/php-sdk/examples/rsf_v2list_bucket.php new file mode 100644 index 0000000..5f9d763 --- /dev/null +++ b/php-sdk/examples/rsf_v2list_bucket.php @@ -0,0 +1,34 @@ +listFilesv2($bucket, $prefix, $marker, $limit, $delimiter, true); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/rtc/README.md b/php-sdk/examples/rtc/README.md new file mode 100644 index 0000000..c7fff4d --- /dev/null +++ b/php-sdk/examples/rtc/README.md @@ -0,0 +1,34 @@ +# Rtc Streaming Cloud Server-Side Library For PHP + +## Features + +- RoomToken 签发 + - [x] 生成 RoomToken: client->appToken() + +- App 管理 + - [x] 创建应用: client->createApp() + - [x] 获取应用配置信息: client->getApp() + - [x] 更新应用配置信息: client->updateApp() + - [x] 删除应用: client->deleteApp() + +- 房间管理 + - [x] 列举房间下的所有用户: client->listUser() + - [x] 指定一个用户踢出房间: client->kickUser() + - [x] 停止一个房间的合流转推: client->stopMerge() + - [x] 获取当前所有活跃的房间: client->listActiveRooms() + +## Demo +- RoomToken 签发 + - [生成 RoomToken](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_create_roomToken.php) + +- App 管理 + - [创建应用](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_createApp.php) + - [获取应用配置信息](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_getApp.php) + - [更新应用配置信息](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_updateApp.php) + - [删除应用](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_deleteApp.php) + +- 房间管理 + - [列举房间下的所有用户](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_rooms_listUser.php) + - [指定一个用户踢出房间](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_rooms_kickUser.php) + - [停止一个房间的合流转推](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_rooms_stopMerge.php) + - [获取当前所有活跃的房间](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_rooms_listActiveRooms.php) \ No newline at end of file diff --git a/php-sdk/examples/rtc/rtc_createApp.php b/php-sdk/examples/rtc/rtc_createApp.php new file mode 100644 index 0000000..039eadd --- /dev/null +++ b/php-sdk/examples/rtc/rtc_createApp.php @@ -0,0 +1,32 @@ +createApp($hub, $title, $maxUsers); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Create Successfully: \n"; + var_dump($ret); +} diff --git a/php-sdk/examples/rtc/rtc_create_roomToken.php b/php-sdk/examples/rtc/rtc_create_roomToken.php new file mode 100644 index 0000000..6a62aa2 --- /dev/null +++ b/php-sdk/examples/rtc/rtc_create_roomToken.php @@ -0,0 +1,34 @@ +appToken($appId, $roomName, $userId, $expireAt, $permission); +echo "\n====> Create RoomToken Successfully: \n"; +var_dump($RoomToken); diff --git a/php-sdk/examples/rtc/rtc_deleteApp.php b/php-sdk/examples/rtc/rtc_deleteApp.php new file mode 100644 index 0000000..68bff33 --- /dev/null +++ b/php-sdk/examples/rtc/rtc_deleteApp.php @@ -0,0 +1,25 @@ +deleteApp($appId); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Delete $appId Successfully \n"; +} diff --git a/php-sdk/examples/rtc/rtc_getApp.php b/php-sdk/examples/rtc/rtc_getApp.php new file mode 100644 index 0000000..9f8e374 --- /dev/null +++ b/php-sdk/examples/rtc/rtc_getApp.php @@ -0,0 +1,26 @@ +getApp($appId); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> $appId Conf: \n"; + var_dump($ret); +} diff --git a/php-sdk/examples/rtc/rtc_rooms_kickUser.php b/php-sdk/examples/rtc/rtc_rooms_kickUser.php new file mode 100644 index 0000000..019c3f2 --- /dev/null +++ b/php-sdk/examples/rtc/rtc_rooms_kickUser.php @@ -0,0 +1,31 @@ +kickUser($appId, $roomName, $userId); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Kick User $userId Successfully \n"; +} diff --git a/php-sdk/examples/rtc/rtc_rooms_listActiveRooms.php b/php-sdk/examples/rtc/rtc_rooms_listActiveRooms.php new file mode 100644 index 0000000..16e6027 --- /dev/null +++ b/php-sdk/examples/rtc/rtc_rooms_listActiveRooms.php @@ -0,0 +1,35 @@ +listActiveRooms($appId, $prefix, $offset, $limit); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Active Rooms:\n"; + var_dump($ret); +} diff --git a/php-sdk/examples/rtc/rtc_rooms_listUser.php b/php-sdk/examples/rtc/rtc_rooms_listUser.php new file mode 100644 index 0000000..a839728 --- /dev/null +++ b/php-sdk/examples/rtc/rtc_rooms_listUser.php @@ -0,0 +1,29 @@ +listUser($appId, $roomName); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> User List: \n"; + var_dump($ret); +} diff --git a/php-sdk/examples/rtc/rtc_rooms_stopMerge.php b/php-sdk/examples/rtc/rtc_rooms_stopMerge.php new file mode 100644 index 0000000..e140907 --- /dev/null +++ b/php-sdk/examples/rtc/rtc_rooms_stopMerge.php @@ -0,0 +1,28 @@ +stopMerge($appId, $roomName); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Stop Merge Successfully \n"; +} diff --git a/php-sdk/examples/rtc/rtc_updateApp.php b/php-sdk/examples/rtc/rtc_updateApp.php new file mode 100644 index 0000000..f771075 --- /dev/null +++ b/php-sdk/examples/rtc/rtc_updateApp.php @@ -0,0 +1,40 @@ +updateApp($appId, $hub, $title, $maxUsers, false, $mergePublishRtmp); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Update $appId Conf Successfully: \n"; + var_dump($ret); +} diff --git a/php-sdk/examples/saveas.php b/php-sdk/examples/saveas.php new file mode 100644 index 0000000..5d51ef4 --- /dev/null +++ b/php-sdk/examples/saveas.php @@ -0,0 +1,33 @@ +为生成缩略图的文件名 +$entry = ':'; + +// 生成的值 +$encodedEntryURI = \Qiniu\base64_urlSafeEncode($entry); + +// 使用 SecretKey 对新的下载 URL 进行 HMAC1-SHA1 签名 +$newurl = "78re52.com1.z0.glb.clouddn.com/resource/Ship.jpg?imageView2/2/w/200/h/200|saveas/" . $encodedEntryURI; + +$sign = hash_hmac("sha1", $newurl, $secretKey, true); + +// 对签名进行 URL 安全的 Base64 编码 +$encodedSign = \Qiniu\base64_urlSafeEncode($sign); + +// 最终得到的完整下载 URL +$finalURL = "http://" . $newurl . "/sign/" . $accessKey . ":" . $encodedSign; + +$callbackBody = file_get_contents("$finalURL"); + +echo $callbackBody; diff --git a/php-sdk/examples/sms/README.md b/php-sdk/examples/sms/README.md new file mode 100644 index 0000000..8c80a38 --- /dev/null +++ b/php-sdk/examples/sms/README.md @@ -0,0 +1,45 @@ +# SMS Server-Side Library For PHP + +## Features + +- 签名管理 + - [x] 创建签名: client->createSignature() + - [x] 列出签名: client->checkSignature() + - [x] 查询单个签名: client->checkSingleSignature() + - [x] 编辑签名: client->updateSignature() + - [x] 删除签名: client->deleteSignature() + +- 模板管理 + - [x] 创建模板: client->createTemplate() + - [x] 列出模板: client->queryTemplate() + - [x] 查询单个模板: client->querySingleTemplate() + - [x] 编辑模板: client->updateTemplate() + - [x] 删除模板: client->deleteTemplate() + +- 发送短信 + - [x] 发送短信: client->sendMessage() + +- 查询发送记录 + - [x] 查询发送记录: client->querySendSms() + +## Demo + +- 签名管理 + - [创建签名](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_create_signature.php) + - [列出签名](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_query_signature.php) + - [查询单个签名](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_query_single_signature.php) + - [编辑签名](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_edit_signature.php) + - [删除签名](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_delete_signature.php) + +- 模板管理 + - [创建模板](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_create_template.php) + - [列出模板](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_query_template.php) + - [查询单个模板](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_query_single_template.php) + - [编辑模板](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_edit_template.php) + - [删除模板](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_delete_template.php) + +- 发送短信 + - [发送短信](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_send_message.php) + +- 查询发送记录 + - [查询发送记录](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_query_send_sms.php) diff --git a/php-sdk/examples/sms/sms_create_signature.php b/php-sdk/examples/sms/sms_create_signature.php new file mode 100644 index 0000000..ea1f158 --- /dev/null +++ b/php-sdk/examples/sms/sms_create_signature.php @@ -0,0 +1,29 @@ +createSignature($signature, $source, $pics); + +echo "\n====> create signature result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/sms/sms_create_template.php b/php-sdk/examples/sms/sms_create_template.php new file mode 100644 index 0000000..3cb3874 --- /dev/null +++ b/php-sdk/examples/sms/sms_create_template.php @@ -0,0 +1,33 @@ +createTemplate($name, $template, $type, $description, $signature_id); + +echo "\n====> create signature result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/sms/sms_delete_signature.php b/php-sdk/examples/sms/sms_delete_signature.php new file mode 100644 index 0000000..fd873fa --- /dev/null +++ b/php-sdk/examples/sms/sms_delete_signature.php @@ -0,0 +1,25 @@ +deleteSignature($signature_id); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Delete Signature $signature_id Successfully\n"; +} diff --git a/php-sdk/examples/sms/sms_delete_template.php b/php-sdk/examples/sms/sms_delete_template.php new file mode 100644 index 0000000..4590835 --- /dev/null +++ b/php-sdk/examples/sms/sms_delete_template.php @@ -0,0 +1,25 @@ +deleteTemplate($template_id); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Delete Template $template_id Successfully\n"; +} diff --git a/php-sdk/examples/sms/sms_edit_signature.php b/php-sdk/examples/sms/sms_edit_signature.php new file mode 100644 index 0000000..edf14e0 --- /dev/null +++ b/php-sdk/examples/sms/sms_edit_signature.php @@ -0,0 +1,30 @@ +updateSignature($id, $signature, $source, $pics); + +echo "\n====> edit signature result: \n"; +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Update Signature Successfully\n"; +} diff --git a/php-sdk/examples/sms/sms_edit_template.php b/php-sdk/examples/sms/sms_edit_template.php new file mode 100644 index 0000000..1be5509 --- /dev/null +++ b/php-sdk/examples/sms/sms_edit_template.php @@ -0,0 +1,31 @@ +updateTemplate($template_id, $name, $template, $description, $signature_id); + +echo "\n====> edit template result: \n"; +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Update Template Successfully\n"; +} diff --git a/php-sdk/examples/sms/sms_query_send_sms.php b/php-sdk/examples/sms/sms_query_send_sms.php new file mode 100644 index 0000000..cdbbe71 --- /dev/null +++ b/php-sdk/examples/sms/sms_query_send_sms.php @@ -0,0 +1,50 @@ +querySendSms( + $job_id, + $message_id, + $mobile, + $status, + $template_id, + $type, + $start, + $end, + $page, + $page_size +); +echo "\n====> query send sms result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/sms/sms_query_signature.php b/php-sdk/examples/sms/sms_query_signature.php new file mode 100644 index 0000000..224d09b --- /dev/null +++ b/php-sdk/examples/sms/sms_query_signature.php @@ -0,0 +1,28 @@ +querySignature($audit_status, $page, $page_size); +echo "\n====> query signature result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/sms/sms_query_single_signature.php b/php-sdk/examples/sms/sms_query_single_signature.php new file mode 100644 index 0000000..8afb4d5 --- /dev/null +++ b/php-sdk/examples/sms/sms_query_single_signature.php @@ -0,0 +1,26 @@ +checkSingleSignature($signature_id); +echo "\n====> query single signature result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/sms/sms_query_single_template.php b/php-sdk/examples/sms/sms_query_single_template.php new file mode 100644 index 0000000..8e0b279 --- /dev/null +++ b/php-sdk/examples/sms/sms_query_single_template.php @@ -0,0 +1,26 @@ +querySingleTemplate($template_id); +echo "\n====> query single template result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/sms/sms_query_template.php b/php-sdk/examples/sms/sms_query_template.php new file mode 100644 index 0000000..6be260e --- /dev/null +++ b/php-sdk/examples/sms/sms_query_template.php @@ -0,0 +1,28 @@ +queryTemplate($audit_status, $page, $page_size); +echo "\n====> query template result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/sms/sms_send_message.php b/php-sdk/examples/sms/sms_send_message.php new file mode 100644 index 0000000..d943e52 --- /dev/null +++ b/php-sdk/examples/sms/sms_send_message.php @@ -0,0 +1,32 @@ + 'xxxx'); + +list($ret, $err) = $client->sendMessage($template_id, $mobiles, $code); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Send Message Successfully: \n"; + var_dump($ret); +} diff --git a/php-sdk/examples/update_bucketEvent.php b/php-sdk/examples/update_bucketEvent.php new file mode 100644 index 0000000..7b0d1d0 --- /dev/null +++ b/php-sdk/examples/update_bucketEvent.php @@ -0,0 +1,31 @@ +updateBucketEvent($bucket, $name, $prefix, $suffix, $event, $callbackURL); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/update_bucketLifecycleRule.php b/php-sdk/examples/update_bucketLifecycleRule.php new file mode 100644 index 0000000..73f0f56 --- /dev/null +++ b/php-sdk/examples/update_bucketLifecycleRule.php @@ -0,0 +1,36 @@ +updateBucketLifecycleRule( + $bucket, + $name, + $prefix, + $delete_after_days, + $to_line_after_days +); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/upload_and_callback.php b/php-sdk/examples/upload_and_callback.php new file mode 100644 index 0000000..a0c793a --- /dev/null +++ b/php-sdk/examples/upload_and_callback.php @@ -0,0 +1,31 @@ + 'http://your.domain.com/upload_verify_callback.php', + 'callbackBody' => 'filename=$(fname)&filesize=$(fsize)' +); +$uptoken = $auth->uploadToken($bucket, null, 3600, $policy); + +// 上传文件的本地路径 +$filePath = './php-logo.png'; + +$uploadMgr = new UploadManager(); +list($ret, $err) = $uploadMgr->putFile($uptoken, null, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/upload_and_pfop.php b/php-sdk/examples/upload_and_pfop.php new file mode 100644 index 0000000..32c1eb5 --- /dev/null +++ b/php-sdk/examples/upload_and_pfop.php @@ -0,0 +1,49 @@ + $pfop, + 'persistentNotifyUrl' => $notifyUrl, + 'persistentPipeline' => $pipeline +); +$token = $auth->uploadToken($bucket, null, 3600, $policy); + +list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/upload_mgr_init.php b/php-sdk/examples/upload_mgr_init.php new file mode 100644 index 0000000..1164c90 --- /dev/null +++ b/php-sdk/examples/upload_mgr_init.php @@ -0,0 +1,19 @@ +uploadToken($bucket); + +// 构建 UploadManager 对象 +$uploadMgr = new UploadManager(); diff --git a/php-sdk/examples/upload_multi_demos.php b/php-sdk/examples/upload_multi_demos.php new file mode 100644 index 0000000..d724235 --- /dev/null +++ b/php-sdk/examples/upload_multi_demos.php @@ -0,0 +1,89 @@ +uploadToken($bucket); +$uploadMgr = new UploadManager(); + +//---------------------------------------- upload demo1 ---------------------------------------- +// 上传字符串到七牛 + +list($ret, $err) = $uploadMgr->put($token, null, 'content string'); +echo "\n====> put result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} + + +//---------------------------------------- upload demo2 ---------------------------------------- +// 上传文件到七牛 + +$filePath = './php-logo.png'; +$key = 'php-logo.png'; +list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} + + +//---------------------------------------- upload demo3 ---------------------------------------- +// 上传文件到七牛后, 七牛将文件名和文件大小回调给业务服务器. +// 可参考文档: https://developer.qiniu.com/kodo/manual/1206/put-policy + +$policy = array( + 'callbackUrl' => 'http://172.30.251.210/upload_verify_callback.php', + 'callbackBody' => 'filename=$(fname)&filesize=$(fsize)' +// 'callbackBodyType' => 'application/json', +// 'callbackBody' => '{"filename":$(fname), "filesize": $(fsize)}' //设置application/json格式回调 +); +$token = $auth->uploadToken($bucket, null, 3600, $policy); + + +list($ret, $err) = $uploadMgr->putFile($token, null, $key); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} + + +//---------------------------------------- upload demo4 ---------------------------------------- +// 上传视频,上传完成后进行 m3u8 的转码, 并给视频打水印 + +$wmImg = Qiniu\base64_urlSafeEncode('http://devtools.qiniudn.com/qiniu.png'); +$pfop = "avthumb/m3u8/wmImage/$wmImg"; + +// 转码完成后回调到业务服务器。(公网可以访问,并相应 200 OK) +$notifyUrl = 'http://notify.fake.com'; + +$policy = array( + 'persistentOps' => $pfop, + 'persistentNotifyUrl' => $notifyUrl, + 'persistentPipeline' => $pipeline +); +$token = $auth->uploadToken($bucket, null, 3600, $policy); +print($token); +list($ret, $err) = $uploadMgr->putFile($token, null, $key); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/upload_simple_file.php b/php-sdk/examples/upload_simple_file.php new file mode 100644 index 0000000..f495a02 --- /dev/null +++ b/php-sdk/examples/upload_simple_file.php @@ -0,0 +1,37 @@ +uploadToken($bucket); + +// 要上传文件的本地路径 +$filePath = './php-logo.png'; + +// 上传到七牛存储后保存的文件名 +$key = 'my-php-logo.png'; + +// 初始化 UploadManager 对象并进行文件的上传。 +$uploadMgr = new UploadManager(); + +// 调用 UploadManager 的 putFile 方法进行文件的上传,该方法会判断文件大小,进而决定使用表单上传还是分片上传,无需手动配置。 +list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/upload_tokens.php b/php-sdk/examples/upload_tokens.php new file mode 100644 index 0000000..d2cf02c --- /dev/null +++ b/php-sdk/examples/upload_tokens.php @@ -0,0 +1,82 @@ +uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo2 ---------------------------------------- +// 自定义凭证有效期(示例2小时) + +$expires = 7200; +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo3 ---------------------------------------- +// 覆盖上传凭证 + +$expires = 3600; +$keyToOverwrite = 'qiniu.mp4'; +$upToken = $auth->uploadToken($bucket, $keyToOverwrite, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo4 ---------------------------------------- +// 自定义上传回复(非callback模式)凭证 + +$returnBody = '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}'; +$policy = array( + 'returnBody' => $returnBody +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo5 ---------------------------------------- +// 带回调业务服务器的凭证(application/json) + +$policy = array( + 'callbackUrl' => 'http://api.example.com/qiniu/upload/callback', + 'callbackBody' => '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}', + 'callbackBodyType' => 'application/json' +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo6 ---------------------------------------- +// 带回调业务服务器的凭证(application/x-www-form-urlencoded) + +$policy = array( + 'callbackUrl' => 'http://api.example.com/qiniu/upload/callback', + 'callbackBody' => 'key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)' +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo7 ---------------------------------------- +// 带数据处理的凭证 + +$saveMp4Entry = \Qiniu\base64_urlSafeEncode($bucket . ":avthumb_test_target.mp4"); +$saveJpgEntry = \Qiniu\base64_urlSafeEncode($bucket . ":vframe_test_target.jpg"); +$avthumbMp4Fop = "avthumb/mp4|saveas/" . $saveMp4Entry; +$vframeJpgFop = "vframe/jpg/offset/1|saveas/" . $saveJpgEntry; +$policy = array( + 'persistentOps' => $avthumbMp4Fop . ";" . $vframeJpgFop, + 'persistentPipeline' => "video-pipe", + 'persistentNotifyUrl' => "http://api.example.com/qiniu/pfop/notify", +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); diff --git a/php-sdk/examples/upload_verify_callback.php b/php-sdk/examples/upload_verify_callback.php new file mode 100644 index 0000000..dcb64c9 --- /dev/null +++ b/php-sdk/examples/upload_verify_callback.php @@ -0,0 +1,34 @@ +verifyCallback($contentType, $authorization, $url, $callbackBody); + +if ($isQiniuCallback) { + $resp = array('ret' => 'success'); +} else { + $resp = array('ret' => 'failed'); +} + +echo json_encode($resp); diff --git a/php-sdk/examples/upload_with_qvmzone.php b/php-sdk/examples/upload_with_qvmzone.php new file mode 100644 index 0000000..ce2b21f --- /dev/null +++ b/php-sdk/examples/upload_with_qvmzone.php @@ -0,0 +1,40 @@ +uploadToken($bucket); + +// 上传文件的本地路径 +$filePath = './php-logo.png'; + +// 七牛云主机QVM和七牛对象存储KODO内网上传,目前支持华东1区域(杭州)和华北2区域(北京)的云主机可以访问同区域的对象存储服务 +// 参考文档:https://developer.qiniu.com/qvm/manual/4269/qvm-kodo + +$zone = Zone::qvmZonez0(); // 华东:z0,华北:z1 +$config = new Config($zone); +$config->useHTTPS = true; + +// 指定 config +$uploadMgr = new UploadManager($config); + +list($ret, $err) = $uploadMgr->putFile($uptoken, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/examples/upload_with_zone.php b/php-sdk/examples/upload_with_zone.php new file mode 100644 index 0000000..6192666 --- /dev/null +++ b/php-sdk/examples/upload_with_zone.php @@ -0,0 +1,39 @@ +uploadToken($bucket); + +// 上传文件的本地路径 +$filePath = './php-logo.png'; + +// 指定 zone 上传 +// 参考文档:https://developer.qiniu.com/kodo/manual/1671/region-endpoint +$zone = Zone::zonez0(); // 华东:z0,华北:z1,华南:z2,北美:na0,东南亚:as0 +$config = new Config($zone); +$config->useHTTPS = true; + +// 指定 config +$uploadMgr = new UploadManager($config); + +list($ret, $err) = $uploadMgr->putFile($uptoken, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/php-sdk/phpunit.xml.dist b/php-sdk/phpunit.xml.dist new file mode 100644 index 0000000..840f6e5 --- /dev/null +++ b/php-sdk/phpunit.xml.dist @@ -0,0 +1,18 @@ + + + + + tests + + + + diff --git a/php-sdk/src/Qiniu/Auth.php b/php-sdk/src/Qiniu/Auth.php new file mode 100644 index 0000000..6da2be4 --- /dev/null +++ b/php-sdk/src/Qiniu/Auth.php @@ -0,0 +1,285 @@ +accessKey = $accessKey; + $this->secretKey = $secretKey; + $defaultOptions = array( + 'disableQiniuTimestampSignature' => null + ); + if ($options == null) { + $options = $defaultOptions; + } + $this->options = array_merge($defaultOptions, $options); + } + + public function getAccessKey() + { + return $this->accessKey; + } + + public function sign($data) + { + $hmac = hash_hmac('sha1', $data, $this->secretKey, true); + return $this->accessKey . ':' . \Qiniu\base64_urlSafeEncode($hmac); + } + + public function signWithData($data) + { + $encodedData = \Qiniu\base64_urlSafeEncode($data); + return $this->sign($encodedData) . ':' . $encodedData; + } + + public function signRequest($urlString, $body, $contentType = null) + { + $url = parse_url($urlString); + $data = ''; + if (array_key_exists('path', $url)) { + $data = $url['path']; + } + if (array_key_exists('query', $url)) { + $data .= '?' . $url['query']; + } + $data .= "\n"; + + if ($body !== null && $contentType === 'application/x-www-form-urlencoded') { + $data .= $body; + } + return $this->sign($data); + } + + /** + * @param string $urlString + * @param string $method + * @param string $body + * @param null|Header $headers + */ + public function signQiniuAuthorization($urlString, $method = "GET", $body = "", $headers = null) + { + $url = parse_url($urlString); + if (!$url) { + return array(null, new \Exception("parse_url error")); + } + + // append method, path and query + if ($method === "") { + $data = "GET "; + } else { + $data = $method . " "; + } + if (isset($url["path"])) { + $data .= $url["path"]; + } + if (isset($url["query"])) { + $data .= "?" . $url["query"]; + } + + // append Host + $data .= "\n"; + $data .= "Host: "; + if (isset($url["host"])) { + $data .= $url["host"]; + } + if (isset($url["port"]) && $url["port"] > 0) { + $data .= ":" . $url["port"]; + } + + // try to append content type + if ($headers != null && isset($headers["Content-Type"])) { + // append content type + $data .= "\n"; + $data .= "Content-Type: " . $headers["Content-Type"]; + } + + // try append xQiniuHeaders + if ($headers != null) { + $headerLines = array(); + $keyPrefix = "X-Qiniu-"; + foreach ($headers as $k => $v) { + if (strlen($k) > strlen($keyPrefix) && strpos($k, $keyPrefix) === 0) { + array_push( + $headerLines, + $k . ": " . $v + ); + } + } + if (count($headerLines) > 0) { + $data .= "\n"; + sort($headerLines); + $data .= implode("\n", $headerLines); + } + } + + // append body + $data .= "\n\n"; + if (!is_null($body) + && strlen($body) > 0 + && isset($headers["Content-Type"]) + && $headers["Content-Type"] != "application/octet-stream" + ) { + $data .= $body; + } + + return array($this->sign($data), null); + } + + public function verifyCallback( + $contentType, + $originAuthorization, + $url, + $body, + $method = "GET", + $headers = array() + ) { + if (strpos($originAuthorization, 'Qiniu') === 0) { + $qnHeaders = new Header($headers); + if (!isset($qnHeaders['Content-Type'])) { + $qnHeaders['Content-Type'] = $contentType; + } + list($sign, $err) = $this->signQiniuAuthorization( + $url, + $method, + $body, + $qnHeaders + ); + if ($err !== null) { + return false; + } + $authorization = 'Qiniu ' . $sign; + } else { + $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType); + } + return $originAuthorization === $authorization; + } + + public function privateDownloadUrl($baseUrl, $expires = 3600) + { + $deadline = time() + $expires; + + $pos = strpos($baseUrl, '?'); + if ($pos !== false) { + $baseUrl .= '&e='; + } else { + $baseUrl .= '?e='; + } + $baseUrl .= $deadline; + + $token = $this->sign($baseUrl); + return "$baseUrl&token=$token"; + } + + public function uploadToken($bucket, $key = null, $expires = 3600, $policy = null, $strictPolicy = true) + { + $deadline = time() + $expires; + $scope = $bucket; + if ($key !== null) { + $scope .= ':' . $key; + } + + $args = self::copyPolicy($args, $policy, $strictPolicy); + $args['scope'] = $scope; + $args['deadline'] = $deadline; + + $b = json_encode($args); + return $this->signWithData($b); + } + + /** + *上传策略,参数规格详见 + *http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html + */ + private static $policyFields = array( + 'callbackUrl', + 'callbackBody', + 'callbackHost', + 'callbackBodyType', + 'callbackFetchKey', + + 'returnUrl', + 'returnBody', + + 'endUser', + 'saveKey', + 'forceSaveKey', + 'insertOnly', + + 'detectMime', + 'mimeLimit', + 'fsizeMin', + 'fsizeLimit', + + 'persistentOps', // 与 persistentWorkflowTemplateID 二选一 + 'persistentNotifyUrl', + 'persistentPipeline', + 'persistentType', // 为 `1` 时开启闲时任务 + 'persistentWorkflowTemplateID', // 与 persistentOps 二选一 + + 'deleteAfterDays', + 'fileType', + 'isPrefixalScope', + + 'transform', // deprecated + 'transformFallbackKey', // deprecated + 'transformFallbackMode', // deprecated + ); + + private static function copyPolicy(&$policy, $originPolicy, $strictPolicy) + { + if ($originPolicy === null) { + return array(); + } + foreach ($originPolicy as $key => $value) { + if (!$strictPolicy || in_array((string)$key, self::$policyFields, true)) { + $policy[$key] = $value; + } + } + return $policy; + } + + public function authorization($url, $body = null, $contentType = null) + { + $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType); + return array('Authorization' => $authorization); + } + + public function authorizationV2($url, $method, $body = null, $contentType = null) + { + $headers = new Header(); + $result = array(); + if ($contentType != null) { + $headers['Content-Type'] = $contentType; + $result['Content-Type'] = $contentType; + } + + $signDate = gmdate('Ymd\THis\Z', time()); + if ($this->options['disableQiniuTimestampSignature'] !== null) { + if (!$this->options['disableQiniuTimestampSignature']) { + $headers['X-Qiniu-Date'] = $signDate; + $result['X-Qiniu-Date'] = $signDate; + } + } elseif (getenv("DISABLE_QINIU_TIMESTAMP_SIGNATURE")) { + if (strtolower(getenv("DISABLE_QINIU_TIMESTAMP_SIGNATURE")) !== "true") { + $headers['X-Qiniu-Date'] = $signDate; + $result['X-Qiniu-Date'] = $signDate; + } + } else { + $headers['X-Qiniu-Date'] = $signDate; + $result['X-Qiniu-Date'] = $signDate; + } + + list($sign) = $this->signQiniuAuthorization($url, $method, $body, $headers); + $result['Authorization'] = 'Qiniu ' . $sign; + return $result; + } +} diff --git a/php-sdk/src/Qiniu/Cdn/CdnManager.php b/php-sdk/src/Qiniu/Cdn/CdnManager.php new file mode 100644 index 0000000..60052d3 --- /dev/null +++ b/php-sdk/src/Qiniu/Cdn/CdnManager.php @@ -0,0 +1,263 @@ +auth = $auth; + $this->server = 'http://fusion.qiniuapi.com'; + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * @param array $urls 待刷新的文件链接数组 + * @return array + */ + public function refreshUrls(array $urls) + { + return $this->refreshUrlsAndDirs($urls, array()); + } + + /** + * @param array $dirs 待刷新的文件链接数组 + * @return array + * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh + * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category + */ + public function refreshDirs(array $dirs) + { + return $this->refreshUrlsAndDirs(array(), $dirs); + } + + /** + * @param array $urls 待刷新的文件链接数组 + * @param array $dirs 待刷新的目录链接数组 + * + * @return array 刷新的请求回复和错误,参考 examples/cdn_manager.php 代码 + * @link http://developer.qiniu.com/article/fusion/api/refresh.html + * + * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh + * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category + */ + public function refreshUrlsAndDirs(array $urls, array $dirs) + { + $req = array(); + if (!empty($urls)) { + $req['urls'] = $urls; + } + if (!empty($dirs)) { + $req['dirs'] = $dirs; + } + + $url = $this->server . '/v2/tune/refresh'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * 查询 CDN 刷新记录 + * + * @param string $requestId 指定要查询记录所在的刷新请求id + * @param string $isDir 指定是否查询目录,取值为 yes/no,默认不填则为两种类型记录都查询 + * @param array $urls 要查询的url列表,每个url可以是文件url,也可以是目录url + * @param string $state 指定要查询记录的状态,取值processing/success/failure + * @param int $pageNo 要求返回的页号,默认为0 + * @param int $pageSize 要求返回的页长度,默认为100 + * @param string $startTime 指定查询的开始日期,格式2006-01-01 + * @param string $endTime 指定查询的结束日期,格式2006-01-01 + * @return array + * @link https://developer.qiniu.com/fusion/api/1229/cache-refresh#4 + */ + public function getCdnRefreshList( + $requestId = null, + $isDir = null, + $urls = array(), + $state = null, + $pageNo = 0, + $pageSize = 100, + $startTime = null, + $endTime = null + ) { + $req = array(); + \Qiniu\setWithoutEmpty($req, 'requestId', $requestId); + \Qiniu\setWithoutEmpty($req, 'isDir', $isDir); + \Qiniu\setWithoutEmpty($req, 'urls', $urls); + \Qiniu\setWithoutEmpty($req, 'state', $state); + \Qiniu\setWithoutEmpty($req, 'pageNo', $pageNo); + \Qiniu\setWithoutEmpty($req, 'pageSize', $pageSize); + \Qiniu\setWithoutEmpty($req, 'startTime', $startTime); + \Qiniu\setWithoutEmpty($req, 'endTime', $endTime); + + $body = json_encode($req); + $url = $this->server . '/v2/tune/refresh/list'; + return $this->post($url, $body); + } + + /** + * @param array $urls 待预取的文件链接数组 + * + * @return array 预取的请求回复和错误,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/refresh.html + */ + public function prefetchUrls(array $urls) + { + $req = array( + 'urls' => $urls, + ); + + $url = $this->server . '/v2/tune/prefetch'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * 查询 CDN 预取记录 + * + * @param string $requestId 指定要查询记录所在的刷新请求id + * @param array $urls 要查询的url列表,每个url可以是文件url,也可以是目录url + * @param string $state 指定要查询记录的状态,取值processing/success/failure + * @param int $pageNo 要求返回的页号,默认为0 + * @param int $pageSize 要求返回的页长度,默认为100 + * @param string $startTime 指定查询的开始日期,格式2006-01-01 + * @param string $endTime 指定查询的结束日期,格式2006-01-01 + * @return array + * @link https://developer.qiniu.com/fusion/api/1227/file-prefetching#4 + */ + public function getCdnPrefetchList( + $requestId = null, + $urls = array(), + $state = null, + $pageNo = 0, + $pageSize = 100, + $startTime = null, + $endTime = null + ) { + $req = array(); + \Qiniu\setWithoutEmpty($req, 'requestId', $requestId); + \Qiniu\setWithoutEmpty($req, 'urls', $urls); + \Qiniu\setWithoutEmpty($req, 'state', $state); + \Qiniu\setWithoutEmpty($req, 'pageNo', $pageNo); + \Qiniu\setWithoutEmpty($req, 'pageSize', $pageSize); + \Qiniu\setWithoutEmpty($req, 'startTime', $startTime); + \Qiniu\setWithoutEmpty($req, 'endTime', $endTime); + + $body = json_encode($req); + $url = $this->server . '/v2/tune/prefetch/list'; + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取带宽数据的域名数组 + * @param string $startDate 开始的日期,格式类似 2017-01-01 + * @param string $endDate 结束的日期,格式类似 2017-01-01 + * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day + * + * @return array 带宽数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html + */ + public function getBandwidthData(array $domains, $startDate, $endDate, $granularity) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['startDate'] = $startDate; + $req['endDate'] = $endDate; + $req['granularity'] = $granularity; + + $url = $this->server . '/v2/tune/bandwidth'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取流量数据的域名数组 + * @param string $startDate 开始的日期,格式类似 2017-01-01 + * @param string $endDate 结束的日期,格式类似 2017-01-01 + * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day + * + * @return array 流量数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html + */ + public function getFluxData(array $domains, $startDate, $endDate, $granularity) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['startDate'] = $startDate; + $req['endDate'] = $endDate; + $req['granularity'] = $granularity; + + $url = $this->server . '/v2/tune/flux'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取日志下载链接的域名数组 + * @param string $logDate 获取指定日期的日志下载链接,格式类似 2017-01-01 + * + * @return array 日志下载链接数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/log.html + */ + public function getCdnLogList(array $domains, $logDate) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['day'] = $logDate; + + $url = $this->server . '/v2/tune/log/list'; + $body = json_encode($req); + return $this->post($url, $body); + } + + private function post($url, $body) + { + $headers = $this->auth->authorization($url, $body, 'application/json'); + $headers['Content-Type'] = 'application/json'; + $ret = Client::post($url, $body, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + /** + * 构建时间戳防盗链鉴权的访问外链 + * + * @param string $rawUrl 需要签名的资源url + * @param string $encryptKey 时间戳防盗链密钥 + * @param string $durationInSeconds 链接的有效期(以秒为单位) + * + * @return string 带鉴权信息的资源外链,参考 examples/cdn_timestamp_antileech.php 代码 + */ + public static function createTimestampAntiLeechUrl($rawUrl, $encryptKey, $durationInSeconds) + { + $parsedUrl = parse_url($rawUrl); + $deadline = time() + $durationInSeconds; + $expireHex = dechex($deadline); + $path = isset($parsedUrl['path']) ? $parsedUrl['path'] : ''; + $strToSign = $encryptKey . $path . $expireHex; + $signStr = md5($strToSign); + if (isset($parsedUrl['query'])) { + $signedUrl = $rawUrl . '&sign=' . $signStr . '&t=' . $expireHex; + } else { + $signedUrl = $rawUrl . '?sign=' . $signStr . '&t=' . $expireHex; + } + return $signedUrl; + } +} diff --git a/php-sdk/src/Qiniu/Config.php b/php-sdk/src/Qiniu/Config.php new file mode 100644 index 0000000..3ce7fa5 --- /dev/null +++ b/php-sdk/src/Qiniu/Config.php @@ -0,0 +1,398 @@ +zone = $z; + $this->useHTTPS = false; + $this->useCdnDomains = false; + $this->regionCache = array(); + $this->ucHost = Config::UC_HOST; + $this->queryRegionHost = Config::QUERY_REGION_HOST; + $this->backupQueryRegionHosts = array( + "kodo-config.qiniuapi.com", + "uc.qbox.me", + ); + $this->backupUcHostsRetryTimes = 2; + } + + public function setUcHost($ucHost) + { + $this->ucHost = $ucHost; + $this->setQueryRegionHost($ucHost); + } + + public function getUcHost() + { + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $this->ucHost; + } + + public function setQueryRegionHost($host, $backupHosts = array()) + { + $this->queryRegionHost = $host; + $this->backupQueryRegionHosts = $backupHosts; + } + + public function getQueryRegionHost() + { + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $this->queryRegionHost; + } + + public function setBackupQueryRegionHosts($hosts = array()) + { + $this->backupQueryRegionHosts = $hosts; + } + + public function getBackupQueryRegionHosts() + { + return $this->backupQueryRegionHosts; + } + + public function getUpHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $region->srcUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $region->cdnUpHosts[0]; + } + + return $scheme . $host; + } + + public function getUpHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $region->srcUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $region->cdnUpHosts[0]; + } + + return array($scheme . $host, null); + } + + public function getUpBackupHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $region->cdnUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $region->srcUpHosts[0]; + } + + return $scheme . $host; + } + + public function getUpBackupHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $region->cdnUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $region->srcUpHosts[0]; + } + + return array($scheme . $host, null); + } + + public function getRsHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->rsHost; + } + + public function getRsHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return array($scheme . $region->rsHost, null); + } + + public function getRsfHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->rsfHost; + } + + public function getRsfHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return array($scheme . $region->rsfHost, null); + } + + public function getIovipHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->iovipHost; + } + + public function getIovipHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return array($scheme . $region->iovipHost, null); + } + + public function getApiHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->apiHost; + } + + public function getApiHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return array($scheme . $region->apiHost, null); + } + + + /** + * 从缓存中获取区域 + * + * @param string $cacheId 缓存 ID + * @return null|Region + */ + private function getRegionCache($cacheId) + { + if (isset($this->regionCache[$cacheId]) && + isset($this->regionCache[$cacheId]["deadline"]) && + time() < $this->regionCache[$cacheId]["deadline"]) { + return $this->regionCache[$cacheId]["region"]; + } + + return null; + } + + /** + * 将区域设置到缓存中 + * + * @param string $cacheId 缓存 ID + * @param Region $region 缓存 ID + * @return void + */ + private function setRegionCache($cacheId, $region) + { + $this->regionCache[$cacheId] = array( + "region" => $region, + ); + if (isset($region->ttl)) { + $this->regionCache[$cacheId]["deadline"] = time() + $region->ttl; + } + } + + /** + * 从缓存中获取区域 + * + * @param string $accessKey + * @param string $bucket + * @return Region + * + * @throws \Exception + */ + private function getRegion($accessKey, $bucket, $reqOpt = null) + { + if (isset($this->zone)) { + return $this->zone; + } + + $cacheId = "$accessKey:$bucket"; + $regionCache = $this->getRegionCache($cacheId); + if ($regionCache) { + return $regionCache; + } + + $region = Zone::queryZone( + $accessKey, + $bucket, + $this->getQueryRegionHost(), + $this->getBackupQueryRegionHosts(), + $this->backupUcHostsRetryTimes, + $reqOpt + ); + if (is_array($region)) { + list($region, $err) = $region; + if ($err != null) { + throw new \Exception($err->message()); + } + } + + $this->setRegionCache($cacheId, $region); + return $region; + } + + private function getRegionV2($accessKey, $bucket, $reqOpt = null) + { + if (isset($this->zone)) { + return array($this->zone, null); + } + + $cacheId = "$accessKey:$bucket"; + $regionCache = $this->getRegionCache($cacheId); + if (isset($regionCache)) { + return array($regionCache, null); + } + + $region = Zone::queryZone( + $accessKey, + $bucket, + $this->getQueryRegionHost(), + $this->getBackupQueryRegionHosts(), + $this->backupUcHostsRetryTimes, + $reqOpt + ); + if (is_array($region)) { + list($region, $err) = $region; + return array($region, $err); + } + + $this->setRegionCache($cacheId, $region); + return array($region, null); + } +} diff --git a/php-sdk/src/Qiniu/Enum/QiniuEnum.php b/php-sdk/src/Qiniu/Enum/QiniuEnum.php new file mode 100644 index 0000000..8399b54 --- /dev/null +++ b/php-sdk/src/Qiniu/Enum/QiniuEnum.php @@ -0,0 +1,53 @@ + $val) { + array_push($data, '--' . $mimeBoundary); + array_push($data, "Content-Disposition: form-data; name=\"$key\""); + array_push($data, ''); + array_push($data, $val); + } + + array_push($data, '--' . $mimeBoundary); + $finalMimeType = empty($mimeType) ? 'application/octet-stream' : $mimeType; + $finalFileName = self::escapeQuotes($fileName); + array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$finalFileName\""); + array_push($data, "Content-Type: $finalMimeType"); + array_push($data, ''); + array_push($data, $fileBody); + + array_push($data, '--' . $mimeBoundary . '--'); + array_push($data, ''); + + $body = implode("\r\n", $data); + $contentType = 'multipart/form-data; boundary=' . $mimeBoundary; + $headers['Content-Type'] = $contentType; + $request = new Request('POST', $url, $headers, $body, $opt); + return self::sendRequest($request); + } + + private static function userAgent() + { + $sdkInfo = "QiniuPHP/" . Config::SDK_VER; + + $systemInfo = php_uname("s"); + $machineInfo = php_uname("m"); + + $envInfo = "($systemInfo/$machineInfo)"; + + $phpVer = phpversion(); + + $ua = "$sdkInfo $envInfo PHP/$phpVer"; + return $ua; + } + + /** + * @param Request $request + * @return Response + */ + public static function sendRequestWithMiddleware($request) + { + $middlewares = $request->opt->middlewares; + $handle = Middleware\compose($middlewares, function ($req) { + return Client::sendRequest($req); + }); + return $handle($request); + } + + /** + * @param Request $request + * @return Response + */ + public static function sendRequest($request) + { + $t1 = microtime(true); + $ch = curl_init(); + $options = array( + CURLOPT_USERAGENT => self::userAgent(), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_NOBODY => false, + CURLOPT_CUSTOMREQUEST => $request->method, + CURLOPT_URL => $request->url, + ); + foreach ($request->opt->getCurlOpt() as $k => $v) { + $options[$k] = $v; + } + // Handle open_basedir & safe mode + if (!ini_get('safe_mode') && !ini_get('open_basedir')) { + $options[CURLOPT_FOLLOWLOCATION] = true; + } + if (!empty($request->headers)) { + $headers = array(); + foreach ($request->headers as $key => $val) { + array_push($headers, "$key: $val"); + } + $options[CURLOPT_HTTPHEADER] = $headers; + } + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); + if (!empty($request->body)) { + $options[CURLOPT_POSTFIELDS] = $request->body; + } + curl_setopt_array($ch, $options); + $result = curl_exec($ch); + $t2 = microtime(true); + $duration = round($t2 - $t1, 3); + $ret = curl_errno($ch); + if ($ret !== 0) { + $r = new Response(-1, $duration, array(), null, curl_error($ch)); + curl_close($ch); + return $r; + } + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $headers = Header::parseRawText(substr($result, 0, $header_size)); + $body = substr($result, $header_size); + curl_close($ch); + return new Response($code, $duration, $headers, $body, null); + } + + private static function escapeQuotes($str) + { + if (is_null($str)) { + return null; + } + $find = array("\\", "\""); + $replace = array("\\\\", "\\\""); + return str_replace($find, $replace, $str); + } +} diff --git a/php-sdk/src/Qiniu/Http/Error.php b/php-sdk/src/Qiniu/Http/Error.php new file mode 100644 index 0000000..8fba74f --- /dev/null +++ b/php-sdk/src/Qiniu/Http/Error.php @@ -0,0 +1,38 @@ + + * {"error" : "detailed error message"} + * + */ +final class Error +{ + private $url; + /** + * @var Response + */ + private $response; + + public function __construct($url, $response) + { + $this->url = $url; + $this->response = $response; + } + + public function code() + { + return $this->response->statusCode; + } + + public function getResponse() + { + return $this->response; + } + + public function message() + { + return $this->response->error; + } +} diff --git a/php-sdk/src/Qiniu/Http/Header.php b/php-sdk/src/Qiniu/Http/Header.php new file mode 100644 index 0000000..1dcf328 --- /dev/null +++ b/php-sdk/src/Qiniu/Http/Header.php @@ -0,0 +1,291 @@ + $values) { + $normalizedKey = self::normalizeKey($key); + $normalizedValues = array(); + if (!is_array($values)) { + array_push( + $normalizedValues, + self::normalizeValue($values) + ); + } else { + foreach ($values as $value) { + array_push( + $normalizedValues, + self::normalizeValue($value) + ); + } + } + $this->data[$normalizedKey] = $normalizedValues; + } + return $this; + } + + /** + * return origin headers, which is field name case-sensitive + * + * @param string $raw + * + * @return array + */ + public static function parseRawText($raw) + { + $multipleHeaders = explode("\r\n\r\n", trim($raw)); + $headers = array(); + $headerLines = explode("\r\n", end($multipleHeaders)); + foreach ($headerLines as $line) { + $headerLine = trim($line); + $kv = explode(':', $headerLine); + if (count($kv) <= 1) { + continue; + } + // for http2 [Pseudo-Header Fields](https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.1) + if ($kv[0] == "") { + $fieldName = ":" . $kv[1]; + } else { + $fieldName = $kv[0]; + } + $fieldValue = trim(substr($headerLine, strlen($fieldName . ":"))); + if (isset($headers[$fieldName])) { + array_push($headers[$fieldName], $fieldValue); + } else { + $headers[$fieldName] = array($fieldValue); + } + } + return $headers; + } + + /** + * @param string $raw + * + * @return Header + */ + public static function fromRawText($raw) + { + return new Header(self::parseRawText($raw)); + } + + /** + * @param string $key + * + * @return string + */ + public static function normalizeKey($key) + { + $key = trim($key); + + if (!self::isValidKeyName($key)) { + return $key; + } + + return \Qiniu\ucwords(strtolower($key), '-'); + } + + /** + * @param string|numeric $value + * + * @return string|numeric + */ + public static function normalizeValue($value) + { + if (is_numeric($value)) { + return $value + 0; + } + return trim($value); + } + + /** + * @return array + */ + public function getRawData() + { + return $this->data; + } + + /** + * @param $offset string + * + * @return boolean + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function offsetExists($offset) + { + $key = self::normalizeKey($offset); + return isset($this->data[$key]); + } + + /** + * @param $offset string + * + * @return string|null + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function offsetGet($offset) + { + $key = self::normalizeKey($offset); + if (isset($this->data[$key]) && count($this->data[$key])) { + return $this->data[$key][0]; + } else { + return null; + } + } + + /** + * @param $offset string + * @param $value string + * + * @return void + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function offsetSet($offset, $value) + { + $key = self::normalizeKey($offset); + if (isset($this->data[$key]) && count($this->data[$key]) > 0) { + $this->data[$key][0] = self::normalizeValue($value); + } else { + $this->data[$key] = array(self::normalizeValue($value)); + } + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function offsetUnset($offset) + { + $key = self::normalizeKey($offset); + unset($this->data[$key]); + } + + /** + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function getIterator() + { + $arr = array(); + foreach ($this->data as $k => $v) { + $arr[$k] = $v[0]; + } + return new \ArrayIterator($arr); + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function count() + { + return count($this->data); + } + + private static $isTokenTable = array( + '!' => true, + '#' => true, + '$' => true, + '%' => true, + '&' => true, + '\'' => true, + '*' => true, + '+' => true, + '-' => true, + '.' => true, + '0' => true, + '1' => true, + '2' => true, + '3' => true, + '4' => true, + '5' => true, + '6' => true, + '7' => true, + '8' => true, + '9' => true, + 'A' => true, + 'B' => true, + 'C' => true, + 'D' => true, + 'E' => true, + 'F' => true, + 'G' => true, + 'H' => true, + 'I' => true, + 'J' => true, + 'K' => true, + 'L' => true, + 'M' => true, + 'N' => true, + 'O' => true, + 'P' => true, + 'Q' => true, + 'R' => true, + 'S' => true, + 'T' => true, + 'U' => true, + 'W' => true, + 'V' => true, + 'X' => true, + 'Y' => true, + 'Z' => true, + '^' => true, + '_' => true, + '`' => true, + 'a' => true, + 'b' => true, + 'c' => true, + 'd' => true, + 'e' => true, + 'f' => true, + 'g' => true, + 'h' => true, + 'i' => true, + 'j' => true, + 'k' => true, + 'l' => true, + 'm' => true, + 'n' => true, + 'o' => true, + 'p' => true, + 'q' => true, + 'r' => true, + 's' => true, + 't' => true, + 'u' => true, + 'v' => true, + 'w' => true, + 'x' => true, + 'y' => true, + 'z' => true, + '|' => true, + '~' => true, + ); + + /** + * @param string $str + * + * @return boolean + */ + private static function isValidKeyName($str) + { + for ($i = 0; $i < strlen($str); $i += 1) { + if (!isset(self::$isTokenTable[$str[$i]])) { + return false; + } + } + return true; + } +} diff --git a/php-sdk/src/Qiniu/Http/Middleware/Middleware.php b/php-sdk/src/Qiniu/Http/Middleware/Middleware.php new file mode 100644 index 0000000..fe8a64c --- /dev/null +++ b/php-sdk/src/Qiniu/Http/Middleware/Middleware.php @@ -0,0 +1,31 @@ + $middlewares + * @param callable(Request): Response $handler + * @return callable(Request): Response + */ +function compose($middlewares, $handler) +{ + $next = $handler; + foreach (array_reverse($middlewares) as $middleware) { + $next = function ($request) use ($middleware, $next) { + return $middleware->send($request, $next); + }; + } + return $next; +} diff --git a/php-sdk/src/Qiniu/Http/Middleware/RetryDomainsMiddleware.php b/php-sdk/src/Qiniu/Http/Middleware/RetryDomainsMiddleware.php new file mode 100644 index 0000000..829ab87 --- /dev/null +++ b/php-sdk/src/Qiniu/Http/Middleware/RetryDomainsMiddleware.php @@ -0,0 +1,76 @@ + backup domains. + */ + private $backupDomains; + + /** + * @var numeric max retry times for each backup domains. + */ + private $maxRetryTimes; + + /** + * @var callable args response and request; returns bool; If true will retry with backup domains. + */ + private $retryCondition; + + /** + * @param array $backupDomains + * @param numeric $maxRetryTimes + */ + public function __construct($backupDomains, $maxRetryTimes = 2, $retryCondition = null) + { + $this->backupDomains = $backupDomains; + $this->maxRetryTimes = $maxRetryTimes; + $this->retryCondition = $retryCondition; + } + + private function shouldRetry($resp, $req) + { + if (is_callable($this->retryCondition)) { + return call_user_func($this->retryCondition, $resp, $req); + } + + return !$resp || $resp->needRetry(); + } + + /** + * @param Request $request + * @param callable(Request): Response $next + * @return Response + */ + public function send($request, $next) + { + $response = null; + $urlComponents = parse_url($request->url); + + foreach (array_merge(array($urlComponents["host"]), $this->backupDomains) as $backupDomain) { + $urlComponents["host"] = $backupDomain; + $request->url = \Qiniu\unparse_url($urlComponents); + $retriedTimes = 0; + + while ($retriedTimes < $this->maxRetryTimes) { + $response = $next($request); + + $retriedTimes += 1; + + if (!$this->shouldRetry($response, $request)) { + return $response; + } + } + } + + if (!$response) { + $response = $next($request); + } + + return $response; + } +} diff --git a/php-sdk/src/Qiniu/Http/Proxy.php b/php-sdk/src/Qiniu/Http/Proxy.php new file mode 100644 index 0000000..fac6ba1 --- /dev/null +++ b/php-sdk/src/Qiniu/Http/Proxy.php @@ -0,0 +1,34 @@ +proxy = $proxy; + $this->proxy_auth = $proxy_auth; + $this->proxy_user_password = $proxy_user_password; + } + + public function makeReqOpt() + { + $reqOpt = new RequestOptions(); + if ($this->proxy !== null) { + $reqOpt->proxy = $this->proxy; + } + if ($this->proxy_auth !== null) { + $reqOpt->proxy_auth = $this->proxy_auth; + } + if ($this->proxy_user_password !== null) { + $reqOpt->proxy_user_password = $this->proxy_user_password; + } + return $reqOpt; + } +} diff --git a/php-sdk/src/Qiniu/Http/Request.php b/php-sdk/src/Qiniu/Http/Request.php new file mode 100644 index 0000000..5a31bf6 --- /dev/null +++ b/php-sdk/src/Qiniu/Http/Request.php @@ -0,0 +1,42 @@ + + */ + public $headers; + + /** + * @var mixed|null + */ + public $body; + + /** + * @var string + */ + public $method; + + /** + * @var RequestOptions + */ + public $opt; + + public function __construct($method, $url, array $headers = array(), $body = null, $opt = null) + { + $this->method = strtoupper($method); + $this->url = $url; + $this->headers = $headers; + $this->body = $body; + if ($opt === null) { + $opt = new RequestOptions(); + } + $this->opt = $opt; + } +} diff --git a/php-sdk/src/Qiniu/Http/RequestOptions.php b/php-sdk/src/Qiniu/Http/RequestOptions.php new file mode 100644 index 0000000..be0c6d5 --- /dev/null +++ b/php-sdk/src/Qiniu/Http/RequestOptions.php @@ -0,0 +1,104 @@ + + */ + public $middlewares; + + public function __construct( + $connection_timeout = null, + $connection_timeout_ms = null, + $timeout = null, + $timeout_ms = null, + $middlewares = array(), + $proxy = null, + $proxy_auth = null, + $proxy_user_password = null + ) { + $this->connection_timeout = $connection_timeout; + $this->connection_timeout_ms = $connection_timeout_ms; + $this->timeout = $timeout; + $this->timeout_ms = $timeout_ms; + $this->proxy = $proxy; + $this->proxy_auth = $proxy_auth; + $this->proxy_user_password = $proxy_user_password; + $this->middlewares = $middlewares; + } + + public function getCurlOpt() + { + $result = array(); + if ($this->connection_timeout != null) { + $result[CURLOPT_CONNECTTIMEOUT] = $this->connection_timeout; + } + if ($this->connection_timeout_ms != null) { + $result[CURLOPT_CONNECTTIMEOUT_MS] = $this->connection_timeout_ms; + } + if ($this->timeout != null) { + $result[CURLOPT_TIMEOUT] = $this->timeout; + } + if ($this->timeout_ms != null) { + $result[CURLOPT_TIMEOUT_MS] = $this->timeout_ms; + } + if ($this->proxy != null) { + $result[CURLOPT_PROXY] = $this->proxy; + } + if ($this->proxy_auth != null) { + $result[CURLOPT_PROXYAUTH] = $this->proxy_auth; + } + if ($this->proxy_user_password != null) { + $result[CURLOPT_PROXYUSERPWD] = $this->proxy_user_password; + } + return $result; + } +} diff --git a/php-sdk/src/Qiniu/Http/Response.php b/php-sdk/src/Qiniu/Http/Response.php new file mode 100644 index 0000000..cd77903 --- /dev/null +++ b/php-sdk/src/Qiniu/Http/Response.php @@ -0,0 +1,220 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Reserved for WebDAV advanced collections expired proposal', + 426 => 'Upgrade required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates (Experimental)', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ); + + /** + * @param int $code 状态码 + * @param double $duration 请求时长 + * @param array $headers 响应头部 + * @param string $body 响应内容 + * @param string $error 错误描述 + */ + public function __construct($code, $duration, array $headers = array(), $body = null, $error = null) + { + $this->statusCode = $code; + $this->duration = $duration; + $this->headers = array(); + $this->body = $body; + $this->error = $error; + $this->jsonData = null; + + if ($error !== null) { + return; + } + + foreach ($headers as $k => $vs) { + if (is_array($vs)) { + $this->headers[$k] = $vs[count($vs) - 1]; + } else { + $this->headers[$k] = $vs; + } + } + $this->normalizedHeaders = new Header($headers); + + if ($body === null) { + if ($code >= 400) { + $this->error = self::$statusTexts[$code]; + } + return; + } + if (self::isJson($this->normalizedHeaders)) { + try { + $jsonData = self::bodyJson($body); + if ($code >= 400) { + $this->error = $body; + if ($jsonData['error'] !== null) { + $this->error = $jsonData['error']; + } + } + $this->jsonData = $jsonData; + } catch (\InvalidArgumentException $e) { + $this->error = $body; + if ($code >= 200 && $code < 300) { + $this->error = $e->getMessage(); + } + } + } elseif ($code >= 400) { + $this->error = $body; + } + return; + } + + public function json() + { + return $this->jsonData; + } + + public function headers($normalized = false) + { + if ($normalized) { + return $this->normalizedHeaders; + } + return $this->headers; + } + + public function body() + { + return $this->body; + } + + private static function bodyJson($body) + { + return \Qiniu\json_decode((string) $body, true, 512); + } + + public function xVia() + { + $via = $this->normalizedHeaders['X-Via']; + if ($via === null) { + $via = $this->normalizedHeaders['X-Px']; + } + if ($via === null) { + $via = $this->normalizedHeaders['Fw-Via']; + } + return $via; + } + + public function xLog() + { + return $this->normalizedHeaders['X-Log']; + } + + public function xReqId() + { + return $this->normalizedHeaders['X-Reqid']; + } + + public function ok() + { + return $this->statusCode >= 200 && $this->statusCode < 300 && $this->error === null; + } + + public function needRetry() + { + if ($this->statusCode > 0 && $this->statusCode < 500) { + return false; + } + + // https://developer.qiniu.com/fusion/kb/1352/the-http-request-return-a-status-code + if (in_array($this->statusCode, array( + 501, 509, 573, 579, 608, 612, 614, 616, 618, 630, 631, 632, 640, 701 + ))) { + return false; + } + + return true; + } + + private static function isJson($headers) + { + return isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'application/json') === 0; + } +} diff --git a/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php b/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php new file mode 100644 index 0000000..f5575ed --- /dev/null +++ b/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php @@ -0,0 +1,292 @@ + + */ + public function thumbnail( + $url, + $mode, + $width, + $height, + $format = null, + $interlace = null, + $quality = null, + $ignoreError = 1 + ) { + + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + // 参数合法性效验 + if (!in_array(intval($mode), $this->modeArr, true)) { + return $url; + } + + if (!$width || !$height) { + return $url; + } + + $thumbStr = 'imageView2/' . $mode . '/w/' . $width . '/h/' . $height . '/'; + + // 拼接输出格式 + if (!is_null($format) + && in_array($format, $this->formatArr) + ) { + $thumbStr .= 'format/' . $format . '/'; + } + + // 拼接渐进显示 + if (!is_null($interlace) + && in_array(intval($interlace), array(0, 1), true) + ) { + $thumbStr .= 'interlace/' . $interlace . '/'; + } + + // 拼接图片质量 + if (!is_null($quality) + && intval($quality) >= 0 + && intval($quality) <= 100 + ) { + $thumbStr .= 'q/' . $quality . '/'; + } + + $thumbStr .= 'ignore-error/' . $ignoreError . '/'; + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $thumbStr; + } + + /** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param int $dissolve 透明度 + * @param string $gravity 水印位置 + * @param int $dx 横轴边距 + * @param int $dy 纵轴边距 + * @param int $watermarkScale 自适应原图的短边比例 + * @return string + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html + * @author Sherlock Ren + */ + public function waterImg( + $url, + $image, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null, + $watermarkScale = null + ) { + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + $waterStr = 'watermark/1/image/' . \Qiniu\base64_urlSafeEncode($image) . '/'; + + // 拼接水印透明度 + if (is_numeric($dissolve) + && $dissolve <= 100 + ) { + $waterStr .= 'dissolve/' . $dissolve . '/'; + } + + // 拼接水印位置 + if (in_array($gravity, $this->gravityArr, true)) { + $waterStr .= 'gravity/' . $gravity . '/'; + } + + // 拼接横轴边距 + if (!is_null($dx) + && is_numeric($dx) + ) { + $waterStr .= 'dx/' . $dx . '/'; + } + + // 拼接纵轴边距 + if (!is_null($dy) + && is_numeric($dy) + ) { + $waterStr .= 'dy/' . $dy . '/'; + } + + // 拼接自适应原图的短边比例 + if (!is_null($watermarkScale) + && is_numeric($watermarkScale) + && $watermarkScale > 0 + && $watermarkScale < 1 + ) { + $waterStr .= 'ws/' . $watermarkScale . '/'; + } + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr; + } + + /** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 + * @param int $dissolve 透明度 + * @param string $gravity 水印位置 + * @param int $dx 横轴边距 + * @param int $dy 纵轴边距 + * @return string + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @author Sherlock Ren + */ + public function waterText( + $url, + $text, + $font = '黑体', + $fontSize = 0, + $fontColor = null, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null + ) { + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + $waterStr = 'watermark/2/text/' + . \Qiniu\base64_urlSafeEncode($text) . '/font/' + . \Qiniu\base64_urlSafeEncode($font) . '/'; + + // 拼接文字大小 + if (is_int($fontSize)) { + $waterStr .= 'fontsize/' . $fontSize . '/'; + } + + // 拼接文字颜色 + if (!is_null($fontColor) + && $fontColor + ) { + $waterStr .= 'fill/' . \Qiniu\base64_urlSafeEncode($fontColor) . '/'; + } + + // 拼接水印透明度 + if (is_numeric($dissolve) + && $dissolve <= 100 + ) { + $waterStr .= 'dissolve/' . $dissolve . '/'; + } + + // 拼接水印位置 + if (in_array($gravity, $this->gravityArr, true)) { + $waterStr .= 'gravity/' . $gravity . '/'; + } + + // 拼接横轴边距 + if (!is_null($dx) + && is_numeric($dx) + ) { + $waterStr .= 'dx/' . $dx . '/'; + } + + // 拼接纵轴边距 + if (!is_null($dy) + && is_numeric($dy) + ) { + $waterStr .= 'dy/' . $dy . '/'; + } + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr; + } + + /** + * 效验url合法性 + * + * @param string $url url链接 + * @return string + * @author Sherlock Ren + */ + protected function isUrl($url) + { + $urlArr = parse_url($url); + + return $urlArr['scheme'] + && in_array($urlArr['scheme'], array('http', 'https')) + && $urlArr['host'] + && $urlArr['path']; + } + + /** + * 检测是否有query + * + * @param string $url url链接 + * @return string + * @author Sherlock Ren + */ + protected function hasQuery($url) + { + $urlArr = parse_url($url); + + return !empty($urlArr['query']); + } +} diff --git a/php-sdk/src/Qiniu/Processing/Operation.php b/php-sdk/src/Qiniu/Processing/Operation.php new file mode 100644 index 0000000..839703c --- /dev/null +++ b/php-sdk/src/Qiniu/Processing/Operation.php @@ -0,0 +1,69 @@ +auth = $auth; + $this->domain = $domain; + $this->token_expire = $token_expire; + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + + /** + * 对资源文件进行处理 + * + * @param string $key 待处理的资源文件名 + * @param string $fops string|array fop操作,多次fop操作以array的形式传入。 + * eg. imageView2/1/w/200/h/200, imageMogr2/thumbnail/!75px + * + * @return array 文件处理后的结果及错误。 + * + * @link http://developer.qiniu.com/docs/v6/api/reference/fop/ + */ + public function execute($key, $fops) + { + $url = $this->buildUrl($key, $fops); + $resp = Client::get($url, array(), $this->proxy->makeReqOpt()); + if (!$resp->ok()) { + return array(null, new Error($url, $resp)); + } + if ($resp->json() !== null) { + return array($resp->json(), null); + } + return array($resp->body, null); + } + + public function buildUrl($key, $fops, $protocol = 'http') + { + if (is_array($fops)) { + $fops = implode('|', $fops); + } + + $url = $protocol . "://$this->domain/$key?$fops"; + if ($this->auth !== null) { + $url = $this->auth->privateDownloadUrl($url, $this->token_expire); + } + + return $url; + } +} diff --git a/php-sdk/src/Qiniu/Processing/PersistentFop.php b/php-sdk/src/Qiniu/Processing/PersistentFop.php new file mode 100644 index 0000000..8dca4a9 --- /dev/null +++ b/php-sdk/src/Qiniu/Processing/PersistentFop.php @@ -0,0 +1,135 @@ +auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * 对资源文件进行异步持久化处理 + * @param string $bucket 资源所在空间 + * @param string $key 待处理的源文件 + * @param string|array $fops 待处理的pfop操作,多个pfop操作以array的形式传入。 + * eg. avthumb/mp3/ab/192k, vframe/jpg/offset/7/w/480/h/360 + * @param string $pipeline 资源处理队列 + * @param string $notify_url 处理结果通知地址 + * @param bool $force 是否强制执行一次新的指令 + * @param int $type 为 `1` 时开启闲时任务 + * + * + * @return array 返回持久化处理的 persistentId 与可能出现的错误。 + * + * @link http://developer.qiniu.com/docs/v6/api/reference/fop/ + */ + public function execute( + $bucket, + $key, + $fops = null, + $pipeline = null, + $notify_url = null, + $force = false, + $type = null, + $workflow_template_id = null + ) { + if (is_array($fops)) { + $fops = implode(';', $fops); + } + + if (!$fops && !$workflow_template_id) { + throw new \InvalidArgumentException('Must provide one of fops or template_id'); + } + + $params = array('bucket' => $bucket, 'key' => $key); + \Qiniu\setWithoutEmpty($params, 'fops', $fops); + \Qiniu\setWithoutEmpty($params, 'pipeline', $pipeline); + \Qiniu\setWithoutEmpty($params, 'notifyURL', $notify_url); + \Qiniu\setWithoutEmpty($params, 'type', $type); + \Qiniu\setWithoutEmpty($params, 'workflowTemplateID', $workflow_template_id); + if ($force) { + $params['force'] = 1; + } + $data = http_build_query($params); + $scheme = "http://"; + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $apiHost = $this->getApiHost(); + $url = $scheme . $apiHost . '/pfop/'; + $headers = $this->auth->authorization($url, $data, 'application/x-www-form-urlencoded'); + $headers['Content-Type'] = 'application/x-www-form-urlencoded'; + $response = Client::post($url, $data, $headers, $this->proxy->makeReqOpt()); + if (!$response->ok()) { + return array(null, new Error($url, $response)); + } + $r = $response->json(); + $id = $r['persistentId']; + return array($id, null); + } + + /** + * @param string $id + * @return array 返回任务状态与可能出现的错误 + */ + public function status($id) + { + $scheme = "http://"; + + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $apiHost = $this->getApiHost(); + $url = $scheme . $apiHost . "/status/get/prefop?id=$id"; + $response = Client::get($url, array(), $this->proxy->makeReqOpt()); + if (!$response->ok()) { + return array(null, new Error($url, $response)); + } + return array($response->json(), null); + } + + private function getApiHost() + { + if (!empty($this->config->zone) && !empty($this->config->zone->apiHost)) { + $apiHost = $this->config->zone->apiHost; + } else { + $apiHost = Config::API_HOST; + } + return $apiHost; + } +} diff --git a/php-sdk/src/Qiniu/Region.php b/php-sdk/src/Qiniu/Region.php new file mode 100644 index 0000000..220a5a3 --- /dev/null +++ b/php-sdk/src/Qiniu/Region.php @@ -0,0 +1,229 @@ +srcUpHosts = $srcUpHosts; + $this->cdnUpHosts = $cdnUpHosts; + $this->rsHost = $rsHost; + $this->rsfHost = $rsfHost; + $this->apiHost = $apiHost; + $this->iovipHost = $iovipHost; + $this->ttl = $ttl; + } + + //华东机房 + public static function regionHuadong() + { + $regionHuadong = new Region( + array("up.qiniup.com"), + array('upload.qiniup.com'), + 'rs-z0.qiniuapi.com', + 'rsf-z0.qiniuapi.com', + 'api.qiniuapi.com', + 'iovip.qbox.me' + ); + return $regionHuadong; + } + + //华东机房内网上传 + public static function qvmRegionHuadong() + { + $qvmRegionHuadong = new Region( + array("free-qvm-z0-xs.qiniup.com"), + 'rs-z0.qiniuapi.com', + 'rsf-z0.qiniuapi.com', + 'api.qiniuapi.com', + 'iovip.qbox.me' + ); + return $qvmRegionHuadong; + } + + //华北机房内网上传 + public static function qvmRegionHuabei() + { + $qvmRegionHuabei = new Region( + array("free-qvm-z1-zz.qiniup.com"), + "rs-z1.qiniuapi.com", + "rsf-z1.qiniuapi.com", + "api-z1.qiniuapi.com", + "iovip-z1.qbox.me" + ); + return $qvmRegionHuabei; + } + + //华北机房 + public static function regionHuabei() + { + $regionHuabei = new Region( + array('up-z1.qiniup.com'), + array('upload-z1.qiniup.com'), + "rs-z1.qiniuapi.com", + "rsf-z1.qiniuapi.com", + "api-z1.qiniuapi.com", + "iovip-z1.qbox.me" + ); + + return $regionHuabei; + } + + //华南机房 + public static function regionHuanan() + { + $regionHuanan = new Region( + array('up-z2.qiniup.com'), + array('upload-z2.qiniup.com'), + "rs-z2.qiniuapi.com", + "rsf-z2.qiniuapi.com", + "api-z2.qiniuapi.com", + "iovip-z2.qbox.me" + ); + return $regionHuanan; + } + + //华东2 机房 + public static function regionHuadong2() + { + return new Region( + array('up-cn-east-2.qiniup.com'), + array('upload-cn-east-2.qiniup.com'), + "rs-cn-east-2.qiniuapi.com", + "rsf-cn-east-2.qiniuapi.com", + "api-cn-east-2.qiniuapi.com", + "iovip-cn-east-2.qiniuio.com" + ); + } + + //北美机房 + public static function regionNorthAmerica() + { + //北美机房 + $regionNorthAmerica = new Region( + array('up-na0.qiniup.com'), + array('upload-na0.qiniup.com'), + "rs-na0.qiniuapi.com", + "rsf-na0.qiniuapi.com", + "api-na0.qiniuapi.com", + "iovip-na0.qbox.me" + ); + return $regionNorthAmerica; + } + + //新加坡机房 + public static function regionSingapore() + { + //新加坡机房 + $regionSingapore = new Region( + array('up-as0.qiniup.com'), + array('upload-as0.qiniup.com'), + "rs-as0.qiniuapi.com", + "rsf-as0.qiniuapi.com", + "api-as0.qiniuapi.com", + "iovip-as0.qbox.me" + ); + return $regionSingapore; + } + + /* + * GET /v4/query?ak=&bucket= + * @param string $ak + * @param string $bucket + * @param string $ucHost|null + * @param array $backupUcHosts + * @param int $retryTimes + * @param RequestOptions|null $reqOpt + * @return Response + **/ + public static function queryRegion( + $ak, + $bucket, + $ucHost = null, + $backupUcHosts = array(), + $retryTimes = 2, + $reqOpt = null + ) { + $region = new Region(); + if (!$ucHost) { + $ucHost = "https://" . Config::QUERY_REGION_HOST; + } + $url = $ucHost . '/v4/query' . "?ak=$ak&bucket=$bucket"; + if ($reqOpt == null) { + $reqOpt = new RequestOptions(); + } + $reqOpt->middlewares = array( + new RetryDomainsMiddleware( + $backupUcHosts, + $retryTimes + ) + ); + $ret = Client::get($url, array(), $reqOpt); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + if (!is_array($r["hosts"]) || count($r["hosts"]) == 0) { + return array(null, new Error($url, $ret)); + } + + // parse region; + $regionHost = $r["hosts"][0]; + $region->cdnUpHosts = array_merge($region->cdnUpHosts, $regionHost['up']['domains']); + $region->srcUpHosts = array_merge($region->srcUpHosts, $regionHost['up']['domains']); + + // set specific hosts + $region->iovipHost = $regionHost['io']['domains'][0]; + if (isset($regionHost['rs']['domains']) && count($regionHost['rs']['domains']) > 0) { + $region->rsHost = $regionHost['rs']['domains'][0]; + } else { + $region->rsHost = Config::RS_HOST; + } + if (isset($regionHost['rsf']['domains']) && count($regionHost['rsf']['domains']) > 0) { + $region->rsfHost = $regionHost['rsf']['domains'][0]; + } else { + $region->rsfHost = Config::RSF_HOST; + } + if (isset($regionHost['api']['domains']) && count($regionHost['api']['domains']) > 0) { + $region->apiHost = $regionHost['api']['domains'][0]; + } else { + $region->apiHost = Config::API_HOST; + } + + // set ttl + $region->ttl = $regionHost['ttl']; + + return $region; + } +} diff --git a/php-sdk/src/Qiniu/Rtc/AppClient.php b/php-sdk/src/Qiniu/Rtc/AppClient.php new file mode 100644 index 0000000..3f245db --- /dev/null +++ b/php-sdk/src/Qiniu/Rtc/AppClient.php @@ -0,0 +1,236 @@ +auth = $auth; + $this->baseURL = sprintf("%s/%s/apps", Config::RTCAPI_HOST, Config::RTCAPI_VERSION); + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * 创建应用 + * + * @param string $hub 绑定的直播 hub + * @param string $title app 的名称 注意,Title 不是唯一标识,重复 create 动作将生成多个 app + * @param int $maxUsers 连麦房间支持的最大在线人数 + * @param bool $noAutoKickUser 禁止自动踢人(抢流),默认为 false + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_1 + */ + public function createApp($hub, $title, $maxUsers = null, $noAutoKickUser = null) + { + $params = array(); + $params['hub'] = $hub; + $params['title'] = $title; + if (!empty($maxUsers)) { + $params['maxUsers'] = $maxUsers; + } + if ($noAutoKickUser !== null) { + $params['noAutoKickUser'] = $noAutoKickUser; + } + $body = json_encode($params); + return $this->post($this->baseURL, $body); + } + + /** + * 更新一个应用的配置信息 + * + * @param string $appId app 的唯一标识,创建的时候由系统生成 + * @param string $hub app 的名称,可选 + * @param string $title 绑定的直播 hub,可选,用于合流后 rtmp 推流 + * @param int $maxUsers 连麦房间支持的最大在线人数,可选 + * @param bool $noAutoKickUser 禁止自动踢人,可选 + * @param null $mergePublishRtmp 连麦合流转推 RTMP 的配置,可选择。其详细配置可以参考文档 + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_1 + */ + public function updateApp($appId, $hub, $title, $maxUsers = null, $noAutoKickUser = null, $mergePublishRtmp = null) + { + $url = $this->baseURL . '/' . $appId; + $params = array(); + $params['hub'] = $hub; + $params['title'] = $title; + if (!empty($maxUsers)) { + $params['maxUsers'] = $maxUsers; + } + if ($noAutoKickUser !== null) { + $params['noAutoKickUser'] = $noAutoKickUser; + } + if (!empty($mergePublishRtmp)) { + $params['mergePublishRtmp'] = $mergePublishRtmp; + } + $body = json_encode($params); + return $this->post($url, $body); + } + + /** + * 获取应用信息 + * + * @param string $appId + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_1 + */ + public function getApp($appId) + { + $url = $this->baseURL . '/' . $appId; + return $this->get($url); + } + + /** + * 删除应用 + * + * @param string $appId app 的唯一标识,创建的时候由系统生成 + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_1 + */ + public function deleteApp($appId) + { + $url = $this->baseURL . '/' . $appId; + return $this->delete($url); + } + + /** + * 获取房间内用户列表 + * + * @param string $appId app 的唯一标识,创建的时候由系统生成 + * @param string $roomName 操作所查询的连麦房间 + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_2 + */ + public function listUser($appId, $roomName) + { + $url = sprintf("%s/%s/rooms/%s/users", $this->baseURL, $appId, $roomName); + return $this->get($url); + } + + /** + * 指定一个用户踢出房间 + * + * @param string $appId app 的唯一标识,创建的时候由系统生成 + * @param string $roomName 连麦房间 + * @param string $userId 操作所剔除的用户 + * @return mixed + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_2 + */ + public function kickUser($appId, $roomName, $userId) + { + $url = sprintf("%s/%s/rooms/%s/users/%s", $this->baseURL, $appId, $roomName, $userId); + return $this->delete($url); + } + + /** + * 停止一个房间的合流转推 + * + * @param string $appId + * @param string $roomName + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_2 + */ + public function stopMerge($appId, $roomName) + { + $url = sprintf("%s/%s/rooms/%s/merge", $this->baseURL, $appId, $roomName); + return $this->delete($url); + } + + /** + * 获取应用中活跃房间 + * + * @param string $appId 连麦房间所属的 app + * @param null $prefix 所查询房间名的前缀索引,可以为空。 + * @param int $offset 分页查询的位移标记 + * @param int $limit 此次查询的最大长度 + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_2 + */ + public function listActiveRooms($appId, $prefix = null, $offset = null, $limit = null) + { + $query = array(); + if (isset($prefix)) { + $query['prefix'] = $prefix; + } + if (isset($offset)) { + $query['offset'] = $offset; + } + if (isset($limit)) { + $query['limit'] = $limit; + } + if (isset($query) && !empty($query)) { + $query = '?' . http_build_query($query); + $url = sprintf("%s/%s/rooms%s", $this->baseURL, $appId, $query); + } else { + $url = sprintf("%s/%s/rooms", $this->baseURL, $appId); + } + return $this->get($url); + } + + /** + * 生成加入房间的令牌 + * + * @param string $appId app 的唯一标识,创建的时候由系统生成 + * @param string $roomName 房间名称,需满足规格 ^[a-zA-Z0-9_-]{3,64}$ + * @param string $userId 请求加入房间的用户 ID,需满足规格 ^[a-zA-Z0-9_-]{3,50}$ + * @param int $expireAt 鉴权的有效时间,传入以秒为单位的64位 Unix 绝对时间 + * @param string $permission 该用户的房间管理权限,"admin" 或 "user",默认为 "user" + * @return string + * @link https://doc.qnsdk.com/rtn/docs/server_overview#1 + */ + public function appToken($appId, $roomName, $userId, $expireAt, $permission) + { + $params = array(); + $params['appId'] = $appId; + $params['userId'] = $userId; + $params['roomName'] = $roomName; + $params['permission'] = $permission; + $params['expireAt'] = $expireAt; + $appAccessString = json_encode($params); + return $this->auth->signWithData($appAccessString); + } + + private function get($url, $cType = null) + { + $rtcToken = $this->auth->authorizationV2($url, "GET", null, $cType); + $rtcToken['Content-Type'] = $cType; + $ret = Client::get($url, $rtcToken, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function delete($url, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "DELETE", null, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::delete($url, $rtcToken, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function post($url, $body, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "POST", $body, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::post($url, $body, $rtcToken, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } +} diff --git a/php-sdk/src/Qiniu/Sms/Sms.php b/php-sdk/src/Qiniu/Sms/Sms.php new file mode 100644 index 0000000..c96409b --- /dev/null +++ b/php-sdk/src/Qiniu/Sms/Sms.php @@ -0,0 +1,382 @@ +auth = $auth; + $this->baseURL = sprintf("%s/%s/", Config::SMS_HOST, Config::SMS_VERSION); + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * 创建签名 + * + * @param string $signature 签名 + * @param string $source 签名来源,申请签名时必须指定签名来源 + * @param string $pics 签名对应的资质证明图片进行 base64 编码格式转换后的字符串,可选 + * @return array + * + * @link https://developer.qiniu.com/sms/api/5844/sms-api-create-signature + */ + public function createSignature($signature, $source, $pics = null) + { + $params = array(); + $params['signature'] = $signature; + $params['source'] = $source; + if (!empty($pics)) { + $params['pics'] = array($this->imgToBase64($pics)); + } + $body = json_encode($params); + $url = $this->baseURL . 'signature'; + return $this->post($url, $body); + } + + /** + * 编辑签名 + * + * @param string $id 签名 ID + * @param string $signature 签名 + * @param string $source 签名来源 + * @param string $pics 签名对应的资质证明图片进行 base64 编码格式转换后的字符串,可选 + * @return array + * @link https://developer.qiniu.com/sms/api/5890/sms-api-edit-signature + */ + public function updateSignature($id, $signature, $source, $pics = null) + { + $params = array(); + $params['signature'] = $signature; + $params['source'] = $source; + if (!empty($pics)) { + $params['pics'] = array($this->imgToBase64($pics)); + } + $body = json_encode($params); + $url = $this->baseURL . 'signature/' . $id; + return $this->PUT($url, $body); + } + + /** + * 列出签名 + * + * @param string $audit_status 审核状态:"passed"(通过), "rejected"(未通过), "reviewing"(审核中) + * @param int $page 页码。默认为 1 + * @param int $page_size 分页大小。默认为 20 + * @return array + * @link https://developer.qiniu.com/sms/api/5889/sms-api-query-signature + */ + public function querySignature($audit_status = null, $page = 1, $page_size = 20) + { + + $url = sprintf( + "%s?audit_status=%s&page=%s&page_size=%s", + $this->baseURL . 'signature', + $audit_status, + $page, + $page_size + ); + return $this->get($url); + } + + /** + * 查询单个签名 + * + * @param string $signature_id + * @return array + * @link https://developer.qiniu.com/sms/api/5970/query-a-single-signature + */ + public function checkSingleSignature($signature_id) + { + + $url = sprintf( + "%s/%s", + $this->baseURL . 'signature', + $signature_id + ); + return $this->get($url); + } + + /** + * 删除签名 + * + * @param string $signature_id 签名 ID + * @return array + * @link https://developer.qiniu.com/sms/api/5891/sms-api-delete-signature + */ + public function deleteSignature($signature_id) + { + $url = $this->baseURL . 'signature/' . $signature_id; + return $this->delete($url); + } + + /** + * 创建模板 + * + * @param string $name 模板名称 + * @param string $template 模板内容 可设置自定义变量,发送短信时候使用,参考:${code} + * @param string $type notification:通知类,verification:验证码,marketing:营销类,voice:语音类 + * @param string $description 申请理由简述 + * @param string $signature_id 已经审核通过的签名 + * @return array array + * @link https://developer.qiniu.com/sms/api/5893/sms-api-create-template + */ + public function createTemplate( + $name, + $template, + $type, + $description, + $signature_id + ) { + $params = array(); + $params['name'] = $name; + $params['template'] = $template; + $params['type'] = $type; + $params['description'] = $description; + $params['signature_id'] = $signature_id; + + $body = json_encode($params); + $url = $this->baseURL . 'template'; + return $this->post($url, $body); + } + + /** + * 列出模板 + * + * @param string $audit_status 审核状态:passed (通过), rejected (未通过), reviewing (审核中) + * @param int $page 页码。默认为 1 + * @param int $page_size 分页大小。默认为 20 + * @return array + * @link https://developer.qiniu.com/sms/api/5894/sms-api-query-template + */ + public function queryTemplate($audit_status = null, $page = 1, $page_size = 20) + { + + $url = sprintf( + "%s?audit_status=%s&page=%s&page_size=%s", + $this->baseURL . 'template', + $audit_status, + $page, + $page_size + ); + return $this->get($url); + } + + /** + * 查询单个模版 + * + * @param string $template_id 模版ID + * @return array + * @link https://developer.qiniu.com/sms/api/5969/query-a-single-template + */ + public function querySingleTemplate($template_id) + { + + $url = sprintf( + "%s/%s", + $this->baseURL . 'template', + $template_id + ); + return $this->get($url); + } + + /** + * 编辑模板 + * + * @param string $id 模板 ID + * @param string $name 模板名称 + * @param string $template 模板内容 + * @param string $description 申请理由简述 + * @param string $signature_id 已经审核通过的签名 ID + * @return array + * @link https://developer.qiniu.com/sms/api/5895/sms-api-edit-template + */ + public function updateTemplate( + $id, + $name, + $template, + $description, + $signature_id + ) { + $params = array(); + $params['name'] = $name; + $params['template'] = $template; + $params['description'] = $description; + $params['signature_id'] = $signature_id; + $body = json_encode($params); + $url = $this->baseURL . 'template/' . $id; + return $this->PUT($url, $body); + } + + /** + * 删除模板 + * + * @param string $template_id 模板 ID + * @return array + * @link https://developer.qiniu.com/sms/api/5896/sms-api-delete-template + */ + public function deleteTemplate($template_id) + { + $url = $this->baseURL . 'template/' . $template_id; + return $this->delete($url); + } + + /** + * 发送短信 + * + * @param string $template_id 模板 ID + * @param array $mobiles 手机号 + * @param array $parameters 自定义模板变量,变量设置在创建模板时,参数template指定 + * @return array + * @link https://developer.qiniu.com/sms/api/5897/sms-api-send-message + */ + public function sendMessage($template_id, $mobiles, $parameters = null) + { + $params = array(); + $params['template_id'] = $template_id; + $params['mobiles'] = $mobiles; + if (!empty($parameters)) { + $params['parameters'] = $parameters; + } + $body = json_encode($params); + $url = $this->baseURL . 'message'; + return $this->post($url, $body); + } + + /** + * 查询发送记录 + * + * @param string $job_id 发送任务返回的 id + * @param string $message_id 单条短信发送接口返回的 id + * @param string $mobile 接收短信的手机号码 + * @param string $status sending: 发送中,success: 发送成功,failed: 发送失败,waiting: 等待发送 + * @param string $template_id 模版 id + * @param string $type marketing:营销,notification:通知,verification:验证码,voice:语音 + * @param string $start 开始时间,timestamp,例如: 1563280448 + * @param int $end 结束时间,timestamp,例如: 1563280471 + * @param int $page 页码,默认为 1 + * @param int $page_size 每页返回的数据条数,默认20,最大200 + * @return array + * @link https://developer.qiniu.com/sms/api/5852/query-send-sms + */ + public function querySendSms( + $job_id = null, + $message_id = null, + $mobile = null, + $status = null, + $template_id = null, + $type = null, + $start = null, + $end = null, + $page = 1, + $page_size = 20 + ) { + $query = array(); + \Qiniu\setWithoutEmpty($query, 'job_id', $job_id); + \Qiniu\setWithoutEmpty($query, 'message_id', $message_id); + \Qiniu\setWithoutEmpty($query, 'mobile', $mobile); + \Qiniu\setWithoutEmpty($query, 'status', $status); + \Qiniu\setWithoutEmpty($query, 'template_id', $template_id); + \Qiniu\setWithoutEmpty($query, 'type', $type); + \Qiniu\setWithoutEmpty($query, 'start', $start); + \Qiniu\setWithoutEmpty($query, 'end', $end); + \Qiniu\setWithoutEmpty($query, 'page', $page); + \Qiniu\setWithoutEmpty($query, 'page_size', $page_size); + + $url = $this->baseURL . 'messages?' . http_build_query($query); + return $this->get($url); + } + + + public function imgToBase64($img_file) + { + $img_base64 = ''; + if (file_exists($img_file)) { + $app_img_file = $img_file; // 图片路径 + $img_info = getimagesize($app_img_file); // 取得图片的大小,类型等 + $fp = fopen($app_img_file, "r"); // 图片是否可读权限 + if ($fp) { + $filesize = filesize($app_img_file); + if ($filesize > 5 * 1024 * 1024) { + die("pic size < 5M !"); + } + $img_type = null; + $content = fread($fp, $filesize); + $file_content = chunk_split(base64_encode($content)); // base64编码 + switch ($img_info[2]) { //判读图片类型 + case 1: + $img_type = 'gif'; + break; + case 2: + $img_type = 'jpg'; + break; + case 3: + $img_type = 'png'; + break; + } + //合成图片的base64编码 + $img_base64 = 'data:image/' . $img_type . ';base64,' . $file_content; + } + fclose($fp); + } + + return $img_base64; + } + + private function get($url, $contentType = 'application/x-www-form-urlencoded') + { + $headers = $this->auth->authorizationV2($url, "GET", null, $contentType); + $headers['Content-Type'] = $contentType; + $ret = Client::get($url, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function delete($url, $contentType = 'application/json') + { + $headers = $this->auth->authorizationV2($url, "DELETE", null, $contentType); + $headers['Content-Type'] = $contentType; + $ret = Client::delete($url, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function post($url, $body, $contentType = 'application/json') + { + $headers = $this->auth->authorizationV2($url, "POST", $body, $contentType); + + $headers['Content-Type'] = $contentType; + $ret = Client::post($url, $body, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + private function PUT($url, $body, $contentType = 'application/json') + { + $headers = $this->auth->authorizationV2($url, "PUT", $body, $contentType); + $headers['Content-Type'] = $contentType; + $ret = Client::put($url, $body, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } +} diff --git a/php-sdk/src/Qiniu/Storage/ArgusManager.php b/php-sdk/src/Qiniu/Storage/ArgusManager.php new file mode 100644 index 0000000..51b4200 --- /dev/null +++ b/php-sdk/src/Qiniu/Storage/ArgusManager.php @@ -0,0 +1,129 @@ +auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * 视频审核 + * + * @param string $body body信息 + * + * @return array 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link https://developer.qiniu.com/censor/api/5620/video-censor + */ + public function censorVideo($body) + { + $path = '/v3/video/censor'; + + return $this->arPost($path, $body); + } + + + /** + * 图片审核 + * + * @param string $body + * + * @return array 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link https://developer.qiniu.com/censor/api/5588/image-censor + */ + public function censorImage($body) + { + $path = '/v3/image/censor'; + + return $this->arPost($path, $body); + } + + /** + * 查询视频审核结果 + * + * @param string $jobid 任务ID + * @return array + * @link https://developer.qiniu.com/censor/api/5620/video-censor + */ + public function censorStatus($jobid) + { + $scheme = "http://"; + + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $url = $scheme . Config::ARGUS_HOST . "/v3/jobs/video/$jobid"; + $response = $this->get($url); + if (!$response->ok()) { + return array(null, new Error($url, $response)); + } + return array($response->json(), null); + } + + private function getArHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + return $scheme . Config::ARGUS_HOST; + } + + private function arPost($path, $body = null) + { + $url = $this->getArHost() . $path; + return $this->post($url, $body); + } + + private function get($url) + { + $headers = $this->auth->authorizationV2($url, 'GET'); + + return Client::get($url, $headers, $this->proxy->makeReqOpt()); + } + + private function post($url, $body) + { + $headers = $this->auth->authorizationV2($url, 'POST', $body, 'application/json'); + $headers['Content-Type'] = 'application/json'; + $ret = Client::post($url, $body, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + if (strstr($url, "video")) { + $jobid = $r['job']; + return array($jobid, null); + } + return array($r, null); + } +} diff --git a/php-sdk/src/Qiniu/Storage/BucketManager.php b/php-sdk/src/Qiniu/Storage/BucketManager.php new file mode 100644 index 0000000..bfca4fc --- /dev/null +++ b/php-sdk/src/Qiniu/Storage/BucketManager.php @@ -0,0 +1,1324 @@ +auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * 获取指定账号下所有的空间名 + * + * @param bool $shared 指定共享空间,rw:读写权限空间,rd:读权限空间 + * @return array 包含所有空间名 + */ + public function buckets($shared = true) + { + $includeShared = "false"; + if ($shared === true) { + $includeShared = "true"; + } + return $this->getV2($this->config->getUcHost() . '/buckets?shared=' . $includeShared); + } + + /** + * 列举空间,返回bucket列表 + * + * @param string $region 区域 + * @param string $line + * @param string $shared 指定共享空间,rw:读写权限空间,rd:读权限空间 + * @return array + */ + public function listbuckets( + $region = null, + $line = 'false', + $shared = 'false' + ) { + $path = '/v3/buckets?region=' . $region . '&line=' . $line . '&shared=' . $shared; + return $this->ucPost($path); + } + + /** + * 创建空间 + * + * @param string $name 创建的空间名 + * @param string $region 创建的区域,默认华东 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1382/mkbucketv3 + */ + public function createBucket($name, $region = 'z0') + { + $path = '/mkbucketv3/' . $name . '/region/' . $region; + return $this->postV2($this->config->getUcHost() . $path, null); + } + + /** + * 删除空间 + * + * @param string $name 需要删除的目标空间名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1601/drop-bucket + */ + public function deleteBucket($name) + { + $path = '/drop/' . $name; + return $this->postV2($this->config->getUcHost() . $path, null); + } + + /** + * 获取指定空间绑定的所有的域名 + * + * @param string $bucket 空间名称 + * @return array + */ + public function domains($bucket) + { + return $this->ucGet('/v2/domains?tbl=' . $bucket); + } + + /** + * 获取指定空间的相关信息 + * + * @param string $bucket 空间名称 + * @return array + */ + public function bucketInfo($bucket) + { + $path = '/v2/bucketInfo?bucket=' . $bucket; + return $this->ucPost($path); + } + + /** + * 获取指定zone的空间信息列表 + * + * @param string $region 区域 + * @param string $shared 指定共享空间,rw:读写权限空间,rd:读权限空间 + * @param string $fs 如果为 true,会返回每个空间当前的文件数和存储量(实时数据) + * @return array + */ + public function bucketInfos($region = null, $shared = 'false', $fs = 'false') + { + $path = '/v2/bucketInfos?region=' . $region . '&shared=' . $shared . '&fs=' . $fs; + return $this->ucPost($path); + } + + /** + * 列取空间的文件列表 + * + * @param string $bucket 空间名 + * @param string $prefix 列举前缀 + * @param string $marker 列举标识符 + * @param int $limit 单次列举个数限制 + * @param string $delimiter 指定目录分隔符 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1284/list + */ + public function listFiles( + $bucket, + $prefix = null, + $marker = null, + $limit = 1000, + $delimiter = null + ) { + $query = array('bucket' => $bucket); + \Qiniu\setWithoutEmpty($query, 'prefix', $prefix); + \Qiniu\setWithoutEmpty($query, 'marker', $marker); + \Qiniu\setWithoutEmpty($query, 'limit', $limit); + \Qiniu\setWithoutEmpty($query, 'delimiter', $delimiter); + return $this->rsfGet($bucket, '/list?' . http_build_query($query)); + } + + /** + * 列取空间的文件列表 + * + * @deprecated API 可能返回仅包含 marker,不包含 item 或 dir 的项,请使用 {@link listFiles} + * + * @param string $bucket 空间名 + * @param string $prefix 列举前缀 + * @param string $marker 列举标识符 + * @param int $limit 单次列举个数限制 + * @param string $delimiter 指定目录分隔符 + * @param bool $skipconfirm 是否跳过已删除条目的确认机制 + * + * @return array + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/list.html + */ + public function listFilesv2( + $bucket, + $prefix = null, + $marker = null, + $limit = 1000, + $delimiter = null, + $skipconfirm = true + ) { + $query = array('bucket' => $bucket); + \Qiniu\setWithoutEmpty($query, 'prefix', $prefix); + \Qiniu\setWithoutEmpty($query, 'marker', $marker); + \Qiniu\setWithoutEmpty($query, 'limit', $limit); + \Qiniu\setWithoutEmpty($query, 'delimiter', $delimiter); + \Qiniu\setWithoutEmpty($query, 'skipconfirm', $skipconfirm); + $path = '/v2/list?' . http_build_query($query); + + list($host, $err) = $this->config->getRsfHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + $url = $host . $path; + $headers = $this->auth->authorizationV2($url, 'POST', null, 'application/x-www-form-urlencoded'); + $ret = Client::post($url, null, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = explode("\n", $ret->body); + array_pop($r); + return array($r, null); + } + + /** + * 增加bucket生命规则 + * + * @param string $bucket + * 空间名 + * @param string $name + * 规则名称 bucket 内唯一,长度小于50,不能为空,只能为字母、数字、下划线 + * @param string $prefix + * 同一个 bucket 里面前缀不能重复 + * @param int $delete_after_days + * 指定上传文件多少天后删除,指定为0表示不删除,大于0表示多少天后删除。 + * 需大于 to_line_after_days + * @param int $to_line_after_days + * 指定文件上传多少天后转低频存储。指定为0表示不转低频存储 + * @param int $to_archive_ir_after_days + * 指定文件上传多少天后转归档直读。指定为0表示不转归档直读 + * @param int $to_archive_after_days + * 指定文件上传多少天后转归档存储。指定为0表示不转归档存储 + * @param int $to_deep_archive_after_days + * 指定文件上传多少天后转深度归档存储。指定为0表示不转深度归档存储 + * @return array + */ + public function bucketLifecycleRule( + $bucket, + $name, + $prefix, + $delete_after_days = null, + $to_line_after_days = null, + $to_archive_after_days = null, + $to_deep_archive_after_days = null, + $to_archive_ir_after_days = null + ) { + $path = '/rules/add'; + $params = array(); + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + if ($prefix) { + $params['prefix'] = $prefix; + } + if ($delete_after_days) { + $params['delete_after_days'] = $delete_after_days; + } + if ($to_line_after_days) { + $params['to_line_after_days'] = $to_line_after_days; + } + if ($to_archive_ir_after_days) { + $params['to_archive_ir_after_days'] = $to_archive_ir_after_days; + } + if ($to_archive_after_days) { + $params['to_archive_after_days'] = $to_archive_after_days; + } + if ($to_deep_archive_after_days) { + $params['to_deep_archive_after_days'] = $to_deep_archive_after_days; + } + $data = http_build_query($params); + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 更新bucket生命规则 + * + * @param string $bucket + * 空间名 + * @param string $name + * 规则名称 bucket 内唯一,长度小于50,不能为空,只能为字母、数字、下划线 + * @param string $prefix + * 同一个 bucket 里面前缀不能重复 + * @param int $delete_after_days + * 指定上传文件多少天后删除,指定为0表示不删除,大于0表示多少天后删除 + * 需大于 to_line_after_days + * @param int $to_line_after_days + * 指定文件上传多少天后转低频存储。指定为0表示不转低频存储 + * @param int $to_archive_ir_after_days + * 指定文件上传多少天后转归档只读。指定为0表示不转归档只读 + * @param int $to_archive_after_days + * 指定文件上传多少天后转归档存储。指定为0表示不转归档存储 + * @param int $to_deep_archive_after_days + * 指定文件上传多少天后转深度归档存储。指定为0表示不转深度归档存储 + * @return array + */ + public function updateBucketLifecycleRule( + $bucket, + $name, + $prefix, + $delete_after_days = null, + $to_line_after_days = null, + $to_archive_after_days = null, + $to_deep_archive_after_days = null, + $to_archive_ir_after_days = null + ) { + $path = '/rules/update'; + $params = array(); + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + if ($prefix) { + $params['prefix'] = $prefix; + } + if ($delete_after_days) { + $params['delete_after_days'] = $delete_after_days; + } + if ($to_line_after_days) { + $params['to_line_after_days'] = $to_line_after_days; + } + if ($to_archive_ir_after_days) { + $params['to_archive_ir_after_days'] = $to_archive_ir_after_days; + } + if ($to_archive_after_days) { + $params['to_archive_after_days'] = $to_archive_after_days; + } + if ($to_deep_archive_after_days) { + $params['to_deep_archive_after_days'] = $to_deep_archive_after_days; + } + $data = http_build_query($params); + return $this->ucPost($path, $data); + } + + /** + * 获取bucket生命规则 + * + * @param string $bucket 空间名 + * @return array + */ + public function getBucketLifecycleRules($bucket) + { + $path = '/rules/get?bucket=' . $bucket; + $info = $this->ucGet($path); + return $info; + } + + /** + * 删除bucket生命规则 + * + * @param string $bucket 空间名 + * @param string $name 规则名称 bucket 内唯一,长度小于50,不能为空, + * 只能为字母、数字、下划线() + * @return array + */ + public function deleteBucketLifecycleRule($bucket, $name) + { + $path = '/rules/delete'; + $params = array(); + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + $data = http_build_query($params); + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 增加bucket事件通知规则 + * + * @param string $bucket 空间名 + * @param string $name 规则名称 bucket 内唯一,长度小于50,不能为空, + * 只能为字母、数字、下划线() + * @param string $prefix 同一个 bucket 里面前缀不能重复 + * @param string $suffix 可选,文件配置的后缀 + * @param array $event 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append, + * disable,enable,deleteMarkerCreate + * @param string $callbackURL 通知URL,可以指定多个,失败依次重试 + * @param string $access_key 可选,设置的话会对通知请求用对应的ak、sk进行签名 + * @param string $host 可选,通知请求的host + * + * @return array + */ + public function putBucketEvent( + $bucket, + $name, + $prefix, + $suffix, + $event, + $callbackURL, + $access_key = null, + $host = null + ) { + $path = '/events/add'; + $params = array(); + if (!empty($bucket)) { + $params['bucket'] = $bucket; + } + if (!empty($name)) { + $params['name'] = $name; + } + if (!empty($prefix)) { + $params['prefix'] = $prefix; + } + if (!empty($suffix)) { + $params['suffix'] = $suffix; + } + if (!empty($callbackURL)) { + $params['callbackURL'] = $callbackURL; + } + if (!empty($access_key)) { + $params['access_key'] = $access_key; + } + if (!empty($host)) { + $params['host'] = $host; + } + $data = http_build_query($params); + if (!empty($event)) { + $eventpath = ""; + foreach ($event as $key => $value) { + $eventpath .= "&event=$value"; + } + $data .= $eventpath; + } + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 更新bucket事件通知规则 + * + * @param string $bucket 空间名 + * @param string $name 规则名称 bucket 内唯一,长度小于50,不能为空, + * 只能为字母、数字、下划线() + * @param string $prefix 同一个 bucket 里面前缀不能重复 + * @param string $suffix 可选,文件配置的后缀 + * @param array $event 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append,disable, + * enable,deleteMarkerCreate + * @param string $callbackURL 通知URL,可以指定多个,失败依次重试 + * @param string $access_key 可选,设置的话会对通知请求用对应的ak、sk进行签名 + * @param string $host 可选,通知请求的host + * + * @return array + */ + public function updateBucketEvent( + $bucket, + $name, + $prefix, + $suffix, + $event, + $callbackURL, + $access_key = null, + $host = null + ) { + $path = '/events/update'; + $params = array(); + if (!empty($bucket)) { + $params['bucket'] = $bucket; + } + if (!empty($name)) { + $params['name'] = $name; + } + if (!empty($prefix)) { + $params['prefix'] = $prefix; + } + if ($suffix) { + $params['suffix'] = $suffix; + } + if (!empty($event)) { + $params['event'] = $event; + } + if (!empty($callbackURL)) { + $params['callbackURL'] = $callbackURL; + } + if (!empty($access_key)) { + $params['access_key'] = $access_key; + } + if (!empty($host)) { + $params['host'] = $host; + } + $data = http_build_query($params); + if (!empty($event)) { + $eventpath = ""; + foreach ($event as $key => $value) { + $eventpath .= "&event=$value"; + } + $data .= $eventpath; + } + return $this->ucPost($path, $data); + } + + /** + * 获取bucket事件通知规则 + * + * @param string $bucket 空间名 + * @return array + */ + public function getBucketEvents($bucket) + { + $path = '/events/get?bucket=' . $bucket; + return $this->ucGet($path); + } + + /** + * 删除bucket事件通知规则 + * + * @param string $bucket 空间名 + * @param string $name 规则名称bucket内唯一,长度小于50,不能为空,只能为字母、数字、下划线 + * @return array + */ + public function deleteBucketEvent($bucket, $name) + { + $path = '/events/delete'; + $params = array(); + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + $data = http_build_query($params); + return $this->ucPost($path, $data); + } + + /** + * 获取bucket的跨域信息 + * + * @param string $bucket 空间名 + * @return array + */ + public function getCorsRules($bucket) + { + $path = '/corsRules/get/' . $bucket; + return $this->ucGet($path); + } + + /** + * 开关原图保护 + * + * @param string $bucket 空间名称 + * @param int $mode mode 为1表示开启原图保护,0表示关闭 + * @return array + */ + public function putBucketAccessStyleMode($bucket, $mode) + { + $path = '/accessMode/' . $bucket . '/mode/' . $mode; + return $this->ucPost($path, null); + } + + /** + * 设置私有属性 + * + * @param string $bucket 空间名称 + * @param int $private private为0表示公开,为1表示私有 + * @return array + */ + public function putBucketAccessMode($bucket, $private) + { + $path = "/private?bucket=$bucket&private=$private"; + return $this->ucPost($path, null); + } + + /** + * 设置 referer 防盗链 + * + * @param string $bucket 空间名称 + * @param int $mode 0:关闭Referer(使用此选项将会忽略以下参数并将恢复默认值); + * 1:设置Referer白名单; 2:设置Referer黑名单 + * @param string $norefer 0:不允许空 Refer 访问; 1:表示允许空Refer访问 + * @param string $pattern 规则字符串 + * @param int $enabled 源站是否支持,默认为0只给CDN配置, 设置为1表示开启源站防盗链 + * @return array + * @link https://developer.qiniu.com/kodo/manual/6093/set-the-hotlinking-prevention + */ + public function putReferAntiLeech($bucket, $mode, $norefer, $pattern, $enabled = 1) + { + $path = "/referAntiLeech?bucket=$bucket&mode=$mode&norefer=$norefer&pattern=$pattern&source_enabled=$enabled"; + return $this->ucPost($path, null); + } + + /** + * 设置Bucket的maxAge + * + * @param string $bucket 空间名称 + * @param int $maxAge maxAge为0或者负数表示为默认值(31536000) + * @return array + */ + public function putBucketMaxAge($bucket, $maxAge) + { + $path = '/maxAge?bucket=' . $bucket . '&maxAge=' . $maxAge; + return $this->ucPost($path, null); + } + + /** + * 设置空间配额 + * + * @param string $bucket 空间名称,不支持授权空间 + * @param string $size 空间存储量配额,参数传入0或不传表示不更改当前配置,传入-1表示取消限额,新创建的空间默认没有限额 + * @param string $count 空间文件数配额,参数含义同 + * @return array + */ + public function putBucketQuota($bucket, $size, $count) + { + $path = '/setbucketquota/' . $bucket . '/size/' . $size . '/count/' . $count; + return $this->apiPost($bucket, $path); + } + + /** + * 获取空间配额 + * + * @param string $bucket 空间名称 + * @return array + */ + public function getBucketQuota($bucket) + { + $path = '/getbucketquota/' . $bucket; + return $this->apiPost($bucket, $path); + } + + /** + * 获取资源的元信息,但不返回文件内容 + * + * @param string $bucket 待获取信息资源所在的空间 + * @param string $key 待获取资源的文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1308/stat + */ + public function stat($bucket, $key) + { + $path = '/stat/' . \Qiniu\entry($bucket, $key); + return $this->rsGet($bucket, $path); + } + + /** + * 删除指定资源 + * + * @param string $bucket 待删除资源所在的空间 + * @param string $key 待删除资源的文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1257/delete + */ + public function delete($bucket, $key) + { + $path = '/delete/' . \Qiniu\entry($bucket, $key); + return $this->rsPost($bucket, $path); + } + + /** + * 给资源进行重命名,本质为move操作。 + * + * @param string $bucket 待操作资源所在空间 + * @param string $oldname 待操作资源文件名 + * @param string $newname 目标资源文件名 + * + * @return array + */ + public function rename($bucket, $oldname, $newname) + { + return $this->move($bucket, $oldname, $bucket, $newname); + } + + /** + * 对资源进行复制。 + * + * @param string $from_bucket 待操作资源所在空间 + * @param string $from_key 待操作资源文件名 + * @param string $to_bucket 目标资源空间名 + * @param string $to_key 目标资源文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1254/copy + */ + public function copy($from_bucket, $from_key, $to_bucket, $to_key, $force = false) + { + $from = \Qiniu\entry($from_bucket, $from_key); + $to = \Qiniu\entry($to_bucket, $to_key); + $path = '/copy/' . $from . '/' . $to; + if ($force === true) { + $path .= '/force/true'; + } + return $this->rsPost($from_bucket, $path); + } + + /** + * 将资源从一个空间到另一个空间 + * + * @param string $from_bucket 待操作资源所在空间 + * @param string $from_key 待操作资源文件名 + * @param string $to_bucket 目标资源空间名 + * @param string $to_key 目标资源文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1288/move + */ + public function move($from_bucket, $from_key, $to_bucket, $to_key, $force = false) + { + $from = \Qiniu\entry($from_bucket, $from_key); + $to = \Qiniu\entry($to_bucket, $to_key); + $path = '/move/' . $from . '/' . $to; + if ($force) { + $path .= '/force/true'; + } + return $this->rsPost($from_bucket, $path); + } + + /** + * 主动修改指定资源的文件元信息 + * + * @param string $bucket 待操作资源所在空间 + * @param string $key 待操作资源文件名 + * @param string $mime 待操作文件目标mimeType + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1252/chgm + */ + public function changeMime($bucket, $key, $mime) + { + $resource = \Qiniu\entry($bucket, $key); + $encode_mime = \Qiniu\base64_urlSafeEncode($mime); + $path = '/chgm/' . $resource . '/mime/' . $encode_mime; + return $this->rsPost($bucket, $path); + } + + + /** + * 修改指定资源的存储类型 + * + * @param string $bucket 待操作资源所在空间 + * @param string $key 待操作资源文件名 + * @param int $fileType 对象存储类型 + * 0 表示标准存储; + * 1 表示低频存储; + * 2 表示归档存储; + * 3 表示深度归档存储; + * 4 表示归档直读存储; + * + * @return array + * @link https://developer.qiniu.com/kodo/api/3710/chtype + */ + public function changeType($bucket, $key, $fileType) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/chtype/' . $resource . '/type/' . $fileType; + return $this->rsPost($bucket, $path); + } + + /** + * 解冻指定资源的存储类型 + * + * @param string $bucket 待操作资源所在空间 + * @param string $key 待操作资源文件名 + * @param int $freezeAfterDays 解冻有效时长,取值范围 1~7 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/6380/restore-archive + */ + public function restoreAr($bucket, $key, $freezeAfterDays) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/restoreAr/' . $resource . '/freezeAfterDays/' . $freezeAfterDays; + return $this->rsPost($bucket, $path); + } + + /** + * 修改文件的存储状态,即禁用状态和启用状态间的的互相转换 + * + * @param string $bucket 待操作资源所在空间 + * @param string $key 待操作资源文件名 + * @param int $status 0表示启用;1表示禁用 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/4173/modify-the-file-status + */ + public function changeStatus($bucket, $key, $status) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/chstatus/' . $resource . '/status/' . $status; + return $this->rsPost($bucket, $path); + } + + /** + * 从指定URL抓取资源,并将该资源存储到指定空间中 + * + * @param string $url 指定的URL + * @param string $bucket 目标资源空间 + * @param string $key 目标资源文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1263/fetch + */ + public function fetch($url, $bucket, $key = null) + { + + $resource = \Qiniu\base64_urlSafeEncode($url); + $to = \Qiniu\entry($bucket, $key); + $path = '/fetch/' . $resource . '/to/' . $to; + + $ak = $this->auth->getAccessKey(); + + + list($ioHost, $err) = $this->config->getIovipHostV2($ak, $bucket, $this->proxy->makeReqOpt()); + if ($err != null) { + return array(null, $err); + } + + $url = $ioHost . $path; + return $this->postV2($url, null); + } + + /** + * 从指定URL异步抓取资源,并将该资源存储到指定空间中 + * + * @param string $url 需要抓取的url + * @param string $bucket 所在区域的bucket + * @param string $host 从指定url下载数据时使用的Host + * @param string $key 文件存储的key + * @param string $md5 文件md5 + * @param string $etag 文件etag + * @param string $callbackurl 回调URL + * @param string $callbackbody 回调Body + * @param string $callbackbodytype 回调Body内容类型,默认为"application/x-www-form-urlencoded" + * @param string $callbackhost 回调时使用的Host + * @param int $file_type 存储文件类型 + * 0:标准存储(默认) + * 1:低频存储 + * 2:归档存储 + * 3:深度归档存储 + * 4:归档直读存储 + * @param bool $ignore_same_key 如果空间中已经存在同名文件则放弃本次抓取 + * @return array + * @link https://developer.qiniu.com/kodo/api/4097/asynch-fetch + */ + public function asynchFetch( + $url, + $bucket, + $host = null, + $key = null, + $md5 = null, + $etag = null, + $callbackurl = null, + $callbackbody = null, + $callbackbodytype = 'application/x-www-form-urlencoded', + $callbackhost = null, + $file_type = 0, + $ignore_same_key = false + ) { + $path = '/sisyphus/fetch'; + + $params = array('url' => $url, 'bucket' => $bucket); + \Qiniu\setWithoutEmpty($params, 'host', $host); + \Qiniu\setWithoutEmpty($params, 'key', $key); + \Qiniu\setWithoutEmpty($params, 'md5', $md5); + \Qiniu\setWithoutEmpty($params, 'etag', $etag); + \Qiniu\setWithoutEmpty($params, 'callbackurl', $callbackurl); + \Qiniu\setWithoutEmpty($params, 'callbackbody', $callbackbody); + \Qiniu\setWithoutEmpty($params, 'callbackbodytype', $callbackbodytype); + \Qiniu\setWithoutEmpty($params, 'callbackhost', $callbackhost); + \Qiniu\setWithoutEmpty($params, 'file_type', $file_type); + \Qiniu\setWithoutEmpty($params, 'ignore_same_key', $ignore_same_key); + $data = json_encode($params); + + return $this->apiPost($bucket, $path, $data); + } + + + /** + * 查询异步第三方资源抓取任务状态 + * + * @param string $zone + * @param string $id + * @return array + * @link https://developer.qiniu.com/kodo/api/4097/asynch-fetch + */ + public function asynchFetchStatus($zone, $id) + { + $scheme = "http://"; + + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + + $url = $scheme . "api-" . $zone . ".qiniuapi.com/sisyphus/fetch?id=" . $id; + + list($ret, $err) = $this->getV2($url); + + if ($err != null) { + return array(null, $err); + } + return array($ret, null); + } + + + /** + * 从镜像源站抓取资源到空间中,如果空间中已经存在,则覆盖该资源 + * + * @param string $bucket 待获取资源所在的空间 + * @param string $key 代获取资源文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1293/prefetch + */ + public function prefetch($bucket, $key) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/prefetch/' . $resource; + + $ak = $this->auth->getAccessKey(); + list($ioHost, $err) = $this->config->getIovipHostV2($ak, $bucket, $this->proxy->makeReqOpt()); + + if ($err != null) { + return array(null, $err); + } + + $url = $ioHost . $path; + return $this->postV2($url, null); + } + + /** + * 在单次请求中进行多个资源管理操作 + * + * @param array $operations 资源管理操作数组 + * + * @return array 每个资源的处理情况,结果类似: + * [ + * { "code" => , "data" => }, + * { "code" => }, + * { "code" => }, + * { "code" => }, + * { "code" => , "data" => { "error": "" } }, + * ... + * ] + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/batch.html + */ + public function batch($operations) + { + $scheme = "http://"; + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $params = 'op=' . implode('&op=', $operations); + $errResp = new Response(0, 0); + if (count($operations) <= 0) { + $errResp->error = 'empty operations'; + return array(null, new Error($scheme . '/batch', $errResp)); + } + $bucket = ''; + foreach ($operations as $op) { + $segments = explode('/', $op); + if (count($segments) < 3) { + continue; + } + list($bucket,) = \Qiniu\decodeEntry($segments[2]); + } + return $this->rsPost($bucket, '/batch', $params); + } + + /** + * 设置文件的生命周期 + * + * @param string $bucket 设置文件生命周期文件所在的空间 + * @param string $key 设置文件生命周期文件的文件名 + * @param int $days 设置该文件多少天后删除,当$days设置为0时表示取消该文件的生命周期 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/update-file-lifecycle + */ + public function deleteAfterDays($bucket, $key, $days) + { + $entry = \Qiniu\entry($bucket, $key); + $path = "/deleteAfterDays/$entry/$days"; + return $this->rsPost($bucket, $path); + } + + /** + * 更新 object 生命周期 + * + * @param string $bucket 空间名 + * @param string $key 目标资源 + * @param int $to_line_after_days 多少天后将文件转为低频存储。 + * -1 表示取消已设置的转低频存储的生命周期规则; + * 0 表示不修改转低频生命周期规则。 + * @param int $to_archive_ir_after_days 多少天后转为归档直读存储。 + * -1 表示取消已设置的转归档直读存储的生命周期规则; + * 0 表示不修改转归档直读生命周期规则。 + * @param int $to_archive_after_days 多少天后将文件转为归档存储。 + * -1 表示取消已设置的转归档存储的生命周期规则; + * 0 表示不修改转归档生命周期规则。 + * @param int $to_deep_archive_after_days 多少天后将文件转为深度归档存储。 + * -1 表示取消已设置的转深度归档存储的生命周期规则; + * 0 表示不修改转深度归档生命周期规则。 + * @param int $delete_after_days 多少天后将文件删除。 + * -1 表示取消已设置的删除存储的生命周期规则; + * 0 表示不修改删除存储的生命周期规则。 + * @return array + */ + public function setObjectLifecycle( + $bucket, + $key, + $to_line_after_days = 0, + $to_archive_after_days = 0, + $to_deep_archive_after_days = 0, + $delete_after_days = 0, + $to_archive_ir_after_days = 0 + ) { + return $this->setObjectLifecycleWithCond( + $bucket, + $key, + null, + $to_line_after_days, + $to_archive_after_days, + $to_deep_archive_after_days, + $delete_after_days, + $to_archive_ir_after_days + ); + } + + /** + * 更新 object 生命周期 + * + * @param string $bucket 空间名 + * @param string $key 目标资源 + * @param int $to_line_after_days 多少天后将文件转为低频存储。 + * 设置为 -1 表示取消已设置的转低频存储的生命周期规则; + * 0 表示不修改转低频生命周期规则。 + * @param int $to_archive_ir_after_days 多少天后将文件转为归档直读存储。 + * 设置为 -1 表示取消已设置的转归档直读存储的生命周期规则; + * 0 表示不修改转归档直读生命周期规则。 + * @param int $to_archive_after_days 多少天后将文件转为归档存储。 + * -1 表示取消已设置的转归档存储的生命周期规则; + * 0 表示不修改转归档生命周期规则。 + * @param int $to_deep_archive_after_days 多少天后将文件转为深度归档存储。 + * -1 表示取消已设置的转深度归档存储的生命周期规则; + * 0 表示不修改转深度归档生命周期规则。 + * @param int $delete_after_days 多少天后将文件删除。 + * -1 表示取消已设置的删除存储的生命周期规则; + * 0 表示不修改删除存储的生命周期规则。 + * @param array $cond 匹配条件,只有条件匹配才会设置成功。 + * 目前支持:hash、mime、fsize、putTime + * @return array + */ + public function setObjectLifecycleWithCond( + $bucket, + $key, + $cond = null, + $to_line_after_days = 0, + $to_archive_after_days = 0, + $to_deep_archive_after_days = 0, + $delete_after_days = 0, + $to_archive_ir_after_days = 0 + ) { + $encodedEntry = \Qiniu\entry($bucket, $key); + $path = '/lifecycle/' . $encodedEntry . + '/toIAAfterDays/' . $to_line_after_days . + '/toArchiveIRAfterDays/' . $to_archive_ir_after_days . + '/toArchiveAfterDays/' . $to_archive_after_days . + '/toDeepArchiveAfterDays/' . $to_deep_archive_after_days . + '/deleteAfterDays/' . $delete_after_days; + if ($cond != null) { + $condStrArr = array(); + foreach ($cond as $key => $value) { + array_push($condStrArr, $key . '=' . $value); + } + $condStr = implode('&', $condStrArr); + $path .= '/cond' . \Qiniu\base64_urlSafeEncode($condStr); + } + return $this->rsPost($bucket, $path); + } + + private function rsfGet($bucket, $path) + { + list($host, $err) = $this->config->getRsfHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + return $this->getV2($host . $path); + } + + private function rsGet($bucket, $path) + { + list($host, $err) = $this->config->getRsHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + return $this->getV2($host . $path); + } + + private function rsPost($bucket, $path, $body = null) + { + list($host, $err) = $this->config->getRsHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + return $this->postV2($host . $path, $body); + } + + private function apiGet($bucket, $path) + { + list($host, $err) = $this->config->getApiHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + return $this->getV2($host . $path); + } + + private function apiPost($bucket, $path, $body = null) + { + + list($host, $err) = $this->config->getApiHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + return $this->postV2($host . $path, $body); + } + + private function ucGet($path) + { + $url = $this->config->getUcHost() . $path; + return $this->getV2($url); + } + + private function ucPost($path, $body = null) + { + $url = $this->config->getUcHost() . $path; + return $this->postV2($url, $body); + } + + private function getV2($url) + { + $headers = $this->auth->authorizationV2($url, 'GET', null, 'application/x-www-form-urlencoded'); + $ret = Client::get($url, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function postV2($url, $body) + { + $headers = $this->auth->authorizationV2($url, 'POST', $body, 'application/x-www-form-urlencoded'); + $ret = Client::post($url, $body, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + public static function buildBatchCopy($source_bucket, $key_pairs, $target_bucket, $force) + { + return self::twoKeyBatch('/copy', $source_bucket, $key_pairs, $target_bucket, $force); + } + + + public static function buildBatchRename($bucket, $key_pairs, $force) + { + return self::buildBatchMove($bucket, $key_pairs, $bucket, $force); + } + + + public static function buildBatchMove($source_bucket, $key_pairs, $target_bucket, $force) + { + return self::twoKeyBatch('/move', $source_bucket, $key_pairs, $target_bucket, $force); + } + + + public static function buildBatchDelete($bucket, $keys) + { + return self::oneKeyBatch('/delete', $bucket, $keys); + } + + + public static function buildBatchStat($bucket, $keys) + { + return self::oneKeyBatch('/stat', $bucket, $keys); + } + + public static function buildBatchDeleteAfterDays($bucket, $key_day_pairs) + { + $data = array(); + foreach ($key_day_pairs as $key => $day) { + array_push($data, '/deleteAfterDays/' . \Qiniu\entry($bucket, $key) . '/' . $day); + } + return $data; + } + + /** + * @param string $bucket 空间名 + * @param array $keys 目标资源 + * @param int $to_line_after_days 多少天后将文件转为低频存储。 + * -1 表示取消已设置的转低频存储的生命周期规则; + * 0 表示不修改转低频生命周期规则。 + * @param int $to_archive_ir_after_days 多少天后将文件转为归档直读。 + * -1 表示取消已设置的转归档只读的生命周期规则; + * 0 表示不修改转归档只读周期规则。 + * @param int $to_archive_after_days 多少天后将文件转为归档存储。 + * -1 表示取消已设置的转归档存储的生命周期规则; + * 0 表示不修改转归档生命周期规则。 + * @param int $to_deep_archive_after_days 多少天后将文件转为深度归档存储。 + * -1 表示取消已设置的转深度归档存储的生命周期规则; + * 0 表示不修改转深度归档生命周期规则。 + * @param int $delete_after_days 多少天后将文件删除。 + * -1 表示取消已设置的删除存储的生命周期规则; + * 0 表示不修改删除存储的生命周期规则。 + * + * @retrun array + */ + public static function buildBatchSetObjectLifecycle( + $bucket, + $keys, + $to_line_after_days, + $to_archive_after_days, + $to_deep_archive_after_days, + $delete_after_days, + $to_archive_ir_after_days = 0 + ) { + $result = array(); + foreach ($keys as $key) { + $encodedEntry = \Qiniu\entry($bucket, $key); + $op = '/lifecycle/' . $encodedEntry . + '/toIAAfterDays/' . $to_line_after_days . + '/toArchiveIRAfterDays/' . $to_archive_ir_after_days . + '/toArchiveAfterDays/' . $to_archive_after_days . + '/toDeepArchiveAfterDays/' . $to_deep_archive_after_days . + '/deleteAfterDays/' . $delete_after_days; + array_push($result, $op); + } + return $result; + } + + public static function buildBatchChangeMime($bucket, $key_mime_pairs) + { + $data = array(); + foreach ($key_mime_pairs as $key => $mime) { + array_push($data, '/chgm/' . \Qiniu\entry($bucket, $key) . '/mime/' . base64_encode($mime)); + } + return $data; + } + + public static function buildBatchChangeType($bucket, $key_type_pairs) + { + $data = array(); + foreach ($key_type_pairs as $key => $type) { + array_push($data, '/chtype/' . \Qiniu\entry($bucket, $key) . '/type/' . $type); + } + return $data; + } + + public static function buildBatchRestoreAr($bucket, $key_restore_days_pairs) + { + $data = array(); + foreach ($key_restore_days_pairs as $key => $restore_days) { + array_push($data, '/restoreAr/' . \Qiniu\entry($bucket, $key) . '/freezeAfterDays/' . $restore_days); + } + return $data; + } + + private static function oneKeyBatch($operation, $bucket, $keys) + { + $data = array(); + foreach ($keys as $key) { + array_push($data, $operation . '/' . \Qiniu\entry($bucket, $key)); + } + return $data; + } + + private static function twoKeyBatch($operation, $source_bucket, $key_pairs, $target_bucket, $force) + { + if ($target_bucket === null) { + $target_bucket = $source_bucket; + } + $data = array(); + $forceOp = "false"; + if ($force) { + $forceOp = "true"; + } + foreach ($key_pairs as $from_key => $to_key) { + $from = \Qiniu\entry($source_bucket, $from_key); + $to = \Qiniu\entry($target_bucket, $to_key); + array_push($data, $operation . '/' . $from . '/' . $to . "/force/" . $forceOp); + } + return $data; + } +} diff --git a/php-sdk/src/Qiniu/Storage/FormUploader.php b/php-sdk/src/Qiniu/Storage/FormUploader.php new file mode 100644 index 0000000..d68654d --- /dev/null +++ b/php-sdk/src/Qiniu/Storage/FormUploader.php @@ -0,0 +1,165 @@ + "", + * "key" => "" + * ] + */ + public static function put( + $upToken, + $key, + $data, + $config, + $params, + $mime, + $fname, + $reqOpt = null + ) { + if ($reqOpt == null) { + $reqOpt = new RequestOptions(); + } + $fields = array('token' => $upToken); + if ($key === null) { + } else { + $fields['key'] = $key; + } + + //enable crc32 check by default + $fields['crc32'] = \Qiniu\crc32_data($data); + + if ($params) { + foreach ($params as $k => $v) { + $fields[$k] = $v; + } + } + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + if ($err != null) { + return array(null, $err); + } + + list($upHost, $err) = $config->getUpHostV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + + $response = Client::multipartPost( + $upHost, + $fields, + 'file', + $fname, + $data, + $mime, + array(), + $reqOpt + ); + if (!$response->ok()) { + return array(null, new Error($upHost, $response)); + } + return array($response->json(), null); + } + + /** + * 上传文件到七牛,内部使用 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param string $filePath 上传文件的路径 + * @param Config $config 上传配置 + * @param string $params 自定义变量,规格参考 + * https://developer.qiniu.com/kodo/manual/1235/vars#xvar + * @param string $mime 上传数据的mimeType + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "", + * "key" => "" + * ] + */ + public static function putFile( + $upToken, + $key, + $filePath, + $config, + $params, + $mime, + $reqOpt = null + ) { + if ($reqOpt == null) { + $reqOpt = new RequestOptions(); + } + + $fields = array('token' => $upToken, 'file' => self::createFile($filePath, $mime)); + if ($key !== null) { + $fields['key'] = $key; + } + + $fields['crc32'] = \Qiniu\crc32_file($filePath); + + if ($params) { + foreach ($params as $k => $v) { + $fields[$k] = $v; + } + } + $fields['key'] = $key; + $headers = array('Content-Type' => 'multipart/form-data'); + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + if ($err != null) { + return array(null, $err); + } + + list($upHost, $err) = $config->getUpHostV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + $response = Client::post($upHost, $fields, $headers, $reqOpt); + if (!$response->ok()) { + return array(null, new Error($upHost, $response)); + } + return array($response->json(), null); + } + + private static function createFile($filename, $mime) + { + // PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax + // See: https://wiki.php.net/rfc/curl-file-upload + if (function_exists('curl_file_create')) { + return curl_file_create($filename, $mime); + } + + // Use the old style if using an older version of PHP + $value = "@{$filename}"; + if (!empty($mime)) { + $value .= ';type=' . $mime; + } + + return $value; + } +} diff --git a/php-sdk/src/Qiniu/Storage/ResumeUploader.php b/php-sdk/src/Qiniu/Storage/ResumeUploader.php new file mode 100644 index 0000000..00e88ef --- /dev/null +++ b/php-sdk/src/Qiniu/Storage/ResumeUploader.php @@ -0,0 +1,580 @@ + $params 自定义变量 + * @param string $mime 上传数据的mimeType + * @param Config $config + * @param string $resumeRecordFile 断点续传的已上传的部分信息记录文件 + * @param string $version 分片上传版本 目前支持v1/v2版本 默认v1 + * @param int $partSize 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB + * @param RequestOptions $reqOpt 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB + * @throws \Exception + * + * @link http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + */ + public function __construct( + $upToken, + $key, + $inputStream, + $size, + $params, + $mime, + $config, + $resumeRecordFile = null, + $version = 'v1', + $partSize = config::BLOCK_SIZE, + $reqOpt = null + ) { + + $this->upToken = $upToken; + $this->key = $key; + $this->inputStream = $inputStream; + $this->size = $size; + $this->params = $params; + $this->mime = $mime; + $this->contexts = array(); + $this->finishedEtags = array("etags" => array(), "uploadId" => "", "expiredAt" => 0, "uploaded" => 0); + $this->config = $config; + $this->resumeRecordFile = $resumeRecordFile ? $resumeRecordFile : null; + $this->partSize = $partSize ? $partSize : config::BLOCK_SIZE; + + if ($reqOpt === null) { + $reqOpt = new RequestOptions(); + } + $this->reqOpt = $reqOpt; + + try { + $this->version = SplitUploadVersion::from($version ? $version : 'v1'); + } catch (\Exception $e) { + throw new \Exception("only support v1/v2 now!", 0, $e); + } + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + $this->bucket = $bucket; + if ($err != null) { + return array(null, $err); + } + + list($upHost, $err) = $config->getUpHostV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + throw new \Exception($err->message(), 1); + } + $this->host = $upHost; + } + + /** + * 上传操作 + * @param $fname string 文件名 + * + * @throws \Exception + */ + public function upload($fname) + { + $blkputRets = null; + // get upload record from resumeRecordFile + if ($this->resumeRecordFile != null) { + if (file_exists($this->resumeRecordFile)) { + $stream = fopen($this->resumeRecordFile, 'r'); + if ($stream) { + $streamLen = filesize($this->resumeRecordFile); + if ($streamLen > 0) { + $contents = fread($stream, $streamLen); + fclose($stream); + if ($contents) { + $blkputRets = json_decode($contents, true); + if ($blkputRets === null) { + error_log("resumeFile contents decode error"); + } + } else { + error_log("read resumeFile failed"); + } + } else { + error_log("resumeFile is empty"); + } + } else { + error_log("resumeFile open failed"); + } + } else { + error_log("resumeFile not exists"); + } + } + + if ($this->version == SplitUploadVersion::V1) { + return $this->uploadV1($fname, $blkputRets); + } elseif ($this->version == SplitUploadVersion::V2) { + return $this->uploadV2($fname, $blkputRets); + } else { + throw new \Exception("only support v1/v2 now!"); + } + } + + /** + * @param string $fname 文件名 + * @param null|array $blkputRets + * + * @throws \Exception + */ + private function uploadV1($fname, $blkputRets = null) + { + // 尝试恢复恢复已上传的数据 + $isResumeUpload = $blkputRets !== null; + $this->contexts = array(); + + if ($blkputRets) { + if (isset($blkputRets['contexts']) && isset($blkputRets['uploaded']) && + is_array($blkputRets['contexts']) && is_int($blkputRets['uploaded']) + ) { + $this->contexts = array_map(function ($ctx) { + if (is_array($ctx)) { + return $ctx; + } else { + // 兼容旧版本(旧版本没有存储 expireAt) + return array( + "ctx" => $ctx, + "expiredAt" => 0, + ); + } + }, $blkputRets['contexts']); + } + } + + // 上传分片 + $uploaded = 0; + while ($uploaded < $this->size) { + $blockSize = $this->blockSize($uploaded); + $blockIndex = $uploaded / $this->partSize; + if (!is_int($blockIndex)) { + throw new \Exception("v1 part size changed"); + } + // 如果已上传该分片且没有过期 + if (isset($this->contexts[$blockIndex]) && $this->contexts[$blockIndex]["expiredAt"] >= time()) { + $uploaded += $blockSize; + fseek($this->inputStream, $blockSize, SEEK_CUR); + continue; + } + $data = fread($this->inputStream, $blockSize); + if ($data === false) { + throw new \Exception("file read failed", 1); + } + $crc = \Qiniu\crc32_data($data); + $response = $this->makeBlock($data, $blockSize); + + + $ret = null; + if ($response->ok() && $response->json() != null) { + $ret = $response->json(); + } + if ($response->statusCode < 0) { + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken); + if ($err != null) { + return array(null, $err); + } + list($upHostBackup, $err) = $this->config->getUpBackupHostV2($accessKey, $bucket, $this->reqOpt); + if ($err != null) { + return array(null, $err); + } + $this->host = $upHostBackup; + } + + if ($response->needRetry() || !isset($ret['crc32']) || $crc != $ret['crc32']) { + $response = $this->makeBlock($data, $blockSize); + $ret = $response->json(); + } + if (!$response->ok() || !isset($ret['crc32']) || $crc != $ret['crc32']) { + return array(null, new Error($this->currentUrl, $response)); + } + + // 如果可以在已上传取到说明是过期分片直接修改已上传信息,否则是新的片添加到已上传分片尾部 + if (isset($this->contexts[$blockIndex])) { + $this->contexts[$blockIndex] = array( + 'ctx' => $ret['ctx'], + 'expiredAt' => $ret['expired_at'], + ); + } else { + array_push($this->contexts, array( + 'ctx' => $ret['ctx'], + 'expiredAt' => $ret['expired_at'], + )); + } + $uploaded += $blockSize; + + // 记录断点 + if ($this->resumeRecordFile !== null) { + $recordData = array( + 'contexts' => $this->contexts, + 'uploaded' => $uploaded + ); + $recordData = json_encode($recordData); + + if ($recordData) { + $isWritten = file_put_contents($this->resumeRecordFile, $recordData); + if ($isWritten === false) { + error_log("write resumeRecordFile failed"); + } + } else { + error_log('resumeRecordData encode failed'); + } + } + } + + // 完成上传 + list($ret, $err) = $this->makeFile($fname); + if ($err !== null) { + $response = $err->getResponse(); + if ($isResumeUpload && $response->statusCode === 701) { + fseek($this->inputStream, 0); + return $this->uploadV1($fname); + } + } + return array($ret, $err); + } + + /** + * @param string $fname 文件名 + * @param null|array $blkputRets + * + * @throws \Exception + */ + private function uploadV2($fname, $blkputRets = null) + { + $uploaded = 0; + $partNumber = 1; + $encodedObjectName = $this->key ? \Qiniu\base64_urlSafeEncode($this->key) : '~'; + $isResumeUpload = $blkputRets !== null; + + // 初始化 upload id + $err = null; + if ($blkputRets) { + if (isset($blkputRets["etags"]) && isset($blkputRets["uploadId"]) && + isset($blkputRets["expiredAt"]) && $blkputRets["expiredAt"] > time() && + $blkputRets["uploaded"] > 0 && is_array($blkputRets["etags"]) && + is_string($blkputRets["uploadId"]) && is_int($blkputRets["expiredAt"]) + ) { + $this->finishedEtags['etags'] = $blkputRets["etags"]; + $this->finishedEtags["uploadId"] = $blkputRets["uploadId"]; + $this->finishedEtags["expiredAt"] = $blkputRets["expiredAt"]; + $this->finishedEtags["uploaded"] = $blkputRets["uploaded"]; + $uploaded = $blkputRets["uploaded"]; + $partNumber = count($this->finishedEtags["etags"]) + 1; + } else { + $err = $this->makeInitReq($encodedObjectName); + } + } else { + $err = $this->makeInitReq($encodedObjectName); + } + if ($err != null) { + return array(null, $err); + } + + // 上传分片 + fseek($this->inputStream, $uploaded); + while ($uploaded < $this->size) { + $blockSize = $this->blockSize($uploaded); + $data = fread($this->inputStream, $blockSize); + if ($data === false) { + throw new \Exception("file read failed", 1); + } + $md5 = md5($data); + $response = $this->uploadPart( + $data, + $partNumber, + $this->finishedEtags["uploadId"], + $encodedObjectName, + $md5 + ); + + $ret = null; + if ($response->ok() && $response->json() != null) { + $ret = $response->json(); + } + if ($response->statusCode < 0) { + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken); + if ($err != null) { + return array(null, $err); + } + list($upHostBackup, $err) = $this->config->getUpBackupHostV2($accessKey, $bucket, $this->reqOpt); + if ($err != null) { + return array(null, $err); + } + $this->host = $upHostBackup; + } + + if ($response->needRetry() || !isset($ret['md5']) || $md5 != $ret['md5']) { + $response = $this->uploadPart( + $data, + $partNumber, + $this->finishedEtags["uploadId"], + $encodedObjectName, + $md5 + ); + $ret = $response->json(); + } + if ($isResumeUpload && $response->statusCode === 612) { + return $this->uploadV2($fname); + } + if (!$response->ok() || !isset($ret['md5']) || $md5 != $ret['md5']) { + return array(null, new Error($this->currentUrl, $response)); + } + $blockStatus = array('etag' => $ret['etag'], 'partNumber' => $partNumber); + array_push($this->finishedEtags['etags'], $blockStatus); + $partNumber += 1; + + $uploaded += $blockSize; + $this->finishedEtags['uploaded'] = $uploaded; + + if ($this->resumeRecordFile !== null) { + $recordData = json_encode($this->finishedEtags); + if ($recordData) { + $isWritten = file_put_contents($this->resumeRecordFile, $recordData); + if ($isWritten === false) { + error_log("write resumeRecordFile failed"); + } + } else { + error_log('resumeRecordData encode failed'); + } + } + } + + list($ret, $err) = $this->completeParts($fname, $this->finishedEtags['uploadId'], $encodedObjectName); + if ($err !== null) { + $response = $err->getResponse(); + if ($isResumeUpload && $response->statusCode === 612) { + return $this->uploadV2($fname); + } + } + return array($ret, $err); + } + + /** + * 创建块 + */ + private function makeBlock($block, $blockSize) + { + $url = $this->host . '/mkblk/' . $blockSize; + return $this->post($url, $block); + } + + private function fileUrl($fname) + { + $url = $this->host . '/mkfile/' . $this->size; + $url .= '/mimeType/' . \Qiniu\base64_urlSafeEncode($this->mime); + if ($this->key != null) { + $url .= '/key/' . \Qiniu\base64_urlSafeEncode($this->key); + } + $url .= '/fname/' . \Qiniu\base64_urlSafeEncode($fname); + if (!empty($this->params)) { + foreach ($this->params as $key => $value) { + $val = \Qiniu\base64_urlSafeEncode($value); + $url .= "/$key/$val"; + } + } + return $url; + } + + /** + * 创建文件 + * + * @param string $fname 文件名 + * @return array{array | null, Error | null} + */ + private function makeFile($fname) + { + $url = $this->fileUrl($fname); + $body = implode(',', array_map(function ($ctx) { + return $ctx['ctx']; + }, $this->contexts)); + $response = $this->post($url, $body); + if ($response->needRetry()) { + $response = $this->post($url, $body); + } + if ($response->statusCode === 200 || $response->statusCode === 701) { + if ($this->resumeRecordFile !== null) { + @unlink($this->resumeRecordFile); + } + } + if (!$response->ok()) { + return array(null, new Error($this->currentUrl, $response)); + } + return array($response->json(), null); + } + + private function post($url, $data) + { + $this->currentUrl = $url; + $headers = array('Authorization' => 'UpToken ' . $this->upToken); + return Client::post($url, $data, $headers, $this->reqOpt); + } + + private function blockSize($uploaded) + { + if ($this->size < $uploaded + $this->partSize) { + return $this->size - $uploaded; + } + return $this->partSize; + } + + private function makeInitReq($encodedObjectName) + { + list($ret, $err) = $this->initReq($encodedObjectName); + + if ($ret == null) { + return $err; + } + + $this->finishedEtags["uploadId"] = $ret['uploadId']; + $this->finishedEtags["expiredAt"] = $ret['expireAt']; + return $err; + } + + /** + * 初始化上传任务 + */ + private function initReq($encodedObjectName) + { + $url = $this->host . '/buckets/' . $this->bucket . '/objects/' . $encodedObjectName . '/uploads'; + $headers = array( + 'Authorization' => 'UpToken ' . $this->upToken, + 'Content-Type' => 'application/json' + ); + $response = $this->postWithHeaders($url, null, $headers); + $ret = $response->json(); + if ($response->ok() && $ret != null) { + return array($ret, null); + } + + return array(null, new Error($url, $response)); + } + + /** + * 分块上传v2 + */ + private function uploadPart($block, $partNumber, $uploadId, $encodedObjectName, $md5) + { + $headers = array( + 'Authorization' => 'UpToken ' . $this->upToken, + 'Content-Type' => 'application/octet-stream', + 'Content-MD5' => $md5 + ); + $url = $this->host . '/buckets/' . $this->bucket . '/objects/' . $encodedObjectName . + '/uploads/' . $uploadId . '/' . $partNumber; + $response = $this->put($url, $block, $headers); + if ($response->statusCode === 612) { + if ($this->resumeRecordFile !== null) { + @unlink($this->resumeRecordFile); + } + } + return $response; + } + + /** + * 完成分片上传V2 + * + * @param string $fname 文件名 + * @param int $uploadId 由 {@see initReq} 获取 + * @param string $encodedObjectName 经过编码的存储路径 + * @return array{array | null, Error | null} + */ + private function completeParts($fname, $uploadId, $encodedObjectName) + { + $headers = array( + 'Authorization' => 'UpToken ' . $this->upToken, + 'Content-Type' => 'application/json' + ); + $etags = $this->finishedEtags['etags']; + $sortedEtags = \Qiniu\arraySort($etags, 'partNumber'); + $metadata = array(); + $customVars = array(); + if ($this->params) { + foreach ($this->params as $k => $v) { + if (strpos($k, 'x:') === 0) { + $customVars[$k] = $v; + } elseif (strpos($k, 'x-qn-meta-') === 0) { + $metadata[$k] = $v; + } + } + } + if (empty($metadata)) { + $metadata = null; + } + if (empty($customVars)) { + $customVars = null; + } + $body = array( + 'fname' => $fname, + 'mimeType' => $this->mime, + 'metadata' => $metadata, + 'customVars' => $customVars, + 'parts' => $sortedEtags + ); + $jsonBody = json_encode($body); + $url = $this->host . '/buckets/' . $this->bucket . '/objects/' . $encodedObjectName . '/uploads/' . $uploadId; + $response = $this->postWithHeaders($url, $jsonBody, $headers); + if ($response->needRetry()) { + $response = $this->postWithHeaders($url, $jsonBody, $headers); + } + if ($response->statusCode === 200 || $response->statusCode === 612) { + if ($this->resumeRecordFile !== null) { + @unlink($this->resumeRecordFile); + } + } + if (!$response->ok()) { + return array(null, new Error($this->currentUrl, $response)); + } + return array($response->json(), null); + } + + private function put($url, $data, $headers) + { + $this->currentUrl = $url; + return Client::put($url, $data, $headers, $this->reqOpt); + } + + private function postWithHeaders($url, $data, $headers) + { + $this->currentUrl = $url; + return Client::post($url, $data, $headers, $this->reqOpt); + } +} diff --git a/php-sdk/src/Qiniu/Storage/UploadManager.php b/php-sdk/src/Qiniu/Storage/UploadManager.php new file mode 100644 index 0000000..fcd11fa --- /dev/null +++ b/php-sdk/src/Qiniu/Storage/UploadManager.php @@ -0,0 +1,176 @@ +config = $config; + + if ($reqOpt === null) { + $reqOpt = new RequestOptions(); + } + + $this->reqOpt = $reqOpt; + } + + /** + * 上传二进制流到七牛 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param string $data 上传二进制流 + * @param array $params 自定义变量,规格参考 + * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param string $mime 上传数据的mimeType + * @param string $fname + * @param RequestOptions $reqOpt + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "", + * "key" => "" + * ] + */ + public function put( + $upToken, + $key, + $data, + $params = null, + $mime = 'application/octet-stream', + $fname = "default_filename", + $reqOpt = null + ) { + $reqOpt = $reqOpt === null ? $this->reqOpt : $reqOpt; + + $params = self::trimParams($params); + return FormUploader::put( + $upToken, + $key, + $data, + $this->config, + $params, + $mime, + $fname, + $reqOpt + ); + } + + + /** + * 上传文件到七牛 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param string $filePath 上传文件的路径 + * @param array $params 定义变量,规格参考 + * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param boolean $mime 上传数据的mimeType + * @param string $checkCrc 是否校验crc32 + * @param string $resumeRecordFile 断点续传文件路径 默认为null + * @param string $version 分片上传版本 目前支持v1/v2版本 默认v1 + * @param int $partSize 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "", + * "key" => "" + * ] + * @throws \Exception + */ + public function putFile( + $upToken, + $key, + $filePath, + $params = null, + $mime = 'application/octet-stream', + $checkCrc = false, + $resumeRecordFile = null, + $version = 'v1', + $partSize = config::BLOCK_SIZE, + $reqOpt = null + ) { + $reqOpt = $reqOpt === null ? $this->reqOpt : $reqOpt; + + $file = fopen($filePath, 'rb'); + if ($file === false) { + throw new \Exception("file can not open", 1); + } + $params = self::trimParams($params); + $stat = fstat($file); + $size = $stat['size']; + if ($size <= Config::BLOCK_SIZE) { + $data = fread($file, $size); + fclose($file); + if ($data === false) { + throw new \Exception("file can not read", 1); + } + return FormUploader::put( + $upToken, + $key, + $data, + $this->config, + $params, + $mime, + basename($filePath), + $reqOpt + ); + } + + $up = new ResumeUploader( + $upToken, + $key, + $file, + $size, + $params, + $mime, + $this->config, + $resumeRecordFile, + $version, + $partSize, + $reqOpt + ); + $ret = $up->upload(basename($filePath)); + fclose($file); + return $ret; + } + + public static function trimParams($params) + { + if ($params === null) { + return null; + } + $ret = array(); + foreach ($params as $k => $v) { + $pos1 = strpos($k, 'x:'); + $pos2 = strpos($k, 'x-qn-meta-'); + if (($pos1 === 0 || $pos2 === 0) && !empty($v)) { + $ret[$k] = $v; + } + } + return $ret; + } +} diff --git a/php-sdk/src/Qiniu/Zone.php b/php-sdk/src/Qiniu/Zone.php new file mode 100644 index 0000000..50d60c6 --- /dev/null +++ b/php-sdk/src/Qiniu/Zone.php @@ -0,0 +1,58 @@ + $v) { + $keysValue[$k] = $v[$key]; + } + array_multisort($keysValue, $sort, $array); + return $array; + } + + /** + * Wrapper for JSON decode that implements error detection with helpful + * error messages. + * + * @param string $json JSON data to parse + * @param bool $assoc When true, returned objects will be converted + * into associative arrays. + * @param int $depth User specified recursion depth. + * + * @return mixed + * @throws \InvalidArgumentException if the JSON cannot be parsed. + * @link http://www.php.net/manual/en/function.json-decode.php + */ + function json_decode($json, $assoc = false, $depth = 512) + { + static $jsonErrors = array( + JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' + ); + + if (empty($json)) { + return null; + } + $data = \json_decode($json, $assoc, $depth); + + if (JSON_ERROR_NONE !== json_last_error()) { + $last = json_last_error(); + throw new \InvalidArgumentException( + 'Unable to parse JSON data: ' + . (isset($jsonErrors[$last]) + ? $jsonErrors[$last] + : 'Unknown error') + ); + } + + return $data; + } + + /** + * 计算七牛API中的数据格式 + * + * @param string $bucket 待操作的空间名 + * @param string $key 待操作的文件名 + * + * @return string 符合七牛API规格的数据格式 + * @link https://developer.qiniu.com/kodo/api/data-format + */ + function entry($bucket, $key = null) + { + $en = $bucket; + if ($key !== null) { + $en = $bucket . ':' . $key; + } + return base64_urlSafeEncode($en); + } + + function decodeEntry($entry) + { + $en = base64_urlSafeDecode($entry); + $en = explode(':', $en); + if (count($en) == 1) { + return array($en[0], null); + } + return array($en[0], $en[1]); + } + + /** + * array 辅助方法,无值时不set + * + * @param array $array 待操作array + * @param string $key key + * @param string $value value 为null时 不设置 + * + * @return array 原来的array,便于连续操作 + */ + function setWithoutEmpty(&$array, $key, $value) + { + if (!empty($value)) { + $array[$key] = $value; + } + return $array; + } + + /** + * 缩略图链接拼接 + * + * @param string $url 图片链接 + * @param int $mode 缩略模式 + * @param int $width 宽度 + * @param int $height 长度 + * @param string $format 输出类型 + * @param int $quality 图片质量 + * @param int $interlace 是否支持渐进显示 + * @param int $ignoreError 忽略结果 + * @return string + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html + * @author Sherlock Ren + */ + function thumbnail( + $url, + $mode, + $width, + $height, + $format = null, + $quality = null, + $interlace = null, + $ignoreError = 1 + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'thumbnail'), func_get_args()); + } + + /** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @param numeric $watermarkScale 自适应原图的短边比例 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html + * @return string + * @author Sherlock Ren + */ + function waterImg( + $url, + $image, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null, + $watermarkScale = null + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'waterImg'), func_get_args()); + } + + /** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @return string + * @author Sherlock Ren + */ + function waterText( + $url, + $text, + $font = '黑体', + $fontSize = 0, + $fontColor = null, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'waterText'), func_get_args()); + } + + /** + * 从uptoken解析accessKey和bucket + * + * @param $upToken + * @return array(ak,bucket,err=null) + */ + function explodeUpToken($upToken) + { + $items = explode(':', $upToken); + if (count($items) != 3) { + return array(null, null, "invalid uptoken"); + } + $accessKey = $items[0]; + $putPolicy = json_decode(base64_urlSafeDecode($items[2])); + $scope = $putPolicy->scope; + $scopeItems = explode(':', $scope); + $bucket = $scopeItems[0]; + return array($accessKey, $bucket, null); + } + + // polyfill ucwords for `php version < 5.4.32` or `5.5.0 <= php version < 5.5.16` + if (version_compare(phpversion(), "5.4.32") < 0 || + ( + version_compare(phpversion(), "5.5.0") >= 0 && + version_compare(phpversion(), "5.5.16") < 0 + ) + ) { + function ucwords($str, $delimiters = " \t\r\n\f\v") + { + $delims = preg_split('//u', $delimiters, -1, PREG_SPLIT_NO_EMPTY); + + foreach ($delims as $delim) { + $str = implode($delim, array_map('ucfirst', explode($delim, $str))); + } + + return $str; + } + } else { + function ucwords($str, $delimiters) + { + return \ucwords($str, $delimiters); + } + } + + /** + * 将 parse_url 的结果转换回字符串 + * TODO: add unit test + * + * @param $parsed_url - parse_url 的结果 + * @return string + */ + function unparse_url($parsed_url) + { + + $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; + + $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; + + $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; + + $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; + + $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; + + $pass = ($user || $pass) ? "$pass@" : ''; + + $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; + + $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; + + $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + + return "$scheme$user$pass$host$port$path$query$fragment"; + } +} diff --git a/php-sdk/test-env.sh b/php-sdk/test-env.sh new file mode 100644 index 0000000..eedf6b5 --- /dev/null +++ b/php-sdk/test-env.sh @@ -0,0 +1,4 @@ +export QINIU_ACCESS_KEY=xxx +export QINIU_SECRET_KEY=xxx +export QINIU_TEST_BUCKET=phpsdk +export QINIU_TEST_DOMAIN=phpsdk.qiniudn.com \ No newline at end of file diff --git a/php-sdk/tests/Qiniu/Tests/AuthTest.php b/php-sdk/tests/Qiniu/Tests/AuthTest.php new file mode 100644 index 0000000..99aec85 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/AuthTest.php @@ -0,0 +1,296 @@ +sign('test'); + $this->assertEquals('abcdefghklmnopq:mSNBTR7uS2crJsyFr2Amwv1LaYg=', $token); + } + + public function testSignWithData() + { + global $dummyAuth; + $token = $dummyAuth->signWithData('test'); + $this->assertEquals('abcdefghklmnopq:-jP8eEV9v48MkYiBGs81aDxl60E=:dGVzdA==', $token); + } + + public function testSignRequest() + { + global $dummyAuth; + $token = $dummyAuth->signRequest('http://www.qiniu.com?go=1', 'test', ''); + $this->assertEquals('abcdefghklmnopq:cFyRVoWrE3IugPIMP5YJFTO-O-Y=', $token); + $ctype = 'application/x-www-form-urlencoded'; + $token = $dummyAuth->signRequest('http://www.qiniu.com?go=1', 'test', $ctype); + $this->assertEquals($token, 'abcdefghklmnopq:svWRNcacOE-YMsc70nuIYdaa1e4='); + } + + public function testPrivateDownloadUrl() + { + global $dummyAuth; + $_SERVER['override_qiniu_auth_time'] = true; + $url = $dummyAuth->privateDownloadUrl('http://www.qiniu.com?go=1'); + $expect = 'http://www.qiniu.com?go=1&e=1234571490&token=abcdefghklmnopq:8vzBeLZ9W3E4kbBLFLW0Xe0u7v4='; + $this->assertEquals($expect, $url); + unset($_SERVER['override_qiniu_auth_time']); + } + + public function testUploadToken() + { + global $dummyAuth; + $_SERVER['override_qiniu_auth_time'] = true; + $token = $dummyAuth->uploadToken('1', '2', 3600, array('endUser' => 'y')); + // @codingStandardsIgnoreStart + $exp = 'abcdefghklmnopq:yyeexeUkPOROoTGvwBjJ0F0VLEo=:eyJlbmRVc2VyIjoieSIsInNjb3BlIjoiMToyIiwiZGVhZGxpbmUiOjEyMzQ1NzE0OTB9'; + // @codingStandardsIgnoreEnd + $this->assertEquals($exp, $token); + unset($_SERVER['override_qiniu_auth_time']); + } + + public function testSignQiniuAuthorization() + { + $auth = new Auth("ak", "sk"); + + $testCases = array( + array( + "url" => "", + "method" => "", + "headers" => array( + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + "Content-Type" => array("application/x-www-form-urlencoded") + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:0i1vKClRDWFyNkcTFzwcE7PzX74=" + ), + array( + "url" => "", + "method" => "", + "headers" => array( + "Content-Type" => array("application/json") + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:K1DI0goT05yhGizDFE5FiPJxAj4=" + ), + array( + "url" => "", + "method" => "GET", + "headers" => array( + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + "Content-Type" => array("application/x-www-form-urlencoded"), + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:0i1vKClRDWFyNkcTFzwcE7PzX74=" + ), + array( + "url" => "", + "method" => "POST", + "headers" => array( + "Content-Type" => array("application/json"), + "X-Qiniu" => array("b"), + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:0ujEjW_vLRZxebsveBgqa3JyQ-w=" + ), + array( + "url" => "http://upload.qiniup.com", + "method" => "", + "headers" => array( + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + "Content-Type" => array("application/x-www-form-urlencoded"), + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:GShw5NitGmd5TLoo38nDkGUofRw=" + ), + array( + "url" => "http://upload.qiniup.com", + "method" => "", + "headers" => array( + "Content-Type" => array("application/json"), + "X-Qiniu-Bbb" => array("BBB", "AAA"), + "X-Qiniu-Aaa" => array("DDD", "CCC"), + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:DhNA1UCaBqSHCsQjMOLRfVn63GQ=" + ), + array( + "url" => "http://upload.qiniup.com", + "method" => "", + "headers" => array( + "Content-Type" => array("application/x-www-form-urlencoded"), + "X-Qiniu-Bbb" => array("BBB", "AAA"), + "X-Qiniu-Aaa" => array("DDD", "CCC"), + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + ), + "body" => "name=test&language=go", + "expectedToken" => "ak:KUAhrYh32P9bv0COD8ugZjDCmII=" + ), + array( + "url" => "http://upload.qiniup.com", + "method" => "", + "headers" => array( + "Content-Type" => array("application/x-www"), + "Content-Type" => array("application/x-www-form-urlencoded"), + "X-Qiniu-Bbb" => array("BBB", "AAA"), + "X-Qiniu-Aaa" => array("DDD", "CCC"), + ), + "body" => "name=test&language=go", + "expectedToken" => "ak:KUAhrYh32P9bv0COD8ugZjDCmII=" + ), + array( + "url" => "http://upload.qiniup.com/mkfile/sdf.jpg", + "method" => "", + "headers" => array( + "Content-Type" => array("application/x-www-form-urlencoded"), + "X-Qiniu-Bbb" => array("BBB", "AAA"), + "X-Qiniu-Aaa" => array("DDD", "CCC"), + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + ), + "body" => "name=test&language=go", + "expectedToken" => "ak:fkRck5_LeyfwdkyyLk-hyNwGKac=" + ), + array( + "url" => "http://upload.qiniup.com/mkfile/sdf.jpg?s=er3&df", + "method" => "", + "headers" => array( + "Content-Type" => array("application/x-www-form-urlencoded"), + "X-Qiniu-Bbb" => array("BBB", "AAA"), + "X-Qiniu-Aaa" => array("DDD", "CCC"), + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + ), + "body" => "name=test&language=go", + "expectedToken" => "ak:PUFPWsEUIpk_dzUvvxTTmwhp3p4=" + ) + ); + + foreach ($testCases as $testCase) { + list($sign, $err) = $auth->signQiniuAuthorization( + $testCase["url"], + $testCase["method"], + $testCase["body"], + new Header($testCase["headers"]) + ); + + $this->assertNull($err); + $this->assertEquals($testCase["expectedToken"], $sign); + } + } + + public function testDisableQiniuTimestampSignatureDefault() + { + $auth = new Auth("ak", "sk"); + $authedHeaders = $auth->authorizationV2("https://example.com", "GET"); + $this->assertArrayHasKey("X-Qiniu-Date", $authedHeaders); + } + + public function testDisableQiniuTimestampSignature() + { + $auth = new Auth("ak", "sk", array( + "disableQiniuTimestampSignature" => true + )); + $authedHeaders = $auth->authorizationV2("https://example.com", "GET"); + $this->assertArrayNotHasKey("X-Qiniu-Date", $authedHeaders); + } + public function testDisableQiniuTimestampSignatureEnv() + { + putenv("DISABLE_QINIU_TIMESTAMP_SIGNATURE=true"); + $auth = new Auth("ak", "sk"); + $authedHeaders = $auth->authorizationV2("https://example.com", "GET"); + $this->assertArrayNotHasKey("X-Qiniu-Date", $authedHeaders); + putenv('DISABLE_QINIU_TIMESTAMP_SIGNATURE'); + } + public function testDisableQiniuTimestampSignatureEnvBeIgnored() + { + putenv("DISABLE_QINIU_TIMESTAMP_SIGNATURE=true"); + $auth = new Auth("ak", "sk", array( + "disableQiniuTimestampSignature" => false + )); + $authedHeaders = $auth->authorizationV2("https://example.com", "GET"); + $this->assertArrayHasKey("X-Qiniu-Date", $authedHeaders); + putenv('DISABLE_QINIU_TIMESTAMP_SIGNATURE'); + } + public function testQboxVerifyCallbackShouldOkWithRequiredOptions() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'QBox abcdefghklmnopq:T7F-SjxX7X2zI4Fc1vANiNt1AUE=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123' + ); + $this->assertTrue($ok); + } + public function testQboxVerifyCallbackShouldOkWithOmitOptions() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'QBox abcdefghklmnopq:T7F-SjxX7X2zI4Fc1vANiNt1AUE=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123', + 'POST', // this should be omit + array( + 'X-Qiniu-Bbb' => 'BBB' + ) // this should be omit + ); + $this->assertTrue($ok); + } + public function testQiniuVerifyCallbackShouldOk() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'Qiniu abcdefghklmnopq:ZqS7EZuAKrhZaEIxqNGxDJi41IQ=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123', + 'GET', + array( + 'X-Qiniu-Bbb' => 'BBB' + ) + ); + $this->assertTrue($ok); + } + public function testQiniuVerifyCallbackShouldFailed() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'Qiniu abcdefghklmnopq:ZqS7EZuAKrhZaEIxqNGxDJi41IQ=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123', + 'POST', + array( + 'X-Qiniu-Bbb' => 'BBB' + ) + ); + $this->assertFalse($ok); + } + } +} diff --git a/php-sdk/tests/Qiniu/Tests/Base64Test.php b/php-sdk/tests/Qiniu/Tests/Base64Test.php new file mode 100644 index 0000000..fed3da0 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/Base64Test.php @@ -0,0 +1,16 @@ +assertEquals($a, \Qiniu\base64_urlSafeDecode($b)); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/BucketTest.php b/php-sdk/tests/Qiniu/Tests/BucketTest.php new file mode 100644 index 0000000..0467698 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/BucketTest.php @@ -0,0 +1,733 @@ +batch($ops); + } + + private static function getObjectKey($key) + { + $result = $key . rand(); + + self::$bucketManager->copy( + self::$bucketName, + $key, + self::$bucketName, + $result + ); + + self::$keysToCleanup[] = $result; + + return $result; + } + + public function testBuckets() + { + + list($list, $error) = self::$bucketManager->buckets(); + $this->assertNull($error); + $this->assertTrue(in_array(self::$bucketName, $list)); + + list($list2, $error) = self::$dummyBucketManager->buckets(); + $this->assertEquals(401, $error->code()); + $this->assertNotNull($error->message()); + $this->assertNotNull($error->getResponse()); + $this->assertNull($list2); + } + + public function testListBuckets() + { + list($ret, $error) = self::$bucketManager->listbuckets('z0'); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testCreateBucket() + { + list($ret, $error) = self::$bucketManager->createBucket(self::$bucketToCreate); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testDeleteBucket() + { + list($ret, $error) = self::$bucketManager->deleteBucket(self::$bucketToCreate); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testDomains() + { + list($ret, $error) = self::$bucketManager->domains(self::$bucketName); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testBucketInfo() + { + list($ret, $error) = self::$bucketManager->bucketInfo(self::$bucketName); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testBucketInfos() + { + list($ret, $error) = self::$bucketManager->bucketInfos('z0'); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testList() + { + list($ret, $error) = self::$bucketManager->listFiles(self::$bucketName, null, null, 10); + $this->assertNull($error); + $this->assertNotNull($ret['items'][0]); + $this->assertNotNull($ret['marker']); + } + + public function testListFilesv2() + { + list($ret, $error) = self::$bucketManager->listFilesv2(self::$bucketName, null, null, 10); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testBucketLifecycleRule() + { + // delete + self::$bucketManager->deleteBucketLifecycleRule(self::$bucketName, self::$bucketLifeRuleName); + + // add + list($ret, $error) = self::$bucketManager->bucketLifecycleRule( + self::$bucketName, + self::$bucketLifeRuleName, + self::$bucketLifeRulePrefix, + 80, + 70, + 72, + 74, + 71 + ); + $this->assertNull($error); + $this->assertNotNull($ret); + + // get + list($ret, $error) = self::$bucketManager->getBucketLifecycleRules(self::$bucketName); + $this->assertNull($error); + $this->assertNotNull($ret); + $rule = null; + foreach ($ret as $r) { + if ($r["name"] === self::$bucketLifeRuleName) { + $rule = $r; + break; + } + } + $this->assertNotNull($rule); + $this->assertEquals(self::$bucketLifeRulePrefix, $rule["prefix"]); + $this->assertEquals(80, $rule["delete_after_days"]); + $this->assertEquals(70, $rule["to_line_after_days"]); + $this->assertEquals(71, $rule["to_archive_ir_after_days"]); + $this->assertEquals(72, $rule["to_archive_after_days"]); + $this->assertEquals(74, $rule["to_deep_archive_after_days"]); + + // update + list($ret, $error) = self::$bucketManager->updateBucketLifecycleRule( + self::$bucketName, + self::$bucketLifeRuleName, + 'update-' . self::$bucketLifeRulePrefix, + 90, + 75, + 80, + 85, + 78 + ); + $this->assertNull($error); + $this->assertNotNull($ret); + + // get + list($ret, $error) = self::$bucketManager->getBucketLifecycleRules(self::$bucketName); + $this->assertNull($error); + $this->assertNotNull($ret); + $rule = null; + foreach ($ret as $r) { + if ($r["name"] === self::$bucketLifeRuleName) { + $rule = $r; + break; + } + } + $this->assertNotNull($rule); + $this->assertEquals('update-' . self::$bucketLifeRulePrefix, $rule["prefix"]); + $this->assertEquals(90, $rule["delete_after_days"]); + $this->assertEquals(75, $rule["to_line_after_days"]); + $this->assertEquals(78, $rule["to_archive_ir_after_days"]); + $this->assertEquals(80, $rule["to_archive_after_days"]); + $this->assertEquals(85, $rule["to_deep_archive_after_days"]); + + // delete + list($ret, $error) = self::$bucketManager->deleteBucketLifecycleRule( + self::$bucketName, + self::$bucketLifeRuleName + ); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testPutBucketEvent() + { + list($ret, $error) = self::$bucketManager->putBucketEvent( + self::$bucketName, + self::$bucketEventName, + self::$bucketEventPrefix, + 'img', + array('copy'), + self::$customCallbackURL + ); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testUpdateBucketEvent() + { + list($ret, $error) = self::$bucketManager->updateBucketEvent( + self::$bucketName, + self::$bucketEventName, + self::$bucketEventPrefix, + 'video', + array('copy'), + self::$customCallbackURL + ); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testGetBucketEvents() + { + list($ret, $error) = self::$bucketManager->getBucketEvents(self::$bucketName); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testDeleteBucketEvent() + { + list($ret, $error) = self::$bucketManager->deleteBucketEvent(self::$bucketName, self::$bucketEventName); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testStat() + { + list($stat, $error) = self::$bucketManager->stat(self::$bucketName, self::$key); + $this->assertNull($error); + $this->assertNotNull($stat); + $this->assertNotNull($stat['hash']); + + list($stat, $error) = self::$bucketManager->stat(self::$bucketName, 'nofile'); + $this->assertEquals(612, $error->code()); + $this->assertNotNull($error->message()); + $this->assertNull($stat); + + list($stat, $error) = self::$bucketManager->stat('nobucket', 'nofile'); + $this->assertEquals(631, $error->code()); + $this->assertNotNull($error->message()); + $this->assertNull($stat); + } + + public function testDelete() + { + $fileToDel = self::getObjectKey(self::$key); + list(, $error) = self::$bucketManager->delete(self::$bucketName, $fileToDel); + $this->assertNull($error); + } + + + public function testRename() + { + $fileToRename = self::getObjectKey(self::$key); + $fileRenamed = $fileToRename . 'new'; + list(, $error) = self::$bucketManager->rename(self::$bucketName, $fileToRename, $fileRenamed); + $this->assertNull($error); + self::$keysToCleanup[] = $fileRenamed; + } + + + public function testCopy() + { + $fileToCopy = self::getObjectKey(self::$key2); + $fileCopied = $fileToCopy . 'copied'; + + //test force copy + list(, $error) = self::$bucketManager->copy( + self::$bucketName, + $fileToCopy, + self::$bucketName, + $fileCopied, + true + ); + $this->assertNull($error); + + list($fileToCopyStat,) = self::$bucketManager->stat(self::$bucketName, $fileToCopy); + list($fileCopiedStat,) = self::$bucketManager->stat(self::$bucketName, $fileCopied); + + $this->assertEquals($fileToCopyStat['hash'], $fileCopiedStat['hash']); + + self::$keysToCleanup[] = $fileCopied; + } + + + public function testChangeMime() + { + $fileToChange = self::getObjectKey('php-sdk.html'); + list(, $error) = self::$bucketManager->changeMime( + self::$bucketName, + $fileToChange, + 'text/plain' + ); + $this->assertNull($error); + + list($ret, $error) = self::$bucketManager->stat( + self::$bucketName, + $fileToChange + ); + $this->assertNull($error); + $this->assertEquals('text/plain', $ret['mimeType']); + } + + public function testPrefetch() + { + list($ret, $error) = self::$bucketManager->prefetch( + self::$bucketName, + 'php-sdk.html' + ); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testPrefetchFailed() + { + list($ret, $error) = self::$bucketManager->prefetch( + 'fakebucket', + 'php-sdk.html' + ); + $this->assertNotNull($error); + $this->assertNull($ret); + } + + public function testFetch() + { + list($ret, $error) = self::$bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + self::$bucketName, + 'fetch.html' + ); + $this->assertNull($error); + $this->assertArrayHasKey('hash', $ret); + + list($ret, $error) = self::$bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + self::$bucketName, + '' + ); + $this->assertNull($error); + $this->assertArrayHasKey('key', $ret); + + list($ret, $error) = self::$bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + self::$bucketName + ); + $this->assertNull($error); + $this->assertArrayHasKey('key', $ret); + } + + public function testFetchFailed() + { + list($ret, $error) = self::$bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + 'fakebucket' + ); + $this->assertNotNull($error); + $this->assertNull($ret); + } + + public function testAsynchFetch() + { + list($ret, $error) = self::$bucketManager->asynchFetch( + 'http://devtools.qiniu.com/qiniu.png', + self::$bucketName, + null, + 'qiniu.png' + ); + $this->assertNull($error); + $this->assertArrayHasKey('id', $ret); + + list($ret, $error) = self::$bucketManager->asynchFetch( + 'http://devtools.qiniu.com/qiniu.png', + self::$bucketName, + null, + '' + ); + $this->assertNull($error); + $this->assertArrayHasKey('id', $ret); + + list($ret, $error) = self::$bucketManager->asynchFetch( + 'http://devtools.qiniu.com/qiniu.png', + self::$bucketName + ); + $this->assertNull($error); + $this->assertArrayHasKey('id', $ret); + } + + public function testAsynchFetchFailed() + { + list($ret, $error) = self::$bucketManager->asynchFetch( + 'http://devtools.qiniu.com/qiniu.png', + 'fakebucket' + ); + $this->assertNotNull($error); + $this->assertNull($ret); + } + + + public function testBatchCopy() + { + $key = 'copyto' . rand(); + $ops = BucketManager::buildBatchCopy( + self::$bucketName, + array(self::$key => $key), + self::$bucketName, + true + ); + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + + self::$keysToCleanup[] = $key; + } + + public function testBatchMove() + { + $fileToMove = self::getObjectKey(self::$key); + $fileMoved = $fileToMove . 'to'; + $ops = BucketManager::buildBatchMove( + self::$bucketName, + array($fileToMove => $fileMoved), + self::$bucketName, + true + ); + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + self::$keysToCleanup[] = $fileMoved; + } + + public function testBatchRename() + { + $fileToRename = self::getObjectKey(self::$key); + $fileRenamed = $fileToRename . 'to'; + + $ops = BucketManager::buildBatchRename( + self::$bucketName, + array($fileToRename => $fileRenamed), + true + ); + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + + self::$keysToCleanup[] = $fileRenamed; + } + + public function testBatchStat() + { + $ops = BucketManager::buildBatchStat(self::$bucketName, array('php-sdk.html')); + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + } + + public function testBatchChangeTypeAndBatchRestoreAr() + { + $key = self::getObjectKey(self::$key); + + $ops = BucketManager::buildBatchChangeType(self::$bucketName, array($key => 2)); // 2 Archive + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + + $ops = BucketManager::buildBatchRestoreAr(self::$bucketName, array($key => 1)); // 1 day + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + } + + public function testDeleteAfterDays() + { + $key = "noexist" . rand(); + list($ret, $error) = self::$bucketManager->deleteAfterDays(self::$bucketName, $key, 1); + $this->assertNotNull($error); + $this->assertNull($ret); + + $key = self::getObjectKey(self::$key); + list(, $error) = self::$bucketManager->deleteAfterDays(self::$bucketName, $key, 1); + $this->assertNull($error); + + list($ret, $error) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($error); + $this->assertGreaterThan(23 * 3600, $ret['expiration'] - time()); + $this->assertLessThan(48 * 3600, $ret['expiration'] - time()); + } + + public function testSetObjectLifecycle() + { + $key = self::getObjectKey(self::$key); + + list(, $err) = self::$bucketManager->setObjectLifecycle( + self::$bucketName, + $key, + 10, + 20, + 30, + 40, + 15 + ); + $this->assertNull($err); + + list($ret, $error) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($error); + $this->assertNotNull($ret['transitionToIA']); + $this->assertNotNull($ret['transitionToArchiveIR']); + $this->assertNotNull($ret['transitionToARCHIVE']); + $this->assertNotNull($ret['transitionToDeepArchive']); + $this->assertNotNull($ret['expiration']); + } + + public function testSetObjectLifecycleWithCond() + { + $key = self::getObjectKey(self::$key); + + list($ret, $err) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + $key_hash = $ret['hash']; + $key_fsize = $ret['fsize']; + + list(, $err) = self::$bucketManager->setObjectLifecycleWithCond( + self::$bucketName, + $key, + array( + 'hash' => $key_hash, + 'fsize' => $key_fsize + ), + 10, + 20, + 30, + 40, + 15 + ); + $this->assertNull($err); + + list($ret, $error) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($error); + $this->assertNotNull($ret['transitionToIA']); + $this->assertNotNull($ret['transitionToArchiveIR']); + $this->assertNotNull($ret['transitionToARCHIVE']); + $this->assertNotNull($ret['transitionToDeepArchive']); + $this->assertNotNull($ret['expiration']); + } + + public function testBatchSetObjectLifecycle() + { + $key = self::getObjectKey(self::$key); + + $ops = BucketManager::buildBatchSetObjectLifecycle( + self::$bucketName, + array($key), + 10, + 20, + 30, + 40, + 15 + ); + list($ret, $err) = self::$bucketManager->batch($ops); + $this->assertNull($err); + $this->assertEquals(200, $ret[0]['code']); + } + + public function testGetCorsRules() + { + list(, $err) = self::$bucketManager->getCorsRules(self::$bucketName); + $this->assertNull($err); + } + + public function testPutBucketAccessStyleMode() + { + list(, $err) = self::$bucketManager->putBucketAccessStyleMode(self::$bucketName, 0); + $this->assertNull($err); + } + + public function testPutBucketAccessMode() + { + list(, $err) = self::$bucketManager->putBucketAccessMode(self::$bucketName, 0); + $this->assertNull($err); + } + + public function testPutReferAntiLeech() + { + list(, $err) = self::$bucketManager->putReferAntiLeech(self::$bucketName, 0, "1", "*"); + $this->assertNull($err); + } + + public function testPutBucketMaxAge() + { + list(, $err) = self::$bucketManager->putBucketMaxAge(self::$bucketName, 31536000); + $this->assertNull($err); + } + + public function testPutBucketQuota() + { + list(, $err) = self::$bucketManager->putBucketQuota(self::$bucketName, -1, -1); + $this->assertNull($err); + } + + public function testGetBucketQuota() + { + list(, $err) = self::$bucketManager->getBucketQuota(self::$bucketName); + $this->assertNull($err); + } + + public function testChangeType() + { + $fileToChange = self::getObjectKey(self::$key); + + list(, $err) = self::$bucketManager->changeType(self::$bucketName, $fileToChange, 0); + $this->assertNull($err); + + list(, $err) = self::$bucketManager->changeType(self::$bucketName, $fileToChange, 1); + $this->assertNull($err); + } + + public function testArchiveRestoreAr() + { + $key = self::getObjectKey(self::$key); + + self::$bucketManager->changeType(self::$bucketName, $key, 2); + + list(, $err) = self::$bucketManager->restoreAr(self::$bucketName, $key, 2); + $this->assertNull($err); + + list($ret, $err) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + + $this->assertEquals(2, $ret["type"]); + + // restoreStatus + // null means frozen; + // 1 means be unfreezing; + // 2 means be unfrozen; + $this->assertNotNull($ret["restoreStatus"]); + $this->assertContains($ret["restoreStatus"], array(1, 2)); + } + + public function testDeepArchiveRestoreAr() + { + $key = self::getObjectKey(self::$key); + + self::$bucketManager->changeType(self::$bucketName, $key, 3); + + list(, $err) = self::$bucketManager->restoreAr(self::$bucketName, $key, 1); + $this->assertNull($err); + list($ret, $err) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + + $this->assertEquals(3, $ret["type"]); + + // restoreStatus + // null means frozen; + // 1 means be unfreezing; + // 2 means be unfrozen; + $this->assertNotNull($ret["restoreStatus"]); + $this->assertContains($ret["restoreStatus"], array(1, 2)); + } + + public function testChangeStatus() + { + $key = self::getObjectKey(self::$key); + + list(, $err) = self::$bucketManager->changeStatus(self::$bucketName, $key, 1); + $this->assertNull($err); + list($ret, $err) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + $this->assertEquals(1, $ret['status']); + + list(, $err) = self::$bucketManager->changeStatus(self::$bucketName, $key, 0); + $this->assertNull($err); + list($ret, $err) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + $this->assertArrayNotHasKey('status', $ret); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php b/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php new file mode 100644 index 0000000..baa9486 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php @@ -0,0 +1,151 @@ +cdnManager = new CdnManager($testAuth); + + global $timestampAntiLeechEncryptKey; + $this->encryptKey = $timestampAntiLeechEncryptKey; + + global $testStartDate; + $this->testStartDate = $testStartDate; + + global $testEndDate; + $this->testEndDate = $testEndDate; + + global $testGranularity; + $this->testGranularity = $testGranularity; + + global $testLogDate; + $this->testLogDate = $testLogDate; + + global $customDomain; + $this->refreshUrl = $customDomain . '/sdktest.png'; + $this->refreshDirs = $customDomain; + $this->customDomain = $customDomain; + + global $customDomain2; + $this->customDomain2 = $customDomain2; + } + + public function testRefreshUrls() + { + list($ret, $err) = $this->cdnManager->refreshUrls(array($this->refreshUrl)); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testRefreshDirs() + { + list($ret, $err) = $this->cdnManager->refreshDirs(array($this->refreshDirs)); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testRefreshUrlsAndDirs() + { + list($ret, $err) = $this->cdnManager->refreshUrlsAndDirs(array($this->refreshUrl), array($this->refreshDirs)); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testGetCdnRefreshList() + { + list($ret, $err) = $this->cdnManager->getCdnRefreshList(null, null, null, 'success'); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testPrefetchUrls() + { + list($ret, $err) = $this->cdnManager->prefetchUrls(array($this->refreshUrl)); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testGetCdnPrefetchList() + { + list($ret, $err) = $this->cdnManager->getCdnPrefetchList(null, null, 'success'); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testGetBandwidthData() + { + list($ret, $err) = $this->cdnManager->getBandwidthData( + array($this->customDomain2), + $this->testStartDate, + $this->testEndDate, + $this->testGranularity + ); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testGetFluxData() + { + list($ret, $err) = $this->cdnManager->getFluxData( + array($this->customDomain2), + $this->testStartDate, + $this->testEndDate, + $this->testGranularity + ); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testGetCdnLogList() + { + $domain = getenv('QINIU_TEST_DOMAIN'); + list($ret, $err) = $this->cdnManager->getCdnLogList(array($domain), $this->testLogDate); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testCreateTimestampAntiLeechUrl() + { + $signUrl = $this->cdnManager->createTimestampAntiLeechUrl($this->refreshUrl, $this->encryptKey, 3600); + $response = Client::get($signUrl); + $this->assertNull($response->error); + $this->assertEquals($response->statusCode, 200); + + $signUrl = $this->cdnManager->createTimestampAntiLeechUrl( + $this->refreshUrl . '?qiniu', + $this->encryptKey, + 3600 + ); + $response = Client::get($signUrl); + $this->assertNull($response->error); + $this->assertEquals($response->statusCode, 200); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/ConfigTest.php b/php-sdk/tests/Qiniu/Tests/ConfigTest.php new file mode 100644 index 0000000..3c39a5c --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/ConfigTest.php @@ -0,0 +1,118 @@ +accessKey = $accessKey; + global $bucketName; + $this->bucketName = $bucketName; + } + + public function testGetApiHost() + { + $conf = new Config(); + $hasException = false; + $apiHost = ''; + try { + $apiHost = $conf->getApiHost($this->accessKey, $this->bucketName); + } catch (\Exception $e) { + $hasException = true; + } + $this->assertFalse($hasException); + } + + public function testGetApiHostErrored() + { + $conf = new Config(); + $hasException = false; + try { + $conf->getApiHost($this->accessKey, "fakebucket"); + } catch (\Exception $e) { + $hasException = true; + } + $this->assertTrue($hasException); + } + + public function testGetApiHostV2() + { + $conf = new Config(); + list($apiHost, $err) = $conf->getApiHostV2($this->accessKey, $this->bucketName); + $this->assertNull($err); + } + + public function testGetApiHostV2Errored() + { + $conf = new Config(); + list($apiHost, $err) = $conf->getApiHostV2($this->accessKey, "fakebucket"); + $this->assertNotNull($err->code()); + $this->assertEquals(631, $err->code()); + $this->assertNull($apiHost); + } + + public function testSetUcHost() + { + $conf = new Config(); + $this->assertEquals('http://' . Config::UC_HOST, $conf->getUcHost()); + $conf->setUcHost("uc.example.com"); + $this->assertEquals("http://uc.example.com", $conf->getUcHost()); + + $conf = new Config(); + $conf->useHTTPS = true; + $this->assertEquals('https://' . Config::UC_HOST, $conf->getUcHost()); + $conf->setUcHost("uc.example.com"); + $this->assertEquals("https://uc.example.com", $conf->getUcHost()); + } + + public function testGetRegionWithCustomDomain() + { + $conf = new Config(); + $conf->setQueryRegionHost( + "uc.qbox.me" + ); + list(, $err) = $conf->getRsHostV2($this->accessKey, $this->bucketName); + $this->assertNull($err); + } + + public function testGetRegionWithBackupDomains() + { + $conf = new Config(); + $conf->setQueryRegionHost( + "fake-uc.phpsdk.qiniu.com", + array( + "unavailable-uc.phpsdk.qiniu.com", + Config::UC_HOST // real uc + ) + ); + list(, $err) = $conf->getRsHostV2($this->accessKey, $this->bucketName); + $this->assertNull($err); + } + + public function testGetRegionWithUcAndBackupDomains() + { + $conf = new Config(); + $conf->setUcHost("fake-uc.phpsdk.qiniu.com"); + $conf->setBackupQueryRegionHosts( + array( + "unavailable-uc.phpsdk.qiniu.com", + Config::UC_HOST // real uc + ) + ); + list(, $err) = $conf->getRsHostV2($this->accessKey, $this->bucketName); + $this->assertNull($err); + } + } +} diff --git a/php-sdk/tests/Qiniu/Tests/Crc32Test.php b/php-sdk/tests/Qiniu/Tests/Crc32Test.php new file mode 100644 index 0000000..63e24fd --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/Crc32Test.php @@ -0,0 +1,23 @@ +assertEquals('1352841281', $b); + } + + public function testFile() + { + $b = \Qiniu\crc32_file(__file__); + $c = \Qiniu\crc32_file(__file__); + $this->assertEquals($c, $b); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/DownloadTest.php b/php-sdk/tests/Qiniu/Tests/DownloadTest.php new file mode 100644 index 0000000..9b4b034 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/DownloadTest.php @@ -0,0 +1,27 @@ +privateDownloadUrl($base_url); + $response = Client::get($private_url); + $this->assertEquals(200, $response->statusCode); + } + + public function testFop() + { + global $testAuth; + $base_url = 'http://sdk.peterpy.cn/gogopher.jpg?exif'; + $private_url = $testAuth->privateDownloadUrl($base_url); + $response = Client::get($private_url); + $this->assertEquals(200, $response->statusCode); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/EntryTest.php b/php-sdk/tests/Qiniu/Tests/EntryTest.php new file mode 100644 index 0000000..73bfac4 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/EntryTest.php @@ -0,0 +1,88 @@ +assertEquals('cWluaXVwaG90b3M6Z29nb3BoZXIuanBn', $encodeEntryURI); + } + + public function testKeyEmpty() + { + $bucket = 'qiniuphotos'; + $key = ''; + $encodeEntryURI = Qiniu\entry($bucket, $key); + $this->assertEquals('cWluaXVwaG90b3M6', $encodeEntryURI); + } + + public function testKeyNull() + { + $bucket = 'qiniuphotos'; + $key = null; + $encodeEntryURI = Qiniu\entry($bucket, $key); + $this->assertEquals('cWluaXVwaG90b3M=', $encodeEntryURI); + } + + public function testKeyNeedReplacePlusSymbol() + { + $bucket = 'qiniuphotos'; + $key = '012ts>a'; + $encodeEntryURI = Qiniu\entry($bucket, $key); + $this->assertEquals('cWluaXVwaG90b3M6MDEydHM-YQ==', $encodeEntryURI); + } + + public function testKeyNeedReplaceSlashSymbol() + { + $bucket = 'qiniuphotos'; + $key = '012ts?a'; + $encodeEntryURI = Qiniu\entry($bucket, $key); + $this->assertEquals('cWluaXVwaG90b3M6MDEydHM_YQ==', $encodeEntryURI); + } + public function testDecodeEntry() + { + $entry = 'cWluaXVwaG90b3M6Z29nb3BoZXIuanBn'; + list($bucket, $key) = Qiniu\decodeEntry($entry); + $this->assertEquals('qiniuphotos', $bucket); + $this->assertEquals('gogopher.jpg', $key); + } + + public function testDecodeEntryWithEmptyKey() + { + $entry = 'cWluaXVwaG90b3M6'; + list($bucket, $key) = Qiniu\decodeEntry($entry); + $this->assertEquals('qiniuphotos', $bucket); + $this->assertEquals('', $key); + } + + public function testDecodeEntryWithNullKey() + { + $entry = 'cWluaXVwaG90b3M='; + list($bucket, $key) = Qiniu\decodeEntry($entry); + $this->assertEquals('qiniuphotos', $bucket); + $this->assertNull($key); + } + + public function testDecodeEntryWithPlusSymbol() + { + $entry = 'cWluaXVwaG90b3M6MDEydHM-YQ=='; + list($bucket, $key) = Qiniu\decodeEntry($entry); + $this->assertEquals('qiniuphotos', $bucket); + $this->assertEquals('012ts>a', $key); + } + + public function testDecodeEntryWithSlashSymbol() + { + $entry = 'cWluaXVwaG90b3M6MDEydHM_YQ=='; + list($bucket, $key) = Qiniu\decodeEntry($entry); + $this->assertEquals('qiniuphotos', $bucket); + $this->assertEquals('012ts?a', $key); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/EtagTest.php b/php-sdk/tests/Qiniu/Tests/EtagTest.php new file mode 100644 index 0000000..4e09a78 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/EtagTest.php @@ -0,0 +1,54 @@ +assertEquals('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', $r); + $this->assertNull($error); + } + + public function testLess4M() + { + $file = qiniuTempFile(3 * 1024 * 1024, false); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('Fs5BpnAjRykYTg6o5E09cjuXrDkG', $r); + $this->assertNull($error); + } + + public function test4M() + { + $file = qiniuTempFile(4 * 1024 * 1024, false); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('FiuKULnybewpEnrfTmxjsxc-3dWp', $r); + $this->assertNull($error); + } + + public function testMore4M() + { + $file = qiniuTempFile(5 * 1024 * 1024, false); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('lhvyfIWMYFTq4s4alzlhXoAkqfVL', $r); + $this->assertNull($error); + } + + public function test8M() + { + $file = qiniuTempFile(8 * 1024 * 1024, false); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('lmRm9ZfGZ86bnMys4wRTWtJj9ClG', $r); + $this->assertNull($error); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/FopTest.php b/php-sdk/tests/Qiniu/Tests/FopTest.php new file mode 100644 index 0000000..42b7997 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/FopTest.php @@ -0,0 +1,39 @@ +execute('gogopher.jpg', 'exif'); + $this->assertNull($error); + $this->assertNotNull($exif); + } + + public function testExifPrivate() + { + global $testAuth; + $fop = new Operation('private-res.qiniudn.com', $testAuth); + list($exif, $error) = $fop->execute('noexif.jpg', 'exif'); + $this->assertNotNull($error); + $this->assertNull($exif); + } + + public function testbuildUrl() + { + $fops = 'imageView2/2/h/200'; + $fop = new Operation('testres.qiniudn.com'); + $url = $fop->buildUrl('gogopher.jpg', $fops); + $this->assertEquals($url, 'http://testres.qiniudn.com/gogopher.jpg?imageView2/2/h/200'); + + $fops = array('imageView2/2/h/200', 'imageInfo'); + $url = $fop->buildUrl('gogopher.jpg', $fops); + $this->assertEquals($url, 'http://testres.qiniudn.com/gogopher.jpg?imageView2/2/h/200|imageInfo'); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/FormUpTest.php b/php-sdk/tests/Qiniu/Tests/FormUpTest.php new file mode 100644 index 0000000..f75794e --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/FormUpTest.php @@ -0,0 +1,205 @@ +batch($ops); + } + + private static function getObjectKey($key) + { + $result = $key . rand(); + self::$keysToCleanup[] = $result; + return $result; + } + + public function testData() + { + $key = self::getObjectKey('formput'); + $token = self::$auth->uploadToken(self::$bucketName); + list($ret, $error) = FormUploader::put($token, $key, 'hello world', self::$cfg, null, 'text/plain', null); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testDataWithProxy() + { + $key = self::getObjectKey('formput'); + $token = self::$auth->uploadToken(self::$bucketName); + list($ret, $error) = FormUploader::put( + $token, + $key, + 'hello world', + self::$cfg, + null, + 'text/plain', + null, + $this->makeReqOpt() + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testData2() + { + $key = self::getObjectKey('formput'); + $upManager = new UploadManager(); + $token = self::$auth->uploadToken(self::$bucketName); + list($ret, $error) = $upManager->put($token, $key, 'hello world', null, 'text/plain', null); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testData2WithProxy() + { + $key = self::getObjectKey('formput'); + $upManager = new UploadManager(); + $token = self::$auth->uploadToken(self::$bucketName); + list($ret, $error) = $upManager->put( + $token, + $key, + 'hello world', + null, + 'text/plain', + null, + $this->makeReqOpt() + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testDataFailed() + { + $key = self::getObjectKey('formput'); + $token = self::$auth->uploadToken('fakebucket'); + list($ret, $error) = FormUploader::put( + $token, + $key, + 'hello world', + self::$cfg, + null, + 'text/plain', + null + ); + $this->assertNull($ret); + $this->assertNotNull($error); + } + + public function testFile() + { + $key = self::getObjectKey('formPutFile'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + list($ret, $error) = FormUploader::putFile( + $token, + $key, + __file__, + self::$cfg, + null, + 'text/plain', + null + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testFileWithProxy() + { + $key = self::getObjectKey('formPutFile'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + list($ret, $error) = FormUploader::putFile( + $token, + $key, + __file__, + self::$cfg, + null, + 'text/plain', + $this->makeReqOpt() + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testFile2() + { + $key = self::getObjectKey('formPutFile'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $upManager = new UploadManager(); + list($ret, $error) = $upManager->putFile($token, $key, __file__, null, 'text/plain', null); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testFile2WithProxy() + { + $key = self::getObjectKey('formPutFile'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $upManager = new UploadManager(); + list($ret, $error) = $upManager->putFile( + $token, + $key, + __file__, + null, + 'text/plain', + false, + null, + 'v1', + Config::BLOCK_SIZE, + $this->makeReqOpt() + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testFileFailed() + { + $key = self::getObjectKey('fakekey'); + $token = self::$auth->uploadToken('fakebucket', $key); + list($ret, $error) = FormUploader::putFile($token, $key, __file__, self::$cfg, null, 'text/plain', null); + $this->assertNull($ret); + $this->assertNotNull($error); + } + + private function makeReqOpt() + { + $reqOpt = new RequestOptions(); + $reqOpt->proxy = 'socks5://127.0.0.1:8080'; + $reqOpt->proxy_user_password = 'user:pass'; + return $reqOpt; + } +} diff --git a/php-sdk/tests/Qiniu/Tests/HeaderTest.php b/php-sdk/tests/Qiniu/Tests/HeaderTest.php new file mode 100644 index 0000000..28af5f3 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/HeaderTest.php @@ -0,0 +1,184 @@ + array('200'), + ':x-test-1' => array('hello1'), + ':x-Test-2' => array('hello2'), + 'content-type' => array('application/json'), + 'CONTENT-LENGTH' => array(1234), + 'oRiGin' => array('https://www.qiniu.com'), + 'ReFer' => array('www.qiniu.com'), + 'Last-Modified' => array('Mon, 06 Sep 2021 06:44:52 GMT'), + 'acCePt-ChArsEt' => array('utf-8'), + 'x-test-3' => array('hello3'), + 'cache-control' => array('no-cache', 'no-store'), + ); + + public function testNormalizeKey() + { + $except = array( + ':status', + ':x-test-1', + ':x-Test-2', + 'Content-Type', + 'Content-Length', + 'Origin', + 'Refer', + 'Last-Modified', + 'Accept-Charset', + 'X-Test-3', + 'Cache-Control' + ); + $actual = array_map(function ($str) { + return Header::normalizeKey($str); + }, array_keys($this->heads)); + $this->assertEquals($actual, $except); + } + + + public function testInvalidKeyName() + { + $except = array( + 'a:x-test-1', + ); + + $actual = array_map(function ($str) { + return Header::normalizeKey($str); + }, $except); + + $this->assertEquals($except, $actual); + } + + public function testGetRawData() + { + $header = new Header($this->heads); + foreach ($this->heads as $k => $v) { + $rawHeader = $header->getRawData(); + $this->assertEquals($v, $rawHeader[Header::normalizeKey($k)]); + } + } + + public function testOffsetExists() + { + $header = new Header($this->heads); + foreach (array_keys($this->heads) as $k) { + $this->assertNotNull($header[$k]); + } + + $except = array( + ':status', + ':x-test-1', + ':x-Test-2', + 'Content-Type', + 'Content-Length', + 'Origin', + 'Refer', + 'Last-Modified', + 'Accept-Charset', + 'X-Test-3', + 'Cache-Control' + ); + foreach ($except as $k) { + $this->assertNotNull($header[$k], $k." is null"); + } + } + + public function testOffsetGet() + { + $header = new Header($this->heads); + foreach ($this->heads as $k => $v) { + $this->assertEquals($v[0], $header[$k]); + } + + $this->assertNull($header['no-exist']); + } + + public function testOffsetSet() + { + $header = new Header($this->heads); + $header["X-Test-3"] = "hello"; + $this->assertEquals("hello", $header["X-Test-3"]); + $header["x-test-3"] = "hello test3"; + $this->assertEquals("hello test3", $header["x-test-3"]); + $header[":x-Test-2"] = "hello"; + $this->assertEquals("hello", $header[":x-Test-2"]); + $header[":x-test-2"] = "hello test2"; + $this->assertEquals("hello", $header[":x-Test-2"]); + } + + public function testOffsetUnset() + { + $header = new Header($this->heads); + unset($header["X-Test-3"]); + $this->assertFalse(isset($header["X-Test-3"])); + + $header = new Header($this->heads); + unset($header["x-test-3"]); + $this->assertFalse(isset($header["x-test-3"])); + + $header = new Header($this->heads); + unset($header[":x-test-2"]); + $this->assertTrue(isset($header[":x-Test-2"])); + + $header = new Header($this->heads); + unset($header[":x-Test-2"]); + $this->assertFalse(isset($header[":x-Test-2"])); + } + + public function testGetIterator() + { + $header = new Header($this->heads); + + $hasException = false; + try { + foreach ($header as $k => $v) { + $hasException = !isset($header[$k]); + } + } catch (\Exception $e) { + $hasException = true; + } + $this->assertFalse($hasException); + } + + public function testEmptyHeaderIterator() + { + $emptyHeader = new Header(); + + $hasException = false; + try { + foreach ($emptyHeader as $k => $v) { + $hasException = !isset($header[$k]); + } + } catch (\Exception $e) { + $hasException = true; + } + $this->assertFalse($hasException); + } + + public function testCount() + { + $header = new Header($this->heads); + + $this->assertEquals(count($this->heads), count($header)); + } + + public function testFromRaw() + { + $lines = array(); + foreach ($this->heads as $k => $vs) { + foreach ($vs as $v) { + array_push($lines, $k . ": " . $v); + } + } + $raw = implode("\r\n", $lines); + $headerFromRaw = Header::fromRawText($raw); + $this->assertEquals(new Header($this->heads), $headerFromRaw); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/HttpTest.php b/php-sdk/tests/Qiniu/Tests/HttpTest.php new file mode 100644 index 0000000..c122f8e --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/HttpTest.php @@ -0,0 +1,163 @@ +assertEquals(200, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNull($response->error); + } + + public function testGetQiniu() + { + $response = Client::get('upload.qiniu.com'); + $this->assertEquals(405, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->xReqId()); + $this->assertNotNull($response->xLog()); + $this->assertNotNull($response->error); + } + + public function testGetTimeout() + { + $reqOpt = new RequestOptions(); + $reqOpt->timeout = 1; + $response = Client::get('localhost:9000/timeout.php', array(), $reqOpt); + $this->assertEquals(-1, $response->statusCode); + } + + public function testGetRedirect() + { + $response = Client::get('localhost:9000/redirect.php'); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals('application/json;charset=UTF-8', $response->normalizedHeaders['Content-Type']); + $respData = $response->json(); + $this->assertEquals('ok', $respData['msg']); + } + + public function testDelete() + { + $response = Client::delete('uc.qbox.me/bucketTagging', array()); + $this->assertEquals(401, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->error); + } + + public function testDeleteQiniu() + { + $response = Client::delete('uc.qbox.me/bucketTagging', array()); + $this->assertEquals(401, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->xReqId()); + $this->assertNotNull($response->xLog()); + $this->assertNotNull($response->error); + } + + public function testDeleteTimeout() + { + $reqOpt = new RequestOptions(); + $reqOpt->timeout = 1; + $response = Client::delete('localhost:9000/timeout.php', array(), $reqOpt); + $this->assertEquals(-1, $response->statusCode); + } + + + public function testPost() + { + $response = Client::post('qiniu.com', null); + $this->assertEquals(200, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNull($response->error); + } + + public function testPostQiniu() + { + $response = Client::post('upload.qiniu.com', null); + $this->assertEquals(400, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->xReqId()); + $this->assertNotNull($response->xLog()); + $this->assertNotNull($response->error); + } + + public function testPostTimeout() + { + $reqOpt = new RequestOptions(); + $reqOpt->timeout = 1; + $response = Client::post('localhost:9000/timeout.php', null, array(), $reqOpt); + $this->assertEquals(-1, $response->statusCode); + } + + public function testSocks5Proxy() + { + $reqOpt = new RequestOptions(); + $reqOpt->proxy = 'socks5://localhost:8080'; + $response = Client::post('qiniu.com', null, array(), $reqOpt); + $this->assertEquals(-1, $response->statusCode); + + $reqOpt->proxy_user_password = 'user:pass'; + $response = Client::post('qiniu.com', null, array(), $reqOpt); + $this->assertEquals(200, $response->statusCode); + } + + public function testPut() + { + $response = Client::PUT('uc.qbox.me/bucketTagging', null); + $this->assertEquals(401, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->error); + } + + public function testPutQiniu() + { + $response = Client::put('uc.qbox.me/bucketTagging', null); + $this->assertEquals(401, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->xReqId()); + $this->assertNotNull($response->xLog()); + $this->assertNotNull($response->error); + } + + + public function testPutTimeout() + { + $reqOpt = new RequestOptions(); + $reqOpt->timeout = 1; + $response = Client::put('localhost:9000/timeout.php', null, array(), $reqOpt); + $this->assertEquals(-1, $response->statusCode); + } + + public function testNeedRetry() + { + $testCases = array_merge( + array(array(-1, true)), + array_map(function ($i) { + return array($i, false); + }, range(100, 499)), + array_map(function ($i) { + if (in_array($i, array( + 501, 509, 573, 579, 608, 612, 614, 616, 618, 630, 631, 632, 640, 701 + ))) { + return array($i, false); + } + return array($i, true); + }, range(500, 799)) + ); + $resp = new Response(-1, 222, array(), '{"msg": "mock"}', null); + foreach ($testCases as $testCase) { + list($code, $expectNeedRetry) = $testCase; + $resp->statusCode = $code; + $msg = $resp->statusCode . ' need' . ($expectNeedRetry ? '' : ' NOT') . ' retry'; + $this->assertEquals($expectNeedRetry, $resp->needRetry(), $msg); + } + } +} diff --git a/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php b/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php new file mode 100644 index 0000000..486323c --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php @@ -0,0 +1,263 @@ + + */ +class ImageUrlBuilderTest extends TestCase +{ + /** + * 缩略图测试 + * + * @test + * @return void + * @author Sherlock Ren + */ + public function testThumbutl() + { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder(); + $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg'; + $url2 = $url . '?watermark/1/gravity/SouthEast/dx/0/dy/0/image/' + . 'aHR0cDovL2Fkcy1jZG4uY2h1Y2h1amllLmNvbS9Ga1R6bnpIY2RLdmRBUFc5cHZZZ3pTc21UY0tB'; + // 异常测试 + $this->assertEquals($url, $imageUrlBuilder->thumbnail($url, 1, 0, 0)); + $this->assertEquals($url, \Qiniu\thumbnail($url, 1, 0, 0)); + + // 简单缩略测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200) + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200) + ); + + // 输出格式测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png') + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200, 'png') + ); + + // 渐进显示测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png', 1) + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200, 'png', 2) + ); + + // 图片质量测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/q/80/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png', 1, 80) + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200, 'png', 1, 101) + ); + + // 多参数测试 + $this->assertEquals( + $url2 . '|imageView2/1/w/200/h/200/ignore-error/1/', + $imageUrlBuilder->thumbnail($url2, 1, 200, 200) + ); + $this->assertEquals( + $url2 . '|imageView2/1/w/200/h/200/ignore-error/1/', + \Qiniu\thumbnail($url2, 1, 200, 200) + ); + } + + /** + * 图片水印测试 + * + * @test + * @param void + * @return void + * @author Sherlock Ren + */ + public function waterImgTest() + { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder(); + $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg'; + $url2 = $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/'; + $image = 'http://developer.qiniu.com/resource/logo-2.jpg'; + + // 水印简单测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterImg($url, $image) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/gravity/SouthEast/', + $imageUrlBuilder->waterImg($url, $image, 101) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==/', + $imageUrlBuilder->waterImg($url, $image, 101, 'sdfsd') + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image) + ); + + // 横轴边距测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/dx/10/', + $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad') + ); + + // 纵轴边距测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/dx/10/dy/10/', + $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10, 10) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad', 'asdf') + ); + + // 自适应原图的短边比例测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/dx/10/dy/10/ws/0.5/', + $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10, 10, 0.5) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad', 'asdf', 2) + ); + + // 多参数测试 + $this->assertEquals( + $url2 . '|watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterImg($url2, $image) + ); + $this->assertEquals( + $url2 . '|watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url2, $image) + ); + } + + /** + * 文字水印测试 + * + * @test + * @param void + * @return void + * @author Sherlock Ren + */ + public function waterTextTest() + { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder(); + $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg'; + $url2 = $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/'; + $text = '测试一下'; + $font = '微软雅黑'; + $fontColor = '#FF0000'; + + // 水印简单测试 + $this->assertEquals($url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'fontsize/500/dissolve/100/gravity/SouthEast/', $imageUrlBuilder->waterText($url, $text, $font, 500)); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'dissolve/100/gravity/SouthEast/', + \Qiniu\waterText($url, $text, $font, 'sdf') + ); + + // 字体颜色测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/' + . 'I0ZGMDAwMA==/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor) + ); + + // 透明度测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/SouthEast/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==' + . '/gravity/SouthEast/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101) + ); + + // 水印位置测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/East/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East') + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf') + ); + + // 横轴距离测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/East/dx/10/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East', 10) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf', 'sdfs') + ); + + // 纵轴距离测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/East/dx/10/dy/10/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East', 10, 10) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf', 'sdfs', 'ssdf') + ); + // 多参数测试 + $this->assertEquals( + $url2 . '|watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'fontsize/500/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterText($url2, $text, $font, 500) + ); + $this->assertEquals( + $url2 . '|watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'fontsize/500/dissolve/100/gravity/SouthEast/', + \Qiniu\waterText($url2, $text, $font, 500) + ); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/MiddlewareTest.php b/php-sdk/tests/Qiniu/Tests/MiddlewareTest.php new file mode 100644 index 0000000..969cad4 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/MiddlewareTest.php @@ -0,0 +1,160 @@ + + */ + private $orderRecorder; + + /** + * @var string + */ + private $label; + + public function __construct(&$orderRecorder, $label) + { + $this->orderRecorder =& $orderRecorder; + $this->label = $label; + } + + public function send($request, $next) + { + $this->orderRecorder[] = "bef_" . $this->label . count($this->orderRecorder); + $response = $next($request); + $this->orderRecorder[] = "aft_" . $this->label . count($this->orderRecorder); + return $response; + } +} + +class MiddlewareTest extends TestCase +{ + public function testSendWithMiddleware() + { + $orderRecorder = array(); + + $reqOpt = new RequestOptions(); + $reqOpt->middlewares = array( + new RecorderMiddleware($orderRecorder, "A"), + new RecorderMiddleware($orderRecorder, "B") + ); + + $request = new Request( + "GET", + "http://localhost:9000/ok.php", + array(), + null, + $reqOpt + ); + $response = Client::sendRequestWithMiddleware($request); + + $expectRecords = array( + "bef_A0", + "bef_B1", + "aft_B2", + "aft_A3" + ); + + $this->assertEquals($expectRecords, $orderRecorder); + $this->assertEquals(200, $response->statusCode); + } + + public function testSendWithRetryDomains() + { + $orderRecorder = array(); + + $reqOpt = new RequestOptions(); + $reqOpt->middlewares = array( + new Middleware\RetryDomainsMiddleware( + array( + "unavailable.phpsdk.qiniu.com", + "localhost:9000", + ), + 3 + ), + new RecorderMiddleware($orderRecorder, "rec") + ); + + $request = new Request( + "GET", + "http://fake.phpsdk.qiniu.com/ok.php", + array(), + null, + $reqOpt + ); + $response = Client::sendRequestWithMiddleware($request); + + $expectRecords = array( + // 'fake.phpsdk.qiniu.com' with retried 3 times + 'bef_rec0', + 'aft_rec1', + 'bef_rec2', + 'aft_rec3', + 'bef_rec4', + 'aft_rec5', + + // 'unavailable.pysdk.qiniu.com' with retried 3 times + 'bef_rec6', + 'aft_rec7', + 'bef_rec8', + 'aft_rec9', + 'bef_rec10', + 'aft_rec11', + + // 'qiniu.com' and it's success + 'bef_rec12', + 'aft_rec13' + ); + + $this->assertEquals($expectRecords, $orderRecorder); + $this->assertEquals(200, $response->statusCode); + } + + public function testSendFailFastWithRetryDomains() + { + $orderRecorder = array(); + + $reqOpt = new RequestOptions(); + $reqOpt->middlewares = array( + new Middleware\RetryDomainsMiddleware( + array( + "unavailable.phpsdk.qiniu.com", + "localhost:9000", + ), + 3, + function () { + return false; + } + ), + new RecorderMiddleware($orderRecorder, "rec") + ); + + $request = new Request( + "GET", + "http://fake.phpsdk.qiniu.com/ok.php", + array(), + null, + $reqOpt + ); + $response = Client::sendRequestWithMiddleware($request); + + $expectRecords = array( + // 'fake.phpsdk.qiniu.com' will fail fast + 'bef_rec0', + 'aft_rec1' + ); + $this->assertEquals($expectRecords, $orderRecorder); + $this->assertEquals(-1, $response->statusCode); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/PfopTest.php b/php-sdk/tests/Qiniu/Tests/PfopTest.php new file mode 100644 index 0000000..77d06ec --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/PfopTest.php @@ -0,0 +1,304 @@ +execute($bucket, $key, $fops); + $this->assertNull($error); + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + } + + + public function testPfopExecuteAndStatusWithMultipleFops() + { + global $testAuth; + $bucket = 'testres'; + $key = 'sintel_trailer.mp4'; + $fops = array( + 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240', + 'vframe/jpg/offset/7/w/480/h/360', + ); + $pfop = new PersistentFop($testAuth, self::getConfig()); + + list($id, $error) = $pfop->execute($bucket, $key, $fops); + $this->assertNull($error); + + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + } + + private function pfopOptionsTestData() + { + return array( + array( + 'type' => null + ), + array( + 'type' => -1 + ), + array( + 'type' => 0 + ), + array( + 'type' => 1 + ), + array( + 'type' => 2 + ), + array( + 'workflowTemplateID' => 'test-workflow' + ) + ); + } + + public function testPfopExecuteWithOptions() + { + $bucket = self::$bucketName; + $key = 'qiniu.png'; + $pfop = new PersistentFop(self::$testAuth, self::getConfig()); + + $testCases = $this->pfopOptionsTestData(); + + foreach ($testCases as $testCase) { + $workflowTemplateID = null; + $type = null; + + if (array_key_exists('workflowTemplateID', $testCase)) { + $workflowTemplateID = $testCase['workflowTemplateID']; + } + if (array_key_exists('type', $testCase)) { + $type = $testCase['type']; + } + + if ($workflowTemplateID) { + $fops = null; + } else { + $persistentEntry = \Qiniu\entry( + $bucket, + implode( + '_', + array( + 'test-pfop/test-pfop-by-api', + 'type', + $type + ) + ) + ); + $fops = 'avinfo|saveas/' . $persistentEntry; + } + list($id, $error) = $pfop->execute( + $bucket, + $key, + $fops, + null, + null, + false, + $type, + $workflowTemplateID + ); + + if (in_array($type, array(null, 0, 1))) { + $this->assertNull($error); + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + if ($type == 1) { + $this->assertEquals(1, $status['type']); + } + if ($workflowTemplateID) { + // assertStringContainsString when PHPUnit >= 8.0 + $this->assertTrue( + strpos( + $status['taskFrom'], + $workflowTemplateID + ) !== false + ); + } + $this->assertNotEmpty($status['creationDate']); + } else { + $this->assertNotNull($error); + } + } + } + + public function testPfopWithInvalidArgument() + { + $bucket = self::$bucketName; + $key = 'qiniu.png'; + $pfop = new PersistentFop(self::$testAuth, self::getConfig()); + $err = null; + try { + $pfop->execute( + $bucket, + $key + ); + } catch (\Exception $e) { + $err = $e; + } + + $this->assertNotEmpty($err); + $this->assertTrue( + strpos( + $err->getMessage(), + 'Must provide one of fops or template_id' + ) !== false + ); + } + + public function testPfopWithUploadPolicy() + { + $bucket = self::$bucketName; + $testAuth = self::$testAuth; + $key = 'test-pfop/upload-file'; + + $testCases = $this->pfopOptionsTestData(); + + foreach ($testCases as $testCase) { + $workflowTemplateID = null; + $type = null; + + if (array_key_exists('workflowTemplateID', $testCase)) { + $workflowTemplateID = $testCase['workflowTemplateID']; + } + if (array_key_exists('type', $testCase)) { + $type = $testCase['type']; + } + + $putPolicy = array( + 'persistentType' => $type + ); + if ($workflowTemplateID) { + $putPolicy['persistentWorkflowTemplateID'] = $workflowTemplateID; + } else { + $persistentEntry = \Qiniu\entry( + $bucket, + implode( + '_', + array( + 'test-pfop/test-pfop-by-upload', + 'type', + $type + ) + ) + ); + $putPolicy['persistentOps'] = 'avinfo|saveas/' . $persistentEntry; + } + + if ($type == null) { + unset($putPolicy['persistentType']); + } + + $token = $testAuth->uploadToken( + $bucket, + $key, + 3600, + $putPolicy + ); + $upManager = new UploadManager(self::getConfig()); + list($ret, $error) = $upManager->putFile( + $token, + $key, + __file__, + null, + 'text/plain', + true + ); + + if (in_array($type, array(null, 0, 1))) { + $this->assertNull($error); + $this->assertNotEmpty($ret['persistentId']); + $id = $ret['persistentId']; + } else { + $this->assertNotNull($error); + return; + } + + $pfop = new PersistentFop($testAuth, self::getConfig()); + list($status, $error) = $pfop->status($id); + + $this->assertNotNull($status); + $this->assertNull($error); + if ($type == 1) { + $this->assertEquals(1, $status['type']); + } + if ($workflowTemplateID) { + // assertStringContainsString when PHPUnit >= 8.0 + $this->assertTrue( + strpos( + $status['taskFrom'], + $workflowTemplateID + ) !== false + ); + } + $this->assertNotEmpty($status['creationDate']); + } + } + + public function testMkzip() + { + $bucket = self::$bucketName; + $key = 'php-logo.png'; + $pfop = new PersistentFop(self::$testAuth, null); + + $url1 = 'http://phpsdk.qiniudn.com/php-logo.png'; + $url2 = 'http://phpsdk.qiniudn.com/php-sdk.html'; + $zipKey = 'test.zip'; + + $fops = 'mkzip/2/url/' . \Qiniu\base64_urlSafeEncode($url1); + $fops .= '/url/' . \Qiniu\base64_urlSafeEncode($url2); + $fops .= '|saveas/' . \Qiniu\base64_urlSafeEncode("$bucket:$zipKey"); + + list($id, $error) = $pfop->execute($bucket, $key, $fops); + $this->assertNull($error); + + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + } +} diff --git a/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php b/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php new file mode 100644 index 0000000..6feee55 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php @@ -0,0 +1,354 @@ +batch($ops); + } + + private static function getObjectKey($key) + { + $result = $key . rand(); + self::$keysToCleanup[] = $result; + return $result; + } + + public function test4ML() + { + $key = self::getObjectKey('resumePutFile4ML_'); + $upManager = new UploadManager(); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + null, + 'application/octet-stream', + false, + $resumeFile + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + unlink($tempFile); + } + + public function test4ML2() + { + $key = self::getObjectKey('resumePutFile4ML_'); + $cfg = new Config(); + $upManager = new UploadManager($cfg); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + null, + 'application/octet-stream', + false, + $resumeFile + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + unlink($tempFile); + } + + public function test4ML2WithProxy() + { + $key = self::getObjectKey('resumePutFile4ML_'); + $cfg = new Config(); + $upManager = new UploadManager($cfg); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + null, + 'application/octet-stream', + false, + $resumeFile, + 'v2', + Config::BLOCK_SIZE, + $this->makeReqOpt() + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + unlink($tempFile); + } + + // public function test8M() + // { + // $key = 'resumePutFile8M'; + // $upManager = new UploadManager(); + // $token = self::$auth->uploadToken(self::$bucketName, $key); + // $tempFile = qiniuTempFile(8*1024*1024+10); + // list($ret, $error) = $upManager->putFile($token, $key, $tempFile); + // $this->assertNull($error); + // $this->assertNotNull($ret['hash']); + // unlink($tempFile); + // } + + public function testFileWithFileType() + { + $config = new Config(); + $bucketManager = new BucketManager(self::$auth, $config); + + $testCases = array( + array( + "fileType" => 1, + "name" => "IA" + ), + array( + "fileType" => 2, + "name" => "Archive" + ), + array( + "fileType" => 3, + "name" => "DeepArchive" + ) + ); + + foreach ($testCases as $testCase) { + $key = self::getObjectKey('FileType' . $testCase["name"]); + $police = array( + "fileType" => $testCase["fileType"], + ); + $token = self::$auth->uploadToken(self::$bucketName, $key, 3600, $police); + $upManager = new UploadManager(); + list($ret, $error) = $upManager->putFile($token, $key, __file__, null, 'text/plain'); + $this->assertNull($error); + $this->assertNotNull($ret); + list($ret, $err) = $bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + $this->assertEquals($testCase["fileType"], $ret["type"]); + } + } + + public function testResumeUploadWithParams() + { + $key = self::getObjectKey('resumePutFile4ML_'); + $upManager = new UploadManager(); + $policy = array('returnBody' => '{"hash":$(etag),"fname":$(fname),"var_1":$(x:var_1),"var_2":$(x:var_2)}'); + $token = self::$auth->uploadToken(self::$bucketName, $key, 3600, $policy); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + array("x:var_1" => "val_1", "x:var_2" => "val_2", "x-qn-meta-m1" => "val_1", "x-qn-meta-m2" => "val_2"), + 'application/octet-stream', + false, + $resumeFile + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + $this->assertEquals("val_1", $ret['var_1']); + $this->assertEquals("val_2", $ret['var_2']); + $this->assertEquals(basename($tempFile), $ret['fname']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + $headers = $response->headers(); + $this->assertEquals("val_1", $headers["X-Qn-Meta-M1"]); + $this->assertEquals("val_2", $headers["X-Qn-Meta-M2"]); + unlink($tempFile); + } + + public function testResumeUploadV2() + { + $cfg = new Config(); + $upManager = new UploadManager($cfg); + $testFileSize = array( + config::BLOCK_SIZE / 2, + config::BLOCK_SIZE, + config::BLOCK_SIZE + 10, + config::BLOCK_SIZE * 2, + config::BLOCK_SIZE * 2.5 + ); + $partSize = 5 * 1024 * 1024; + foreach ($testFileSize as $item) { + $key = self::getObjectKey('resumePutFile4ML_'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $tempFile = qiniuTempFile($item); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + null, + 'application/octet-stream', + false, + $resumeFile, + 'v2', + $partSize + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + unlink($tempFile); + } + } + + public function testResumeUploadV2WithParams() + { + $key = self::getObjectKey('resumePutFile4ML_'); + $upManager = new UploadManager(); + $policy = array('returnBody' => '{"hash":$(etag),"fname":$(fname),"var_1":$(x:var_1),"var_2":$(x:var_2)}'); + $token = self::$auth->uploadToken(self::$bucketName, $key, 3600, $policy); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + array("x:var_1" => "val_1", "x:var_2" => "val_2", "x-qn-meta-m1" => "val_1", "x-qn-meta-m2" => "val_2"), + 'application/octet-stream', + false, + $resumeFile, + 'v2' + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + $this->assertEquals("val_1", $ret['var_1']); + $this->assertEquals("val_2", $ret['var_2']); + $this->assertEquals(basename($tempFile), $ret['fname']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + $headers = $response->headers(); + $this->assertEquals("val_1", $headers["X-Qn-Meta-M1"]); + $this->assertEquals("val_2", $headers["X-Qn-Meta-M2"]); + unlink($tempFile); + } + + // valid versions are tested above + // Use PHPUnit's Data Provider to test multiple Exception is better, + // but not match the test style of this project + public function testResumeUploadWithInvalidVersion() + { + $cfg = new Config(); + $upManager = new UploadManager($cfg); + $testFileSize = config::BLOCK_SIZE * 2; + $partSize = 5 * 1024 * 1024; + $testInvalidVersions = array( + // High probability invalid versions + 'v', + '1', + '2' + ); + + $expectExceptionCount = 0; + foreach ($testInvalidVersions as $invalidVersion) { + $key = self::getObjectKey('resumePutFile4ML_'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $tempFile = qiniuTempFile($testFileSize); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + try { + $upManager->putFile( + $token, + $key, + $tempFile, + null, + 'application/octet-stream', + false, + $resumeFile, + $invalidVersion, + $partSize + ); + } catch (\Exception $e) { + $isRightException = false; + $expectExceptionCount++; + while ($e) { + $isRightException = $e instanceof \UnexpectedValueException; + if ($isRightException) { + break; + } + $e = $e->getPrevious(); + } + $this->assertTrue($isRightException); + } + + unlink($tempFile); + } + $this->assertEquals(count($testInvalidVersions), $expectExceptionCount); + } + + private function makeReqOpt() + { + $reqOpt = new RequestOptions(); + $reqOpt->proxy = 'socks5://127.0.0.1:8080'; + $reqOpt->proxy_user_password = 'user:pass'; + return $reqOpt; + } +} diff --git a/php-sdk/tests/Qiniu/Tests/ZoneTest.php b/php-sdk/tests/Qiniu/Tests/ZoneTest.php new file mode 100644 index 0000000..fbab528 --- /dev/null +++ b/php-sdk/tests/Qiniu/Tests/ZoneTest.php @@ -0,0 +1,136 @@ +bucketName = $bucketName; + + global $bucketNameBC; + $this->bucketNameBC = $bucketNameBC; + + global $bucketNameNA; + $this->bucketNameNA = $bucketNameNA; + + global $bucketNameFS; + $this->bucketNameFS = $bucketNameFS; + + global $bucketNameAS; + $this->bucketNameAS = $bucketNameAS; + + global $accessKey; + $this->ak = $accessKey; + + $this->zone = new Zone(); + $this->zoneHttps = new Zone('https'); + } + + public function testUpHosts() + { + list($ret, $err) = Zone::queryZone($this->ak, 'fakebucket'); + $this->assertNull($ret); + $this->assertNotNull($err); + + $zone = Zone::queryZone($this->ak, $this->bucketName); + $this->assertContains('upload.qiniup.com', $zone->cdnUpHosts); + + $zone = Zone::queryZone($this->ak, $this->bucketNameBC); + $this->assertContains('upload-z1.qiniup.com', $zone->cdnUpHosts); + + $zone = Zone::queryZone($this->ak, $this->bucketNameFS); + $this->assertContains('upload-z2.qiniup.com', $zone->cdnUpHosts); + + $zone = Zone::queryZone($this->ak, $this->bucketNameNA); + $this->assertContains('upload-na0.qiniup.com', $zone->cdnUpHosts); + + $zone = Zone::queryZone($this->ak, $this->bucketNameAS); + $this->assertContains('upload-as0.qiniup.com', $zone->cdnUpHosts); + } + + public function testIoHosts() + { + $zone = Zone::queryZone($this->ak, $this->bucketName); + $this->assertEquals($zone->iovipHost, 'iovip.qbox.me'); + + $zone = Zone::queryZone($this->ak, $this->bucketNameBC); + $this->assertEquals($zone->iovipHost, 'iovip-z1.qbox.me'); + + $zone = Zone::queryZone($this->ak, $this->bucketNameFS); + $this->assertEquals($zone->iovipHost, 'iovip-z2.qbox.me'); + + $zone = Zone::queryZone($this->ak, $this->bucketNameNA); + $this->assertEquals($zone->iovipHost, 'iovip-na0.qbox.me'); + + $zone = Zone::queryZone($this->ak, $this->bucketNameAS); + $this->assertEquals($zone->iovipHost, 'iovip-as0.qbox.me'); + } + + public function testZonez0() + { + $zone = Zone::zonez0(); + $this->assertContains('upload.qiniup.com', $zone->cdnUpHosts); + } + + public function testZonez1() + { + $zone = Zone::zonez1(); + $this->assertContains('upload-z1.qiniup.com', $zone->cdnUpHosts); + } + + public function testZonez2() + { + $zone = Zone::zonez2(); + $this->assertContains('upload-z2.qiniup.com', $zone->cdnUpHosts); + } + + public function testZoneCnEast2() + { + $zone = Zone::zoneCnEast2(); + $this->assertContains('upload-cn-east-2.qiniup.com', $zone->cdnUpHosts); + } + + public function testZoneNa0() + { + $zone = Zone::zoneNa0(); + $this->assertContains('upload-na0.qiniup.com', $zone->cdnUpHosts); + } + + public function testZoneAs0() + { + $zone = Zone::zoneAs0(); + $this->assertContains('upload-as0.qiniup.com', $zone->cdnUpHosts); + } + + public function testQvmZonez0() + { + $zone = Zone::qvmZonez0(); + $this->assertContains('free-qvm-z0-xs.qiniup.com', $zone->srcUpHosts); + } + + public function testQvmZonez1() + { + $zone = Zone::qvmZonez1(); + $this->assertContains('free-qvm-z1-zz.qiniup.com', $zone->srcUpHosts); + } +} diff --git a/php-sdk/tests/bootstrap.php b/php-sdk/tests/bootstrap.php new file mode 100644 index 0000000..9859a81 --- /dev/null +++ b/php-sdk/tests/bootstrap.php @@ -0,0 +1,61 @@ + 0) { + $length = min($rest_size, 4 * 1024); + if (fwrite($file, random_bytes($length)) == false) { + return false; + } + $rest_size -= $length; + } + } else if ($size > 0) { + fseek($file, $size - 1); + fwrite($file, ' '); + } + fclose($file); + return $fileName; +} diff --git a/php-sdk/tests/mock-server/ok.php b/php-sdk/tests/mock-server/ok.php new file mode 100644 index 0000000..5b0a65d --- /dev/null +++ b/php-sdk/tests/mock-server/ok.php @@ -0,0 +1,3 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + private $vendorDir; + + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + private static $registeredLoaders = array(); + + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + } + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..2dcfa10 --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,317 @@ + + array ( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => NULL, + 'name' => '__root__', + ), + 'versions' => + array ( + '__root__' => + array ( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => NULL, + ), + 'myclabs/php-enum' => + array ( + 'pretty_version' => '1.8.5', + 'version' => '1.8.5.0', + 'aliases' => + array ( + ), + 'reference' => 'e7be26966b7398204a234f8673fdad5ac6277802', + ), + 'qiniu/php-sdk' => + array ( + 'pretty_version' => 'v7.14.0', + 'version' => '7.14.0.0', + 'aliases' => + array ( + ), + 'reference' => 'ee752ffa7263ce99fca0bd7340cf13c486a3516c', + ), + ), +); +private static $canGetVendors; +private static $installedByVendor = array(); + + + + + + + +public static function getInstalledPackages() +{ +$packages = array(); +foreach (self::getInstalled() as $installed) { +$packages[] = array_keys($installed['versions']); +} + +if (1 === \count($packages)) { +return $packages[0]; +} + +return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); +} + + + + + + + + + +public static function isInstalled($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (isset($installed['versions'][$packageName])) { +return true; +} +} + +return false; +} + + + + + + + + + + + + + + +public static function satisfies(VersionParser $parser, $packageName, $constraint) +{ +$constraint = $parser->parseConstraints($constraint); +$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + +return $provided->matches($constraint); +} + + + + + + + + + + +public static function getVersionRanges($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +$ranges = array(); +if (isset($installed['versions'][$packageName]['pretty_version'])) { +$ranges[] = $installed['versions'][$packageName]['pretty_version']; +} +if (array_key_exists('aliases', $installed['versions'][$packageName])) { +$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); +} +if (array_key_exists('replaced', $installed['versions'][$packageName])) { +$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); +} +if (array_key_exists('provided', $installed['versions'][$packageName])) { +$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); +} + +return implode(' || ', $ranges); +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getVersion($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +if (!isset($installed['versions'][$packageName]['version'])) { +return null; +} + +return $installed['versions'][$packageName]['version']; +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getPrettyVersion($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +if (!isset($installed['versions'][$packageName]['pretty_version'])) { +return null; +} + +return $installed['versions'][$packageName]['pretty_version']; +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getReference($packageName) +{ +foreach (self::getInstalled() as $installed) { +if (!isset($installed['versions'][$packageName])) { +continue; +} + +if (!isset($installed['versions'][$packageName]['reference'])) { +return null; +} + +return $installed['versions'][$packageName]['reference']; +} + +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + + + + + +public static function getRootPackage() +{ +$installed = self::getInstalled(); + +return $installed[0]['root']; +} + + + + + + + + +public static function getRawData() +{ +@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + +return self::$installed; +} + + + + + + + +public static function getAllRawData() +{ +return self::getInstalled(); +} + + + + + + + + + + + + + + + + + + + +public static function reload($data) +{ +self::$installed = $data; +self::$installedByVendor = array(); +} + + + + + +private static function getInstalled() +{ +if (null === self::$canGetVendors) { +self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); +} + +$installed = array(); + +if (self::$canGetVendors) { +foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { +if (isset(self::$installedByVendor[$vendorDir])) { +$installed[] = self::$installedByVendor[$vendorDir]; +} elseif (is_file($vendorDir.'/composer/installed.php')) { +$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; +} +} +} + +$installed[] = self::$installed; + +return $installed; +} +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..bb58823 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,11 @@ + $vendorDir . '/composer/InstalledVersions.php', + 'Stringable' => $vendorDir . '/myclabs/php-enum/stubs/Stringable.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..8402a80 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,11 @@ + $vendorDir . '/qiniu/php-sdk/src/Qiniu/functions.php', + '5dd19d8a547b7318af0c3a93c8bd6565' => $vendorDir . '/qiniu/php-sdk/src/Qiniu/Http/Middleware/Middleware.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/qiniu/php-sdk/src/Qiniu'), + 'MyCLabs\\Enum\\' => array($vendorDir . '/myclabs/php-enum/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..70531e2 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,75 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit33806c1377e553ceececb4a264f74f6f::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit33806c1377e553ceececb4a264f74f6f::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire33806c1377e553ceececb4a264f74f6f($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire33806c1377e553ceececb4a264f74f6f($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..75011ef --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,50 @@ + __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu/functions.php', + '5dd19d8a547b7318af0c3a93c8bd6565' => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu/Http/Middleware/Middleware.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'Q' => + array ( + 'Qiniu\\' => 6, + ), + 'M' => + array ( + 'MyCLabs\\Enum\\' => 13, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Qiniu\\' => + array ( + 0 => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu', + ), + 'MyCLabs\\Enum\\' => + array ( + 0 => __DIR__ . '/..' . '/myclabs/php-enum/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Stringable' => __DIR__ . '/..' . '/myclabs/php-enum/stubs/Stringable.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit33806c1377e553ceececb4a264f74f6f::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit33806c1377e553ceececb4a264f74f6f::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit33806c1377e553ceececb4a264f74f6f::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..0e6edfc --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,135 @@ +{ + "packages": [ + { + "name": "myclabs/php-enum", + "version": "1.8.5", + "version_normalized": "1.8.5.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/e7be26966b7398204a234f8673fdad5ac6277802", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2 || ^5.2" + }, + "time": "2025-01-14T11:49:03+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "https://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "install-path": "../myclabs/php-enum" + }, + { + "name": "qiniu/php-sdk", + "version": "v7.14.0", + "version_normalized": "7.14.0.0", + "source": { + "type": "git", + "url": "https://github.com/qiniu/php-sdk.git", + "reference": "ee752ffa7263ce99fca0bd7340cf13c486a3516c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/qiniu/php-sdk/zipball/ee752ffa7263ce99fca0bd7340cf13c486a3516c", + "reference": "ee752ffa7263ce99fca0bd7340cf13c486a3516c", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-xml": "*", + "myclabs/php-enum": "~1.5.2 || ~1.6.6 || ~1.7.7 || ~1.8.4", + "php": ">=5.3.3" + }, + "require-dev": { + "paragonie/random_compat": ">=2", + "phpunit/phpunit": "^4.8 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4", + "squizlabs/php_codesniffer": "^2.3 || ~3.6" + }, + "time": "2024-10-25T08:39:01+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/Qiniu/functions.php", + "src/Qiniu/Http/Middleware/Middleware.php" + ], + "psr-4": { + "Qiniu\\": "src/Qiniu" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Qiniu", + "email": "sdk@qiniu.com", + "homepage": "http://www.qiniu.com" + } + ], + "description": "Qiniu Resource (Cloud) Storage SDK for PHP", + "homepage": "http://developer.qiniu.com/", + "keywords": [ + "cloud", + "qiniu", + "sdk", + "storage" + ], + "support": { + "issues": "https://github.com/qiniu/php-sdk/issues", + "source": "https://github.com/qiniu/php-sdk/tree/v7.14.0" + }, + "install-path": "../qiniu/php-sdk" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..fed962a --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,42 @@ + + array ( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => NULL, + 'name' => '__root__', + ), + 'versions' => + array ( + '__root__' => + array ( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => NULL, + ), + 'myclabs/php-enum' => + array ( + 'pretty_version' => '1.8.5', + 'version' => '1.8.5.0', + 'aliases' => + array ( + ), + 'reference' => 'e7be26966b7398204a234f8673fdad5ac6277802', + ), + 'qiniu/php-sdk' => + array ( + 'pretty_version' => 'v7.14.0', + 'version' => '7.14.0.0', + 'aliases' => + array ( + ), + 'reference' => 'ee752ffa7263ce99fca0bd7340cf13c486a3516c', + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..92370c5 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70300)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.3.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/vendor/myclabs/php-enum/LICENSE b/vendor/myclabs/php-enum/LICENSE new file mode 100644 index 0000000..2a8cf22 --- /dev/null +++ b/vendor/myclabs/php-enum/LICENSE @@ -0,0 +1,18 @@ +The MIT License (MIT) + +Copyright (c) 2015 My C-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/myclabs/php-enum/README.md b/vendor/myclabs/php-enum/README.md new file mode 100644 index 0000000..948f374 --- /dev/null +++ b/vendor/myclabs/php-enum/README.md @@ -0,0 +1,196 @@ +# PHP Enum implementation inspired from SplEnum + +[![GitHub Actions][GA Image]][GA Link] +[![Latest Stable Version](https://poser.pugx.org/myclabs/php-enum/version.png)](https://packagist.org/packages/myclabs/php-enum) +[![Total Downloads](https://poser.pugx.org/myclabs/php-enum/downloads.png)](https://packagist.org/packages/myclabs/php-enum) +[![Psalm Shepherd][Shepherd Image]][Shepherd Link] + +Maintenance for this project is [supported via Tidelift](https://tidelift.com/subscription/pkg/packagist-myclabs-php-enum?utm_source=packagist-myclabs-php-enum&utm_medium=referral&utm_campaign=readme). + +## Why? + +First, and mainly, `SplEnum` is not integrated to PHP, you have to install the extension separately. + +Using an enum instead of class constants provides the following advantages: + +- You can use an enum as a parameter type: `function setAction(Action $action) {` +- You can use an enum as a return type: `function getAction() : Action {` +- You can enrich the enum with methods (e.g. `format`, `parse`, …) +- You can extend the enum to add new values (make your enum `final` to prevent it) +- You can get a list of all the possible values (see below) + +This Enum class is not intended to replace class constants, but only to be used when it makes sense. + +## Installation + +``` +composer require myclabs/php-enum +``` + +## Declaration + +```php +use MyCLabs\Enum\Enum; + +/** + * Action enum + * + * @extends Enum + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Usage + +```php +$action = Action::VIEW(); + +// or with a dynamic key: +$action = Action::$key(); +// or with a dynamic value: +$action = Action::from($value); +// or +$action = new Action($value); +``` + +As you can see, static methods are automatically implemented to provide quick access to an enum value. + +One advantage over using class constants is to be able to use an enum as a parameter type: + +```php +function setAction(Action $action) { + // ... +} +``` + +## Documentation + +- `__construct()` The constructor checks that the value exist in the enum +- `__toString()` You can `echo $myValue`, it will display the enum value (value of the constant) +- `getValue()` Returns the current value of the enum +- `getKey()` Returns the key of the current value on Enum +- `equals()` Tests whether enum instances are equal (returns `true` if enum values are equal, `false` otherwise) + +Static methods: + +- `from()` Creates an Enum instance, checking that the value exist in the enum +- `toArray()` method Returns all possible values as an array (constant name in key, constant value in value) +- `keys()` Returns the names (keys) of all constants in the Enum class +- `values()` Returns instances of the Enum class of all Enum constants (constant name in key, Enum instance in value) +- `isValid()` Check if tested value is valid on enum set +- `isValidKey()` Check if tested key is valid on enum set +- `assertValidValue()` Assert the value is valid on enum set, throwing exception otherwise +- `search()` Return key for searched value + +### Static methods + +```php +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} + +// Static method: +$action = Action::VIEW(); +$action = Action::EDIT(); +``` + +Static method helpers are implemented using [`__callStatic()`](http://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic). + +If you care about IDE autocompletion, you can either implement the static methods yourself: + +```php +final class Action extends Enum +{ + private const VIEW = 'view'; + + /** + * @return Action + */ + public static function VIEW() { + return new Action(self::VIEW); + } +} +``` + +or you can use phpdoc (this is supported in PhpStorm for example): + +```php +/** + * @method static Action VIEW() + * @method static Action EDIT() + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Native enums and migration +Native enum arrived to PHP in version 8.1: https://www.php.net/enumerations +If your project is running PHP 8.1+ or your library has it as a minimum requirement you should use it instead of this library. + +When migrating from `myclabs/php-enum`, the effort should be small if the usage was in the recommended way: +- private constants +- final classes +- no method overridden + +Changes for migration: +- Class definition should be changed from +```php +/** + * @method static Action VIEW() + * @method static Action EDIT() + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + to +```php +enum Action: string +{ + case VIEW = 'view'; + case EDIT = 'edit'; +} +``` +All places where the class was used as a type will continue to work. + +Usages and the change needed: + +| Operation | myclabs/php-enum | native enum | +|----------------------------------------------------------------|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Obtain an instance will change from | `$enumCase = Action::VIEW()` | `$enumCase = Action::VIEW` | +| Create an enum from a backed value | `$enumCase = new Action('view')` | `$enumCase = Action::from('view')` | +| Get the backed value of the enum instance | `$enumCase->getValue()` | `$enumCase->value` | +| Compare two enum instances | `$enumCase1 == $enumCase2`
or
`$enumCase1->equals($enumCase2)` | `$enumCase1 === $enumCase2` | +| Get the key/name of the enum instance | `$enumCase->getKey()` | `$enumCase->name` | +| Get a list of all the possible instances of the enum | `Action::values()` | `Action::cases()` | +| Get a map of possible instances of the enum mapped by name | `Action::values()` | `array_combine(array_map(fn($case) => $case->name, Action::cases()), Action::cases())`
or
`(new ReflectionEnum(Action::class))->getConstants()` | +| Get a list of all possible names of the enum | `Action::keys()` | `array_map(fn($case) => $case->name, Action::cases())` | +| Get a list of all possible backed values of the enum | `Action::toArray()` | `array_map(fn($case) => $case->value, Action::cases())` | +| Get a map of possible backed values of the enum mapped by name | `Action::toArray()` | `array_combine(array_map(fn($case) => $case->name, Action::cases()), array_map(fn($case) => $case->value, Action::cases()))`
or
`array_map(fn($case) => $case->value, (new ReflectionEnum(Action::class))->getConstants()))` | + +## Related projects + +- [PHP 8.1+ native enum](https://www.php.net/enumerations) +- [Doctrine enum mapping](https://github.com/acelaya/doctrine-enum-type) +- [Symfony ParamConverter integration](https://github.com/Ex3v/MyCLabsEnumParamConverter) +- [PHPStan integration](https://github.com/timeweb/phpstan-enum) + + +[GA Image]: https://github.com/myclabs/php-enum/workflows/CI/badge.svg + +[GA Link]: https://github.com/myclabs/php-enum/actions?query=workflow%3A%22CI%22+branch%3Amaster + +[Shepherd Image]: https://shepherd.dev/github/myclabs/php-enum/coverage.svg + +[Shepherd Link]: https://shepherd.dev/github/myclabs/php-enum diff --git a/vendor/myclabs/php-enum/SECURITY.md b/vendor/myclabs/php-enum/SECURITY.md new file mode 100644 index 0000000..84fd4e3 --- /dev/null +++ b/vendor/myclabs/php-enum/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +Only the latest stable release is supported. + +## Reporting a Vulnerability + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). + +Tidelift will coordinate the fix and disclosure. diff --git a/vendor/myclabs/php-enum/composer.json b/vendor/myclabs/php-enum/composer.json new file mode 100644 index 0000000..eab6263 --- /dev/null +++ b/vendor/myclabs/php-enum/composer.json @@ -0,0 +1,36 @@ +{ + "name": "myclabs/php-enum", + "type": "library", + "description": "PHP Enum implementation", + "keywords": ["enum"], + "homepage": "https://github.com/myclabs/php-enum", + "license": "MIT", + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "autoload-dev": { + "psr-4": { + "MyCLabs\\Tests\\Enum\\": "tests/" + } + }, + "require": { + "php": "^7.3 || ^8.0", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2 || ^5.2" + } +} diff --git a/vendor/myclabs/php-enum/src/Enum.php b/vendor/myclabs/php-enum/src/Enum.php new file mode 100644 index 0000000..1bd5592 --- /dev/null +++ b/vendor/myclabs/php-enum/src/Enum.php @@ -0,0 +1,319 @@ + + * @author Daniel Costa + * @author Mirosław Filip + * + * @psalm-template T + * @psalm-immutable + * @psalm-consistent-constructor + */ +abstract class Enum implements \JsonSerializable, \Stringable +{ + /** + * Enum value + * + * @var mixed + * @psalm-var T + */ + protected $value; + + /** + * Enum key, the constant name + * + * @var string + */ + private $key; + + /** + * Store existing constants in a static cache per object. + * + * + * @var array + * @psalm-var array> + */ + protected static $cache = []; + + /** + * Cache of instances of the Enum class + * + * @var array + * @psalm-var array> + */ + protected static $instances = []; + + /** + * Creates a new value of some type + * + * @psalm-pure + * @param mixed $value + * + * @psalm-param T $value + * @throws \UnexpectedValueException if incompatible type is given. + */ + public function __construct($value) + { + if ($value instanceof static) { + /** @psalm-var T */ + $value = $value->getValue(); + } + + /** @psalm-suppress ImplicitToStringCast assertValidValueReturningKey returns always a string but psalm has currently an issue here */ + $this->key = static::assertValidValueReturningKey($value); + + /** @psalm-var T */ + $this->value = $value; + } + + /** + * This method exists only for the compatibility reason when deserializing a previously serialized version + * that didn't had the key property + */ + public function __wakeup() + { + /** @psalm-suppress DocblockTypeContradiction key can be null when deserializing an enum without the key */ + if ($this->key === null) { + /** + * @psalm-suppress InaccessibleProperty key is not readonly as marked by psalm + * @psalm-suppress PossiblyFalsePropertyAssignmentValue deserializing a case that was removed + */ + $this->key = static::search($this->value); + } + } + + /** + * @param mixed $value + * @return static + */ + public static function from($value): self + { + $key = static::assertValidValueReturningKey($value); + + return self::__callStatic($key, []); + } + + /** + * @psalm-pure + * @return mixed + * @psalm-return T + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the enum key (i.e. the constant name). + * + * @psalm-pure + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @psalm-pure + * @psalm-suppress InvalidCast + * @return string + */ + public function __toString() + { + return (string)$this->value; + } + + /** + * Determines if Enum should be considered equal with the variable passed as a parameter. + * Returns false if an argument is an object of different class or not an object. + * + * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 + * + * @psalm-pure + * @psalm-param mixed $variable + * @return bool + */ + final public function equals($variable = null): bool + { + return $variable instanceof self + && $this->getValue() === $variable->getValue() + && static::class === \get_class($variable); + } + + /** + * Returns the names (keys) of all constants in the Enum class + * + * @psalm-pure + * @psalm-return list + * @return array + */ + public static function keys() + { + return \array_keys(static::toArray()); + } + + /** + * Returns instances of the Enum class of all Enum constants + * + * @psalm-pure + * @psalm-return array + * @return static[] Constant name in key, Enum instance in value + */ + public static function values() + { + $values = array(); + + /** @psalm-var T $value */ + foreach (static::toArray() as $key => $value) { + /** @psalm-suppress UnsafeGenericInstantiation */ + $values[$key] = new static($value); + } + + return $values; + } + + /** + * Returns all possible values as an array + * + * @psalm-pure + * @psalm-suppress ImpureStaticProperty + * + * @psalm-return array + * @return array Constant name in key, constant value in value + */ + public static function toArray() + { + $class = static::class; + + if (!isset(static::$cache[$class])) { + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + $reflection = new \ReflectionClass($class); + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + static::$cache[$class] = $reflection->getConstants(); + } + + return static::$cache[$class]; + } + + /** + * Check if is valid enum value + * + * @param $value + * @psalm-param mixed $value + * @psalm-pure + * @psalm-assert-if-true T $value + * @return bool + */ + public static function isValid($value) + { + return \in_array($value, static::toArray(), true); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + */ + public static function assertValidValue($value): void + { + self::assertValidValueReturningKey($value); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + * @return string + */ + private static function assertValidValueReturningKey($value): string + { + if (false === ($key = static::search($value))) { + throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); + } + + return $key; + } + + /** + * Check if is valid enum key + * + * @param $key + * @psalm-param string $key + * @psalm-pure + * @return bool + */ + public static function isValidKey($key) + { + $array = static::toArray(); + + return isset($array[$key]) || \array_key_exists($key, $array); + } + + /** + * Return key for value + * + * @param mixed $value + * + * @psalm-param mixed $value + * @psalm-pure + * @return string|false + */ + public static function search($value) + { + return \array_search($value, static::toArray(), true); + } + + /** + * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant + * + * @param string $name + * @param array $arguments + * + * @return static + * @throws \BadMethodCallException + * + * @psalm-pure + */ + public static function __callStatic($name, $arguments) + { + $class = static::class; + if (!isset(self::$instances[$class][$name])) { + $array = static::toArray(); + if (!isset($array[$name]) && !\array_key_exists($name, $array)) { + $message = "No static method or enum constant '$name' in class " . static::class; + throw new \BadMethodCallException($message); + } + /** @psalm-suppress UnsafeGenericInstantiation */ + return self::$instances[$class][$name] = new static($array[$name]); + } + return clone self::$instances[$class][$name]; + } + + /** + * Specify data which should be serialized to JSON. This method returns data that can be serialized by json_encode() + * natively. + * + * @return mixed + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getValue(); + } +} diff --git a/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php new file mode 100644 index 0000000..7c65e4e --- /dev/null +++ b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php @@ -0,0 +1,54 @@ +register(new \MyCLabs\Enum\PHPUnit\Comparator()); + */ +final class Comparator extends \SebastianBergmann\Comparator\Comparator +{ + public function accepts($expected, $actual) + { + return $expected instanceof Enum && ( + $actual instanceof Enum || $actual === null + ); + } + + /** + * @param Enum $expected + * @param Enum|null $actual + * + * @return void + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) + { + if ($expected->equals($actual)) { + return; + } + + throw new ComparisonFailure( + $expected, + $actual, + $this->formatEnum($expected), + $this->formatEnum($actual), + false, + 'Failed asserting that two Enums are equal.' + ); + } + + private function formatEnum(?Enum $enum = null) + { + if ($enum === null) { + return "null"; + } + + return get_class($enum)."::{$enum->getKey()}()"; + } +} diff --git a/vendor/myclabs/php-enum/stubs/Stringable.php b/vendor/myclabs/php-enum/stubs/Stringable.php new file mode 100644 index 0000000..4811af7 --- /dev/null +++ b/vendor/myclabs/php-enum/stubs/Stringable.php @@ -0,0 +1,11 @@ + phpd.log 2>&1 & + echo $! > mock-server.pid + + cd tests/socks5-server/ + nohup go run main.go > ../../socks5.log 2>&1 & + echo $! > ../../socks-server.pid + + - name: Setup php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + + - name: Install dependencies + run: | + composer self-update + composer install --no-interaction --prefer-source --dev + + - name: Run cases + run: | + ./vendor/bin/phpcs --standard=PSR2 src + ./vendor/bin/phpcs --standard=PSR2 examples + ./vendor/bin/phpcs --standard=PSR2 tests + ./vendor/bin/phpunit --coverage-clover=coverage.xml + cat mock-server.pid | xargs kill + cat socks-server.pid | xargs kill + + env: + QINIU_ACCESS_KEY: ${{ secrets.QINIU_ACCESS_KEY }} + QINIU_SECRET_KEY: ${{ secrets.QINIU_SECRET_KEY }} + QINIU_TEST_BUCKET: ${{ secrets.QINIU_TEST_BUCKET }} + QINIU_TEST_DOMAIN: ${{ secrets.QINIU_TEST_DOMAIN }} + + - name: Print mock server log + if: ${{ failure() }} + run: | + cat phpd.log + + - name: Print socks5 server log + if: ${{ failure() }} + run: | + cat socks5.log + + - name: After_success + run: bash <(curl -s https://codecov.io/bash) diff --git a/vendor/qiniu/php-sdk/.github/workflows/version-check.yml b/vendor/qiniu/php-sdk/.github/workflows/version-check.yml new file mode 100644 index 0000000..983a98f --- /dev/null +++ b/vendor/qiniu/php-sdk/.github/workflows/version-check.yml @@ -0,0 +1,19 @@ +name: PHP SDK Version Check +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" +jobs: + linux: + name: Version Check + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Set env + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV + - name: Check + run: | + set -e + grep -qF "## ${RELEASE_VERSION}" CHANGELOG.md + grep -qF "const SDK_VER = '${RELEASE_VERSION}';" src/Qiniu/Config.php diff --git a/vendor/qiniu/php-sdk/.gitignore b/vendor/qiniu/php-sdk/.gitignore new file mode 100644 index 0000000..4c842c8 --- /dev/null +++ b/vendor/qiniu/php-sdk/.gitignore @@ -0,0 +1,12 @@ +*.phar +*.zip +build/artifacts +phpunit.xml +phpunit.functional.xml +.DS_Store +.swp +.build +composer.lock +vendor +src/package.xml +.idea/ diff --git a/vendor/qiniu/php-sdk/.scrutinizer.yml b/vendor/qiniu/php-sdk/.scrutinizer.yml new file mode 100644 index 0000000..6a2d0d8 --- /dev/null +++ b/vendor/qiniu/php-sdk/.scrutinizer.yml @@ -0,0 +1,42 @@ +filter: + excluded_paths: [tests/*] +checks: + php: + code_rating: true + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true +tools: + external_code_coverage: + timeout: 1200 + runs: 3 + php_analyzer: true + php_code_coverage: false + php_code_sniffer: + config: + standard: PSR2 + filter: + paths: ['src'] + php_loc: + enabled: true + excluded_dirs: [vendor, tests] + php_cpd: + enabled: true + excluded_dirs: [vendor, tests] +build: + nodes: + analysis: + tests: + override: + - php-scrutinizer-run + diff --git a/vendor/qiniu/php-sdk/CHANGELOG.md b/vendor/qiniu/php-sdk/CHANGELOG.md new file mode 100644 index 0000000..6f8da5d --- /dev/null +++ b/vendor/qiniu/php-sdk/CHANGELOG.md @@ -0,0 +1,196 @@ +# Changelog + +## 7.14.0 (2024-10-16) +* 对象存储,持久化处理支持工作流模版 + +## 7.13.0 (2024-09-05) +* 对象存储,验证回调方法新增支持 Qiniu 签名 +* 对象存储,调整查询空间区域域名顺序与默认空间管理域名 +* 支持闲时任务配置 + +## 7.12.1 (2024-02-21) +* 对象存储,添加上传策略部分字段 + +## 7.12.0 (2023-12-11) +* 对象存储,支持归档直读存储 +* 对象存储,批量操作支持自动查询 rs 服务域名 + +## 7.11.0 (2023-09-05) +* 支持代理 + +## 7.10.1 (2023-08-04) +* 修复部分 API 调用中间件合并失败(#417) + +## 7.10.0 (2023-06-20) +* 对象存储,新增请求中间件逻辑,方便拓展请求逻辑 +* 对象存储,新增备用 UC 域名用于查询区域域名 +* 对象存储,修复分片上传初始化失败无法快速失败 +* 对象存储,移除首尔区域 + +## 7.9.0 (2023-03-31) +* 对象存储,修复无法对 key 为空字符串的对象进行操作 +* 修复 301 重定向无法正确获取 header 信息 +* 对象存储,新增查询区域域名过期时间 +* 对象存储,更新获取区域域名的接口 +* 对象存储,更新查询 bucket 域名为 uc 服务 +* 对象存储,新增 uc 服务可配置 + +## 7.8.0 (2022-10-25) +* 移除不推荐域名,并增加区域亚太-首尔和华东-浙江2 +* 对象存储,修复断点上传的文件内容不正确 +* 对象存储,优化分片上传 ctx 超时检测 + +## 7.7.0 (2022-09-02) +* 对象存储,新增支持设置文件级别生命周期 setObjectLifecycle API +* 对象存储,内置增加七牛新建存储区域域名信息 +* 修复当前已知问题 + +## 7.6.0 (2022-06-08) +* 对象存储,管理类 API 发送请求时增加 [X-Qiniu-Date](https://developer.qiniu.com/kodo/3924/common-request-headers) (生成请求的时间) header + + +## 7.5.0 (2022-04-18) +* 对象存储,新增支持 [深度归档存储类型](https://developer.qiniu.com/kodo/3956/kodo-category#deep_archive) + +## 7.4.3 (2022-04-01) +* 优化签名算法逻辑 + +## 7.4.2(2022-03-01) +* 修复已知关于请求 Header 处理不当问题,比如没有处理为大小写不敏感等问题 + +## 7.4.1(2021-09-24) +* 修复了 分片上传 v2 已知问题,明确给出了参数不合理情况下对应的错误提示信息 + +## 7.4.0 (2021-07-19) +* 【对象存储】支持 [分片上传 v2](https://developer.qiniu.com/kodo/7458/multipartupload) 和 断点续传,使用方式见 [开发者文档](https://developer.qiniu.com/kodo/1241/php#resume-upload-file) + +## 7.3.0 (2020-09-24) +### 新增 +* 【对象存储】增加异步抓取方法与demo +* 【融合cdn】增加查询CDN刷新记录、查询CDN预取记录方法与demo +* 【云短信】增加查询短信发送记录的方法 +* 【实时音视频】增加rtc停止房间的合流转推方法 +* 【内容审核】增加图片审核、视频审核方法与demo + +### 修复 +* 【对象存储】修复签算 token 时上传策略中的 forceSaveKey 字段不生效的问题 +* 【对象存储】修复更新空间事件通知规则方法 + +### 优化 +* 【对象存储】创建空间迁移到mkbucketv3 api +* 优化对 http2 返回头的判断 +* 优化 demo 中的文档注释说明 +* docs 目录下的 rtc demo 移动至 examples/rtc 目录下 +* docs 目录下的 sms demo 移动至 examples/sms 目录下 + +## 7.2.10 (2019-10-28) +* 去除云短信类类型指定 +* 修改不传文件名时存在表单上传错误的情况 + +## 7.2.9 (2019-07-09) +* 添加空间管理、云短信接口 +* 去除无效参数 + +## 7.2.7 (2018-11-06) +* 添加 QVM 内网上传到 KODO 的 zone 设置 + +## 7.2.6 (2018-05-18) +* 修复rs,rsf在不同机房默认的https域名 + +## 7.2.5 (2018-05-10) +* 修复表单上传中多余的参数checkCrc导致的fname错位问题 + +## 7.2.4 (2018-05-09) +### 增加 +* 连麦功能 + +## 7.2.3 (2018-01-20) +### 增加 +* 新加坡机房 +### 修正 +* 获取域名的入口域名 +* http回复头部兼容大小写 + +## 7.2.2 (2017-11-06) +### 增加 +* Qiniu算法的鉴权方法 + +## 7.1.4 (2017-06-21) +### 增加 +* cdn 文件/目录 刷新 +* cdn 获取 流量/带宽 +* cdn 获取域名的访问日志列表 +* cdn 对资源链接进行时间戳防盗链签名 + +## 7.1.3 (2016-11-18) +### 增加 +* move, copy操作增加force参数 + +## 7.1.2 (2016-11-12) +### 修正 +* 明确抛出获取各区域域名失败时的报错 + +## 7.1.1 (2016-11-02) +### 修正 +* 多区域配置文件存储目录从home修改到tmp目录 + + +## 7.1.0 (2016-10-22) +### 增加 +* 多存储区域的支持 + +## 7.0.8 (2016-07-19) +### 增加 +* demo +* https url 支持 +* deleteAfterDays 策略 +* 添加图片处理链接统一拼接方法 by @SherlockRen + +## 7.0.7 (2016-01-12) +### 修正 +* PersistentFop参数pipeline和notify_url失效 +* resume 模式 close file inputstream + +## 7.0.6 (2015-12-05) +### 修正 +* php7.0 Json 对空字符串解析单元测试报错 +* 开启安全模式或者设置可操作目录树时,设置CURLOPT_FOLLOWLOCATION报错, by @twocabbages +* fetch 支持不指定key, by @sinkcup + +## 7.0.5 (2015-10-29) +### 增加 +* 增加上传策略最小文件大小限制 fsizeMin +* 增加常见examples + +## 7.0.4 (2015-07-23) +### 修正 +* 一些地方的严格比较检查 +* resumeupload 备用地址失效 + +## 7.0.3 (2015-07-10) +### 修改 +* 多zone 支持 + +## 7.0.2 (2015-04-18) +### 修改 +* fetch 接口返回内容调整 +* pfop 接口调整 + +###修正 +* exception 类调用 + +## 7.0.1 (2015-03-27) +### 增加 +* 增加代码注释 + +## 7.0.0 (2015-02-03) + +### 增加 +* 简化上传接口 +* 自动选择断点续上传还是直传 +* 重构代码,接口和内部结构更清晰 +* 改变mime +* 代码覆盖度报告 +* policy改为array, 便于灵活增加,并加入过期字段检查 +* 文件列表支持目录形式 +* 利用元编程方式支持 fop 和 pfop diff --git a/vendor/qiniu/php-sdk/CONTRIBUTING.md b/vendor/qiniu/php-sdk/CONTRIBUTING.md new file mode 100644 index 0000000..0466bf9 --- /dev/null +++ b/vendor/qiniu/php-sdk/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# 贡献代码指南 + +我们非常欢迎大家来贡献代码,我们会向贡献者致以最诚挚的敬意。 + +一般可以通过在Github上提交[Pull Request](https://github.com/qiniu/php-sdk)来贡献代码。 + +## Pull Request要求 + +- **[PSR-2 编码风格标准](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** 。要通过项目中的code sniffer检查。 + +- **代码格式** 提交前 请按 ./vendor/bin/phpcbf --standard=PSR2 进行格式化。 + +- **必须添加测试!** - 如果没有测试(单元测试、集成测试都可以),那么提交的补丁是不会通过的。 + +- **记得更新文档** - 保证`README.md`以及其他相关文档及时更新,和代码的变更保持一致性。 + +- **考虑我们的发布周期** - 我们的版本号会服从[SemVer v2.0.0](http://semver.org/),我们绝对不会随意变更对外的API。 + +- **创建feature分支** - 最好不要从你的master分支提交 pull request。 + +- **一个feature提交一个pull请求** - 如果你的代码变更了多个操作,那就提交多个pull请求吧。 + +- **清晰的commit历史** - 保证你的pull请求的每次commit操作都是有意义的。如果你开发中需要执行多次的即时commit操作,那么请把它们放到一起再提交pull请求。 + +## 运行测试 + +``` bash +./vendor/bin/phpunit tests/Qiniu/Tests/ + +``` diff --git a/vendor/qiniu/php-sdk/LICENSE b/vendor/qiniu/php-sdk/LICENSE new file mode 100644 index 0000000..ba646be --- /dev/null +++ b/vendor/qiniu/php-sdk/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Qiniu, Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/qiniu/php-sdk/README.md b/vendor/qiniu/php-sdk/README.md new file mode 100644 index 0000000..784d735 --- /dev/null +++ b/vendor/qiniu/php-sdk/README.md @@ -0,0 +1,76 @@ +# Qiniu Cloud SDK for PHP +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) +[![Build Status](https://travis-ci.org/qiniu/php-sdk.svg)](https://travis-ci.org/qiniu/php-sdk) +[![GitHub release](https://img.shields.io/github/v/tag/qiniu/php-sdk.svg?label=release)](https://github.com/qiniu/php-sdk/releases) +[![Latest Stable Version](https://img.shields.io/packagist/v/qiniu/php-sdk.svg)](https://packagist.org/packages/qiniu/php-sdk) +[![Total Downloads](https://img.shields.io/packagist/dt/qiniu/php-sdk.svg)](https://packagist.org/packages/qiniu/php-sdk) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/qiniu/php-sdk/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/qiniu/php-sdk/?branch=master) +[![Coverage Status](https://codecov.io/gh/qiniu/php-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/qiniu/php-sdk) +[![Join Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/qiniu/php-sdk?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![@qiniu on weibo](http://img.shields.io/badge/weibo-%40qiniutek-blue.svg)](http://weibo.com/qiniutek) + + +## 安装 + +推荐使用 `composer` 进行安装。可以使用 composer.json 声明依赖,或者运行下面的命令。SDK 包已经放到这里 [`qiniu/php-sdk`][install-packagist] 。 + +```bash +$ composer require qiniu/php-sdk +``` + +## 运行环境 + +| Qiniu SDK版本 | PHP 版本 | +|:--------------------:|:-----------------------------------------------:| +| 7.x | cURL extension, 5.3 - 5.6, 7.0 - 7.4, 8.0-8.1 | +| 6.x | cURL extension, 5.2 - 5.6 | + +## 使用方法 + +### 上传 +```php +use Qiniu\Storage\UploadManager; +use Qiniu\Auth; +... + $uploadMgr = new UploadManager(); + $auth = new Auth($accessKey, $secretKey); + $token = $auth->uploadToken($bucket); + list($ret, $error) = $uploadMgr->putFile($token, 'key', 'filePath'); +... +``` + +## 测试 + +``` bash +$ ./vendor/bin/phpunit tests/Qiniu/Tests/ +``` + +## 常见问题 + +- `$error` 保留了请求响应的信息,失败情况下 `ret` 为 `none`, 将 `$error` 可以打印出来,提交给我们。 +- API 的使用 demo 可以参考 [examples](https://github.com/qiniu/php-sdk/tree/master/examples)。 + +## 代码贡献 + +详情参考[代码提交指南](https://github.com/qiniu/php-sdk/blob/master/CONTRIBUTING.md)。 + +## 贡献记录 + +- [所有贡献者](https://github.com/qiniu/php-sdk/contributors) + +## 联系我们 + +- 如果需要帮助,请提交工单(在portal右侧点击咨询和建议提交工单,或者直接向 support@qiniu.com 发送邮件) +- 如果有什么问题,可以到问答社区提问,[问答社区](https://qiniu.segmentfault.com/) +- 更详细的文档,见[官方文档站](https://developer.qiniu.com/) +- 如果发现了 bug, 欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues) +- 如果有功能需求,欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues) +- 如果要提交代码,欢迎提交 pull request +- 欢迎关注我们的[微信](https://www.qiniu.com/#weixin) [微博](https://weibo.com/qiniutek),及时获取动态信息。 + +## 代码许可 + +The MIT License (MIT).详情见 [License文件](https://github.com/qiniu/php-sdk/blob/master/LICENSE). + +[packagist]: http://packagist.org +[install-packagist]: https://packagist.org/packages/qiniu/php-sdk diff --git a/vendor/qiniu/php-sdk/autoload.php b/vendor/qiniu/php-sdk/autoload.php new file mode 100644 index 0000000..9efddd7 --- /dev/null +++ b/vendor/qiniu/php-sdk/autoload.php @@ -0,0 +1,19 @@ +=5.3.3", + "ext-xml": "*", + "ext-curl": "*", + "myclabs/php-enum": "~1.5.2 || ~1.6.6 || ~1.7.7 || ~1.8.4" + }, + "require-dev": { + "paragonie/random_compat": ">=2", + "phpunit/phpunit": "^4.8 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4", + "squizlabs/php_codesniffer": "^2.3 || ~3.6" + }, + "autoload": { + "psr-4": { + "Qiniu\\": "src/Qiniu" + }, + "files": [ + "src/Qiniu/functions.php", + "src/Qiniu/Http/Middleware/Middleware.php" + ] + } +} diff --git a/vendor/qiniu/php-sdk/examples/README.md b/vendor/qiniu/php-sdk/examples/README.md new file mode 100644 index 0000000..b7b4f98 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/README.md @@ -0,0 +1,10 @@ +# examples + +这些 examples 旨在帮助你快速了解使用七牛的 SDK。这些 demo 都是可以直接运行的, 但是在运行之前需要填上您自己的参数。 + +比如: + +* `$bucket` 需要填上您想操作的 [bucket名字](https://portal.qiniu.com/kodo/bucket)。 +* `$accessKey` 和 `$secretKey` 可以在我们的[管理后台](https://portal.qiniu.com/user/key)找到。 +* 在进行`视频转码`, `压缩文件`等异步操作时 需要使用到的队列名称也可以在我们[管理后台](https://portal.qiniu.com/dora/media-gate/pipeline)新建。 + diff --git a/vendor/qiniu/php-sdk/examples/bucket_lifecycleRule.php b/vendor/qiniu/php-sdk/examples/bucket_lifecycleRule.php new file mode 100644 index 0000000..f51524c --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/bucket_lifecycleRule.php @@ -0,0 +1,42 @@ +bucketLifecycleRule( + $bucket, + $name, + $prefix, + $delete_after_days, + $to_line_after_days, + $to_archive_after_days, + $to_deep_archive_after_days, + $to_archive_ir_after_days +); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php b/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php new file mode 100644 index 0000000..c9de0e6 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php @@ -0,0 +1,41 @@ +getBandwidthData( + $domains, + $startDate, + $endDate, + $granularity +); + +if ($getBandwidthErr != null) { + var_dump($getBandwidthErr); +} else { + echo "get bandwidth data success\n"; + print_r($bandwidthData); +} diff --git a/vendor/qiniu/php-sdk/examples/cdn_get_flux.php b/vendor/qiniu/php-sdk/examples/cdn_get_flux.php new file mode 100644 index 0000000..57df808 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/cdn_get_flux.php @@ -0,0 +1,35 @@ +getFluxData($domains, $startDate, $endDate, $granularity); +if ($getFluxErr != null) { + var_dump($getFluxErr); +} else { + echo "get flux data success\n"; + print_r($fluxData); +} diff --git a/vendor/qiniu/php-sdk/examples/cdn_get_log_list.php b/vendor/qiniu/php-sdk/examples/cdn_get_log_list.php new file mode 100644 index 0000000..2b3f7dd --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/cdn_get_log_list.php @@ -0,0 +1,31 @@ +getCdnLogList($domains, $logDate); +if ($getLogErr != null) { + var_dump($getLogErr); +} else { + echo "get cdn log list success\n"; + print_r($logListData); +} diff --git a/vendor/qiniu/php-sdk/examples/cdn_get_prefetch_list.php b/vendor/qiniu/php-sdk/examples/cdn_get_prefetch_list.php new file mode 100644 index 0000000..958e5eb --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/cdn_get_prefetch_list.php @@ -0,0 +1,46 @@ +getCdnPrefetchList( + $requestId, + $urls, + $state, + $pageNo, + $pageSize, + $startTime, + $endTime +); +echo "\n====> query prefetch list: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/cdn_get_refresh_list.php b/vendor/qiniu/php-sdk/examples/cdn_get_refresh_list.php new file mode 100644 index 0000000..ad4fca2 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/cdn_get_refresh_list.php @@ -0,0 +1,48 @@ +getCdnRefreshList( + $requestId, + $isDir, + $urls, + $state, + $pageNo, + $pageSize, + $startTime, + $endTime +); +echo "\n====> query refresh list: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php b/vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php new file mode 100644 index 0000000..2140378 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php @@ -0,0 +1,59 @@ +refreshUrlsAndDirs($urls, $dirs); +if ($refreshErr != null) { + var_dump($refreshErr); +} else { + echo "refresh request sent\n"; + print_r($refreshResult); +} + +//---------------------------------------- demo2 ---------------------------------------- +// 刷新文件 + +list($refreshResult, $refreshErr) = $cdnManager->refreshUrls($urls); +if ($refreshErr != null) { + var_dump($refreshErr); +} else { + echo "refresh urls request sent\n"; + print_r($refreshResult); +} + +//---------------------------------------- demo3 ---------------------------------------- +// 刷新目录 + +list($refreshResult, $refreshErr) = $cdnManager->refreshDirs($dirs); +if ($refreshErr != null) { + var_dump($refreshErr); +} else { + echo "refresh dirs request sent\n"; + print_r($refreshResult); +} diff --git a/vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php b/vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php new file mode 100644 index 0000000..f2d7855 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php @@ -0,0 +1,20 @@ +censorImage($body); +echo "\n====> Result is: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/censor_video.php b/vendor/qiniu/php-sdk/examples/censor_video.php new file mode 100644 index 0000000..7ac056f --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/censor_video.php @@ -0,0 +1,52 @@ +censorVideo($body); +echo "\n====> Result is: \n"; +if ($err !== null) { + var_dump($err); +} else { + echo "job_id is: $jobid\n"; +} + +// 查询视频审核结果 +list($ret, $err) = $argusManager->censorStatus($jobid); +echo "\n====> job status: \n"; + +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/delete_bucket.php b/vendor/qiniu/php-sdk/examples/delete_bucket.php new file mode 100644 index 0000000..325a47a --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/delete_bucket.php @@ -0,0 +1,27 @@ +deleteBucket($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/delete_bucketEvent.php b/vendor/qiniu/php-sdk/examples/delete_bucketEvent.php new file mode 100644 index 0000000..7eb744d --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/delete_bucketEvent.php @@ -0,0 +1,28 @@ +deleteBucketEvent($bucket, $name); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/delete_bucketLifecycleRule.php b/vendor/qiniu/php-sdk/examples/delete_bucketLifecycleRule.php new file mode 100644 index 0000000..2146b1b --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/delete_bucketLifecycleRule.php @@ -0,0 +1,27 @@ +deleteBucketLifecycleRule($bucket, $name); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/get_bucketEvents.php b/vendor/qiniu/php-sdk/examples/get_bucketEvents.php new file mode 100644 index 0000000..2379584 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/get_bucketEvents.php @@ -0,0 +1,26 @@ +getBucketEvents($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/get_bucketLifecycleRules.php b/vendor/qiniu/php-sdk/examples/get_bucketLifecycleRules.php new file mode 100644 index 0000000..a35feed --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/get_bucketLifecycleRules.php @@ -0,0 +1,26 @@ +getBucketLifecycleRules($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/get_bucketList.php b/vendor/qiniu/php-sdk/examples/get_bucketList.php new file mode 100644 index 0000000..6a2f7b0 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/get_bucketList.php @@ -0,0 +1,26 @@ +listbuckets($region); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/get_bucketQuota.php b/vendor/qiniu/php-sdk/examples/get_bucketQuota.php new file mode 100644 index 0000000..93474b5 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/get_bucketQuota.php @@ -0,0 +1,26 @@ +getBucketQuota($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/get_bucketinfo.php b/vendor/qiniu/php-sdk/examples/get_bucketinfo.php new file mode 100644 index 0000000..98fd9f7 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/get_bucketinfo.php @@ -0,0 +1,25 @@ +bucketInfo($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/get_bucketinfos.php b/vendor/qiniu/php-sdk/examples/get_bucketinfos.php new file mode 100644 index 0000000..5eec1d8 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/get_bucketinfos.php @@ -0,0 +1,26 @@ +bucketInfos($region); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/get_corsRules.php b/vendor/qiniu/php-sdk/examples/get_corsRules.php new file mode 100644 index 0000000..58e28be --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/get_corsRules.php @@ -0,0 +1,26 @@ +getCorsRules($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/image_url_builder.php b/vendor/qiniu/php-sdk/examples/image_url_builder.php new file mode 100644 index 0000000..20e2b00 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/image_url_builder.php @@ -0,0 +1,74 @@ + + */ +$thumbLink = $imageUrlBuilder->thumbnail($url, 1, 100, 100); + +// 函数方式调用 也可拼接多个操作参数 图片+水印 +$thumbLink2 = \Qiniu\thumbnail($url2, 1, 100, 100); +var_dump($thumbLink, $thumbLink2); + +/** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param int $dissolve 透明度 [可选] + * @param string $gravity 水印位置 [可选] + * @param int $dx 横轴边距 [可选] + * @param int $dy 纵轴边距 [可选] + * @param int $watermarkScale 自适应原图的短边比例 [可选] + * @link https://developer.qiniu.com/dora/api/1316/image-watermarking-processing-watermark + * @return string + * @author Sherlock Ren + */ +$waterLink = $imageUrlBuilder->waterImg($url, $waterImage); +// 函数调用方法 +//$waterLink = \Qiniu\waterImg($url, $waterImage); +var_dump($waterLink); + +/** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 [可选] + * @param int $dissolve 透明度 [可选] + * @param string $gravity 水印位置 [可选] + * @param int $dx 横轴边距 [可选] + * @param int $dy 纵轴边距 [可选] + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @return string + * @author Sherlock Ren + */ +$textLink = $imageUrlBuilder->waterText($url, '你瞅啥', '微软雅黑', 300); +// 函数调用方法 +// $textLink = \Qiniu\waterText($url, '你瞅啥', '微软雅黑', 300); +var_dump($textLink); diff --git a/vendor/qiniu/php-sdk/examples/persistent_fop_init.php b/vendor/qiniu/php-sdk/examples/persistent_fop_init.php new file mode 100644 index 0000000..baca846 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/persistent_fop_init.php @@ -0,0 +1,18 @@ +useHTTPS=true; + +// 初始化 +$pfop = new PersistentFop($auth, $config); diff --git a/vendor/qiniu/php-sdk/examples/persistent_fop_status.php b/vendor/qiniu/php-sdk/examples/persistent_fop_status.php new file mode 100644 index 0000000..73e85a3 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/persistent_fop_status.php @@ -0,0 +1,19 @@ +status($persistentId); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/pfop_mkzip.php b/vendor/qiniu/php-sdk/examples/pfop_mkzip.php new file mode 100644 index 0000000..fb95cc2 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/pfop_mkzip.php @@ -0,0 +1,58 @@ +execute($bucket, $key, $fops, $pipeline, $notify_url, $force); + +echo "\n====> pfop mkzip result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +// 查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop mkzip status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/pfop_vframe.php b/vendor/qiniu/php-sdk/examples/pfop_vframe.php new file mode 100644 index 0000000..49fd36d --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/pfop_vframe.php @@ -0,0 +1,55 @@ +useHTTPS = true; +$pfop = new PersistentFop($auth, $config); + +// 视频处理完毕后保存到空间中的名称 +$saveasKey = 'qiniu_480x360.jpg'; + +// 进行视频截帧操作 +$fops = "vframe/jpg/offset/1/w/480/h/360/rotate/90|saveas/" . + \Qiniu\base64_urlSafeEncode("$bucket:$saveasKey"); + +list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force); +echo "\n====> pfop avthumb result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +// 查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php b/vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php new file mode 100644 index 0000000..986aa8c --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php @@ -0,0 +1,55 @@ +useHTTPS=true; + +// 视频处理完毕后保存到空间中的名称 +$saveasKey = 'qiniu_640x360.mp4'; + +$pfop = new PersistentFop($auth, $config); + +// 进行视频转码操作 +$fops = "avthumb/mp4/s/640x360/vb/1.4m|saveas/" . \Qiniu\base64_urlSafeEncode("$bucket:$saveasKey"); + +list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force); +echo "\n====> pfop avthumb result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +// 查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/pfop_watermark.php b/vendor/qiniu/php-sdk/examples/pfop_watermark.php new file mode 100644 index 0000000..ea3d6bc --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/pfop_watermark.php @@ -0,0 +1,59 @@ +useHTTPS=true; +$pfop = new PersistentFop($auth, $config); + +// 图片水印的源路径,也就是给视频打图片水印的图片 +$base64URL = Qiniu\base64_urlSafeEncode('http://test-2.qiniudn.com/logo.png'); + +// 视频处理完毕后保存到空间中的名称 +$saveasKey = 'qiniu_watermark.mp4'; + +// 进行视频打图片水印操作 +$fops = "avthumb/mp4/wmImage/" . $base64URL . "|saveas/" + . \Qiniu\base64_urlSafeEncode("$bucket:$saveasKey"); + +list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force); +echo "\n====> pfop avthumb result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +// 查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/php-logo.png b/vendor/qiniu/php-sdk/examples/php-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..77e051fe413b2754bc121876b5f986d028a8449a GIT binary patch literal 65062 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 NC#5X;17sTf{{R#))Y1R| literal 0 HcmV?d00001 diff --git a/vendor/qiniu/php-sdk/examples/prefop.php b/vendor/qiniu/php-sdk/examples/prefop.php new file mode 100644 index 0000000..1b8950a --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/prefop.php @@ -0,0 +1,27 @@ +useHTTPS=true; + +$pfop = new PersistentFop($auth, $config); + +$id = "z2.01z201c4oyre6q1hgy00murnel0002nh"; + +// 查询持久化处理的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/put_bucketAccessMode.php b/vendor/qiniu/php-sdk/examples/put_bucketAccessMode.php new file mode 100644 index 0000000..638ae3c --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/put_bucketAccessMode.php @@ -0,0 +1,27 @@ +putBucketAccessMode($bucket, $private); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/put_bucketAccessStyleMode.php b/vendor/qiniu/php-sdk/examples/put_bucketAccessStyleMode.php new file mode 100644 index 0000000..3cc2aec --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/put_bucketAccessStyleMode.php @@ -0,0 +1,27 @@ +putBucketAccessStyleMode($bucket, $mode); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/put_bucketEvent.php b/vendor/qiniu/php-sdk/examples/put_bucketEvent.php new file mode 100644 index 0000000..f3c830d --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/put_bucketEvent.php @@ -0,0 +1,32 @@ +putBucketEvent($bucket, $name, $prefix, $suffix, $event, $callbackURL); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/put_bucketMaxAge.php b/vendor/qiniu/php-sdk/examples/put_bucketMaxAge.php new file mode 100644 index 0000000..4890174 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/put_bucketMaxAge.php @@ -0,0 +1,27 @@ +putBucketMaxAge($bucket, $maxAge); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/put_bucketQuota.php b/vendor/qiniu/php-sdk/examples/put_bucketQuota.php new file mode 100644 index 0000000..b00ec48 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/put_bucketQuota.php @@ -0,0 +1,29 @@ +putBucketQuota($bucket, $size, $count); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/put_referAntiLeech.php b/vendor/qiniu/php-sdk/examples/put_referAntiLeech.php new file mode 100644 index 0000000..7d56d1e --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/put_referAntiLeech.php @@ -0,0 +1,30 @@ +putReferAntiLeech($bucket, $mode, $norefer, $pattern); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/qetag.php b/vendor/qiniu/php-sdk/examples/qetag.php new file mode 100644 index 0000000..1fe90d1 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/qetag.php @@ -0,0 +1,14 @@ +useHTTPS = true; // 接口是否使用 HTTPS 协议 + +$bucketManager = new BucketManager($auth, $config); + +// 异步第三方资源抓取 +// 参考文档:https://developer.qiniu.com/kodo/api/4097/asynch-fetch + +// 需要抓取的文件 URL +$url = 'http://devtools.qiniu.com/qiniu.png'; + +//回调 URL(需要可以公网访问,并能够相应 200 OK) +$callbackurl = "http://your.domain.com/upload_verify_callback.php"; + +// 回调Body +$callbackbody = '{"key":"$(key)","hash":"$(etag)","w":"$(imageInfo.width)","h":"$(imageInfo.height)"}'; + + +//---------------------------------------- demo1 ---------------------------------------- +// 指定抓取的文件保存到七牛云空间中的名称 + +$key = time() . '.png'; +list($ret, $err) = $bucketManager->asynchFetch($url, $bucket, null, $key, null, null, $callbackurl, $callbackbody); +echo "=====> asynch fetch $url to bucket: $bucket key: $key\n"; +if ($err !== null) { + var_dump($err); +} else { + $id = $ret['id']; + echo "id is: $id\n"; +} + +//---------------------------------------- demo2 ---------------------------------------- +// 不指定 key 时,以文件内容的 hash 作为文件名 + +$key = null; +list($ret, $err) = $bucketManager->asynchFetch($url, $bucket, null, $key, null, null, $callbackurl, $callbackbody); +echo "=====> asynch fetch $url to bucket: $bucket key: $(etag)\n"; +if ($err !== null) { + var_dump($err); +} else { + $id = $ret['id']; + echo "id is: $id\n"; +} + +// 查询异步抓取的进度和状态 + +// 华东:z0,华北:z1,华南:z2,北美:na0,东南亚:as0 +$zone = 'z2'; + +sleep(10); // 由于异步抓取需要耗时,等待 10 秒后再查询状态 +list($ret, $err) = $bucketManager->asynchFetchStatus($zone, $id); +echo "\n====> asynch fetch status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_change_mime.php b/vendor/qiniu/php-sdk/examples/rs_batch_change_mime.php new file mode 100644 index 0000000..c5bd6b4 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_batch_change_mime.php @@ -0,0 +1,32 @@ + 'video/x-mp4', + 'qiniu.png' => 'image/x-png', + 'qiniu.jpg' => 'image/x-jpg' +); + +$ops = $bucketManager->buildBatchChangeMime($bucket, $keyMimePairs); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_change_type.php b/vendor/qiniu/php-sdk/examples/rs_batch_change_type.php new file mode 100644 index 0000000..a19d0d4 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_batch_change_type.php @@ -0,0 +1,45 @@ +batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_copy.php b/vendor/qiniu/php-sdk/examples/rs_batch_copy.php new file mode 100644 index 0000000..66c4d4d --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_batch_copy.php @@ -0,0 +1,40 @@ +buildBatchCopy($srcBucket, $keyPairs, $destBucket, true); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_delete.php b/vendor/qiniu/php-sdk/examples/rs_batch_delete.php new file mode 100644 index 0000000..ebcdbe6 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_batch_delete.php @@ -0,0 +1,32 @@ +buildBatchDelete($bucket, $keys); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php b/vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php new file mode 100644 index 0000000..928dd14 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php @@ -0,0 +1,39 @@ +buildBatchDeleteAfterDays($bucket, $keyDayPairs); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_move.php b/vendor/qiniu/php-sdk/examples/rs_batch_move.php new file mode 100644 index 0000000..01d8c91 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_batch_move.php @@ -0,0 +1,40 @@ +buildBatchMove($srcBucket, $keyPairs, $destBucket, true); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_restore_ar.php b/vendor/qiniu/php-sdk/examples/rs_batch_restore_ar.php new file mode 100644 index 0000000..b2f79d0 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_batch_restore_ar.php @@ -0,0 +1,41 @@ +batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_batch_stat.php b/vendor/qiniu/php-sdk/examples/rs_batch_stat.php new file mode 100644 index 0000000..88bc32e --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_batch_stat.php @@ -0,0 +1,32 @@ +buildBatchStat($bucket, $keys); +list($ret, $err) = $bucketManager->batch($ops); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_bucket_domains.php b/vendor/qiniu/php-sdk/examples/rs_bucket_domains.php new file mode 100644 index 0000000..3cc9cb3 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_bucket_domains.php @@ -0,0 +1,26 @@ +domains($bucket); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_buckets.php b/vendor/qiniu/php-sdk/examples/rs_buckets.php new file mode 100644 index 0000000..84263a9 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_buckets.php @@ -0,0 +1,25 @@ +buckets(true); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_change_mime.php b/vendor/qiniu/php-sdk/examples/rs_change_mime.php new file mode 100644 index 0000000..f4442aa --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_change_mime.php @@ -0,0 +1,29 @@ +changeMime($bucket, $key, $newMime); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_change_status.php b/vendor/qiniu/php-sdk/examples/rs_change_status.php new file mode 100644 index 0000000..bedf61c --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_change_status.php @@ -0,0 +1,29 @@ +changeStatus($bucket, $key, $status); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_change_type.php b/vendor/qiniu/php-sdk/examples/rs_change_type.php new file mode 100644 index 0000000..8b3201f --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_change_type.php @@ -0,0 +1,36 @@ +changeType($bucket, $key, $fileType); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_copy.php b/vendor/qiniu/php-sdk/examples/rs_copy.php new file mode 100644 index 0000000..aae4d96 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_copy.php @@ -0,0 +1,33 @@ +copy($srcBucket, $srcKey, $destBucket, $destKey, true); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_delete.php b/vendor/qiniu/php-sdk/examples/rs_delete.php new file mode 100644 index 0000000..ad97266 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_delete.php @@ -0,0 +1,27 @@ +delete($bucket, $key); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_delete_after_days.php b/vendor/qiniu/php-sdk/examples/rs_delete_after_days.php new file mode 100644 index 0000000..96e55de --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_delete_after_days.php @@ -0,0 +1,26 @@ +deleteAfterDays($bucket, $key, $days); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_download_urls.php b/vendor/qiniu/php-sdk/examples/rs_download_urls.php new file mode 100644 index 0000000..e803ddc --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_download_urls.php @@ -0,0 +1,19 @@ +/,一定要带访问协议,也就是 http:// 或者 https:// +$baseUrl = 'http://if-pri.qiniudn.com/qiniu.png?imageView2/1/h/500'; + +// 对链接进行签名,参考文档:https://developer.qiniu.com/kodo/manual/1656/download-private +$signedUrl = $auth->privateDownloadUrl($baseUrl); + +echo $signedUrl; diff --git a/vendor/qiniu/php-sdk/examples/rs_fetch.php b/vendor/qiniu/php-sdk/examples/rs_fetch.php new file mode 100644 index 0000000..5c1a5ab --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_fetch.php @@ -0,0 +1,43 @@ +fetch($url, $bucket, $key); +echo "=====> fetch $url to bucket: $bucket key: $key\n"; +if ($err !== null) { + var_dump($err); +} else { + print_r($ret); +} + +//---------------------------------------- demo2 ---------------------------------------- +// 不指定 key 时,以文件内容的 hash 作为文件名 + +$key = null; +list($ret, $err) = $bucketManager->fetch($url, $bucket, $key); +echo "=====> fetch $url to bucket: $bucket key: $(etag)\n"; +if ($err !== null) { + var_dump($err); +} else { + print_r($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_move.php b/vendor/qiniu/php-sdk/examples/rs_move.php new file mode 100644 index 0000000..a399665 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_move.php @@ -0,0 +1,29 @@ +move($srcBucket, $srcKey, $destBucket, $destKey, true); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_prefetch.php b/vendor/qiniu/php-sdk/examples/rs_prefetch.php new file mode 100644 index 0000000..28af115 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_prefetch.php @@ -0,0 +1,25 @@ +prefetch($bucket, $key); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_restore.php b/vendor/qiniu/php-sdk/examples/rs_restore.php new file mode 100644 index 0000000..a3bf070 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_restore.php @@ -0,0 +1,28 @@ +restoreAr($bucket, $key, 1); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rs_stat.php b/vendor/qiniu/php-sdk/examples/rs_stat.php new file mode 100644 index 0000000..36e863e --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rs_stat.php @@ -0,0 +1,28 @@ +stat($bucket, $key); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rsf_list_bucket.php b/vendor/qiniu/php-sdk/examples/rsf_list_bucket.php new file mode 100644 index 0000000..97a5838 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rsf_list_bucket.php @@ -0,0 +1,47 @@ +listFiles($bucket, $prefix, $marker, $limit, $delimiter); + if ($err !== null) { + echo "\n====> list file err: \n"; + var_dump($err); + } else { + $marker = null; + if (array_key_exists('marker', $ret)) { + $marker = $ret['marker']; + } + echo "Marker: $marker\n"; + echo "\nList Items====>\n"; + //var_dump($ret['items']); + print('items count:' . count($ret['items']) . "\n"); + if (array_key_exists('commonPrefixes', $ret)) { + print_r($ret['commonPrefixes']); + } + } +} while (!empty($marker)); diff --git a/vendor/qiniu/php-sdk/examples/rsf_list_files.php b/vendor/qiniu/php-sdk/examples/rsf_list_files.php new file mode 100644 index 0000000..31c455b --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rsf_list_files.php @@ -0,0 +1,39 @@ +listFiles($bucket, $prefix, $marker, $limit, $delimiter); +if ($err !== null) { + echo "\n====> list file err: \n"; + var_dump($err); +} else { + if (array_key_exists('marker', $ret)) { + echo "Marker:" . $ret["marker"] . "\n"; + } + echo "\nList Iterms====>\n"; +} diff --git a/vendor/qiniu/php-sdk/examples/rsf_v2list_bucket.php b/vendor/qiniu/php-sdk/examples/rsf_v2list_bucket.php new file mode 100644 index 0000000..5f9d763 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rsf_v2list_bucket.php @@ -0,0 +1,34 @@ +listFilesv2($bucket, $prefix, $marker, $limit, $delimiter, true); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rtc/README.md b/vendor/qiniu/php-sdk/examples/rtc/README.md new file mode 100644 index 0000000..c7fff4d --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rtc/README.md @@ -0,0 +1,34 @@ +# Rtc Streaming Cloud Server-Side Library For PHP + +## Features + +- RoomToken 签发 + - [x] 生成 RoomToken: client->appToken() + +- App 管理 + - [x] 创建应用: client->createApp() + - [x] 获取应用配置信息: client->getApp() + - [x] 更新应用配置信息: client->updateApp() + - [x] 删除应用: client->deleteApp() + +- 房间管理 + - [x] 列举房间下的所有用户: client->listUser() + - [x] 指定一个用户踢出房间: client->kickUser() + - [x] 停止一个房间的合流转推: client->stopMerge() + - [x] 获取当前所有活跃的房间: client->listActiveRooms() + +## Demo +- RoomToken 签发 + - [生成 RoomToken](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_create_roomToken.php) + +- App 管理 + - [创建应用](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_createApp.php) + - [获取应用配置信息](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_getApp.php) + - [更新应用配置信息](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_updateApp.php) + - [删除应用](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_deleteApp.php) + +- 房间管理 + - [列举房间下的所有用户](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_rooms_listUser.php) + - [指定一个用户踢出房间](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_rooms_kickUser.php) + - [停止一个房间的合流转推](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_rooms_stopMerge.php) + - [获取当前所有活跃的房间](https://github.com/qiniu/php-sdk/tree/master/examples/rtc/rtc_rooms_listActiveRooms.php) \ No newline at end of file diff --git a/vendor/qiniu/php-sdk/examples/rtc/rtc_createApp.php b/vendor/qiniu/php-sdk/examples/rtc/rtc_createApp.php new file mode 100644 index 0000000..039eadd --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rtc/rtc_createApp.php @@ -0,0 +1,32 @@ +createApp($hub, $title, $maxUsers); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Create Successfully: \n"; + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rtc/rtc_create_roomToken.php b/vendor/qiniu/php-sdk/examples/rtc/rtc_create_roomToken.php new file mode 100644 index 0000000..6a62aa2 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rtc/rtc_create_roomToken.php @@ -0,0 +1,34 @@ +appToken($appId, $roomName, $userId, $expireAt, $permission); +echo "\n====> Create RoomToken Successfully: \n"; +var_dump($RoomToken); diff --git a/vendor/qiniu/php-sdk/examples/rtc/rtc_deleteApp.php b/vendor/qiniu/php-sdk/examples/rtc/rtc_deleteApp.php new file mode 100644 index 0000000..68bff33 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rtc/rtc_deleteApp.php @@ -0,0 +1,25 @@ +deleteApp($appId); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Delete $appId Successfully \n"; +} diff --git a/vendor/qiniu/php-sdk/examples/rtc/rtc_getApp.php b/vendor/qiniu/php-sdk/examples/rtc/rtc_getApp.php new file mode 100644 index 0000000..9f8e374 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rtc/rtc_getApp.php @@ -0,0 +1,26 @@ +getApp($appId); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> $appId Conf: \n"; + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_kickUser.php b/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_kickUser.php new file mode 100644 index 0000000..019c3f2 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_kickUser.php @@ -0,0 +1,31 @@ +kickUser($appId, $roomName, $userId); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Kick User $userId Successfully \n"; +} diff --git a/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_listActiveRooms.php b/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_listActiveRooms.php new file mode 100644 index 0000000..16e6027 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_listActiveRooms.php @@ -0,0 +1,35 @@ +listActiveRooms($appId, $prefix, $offset, $limit); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Active Rooms:\n"; + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_listUser.php b/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_listUser.php new file mode 100644 index 0000000..a839728 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_listUser.php @@ -0,0 +1,29 @@ +listUser($appId, $roomName); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> User List: \n"; + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_stopMerge.php b/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_stopMerge.php new file mode 100644 index 0000000..e140907 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rtc/rtc_rooms_stopMerge.php @@ -0,0 +1,28 @@ +stopMerge($appId, $roomName); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Stop Merge Successfully \n"; +} diff --git a/vendor/qiniu/php-sdk/examples/rtc/rtc_updateApp.php b/vendor/qiniu/php-sdk/examples/rtc/rtc_updateApp.php new file mode 100644 index 0000000..f771075 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/rtc/rtc_updateApp.php @@ -0,0 +1,40 @@ +updateApp($appId, $hub, $title, $maxUsers, false, $mergePublishRtmp); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Update $appId Conf Successfully: \n"; + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/saveas.php b/vendor/qiniu/php-sdk/examples/saveas.php new file mode 100644 index 0000000..5d51ef4 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/saveas.php @@ -0,0 +1,33 @@ +为生成缩略图的文件名 +$entry = ':'; + +// 生成的值 +$encodedEntryURI = \Qiniu\base64_urlSafeEncode($entry); + +// 使用 SecretKey 对新的下载 URL 进行 HMAC1-SHA1 签名 +$newurl = "78re52.com1.z0.glb.clouddn.com/resource/Ship.jpg?imageView2/2/w/200/h/200|saveas/" . $encodedEntryURI; + +$sign = hash_hmac("sha1", $newurl, $secretKey, true); + +// 对签名进行 URL 安全的 Base64 编码 +$encodedSign = \Qiniu\base64_urlSafeEncode($sign); + +// 最终得到的完整下载 URL +$finalURL = "http://" . $newurl . "/sign/" . $accessKey . ":" . $encodedSign; + +$callbackBody = file_get_contents("$finalURL"); + +echo $callbackBody; diff --git a/vendor/qiniu/php-sdk/examples/sms/README.md b/vendor/qiniu/php-sdk/examples/sms/README.md new file mode 100644 index 0000000..8c80a38 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/README.md @@ -0,0 +1,45 @@ +# SMS Server-Side Library For PHP + +## Features + +- 签名管理 + - [x] 创建签名: client->createSignature() + - [x] 列出签名: client->checkSignature() + - [x] 查询单个签名: client->checkSingleSignature() + - [x] 编辑签名: client->updateSignature() + - [x] 删除签名: client->deleteSignature() + +- 模板管理 + - [x] 创建模板: client->createTemplate() + - [x] 列出模板: client->queryTemplate() + - [x] 查询单个模板: client->querySingleTemplate() + - [x] 编辑模板: client->updateTemplate() + - [x] 删除模板: client->deleteTemplate() + +- 发送短信 + - [x] 发送短信: client->sendMessage() + +- 查询发送记录 + - [x] 查询发送记录: client->querySendSms() + +## Demo + +- 签名管理 + - [创建签名](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_create_signature.php) + - [列出签名](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_query_signature.php) + - [查询单个签名](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_query_single_signature.php) + - [编辑签名](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_edit_signature.php) + - [删除签名](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_delete_signature.php) + +- 模板管理 + - [创建模板](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_create_template.php) + - [列出模板](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_query_template.php) + - [查询单个模板](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_query_single_template.php) + - [编辑模板](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_edit_template.php) + - [删除模板](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_delete_template.php) + +- 发送短信 + - [发送短信](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_send_message.php) + +- 查询发送记录 + - [查询发送记录](https://github.com/qiniu/php-sdk/tree/master/examples/sms/sms_query_send_sms.php) diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_create_signature.php b/vendor/qiniu/php-sdk/examples/sms/sms_create_signature.php new file mode 100644 index 0000000..ea1f158 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_create_signature.php @@ -0,0 +1,29 @@ +createSignature($signature, $source, $pics); + +echo "\n====> create signature result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_create_template.php b/vendor/qiniu/php-sdk/examples/sms/sms_create_template.php new file mode 100644 index 0000000..3cb3874 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_create_template.php @@ -0,0 +1,33 @@ +createTemplate($name, $template, $type, $description, $signature_id); + +echo "\n====> create signature result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_delete_signature.php b/vendor/qiniu/php-sdk/examples/sms/sms_delete_signature.php new file mode 100644 index 0000000..fd873fa --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_delete_signature.php @@ -0,0 +1,25 @@ +deleteSignature($signature_id); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Delete Signature $signature_id Successfully\n"; +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_delete_template.php b/vendor/qiniu/php-sdk/examples/sms/sms_delete_template.php new file mode 100644 index 0000000..4590835 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_delete_template.php @@ -0,0 +1,25 @@ +deleteTemplate($template_id); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Delete Template $template_id Successfully\n"; +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_edit_signature.php b/vendor/qiniu/php-sdk/examples/sms/sms_edit_signature.php new file mode 100644 index 0000000..edf14e0 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_edit_signature.php @@ -0,0 +1,30 @@ +updateSignature($id, $signature, $source, $pics); + +echo "\n====> edit signature result: \n"; +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Update Signature Successfully\n"; +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_edit_template.php b/vendor/qiniu/php-sdk/examples/sms/sms_edit_template.php new file mode 100644 index 0000000..1be5509 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_edit_template.php @@ -0,0 +1,31 @@ +updateTemplate($template_id, $name, $template, $description, $signature_id); + +echo "\n====> edit template result: \n"; +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Update Template Successfully\n"; +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_query_send_sms.php b/vendor/qiniu/php-sdk/examples/sms/sms_query_send_sms.php new file mode 100644 index 0000000..cdbbe71 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_query_send_sms.php @@ -0,0 +1,50 @@ +querySendSms( + $job_id, + $message_id, + $mobile, + $status, + $template_id, + $type, + $start, + $end, + $page, + $page_size +); +echo "\n====> query send sms result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_query_signature.php b/vendor/qiniu/php-sdk/examples/sms/sms_query_signature.php new file mode 100644 index 0000000..224d09b --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_query_signature.php @@ -0,0 +1,28 @@ +querySignature($audit_status, $page, $page_size); +echo "\n====> query signature result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_query_single_signature.php b/vendor/qiniu/php-sdk/examples/sms/sms_query_single_signature.php new file mode 100644 index 0000000..8afb4d5 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_query_single_signature.php @@ -0,0 +1,26 @@ +checkSingleSignature($signature_id); +echo "\n====> query single signature result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_query_single_template.php b/vendor/qiniu/php-sdk/examples/sms/sms_query_single_template.php new file mode 100644 index 0000000..8e0b279 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_query_single_template.php @@ -0,0 +1,26 @@ +querySingleTemplate($template_id); +echo "\n====> query single template result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_query_template.php b/vendor/qiniu/php-sdk/examples/sms/sms_query_template.php new file mode 100644 index 0000000..6be260e --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_query_template.php @@ -0,0 +1,28 @@ +queryTemplate($audit_status, $page, $page_size); +echo "\n====> query template result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/sms/sms_send_message.php b/vendor/qiniu/php-sdk/examples/sms/sms_send_message.php new file mode 100644 index 0000000..d943e52 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/sms/sms_send_message.php @@ -0,0 +1,32 @@ + 'xxxx'); + +list($ret, $err) = $client->sendMessage($template_id, $mobiles, $code); +if ($err !== null) { + var_dump($err); +} else { + echo "\n====> Send Message Successfully: \n"; + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/update_bucketEvent.php b/vendor/qiniu/php-sdk/examples/update_bucketEvent.php new file mode 100644 index 0000000..7b0d1d0 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/update_bucketEvent.php @@ -0,0 +1,31 @@ +updateBucketEvent($bucket, $name, $prefix, $suffix, $event, $callbackURL); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/update_bucketLifecycleRule.php b/vendor/qiniu/php-sdk/examples/update_bucketLifecycleRule.php new file mode 100644 index 0000000..73f0f56 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/update_bucketLifecycleRule.php @@ -0,0 +1,36 @@ +updateBucketLifecycleRule( + $bucket, + $name, + $prefix, + $delete_after_days, + $to_line_after_days +); +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/upload_and_callback.php b/vendor/qiniu/php-sdk/examples/upload_and_callback.php new file mode 100644 index 0000000..a0c793a --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/upload_and_callback.php @@ -0,0 +1,31 @@ + 'http://your.domain.com/upload_verify_callback.php', + 'callbackBody' => 'filename=$(fname)&filesize=$(fsize)' +); +$uptoken = $auth->uploadToken($bucket, null, 3600, $policy); + +// 上传文件的本地路径 +$filePath = './php-logo.png'; + +$uploadMgr = new UploadManager(); +list($ret, $err) = $uploadMgr->putFile($uptoken, null, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/upload_and_pfop.php b/vendor/qiniu/php-sdk/examples/upload_and_pfop.php new file mode 100644 index 0000000..32c1eb5 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/upload_and_pfop.php @@ -0,0 +1,49 @@ + $pfop, + 'persistentNotifyUrl' => $notifyUrl, + 'persistentPipeline' => $pipeline +); +$token = $auth->uploadToken($bucket, null, 3600, $policy); + +list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/upload_mgr_init.php b/vendor/qiniu/php-sdk/examples/upload_mgr_init.php new file mode 100644 index 0000000..1164c90 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/upload_mgr_init.php @@ -0,0 +1,19 @@ +uploadToken($bucket); + +// 构建 UploadManager 对象 +$uploadMgr = new UploadManager(); diff --git a/vendor/qiniu/php-sdk/examples/upload_multi_demos.php b/vendor/qiniu/php-sdk/examples/upload_multi_demos.php new file mode 100644 index 0000000..d724235 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/upload_multi_demos.php @@ -0,0 +1,89 @@ +uploadToken($bucket); +$uploadMgr = new UploadManager(); + +//---------------------------------------- upload demo1 ---------------------------------------- +// 上传字符串到七牛 + +list($ret, $err) = $uploadMgr->put($token, null, 'content string'); +echo "\n====> put result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} + + +//---------------------------------------- upload demo2 ---------------------------------------- +// 上传文件到七牛 + +$filePath = './php-logo.png'; +$key = 'php-logo.png'; +list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} + + +//---------------------------------------- upload demo3 ---------------------------------------- +// 上传文件到七牛后, 七牛将文件名和文件大小回调给业务服务器. +// 可参考文档: https://developer.qiniu.com/kodo/manual/1206/put-policy + +$policy = array( + 'callbackUrl' => 'http://172.30.251.210/upload_verify_callback.php', + 'callbackBody' => 'filename=$(fname)&filesize=$(fsize)' +// 'callbackBodyType' => 'application/json', +// 'callbackBody' => '{"filename":$(fname), "filesize": $(fsize)}' //设置application/json格式回调 +); +$token = $auth->uploadToken($bucket, null, 3600, $policy); + + +list($ret, $err) = $uploadMgr->putFile($token, null, $key); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} + + +//---------------------------------------- upload demo4 ---------------------------------------- +// 上传视频,上传完成后进行 m3u8 的转码, 并给视频打水印 + +$wmImg = Qiniu\base64_urlSafeEncode('http://devtools.qiniudn.com/qiniu.png'); +$pfop = "avthumb/m3u8/wmImage/$wmImg"; + +// 转码完成后回调到业务服务器。(公网可以访问,并相应 200 OK) +$notifyUrl = 'http://notify.fake.com'; + +$policy = array( + 'persistentOps' => $pfop, + 'persistentNotifyUrl' => $notifyUrl, + 'persistentPipeline' => $pipeline +); +$token = $auth->uploadToken($bucket, null, 3600, $policy); +print($token); +list($ret, $err) = $uploadMgr->putFile($token, null, $key); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/upload_simple_file.php b/vendor/qiniu/php-sdk/examples/upload_simple_file.php new file mode 100644 index 0000000..f495a02 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/upload_simple_file.php @@ -0,0 +1,37 @@ +uploadToken($bucket); + +// 要上传文件的本地路径 +$filePath = './php-logo.png'; + +// 上传到七牛存储后保存的文件名 +$key = 'my-php-logo.png'; + +// 初始化 UploadManager 对象并进行文件的上传。 +$uploadMgr = new UploadManager(); + +// 调用 UploadManager 的 putFile 方法进行文件的上传,该方法会判断文件大小,进而决定使用表单上传还是分片上传,无需手动配置。 +list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/upload_tokens.php b/vendor/qiniu/php-sdk/examples/upload_tokens.php new file mode 100644 index 0000000..d2cf02c --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/upload_tokens.php @@ -0,0 +1,82 @@ +uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo2 ---------------------------------------- +// 自定义凭证有效期(示例2小时) + +$expires = 7200; +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo3 ---------------------------------------- +// 覆盖上传凭证 + +$expires = 3600; +$keyToOverwrite = 'qiniu.mp4'; +$upToken = $auth->uploadToken($bucket, $keyToOverwrite, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo4 ---------------------------------------- +// 自定义上传回复(非callback模式)凭证 + +$returnBody = '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}'; +$policy = array( + 'returnBody' => $returnBody +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo5 ---------------------------------------- +// 带回调业务服务器的凭证(application/json) + +$policy = array( + 'callbackUrl' => 'http://api.example.com/qiniu/upload/callback', + 'callbackBody' => '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}', + 'callbackBodyType' => 'application/json' +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo6 ---------------------------------------- +// 带回调业务服务器的凭证(application/x-www-form-urlencoded) + +$policy = array( + 'callbackUrl' => 'http://api.example.com/qiniu/upload/callback', + 'callbackBody' => 'key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)' +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//---------------------------------------- demo7 ---------------------------------------- +// 带数据处理的凭证 + +$saveMp4Entry = \Qiniu\base64_urlSafeEncode($bucket . ":avthumb_test_target.mp4"); +$saveJpgEntry = \Qiniu\base64_urlSafeEncode($bucket . ":vframe_test_target.jpg"); +$avthumbMp4Fop = "avthumb/mp4|saveas/" . $saveMp4Entry; +$vframeJpgFop = "vframe/jpg/offset/1|saveas/" . $saveJpgEntry; +$policy = array( + 'persistentOps' => $avthumbMp4Fop . ";" . $vframeJpgFop, + 'persistentPipeline' => "video-pipe", + 'persistentNotifyUrl' => "http://api.example.com/qiniu/pfop/notify", +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); diff --git a/vendor/qiniu/php-sdk/examples/upload_verify_callback.php b/vendor/qiniu/php-sdk/examples/upload_verify_callback.php new file mode 100644 index 0000000..dcb64c9 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/upload_verify_callback.php @@ -0,0 +1,34 @@ +verifyCallback($contentType, $authorization, $url, $callbackBody); + +if ($isQiniuCallback) { + $resp = array('ret' => 'success'); +} else { + $resp = array('ret' => 'failed'); +} + +echo json_encode($resp); diff --git a/vendor/qiniu/php-sdk/examples/upload_with_qvmzone.php b/vendor/qiniu/php-sdk/examples/upload_with_qvmzone.php new file mode 100644 index 0000000..ce2b21f --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/upload_with_qvmzone.php @@ -0,0 +1,40 @@ +uploadToken($bucket); + +// 上传文件的本地路径 +$filePath = './php-logo.png'; + +// 七牛云主机QVM和七牛对象存储KODO内网上传,目前支持华东1区域(杭州)和华北2区域(北京)的云主机可以访问同区域的对象存储服务 +// 参考文档:https://developer.qiniu.com/qvm/manual/4269/qvm-kodo + +$zone = Zone::qvmZonez0(); // 华东:z0,华北:z1 +$config = new Config($zone); +$config->useHTTPS = true; + +// 指定 config +$uploadMgr = new UploadManager($config); + +list($ret, $err) = $uploadMgr->putFile($uptoken, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/examples/upload_with_zone.php b/vendor/qiniu/php-sdk/examples/upload_with_zone.php new file mode 100644 index 0000000..6192666 --- /dev/null +++ b/vendor/qiniu/php-sdk/examples/upload_with_zone.php @@ -0,0 +1,39 @@ +uploadToken($bucket); + +// 上传文件的本地路径 +$filePath = './php-logo.png'; + +// 指定 zone 上传 +// 参考文档:https://developer.qiniu.com/kodo/manual/1671/region-endpoint +$zone = Zone::zonez0(); // 华东:z0,华北:z1,华南:z2,北美:na0,东南亚:as0 +$config = new Config($zone); +$config->useHTTPS = true; + +// 指定 config +$uploadMgr = new UploadManager($config); + +list($ret, $err) = $uploadMgr->putFile($uptoken, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/vendor/qiniu/php-sdk/phpunit.xml.dist b/vendor/qiniu/php-sdk/phpunit.xml.dist new file mode 100644 index 0000000..840f6e5 --- /dev/null +++ b/vendor/qiniu/php-sdk/phpunit.xml.dist @@ -0,0 +1,18 @@ + + + + + tests + + + + diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Auth.php b/vendor/qiniu/php-sdk/src/Qiniu/Auth.php new file mode 100644 index 0000000..6da2be4 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Auth.php @@ -0,0 +1,285 @@ +accessKey = $accessKey; + $this->secretKey = $secretKey; + $defaultOptions = array( + 'disableQiniuTimestampSignature' => null + ); + if ($options == null) { + $options = $defaultOptions; + } + $this->options = array_merge($defaultOptions, $options); + } + + public function getAccessKey() + { + return $this->accessKey; + } + + public function sign($data) + { + $hmac = hash_hmac('sha1', $data, $this->secretKey, true); + return $this->accessKey . ':' . \Qiniu\base64_urlSafeEncode($hmac); + } + + public function signWithData($data) + { + $encodedData = \Qiniu\base64_urlSafeEncode($data); + return $this->sign($encodedData) . ':' . $encodedData; + } + + public function signRequest($urlString, $body, $contentType = null) + { + $url = parse_url($urlString); + $data = ''; + if (array_key_exists('path', $url)) { + $data = $url['path']; + } + if (array_key_exists('query', $url)) { + $data .= '?' . $url['query']; + } + $data .= "\n"; + + if ($body !== null && $contentType === 'application/x-www-form-urlencoded') { + $data .= $body; + } + return $this->sign($data); + } + + /** + * @param string $urlString + * @param string $method + * @param string $body + * @param null|Header $headers + */ + public function signQiniuAuthorization($urlString, $method = "GET", $body = "", $headers = null) + { + $url = parse_url($urlString); + if (!$url) { + return array(null, new \Exception("parse_url error")); + } + + // append method, path and query + if ($method === "") { + $data = "GET "; + } else { + $data = $method . " "; + } + if (isset($url["path"])) { + $data .= $url["path"]; + } + if (isset($url["query"])) { + $data .= "?" . $url["query"]; + } + + // append Host + $data .= "\n"; + $data .= "Host: "; + if (isset($url["host"])) { + $data .= $url["host"]; + } + if (isset($url["port"]) && $url["port"] > 0) { + $data .= ":" . $url["port"]; + } + + // try to append content type + if ($headers != null && isset($headers["Content-Type"])) { + // append content type + $data .= "\n"; + $data .= "Content-Type: " . $headers["Content-Type"]; + } + + // try append xQiniuHeaders + if ($headers != null) { + $headerLines = array(); + $keyPrefix = "X-Qiniu-"; + foreach ($headers as $k => $v) { + if (strlen($k) > strlen($keyPrefix) && strpos($k, $keyPrefix) === 0) { + array_push( + $headerLines, + $k . ": " . $v + ); + } + } + if (count($headerLines) > 0) { + $data .= "\n"; + sort($headerLines); + $data .= implode("\n", $headerLines); + } + } + + // append body + $data .= "\n\n"; + if (!is_null($body) + && strlen($body) > 0 + && isset($headers["Content-Type"]) + && $headers["Content-Type"] != "application/octet-stream" + ) { + $data .= $body; + } + + return array($this->sign($data), null); + } + + public function verifyCallback( + $contentType, + $originAuthorization, + $url, + $body, + $method = "GET", + $headers = array() + ) { + if (strpos($originAuthorization, 'Qiniu') === 0) { + $qnHeaders = new Header($headers); + if (!isset($qnHeaders['Content-Type'])) { + $qnHeaders['Content-Type'] = $contentType; + } + list($sign, $err) = $this->signQiniuAuthorization( + $url, + $method, + $body, + $qnHeaders + ); + if ($err !== null) { + return false; + } + $authorization = 'Qiniu ' . $sign; + } else { + $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType); + } + return $originAuthorization === $authorization; + } + + public function privateDownloadUrl($baseUrl, $expires = 3600) + { + $deadline = time() + $expires; + + $pos = strpos($baseUrl, '?'); + if ($pos !== false) { + $baseUrl .= '&e='; + } else { + $baseUrl .= '?e='; + } + $baseUrl .= $deadline; + + $token = $this->sign($baseUrl); + return "$baseUrl&token=$token"; + } + + public function uploadToken($bucket, $key = null, $expires = 3600, $policy = null, $strictPolicy = true) + { + $deadline = time() + $expires; + $scope = $bucket; + if ($key !== null) { + $scope .= ':' . $key; + } + + $args = self::copyPolicy($args, $policy, $strictPolicy); + $args['scope'] = $scope; + $args['deadline'] = $deadline; + + $b = json_encode($args); + return $this->signWithData($b); + } + + /** + *上传策略,参数规格详见 + *http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html + */ + private static $policyFields = array( + 'callbackUrl', + 'callbackBody', + 'callbackHost', + 'callbackBodyType', + 'callbackFetchKey', + + 'returnUrl', + 'returnBody', + + 'endUser', + 'saveKey', + 'forceSaveKey', + 'insertOnly', + + 'detectMime', + 'mimeLimit', + 'fsizeMin', + 'fsizeLimit', + + 'persistentOps', // 与 persistentWorkflowTemplateID 二选一 + 'persistentNotifyUrl', + 'persistentPipeline', + 'persistentType', // 为 `1` 时开启闲时任务 + 'persistentWorkflowTemplateID', // 与 persistentOps 二选一 + + 'deleteAfterDays', + 'fileType', + 'isPrefixalScope', + + 'transform', // deprecated + 'transformFallbackKey', // deprecated + 'transformFallbackMode', // deprecated + ); + + private static function copyPolicy(&$policy, $originPolicy, $strictPolicy) + { + if ($originPolicy === null) { + return array(); + } + foreach ($originPolicy as $key => $value) { + if (!$strictPolicy || in_array((string)$key, self::$policyFields, true)) { + $policy[$key] = $value; + } + } + return $policy; + } + + public function authorization($url, $body = null, $contentType = null) + { + $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType); + return array('Authorization' => $authorization); + } + + public function authorizationV2($url, $method, $body = null, $contentType = null) + { + $headers = new Header(); + $result = array(); + if ($contentType != null) { + $headers['Content-Type'] = $contentType; + $result['Content-Type'] = $contentType; + } + + $signDate = gmdate('Ymd\THis\Z', time()); + if ($this->options['disableQiniuTimestampSignature'] !== null) { + if (!$this->options['disableQiniuTimestampSignature']) { + $headers['X-Qiniu-Date'] = $signDate; + $result['X-Qiniu-Date'] = $signDate; + } + } elseif (getenv("DISABLE_QINIU_TIMESTAMP_SIGNATURE")) { + if (strtolower(getenv("DISABLE_QINIU_TIMESTAMP_SIGNATURE")) !== "true") { + $headers['X-Qiniu-Date'] = $signDate; + $result['X-Qiniu-Date'] = $signDate; + } + } else { + $headers['X-Qiniu-Date'] = $signDate; + $result['X-Qiniu-Date'] = $signDate; + } + + list($sign) = $this->signQiniuAuthorization($url, $method, $body, $headers); + $result['Authorization'] = 'Qiniu ' . $sign; + return $result; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php b/vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php new file mode 100644 index 0000000..60052d3 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php @@ -0,0 +1,263 @@ +auth = $auth; + $this->server = 'http://fusion.qiniuapi.com'; + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * @param array $urls 待刷新的文件链接数组 + * @return array + */ + public function refreshUrls(array $urls) + { + return $this->refreshUrlsAndDirs($urls, array()); + } + + /** + * @param array $dirs 待刷新的文件链接数组 + * @return array + * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh + * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category + */ + public function refreshDirs(array $dirs) + { + return $this->refreshUrlsAndDirs(array(), $dirs); + } + + /** + * @param array $urls 待刷新的文件链接数组 + * @param array $dirs 待刷新的目录链接数组 + * + * @return array 刷新的请求回复和错误,参考 examples/cdn_manager.php 代码 + * @link http://developer.qiniu.com/article/fusion/api/refresh.html + * + * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh + * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category + */ + public function refreshUrlsAndDirs(array $urls, array $dirs) + { + $req = array(); + if (!empty($urls)) { + $req['urls'] = $urls; + } + if (!empty($dirs)) { + $req['dirs'] = $dirs; + } + + $url = $this->server . '/v2/tune/refresh'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * 查询 CDN 刷新记录 + * + * @param string $requestId 指定要查询记录所在的刷新请求id + * @param string $isDir 指定是否查询目录,取值为 yes/no,默认不填则为两种类型记录都查询 + * @param array $urls 要查询的url列表,每个url可以是文件url,也可以是目录url + * @param string $state 指定要查询记录的状态,取值processing/success/failure + * @param int $pageNo 要求返回的页号,默认为0 + * @param int $pageSize 要求返回的页长度,默认为100 + * @param string $startTime 指定查询的开始日期,格式2006-01-01 + * @param string $endTime 指定查询的结束日期,格式2006-01-01 + * @return array + * @link https://developer.qiniu.com/fusion/api/1229/cache-refresh#4 + */ + public function getCdnRefreshList( + $requestId = null, + $isDir = null, + $urls = array(), + $state = null, + $pageNo = 0, + $pageSize = 100, + $startTime = null, + $endTime = null + ) { + $req = array(); + \Qiniu\setWithoutEmpty($req, 'requestId', $requestId); + \Qiniu\setWithoutEmpty($req, 'isDir', $isDir); + \Qiniu\setWithoutEmpty($req, 'urls', $urls); + \Qiniu\setWithoutEmpty($req, 'state', $state); + \Qiniu\setWithoutEmpty($req, 'pageNo', $pageNo); + \Qiniu\setWithoutEmpty($req, 'pageSize', $pageSize); + \Qiniu\setWithoutEmpty($req, 'startTime', $startTime); + \Qiniu\setWithoutEmpty($req, 'endTime', $endTime); + + $body = json_encode($req); + $url = $this->server . '/v2/tune/refresh/list'; + return $this->post($url, $body); + } + + /** + * @param array $urls 待预取的文件链接数组 + * + * @return array 预取的请求回复和错误,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/refresh.html + */ + public function prefetchUrls(array $urls) + { + $req = array( + 'urls' => $urls, + ); + + $url = $this->server . '/v2/tune/prefetch'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * 查询 CDN 预取记录 + * + * @param string $requestId 指定要查询记录所在的刷新请求id + * @param array $urls 要查询的url列表,每个url可以是文件url,也可以是目录url + * @param string $state 指定要查询记录的状态,取值processing/success/failure + * @param int $pageNo 要求返回的页号,默认为0 + * @param int $pageSize 要求返回的页长度,默认为100 + * @param string $startTime 指定查询的开始日期,格式2006-01-01 + * @param string $endTime 指定查询的结束日期,格式2006-01-01 + * @return array + * @link https://developer.qiniu.com/fusion/api/1227/file-prefetching#4 + */ + public function getCdnPrefetchList( + $requestId = null, + $urls = array(), + $state = null, + $pageNo = 0, + $pageSize = 100, + $startTime = null, + $endTime = null + ) { + $req = array(); + \Qiniu\setWithoutEmpty($req, 'requestId', $requestId); + \Qiniu\setWithoutEmpty($req, 'urls', $urls); + \Qiniu\setWithoutEmpty($req, 'state', $state); + \Qiniu\setWithoutEmpty($req, 'pageNo', $pageNo); + \Qiniu\setWithoutEmpty($req, 'pageSize', $pageSize); + \Qiniu\setWithoutEmpty($req, 'startTime', $startTime); + \Qiniu\setWithoutEmpty($req, 'endTime', $endTime); + + $body = json_encode($req); + $url = $this->server . '/v2/tune/prefetch/list'; + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取带宽数据的域名数组 + * @param string $startDate 开始的日期,格式类似 2017-01-01 + * @param string $endDate 结束的日期,格式类似 2017-01-01 + * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day + * + * @return array 带宽数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html + */ + public function getBandwidthData(array $domains, $startDate, $endDate, $granularity) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['startDate'] = $startDate; + $req['endDate'] = $endDate; + $req['granularity'] = $granularity; + + $url = $this->server . '/v2/tune/bandwidth'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取流量数据的域名数组 + * @param string $startDate 开始的日期,格式类似 2017-01-01 + * @param string $endDate 结束的日期,格式类似 2017-01-01 + * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day + * + * @return array 流量数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html + */ + public function getFluxData(array $domains, $startDate, $endDate, $granularity) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['startDate'] = $startDate; + $req['endDate'] = $endDate; + $req['granularity'] = $granularity; + + $url = $this->server . '/v2/tune/flux'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取日志下载链接的域名数组 + * @param string $logDate 获取指定日期的日志下载链接,格式类似 2017-01-01 + * + * @return array 日志下载链接数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/log.html + */ + public function getCdnLogList(array $domains, $logDate) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['day'] = $logDate; + + $url = $this->server . '/v2/tune/log/list'; + $body = json_encode($req); + return $this->post($url, $body); + } + + private function post($url, $body) + { + $headers = $this->auth->authorization($url, $body, 'application/json'); + $headers['Content-Type'] = 'application/json'; + $ret = Client::post($url, $body, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + /** + * 构建时间戳防盗链鉴权的访问外链 + * + * @param string $rawUrl 需要签名的资源url + * @param string $encryptKey 时间戳防盗链密钥 + * @param string $durationInSeconds 链接的有效期(以秒为单位) + * + * @return string 带鉴权信息的资源外链,参考 examples/cdn_timestamp_antileech.php 代码 + */ + public static function createTimestampAntiLeechUrl($rawUrl, $encryptKey, $durationInSeconds) + { + $parsedUrl = parse_url($rawUrl); + $deadline = time() + $durationInSeconds; + $expireHex = dechex($deadline); + $path = isset($parsedUrl['path']) ? $parsedUrl['path'] : ''; + $strToSign = $encryptKey . $path . $expireHex; + $signStr = md5($strToSign); + if (isset($parsedUrl['query'])) { + $signedUrl = $rawUrl . '&sign=' . $signStr . '&t=' . $expireHex; + } else { + $signedUrl = $rawUrl . '?sign=' . $signStr . '&t=' . $expireHex; + } + return $signedUrl; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Config.php b/vendor/qiniu/php-sdk/src/Qiniu/Config.php new file mode 100644 index 0000000..3ce7fa5 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Config.php @@ -0,0 +1,398 @@ +zone = $z; + $this->useHTTPS = false; + $this->useCdnDomains = false; + $this->regionCache = array(); + $this->ucHost = Config::UC_HOST; + $this->queryRegionHost = Config::QUERY_REGION_HOST; + $this->backupQueryRegionHosts = array( + "kodo-config.qiniuapi.com", + "uc.qbox.me", + ); + $this->backupUcHostsRetryTimes = 2; + } + + public function setUcHost($ucHost) + { + $this->ucHost = $ucHost; + $this->setQueryRegionHost($ucHost); + } + + public function getUcHost() + { + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $this->ucHost; + } + + public function setQueryRegionHost($host, $backupHosts = array()) + { + $this->queryRegionHost = $host; + $this->backupQueryRegionHosts = $backupHosts; + } + + public function getQueryRegionHost() + { + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $this->queryRegionHost; + } + + public function setBackupQueryRegionHosts($hosts = array()) + { + $this->backupQueryRegionHosts = $hosts; + } + + public function getBackupQueryRegionHosts() + { + return $this->backupQueryRegionHosts; + } + + public function getUpHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $region->srcUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $region->cdnUpHosts[0]; + } + + return $scheme . $host; + } + + public function getUpHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $region->srcUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $region->cdnUpHosts[0]; + } + + return array($scheme . $host, null); + } + + public function getUpBackupHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $region->cdnUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $region->srcUpHosts[0]; + } + + return $scheme . $host; + } + + public function getUpBackupHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $region->cdnUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $region->srcUpHosts[0]; + } + + return array($scheme . $host, null); + } + + public function getRsHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->rsHost; + } + + public function getRsHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return array($scheme . $region->rsHost, null); + } + + public function getRsfHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->rsfHost; + } + + public function getRsfHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return array($scheme . $region->rsfHost, null); + } + + public function getIovipHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->iovipHost; + } + + public function getIovipHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return array($scheme . $region->iovipHost, null); + } + + public function getApiHost($accessKey, $bucket, $reqOpt = null) + { + $region = $this->getRegion($accessKey, $bucket, $reqOpt); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $region->apiHost; + } + + public function getApiHostV2($accessKey, $bucket, $reqOpt = null) + { + list($region, $err) = $this->getRegionV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return array($scheme . $region->apiHost, null); + } + + + /** + * 从缓存中获取区域 + * + * @param string $cacheId 缓存 ID + * @return null|Region + */ + private function getRegionCache($cacheId) + { + if (isset($this->regionCache[$cacheId]) && + isset($this->regionCache[$cacheId]["deadline"]) && + time() < $this->regionCache[$cacheId]["deadline"]) { + return $this->regionCache[$cacheId]["region"]; + } + + return null; + } + + /** + * 将区域设置到缓存中 + * + * @param string $cacheId 缓存 ID + * @param Region $region 缓存 ID + * @return void + */ + private function setRegionCache($cacheId, $region) + { + $this->regionCache[$cacheId] = array( + "region" => $region, + ); + if (isset($region->ttl)) { + $this->regionCache[$cacheId]["deadline"] = time() + $region->ttl; + } + } + + /** + * 从缓存中获取区域 + * + * @param string $accessKey + * @param string $bucket + * @return Region + * + * @throws \Exception + */ + private function getRegion($accessKey, $bucket, $reqOpt = null) + { + if (isset($this->zone)) { + return $this->zone; + } + + $cacheId = "$accessKey:$bucket"; + $regionCache = $this->getRegionCache($cacheId); + if ($regionCache) { + return $regionCache; + } + + $region = Zone::queryZone( + $accessKey, + $bucket, + $this->getQueryRegionHost(), + $this->getBackupQueryRegionHosts(), + $this->backupUcHostsRetryTimes, + $reqOpt + ); + if (is_array($region)) { + list($region, $err) = $region; + if ($err != null) { + throw new \Exception($err->message()); + } + } + + $this->setRegionCache($cacheId, $region); + return $region; + } + + private function getRegionV2($accessKey, $bucket, $reqOpt = null) + { + if (isset($this->zone)) { + return array($this->zone, null); + } + + $cacheId = "$accessKey:$bucket"; + $regionCache = $this->getRegionCache($cacheId); + if (isset($regionCache)) { + return array($regionCache, null); + } + + $region = Zone::queryZone( + $accessKey, + $bucket, + $this->getQueryRegionHost(), + $this->getBackupQueryRegionHosts(), + $this->backupUcHostsRetryTimes, + $reqOpt + ); + if (is_array($region)) { + list($region, $err) = $region; + return array($region, $err); + } + + $this->setRegionCache($cacheId, $region); + return array($region, null); + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Enum/QiniuEnum.php b/vendor/qiniu/php-sdk/src/Qiniu/Enum/QiniuEnum.php new file mode 100644 index 0000000..8399b54 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Enum/QiniuEnum.php @@ -0,0 +1,53 @@ + $val) { + array_push($data, '--' . $mimeBoundary); + array_push($data, "Content-Disposition: form-data; name=\"$key\""); + array_push($data, ''); + array_push($data, $val); + } + + array_push($data, '--' . $mimeBoundary); + $finalMimeType = empty($mimeType) ? 'application/octet-stream' : $mimeType; + $finalFileName = self::escapeQuotes($fileName); + array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$finalFileName\""); + array_push($data, "Content-Type: $finalMimeType"); + array_push($data, ''); + array_push($data, $fileBody); + + array_push($data, '--' . $mimeBoundary . '--'); + array_push($data, ''); + + $body = implode("\r\n", $data); + $contentType = 'multipart/form-data; boundary=' . $mimeBoundary; + $headers['Content-Type'] = $contentType; + $request = new Request('POST', $url, $headers, $body, $opt); + return self::sendRequest($request); + } + + private static function userAgent() + { + $sdkInfo = "QiniuPHP/" . Config::SDK_VER; + + $systemInfo = php_uname("s"); + $machineInfo = php_uname("m"); + + $envInfo = "($systemInfo/$machineInfo)"; + + $phpVer = phpversion(); + + $ua = "$sdkInfo $envInfo PHP/$phpVer"; + return $ua; + } + + /** + * @param Request $request + * @return Response + */ + public static function sendRequestWithMiddleware($request) + { + $middlewares = $request->opt->middlewares; + $handle = Middleware\compose($middlewares, function ($req) { + return Client::sendRequest($req); + }); + return $handle($request); + } + + /** + * @param Request $request + * @return Response + */ + public static function sendRequest($request) + { + $t1 = microtime(true); + $ch = curl_init(); + $options = array( + CURLOPT_USERAGENT => self::userAgent(), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_NOBODY => false, + CURLOPT_CUSTOMREQUEST => $request->method, + CURLOPT_URL => $request->url, + ); + foreach ($request->opt->getCurlOpt() as $k => $v) { + $options[$k] = $v; + } + // Handle open_basedir & safe mode + if (!ini_get('safe_mode') && !ini_get('open_basedir')) { + $options[CURLOPT_FOLLOWLOCATION] = true; + } + if (!empty($request->headers)) { + $headers = array(); + foreach ($request->headers as $key => $val) { + array_push($headers, "$key: $val"); + } + $options[CURLOPT_HTTPHEADER] = $headers; + } + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); + if (!empty($request->body)) { + $options[CURLOPT_POSTFIELDS] = $request->body; + } + curl_setopt_array($ch, $options); + $result = curl_exec($ch); + $t2 = microtime(true); + $duration = round($t2 - $t1, 3); + $ret = curl_errno($ch); + if ($ret !== 0) { + $r = new Response(-1, $duration, array(), null, curl_error($ch)); + curl_close($ch); + return $r; + } + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $headers = Header::parseRawText(substr($result, 0, $header_size)); + $body = substr($result, $header_size); + curl_close($ch); + return new Response($code, $duration, $headers, $body, null); + } + + private static function escapeQuotes($str) + { + if (is_null($str)) { + return null; + } + $find = array("\\", "\""); + $replace = array("\\\\", "\\\""); + return str_replace($find, $replace, $str); + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php new file mode 100644 index 0000000..8fba74f --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php @@ -0,0 +1,38 @@ + + * {"error" : "detailed error message"} + * + */ +final class Error +{ + private $url; + /** + * @var Response + */ + private $response; + + public function __construct($url, $response) + { + $this->url = $url; + $this->response = $response; + } + + public function code() + { + return $this->response->statusCode; + } + + public function getResponse() + { + return $this->response; + } + + public function message() + { + return $this->response->error; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Header.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Header.php new file mode 100644 index 0000000..1dcf328 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Header.php @@ -0,0 +1,291 @@ + $values) { + $normalizedKey = self::normalizeKey($key); + $normalizedValues = array(); + if (!is_array($values)) { + array_push( + $normalizedValues, + self::normalizeValue($values) + ); + } else { + foreach ($values as $value) { + array_push( + $normalizedValues, + self::normalizeValue($value) + ); + } + } + $this->data[$normalizedKey] = $normalizedValues; + } + return $this; + } + + /** + * return origin headers, which is field name case-sensitive + * + * @param string $raw + * + * @return array + */ + public static function parseRawText($raw) + { + $multipleHeaders = explode("\r\n\r\n", trim($raw)); + $headers = array(); + $headerLines = explode("\r\n", end($multipleHeaders)); + foreach ($headerLines as $line) { + $headerLine = trim($line); + $kv = explode(':', $headerLine); + if (count($kv) <= 1) { + continue; + } + // for http2 [Pseudo-Header Fields](https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.1) + if ($kv[0] == "") { + $fieldName = ":" . $kv[1]; + } else { + $fieldName = $kv[0]; + } + $fieldValue = trim(substr($headerLine, strlen($fieldName . ":"))); + if (isset($headers[$fieldName])) { + array_push($headers[$fieldName], $fieldValue); + } else { + $headers[$fieldName] = array($fieldValue); + } + } + return $headers; + } + + /** + * @param string $raw + * + * @return Header + */ + public static function fromRawText($raw) + { + return new Header(self::parseRawText($raw)); + } + + /** + * @param string $key + * + * @return string + */ + public static function normalizeKey($key) + { + $key = trim($key); + + if (!self::isValidKeyName($key)) { + return $key; + } + + return \Qiniu\ucwords(strtolower($key), '-'); + } + + /** + * @param string|numeric $value + * + * @return string|numeric + */ + public static function normalizeValue($value) + { + if (is_numeric($value)) { + return $value + 0; + } + return trim($value); + } + + /** + * @return array + */ + public function getRawData() + { + return $this->data; + } + + /** + * @param $offset string + * + * @return boolean + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function offsetExists($offset) + { + $key = self::normalizeKey($offset); + return isset($this->data[$key]); + } + + /** + * @param $offset string + * + * @return string|null + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function offsetGet($offset) + { + $key = self::normalizeKey($offset); + if (isset($this->data[$key]) && count($this->data[$key])) { + return $this->data[$key][0]; + } else { + return null; + } + } + + /** + * @param $offset string + * @param $value string + * + * @return void + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function offsetSet($offset, $value) + { + $key = self::normalizeKey($offset); + if (isset($this->data[$key]) && count($this->data[$key]) > 0) { + $this->data[$key][0] = self::normalizeValue($value); + } else { + $this->data[$key] = array(self::normalizeValue($value)); + } + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function offsetUnset($offset) + { + $key = self::normalizeKey($offset); + unset($this->data[$key]); + } + + /** + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function getIterator() + { + $arr = array(); + foreach ($this->data as $k => $v) { + $arr[$k] = $v[0]; + } + return new \ArrayIterator($arr); + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] // temporarily suppress the type check of php 8.x + public function count() + { + return count($this->data); + } + + private static $isTokenTable = array( + '!' => true, + '#' => true, + '$' => true, + '%' => true, + '&' => true, + '\'' => true, + '*' => true, + '+' => true, + '-' => true, + '.' => true, + '0' => true, + '1' => true, + '2' => true, + '3' => true, + '4' => true, + '5' => true, + '6' => true, + '7' => true, + '8' => true, + '9' => true, + 'A' => true, + 'B' => true, + 'C' => true, + 'D' => true, + 'E' => true, + 'F' => true, + 'G' => true, + 'H' => true, + 'I' => true, + 'J' => true, + 'K' => true, + 'L' => true, + 'M' => true, + 'N' => true, + 'O' => true, + 'P' => true, + 'Q' => true, + 'R' => true, + 'S' => true, + 'T' => true, + 'U' => true, + 'W' => true, + 'V' => true, + 'X' => true, + 'Y' => true, + 'Z' => true, + '^' => true, + '_' => true, + '`' => true, + 'a' => true, + 'b' => true, + 'c' => true, + 'd' => true, + 'e' => true, + 'f' => true, + 'g' => true, + 'h' => true, + 'i' => true, + 'j' => true, + 'k' => true, + 'l' => true, + 'm' => true, + 'n' => true, + 'o' => true, + 'p' => true, + 'q' => true, + 'r' => true, + 's' => true, + 't' => true, + 'u' => true, + 'v' => true, + 'w' => true, + 'x' => true, + 'y' => true, + 'z' => true, + '|' => true, + '~' => true, + ); + + /** + * @param string $str + * + * @return boolean + */ + private static function isValidKeyName($str) + { + for ($i = 0; $i < strlen($str); $i += 1) { + if (!isset(self::$isTokenTable[$str[$i]])) { + return false; + } + } + return true; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Middleware/Middleware.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Middleware/Middleware.php new file mode 100644 index 0000000..fe8a64c --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Middleware/Middleware.php @@ -0,0 +1,31 @@ + $middlewares + * @param callable(Request): Response $handler + * @return callable(Request): Response + */ +function compose($middlewares, $handler) +{ + $next = $handler; + foreach (array_reverse($middlewares) as $middleware) { + $next = function ($request) use ($middleware, $next) { + return $middleware->send($request, $next); + }; + } + return $next; +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Middleware/RetryDomainsMiddleware.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Middleware/RetryDomainsMiddleware.php new file mode 100644 index 0000000..829ab87 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Middleware/RetryDomainsMiddleware.php @@ -0,0 +1,76 @@ + backup domains. + */ + private $backupDomains; + + /** + * @var numeric max retry times for each backup domains. + */ + private $maxRetryTimes; + + /** + * @var callable args response and request; returns bool; If true will retry with backup domains. + */ + private $retryCondition; + + /** + * @param array $backupDomains + * @param numeric $maxRetryTimes + */ + public function __construct($backupDomains, $maxRetryTimes = 2, $retryCondition = null) + { + $this->backupDomains = $backupDomains; + $this->maxRetryTimes = $maxRetryTimes; + $this->retryCondition = $retryCondition; + } + + private function shouldRetry($resp, $req) + { + if (is_callable($this->retryCondition)) { + return call_user_func($this->retryCondition, $resp, $req); + } + + return !$resp || $resp->needRetry(); + } + + /** + * @param Request $request + * @param callable(Request): Response $next + * @return Response + */ + public function send($request, $next) + { + $response = null; + $urlComponents = parse_url($request->url); + + foreach (array_merge(array($urlComponents["host"]), $this->backupDomains) as $backupDomain) { + $urlComponents["host"] = $backupDomain; + $request->url = \Qiniu\unparse_url($urlComponents); + $retriedTimes = 0; + + while ($retriedTimes < $this->maxRetryTimes) { + $response = $next($request); + + $retriedTimes += 1; + + if (!$this->shouldRetry($response, $request)) { + return $response; + } + } + } + + if (!$response) { + $response = $next($request); + } + + return $response; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Proxy.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Proxy.php new file mode 100644 index 0000000..fac6ba1 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Proxy.php @@ -0,0 +1,34 @@ +proxy = $proxy; + $this->proxy_auth = $proxy_auth; + $this->proxy_user_password = $proxy_user_password; + } + + public function makeReqOpt() + { + $reqOpt = new RequestOptions(); + if ($this->proxy !== null) { + $reqOpt->proxy = $this->proxy; + } + if ($this->proxy_auth !== null) { + $reqOpt->proxy_auth = $this->proxy_auth; + } + if ($this->proxy_user_password !== null) { + $reqOpt->proxy_user_password = $this->proxy_user_password; + } + return $reqOpt; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php new file mode 100644 index 0000000..5a31bf6 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php @@ -0,0 +1,42 @@ + + */ + public $headers; + + /** + * @var mixed|null + */ + public $body; + + /** + * @var string + */ + public $method; + + /** + * @var RequestOptions + */ + public $opt; + + public function __construct($method, $url, array $headers = array(), $body = null, $opt = null) + { + $this->method = strtoupper($method); + $this->url = $url; + $this->headers = $headers; + $this->body = $body; + if ($opt === null) { + $opt = new RequestOptions(); + } + $this->opt = $opt; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/RequestOptions.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/RequestOptions.php new file mode 100644 index 0000000..be0c6d5 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/RequestOptions.php @@ -0,0 +1,104 @@ + + */ + public $middlewares; + + public function __construct( + $connection_timeout = null, + $connection_timeout_ms = null, + $timeout = null, + $timeout_ms = null, + $middlewares = array(), + $proxy = null, + $proxy_auth = null, + $proxy_user_password = null + ) { + $this->connection_timeout = $connection_timeout; + $this->connection_timeout_ms = $connection_timeout_ms; + $this->timeout = $timeout; + $this->timeout_ms = $timeout_ms; + $this->proxy = $proxy; + $this->proxy_auth = $proxy_auth; + $this->proxy_user_password = $proxy_user_password; + $this->middlewares = $middlewares; + } + + public function getCurlOpt() + { + $result = array(); + if ($this->connection_timeout != null) { + $result[CURLOPT_CONNECTTIMEOUT] = $this->connection_timeout; + } + if ($this->connection_timeout_ms != null) { + $result[CURLOPT_CONNECTTIMEOUT_MS] = $this->connection_timeout_ms; + } + if ($this->timeout != null) { + $result[CURLOPT_TIMEOUT] = $this->timeout; + } + if ($this->timeout_ms != null) { + $result[CURLOPT_TIMEOUT_MS] = $this->timeout_ms; + } + if ($this->proxy != null) { + $result[CURLOPT_PROXY] = $this->proxy; + } + if ($this->proxy_auth != null) { + $result[CURLOPT_PROXYAUTH] = $this->proxy_auth; + } + if ($this->proxy_user_password != null) { + $result[CURLOPT_PROXYUSERPWD] = $this->proxy_user_password; + } + return $result; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php b/vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php new file mode 100644 index 0000000..cd77903 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php @@ -0,0 +1,220 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Reserved for WebDAV advanced collections expired proposal', + 426 => 'Upgrade required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates (Experimental)', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ); + + /** + * @param int $code 状态码 + * @param double $duration 请求时长 + * @param array $headers 响应头部 + * @param string $body 响应内容 + * @param string $error 错误描述 + */ + public function __construct($code, $duration, array $headers = array(), $body = null, $error = null) + { + $this->statusCode = $code; + $this->duration = $duration; + $this->headers = array(); + $this->body = $body; + $this->error = $error; + $this->jsonData = null; + + if ($error !== null) { + return; + } + + foreach ($headers as $k => $vs) { + if (is_array($vs)) { + $this->headers[$k] = $vs[count($vs) - 1]; + } else { + $this->headers[$k] = $vs; + } + } + $this->normalizedHeaders = new Header($headers); + + if ($body === null) { + if ($code >= 400) { + $this->error = self::$statusTexts[$code]; + } + return; + } + if (self::isJson($this->normalizedHeaders)) { + try { + $jsonData = self::bodyJson($body); + if ($code >= 400) { + $this->error = $body; + if ($jsonData['error'] !== null) { + $this->error = $jsonData['error']; + } + } + $this->jsonData = $jsonData; + } catch (\InvalidArgumentException $e) { + $this->error = $body; + if ($code >= 200 && $code < 300) { + $this->error = $e->getMessage(); + } + } + } elseif ($code >= 400) { + $this->error = $body; + } + return; + } + + public function json() + { + return $this->jsonData; + } + + public function headers($normalized = false) + { + if ($normalized) { + return $this->normalizedHeaders; + } + return $this->headers; + } + + public function body() + { + return $this->body; + } + + private static function bodyJson($body) + { + return \Qiniu\json_decode((string) $body, true, 512); + } + + public function xVia() + { + $via = $this->normalizedHeaders['X-Via']; + if ($via === null) { + $via = $this->normalizedHeaders['X-Px']; + } + if ($via === null) { + $via = $this->normalizedHeaders['Fw-Via']; + } + return $via; + } + + public function xLog() + { + return $this->normalizedHeaders['X-Log']; + } + + public function xReqId() + { + return $this->normalizedHeaders['X-Reqid']; + } + + public function ok() + { + return $this->statusCode >= 200 && $this->statusCode < 300 && $this->error === null; + } + + public function needRetry() + { + if ($this->statusCode > 0 && $this->statusCode < 500) { + return false; + } + + // https://developer.qiniu.com/fusion/kb/1352/the-http-request-return-a-status-code + if (in_array($this->statusCode, array( + 501, 509, 573, 579, 608, 612, 614, 616, 618, 630, 631, 632, 640, 701 + ))) { + return false; + } + + return true; + } + + private static function isJson($headers) + { + return isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'application/json') === 0; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php b/vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php new file mode 100644 index 0000000..f5575ed --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php @@ -0,0 +1,292 @@ + + */ + public function thumbnail( + $url, + $mode, + $width, + $height, + $format = null, + $interlace = null, + $quality = null, + $ignoreError = 1 + ) { + + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + // 参数合法性效验 + if (!in_array(intval($mode), $this->modeArr, true)) { + return $url; + } + + if (!$width || !$height) { + return $url; + } + + $thumbStr = 'imageView2/' . $mode . '/w/' . $width . '/h/' . $height . '/'; + + // 拼接输出格式 + if (!is_null($format) + && in_array($format, $this->formatArr) + ) { + $thumbStr .= 'format/' . $format . '/'; + } + + // 拼接渐进显示 + if (!is_null($interlace) + && in_array(intval($interlace), array(0, 1), true) + ) { + $thumbStr .= 'interlace/' . $interlace . '/'; + } + + // 拼接图片质量 + if (!is_null($quality) + && intval($quality) >= 0 + && intval($quality) <= 100 + ) { + $thumbStr .= 'q/' . $quality . '/'; + } + + $thumbStr .= 'ignore-error/' . $ignoreError . '/'; + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $thumbStr; + } + + /** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param int $dissolve 透明度 + * @param string $gravity 水印位置 + * @param int $dx 横轴边距 + * @param int $dy 纵轴边距 + * @param int $watermarkScale 自适应原图的短边比例 + * @return string + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html + * @author Sherlock Ren + */ + public function waterImg( + $url, + $image, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null, + $watermarkScale = null + ) { + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + $waterStr = 'watermark/1/image/' . \Qiniu\base64_urlSafeEncode($image) . '/'; + + // 拼接水印透明度 + if (is_numeric($dissolve) + && $dissolve <= 100 + ) { + $waterStr .= 'dissolve/' . $dissolve . '/'; + } + + // 拼接水印位置 + if (in_array($gravity, $this->gravityArr, true)) { + $waterStr .= 'gravity/' . $gravity . '/'; + } + + // 拼接横轴边距 + if (!is_null($dx) + && is_numeric($dx) + ) { + $waterStr .= 'dx/' . $dx . '/'; + } + + // 拼接纵轴边距 + if (!is_null($dy) + && is_numeric($dy) + ) { + $waterStr .= 'dy/' . $dy . '/'; + } + + // 拼接自适应原图的短边比例 + if (!is_null($watermarkScale) + && is_numeric($watermarkScale) + && $watermarkScale > 0 + && $watermarkScale < 1 + ) { + $waterStr .= 'ws/' . $watermarkScale . '/'; + } + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr; + } + + /** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 + * @param int $dissolve 透明度 + * @param string $gravity 水印位置 + * @param int $dx 横轴边距 + * @param int $dy 纵轴边距 + * @return string + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @author Sherlock Ren + */ + public function waterText( + $url, + $text, + $font = '黑体', + $fontSize = 0, + $fontColor = null, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null + ) { + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + $waterStr = 'watermark/2/text/' + . \Qiniu\base64_urlSafeEncode($text) . '/font/' + . \Qiniu\base64_urlSafeEncode($font) . '/'; + + // 拼接文字大小 + if (is_int($fontSize)) { + $waterStr .= 'fontsize/' . $fontSize . '/'; + } + + // 拼接文字颜色 + if (!is_null($fontColor) + && $fontColor + ) { + $waterStr .= 'fill/' . \Qiniu\base64_urlSafeEncode($fontColor) . '/'; + } + + // 拼接水印透明度 + if (is_numeric($dissolve) + && $dissolve <= 100 + ) { + $waterStr .= 'dissolve/' . $dissolve . '/'; + } + + // 拼接水印位置 + if (in_array($gravity, $this->gravityArr, true)) { + $waterStr .= 'gravity/' . $gravity . '/'; + } + + // 拼接横轴边距 + if (!is_null($dx) + && is_numeric($dx) + ) { + $waterStr .= 'dx/' . $dx . '/'; + } + + // 拼接纵轴边距 + if (!is_null($dy) + && is_numeric($dy) + ) { + $waterStr .= 'dy/' . $dy . '/'; + } + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr; + } + + /** + * 效验url合法性 + * + * @param string $url url链接 + * @return string + * @author Sherlock Ren + */ + protected function isUrl($url) + { + $urlArr = parse_url($url); + + return $urlArr['scheme'] + && in_array($urlArr['scheme'], array('http', 'https')) + && $urlArr['host'] + && $urlArr['path']; + } + + /** + * 检测是否有query + * + * @param string $url url链接 + * @return string + * @author Sherlock Ren + */ + protected function hasQuery($url) + { + $urlArr = parse_url($url); + + return !empty($urlArr['query']); + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php b/vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php new file mode 100644 index 0000000..839703c --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php @@ -0,0 +1,69 @@ +auth = $auth; + $this->domain = $domain; + $this->token_expire = $token_expire; + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + + /** + * 对资源文件进行处理 + * + * @param string $key 待处理的资源文件名 + * @param string $fops string|array fop操作,多次fop操作以array的形式传入。 + * eg. imageView2/1/w/200/h/200, imageMogr2/thumbnail/!75px + * + * @return array 文件处理后的结果及错误。 + * + * @link http://developer.qiniu.com/docs/v6/api/reference/fop/ + */ + public function execute($key, $fops) + { + $url = $this->buildUrl($key, $fops); + $resp = Client::get($url, array(), $this->proxy->makeReqOpt()); + if (!$resp->ok()) { + return array(null, new Error($url, $resp)); + } + if ($resp->json() !== null) { + return array($resp->json(), null); + } + return array($resp->body, null); + } + + public function buildUrl($key, $fops, $protocol = 'http') + { + if (is_array($fops)) { + $fops = implode('|', $fops); + } + + $url = $protocol . "://$this->domain/$key?$fops"; + if ($this->auth !== null) { + $url = $this->auth->privateDownloadUrl($url, $this->token_expire); + } + + return $url; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php b/vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php new file mode 100644 index 0000000..8dca4a9 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php @@ -0,0 +1,135 @@ +auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * 对资源文件进行异步持久化处理 + * @param string $bucket 资源所在空间 + * @param string $key 待处理的源文件 + * @param string|array $fops 待处理的pfop操作,多个pfop操作以array的形式传入。 + * eg. avthumb/mp3/ab/192k, vframe/jpg/offset/7/w/480/h/360 + * @param string $pipeline 资源处理队列 + * @param string $notify_url 处理结果通知地址 + * @param bool $force 是否强制执行一次新的指令 + * @param int $type 为 `1` 时开启闲时任务 + * + * + * @return array 返回持久化处理的 persistentId 与可能出现的错误。 + * + * @link http://developer.qiniu.com/docs/v6/api/reference/fop/ + */ + public function execute( + $bucket, + $key, + $fops = null, + $pipeline = null, + $notify_url = null, + $force = false, + $type = null, + $workflow_template_id = null + ) { + if (is_array($fops)) { + $fops = implode(';', $fops); + } + + if (!$fops && !$workflow_template_id) { + throw new \InvalidArgumentException('Must provide one of fops or template_id'); + } + + $params = array('bucket' => $bucket, 'key' => $key); + \Qiniu\setWithoutEmpty($params, 'fops', $fops); + \Qiniu\setWithoutEmpty($params, 'pipeline', $pipeline); + \Qiniu\setWithoutEmpty($params, 'notifyURL', $notify_url); + \Qiniu\setWithoutEmpty($params, 'type', $type); + \Qiniu\setWithoutEmpty($params, 'workflowTemplateID', $workflow_template_id); + if ($force) { + $params['force'] = 1; + } + $data = http_build_query($params); + $scheme = "http://"; + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $apiHost = $this->getApiHost(); + $url = $scheme . $apiHost . '/pfop/'; + $headers = $this->auth->authorization($url, $data, 'application/x-www-form-urlencoded'); + $headers['Content-Type'] = 'application/x-www-form-urlencoded'; + $response = Client::post($url, $data, $headers, $this->proxy->makeReqOpt()); + if (!$response->ok()) { + return array(null, new Error($url, $response)); + } + $r = $response->json(); + $id = $r['persistentId']; + return array($id, null); + } + + /** + * @param string $id + * @return array 返回任务状态与可能出现的错误 + */ + public function status($id) + { + $scheme = "http://"; + + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $apiHost = $this->getApiHost(); + $url = $scheme . $apiHost . "/status/get/prefop?id=$id"; + $response = Client::get($url, array(), $this->proxy->makeReqOpt()); + if (!$response->ok()) { + return array(null, new Error($url, $response)); + } + return array($response->json(), null); + } + + private function getApiHost() + { + if (!empty($this->config->zone) && !empty($this->config->zone->apiHost)) { + $apiHost = $this->config->zone->apiHost; + } else { + $apiHost = Config::API_HOST; + } + return $apiHost; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Region.php b/vendor/qiniu/php-sdk/src/Qiniu/Region.php new file mode 100644 index 0000000..220a5a3 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Region.php @@ -0,0 +1,229 @@ +srcUpHosts = $srcUpHosts; + $this->cdnUpHosts = $cdnUpHosts; + $this->rsHost = $rsHost; + $this->rsfHost = $rsfHost; + $this->apiHost = $apiHost; + $this->iovipHost = $iovipHost; + $this->ttl = $ttl; + } + + //华东机房 + public static function regionHuadong() + { + $regionHuadong = new Region( + array("up.qiniup.com"), + array('upload.qiniup.com'), + 'rs-z0.qiniuapi.com', + 'rsf-z0.qiniuapi.com', + 'api.qiniuapi.com', + 'iovip.qbox.me' + ); + return $regionHuadong; + } + + //华东机房内网上传 + public static function qvmRegionHuadong() + { + $qvmRegionHuadong = new Region( + array("free-qvm-z0-xs.qiniup.com"), + 'rs-z0.qiniuapi.com', + 'rsf-z0.qiniuapi.com', + 'api.qiniuapi.com', + 'iovip.qbox.me' + ); + return $qvmRegionHuadong; + } + + //华北机房内网上传 + public static function qvmRegionHuabei() + { + $qvmRegionHuabei = new Region( + array("free-qvm-z1-zz.qiniup.com"), + "rs-z1.qiniuapi.com", + "rsf-z1.qiniuapi.com", + "api-z1.qiniuapi.com", + "iovip-z1.qbox.me" + ); + return $qvmRegionHuabei; + } + + //华北机房 + public static function regionHuabei() + { + $regionHuabei = new Region( + array('up-z1.qiniup.com'), + array('upload-z1.qiniup.com'), + "rs-z1.qiniuapi.com", + "rsf-z1.qiniuapi.com", + "api-z1.qiniuapi.com", + "iovip-z1.qbox.me" + ); + + return $regionHuabei; + } + + //华南机房 + public static function regionHuanan() + { + $regionHuanan = new Region( + array('up-z2.qiniup.com'), + array('upload-z2.qiniup.com'), + "rs-z2.qiniuapi.com", + "rsf-z2.qiniuapi.com", + "api-z2.qiniuapi.com", + "iovip-z2.qbox.me" + ); + return $regionHuanan; + } + + //华东2 机房 + public static function regionHuadong2() + { + return new Region( + array('up-cn-east-2.qiniup.com'), + array('upload-cn-east-2.qiniup.com'), + "rs-cn-east-2.qiniuapi.com", + "rsf-cn-east-2.qiniuapi.com", + "api-cn-east-2.qiniuapi.com", + "iovip-cn-east-2.qiniuio.com" + ); + } + + //北美机房 + public static function regionNorthAmerica() + { + //北美机房 + $regionNorthAmerica = new Region( + array('up-na0.qiniup.com'), + array('upload-na0.qiniup.com'), + "rs-na0.qiniuapi.com", + "rsf-na0.qiniuapi.com", + "api-na0.qiniuapi.com", + "iovip-na0.qbox.me" + ); + return $regionNorthAmerica; + } + + //新加坡机房 + public static function regionSingapore() + { + //新加坡机房 + $regionSingapore = new Region( + array('up-as0.qiniup.com'), + array('upload-as0.qiniup.com'), + "rs-as0.qiniuapi.com", + "rsf-as0.qiniuapi.com", + "api-as0.qiniuapi.com", + "iovip-as0.qbox.me" + ); + return $regionSingapore; + } + + /* + * GET /v4/query?ak=&bucket= + * @param string $ak + * @param string $bucket + * @param string $ucHost|null + * @param array $backupUcHosts + * @param int $retryTimes + * @param RequestOptions|null $reqOpt + * @return Response + **/ + public static function queryRegion( + $ak, + $bucket, + $ucHost = null, + $backupUcHosts = array(), + $retryTimes = 2, + $reqOpt = null + ) { + $region = new Region(); + if (!$ucHost) { + $ucHost = "https://" . Config::QUERY_REGION_HOST; + } + $url = $ucHost . '/v4/query' . "?ak=$ak&bucket=$bucket"; + if ($reqOpt == null) { + $reqOpt = new RequestOptions(); + } + $reqOpt->middlewares = array( + new RetryDomainsMiddleware( + $backupUcHosts, + $retryTimes + ) + ); + $ret = Client::get($url, array(), $reqOpt); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + if (!is_array($r["hosts"]) || count($r["hosts"]) == 0) { + return array(null, new Error($url, $ret)); + } + + // parse region; + $regionHost = $r["hosts"][0]; + $region->cdnUpHosts = array_merge($region->cdnUpHosts, $regionHost['up']['domains']); + $region->srcUpHosts = array_merge($region->srcUpHosts, $regionHost['up']['domains']); + + // set specific hosts + $region->iovipHost = $regionHost['io']['domains'][0]; + if (isset($regionHost['rs']['domains']) && count($regionHost['rs']['domains']) > 0) { + $region->rsHost = $regionHost['rs']['domains'][0]; + } else { + $region->rsHost = Config::RS_HOST; + } + if (isset($regionHost['rsf']['domains']) && count($regionHost['rsf']['domains']) > 0) { + $region->rsfHost = $regionHost['rsf']['domains'][0]; + } else { + $region->rsfHost = Config::RSF_HOST; + } + if (isset($regionHost['api']['domains']) && count($regionHost['api']['domains']) > 0) { + $region->apiHost = $regionHost['api']['domains'][0]; + } else { + $region->apiHost = Config::API_HOST; + } + + // set ttl + $region->ttl = $regionHost['ttl']; + + return $region; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php b/vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php new file mode 100644 index 0000000..3f245db --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php @@ -0,0 +1,236 @@ +auth = $auth; + $this->baseURL = sprintf("%s/%s/apps", Config::RTCAPI_HOST, Config::RTCAPI_VERSION); + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * 创建应用 + * + * @param string $hub 绑定的直播 hub + * @param string $title app 的名称 注意,Title 不是唯一标识,重复 create 动作将生成多个 app + * @param int $maxUsers 连麦房间支持的最大在线人数 + * @param bool $noAutoKickUser 禁止自动踢人(抢流),默认为 false + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_1 + */ + public function createApp($hub, $title, $maxUsers = null, $noAutoKickUser = null) + { + $params = array(); + $params['hub'] = $hub; + $params['title'] = $title; + if (!empty($maxUsers)) { + $params['maxUsers'] = $maxUsers; + } + if ($noAutoKickUser !== null) { + $params['noAutoKickUser'] = $noAutoKickUser; + } + $body = json_encode($params); + return $this->post($this->baseURL, $body); + } + + /** + * 更新一个应用的配置信息 + * + * @param string $appId app 的唯一标识,创建的时候由系统生成 + * @param string $hub app 的名称,可选 + * @param string $title 绑定的直播 hub,可选,用于合流后 rtmp 推流 + * @param int $maxUsers 连麦房间支持的最大在线人数,可选 + * @param bool $noAutoKickUser 禁止自动踢人,可选 + * @param null $mergePublishRtmp 连麦合流转推 RTMP 的配置,可选择。其详细配置可以参考文档 + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_1 + */ + public function updateApp($appId, $hub, $title, $maxUsers = null, $noAutoKickUser = null, $mergePublishRtmp = null) + { + $url = $this->baseURL . '/' . $appId; + $params = array(); + $params['hub'] = $hub; + $params['title'] = $title; + if (!empty($maxUsers)) { + $params['maxUsers'] = $maxUsers; + } + if ($noAutoKickUser !== null) { + $params['noAutoKickUser'] = $noAutoKickUser; + } + if (!empty($mergePublishRtmp)) { + $params['mergePublishRtmp'] = $mergePublishRtmp; + } + $body = json_encode($params); + return $this->post($url, $body); + } + + /** + * 获取应用信息 + * + * @param string $appId + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_1 + */ + public function getApp($appId) + { + $url = $this->baseURL . '/' . $appId; + return $this->get($url); + } + + /** + * 删除应用 + * + * @param string $appId app 的唯一标识,创建的时候由系统生成 + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_1 + */ + public function deleteApp($appId) + { + $url = $this->baseURL . '/' . $appId; + return $this->delete($url); + } + + /** + * 获取房间内用户列表 + * + * @param string $appId app 的唯一标识,创建的时候由系统生成 + * @param string $roomName 操作所查询的连麦房间 + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_2 + */ + public function listUser($appId, $roomName) + { + $url = sprintf("%s/%s/rooms/%s/users", $this->baseURL, $appId, $roomName); + return $this->get($url); + } + + /** + * 指定一个用户踢出房间 + * + * @param string $appId app 的唯一标识,创建的时候由系统生成 + * @param string $roomName 连麦房间 + * @param string $userId 操作所剔除的用户 + * @return mixed + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_2 + */ + public function kickUser($appId, $roomName, $userId) + { + $url = sprintf("%s/%s/rooms/%s/users/%s", $this->baseURL, $appId, $roomName, $userId); + return $this->delete($url); + } + + /** + * 停止一个房间的合流转推 + * + * @param string $appId + * @param string $roomName + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_2 + */ + public function stopMerge($appId, $roomName) + { + $url = sprintf("%s/%s/rooms/%s/merge", $this->baseURL, $appId, $roomName); + return $this->delete($url); + } + + /** + * 获取应用中活跃房间 + * + * @param string $appId 连麦房间所属的 app + * @param null $prefix 所查询房间名的前缀索引,可以为空。 + * @param int $offset 分页查询的位移标记 + * @param int $limit 此次查询的最大长度 + * @return array + * @link https://doc.qnsdk.com/rtn/docs/server_overview#2_2 + */ + public function listActiveRooms($appId, $prefix = null, $offset = null, $limit = null) + { + $query = array(); + if (isset($prefix)) { + $query['prefix'] = $prefix; + } + if (isset($offset)) { + $query['offset'] = $offset; + } + if (isset($limit)) { + $query['limit'] = $limit; + } + if (isset($query) && !empty($query)) { + $query = '?' . http_build_query($query); + $url = sprintf("%s/%s/rooms%s", $this->baseURL, $appId, $query); + } else { + $url = sprintf("%s/%s/rooms", $this->baseURL, $appId); + } + return $this->get($url); + } + + /** + * 生成加入房间的令牌 + * + * @param string $appId app 的唯一标识,创建的时候由系统生成 + * @param string $roomName 房间名称,需满足规格 ^[a-zA-Z0-9_-]{3,64}$ + * @param string $userId 请求加入房间的用户 ID,需满足规格 ^[a-zA-Z0-9_-]{3,50}$ + * @param int $expireAt 鉴权的有效时间,传入以秒为单位的64位 Unix 绝对时间 + * @param string $permission 该用户的房间管理权限,"admin" 或 "user",默认为 "user" + * @return string + * @link https://doc.qnsdk.com/rtn/docs/server_overview#1 + */ + public function appToken($appId, $roomName, $userId, $expireAt, $permission) + { + $params = array(); + $params['appId'] = $appId; + $params['userId'] = $userId; + $params['roomName'] = $roomName; + $params['permission'] = $permission; + $params['expireAt'] = $expireAt; + $appAccessString = json_encode($params); + return $this->auth->signWithData($appAccessString); + } + + private function get($url, $cType = null) + { + $rtcToken = $this->auth->authorizationV2($url, "GET", null, $cType); + $rtcToken['Content-Type'] = $cType; + $ret = Client::get($url, $rtcToken, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function delete($url, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "DELETE", null, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::delete($url, $rtcToken, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function post($url, $body, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "POST", $body, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::post($url, $body, $rtcToken, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Sms/Sms.php b/vendor/qiniu/php-sdk/src/Qiniu/Sms/Sms.php new file mode 100644 index 0000000..c96409b --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Sms/Sms.php @@ -0,0 +1,382 @@ +auth = $auth; + $this->baseURL = sprintf("%s/%s/", Config::SMS_HOST, Config::SMS_VERSION); + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * 创建签名 + * + * @param string $signature 签名 + * @param string $source 签名来源,申请签名时必须指定签名来源 + * @param string $pics 签名对应的资质证明图片进行 base64 编码格式转换后的字符串,可选 + * @return array + * + * @link https://developer.qiniu.com/sms/api/5844/sms-api-create-signature + */ + public function createSignature($signature, $source, $pics = null) + { + $params = array(); + $params['signature'] = $signature; + $params['source'] = $source; + if (!empty($pics)) { + $params['pics'] = array($this->imgToBase64($pics)); + } + $body = json_encode($params); + $url = $this->baseURL . 'signature'; + return $this->post($url, $body); + } + + /** + * 编辑签名 + * + * @param string $id 签名 ID + * @param string $signature 签名 + * @param string $source 签名来源 + * @param string $pics 签名对应的资质证明图片进行 base64 编码格式转换后的字符串,可选 + * @return array + * @link https://developer.qiniu.com/sms/api/5890/sms-api-edit-signature + */ + public function updateSignature($id, $signature, $source, $pics = null) + { + $params = array(); + $params['signature'] = $signature; + $params['source'] = $source; + if (!empty($pics)) { + $params['pics'] = array($this->imgToBase64($pics)); + } + $body = json_encode($params); + $url = $this->baseURL . 'signature/' . $id; + return $this->PUT($url, $body); + } + + /** + * 列出签名 + * + * @param string $audit_status 审核状态:"passed"(通过), "rejected"(未通过), "reviewing"(审核中) + * @param int $page 页码。默认为 1 + * @param int $page_size 分页大小。默认为 20 + * @return array + * @link https://developer.qiniu.com/sms/api/5889/sms-api-query-signature + */ + public function querySignature($audit_status = null, $page = 1, $page_size = 20) + { + + $url = sprintf( + "%s?audit_status=%s&page=%s&page_size=%s", + $this->baseURL . 'signature', + $audit_status, + $page, + $page_size + ); + return $this->get($url); + } + + /** + * 查询单个签名 + * + * @param string $signature_id + * @return array + * @link https://developer.qiniu.com/sms/api/5970/query-a-single-signature + */ + public function checkSingleSignature($signature_id) + { + + $url = sprintf( + "%s/%s", + $this->baseURL . 'signature', + $signature_id + ); + return $this->get($url); + } + + /** + * 删除签名 + * + * @param string $signature_id 签名 ID + * @return array + * @link https://developer.qiniu.com/sms/api/5891/sms-api-delete-signature + */ + public function deleteSignature($signature_id) + { + $url = $this->baseURL . 'signature/' . $signature_id; + return $this->delete($url); + } + + /** + * 创建模板 + * + * @param string $name 模板名称 + * @param string $template 模板内容 可设置自定义变量,发送短信时候使用,参考:${code} + * @param string $type notification:通知类,verification:验证码,marketing:营销类,voice:语音类 + * @param string $description 申请理由简述 + * @param string $signature_id 已经审核通过的签名 + * @return array array + * @link https://developer.qiniu.com/sms/api/5893/sms-api-create-template + */ + public function createTemplate( + $name, + $template, + $type, + $description, + $signature_id + ) { + $params = array(); + $params['name'] = $name; + $params['template'] = $template; + $params['type'] = $type; + $params['description'] = $description; + $params['signature_id'] = $signature_id; + + $body = json_encode($params); + $url = $this->baseURL . 'template'; + return $this->post($url, $body); + } + + /** + * 列出模板 + * + * @param string $audit_status 审核状态:passed (通过), rejected (未通过), reviewing (审核中) + * @param int $page 页码。默认为 1 + * @param int $page_size 分页大小。默认为 20 + * @return array + * @link https://developer.qiniu.com/sms/api/5894/sms-api-query-template + */ + public function queryTemplate($audit_status = null, $page = 1, $page_size = 20) + { + + $url = sprintf( + "%s?audit_status=%s&page=%s&page_size=%s", + $this->baseURL . 'template', + $audit_status, + $page, + $page_size + ); + return $this->get($url); + } + + /** + * 查询单个模版 + * + * @param string $template_id 模版ID + * @return array + * @link https://developer.qiniu.com/sms/api/5969/query-a-single-template + */ + public function querySingleTemplate($template_id) + { + + $url = sprintf( + "%s/%s", + $this->baseURL . 'template', + $template_id + ); + return $this->get($url); + } + + /** + * 编辑模板 + * + * @param string $id 模板 ID + * @param string $name 模板名称 + * @param string $template 模板内容 + * @param string $description 申请理由简述 + * @param string $signature_id 已经审核通过的签名 ID + * @return array + * @link https://developer.qiniu.com/sms/api/5895/sms-api-edit-template + */ + public function updateTemplate( + $id, + $name, + $template, + $description, + $signature_id + ) { + $params = array(); + $params['name'] = $name; + $params['template'] = $template; + $params['description'] = $description; + $params['signature_id'] = $signature_id; + $body = json_encode($params); + $url = $this->baseURL . 'template/' . $id; + return $this->PUT($url, $body); + } + + /** + * 删除模板 + * + * @param string $template_id 模板 ID + * @return array + * @link https://developer.qiniu.com/sms/api/5896/sms-api-delete-template + */ + public function deleteTemplate($template_id) + { + $url = $this->baseURL . 'template/' . $template_id; + return $this->delete($url); + } + + /** + * 发送短信 + * + * @param string $template_id 模板 ID + * @param array $mobiles 手机号 + * @param array $parameters 自定义模板变量,变量设置在创建模板时,参数template指定 + * @return array + * @link https://developer.qiniu.com/sms/api/5897/sms-api-send-message + */ + public function sendMessage($template_id, $mobiles, $parameters = null) + { + $params = array(); + $params['template_id'] = $template_id; + $params['mobiles'] = $mobiles; + if (!empty($parameters)) { + $params['parameters'] = $parameters; + } + $body = json_encode($params); + $url = $this->baseURL . 'message'; + return $this->post($url, $body); + } + + /** + * 查询发送记录 + * + * @param string $job_id 发送任务返回的 id + * @param string $message_id 单条短信发送接口返回的 id + * @param string $mobile 接收短信的手机号码 + * @param string $status sending: 发送中,success: 发送成功,failed: 发送失败,waiting: 等待发送 + * @param string $template_id 模版 id + * @param string $type marketing:营销,notification:通知,verification:验证码,voice:语音 + * @param string $start 开始时间,timestamp,例如: 1563280448 + * @param int $end 结束时间,timestamp,例如: 1563280471 + * @param int $page 页码,默认为 1 + * @param int $page_size 每页返回的数据条数,默认20,最大200 + * @return array + * @link https://developer.qiniu.com/sms/api/5852/query-send-sms + */ + public function querySendSms( + $job_id = null, + $message_id = null, + $mobile = null, + $status = null, + $template_id = null, + $type = null, + $start = null, + $end = null, + $page = 1, + $page_size = 20 + ) { + $query = array(); + \Qiniu\setWithoutEmpty($query, 'job_id', $job_id); + \Qiniu\setWithoutEmpty($query, 'message_id', $message_id); + \Qiniu\setWithoutEmpty($query, 'mobile', $mobile); + \Qiniu\setWithoutEmpty($query, 'status', $status); + \Qiniu\setWithoutEmpty($query, 'template_id', $template_id); + \Qiniu\setWithoutEmpty($query, 'type', $type); + \Qiniu\setWithoutEmpty($query, 'start', $start); + \Qiniu\setWithoutEmpty($query, 'end', $end); + \Qiniu\setWithoutEmpty($query, 'page', $page); + \Qiniu\setWithoutEmpty($query, 'page_size', $page_size); + + $url = $this->baseURL . 'messages?' . http_build_query($query); + return $this->get($url); + } + + + public function imgToBase64($img_file) + { + $img_base64 = ''; + if (file_exists($img_file)) { + $app_img_file = $img_file; // 图片路径 + $img_info = getimagesize($app_img_file); // 取得图片的大小,类型等 + $fp = fopen($app_img_file, "r"); // 图片是否可读权限 + if ($fp) { + $filesize = filesize($app_img_file); + if ($filesize > 5 * 1024 * 1024) { + die("pic size < 5M !"); + } + $img_type = null; + $content = fread($fp, $filesize); + $file_content = chunk_split(base64_encode($content)); // base64编码 + switch ($img_info[2]) { //判读图片类型 + case 1: + $img_type = 'gif'; + break; + case 2: + $img_type = 'jpg'; + break; + case 3: + $img_type = 'png'; + break; + } + //合成图片的base64编码 + $img_base64 = 'data:image/' . $img_type . ';base64,' . $file_content; + } + fclose($fp); + } + + return $img_base64; + } + + private function get($url, $contentType = 'application/x-www-form-urlencoded') + { + $headers = $this->auth->authorizationV2($url, "GET", null, $contentType); + $headers['Content-Type'] = $contentType; + $ret = Client::get($url, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function delete($url, $contentType = 'application/json') + { + $headers = $this->auth->authorizationV2($url, "DELETE", null, $contentType); + $headers['Content-Type'] = $contentType; + $ret = Client::delete($url, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function post($url, $body, $contentType = 'application/json') + { + $headers = $this->auth->authorizationV2($url, "POST", $body, $contentType); + + $headers['Content-Type'] = $contentType; + $ret = Client::post($url, $body, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + private function PUT($url, $body, $contentType = 'application/json') + { + $headers = $this->auth->authorizationV2($url, "PUT", $body, $contentType); + $headers['Content-Type'] = $contentType; + $ret = Client::put($url, $body, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php b/vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php new file mode 100644 index 0000000..51b4200 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php @@ -0,0 +1,129 @@ +auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * 视频审核 + * + * @param string $body body信息 + * + * @return array 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link https://developer.qiniu.com/censor/api/5620/video-censor + */ + public function censorVideo($body) + { + $path = '/v3/video/censor'; + + return $this->arPost($path, $body); + } + + + /** + * 图片审核 + * + * @param string $body + * + * @return array 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link https://developer.qiniu.com/censor/api/5588/image-censor + */ + public function censorImage($body) + { + $path = '/v3/image/censor'; + + return $this->arPost($path, $body); + } + + /** + * 查询视频审核结果 + * + * @param string $jobid 任务ID + * @return array + * @link https://developer.qiniu.com/censor/api/5620/video-censor + */ + public function censorStatus($jobid) + { + $scheme = "http://"; + + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $url = $scheme . Config::ARGUS_HOST . "/v3/jobs/video/$jobid"; + $response = $this->get($url); + if (!$response->ok()) { + return array(null, new Error($url, $response)); + } + return array($response->json(), null); + } + + private function getArHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + return $scheme . Config::ARGUS_HOST; + } + + private function arPost($path, $body = null) + { + $url = $this->getArHost() . $path; + return $this->post($url, $body); + } + + private function get($url) + { + $headers = $this->auth->authorizationV2($url, 'GET'); + + return Client::get($url, $headers, $this->proxy->makeReqOpt()); + } + + private function post($url, $body) + { + $headers = $this->auth->authorizationV2($url, 'POST', $body, 'application/json'); + $headers['Content-Type'] = 'application/json'; + $ret = Client::post($url, $body, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + if (strstr($url, "video")) { + $jobid = $r['job']; + return array($jobid, null); + } + return array($r, null); + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php b/vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php new file mode 100644 index 0000000..bfca4fc --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php @@ -0,0 +1,1324 @@ +auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + $this->proxy = new Proxy($proxy, $proxy_auth, $proxy_user_password); + } + + /** + * 获取指定账号下所有的空间名 + * + * @param bool $shared 指定共享空间,rw:读写权限空间,rd:读权限空间 + * @return array 包含所有空间名 + */ + public function buckets($shared = true) + { + $includeShared = "false"; + if ($shared === true) { + $includeShared = "true"; + } + return $this->getV2($this->config->getUcHost() . '/buckets?shared=' . $includeShared); + } + + /** + * 列举空间,返回bucket列表 + * + * @param string $region 区域 + * @param string $line + * @param string $shared 指定共享空间,rw:读写权限空间,rd:读权限空间 + * @return array + */ + public function listbuckets( + $region = null, + $line = 'false', + $shared = 'false' + ) { + $path = '/v3/buckets?region=' . $region . '&line=' . $line . '&shared=' . $shared; + return $this->ucPost($path); + } + + /** + * 创建空间 + * + * @param string $name 创建的空间名 + * @param string $region 创建的区域,默认华东 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1382/mkbucketv3 + */ + public function createBucket($name, $region = 'z0') + { + $path = '/mkbucketv3/' . $name . '/region/' . $region; + return $this->postV2($this->config->getUcHost() . $path, null); + } + + /** + * 删除空间 + * + * @param string $name 需要删除的目标空间名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1601/drop-bucket + */ + public function deleteBucket($name) + { + $path = '/drop/' . $name; + return $this->postV2($this->config->getUcHost() . $path, null); + } + + /** + * 获取指定空间绑定的所有的域名 + * + * @param string $bucket 空间名称 + * @return array + */ + public function domains($bucket) + { + return $this->ucGet('/v2/domains?tbl=' . $bucket); + } + + /** + * 获取指定空间的相关信息 + * + * @param string $bucket 空间名称 + * @return array + */ + public function bucketInfo($bucket) + { + $path = '/v2/bucketInfo?bucket=' . $bucket; + return $this->ucPost($path); + } + + /** + * 获取指定zone的空间信息列表 + * + * @param string $region 区域 + * @param string $shared 指定共享空间,rw:读写权限空间,rd:读权限空间 + * @param string $fs 如果为 true,会返回每个空间当前的文件数和存储量(实时数据) + * @return array + */ + public function bucketInfos($region = null, $shared = 'false', $fs = 'false') + { + $path = '/v2/bucketInfos?region=' . $region . '&shared=' . $shared . '&fs=' . $fs; + return $this->ucPost($path); + } + + /** + * 列取空间的文件列表 + * + * @param string $bucket 空间名 + * @param string $prefix 列举前缀 + * @param string $marker 列举标识符 + * @param int $limit 单次列举个数限制 + * @param string $delimiter 指定目录分隔符 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1284/list + */ + public function listFiles( + $bucket, + $prefix = null, + $marker = null, + $limit = 1000, + $delimiter = null + ) { + $query = array('bucket' => $bucket); + \Qiniu\setWithoutEmpty($query, 'prefix', $prefix); + \Qiniu\setWithoutEmpty($query, 'marker', $marker); + \Qiniu\setWithoutEmpty($query, 'limit', $limit); + \Qiniu\setWithoutEmpty($query, 'delimiter', $delimiter); + return $this->rsfGet($bucket, '/list?' . http_build_query($query)); + } + + /** + * 列取空间的文件列表 + * + * @deprecated API 可能返回仅包含 marker,不包含 item 或 dir 的项,请使用 {@link listFiles} + * + * @param string $bucket 空间名 + * @param string $prefix 列举前缀 + * @param string $marker 列举标识符 + * @param int $limit 单次列举个数限制 + * @param string $delimiter 指定目录分隔符 + * @param bool $skipconfirm 是否跳过已删除条目的确认机制 + * + * @return array + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/list.html + */ + public function listFilesv2( + $bucket, + $prefix = null, + $marker = null, + $limit = 1000, + $delimiter = null, + $skipconfirm = true + ) { + $query = array('bucket' => $bucket); + \Qiniu\setWithoutEmpty($query, 'prefix', $prefix); + \Qiniu\setWithoutEmpty($query, 'marker', $marker); + \Qiniu\setWithoutEmpty($query, 'limit', $limit); + \Qiniu\setWithoutEmpty($query, 'delimiter', $delimiter); + \Qiniu\setWithoutEmpty($query, 'skipconfirm', $skipconfirm); + $path = '/v2/list?' . http_build_query($query); + + list($host, $err) = $this->config->getRsfHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + $url = $host . $path; + $headers = $this->auth->authorizationV2($url, 'POST', null, 'application/x-www-form-urlencoded'); + $ret = Client::post($url, null, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = explode("\n", $ret->body); + array_pop($r); + return array($r, null); + } + + /** + * 增加bucket生命规则 + * + * @param string $bucket + * 空间名 + * @param string $name + * 规则名称 bucket 内唯一,长度小于50,不能为空,只能为字母、数字、下划线 + * @param string $prefix + * 同一个 bucket 里面前缀不能重复 + * @param int $delete_after_days + * 指定上传文件多少天后删除,指定为0表示不删除,大于0表示多少天后删除。 + * 需大于 to_line_after_days + * @param int $to_line_after_days + * 指定文件上传多少天后转低频存储。指定为0表示不转低频存储 + * @param int $to_archive_ir_after_days + * 指定文件上传多少天后转归档直读。指定为0表示不转归档直读 + * @param int $to_archive_after_days + * 指定文件上传多少天后转归档存储。指定为0表示不转归档存储 + * @param int $to_deep_archive_after_days + * 指定文件上传多少天后转深度归档存储。指定为0表示不转深度归档存储 + * @return array + */ + public function bucketLifecycleRule( + $bucket, + $name, + $prefix, + $delete_after_days = null, + $to_line_after_days = null, + $to_archive_after_days = null, + $to_deep_archive_after_days = null, + $to_archive_ir_after_days = null + ) { + $path = '/rules/add'; + $params = array(); + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + if ($prefix) { + $params['prefix'] = $prefix; + } + if ($delete_after_days) { + $params['delete_after_days'] = $delete_after_days; + } + if ($to_line_after_days) { + $params['to_line_after_days'] = $to_line_after_days; + } + if ($to_archive_ir_after_days) { + $params['to_archive_ir_after_days'] = $to_archive_ir_after_days; + } + if ($to_archive_after_days) { + $params['to_archive_after_days'] = $to_archive_after_days; + } + if ($to_deep_archive_after_days) { + $params['to_deep_archive_after_days'] = $to_deep_archive_after_days; + } + $data = http_build_query($params); + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 更新bucket生命规则 + * + * @param string $bucket + * 空间名 + * @param string $name + * 规则名称 bucket 内唯一,长度小于50,不能为空,只能为字母、数字、下划线 + * @param string $prefix + * 同一个 bucket 里面前缀不能重复 + * @param int $delete_after_days + * 指定上传文件多少天后删除,指定为0表示不删除,大于0表示多少天后删除 + * 需大于 to_line_after_days + * @param int $to_line_after_days + * 指定文件上传多少天后转低频存储。指定为0表示不转低频存储 + * @param int $to_archive_ir_after_days + * 指定文件上传多少天后转归档只读。指定为0表示不转归档只读 + * @param int $to_archive_after_days + * 指定文件上传多少天后转归档存储。指定为0表示不转归档存储 + * @param int $to_deep_archive_after_days + * 指定文件上传多少天后转深度归档存储。指定为0表示不转深度归档存储 + * @return array + */ + public function updateBucketLifecycleRule( + $bucket, + $name, + $prefix, + $delete_after_days = null, + $to_line_after_days = null, + $to_archive_after_days = null, + $to_deep_archive_after_days = null, + $to_archive_ir_after_days = null + ) { + $path = '/rules/update'; + $params = array(); + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + if ($prefix) { + $params['prefix'] = $prefix; + } + if ($delete_after_days) { + $params['delete_after_days'] = $delete_after_days; + } + if ($to_line_after_days) { + $params['to_line_after_days'] = $to_line_after_days; + } + if ($to_archive_ir_after_days) { + $params['to_archive_ir_after_days'] = $to_archive_ir_after_days; + } + if ($to_archive_after_days) { + $params['to_archive_after_days'] = $to_archive_after_days; + } + if ($to_deep_archive_after_days) { + $params['to_deep_archive_after_days'] = $to_deep_archive_after_days; + } + $data = http_build_query($params); + return $this->ucPost($path, $data); + } + + /** + * 获取bucket生命规则 + * + * @param string $bucket 空间名 + * @return array + */ + public function getBucketLifecycleRules($bucket) + { + $path = '/rules/get?bucket=' . $bucket; + $info = $this->ucGet($path); + return $info; + } + + /** + * 删除bucket生命规则 + * + * @param string $bucket 空间名 + * @param string $name 规则名称 bucket 内唯一,长度小于50,不能为空, + * 只能为字母、数字、下划线() + * @return array + */ + public function deleteBucketLifecycleRule($bucket, $name) + { + $path = '/rules/delete'; + $params = array(); + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + $data = http_build_query($params); + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 增加bucket事件通知规则 + * + * @param string $bucket 空间名 + * @param string $name 规则名称 bucket 内唯一,长度小于50,不能为空, + * 只能为字母、数字、下划线() + * @param string $prefix 同一个 bucket 里面前缀不能重复 + * @param string $suffix 可选,文件配置的后缀 + * @param array $event 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append, + * disable,enable,deleteMarkerCreate + * @param string $callbackURL 通知URL,可以指定多个,失败依次重试 + * @param string $access_key 可选,设置的话会对通知请求用对应的ak、sk进行签名 + * @param string $host 可选,通知请求的host + * + * @return array + */ + public function putBucketEvent( + $bucket, + $name, + $prefix, + $suffix, + $event, + $callbackURL, + $access_key = null, + $host = null + ) { + $path = '/events/add'; + $params = array(); + if (!empty($bucket)) { + $params['bucket'] = $bucket; + } + if (!empty($name)) { + $params['name'] = $name; + } + if (!empty($prefix)) { + $params['prefix'] = $prefix; + } + if (!empty($suffix)) { + $params['suffix'] = $suffix; + } + if (!empty($callbackURL)) { + $params['callbackURL'] = $callbackURL; + } + if (!empty($access_key)) { + $params['access_key'] = $access_key; + } + if (!empty($host)) { + $params['host'] = $host; + } + $data = http_build_query($params); + if (!empty($event)) { + $eventpath = ""; + foreach ($event as $key => $value) { + $eventpath .= "&event=$value"; + } + $data .= $eventpath; + } + $info = $this->ucPost($path, $data); + return $info; + } + + /** + * 更新bucket事件通知规则 + * + * @param string $bucket 空间名 + * @param string $name 规则名称 bucket 内唯一,长度小于50,不能为空, + * 只能为字母、数字、下划线() + * @param string $prefix 同一个 bucket 里面前缀不能重复 + * @param string $suffix 可选,文件配置的后缀 + * @param array $event 事件类型,可以指定多个,包括 put,mkfile,delete,copy,move,append,disable, + * enable,deleteMarkerCreate + * @param string $callbackURL 通知URL,可以指定多个,失败依次重试 + * @param string $access_key 可选,设置的话会对通知请求用对应的ak、sk进行签名 + * @param string $host 可选,通知请求的host + * + * @return array + */ + public function updateBucketEvent( + $bucket, + $name, + $prefix, + $suffix, + $event, + $callbackURL, + $access_key = null, + $host = null + ) { + $path = '/events/update'; + $params = array(); + if (!empty($bucket)) { + $params['bucket'] = $bucket; + } + if (!empty($name)) { + $params['name'] = $name; + } + if (!empty($prefix)) { + $params['prefix'] = $prefix; + } + if ($suffix) { + $params['suffix'] = $suffix; + } + if (!empty($event)) { + $params['event'] = $event; + } + if (!empty($callbackURL)) { + $params['callbackURL'] = $callbackURL; + } + if (!empty($access_key)) { + $params['access_key'] = $access_key; + } + if (!empty($host)) { + $params['host'] = $host; + } + $data = http_build_query($params); + if (!empty($event)) { + $eventpath = ""; + foreach ($event as $key => $value) { + $eventpath .= "&event=$value"; + } + $data .= $eventpath; + } + return $this->ucPost($path, $data); + } + + /** + * 获取bucket事件通知规则 + * + * @param string $bucket 空间名 + * @return array + */ + public function getBucketEvents($bucket) + { + $path = '/events/get?bucket=' . $bucket; + return $this->ucGet($path); + } + + /** + * 删除bucket事件通知规则 + * + * @param string $bucket 空间名 + * @param string $name 规则名称bucket内唯一,长度小于50,不能为空,只能为字母、数字、下划线 + * @return array + */ + public function deleteBucketEvent($bucket, $name) + { + $path = '/events/delete'; + $params = array(); + if ($bucket) { + $params['bucket'] = $bucket; + } + if ($name) { + $params['name'] = $name; + } + $data = http_build_query($params); + return $this->ucPost($path, $data); + } + + /** + * 获取bucket的跨域信息 + * + * @param string $bucket 空间名 + * @return array + */ + public function getCorsRules($bucket) + { + $path = '/corsRules/get/' . $bucket; + return $this->ucGet($path); + } + + /** + * 开关原图保护 + * + * @param string $bucket 空间名称 + * @param int $mode mode 为1表示开启原图保护,0表示关闭 + * @return array + */ + public function putBucketAccessStyleMode($bucket, $mode) + { + $path = '/accessMode/' . $bucket . '/mode/' . $mode; + return $this->ucPost($path, null); + } + + /** + * 设置私有属性 + * + * @param string $bucket 空间名称 + * @param int $private private为0表示公开,为1表示私有 + * @return array + */ + public function putBucketAccessMode($bucket, $private) + { + $path = "/private?bucket=$bucket&private=$private"; + return $this->ucPost($path, null); + } + + /** + * 设置 referer 防盗链 + * + * @param string $bucket 空间名称 + * @param int $mode 0:关闭Referer(使用此选项将会忽略以下参数并将恢复默认值); + * 1:设置Referer白名单; 2:设置Referer黑名单 + * @param string $norefer 0:不允许空 Refer 访问; 1:表示允许空Refer访问 + * @param string $pattern 规则字符串 + * @param int $enabled 源站是否支持,默认为0只给CDN配置, 设置为1表示开启源站防盗链 + * @return array + * @link https://developer.qiniu.com/kodo/manual/6093/set-the-hotlinking-prevention + */ + public function putReferAntiLeech($bucket, $mode, $norefer, $pattern, $enabled = 1) + { + $path = "/referAntiLeech?bucket=$bucket&mode=$mode&norefer=$norefer&pattern=$pattern&source_enabled=$enabled"; + return $this->ucPost($path, null); + } + + /** + * 设置Bucket的maxAge + * + * @param string $bucket 空间名称 + * @param int $maxAge maxAge为0或者负数表示为默认值(31536000) + * @return array + */ + public function putBucketMaxAge($bucket, $maxAge) + { + $path = '/maxAge?bucket=' . $bucket . '&maxAge=' . $maxAge; + return $this->ucPost($path, null); + } + + /** + * 设置空间配额 + * + * @param string $bucket 空间名称,不支持授权空间 + * @param string $size 空间存储量配额,参数传入0或不传表示不更改当前配置,传入-1表示取消限额,新创建的空间默认没有限额 + * @param string $count 空间文件数配额,参数含义同 + * @return array + */ + public function putBucketQuota($bucket, $size, $count) + { + $path = '/setbucketquota/' . $bucket . '/size/' . $size . '/count/' . $count; + return $this->apiPost($bucket, $path); + } + + /** + * 获取空间配额 + * + * @param string $bucket 空间名称 + * @return array + */ + public function getBucketQuota($bucket) + { + $path = '/getbucketquota/' . $bucket; + return $this->apiPost($bucket, $path); + } + + /** + * 获取资源的元信息,但不返回文件内容 + * + * @param string $bucket 待获取信息资源所在的空间 + * @param string $key 待获取资源的文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1308/stat + */ + public function stat($bucket, $key) + { + $path = '/stat/' . \Qiniu\entry($bucket, $key); + return $this->rsGet($bucket, $path); + } + + /** + * 删除指定资源 + * + * @param string $bucket 待删除资源所在的空间 + * @param string $key 待删除资源的文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1257/delete + */ + public function delete($bucket, $key) + { + $path = '/delete/' . \Qiniu\entry($bucket, $key); + return $this->rsPost($bucket, $path); + } + + /** + * 给资源进行重命名,本质为move操作。 + * + * @param string $bucket 待操作资源所在空间 + * @param string $oldname 待操作资源文件名 + * @param string $newname 目标资源文件名 + * + * @return array + */ + public function rename($bucket, $oldname, $newname) + { + return $this->move($bucket, $oldname, $bucket, $newname); + } + + /** + * 对资源进行复制。 + * + * @param string $from_bucket 待操作资源所在空间 + * @param string $from_key 待操作资源文件名 + * @param string $to_bucket 目标资源空间名 + * @param string $to_key 目标资源文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1254/copy + */ + public function copy($from_bucket, $from_key, $to_bucket, $to_key, $force = false) + { + $from = \Qiniu\entry($from_bucket, $from_key); + $to = \Qiniu\entry($to_bucket, $to_key); + $path = '/copy/' . $from . '/' . $to; + if ($force === true) { + $path .= '/force/true'; + } + return $this->rsPost($from_bucket, $path); + } + + /** + * 将资源从一个空间到另一个空间 + * + * @param string $from_bucket 待操作资源所在空间 + * @param string $from_key 待操作资源文件名 + * @param string $to_bucket 目标资源空间名 + * @param string $to_key 目标资源文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1288/move + */ + public function move($from_bucket, $from_key, $to_bucket, $to_key, $force = false) + { + $from = \Qiniu\entry($from_bucket, $from_key); + $to = \Qiniu\entry($to_bucket, $to_key); + $path = '/move/' . $from . '/' . $to; + if ($force) { + $path .= '/force/true'; + } + return $this->rsPost($from_bucket, $path); + } + + /** + * 主动修改指定资源的文件元信息 + * + * @param string $bucket 待操作资源所在空间 + * @param string $key 待操作资源文件名 + * @param string $mime 待操作文件目标mimeType + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1252/chgm + */ + public function changeMime($bucket, $key, $mime) + { + $resource = \Qiniu\entry($bucket, $key); + $encode_mime = \Qiniu\base64_urlSafeEncode($mime); + $path = '/chgm/' . $resource . '/mime/' . $encode_mime; + return $this->rsPost($bucket, $path); + } + + + /** + * 修改指定资源的存储类型 + * + * @param string $bucket 待操作资源所在空间 + * @param string $key 待操作资源文件名 + * @param int $fileType 对象存储类型 + * 0 表示标准存储; + * 1 表示低频存储; + * 2 表示归档存储; + * 3 表示深度归档存储; + * 4 表示归档直读存储; + * + * @return array + * @link https://developer.qiniu.com/kodo/api/3710/chtype + */ + public function changeType($bucket, $key, $fileType) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/chtype/' . $resource . '/type/' . $fileType; + return $this->rsPost($bucket, $path); + } + + /** + * 解冻指定资源的存储类型 + * + * @param string $bucket 待操作资源所在空间 + * @param string $key 待操作资源文件名 + * @param int $freezeAfterDays 解冻有效时长,取值范围 1~7 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/6380/restore-archive + */ + public function restoreAr($bucket, $key, $freezeAfterDays) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/restoreAr/' . $resource . '/freezeAfterDays/' . $freezeAfterDays; + return $this->rsPost($bucket, $path); + } + + /** + * 修改文件的存储状态,即禁用状态和启用状态间的的互相转换 + * + * @param string $bucket 待操作资源所在空间 + * @param string $key 待操作资源文件名 + * @param int $status 0表示启用;1表示禁用 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/4173/modify-the-file-status + */ + public function changeStatus($bucket, $key, $status) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/chstatus/' . $resource . '/status/' . $status; + return $this->rsPost($bucket, $path); + } + + /** + * 从指定URL抓取资源,并将该资源存储到指定空间中 + * + * @param string $url 指定的URL + * @param string $bucket 目标资源空间 + * @param string $key 目标资源文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1263/fetch + */ + public function fetch($url, $bucket, $key = null) + { + + $resource = \Qiniu\base64_urlSafeEncode($url); + $to = \Qiniu\entry($bucket, $key); + $path = '/fetch/' . $resource . '/to/' . $to; + + $ak = $this->auth->getAccessKey(); + + + list($ioHost, $err) = $this->config->getIovipHostV2($ak, $bucket, $this->proxy->makeReqOpt()); + if ($err != null) { + return array(null, $err); + } + + $url = $ioHost . $path; + return $this->postV2($url, null); + } + + /** + * 从指定URL异步抓取资源,并将该资源存储到指定空间中 + * + * @param string $url 需要抓取的url + * @param string $bucket 所在区域的bucket + * @param string $host 从指定url下载数据时使用的Host + * @param string $key 文件存储的key + * @param string $md5 文件md5 + * @param string $etag 文件etag + * @param string $callbackurl 回调URL + * @param string $callbackbody 回调Body + * @param string $callbackbodytype 回调Body内容类型,默认为"application/x-www-form-urlencoded" + * @param string $callbackhost 回调时使用的Host + * @param int $file_type 存储文件类型 + * 0:标准存储(默认) + * 1:低频存储 + * 2:归档存储 + * 3:深度归档存储 + * 4:归档直读存储 + * @param bool $ignore_same_key 如果空间中已经存在同名文件则放弃本次抓取 + * @return array + * @link https://developer.qiniu.com/kodo/api/4097/asynch-fetch + */ + public function asynchFetch( + $url, + $bucket, + $host = null, + $key = null, + $md5 = null, + $etag = null, + $callbackurl = null, + $callbackbody = null, + $callbackbodytype = 'application/x-www-form-urlencoded', + $callbackhost = null, + $file_type = 0, + $ignore_same_key = false + ) { + $path = '/sisyphus/fetch'; + + $params = array('url' => $url, 'bucket' => $bucket); + \Qiniu\setWithoutEmpty($params, 'host', $host); + \Qiniu\setWithoutEmpty($params, 'key', $key); + \Qiniu\setWithoutEmpty($params, 'md5', $md5); + \Qiniu\setWithoutEmpty($params, 'etag', $etag); + \Qiniu\setWithoutEmpty($params, 'callbackurl', $callbackurl); + \Qiniu\setWithoutEmpty($params, 'callbackbody', $callbackbody); + \Qiniu\setWithoutEmpty($params, 'callbackbodytype', $callbackbodytype); + \Qiniu\setWithoutEmpty($params, 'callbackhost', $callbackhost); + \Qiniu\setWithoutEmpty($params, 'file_type', $file_type); + \Qiniu\setWithoutEmpty($params, 'ignore_same_key', $ignore_same_key); + $data = json_encode($params); + + return $this->apiPost($bucket, $path, $data); + } + + + /** + * 查询异步第三方资源抓取任务状态 + * + * @param string $zone + * @param string $id + * @return array + * @link https://developer.qiniu.com/kodo/api/4097/asynch-fetch + */ + public function asynchFetchStatus($zone, $id) + { + $scheme = "http://"; + + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + + $url = $scheme . "api-" . $zone . ".qiniuapi.com/sisyphus/fetch?id=" . $id; + + list($ret, $err) = $this->getV2($url); + + if ($err != null) { + return array(null, $err); + } + return array($ret, null); + } + + + /** + * 从镜像源站抓取资源到空间中,如果空间中已经存在,则覆盖该资源 + * + * @param string $bucket 待获取资源所在的空间 + * @param string $key 代获取资源文件名 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/1293/prefetch + */ + public function prefetch($bucket, $key) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/prefetch/' . $resource; + + $ak = $this->auth->getAccessKey(); + list($ioHost, $err) = $this->config->getIovipHostV2($ak, $bucket, $this->proxy->makeReqOpt()); + + if ($err != null) { + return array(null, $err); + } + + $url = $ioHost . $path; + return $this->postV2($url, null); + } + + /** + * 在单次请求中进行多个资源管理操作 + * + * @param array $operations 资源管理操作数组 + * + * @return array 每个资源的处理情况,结果类似: + * [ + * { "code" => , "data" => }, + * { "code" => }, + * { "code" => }, + * { "code" => }, + * { "code" => , "data" => { "error": "" } }, + * ... + * ] + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/batch.html + */ + public function batch($operations) + { + $scheme = "http://"; + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $params = 'op=' . implode('&op=', $operations); + $errResp = new Response(0, 0); + if (count($operations) <= 0) { + $errResp->error = 'empty operations'; + return array(null, new Error($scheme . '/batch', $errResp)); + } + $bucket = ''; + foreach ($operations as $op) { + $segments = explode('/', $op); + if (count($segments) < 3) { + continue; + } + list($bucket,) = \Qiniu\decodeEntry($segments[2]); + } + return $this->rsPost($bucket, '/batch', $params); + } + + /** + * 设置文件的生命周期 + * + * @param string $bucket 设置文件生命周期文件所在的空间 + * @param string $key 设置文件生命周期文件的文件名 + * @param int $days 设置该文件多少天后删除,当$days设置为0时表示取消该文件的生命周期 + * + * @return array + * @link https://developer.qiniu.com/kodo/api/update-file-lifecycle + */ + public function deleteAfterDays($bucket, $key, $days) + { + $entry = \Qiniu\entry($bucket, $key); + $path = "/deleteAfterDays/$entry/$days"; + return $this->rsPost($bucket, $path); + } + + /** + * 更新 object 生命周期 + * + * @param string $bucket 空间名 + * @param string $key 目标资源 + * @param int $to_line_after_days 多少天后将文件转为低频存储。 + * -1 表示取消已设置的转低频存储的生命周期规则; + * 0 表示不修改转低频生命周期规则。 + * @param int $to_archive_ir_after_days 多少天后转为归档直读存储。 + * -1 表示取消已设置的转归档直读存储的生命周期规则; + * 0 表示不修改转归档直读生命周期规则。 + * @param int $to_archive_after_days 多少天后将文件转为归档存储。 + * -1 表示取消已设置的转归档存储的生命周期规则; + * 0 表示不修改转归档生命周期规则。 + * @param int $to_deep_archive_after_days 多少天后将文件转为深度归档存储。 + * -1 表示取消已设置的转深度归档存储的生命周期规则; + * 0 表示不修改转深度归档生命周期规则。 + * @param int $delete_after_days 多少天后将文件删除。 + * -1 表示取消已设置的删除存储的生命周期规则; + * 0 表示不修改删除存储的生命周期规则。 + * @return array + */ + public function setObjectLifecycle( + $bucket, + $key, + $to_line_after_days = 0, + $to_archive_after_days = 0, + $to_deep_archive_after_days = 0, + $delete_after_days = 0, + $to_archive_ir_after_days = 0 + ) { + return $this->setObjectLifecycleWithCond( + $bucket, + $key, + null, + $to_line_after_days, + $to_archive_after_days, + $to_deep_archive_after_days, + $delete_after_days, + $to_archive_ir_after_days + ); + } + + /** + * 更新 object 生命周期 + * + * @param string $bucket 空间名 + * @param string $key 目标资源 + * @param int $to_line_after_days 多少天后将文件转为低频存储。 + * 设置为 -1 表示取消已设置的转低频存储的生命周期规则; + * 0 表示不修改转低频生命周期规则。 + * @param int $to_archive_ir_after_days 多少天后将文件转为归档直读存储。 + * 设置为 -1 表示取消已设置的转归档直读存储的生命周期规则; + * 0 表示不修改转归档直读生命周期规则。 + * @param int $to_archive_after_days 多少天后将文件转为归档存储。 + * -1 表示取消已设置的转归档存储的生命周期规则; + * 0 表示不修改转归档生命周期规则。 + * @param int $to_deep_archive_after_days 多少天后将文件转为深度归档存储。 + * -1 表示取消已设置的转深度归档存储的生命周期规则; + * 0 表示不修改转深度归档生命周期规则。 + * @param int $delete_after_days 多少天后将文件删除。 + * -1 表示取消已设置的删除存储的生命周期规则; + * 0 表示不修改删除存储的生命周期规则。 + * @param array $cond 匹配条件,只有条件匹配才会设置成功。 + * 目前支持:hash、mime、fsize、putTime + * @return array + */ + public function setObjectLifecycleWithCond( + $bucket, + $key, + $cond = null, + $to_line_after_days = 0, + $to_archive_after_days = 0, + $to_deep_archive_after_days = 0, + $delete_after_days = 0, + $to_archive_ir_after_days = 0 + ) { + $encodedEntry = \Qiniu\entry($bucket, $key); + $path = '/lifecycle/' . $encodedEntry . + '/toIAAfterDays/' . $to_line_after_days . + '/toArchiveIRAfterDays/' . $to_archive_ir_after_days . + '/toArchiveAfterDays/' . $to_archive_after_days . + '/toDeepArchiveAfterDays/' . $to_deep_archive_after_days . + '/deleteAfterDays/' . $delete_after_days; + if ($cond != null) { + $condStrArr = array(); + foreach ($cond as $key => $value) { + array_push($condStrArr, $key . '=' . $value); + } + $condStr = implode('&', $condStrArr); + $path .= '/cond' . \Qiniu\base64_urlSafeEncode($condStr); + } + return $this->rsPost($bucket, $path); + } + + private function rsfGet($bucket, $path) + { + list($host, $err) = $this->config->getRsfHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + return $this->getV2($host . $path); + } + + private function rsGet($bucket, $path) + { + list($host, $err) = $this->config->getRsHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + return $this->getV2($host . $path); + } + + private function rsPost($bucket, $path, $body = null) + { + list($host, $err) = $this->config->getRsHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + return $this->postV2($host . $path, $body); + } + + private function apiGet($bucket, $path) + { + list($host, $err) = $this->config->getApiHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + return $this->getV2($host . $path); + } + + private function apiPost($bucket, $path, $body = null) + { + + list($host, $err) = $this->config->getApiHostV2( + $this->auth->getAccessKey(), + $bucket, + $this->proxy->makeReqOpt() + ); + + if ($err != null) { + return array(null, $err); + } + + return $this->postV2($host . $path, $body); + } + + private function ucGet($path) + { + $url = $this->config->getUcHost() . $path; + return $this->getV2($url); + } + + private function ucPost($path, $body = null) + { + $url = $this->config->getUcHost() . $path; + return $this->postV2($url, $body); + } + + private function getV2($url) + { + $headers = $this->auth->authorizationV2($url, 'GET', null, 'application/x-www-form-urlencoded'); + $ret = Client::get($url, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function postV2($url, $body) + { + $headers = $this->auth->authorizationV2($url, 'POST', $body, 'application/x-www-form-urlencoded'); + $ret = Client::post($url, $body, $headers, $this->proxy->makeReqOpt()); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + public static function buildBatchCopy($source_bucket, $key_pairs, $target_bucket, $force) + { + return self::twoKeyBatch('/copy', $source_bucket, $key_pairs, $target_bucket, $force); + } + + + public static function buildBatchRename($bucket, $key_pairs, $force) + { + return self::buildBatchMove($bucket, $key_pairs, $bucket, $force); + } + + + public static function buildBatchMove($source_bucket, $key_pairs, $target_bucket, $force) + { + return self::twoKeyBatch('/move', $source_bucket, $key_pairs, $target_bucket, $force); + } + + + public static function buildBatchDelete($bucket, $keys) + { + return self::oneKeyBatch('/delete', $bucket, $keys); + } + + + public static function buildBatchStat($bucket, $keys) + { + return self::oneKeyBatch('/stat', $bucket, $keys); + } + + public static function buildBatchDeleteAfterDays($bucket, $key_day_pairs) + { + $data = array(); + foreach ($key_day_pairs as $key => $day) { + array_push($data, '/deleteAfterDays/' . \Qiniu\entry($bucket, $key) . '/' . $day); + } + return $data; + } + + /** + * @param string $bucket 空间名 + * @param array $keys 目标资源 + * @param int $to_line_after_days 多少天后将文件转为低频存储。 + * -1 表示取消已设置的转低频存储的生命周期规则; + * 0 表示不修改转低频生命周期规则。 + * @param int $to_archive_ir_after_days 多少天后将文件转为归档直读。 + * -1 表示取消已设置的转归档只读的生命周期规则; + * 0 表示不修改转归档只读周期规则。 + * @param int $to_archive_after_days 多少天后将文件转为归档存储。 + * -1 表示取消已设置的转归档存储的生命周期规则; + * 0 表示不修改转归档生命周期规则。 + * @param int $to_deep_archive_after_days 多少天后将文件转为深度归档存储。 + * -1 表示取消已设置的转深度归档存储的生命周期规则; + * 0 表示不修改转深度归档生命周期规则。 + * @param int $delete_after_days 多少天后将文件删除。 + * -1 表示取消已设置的删除存储的生命周期规则; + * 0 表示不修改删除存储的生命周期规则。 + * + * @retrun array + */ + public static function buildBatchSetObjectLifecycle( + $bucket, + $keys, + $to_line_after_days, + $to_archive_after_days, + $to_deep_archive_after_days, + $delete_after_days, + $to_archive_ir_after_days = 0 + ) { + $result = array(); + foreach ($keys as $key) { + $encodedEntry = \Qiniu\entry($bucket, $key); + $op = '/lifecycle/' . $encodedEntry . + '/toIAAfterDays/' . $to_line_after_days . + '/toArchiveIRAfterDays/' . $to_archive_ir_after_days . + '/toArchiveAfterDays/' . $to_archive_after_days . + '/toDeepArchiveAfterDays/' . $to_deep_archive_after_days . + '/deleteAfterDays/' . $delete_after_days; + array_push($result, $op); + } + return $result; + } + + public static function buildBatchChangeMime($bucket, $key_mime_pairs) + { + $data = array(); + foreach ($key_mime_pairs as $key => $mime) { + array_push($data, '/chgm/' . \Qiniu\entry($bucket, $key) . '/mime/' . base64_encode($mime)); + } + return $data; + } + + public static function buildBatchChangeType($bucket, $key_type_pairs) + { + $data = array(); + foreach ($key_type_pairs as $key => $type) { + array_push($data, '/chtype/' . \Qiniu\entry($bucket, $key) . '/type/' . $type); + } + return $data; + } + + public static function buildBatchRestoreAr($bucket, $key_restore_days_pairs) + { + $data = array(); + foreach ($key_restore_days_pairs as $key => $restore_days) { + array_push($data, '/restoreAr/' . \Qiniu\entry($bucket, $key) . '/freezeAfterDays/' . $restore_days); + } + return $data; + } + + private static function oneKeyBatch($operation, $bucket, $keys) + { + $data = array(); + foreach ($keys as $key) { + array_push($data, $operation . '/' . \Qiniu\entry($bucket, $key)); + } + return $data; + } + + private static function twoKeyBatch($operation, $source_bucket, $key_pairs, $target_bucket, $force) + { + if ($target_bucket === null) { + $target_bucket = $source_bucket; + } + $data = array(); + $forceOp = "false"; + if ($force) { + $forceOp = "true"; + } + foreach ($key_pairs as $from_key => $to_key) { + $from = \Qiniu\entry($source_bucket, $from_key); + $to = \Qiniu\entry($target_bucket, $to_key); + array_push($data, $operation . '/' . $from . '/' . $to . "/force/" . $forceOp); + } + return $data; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php b/vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php new file mode 100644 index 0000000..d68654d --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php @@ -0,0 +1,165 @@ + "", + * "key" => "" + * ] + */ + public static function put( + $upToken, + $key, + $data, + $config, + $params, + $mime, + $fname, + $reqOpt = null + ) { + if ($reqOpt == null) { + $reqOpt = new RequestOptions(); + } + $fields = array('token' => $upToken); + if ($key === null) { + } else { + $fields['key'] = $key; + } + + //enable crc32 check by default + $fields['crc32'] = \Qiniu\crc32_data($data); + + if ($params) { + foreach ($params as $k => $v) { + $fields[$k] = $v; + } + } + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + if ($err != null) { + return array(null, $err); + } + + list($upHost, $err) = $config->getUpHostV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + + $response = Client::multipartPost( + $upHost, + $fields, + 'file', + $fname, + $data, + $mime, + array(), + $reqOpt + ); + if (!$response->ok()) { + return array(null, new Error($upHost, $response)); + } + return array($response->json(), null); + } + + /** + * 上传文件到七牛,内部使用 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param string $filePath 上传文件的路径 + * @param Config $config 上传配置 + * @param string $params 自定义变量,规格参考 + * https://developer.qiniu.com/kodo/manual/1235/vars#xvar + * @param string $mime 上传数据的mimeType + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "", + * "key" => "" + * ] + */ + public static function putFile( + $upToken, + $key, + $filePath, + $config, + $params, + $mime, + $reqOpt = null + ) { + if ($reqOpt == null) { + $reqOpt = new RequestOptions(); + } + + $fields = array('token' => $upToken, 'file' => self::createFile($filePath, $mime)); + if ($key !== null) { + $fields['key'] = $key; + } + + $fields['crc32'] = \Qiniu\crc32_file($filePath); + + if ($params) { + foreach ($params as $k => $v) { + $fields[$k] = $v; + } + } + $fields['key'] = $key; + $headers = array('Content-Type' => 'multipart/form-data'); + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + if ($err != null) { + return array(null, $err); + } + + list($upHost, $err) = $config->getUpHostV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + return array(null, $err); + } + + $response = Client::post($upHost, $fields, $headers, $reqOpt); + if (!$response->ok()) { + return array(null, new Error($upHost, $response)); + } + return array($response->json(), null); + } + + private static function createFile($filename, $mime) + { + // PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax + // See: https://wiki.php.net/rfc/curl-file-upload + if (function_exists('curl_file_create')) { + return curl_file_create($filename, $mime); + } + + // Use the old style if using an older version of PHP + $value = "@{$filename}"; + if (!empty($mime)) { + $value .= ';type=' . $mime; + } + + return $value; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php b/vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php new file mode 100644 index 0000000..00e88ef --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php @@ -0,0 +1,580 @@ + $params 自定义变量 + * @param string $mime 上传数据的mimeType + * @param Config $config + * @param string $resumeRecordFile 断点续传的已上传的部分信息记录文件 + * @param string $version 分片上传版本 目前支持v1/v2版本 默认v1 + * @param int $partSize 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB + * @param RequestOptions $reqOpt 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB + * @throws \Exception + * + * @link http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + */ + public function __construct( + $upToken, + $key, + $inputStream, + $size, + $params, + $mime, + $config, + $resumeRecordFile = null, + $version = 'v1', + $partSize = config::BLOCK_SIZE, + $reqOpt = null + ) { + + $this->upToken = $upToken; + $this->key = $key; + $this->inputStream = $inputStream; + $this->size = $size; + $this->params = $params; + $this->mime = $mime; + $this->contexts = array(); + $this->finishedEtags = array("etags" => array(), "uploadId" => "", "expiredAt" => 0, "uploaded" => 0); + $this->config = $config; + $this->resumeRecordFile = $resumeRecordFile ? $resumeRecordFile : null; + $this->partSize = $partSize ? $partSize : config::BLOCK_SIZE; + + if ($reqOpt === null) { + $reqOpt = new RequestOptions(); + } + $this->reqOpt = $reqOpt; + + try { + $this->version = SplitUploadVersion::from($version ? $version : 'v1'); + } catch (\Exception $e) { + throw new \Exception("only support v1/v2 now!", 0, $e); + } + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + $this->bucket = $bucket; + if ($err != null) { + return array(null, $err); + } + + list($upHost, $err) = $config->getUpHostV2($accessKey, $bucket, $reqOpt); + if ($err != null) { + throw new \Exception($err->message(), 1); + } + $this->host = $upHost; + } + + /** + * 上传操作 + * @param $fname string 文件名 + * + * @throws \Exception + */ + public function upload($fname) + { + $blkputRets = null; + // get upload record from resumeRecordFile + if ($this->resumeRecordFile != null) { + if (file_exists($this->resumeRecordFile)) { + $stream = fopen($this->resumeRecordFile, 'r'); + if ($stream) { + $streamLen = filesize($this->resumeRecordFile); + if ($streamLen > 0) { + $contents = fread($stream, $streamLen); + fclose($stream); + if ($contents) { + $blkputRets = json_decode($contents, true); + if ($blkputRets === null) { + error_log("resumeFile contents decode error"); + } + } else { + error_log("read resumeFile failed"); + } + } else { + error_log("resumeFile is empty"); + } + } else { + error_log("resumeFile open failed"); + } + } else { + error_log("resumeFile not exists"); + } + } + + if ($this->version == SplitUploadVersion::V1) { + return $this->uploadV1($fname, $blkputRets); + } elseif ($this->version == SplitUploadVersion::V2) { + return $this->uploadV2($fname, $blkputRets); + } else { + throw new \Exception("only support v1/v2 now!"); + } + } + + /** + * @param string $fname 文件名 + * @param null|array $blkputRets + * + * @throws \Exception + */ + private function uploadV1($fname, $blkputRets = null) + { + // 尝试恢复恢复已上传的数据 + $isResumeUpload = $blkputRets !== null; + $this->contexts = array(); + + if ($blkputRets) { + if (isset($blkputRets['contexts']) && isset($blkputRets['uploaded']) && + is_array($blkputRets['contexts']) && is_int($blkputRets['uploaded']) + ) { + $this->contexts = array_map(function ($ctx) { + if (is_array($ctx)) { + return $ctx; + } else { + // 兼容旧版本(旧版本没有存储 expireAt) + return array( + "ctx" => $ctx, + "expiredAt" => 0, + ); + } + }, $blkputRets['contexts']); + } + } + + // 上传分片 + $uploaded = 0; + while ($uploaded < $this->size) { + $blockSize = $this->blockSize($uploaded); + $blockIndex = $uploaded / $this->partSize; + if (!is_int($blockIndex)) { + throw new \Exception("v1 part size changed"); + } + // 如果已上传该分片且没有过期 + if (isset($this->contexts[$blockIndex]) && $this->contexts[$blockIndex]["expiredAt"] >= time()) { + $uploaded += $blockSize; + fseek($this->inputStream, $blockSize, SEEK_CUR); + continue; + } + $data = fread($this->inputStream, $blockSize); + if ($data === false) { + throw new \Exception("file read failed", 1); + } + $crc = \Qiniu\crc32_data($data); + $response = $this->makeBlock($data, $blockSize); + + + $ret = null; + if ($response->ok() && $response->json() != null) { + $ret = $response->json(); + } + if ($response->statusCode < 0) { + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken); + if ($err != null) { + return array(null, $err); + } + list($upHostBackup, $err) = $this->config->getUpBackupHostV2($accessKey, $bucket, $this->reqOpt); + if ($err != null) { + return array(null, $err); + } + $this->host = $upHostBackup; + } + + if ($response->needRetry() || !isset($ret['crc32']) || $crc != $ret['crc32']) { + $response = $this->makeBlock($data, $blockSize); + $ret = $response->json(); + } + if (!$response->ok() || !isset($ret['crc32']) || $crc != $ret['crc32']) { + return array(null, new Error($this->currentUrl, $response)); + } + + // 如果可以在已上传取到说明是过期分片直接修改已上传信息,否则是新的片添加到已上传分片尾部 + if (isset($this->contexts[$blockIndex])) { + $this->contexts[$blockIndex] = array( + 'ctx' => $ret['ctx'], + 'expiredAt' => $ret['expired_at'], + ); + } else { + array_push($this->contexts, array( + 'ctx' => $ret['ctx'], + 'expiredAt' => $ret['expired_at'], + )); + } + $uploaded += $blockSize; + + // 记录断点 + if ($this->resumeRecordFile !== null) { + $recordData = array( + 'contexts' => $this->contexts, + 'uploaded' => $uploaded + ); + $recordData = json_encode($recordData); + + if ($recordData) { + $isWritten = file_put_contents($this->resumeRecordFile, $recordData); + if ($isWritten === false) { + error_log("write resumeRecordFile failed"); + } + } else { + error_log('resumeRecordData encode failed'); + } + } + } + + // 完成上传 + list($ret, $err) = $this->makeFile($fname); + if ($err !== null) { + $response = $err->getResponse(); + if ($isResumeUpload && $response->statusCode === 701) { + fseek($this->inputStream, 0); + return $this->uploadV1($fname); + } + } + return array($ret, $err); + } + + /** + * @param string $fname 文件名 + * @param null|array $blkputRets + * + * @throws \Exception + */ + private function uploadV2($fname, $blkputRets = null) + { + $uploaded = 0; + $partNumber = 1; + $encodedObjectName = $this->key ? \Qiniu\base64_urlSafeEncode($this->key) : '~'; + $isResumeUpload = $blkputRets !== null; + + // 初始化 upload id + $err = null; + if ($blkputRets) { + if (isset($blkputRets["etags"]) && isset($blkputRets["uploadId"]) && + isset($blkputRets["expiredAt"]) && $blkputRets["expiredAt"] > time() && + $blkputRets["uploaded"] > 0 && is_array($blkputRets["etags"]) && + is_string($blkputRets["uploadId"]) && is_int($blkputRets["expiredAt"]) + ) { + $this->finishedEtags['etags'] = $blkputRets["etags"]; + $this->finishedEtags["uploadId"] = $blkputRets["uploadId"]; + $this->finishedEtags["expiredAt"] = $blkputRets["expiredAt"]; + $this->finishedEtags["uploaded"] = $blkputRets["uploaded"]; + $uploaded = $blkputRets["uploaded"]; + $partNumber = count($this->finishedEtags["etags"]) + 1; + } else { + $err = $this->makeInitReq($encodedObjectName); + } + } else { + $err = $this->makeInitReq($encodedObjectName); + } + if ($err != null) { + return array(null, $err); + } + + // 上传分片 + fseek($this->inputStream, $uploaded); + while ($uploaded < $this->size) { + $blockSize = $this->blockSize($uploaded); + $data = fread($this->inputStream, $blockSize); + if ($data === false) { + throw new \Exception("file read failed", 1); + } + $md5 = md5($data); + $response = $this->uploadPart( + $data, + $partNumber, + $this->finishedEtags["uploadId"], + $encodedObjectName, + $md5 + ); + + $ret = null; + if ($response->ok() && $response->json() != null) { + $ret = $response->json(); + } + if ($response->statusCode < 0) { + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken); + if ($err != null) { + return array(null, $err); + } + list($upHostBackup, $err) = $this->config->getUpBackupHostV2($accessKey, $bucket, $this->reqOpt); + if ($err != null) { + return array(null, $err); + } + $this->host = $upHostBackup; + } + + if ($response->needRetry() || !isset($ret['md5']) || $md5 != $ret['md5']) { + $response = $this->uploadPart( + $data, + $partNumber, + $this->finishedEtags["uploadId"], + $encodedObjectName, + $md5 + ); + $ret = $response->json(); + } + if ($isResumeUpload && $response->statusCode === 612) { + return $this->uploadV2($fname); + } + if (!$response->ok() || !isset($ret['md5']) || $md5 != $ret['md5']) { + return array(null, new Error($this->currentUrl, $response)); + } + $blockStatus = array('etag' => $ret['etag'], 'partNumber' => $partNumber); + array_push($this->finishedEtags['etags'], $blockStatus); + $partNumber += 1; + + $uploaded += $blockSize; + $this->finishedEtags['uploaded'] = $uploaded; + + if ($this->resumeRecordFile !== null) { + $recordData = json_encode($this->finishedEtags); + if ($recordData) { + $isWritten = file_put_contents($this->resumeRecordFile, $recordData); + if ($isWritten === false) { + error_log("write resumeRecordFile failed"); + } + } else { + error_log('resumeRecordData encode failed'); + } + } + } + + list($ret, $err) = $this->completeParts($fname, $this->finishedEtags['uploadId'], $encodedObjectName); + if ($err !== null) { + $response = $err->getResponse(); + if ($isResumeUpload && $response->statusCode === 612) { + return $this->uploadV2($fname); + } + } + return array($ret, $err); + } + + /** + * 创建块 + */ + private function makeBlock($block, $blockSize) + { + $url = $this->host . '/mkblk/' . $blockSize; + return $this->post($url, $block); + } + + private function fileUrl($fname) + { + $url = $this->host . '/mkfile/' . $this->size; + $url .= '/mimeType/' . \Qiniu\base64_urlSafeEncode($this->mime); + if ($this->key != null) { + $url .= '/key/' . \Qiniu\base64_urlSafeEncode($this->key); + } + $url .= '/fname/' . \Qiniu\base64_urlSafeEncode($fname); + if (!empty($this->params)) { + foreach ($this->params as $key => $value) { + $val = \Qiniu\base64_urlSafeEncode($value); + $url .= "/$key/$val"; + } + } + return $url; + } + + /** + * 创建文件 + * + * @param string $fname 文件名 + * @return array{array | null, Error | null} + */ + private function makeFile($fname) + { + $url = $this->fileUrl($fname); + $body = implode(',', array_map(function ($ctx) { + return $ctx['ctx']; + }, $this->contexts)); + $response = $this->post($url, $body); + if ($response->needRetry()) { + $response = $this->post($url, $body); + } + if ($response->statusCode === 200 || $response->statusCode === 701) { + if ($this->resumeRecordFile !== null) { + @unlink($this->resumeRecordFile); + } + } + if (!$response->ok()) { + return array(null, new Error($this->currentUrl, $response)); + } + return array($response->json(), null); + } + + private function post($url, $data) + { + $this->currentUrl = $url; + $headers = array('Authorization' => 'UpToken ' . $this->upToken); + return Client::post($url, $data, $headers, $this->reqOpt); + } + + private function blockSize($uploaded) + { + if ($this->size < $uploaded + $this->partSize) { + return $this->size - $uploaded; + } + return $this->partSize; + } + + private function makeInitReq($encodedObjectName) + { + list($ret, $err) = $this->initReq($encodedObjectName); + + if ($ret == null) { + return $err; + } + + $this->finishedEtags["uploadId"] = $ret['uploadId']; + $this->finishedEtags["expiredAt"] = $ret['expireAt']; + return $err; + } + + /** + * 初始化上传任务 + */ + private function initReq($encodedObjectName) + { + $url = $this->host . '/buckets/' . $this->bucket . '/objects/' . $encodedObjectName . '/uploads'; + $headers = array( + 'Authorization' => 'UpToken ' . $this->upToken, + 'Content-Type' => 'application/json' + ); + $response = $this->postWithHeaders($url, null, $headers); + $ret = $response->json(); + if ($response->ok() && $ret != null) { + return array($ret, null); + } + + return array(null, new Error($url, $response)); + } + + /** + * 分块上传v2 + */ + private function uploadPart($block, $partNumber, $uploadId, $encodedObjectName, $md5) + { + $headers = array( + 'Authorization' => 'UpToken ' . $this->upToken, + 'Content-Type' => 'application/octet-stream', + 'Content-MD5' => $md5 + ); + $url = $this->host . '/buckets/' . $this->bucket . '/objects/' . $encodedObjectName . + '/uploads/' . $uploadId . '/' . $partNumber; + $response = $this->put($url, $block, $headers); + if ($response->statusCode === 612) { + if ($this->resumeRecordFile !== null) { + @unlink($this->resumeRecordFile); + } + } + return $response; + } + + /** + * 完成分片上传V2 + * + * @param string $fname 文件名 + * @param int $uploadId 由 {@see initReq} 获取 + * @param string $encodedObjectName 经过编码的存储路径 + * @return array{array | null, Error | null} + */ + private function completeParts($fname, $uploadId, $encodedObjectName) + { + $headers = array( + 'Authorization' => 'UpToken ' . $this->upToken, + 'Content-Type' => 'application/json' + ); + $etags = $this->finishedEtags['etags']; + $sortedEtags = \Qiniu\arraySort($etags, 'partNumber'); + $metadata = array(); + $customVars = array(); + if ($this->params) { + foreach ($this->params as $k => $v) { + if (strpos($k, 'x:') === 0) { + $customVars[$k] = $v; + } elseif (strpos($k, 'x-qn-meta-') === 0) { + $metadata[$k] = $v; + } + } + } + if (empty($metadata)) { + $metadata = null; + } + if (empty($customVars)) { + $customVars = null; + } + $body = array( + 'fname' => $fname, + 'mimeType' => $this->mime, + 'metadata' => $metadata, + 'customVars' => $customVars, + 'parts' => $sortedEtags + ); + $jsonBody = json_encode($body); + $url = $this->host . '/buckets/' . $this->bucket . '/objects/' . $encodedObjectName . '/uploads/' . $uploadId; + $response = $this->postWithHeaders($url, $jsonBody, $headers); + if ($response->needRetry()) { + $response = $this->postWithHeaders($url, $jsonBody, $headers); + } + if ($response->statusCode === 200 || $response->statusCode === 612) { + if ($this->resumeRecordFile !== null) { + @unlink($this->resumeRecordFile); + } + } + if (!$response->ok()) { + return array(null, new Error($this->currentUrl, $response)); + } + return array($response->json(), null); + } + + private function put($url, $data, $headers) + { + $this->currentUrl = $url; + return Client::put($url, $data, $headers, $this->reqOpt); + } + + private function postWithHeaders($url, $data, $headers) + { + $this->currentUrl = $url; + return Client::post($url, $data, $headers, $this->reqOpt); + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php b/vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php new file mode 100644 index 0000000..fcd11fa --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php @@ -0,0 +1,176 @@ +config = $config; + + if ($reqOpt === null) { + $reqOpt = new RequestOptions(); + } + + $this->reqOpt = $reqOpt; + } + + /** + * 上传二进制流到七牛 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param string $data 上传二进制流 + * @param array $params 自定义变量,规格参考 + * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param string $mime 上传数据的mimeType + * @param string $fname + * @param RequestOptions $reqOpt + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "", + * "key" => "" + * ] + */ + public function put( + $upToken, + $key, + $data, + $params = null, + $mime = 'application/octet-stream', + $fname = "default_filename", + $reqOpt = null + ) { + $reqOpt = $reqOpt === null ? $this->reqOpt : $reqOpt; + + $params = self::trimParams($params); + return FormUploader::put( + $upToken, + $key, + $data, + $this->config, + $params, + $mime, + $fname, + $reqOpt + ); + } + + + /** + * 上传文件到七牛 + * + * @param string $upToken 上传凭证 + * @param string $key 上传文件名 + * @param string $filePath 上传文件的路径 + * @param array $params 定义变量,规格参考 + * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param boolean $mime 上传数据的mimeType + * @param string $checkCrc 是否校验crc32 + * @param string $resumeRecordFile 断点续传文件路径 默认为null + * @param string $version 分片上传版本 目前支持v1/v2版本 默认v1 + * @param int $partSize 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "", + * "key" => "" + * ] + * @throws \Exception + */ + public function putFile( + $upToken, + $key, + $filePath, + $params = null, + $mime = 'application/octet-stream', + $checkCrc = false, + $resumeRecordFile = null, + $version = 'v1', + $partSize = config::BLOCK_SIZE, + $reqOpt = null + ) { + $reqOpt = $reqOpt === null ? $this->reqOpt : $reqOpt; + + $file = fopen($filePath, 'rb'); + if ($file === false) { + throw new \Exception("file can not open", 1); + } + $params = self::trimParams($params); + $stat = fstat($file); + $size = $stat['size']; + if ($size <= Config::BLOCK_SIZE) { + $data = fread($file, $size); + fclose($file); + if ($data === false) { + throw new \Exception("file can not read", 1); + } + return FormUploader::put( + $upToken, + $key, + $data, + $this->config, + $params, + $mime, + basename($filePath), + $reqOpt + ); + } + + $up = new ResumeUploader( + $upToken, + $key, + $file, + $size, + $params, + $mime, + $this->config, + $resumeRecordFile, + $version, + $partSize, + $reqOpt + ); + $ret = $up->upload(basename($filePath)); + fclose($file); + return $ret; + } + + public static function trimParams($params) + { + if ($params === null) { + return null; + } + $ret = array(); + foreach ($params as $k => $v) { + $pos1 = strpos($k, 'x:'); + $pos2 = strpos($k, 'x-qn-meta-'); + if (($pos1 === 0 || $pos2 === 0) && !empty($v)) { + $ret[$k] = $v; + } + } + return $ret; + } +} diff --git a/vendor/qiniu/php-sdk/src/Qiniu/Zone.php b/vendor/qiniu/php-sdk/src/Qiniu/Zone.php new file mode 100644 index 0000000..50d60c6 --- /dev/null +++ b/vendor/qiniu/php-sdk/src/Qiniu/Zone.php @@ -0,0 +1,58 @@ + $v) { + $keysValue[$k] = $v[$key]; + } + array_multisort($keysValue, $sort, $array); + return $array; + } + + /** + * Wrapper for JSON decode that implements error detection with helpful + * error messages. + * + * @param string $json JSON data to parse + * @param bool $assoc When true, returned objects will be converted + * into associative arrays. + * @param int $depth User specified recursion depth. + * + * @return mixed + * @throws \InvalidArgumentException if the JSON cannot be parsed. + * @link http://www.php.net/manual/en/function.json-decode.php + */ + function json_decode($json, $assoc = false, $depth = 512) + { + static $jsonErrors = array( + JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' + ); + + if (empty($json)) { + return null; + } + $data = \json_decode($json, $assoc, $depth); + + if (JSON_ERROR_NONE !== json_last_error()) { + $last = json_last_error(); + throw new \InvalidArgumentException( + 'Unable to parse JSON data: ' + . (isset($jsonErrors[$last]) + ? $jsonErrors[$last] + : 'Unknown error') + ); + } + + return $data; + } + + /** + * 计算七牛API中的数据格式 + * + * @param string $bucket 待操作的空间名 + * @param string $key 待操作的文件名 + * + * @return string 符合七牛API规格的数据格式 + * @link https://developer.qiniu.com/kodo/api/data-format + */ + function entry($bucket, $key = null) + { + $en = $bucket; + if ($key !== null) { + $en = $bucket . ':' . $key; + } + return base64_urlSafeEncode($en); + } + + function decodeEntry($entry) + { + $en = base64_urlSafeDecode($entry); + $en = explode(':', $en); + if (count($en) == 1) { + return array($en[0], null); + } + return array($en[0], $en[1]); + } + + /** + * array 辅助方法,无值时不set + * + * @param array $array 待操作array + * @param string $key key + * @param string $value value 为null时 不设置 + * + * @return array 原来的array,便于连续操作 + */ + function setWithoutEmpty(&$array, $key, $value) + { + if (!empty($value)) { + $array[$key] = $value; + } + return $array; + } + + /** + * 缩略图链接拼接 + * + * @param string $url 图片链接 + * @param int $mode 缩略模式 + * @param int $width 宽度 + * @param int $height 长度 + * @param string $format 输出类型 + * @param int $quality 图片质量 + * @param int $interlace 是否支持渐进显示 + * @param int $ignoreError 忽略结果 + * @return string + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html + * @author Sherlock Ren + */ + function thumbnail( + $url, + $mode, + $width, + $height, + $format = null, + $quality = null, + $interlace = null, + $ignoreError = 1 + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'thumbnail'), func_get_args()); + } + + /** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @param numeric $watermarkScale 自适应原图的短边比例 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html + * @return string + * @author Sherlock Ren + */ + function waterImg( + $url, + $image, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null, + $watermarkScale = null + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'waterImg'), func_get_args()); + } + + /** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @return string + * @author Sherlock Ren + */ + function waterText( + $url, + $text, + $font = '黑体', + $fontSize = 0, + $fontColor = null, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'waterText'), func_get_args()); + } + + /** + * 从uptoken解析accessKey和bucket + * + * @param $upToken + * @return array(ak,bucket,err=null) + */ + function explodeUpToken($upToken) + { + $items = explode(':', $upToken); + if (count($items) != 3) { + return array(null, null, "invalid uptoken"); + } + $accessKey = $items[0]; + $putPolicy = json_decode(base64_urlSafeDecode($items[2])); + $scope = $putPolicy->scope; + $scopeItems = explode(':', $scope); + $bucket = $scopeItems[0]; + return array($accessKey, $bucket, null); + } + + // polyfill ucwords for `php version < 5.4.32` or `5.5.0 <= php version < 5.5.16` + if (version_compare(phpversion(), "5.4.32") < 0 || + ( + version_compare(phpversion(), "5.5.0") >= 0 && + version_compare(phpversion(), "5.5.16") < 0 + ) + ) { + function ucwords($str, $delimiters = " \t\r\n\f\v") + { + $delims = preg_split('//u', $delimiters, -1, PREG_SPLIT_NO_EMPTY); + + foreach ($delims as $delim) { + $str = implode($delim, array_map('ucfirst', explode($delim, $str))); + } + + return $str; + } + } else { + function ucwords($str, $delimiters) + { + return \ucwords($str, $delimiters); + } + } + + /** + * 将 parse_url 的结果转换回字符串 + * TODO: add unit test + * + * @param $parsed_url - parse_url 的结果 + * @return string + */ + function unparse_url($parsed_url) + { + + $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; + + $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; + + $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; + + $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; + + $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; + + $pass = ($user || $pass) ? "$pass@" : ''; + + $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; + + $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; + + $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; + + return "$scheme$user$pass$host$port$path$query$fragment"; + } +} diff --git a/vendor/qiniu/php-sdk/test-env.sh b/vendor/qiniu/php-sdk/test-env.sh new file mode 100644 index 0000000..eedf6b5 --- /dev/null +++ b/vendor/qiniu/php-sdk/test-env.sh @@ -0,0 +1,4 @@ +export QINIU_ACCESS_KEY=xxx +export QINIU_SECRET_KEY=xxx +export QINIU_TEST_BUCKET=phpsdk +export QINIU_TEST_DOMAIN=phpsdk.qiniudn.com \ No newline at end of file diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php new file mode 100644 index 0000000..99aec85 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php @@ -0,0 +1,296 @@ +sign('test'); + $this->assertEquals('abcdefghklmnopq:mSNBTR7uS2crJsyFr2Amwv1LaYg=', $token); + } + + public function testSignWithData() + { + global $dummyAuth; + $token = $dummyAuth->signWithData('test'); + $this->assertEquals('abcdefghklmnopq:-jP8eEV9v48MkYiBGs81aDxl60E=:dGVzdA==', $token); + } + + public function testSignRequest() + { + global $dummyAuth; + $token = $dummyAuth->signRequest('http://www.qiniu.com?go=1', 'test', ''); + $this->assertEquals('abcdefghklmnopq:cFyRVoWrE3IugPIMP5YJFTO-O-Y=', $token); + $ctype = 'application/x-www-form-urlencoded'; + $token = $dummyAuth->signRequest('http://www.qiniu.com?go=1', 'test', $ctype); + $this->assertEquals($token, 'abcdefghklmnopq:svWRNcacOE-YMsc70nuIYdaa1e4='); + } + + public function testPrivateDownloadUrl() + { + global $dummyAuth; + $_SERVER['override_qiniu_auth_time'] = true; + $url = $dummyAuth->privateDownloadUrl('http://www.qiniu.com?go=1'); + $expect = 'http://www.qiniu.com?go=1&e=1234571490&token=abcdefghklmnopq:8vzBeLZ9W3E4kbBLFLW0Xe0u7v4='; + $this->assertEquals($expect, $url); + unset($_SERVER['override_qiniu_auth_time']); + } + + public function testUploadToken() + { + global $dummyAuth; + $_SERVER['override_qiniu_auth_time'] = true; + $token = $dummyAuth->uploadToken('1', '2', 3600, array('endUser' => 'y')); + // @codingStandardsIgnoreStart + $exp = 'abcdefghklmnopq:yyeexeUkPOROoTGvwBjJ0F0VLEo=:eyJlbmRVc2VyIjoieSIsInNjb3BlIjoiMToyIiwiZGVhZGxpbmUiOjEyMzQ1NzE0OTB9'; + // @codingStandardsIgnoreEnd + $this->assertEquals($exp, $token); + unset($_SERVER['override_qiniu_auth_time']); + } + + public function testSignQiniuAuthorization() + { + $auth = new Auth("ak", "sk"); + + $testCases = array( + array( + "url" => "", + "method" => "", + "headers" => array( + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + "Content-Type" => array("application/x-www-form-urlencoded") + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:0i1vKClRDWFyNkcTFzwcE7PzX74=" + ), + array( + "url" => "", + "method" => "", + "headers" => array( + "Content-Type" => array("application/json") + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:K1DI0goT05yhGizDFE5FiPJxAj4=" + ), + array( + "url" => "", + "method" => "GET", + "headers" => array( + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + "Content-Type" => array("application/x-www-form-urlencoded"), + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:0i1vKClRDWFyNkcTFzwcE7PzX74=" + ), + array( + "url" => "", + "method" => "POST", + "headers" => array( + "Content-Type" => array("application/json"), + "X-Qiniu" => array("b"), + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:0ujEjW_vLRZxebsveBgqa3JyQ-w=" + ), + array( + "url" => "http://upload.qiniup.com", + "method" => "", + "headers" => array( + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + "Content-Type" => array("application/x-www-form-urlencoded"), + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:GShw5NitGmd5TLoo38nDkGUofRw=" + ), + array( + "url" => "http://upload.qiniup.com", + "method" => "", + "headers" => array( + "Content-Type" => array("application/json"), + "X-Qiniu-Bbb" => array("BBB", "AAA"), + "X-Qiniu-Aaa" => array("DDD", "CCC"), + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + ), + "body" => "{\"name\": \"test\"}", + "expectedToken" => "ak:DhNA1UCaBqSHCsQjMOLRfVn63GQ=" + ), + array( + "url" => "http://upload.qiniup.com", + "method" => "", + "headers" => array( + "Content-Type" => array("application/x-www-form-urlencoded"), + "X-Qiniu-Bbb" => array("BBB", "AAA"), + "X-Qiniu-Aaa" => array("DDD", "CCC"), + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + ), + "body" => "name=test&language=go", + "expectedToken" => "ak:KUAhrYh32P9bv0COD8ugZjDCmII=" + ), + array( + "url" => "http://upload.qiniup.com", + "method" => "", + "headers" => array( + "Content-Type" => array("application/x-www"), + "Content-Type" => array("application/x-www-form-urlencoded"), + "X-Qiniu-Bbb" => array("BBB", "AAA"), + "X-Qiniu-Aaa" => array("DDD", "CCC"), + ), + "body" => "name=test&language=go", + "expectedToken" => "ak:KUAhrYh32P9bv0COD8ugZjDCmII=" + ), + array( + "url" => "http://upload.qiniup.com/mkfile/sdf.jpg", + "method" => "", + "headers" => array( + "Content-Type" => array("application/x-www-form-urlencoded"), + "X-Qiniu-Bbb" => array("BBB", "AAA"), + "X-Qiniu-Aaa" => array("DDD", "CCC"), + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + ), + "body" => "name=test&language=go", + "expectedToken" => "ak:fkRck5_LeyfwdkyyLk-hyNwGKac=" + ), + array( + "url" => "http://upload.qiniup.com/mkfile/sdf.jpg?s=er3&df", + "method" => "", + "headers" => array( + "Content-Type" => array("application/x-www-form-urlencoded"), + "X-Qiniu-Bbb" => array("BBB", "AAA"), + "X-Qiniu-Aaa" => array("DDD", "CCC"), + "X-Qiniu-" => array("a"), + "X-Qiniu" => array("b"), + ), + "body" => "name=test&language=go", + "expectedToken" => "ak:PUFPWsEUIpk_dzUvvxTTmwhp3p4=" + ) + ); + + foreach ($testCases as $testCase) { + list($sign, $err) = $auth->signQiniuAuthorization( + $testCase["url"], + $testCase["method"], + $testCase["body"], + new Header($testCase["headers"]) + ); + + $this->assertNull($err); + $this->assertEquals($testCase["expectedToken"], $sign); + } + } + + public function testDisableQiniuTimestampSignatureDefault() + { + $auth = new Auth("ak", "sk"); + $authedHeaders = $auth->authorizationV2("https://example.com", "GET"); + $this->assertArrayHasKey("X-Qiniu-Date", $authedHeaders); + } + + public function testDisableQiniuTimestampSignature() + { + $auth = new Auth("ak", "sk", array( + "disableQiniuTimestampSignature" => true + )); + $authedHeaders = $auth->authorizationV2("https://example.com", "GET"); + $this->assertArrayNotHasKey("X-Qiniu-Date", $authedHeaders); + } + public function testDisableQiniuTimestampSignatureEnv() + { + putenv("DISABLE_QINIU_TIMESTAMP_SIGNATURE=true"); + $auth = new Auth("ak", "sk"); + $authedHeaders = $auth->authorizationV2("https://example.com", "GET"); + $this->assertArrayNotHasKey("X-Qiniu-Date", $authedHeaders); + putenv('DISABLE_QINIU_TIMESTAMP_SIGNATURE'); + } + public function testDisableQiniuTimestampSignatureEnvBeIgnored() + { + putenv("DISABLE_QINIU_TIMESTAMP_SIGNATURE=true"); + $auth = new Auth("ak", "sk", array( + "disableQiniuTimestampSignature" => false + )); + $authedHeaders = $auth->authorizationV2("https://example.com", "GET"); + $this->assertArrayHasKey("X-Qiniu-Date", $authedHeaders); + putenv('DISABLE_QINIU_TIMESTAMP_SIGNATURE'); + } + public function testQboxVerifyCallbackShouldOkWithRequiredOptions() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'QBox abcdefghklmnopq:T7F-SjxX7X2zI4Fc1vANiNt1AUE=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123' + ); + $this->assertTrue($ok); + } + public function testQboxVerifyCallbackShouldOkWithOmitOptions() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'QBox abcdefghklmnopq:T7F-SjxX7X2zI4Fc1vANiNt1AUE=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123', + 'POST', // this should be omit + array( + 'X-Qiniu-Bbb' => 'BBB' + ) // this should be omit + ); + $this->assertTrue($ok); + } + public function testQiniuVerifyCallbackShouldOk() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'Qiniu abcdefghklmnopq:ZqS7EZuAKrhZaEIxqNGxDJi41IQ=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123', + 'GET', + array( + 'X-Qiniu-Bbb' => 'BBB' + ) + ); + $this->assertTrue($ok); + } + public function testQiniuVerifyCallbackShouldFailed() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'Qiniu abcdefghklmnopq:ZqS7EZuAKrhZaEIxqNGxDJi41IQ=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123', + 'POST', + array( + 'X-Qiniu-Bbb' => 'BBB' + ) + ); + $this->assertFalse($ok); + } + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php new file mode 100644 index 0000000..fed3da0 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php @@ -0,0 +1,16 @@ +assertEquals($a, \Qiniu\base64_urlSafeDecode($b)); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php new file mode 100644 index 0000000..0467698 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php @@ -0,0 +1,733 @@ +batch($ops); + } + + private static function getObjectKey($key) + { + $result = $key . rand(); + + self::$bucketManager->copy( + self::$bucketName, + $key, + self::$bucketName, + $result + ); + + self::$keysToCleanup[] = $result; + + return $result; + } + + public function testBuckets() + { + + list($list, $error) = self::$bucketManager->buckets(); + $this->assertNull($error); + $this->assertTrue(in_array(self::$bucketName, $list)); + + list($list2, $error) = self::$dummyBucketManager->buckets(); + $this->assertEquals(401, $error->code()); + $this->assertNotNull($error->message()); + $this->assertNotNull($error->getResponse()); + $this->assertNull($list2); + } + + public function testListBuckets() + { + list($ret, $error) = self::$bucketManager->listbuckets('z0'); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testCreateBucket() + { + list($ret, $error) = self::$bucketManager->createBucket(self::$bucketToCreate); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testDeleteBucket() + { + list($ret, $error) = self::$bucketManager->deleteBucket(self::$bucketToCreate); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testDomains() + { + list($ret, $error) = self::$bucketManager->domains(self::$bucketName); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testBucketInfo() + { + list($ret, $error) = self::$bucketManager->bucketInfo(self::$bucketName); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testBucketInfos() + { + list($ret, $error) = self::$bucketManager->bucketInfos('z0'); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testList() + { + list($ret, $error) = self::$bucketManager->listFiles(self::$bucketName, null, null, 10); + $this->assertNull($error); + $this->assertNotNull($ret['items'][0]); + $this->assertNotNull($ret['marker']); + } + + public function testListFilesv2() + { + list($ret, $error) = self::$bucketManager->listFilesv2(self::$bucketName, null, null, 10); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testBucketLifecycleRule() + { + // delete + self::$bucketManager->deleteBucketLifecycleRule(self::$bucketName, self::$bucketLifeRuleName); + + // add + list($ret, $error) = self::$bucketManager->bucketLifecycleRule( + self::$bucketName, + self::$bucketLifeRuleName, + self::$bucketLifeRulePrefix, + 80, + 70, + 72, + 74, + 71 + ); + $this->assertNull($error); + $this->assertNotNull($ret); + + // get + list($ret, $error) = self::$bucketManager->getBucketLifecycleRules(self::$bucketName); + $this->assertNull($error); + $this->assertNotNull($ret); + $rule = null; + foreach ($ret as $r) { + if ($r["name"] === self::$bucketLifeRuleName) { + $rule = $r; + break; + } + } + $this->assertNotNull($rule); + $this->assertEquals(self::$bucketLifeRulePrefix, $rule["prefix"]); + $this->assertEquals(80, $rule["delete_after_days"]); + $this->assertEquals(70, $rule["to_line_after_days"]); + $this->assertEquals(71, $rule["to_archive_ir_after_days"]); + $this->assertEquals(72, $rule["to_archive_after_days"]); + $this->assertEquals(74, $rule["to_deep_archive_after_days"]); + + // update + list($ret, $error) = self::$bucketManager->updateBucketLifecycleRule( + self::$bucketName, + self::$bucketLifeRuleName, + 'update-' . self::$bucketLifeRulePrefix, + 90, + 75, + 80, + 85, + 78 + ); + $this->assertNull($error); + $this->assertNotNull($ret); + + // get + list($ret, $error) = self::$bucketManager->getBucketLifecycleRules(self::$bucketName); + $this->assertNull($error); + $this->assertNotNull($ret); + $rule = null; + foreach ($ret as $r) { + if ($r["name"] === self::$bucketLifeRuleName) { + $rule = $r; + break; + } + } + $this->assertNotNull($rule); + $this->assertEquals('update-' . self::$bucketLifeRulePrefix, $rule["prefix"]); + $this->assertEquals(90, $rule["delete_after_days"]); + $this->assertEquals(75, $rule["to_line_after_days"]); + $this->assertEquals(78, $rule["to_archive_ir_after_days"]); + $this->assertEquals(80, $rule["to_archive_after_days"]); + $this->assertEquals(85, $rule["to_deep_archive_after_days"]); + + // delete + list($ret, $error) = self::$bucketManager->deleteBucketLifecycleRule( + self::$bucketName, + self::$bucketLifeRuleName + ); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testPutBucketEvent() + { + list($ret, $error) = self::$bucketManager->putBucketEvent( + self::$bucketName, + self::$bucketEventName, + self::$bucketEventPrefix, + 'img', + array('copy'), + self::$customCallbackURL + ); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testUpdateBucketEvent() + { + list($ret, $error) = self::$bucketManager->updateBucketEvent( + self::$bucketName, + self::$bucketEventName, + self::$bucketEventPrefix, + 'video', + array('copy'), + self::$customCallbackURL + ); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testGetBucketEvents() + { + list($ret, $error) = self::$bucketManager->getBucketEvents(self::$bucketName); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testDeleteBucketEvent() + { + list($ret, $error) = self::$bucketManager->deleteBucketEvent(self::$bucketName, self::$bucketEventName); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testStat() + { + list($stat, $error) = self::$bucketManager->stat(self::$bucketName, self::$key); + $this->assertNull($error); + $this->assertNotNull($stat); + $this->assertNotNull($stat['hash']); + + list($stat, $error) = self::$bucketManager->stat(self::$bucketName, 'nofile'); + $this->assertEquals(612, $error->code()); + $this->assertNotNull($error->message()); + $this->assertNull($stat); + + list($stat, $error) = self::$bucketManager->stat('nobucket', 'nofile'); + $this->assertEquals(631, $error->code()); + $this->assertNotNull($error->message()); + $this->assertNull($stat); + } + + public function testDelete() + { + $fileToDel = self::getObjectKey(self::$key); + list(, $error) = self::$bucketManager->delete(self::$bucketName, $fileToDel); + $this->assertNull($error); + } + + + public function testRename() + { + $fileToRename = self::getObjectKey(self::$key); + $fileRenamed = $fileToRename . 'new'; + list(, $error) = self::$bucketManager->rename(self::$bucketName, $fileToRename, $fileRenamed); + $this->assertNull($error); + self::$keysToCleanup[] = $fileRenamed; + } + + + public function testCopy() + { + $fileToCopy = self::getObjectKey(self::$key2); + $fileCopied = $fileToCopy . 'copied'; + + //test force copy + list(, $error) = self::$bucketManager->copy( + self::$bucketName, + $fileToCopy, + self::$bucketName, + $fileCopied, + true + ); + $this->assertNull($error); + + list($fileToCopyStat,) = self::$bucketManager->stat(self::$bucketName, $fileToCopy); + list($fileCopiedStat,) = self::$bucketManager->stat(self::$bucketName, $fileCopied); + + $this->assertEquals($fileToCopyStat['hash'], $fileCopiedStat['hash']); + + self::$keysToCleanup[] = $fileCopied; + } + + + public function testChangeMime() + { + $fileToChange = self::getObjectKey('php-sdk.html'); + list(, $error) = self::$bucketManager->changeMime( + self::$bucketName, + $fileToChange, + 'text/plain' + ); + $this->assertNull($error); + + list($ret, $error) = self::$bucketManager->stat( + self::$bucketName, + $fileToChange + ); + $this->assertNull($error); + $this->assertEquals('text/plain', $ret['mimeType']); + } + + public function testPrefetch() + { + list($ret, $error) = self::$bucketManager->prefetch( + self::$bucketName, + 'php-sdk.html' + ); + $this->assertNull($error); + $this->assertNotNull($ret); + } + + public function testPrefetchFailed() + { + list($ret, $error) = self::$bucketManager->prefetch( + 'fakebucket', + 'php-sdk.html' + ); + $this->assertNotNull($error); + $this->assertNull($ret); + } + + public function testFetch() + { + list($ret, $error) = self::$bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + self::$bucketName, + 'fetch.html' + ); + $this->assertNull($error); + $this->assertArrayHasKey('hash', $ret); + + list($ret, $error) = self::$bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + self::$bucketName, + '' + ); + $this->assertNull($error); + $this->assertArrayHasKey('key', $ret); + + list($ret, $error) = self::$bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + self::$bucketName + ); + $this->assertNull($error); + $this->assertArrayHasKey('key', $ret); + } + + public function testFetchFailed() + { + list($ret, $error) = self::$bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + 'fakebucket' + ); + $this->assertNotNull($error); + $this->assertNull($ret); + } + + public function testAsynchFetch() + { + list($ret, $error) = self::$bucketManager->asynchFetch( + 'http://devtools.qiniu.com/qiniu.png', + self::$bucketName, + null, + 'qiniu.png' + ); + $this->assertNull($error); + $this->assertArrayHasKey('id', $ret); + + list($ret, $error) = self::$bucketManager->asynchFetch( + 'http://devtools.qiniu.com/qiniu.png', + self::$bucketName, + null, + '' + ); + $this->assertNull($error); + $this->assertArrayHasKey('id', $ret); + + list($ret, $error) = self::$bucketManager->asynchFetch( + 'http://devtools.qiniu.com/qiniu.png', + self::$bucketName + ); + $this->assertNull($error); + $this->assertArrayHasKey('id', $ret); + } + + public function testAsynchFetchFailed() + { + list($ret, $error) = self::$bucketManager->asynchFetch( + 'http://devtools.qiniu.com/qiniu.png', + 'fakebucket' + ); + $this->assertNotNull($error); + $this->assertNull($ret); + } + + + public function testBatchCopy() + { + $key = 'copyto' . rand(); + $ops = BucketManager::buildBatchCopy( + self::$bucketName, + array(self::$key => $key), + self::$bucketName, + true + ); + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + + self::$keysToCleanup[] = $key; + } + + public function testBatchMove() + { + $fileToMove = self::getObjectKey(self::$key); + $fileMoved = $fileToMove . 'to'; + $ops = BucketManager::buildBatchMove( + self::$bucketName, + array($fileToMove => $fileMoved), + self::$bucketName, + true + ); + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + self::$keysToCleanup[] = $fileMoved; + } + + public function testBatchRename() + { + $fileToRename = self::getObjectKey(self::$key); + $fileRenamed = $fileToRename . 'to'; + + $ops = BucketManager::buildBatchRename( + self::$bucketName, + array($fileToRename => $fileRenamed), + true + ); + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + + self::$keysToCleanup[] = $fileRenamed; + } + + public function testBatchStat() + { + $ops = BucketManager::buildBatchStat(self::$bucketName, array('php-sdk.html')); + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + } + + public function testBatchChangeTypeAndBatchRestoreAr() + { + $key = self::getObjectKey(self::$key); + + $ops = BucketManager::buildBatchChangeType(self::$bucketName, array($key => 2)); // 2 Archive + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + + $ops = BucketManager::buildBatchRestoreAr(self::$bucketName, array($key => 1)); // 1 day + list($ret, $error) = self::$bucketManager->batch($ops); + $this->assertNull($error); + $this->assertEquals(200, $ret[0]['code']); + } + + public function testDeleteAfterDays() + { + $key = "noexist" . rand(); + list($ret, $error) = self::$bucketManager->deleteAfterDays(self::$bucketName, $key, 1); + $this->assertNotNull($error); + $this->assertNull($ret); + + $key = self::getObjectKey(self::$key); + list(, $error) = self::$bucketManager->deleteAfterDays(self::$bucketName, $key, 1); + $this->assertNull($error); + + list($ret, $error) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($error); + $this->assertGreaterThan(23 * 3600, $ret['expiration'] - time()); + $this->assertLessThan(48 * 3600, $ret['expiration'] - time()); + } + + public function testSetObjectLifecycle() + { + $key = self::getObjectKey(self::$key); + + list(, $err) = self::$bucketManager->setObjectLifecycle( + self::$bucketName, + $key, + 10, + 20, + 30, + 40, + 15 + ); + $this->assertNull($err); + + list($ret, $error) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($error); + $this->assertNotNull($ret['transitionToIA']); + $this->assertNotNull($ret['transitionToArchiveIR']); + $this->assertNotNull($ret['transitionToARCHIVE']); + $this->assertNotNull($ret['transitionToDeepArchive']); + $this->assertNotNull($ret['expiration']); + } + + public function testSetObjectLifecycleWithCond() + { + $key = self::getObjectKey(self::$key); + + list($ret, $err) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + $key_hash = $ret['hash']; + $key_fsize = $ret['fsize']; + + list(, $err) = self::$bucketManager->setObjectLifecycleWithCond( + self::$bucketName, + $key, + array( + 'hash' => $key_hash, + 'fsize' => $key_fsize + ), + 10, + 20, + 30, + 40, + 15 + ); + $this->assertNull($err); + + list($ret, $error) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($error); + $this->assertNotNull($ret['transitionToIA']); + $this->assertNotNull($ret['transitionToArchiveIR']); + $this->assertNotNull($ret['transitionToARCHIVE']); + $this->assertNotNull($ret['transitionToDeepArchive']); + $this->assertNotNull($ret['expiration']); + } + + public function testBatchSetObjectLifecycle() + { + $key = self::getObjectKey(self::$key); + + $ops = BucketManager::buildBatchSetObjectLifecycle( + self::$bucketName, + array($key), + 10, + 20, + 30, + 40, + 15 + ); + list($ret, $err) = self::$bucketManager->batch($ops); + $this->assertNull($err); + $this->assertEquals(200, $ret[0]['code']); + } + + public function testGetCorsRules() + { + list(, $err) = self::$bucketManager->getCorsRules(self::$bucketName); + $this->assertNull($err); + } + + public function testPutBucketAccessStyleMode() + { + list(, $err) = self::$bucketManager->putBucketAccessStyleMode(self::$bucketName, 0); + $this->assertNull($err); + } + + public function testPutBucketAccessMode() + { + list(, $err) = self::$bucketManager->putBucketAccessMode(self::$bucketName, 0); + $this->assertNull($err); + } + + public function testPutReferAntiLeech() + { + list(, $err) = self::$bucketManager->putReferAntiLeech(self::$bucketName, 0, "1", "*"); + $this->assertNull($err); + } + + public function testPutBucketMaxAge() + { + list(, $err) = self::$bucketManager->putBucketMaxAge(self::$bucketName, 31536000); + $this->assertNull($err); + } + + public function testPutBucketQuota() + { + list(, $err) = self::$bucketManager->putBucketQuota(self::$bucketName, -1, -1); + $this->assertNull($err); + } + + public function testGetBucketQuota() + { + list(, $err) = self::$bucketManager->getBucketQuota(self::$bucketName); + $this->assertNull($err); + } + + public function testChangeType() + { + $fileToChange = self::getObjectKey(self::$key); + + list(, $err) = self::$bucketManager->changeType(self::$bucketName, $fileToChange, 0); + $this->assertNull($err); + + list(, $err) = self::$bucketManager->changeType(self::$bucketName, $fileToChange, 1); + $this->assertNull($err); + } + + public function testArchiveRestoreAr() + { + $key = self::getObjectKey(self::$key); + + self::$bucketManager->changeType(self::$bucketName, $key, 2); + + list(, $err) = self::$bucketManager->restoreAr(self::$bucketName, $key, 2); + $this->assertNull($err); + + list($ret, $err) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + + $this->assertEquals(2, $ret["type"]); + + // restoreStatus + // null means frozen; + // 1 means be unfreezing; + // 2 means be unfrozen; + $this->assertNotNull($ret["restoreStatus"]); + $this->assertContains($ret["restoreStatus"], array(1, 2)); + } + + public function testDeepArchiveRestoreAr() + { + $key = self::getObjectKey(self::$key); + + self::$bucketManager->changeType(self::$bucketName, $key, 3); + + list(, $err) = self::$bucketManager->restoreAr(self::$bucketName, $key, 1); + $this->assertNull($err); + list($ret, $err) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + + $this->assertEquals(3, $ret["type"]); + + // restoreStatus + // null means frozen; + // 1 means be unfreezing; + // 2 means be unfrozen; + $this->assertNotNull($ret["restoreStatus"]); + $this->assertContains($ret["restoreStatus"], array(1, 2)); + } + + public function testChangeStatus() + { + $key = self::getObjectKey(self::$key); + + list(, $err) = self::$bucketManager->changeStatus(self::$bucketName, $key, 1); + $this->assertNull($err); + list($ret, $err) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + $this->assertEquals(1, $ret['status']); + + list(, $err) = self::$bucketManager->changeStatus(self::$bucketName, $key, 0); + $this->assertNull($err); + list($ret, $err) = self::$bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + $this->assertArrayNotHasKey('status', $ret); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php new file mode 100644 index 0000000..baa9486 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php @@ -0,0 +1,151 @@ +cdnManager = new CdnManager($testAuth); + + global $timestampAntiLeechEncryptKey; + $this->encryptKey = $timestampAntiLeechEncryptKey; + + global $testStartDate; + $this->testStartDate = $testStartDate; + + global $testEndDate; + $this->testEndDate = $testEndDate; + + global $testGranularity; + $this->testGranularity = $testGranularity; + + global $testLogDate; + $this->testLogDate = $testLogDate; + + global $customDomain; + $this->refreshUrl = $customDomain . '/sdktest.png'; + $this->refreshDirs = $customDomain; + $this->customDomain = $customDomain; + + global $customDomain2; + $this->customDomain2 = $customDomain2; + } + + public function testRefreshUrls() + { + list($ret, $err) = $this->cdnManager->refreshUrls(array($this->refreshUrl)); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testRefreshDirs() + { + list($ret, $err) = $this->cdnManager->refreshDirs(array($this->refreshDirs)); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testRefreshUrlsAndDirs() + { + list($ret, $err) = $this->cdnManager->refreshUrlsAndDirs(array($this->refreshUrl), array($this->refreshDirs)); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testGetCdnRefreshList() + { + list($ret, $err) = $this->cdnManager->getCdnRefreshList(null, null, null, 'success'); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testPrefetchUrls() + { + list($ret, $err) = $this->cdnManager->prefetchUrls(array($this->refreshUrl)); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testGetCdnPrefetchList() + { + list($ret, $err) = $this->cdnManager->getCdnPrefetchList(null, null, 'success'); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testGetBandwidthData() + { + list($ret, $err) = $this->cdnManager->getBandwidthData( + array($this->customDomain2), + $this->testStartDate, + $this->testEndDate, + $this->testGranularity + ); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testGetFluxData() + { + list($ret, $err) = $this->cdnManager->getFluxData( + array($this->customDomain2), + $this->testStartDate, + $this->testEndDate, + $this->testGranularity + ); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testGetCdnLogList() + { + $domain = getenv('QINIU_TEST_DOMAIN'); + list($ret, $err) = $this->cdnManager->getCdnLogList(array($domain), $this->testLogDate); + $this->assertNull($err); + $this->assertNotNull($ret); + } + + public function testCreateTimestampAntiLeechUrl() + { + $signUrl = $this->cdnManager->createTimestampAntiLeechUrl($this->refreshUrl, $this->encryptKey, 3600); + $response = Client::get($signUrl); + $this->assertNull($response->error); + $this->assertEquals($response->statusCode, 200); + + $signUrl = $this->cdnManager->createTimestampAntiLeechUrl( + $this->refreshUrl . '?qiniu', + $this->encryptKey, + 3600 + ); + $response = Client::get($signUrl); + $this->assertNull($response->error); + $this->assertEquals($response->statusCode, 200); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ConfigTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ConfigTest.php new file mode 100644 index 0000000..3c39a5c --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ConfigTest.php @@ -0,0 +1,118 @@ +accessKey = $accessKey; + global $bucketName; + $this->bucketName = $bucketName; + } + + public function testGetApiHost() + { + $conf = new Config(); + $hasException = false; + $apiHost = ''; + try { + $apiHost = $conf->getApiHost($this->accessKey, $this->bucketName); + } catch (\Exception $e) { + $hasException = true; + } + $this->assertFalse($hasException); + } + + public function testGetApiHostErrored() + { + $conf = new Config(); + $hasException = false; + try { + $conf->getApiHost($this->accessKey, "fakebucket"); + } catch (\Exception $e) { + $hasException = true; + } + $this->assertTrue($hasException); + } + + public function testGetApiHostV2() + { + $conf = new Config(); + list($apiHost, $err) = $conf->getApiHostV2($this->accessKey, $this->bucketName); + $this->assertNull($err); + } + + public function testGetApiHostV2Errored() + { + $conf = new Config(); + list($apiHost, $err) = $conf->getApiHostV2($this->accessKey, "fakebucket"); + $this->assertNotNull($err->code()); + $this->assertEquals(631, $err->code()); + $this->assertNull($apiHost); + } + + public function testSetUcHost() + { + $conf = new Config(); + $this->assertEquals('http://' . Config::UC_HOST, $conf->getUcHost()); + $conf->setUcHost("uc.example.com"); + $this->assertEquals("http://uc.example.com", $conf->getUcHost()); + + $conf = new Config(); + $conf->useHTTPS = true; + $this->assertEquals('https://' . Config::UC_HOST, $conf->getUcHost()); + $conf->setUcHost("uc.example.com"); + $this->assertEquals("https://uc.example.com", $conf->getUcHost()); + } + + public function testGetRegionWithCustomDomain() + { + $conf = new Config(); + $conf->setQueryRegionHost( + "uc.qbox.me" + ); + list(, $err) = $conf->getRsHostV2($this->accessKey, $this->bucketName); + $this->assertNull($err); + } + + public function testGetRegionWithBackupDomains() + { + $conf = new Config(); + $conf->setQueryRegionHost( + "fake-uc.phpsdk.qiniu.com", + array( + "unavailable-uc.phpsdk.qiniu.com", + Config::UC_HOST // real uc + ) + ); + list(, $err) = $conf->getRsHostV2($this->accessKey, $this->bucketName); + $this->assertNull($err); + } + + public function testGetRegionWithUcAndBackupDomains() + { + $conf = new Config(); + $conf->setUcHost("fake-uc.phpsdk.qiniu.com"); + $conf->setBackupQueryRegionHosts( + array( + "unavailable-uc.phpsdk.qiniu.com", + Config::UC_HOST // real uc + ) + ); + list(, $err) = $conf->getRsHostV2($this->accessKey, $this->bucketName); + $this->assertNull($err); + } + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php new file mode 100644 index 0000000..63e24fd --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php @@ -0,0 +1,23 @@ +assertEquals('1352841281', $b); + } + + public function testFile() + { + $b = \Qiniu\crc32_file(__file__); + $c = \Qiniu\crc32_file(__file__); + $this->assertEquals($c, $b); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php new file mode 100644 index 0000000..9b4b034 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php @@ -0,0 +1,27 @@ +privateDownloadUrl($base_url); + $response = Client::get($private_url); + $this->assertEquals(200, $response->statusCode); + } + + public function testFop() + { + global $testAuth; + $base_url = 'http://sdk.peterpy.cn/gogopher.jpg?exif'; + $private_url = $testAuth->privateDownloadUrl($base_url); + $response = Client::get($private_url); + $this->assertEquals(200, $response->statusCode); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EntryTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EntryTest.php new file mode 100644 index 0000000..73bfac4 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EntryTest.php @@ -0,0 +1,88 @@ +assertEquals('cWluaXVwaG90b3M6Z29nb3BoZXIuanBn', $encodeEntryURI); + } + + public function testKeyEmpty() + { + $bucket = 'qiniuphotos'; + $key = ''; + $encodeEntryURI = Qiniu\entry($bucket, $key); + $this->assertEquals('cWluaXVwaG90b3M6', $encodeEntryURI); + } + + public function testKeyNull() + { + $bucket = 'qiniuphotos'; + $key = null; + $encodeEntryURI = Qiniu\entry($bucket, $key); + $this->assertEquals('cWluaXVwaG90b3M=', $encodeEntryURI); + } + + public function testKeyNeedReplacePlusSymbol() + { + $bucket = 'qiniuphotos'; + $key = '012ts>a'; + $encodeEntryURI = Qiniu\entry($bucket, $key); + $this->assertEquals('cWluaXVwaG90b3M6MDEydHM-YQ==', $encodeEntryURI); + } + + public function testKeyNeedReplaceSlashSymbol() + { + $bucket = 'qiniuphotos'; + $key = '012ts?a'; + $encodeEntryURI = Qiniu\entry($bucket, $key); + $this->assertEquals('cWluaXVwaG90b3M6MDEydHM_YQ==', $encodeEntryURI); + } + public function testDecodeEntry() + { + $entry = 'cWluaXVwaG90b3M6Z29nb3BoZXIuanBn'; + list($bucket, $key) = Qiniu\decodeEntry($entry); + $this->assertEquals('qiniuphotos', $bucket); + $this->assertEquals('gogopher.jpg', $key); + } + + public function testDecodeEntryWithEmptyKey() + { + $entry = 'cWluaXVwaG90b3M6'; + list($bucket, $key) = Qiniu\decodeEntry($entry); + $this->assertEquals('qiniuphotos', $bucket); + $this->assertEquals('', $key); + } + + public function testDecodeEntryWithNullKey() + { + $entry = 'cWluaXVwaG90b3M='; + list($bucket, $key) = Qiniu\decodeEntry($entry); + $this->assertEquals('qiniuphotos', $bucket); + $this->assertNull($key); + } + + public function testDecodeEntryWithPlusSymbol() + { + $entry = 'cWluaXVwaG90b3M6MDEydHM-YQ=='; + list($bucket, $key) = Qiniu\decodeEntry($entry); + $this->assertEquals('qiniuphotos', $bucket); + $this->assertEquals('012ts>a', $key); + } + + public function testDecodeEntryWithSlashSymbol() + { + $entry = 'cWluaXVwaG90b3M6MDEydHM_YQ=='; + list($bucket, $key) = Qiniu\decodeEntry($entry); + $this->assertEquals('qiniuphotos', $bucket); + $this->assertEquals('012ts?a', $key); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php new file mode 100644 index 0000000..4e09a78 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php @@ -0,0 +1,54 @@ +assertEquals('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', $r); + $this->assertNull($error); + } + + public function testLess4M() + { + $file = qiniuTempFile(3 * 1024 * 1024, false); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('Fs5BpnAjRykYTg6o5E09cjuXrDkG', $r); + $this->assertNull($error); + } + + public function test4M() + { + $file = qiniuTempFile(4 * 1024 * 1024, false); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('FiuKULnybewpEnrfTmxjsxc-3dWp', $r); + $this->assertNull($error); + } + + public function testMore4M() + { + $file = qiniuTempFile(5 * 1024 * 1024, false); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('lhvyfIWMYFTq4s4alzlhXoAkqfVL', $r); + $this->assertNull($error); + } + + public function test8M() + { + $file = qiniuTempFile(8 * 1024 * 1024, false); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('lmRm9ZfGZ86bnMys4wRTWtJj9ClG', $r); + $this->assertNull($error); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php new file mode 100644 index 0000000..42b7997 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php @@ -0,0 +1,39 @@ +execute('gogopher.jpg', 'exif'); + $this->assertNull($error); + $this->assertNotNull($exif); + } + + public function testExifPrivate() + { + global $testAuth; + $fop = new Operation('private-res.qiniudn.com', $testAuth); + list($exif, $error) = $fop->execute('noexif.jpg', 'exif'); + $this->assertNotNull($error); + $this->assertNull($exif); + } + + public function testbuildUrl() + { + $fops = 'imageView2/2/h/200'; + $fop = new Operation('testres.qiniudn.com'); + $url = $fop->buildUrl('gogopher.jpg', $fops); + $this->assertEquals($url, 'http://testres.qiniudn.com/gogopher.jpg?imageView2/2/h/200'); + + $fops = array('imageView2/2/h/200', 'imageInfo'); + $url = $fop->buildUrl('gogopher.jpg', $fops); + $this->assertEquals($url, 'http://testres.qiniudn.com/gogopher.jpg?imageView2/2/h/200|imageInfo'); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php new file mode 100644 index 0000000..f75794e --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php @@ -0,0 +1,205 @@ +batch($ops); + } + + private static function getObjectKey($key) + { + $result = $key . rand(); + self::$keysToCleanup[] = $result; + return $result; + } + + public function testData() + { + $key = self::getObjectKey('formput'); + $token = self::$auth->uploadToken(self::$bucketName); + list($ret, $error) = FormUploader::put($token, $key, 'hello world', self::$cfg, null, 'text/plain', null); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testDataWithProxy() + { + $key = self::getObjectKey('formput'); + $token = self::$auth->uploadToken(self::$bucketName); + list($ret, $error) = FormUploader::put( + $token, + $key, + 'hello world', + self::$cfg, + null, + 'text/plain', + null, + $this->makeReqOpt() + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testData2() + { + $key = self::getObjectKey('formput'); + $upManager = new UploadManager(); + $token = self::$auth->uploadToken(self::$bucketName); + list($ret, $error) = $upManager->put($token, $key, 'hello world', null, 'text/plain', null); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testData2WithProxy() + { + $key = self::getObjectKey('formput'); + $upManager = new UploadManager(); + $token = self::$auth->uploadToken(self::$bucketName); + list($ret, $error) = $upManager->put( + $token, + $key, + 'hello world', + null, + 'text/plain', + null, + $this->makeReqOpt() + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testDataFailed() + { + $key = self::getObjectKey('formput'); + $token = self::$auth->uploadToken('fakebucket'); + list($ret, $error) = FormUploader::put( + $token, + $key, + 'hello world', + self::$cfg, + null, + 'text/plain', + null + ); + $this->assertNull($ret); + $this->assertNotNull($error); + } + + public function testFile() + { + $key = self::getObjectKey('formPutFile'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + list($ret, $error) = FormUploader::putFile( + $token, + $key, + __file__, + self::$cfg, + null, + 'text/plain', + null + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testFileWithProxy() + { + $key = self::getObjectKey('formPutFile'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + list($ret, $error) = FormUploader::putFile( + $token, + $key, + __file__, + self::$cfg, + null, + 'text/plain', + $this->makeReqOpt() + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testFile2() + { + $key = self::getObjectKey('formPutFile'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $upManager = new UploadManager(); + list($ret, $error) = $upManager->putFile($token, $key, __file__, null, 'text/plain', null); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testFile2WithProxy() + { + $key = self::getObjectKey('formPutFile'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $upManager = new UploadManager(); + list($ret, $error) = $upManager->putFile( + $token, + $key, + __file__, + null, + 'text/plain', + false, + null, + 'v1', + Config::BLOCK_SIZE, + $this->makeReqOpt() + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testFileFailed() + { + $key = self::getObjectKey('fakekey'); + $token = self::$auth->uploadToken('fakebucket', $key); + list($ret, $error) = FormUploader::putFile($token, $key, __file__, self::$cfg, null, 'text/plain', null); + $this->assertNull($ret); + $this->assertNotNull($error); + } + + private function makeReqOpt() + { + $reqOpt = new RequestOptions(); + $reqOpt->proxy = 'socks5://127.0.0.1:8080'; + $reqOpt->proxy_user_password = 'user:pass'; + return $reqOpt; + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HeaderTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HeaderTest.php new file mode 100644 index 0000000..28af5f3 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HeaderTest.php @@ -0,0 +1,184 @@ + array('200'), + ':x-test-1' => array('hello1'), + ':x-Test-2' => array('hello2'), + 'content-type' => array('application/json'), + 'CONTENT-LENGTH' => array(1234), + 'oRiGin' => array('https://www.qiniu.com'), + 'ReFer' => array('www.qiniu.com'), + 'Last-Modified' => array('Mon, 06 Sep 2021 06:44:52 GMT'), + 'acCePt-ChArsEt' => array('utf-8'), + 'x-test-3' => array('hello3'), + 'cache-control' => array('no-cache', 'no-store'), + ); + + public function testNormalizeKey() + { + $except = array( + ':status', + ':x-test-1', + ':x-Test-2', + 'Content-Type', + 'Content-Length', + 'Origin', + 'Refer', + 'Last-Modified', + 'Accept-Charset', + 'X-Test-3', + 'Cache-Control' + ); + $actual = array_map(function ($str) { + return Header::normalizeKey($str); + }, array_keys($this->heads)); + $this->assertEquals($actual, $except); + } + + + public function testInvalidKeyName() + { + $except = array( + 'a:x-test-1', + ); + + $actual = array_map(function ($str) { + return Header::normalizeKey($str); + }, $except); + + $this->assertEquals($except, $actual); + } + + public function testGetRawData() + { + $header = new Header($this->heads); + foreach ($this->heads as $k => $v) { + $rawHeader = $header->getRawData(); + $this->assertEquals($v, $rawHeader[Header::normalizeKey($k)]); + } + } + + public function testOffsetExists() + { + $header = new Header($this->heads); + foreach (array_keys($this->heads) as $k) { + $this->assertNotNull($header[$k]); + } + + $except = array( + ':status', + ':x-test-1', + ':x-Test-2', + 'Content-Type', + 'Content-Length', + 'Origin', + 'Refer', + 'Last-Modified', + 'Accept-Charset', + 'X-Test-3', + 'Cache-Control' + ); + foreach ($except as $k) { + $this->assertNotNull($header[$k], $k." is null"); + } + } + + public function testOffsetGet() + { + $header = new Header($this->heads); + foreach ($this->heads as $k => $v) { + $this->assertEquals($v[0], $header[$k]); + } + + $this->assertNull($header['no-exist']); + } + + public function testOffsetSet() + { + $header = new Header($this->heads); + $header["X-Test-3"] = "hello"; + $this->assertEquals("hello", $header["X-Test-3"]); + $header["x-test-3"] = "hello test3"; + $this->assertEquals("hello test3", $header["x-test-3"]); + $header[":x-Test-2"] = "hello"; + $this->assertEquals("hello", $header[":x-Test-2"]); + $header[":x-test-2"] = "hello test2"; + $this->assertEquals("hello", $header[":x-Test-2"]); + } + + public function testOffsetUnset() + { + $header = new Header($this->heads); + unset($header["X-Test-3"]); + $this->assertFalse(isset($header["X-Test-3"])); + + $header = new Header($this->heads); + unset($header["x-test-3"]); + $this->assertFalse(isset($header["x-test-3"])); + + $header = new Header($this->heads); + unset($header[":x-test-2"]); + $this->assertTrue(isset($header[":x-Test-2"])); + + $header = new Header($this->heads); + unset($header[":x-Test-2"]); + $this->assertFalse(isset($header[":x-Test-2"])); + } + + public function testGetIterator() + { + $header = new Header($this->heads); + + $hasException = false; + try { + foreach ($header as $k => $v) { + $hasException = !isset($header[$k]); + } + } catch (\Exception $e) { + $hasException = true; + } + $this->assertFalse($hasException); + } + + public function testEmptyHeaderIterator() + { + $emptyHeader = new Header(); + + $hasException = false; + try { + foreach ($emptyHeader as $k => $v) { + $hasException = !isset($header[$k]); + } + } catch (\Exception $e) { + $hasException = true; + } + $this->assertFalse($hasException); + } + + public function testCount() + { + $header = new Header($this->heads); + + $this->assertEquals(count($this->heads), count($header)); + } + + public function testFromRaw() + { + $lines = array(); + foreach ($this->heads as $k => $vs) { + foreach ($vs as $v) { + array_push($lines, $k . ": " . $v); + } + } + $raw = implode("\r\n", $lines); + $headerFromRaw = Header::fromRawText($raw); + $this->assertEquals(new Header($this->heads), $headerFromRaw); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php new file mode 100644 index 0000000..c122f8e --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php @@ -0,0 +1,163 @@ +assertEquals(200, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNull($response->error); + } + + public function testGetQiniu() + { + $response = Client::get('upload.qiniu.com'); + $this->assertEquals(405, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->xReqId()); + $this->assertNotNull($response->xLog()); + $this->assertNotNull($response->error); + } + + public function testGetTimeout() + { + $reqOpt = new RequestOptions(); + $reqOpt->timeout = 1; + $response = Client::get('localhost:9000/timeout.php', array(), $reqOpt); + $this->assertEquals(-1, $response->statusCode); + } + + public function testGetRedirect() + { + $response = Client::get('localhost:9000/redirect.php'); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals('application/json;charset=UTF-8', $response->normalizedHeaders['Content-Type']); + $respData = $response->json(); + $this->assertEquals('ok', $respData['msg']); + } + + public function testDelete() + { + $response = Client::delete('uc.qbox.me/bucketTagging', array()); + $this->assertEquals(401, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->error); + } + + public function testDeleteQiniu() + { + $response = Client::delete('uc.qbox.me/bucketTagging', array()); + $this->assertEquals(401, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->xReqId()); + $this->assertNotNull($response->xLog()); + $this->assertNotNull($response->error); + } + + public function testDeleteTimeout() + { + $reqOpt = new RequestOptions(); + $reqOpt->timeout = 1; + $response = Client::delete('localhost:9000/timeout.php', array(), $reqOpt); + $this->assertEquals(-1, $response->statusCode); + } + + + public function testPost() + { + $response = Client::post('qiniu.com', null); + $this->assertEquals(200, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNull($response->error); + } + + public function testPostQiniu() + { + $response = Client::post('upload.qiniu.com', null); + $this->assertEquals(400, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->xReqId()); + $this->assertNotNull($response->xLog()); + $this->assertNotNull($response->error); + } + + public function testPostTimeout() + { + $reqOpt = new RequestOptions(); + $reqOpt->timeout = 1; + $response = Client::post('localhost:9000/timeout.php', null, array(), $reqOpt); + $this->assertEquals(-1, $response->statusCode); + } + + public function testSocks5Proxy() + { + $reqOpt = new RequestOptions(); + $reqOpt->proxy = 'socks5://localhost:8080'; + $response = Client::post('qiniu.com', null, array(), $reqOpt); + $this->assertEquals(-1, $response->statusCode); + + $reqOpt->proxy_user_password = 'user:pass'; + $response = Client::post('qiniu.com', null, array(), $reqOpt); + $this->assertEquals(200, $response->statusCode); + } + + public function testPut() + { + $response = Client::PUT('uc.qbox.me/bucketTagging', null); + $this->assertEquals(401, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->error); + } + + public function testPutQiniu() + { + $response = Client::put('uc.qbox.me/bucketTagging', null); + $this->assertEquals(401, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->xReqId()); + $this->assertNotNull($response->xLog()); + $this->assertNotNull($response->error); + } + + + public function testPutTimeout() + { + $reqOpt = new RequestOptions(); + $reqOpt->timeout = 1; + $response = Client::put('localhost:9000/timeout.php', null, array(), $reqOpt); + $this->assertEquals(-1, $response->statusCode); + } + + public function testNeedRetry() + { + $testCases = array_merge( + array(array(-1, true)), + array_map(function ($i) { + return array($i, false); + }, range(100, 499)), + array_map(function ($i) { + if (in_array($i, array( + 501, 509, 573, 579, 608, 612, 614, 616, 618, 630, 631, 632, 640, 701 + ))) { + return array($i, false); + } + return array($i, true); + }, range(500, 799)) + ); + $resp = new Response(-1, 222, array(), '{"msg": "mock"}', null); + foreach ($testCases as $testCase) { + list($code, $expectNeedRetry) = $testCase; + $resp->statusCode = $code; + $msg = $resp->statusCode . ' need' . ($expectNeedRetry ? '' : ' NOT') . ' retry'; + $this->assertEquals($expectNeedRetry, $resp->needRetry(), $msg); + } + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php new file mode 100644 index 0000000..486323c --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php @@ -0,0 +1,263 @@ + + */ +class ImageUrlBuilderTest extends TestCase +{ + /** + * 缩略图测试 + * + * @test + * @return void + * @author Sherlock Ren + */ + public function testThumbutl() + { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder(); + $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg'; + $url2 = $url . '?watermark/1/gravity/SouthEast/dx/0/dy/0/image/' + . 'aHR0cDovL2Fkcy1jZG4uY2h1Y2h1amllLmNvbS9Ga1R6bnpIY2RLdmRBUFc5cHZZZ3pTc21UY0tB'; + // 异常测试 + $this->assertEquals($url, $imageUrlBuilder->thumbnail($url, 1, 0, 0)); + $this->assertEquals($url, \Qiniu\thumbnail($url, 1, 0, 0)); + + // 简单缩略测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200) + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200) + ); + + // 输出格式测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png') + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200, 'png') + ); + + // 渐进显示测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png', 1) + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200, 'png', 2) + ); + + // 图片质量测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/q/80/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png', 1, 80) + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200, 'png', 1, 101) + ); + + // 多参数测试 + $this->assertEquals( + $url2 . '|imageView2/1/w/200/h/200/ignore-error/1/', + $imageUrlBuilder->thumbnail($url2, 1, 200, 200) + ); + $this->assertEquals( + $url2 . '|imageView2/1/w/200/h/200/ignore-error/1/', + \Qiniu\thumbnail($url2, 1, 200, 200) + ); + } + + /** + * 图片水印测试 + * + * @test + * @param void + * @return void + * @author Sherlock Ren + */ + public function waterImgTest() + { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder(); + $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg'; + $url2 = $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/'; + $image = 'http://developer.qiniu.com/resource/logo-2.jpg'; + + // 水印简单测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterImg($url, $image) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/gravity/SouthEast/', + $imageUrlBuilder->waterImg($url, $image, 101) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==/', + $imageUrlBuilder->waterImg($url, $image, 101, 'sdfsd') + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image) + ); + + // 横轴边距测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/dx/10/', + $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad') + ); + + // 纵轴边距测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/dx/10/dy/10/', + $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10, 10) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad', 'asdf') + ); + + // 自适应原图的短边比例测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/dx/10/dy/10/ws/0.5/', + $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10, 10, 0.5) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad', 'asdf', 2) + ); + + // 多参数测试 + $this->assertEquals( + $url2 . '|watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterImg($url2, $image) + ); + $this->assertEquals( + $url2 . '|watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url2, $image) + ); + } + + /** + * 文字水印测试 + * + * @test + * @param void + * @return void + * @author Sherlock Ren + */ + public function waterTextTest() + { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder(); + $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg'; + $url2 = $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/'; + $text = '测试一下'; + $font = '微软雅黑'; + $fontColor = '#FF0000'; + + // 水印简单测试 + $this->assertEquals($url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'fontsize/500/dissolve/100/gravity/SouthEast/', $imageUrlBuilder->waterText($url, $text, $font, 500)); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'dissolve/100/gravity/SouthEast/', + \Qiniu\waterText($url, $text, $font, 'sdf') + ); + + // 字体颜色测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/' + . 'I0ZGMDAwMA==/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor) + ); + + // 透明度测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/SouthEast/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==' + . '/gravity/SouthEast/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101) + ); + + // 水印位置测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/East/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East') + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf') + ); + + // 横轴距离测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/East/dx/10/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East', 10) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf', 'sdfs') + ); + + // 纵轴距离测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/East/dx/10/dy/10/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East', 10, 10) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf', 'sdfs', 'ssdf') + ); + // 多参数测试 + $this->assertEquals( + $url2 . '|watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'fontsize/500/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterText($url2, $text, $font, 500) + ); + $this->assertEquals( + $url2 . '|watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'fontsize/500/dissolve/100/gravity/SouthEast/', + \Qiniu\waterText($url2, $text, $font, 500) + ); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/MiddlewareTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/MiddlewareTest.php new file mode 100644 index 0000000..969cad4 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/MiddlewareTest.php @@ -0,0 +1,160 @@ + + */ + private $orderRecorder; + + /** + * @var string + */ + private $label; + + public function __construct(&$orderRecorder, $label) + { + $this->orderRecorder =& $orderRecorder; + $this->label = $label; + } + + public function send($request, $next) + { + $this->orderRecorder[] = "bef_" . $this->label . count($this->orderRecorder); + $response = $next($request); + $this->orderRecorder[] = "aft_" . $this->label . count($this->orderRecorder); + return $response; + } +} + +class MiddlewareTest extends TestCase +{ + public function testSendWithMiddleware() + { + $orderRecorder = array(); + + $reqOpt = new RequestOptions(); + $reqOpt->middlewares = array( + new RecorderMiddleware($orderRecorder, "A"), + new RecorderMiddleware($orderRecorder, "B") + ); + + $request = new Request( + "GET", + "http://localhost:9000/ok.php", + array(), + null, + $reqOpt + ); + $response = Client::sendRequestWithMiddleware($request); + + $expectRecords = array( + "bef_A0", + "bef_B1", + "aft_B2", + "aft_A3" + ); + + $this->assertEquals($expectRecords, $orderRecorder); + $this->assertEquals(200, $response->statusCode); + } + + public function testSendWithRetryDomains() + { + $orderRecorder = array(); + + $reqOpt = new RequestOptions(); + $reqOpt->middlewares = array( + new Middleware\RetryDomainsMiddleware( + array( + "unavailable.phpsdk.qiniu.com", + "localhost:9000", + ), + 3 + ), + new RecorderMiddleware($orderRecorder, "rec") + ); + + $request = new Request( + "GET", + "http://fake.phpsdk.qiniu.com/ok.php", + array(), + null, + $reqOpt + ); + $response = Client::sendRequestWithMiddleware($request); + + $expectRecords = array( + // 'fake.phpsdk.qiniu.com' with retried 3 times + 'bef_rec0', + 'aft_rec1', + 'bef_rec2', + 'aft_rec3', + 'bef_rec4', + 'aft_rec5', + + // 'unavailable.pysdk.qiniu.com' with retried 3 times + 'bef_rec6', + 'aft_rec7', + 'bef_rec8', + 'aft_rec9', + 'bef_rec10', + 'aft_rec11', + + // 'qiniu.com' and it's success + 'bef_rec12', + 'aft_rec13' + ); + + $this->assertEquals($expectRecords, $orderRecorder); + $this->assertEquals(200, $response->statusCode); + } + + public function testSendFailFastWithRetryDomains() + { + $orderRecorder = array(); + + $reqOpt = new RequestOptions(); + $reqOpt->middlewares = array( + new Middleware\RetryDomainsMiddleware( + array( + "unavailable.phpsdk.qiniu.com", + "localhost:9000", + ), + 3, + function () { + return false; + } + ), + new RecorderMiddleware($orderRecorder, "rec") + ); + + $request = new Request( + "GET", + "http://fake.phpsdk.qiniu.com/ok.php", + array(), + null, + $reqOpt + ); + $response = Client::sendRequestWithMiddleware($request); + + $expectRecords = array( + // 'fake.phpsdk.qiniu.com' will fail fast + 'bef_rec0', + 'aft_rec1' + ); + $this->assertEquals($expectRecords, $orderRecorder); + $this->assertEquals(-1, $response->statusCode); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php new file mode 100644 index 0000000..77d06ec --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php @@ -0,0 +1,304 @@ +execute($bucket, $key, $fops); + $this->assertNull($error); + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + } + + + public function testPfopExecuteAndStatusWithMultipleFops() + { + global $testAuth; + $bucket = 'testres'; + $key = 'sintel_trailer.mp4'; + $fops = array( + 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240', + 'vframe/jpg/offset/7/w/480/h/360', + ); + $pfop = new PersistentFop($testAuth, self::getConfig()); + + list($id, $error) = $pfop->execute($bucket, $key, $fops); + $this->assertNull($error); + + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + } + + private function pfopOptionsTestData() + { + return array( + array( + 'type' => null + ), + array( + 'type' => -1 + ), + array( + 'type' => 0 + ), + array( + 'type' => 1 + ), + array( + 'type' => 2 + ), + array( + 'workflowTemplateID' => 'test-workflow' + ) + ); + } + + public function testPfopExecuteWithOptions() + { + $bucket = self::$bucketName; + $key = 'qiniu.png'; + $pfop = new PersistentFop(self::$testAuth, self::getConfig()); + + $testCases = $this->pfopOptionsTestData(); + + foreach ($testCases as $testCase) { + $workflowTemplateID = null; + $type = null; + + if (array_key_exists('workflowTemplateID', $testCase)) { + $workflowTemplateID = $testCase['workflowTemplateID']; + } + if (array_key_exists('type', $testCase)) { + $type = $testCase['type']; + } + + if ($workflowTemplateID) { + $fops = null; + } else { + $persistentEntry = \Qiniu\entry( + $bucket, + implode( + '_', + array( + 'test-pfop/test-pfop-by-api', + 'type', + $type + ) + ) + ); + $fops = 'avinfo|saveas/' . $persistentEntry; + } + list($id, $error) = $pfop->execute( + $bucket, + $key, + $fops, + null, + null, + false, + $type, + $workflowTemplateID + ); + + if (in_array($type, array(null, 0, 1))) { + $this->assertNull($error); + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + if ($type == 1) { + $this->assertEquals(1, $status['type']); + } + if ($workflowTemplateID) { + // assertStringContainsString when PHPUnit >= 8.0 + $this->assertTrue( + strpos( + $status['taskFrom'], + $workflowTemplateID + ) !== false + ); + } + $this->assertNotEmpty($status['creationDate']); + } else { + $this->assertNotNull($error); + } + } + } + + public function testPfopWithInvalidArgument() + { + $bucket = self::$bucketName; + $key = 'qiniu.png'; + $pfop = new PersistentFop(self::$testAuth, self::getConfig()); + $err = null; + try { + $pfop->execute( + $bucket, + $key + ); + } catch (\Exception $e) { + $err = $e; + } + + $this->assertNotEmpty($err); + $this->assertTrue( + strpos( + $err->getMessage(), + 'Must provide one of fops or template_id' + ) !== false + ); + } + + public function testPfopWithUploadPolicy() + { + $bucket = self::$bucketName; + $testAuth = self::$testAuth; + $key = 'test-pfop/upload-file'; + + $testCases = $this->pfopOptionsTestData(); + + foreach ($testCases as $testCase) { + $workflowTemplateID = null; + $type = null; + + if (array_key_exists('workflowTemplateID', $testCase)) { + $workflowTemplateID = $testCase['workflowTemplateID']; + } + if (array_key_exists('type', $testCase)) { + $type = $testCase['type']; + } + + $putPolicy = array( + 'persistentType' => $type + ); + if ($workflowTemplateID) { + $putPolicy['persistentWorkflowTemplateID'] = $workflowTemplateID; + } else { + $persistentEntry = \Qiniu\entry( + $bucket, + implode( + '_', + array( + 'test-pfop/test-pfop-by-upload', + 'type', + $type + ) + ) + ); + $putPolicy['persistentOps'] = 'avinfo|saveas/' . $persistentEntry; + } + + if ($type == null) { + unset($putPolicy['persistentType']); + } + + $token = $testAuth->uploadToken( + $bucket, + $key, + 3600, + $putPolicy + ); + $upManager = new UploadManager(self::getConfig()); + list($ret, $error) = $upManager->putFile( + $token, + $key, + __file__, + null, + 'text/plain', + true + ); + + if (in_array($type, array(null, 0, 1))) { + $this->assertNull($error); + $this->assertNotEmpty($ret['persistentId']); + $id = $ret['persistentId']; + } else { + $this->assertNotNull($error); + return; + } + + $pfop = new PersistentFop($testAuth, self::getConfig()); + list($status, $error) = $pfop->status($id); + + $this->assertNotNull($status); + $this->assertNull($error); + if ($type == 1) { + $this->assertEquals(1, $status['type']); + } + if ($workflowTemplateID) { + // assertStringContainsString when PHPUnit >= 8.0 + $this->assertTrue( + strpos( + $status['taskFrom'], + $workflowTemplateID + ) !== false + ); + } + $this->assertNotEmpty($status['creationDate']); + } + } + + public function testMkzip() + { + $bucket = self::$bucketName; + $key = 'php-logo.png'; + $pfop = new PersistentFop(self::$testAuth, null); + + $url1 = 'http://phpsdk.qiniudn.com/php-logo.png'; + $url2 = 'http://phpsdk.qiniudn.com/php-sdk.html'; + $zipKey = 'test.zip'; + + $fops = 'mkzip/2/url/' . \Qiniu\base64_urlSafeEncode($url1); + $fops .= '/url/' . \Qiniu\base64_urlSafeEncode($url2); + $fops .= '|saveas/' . \Qiniu\base64_urlSafeEncode("$bucket:$zipKey"); + + list($id, $error) = $pfop->execute($bucket, $key, $fops); + $this->assertNull($error); + + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php new file mode 100644 index 0000000..6feee55 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php @@ -0,0 +1,354 @@ +batch($ops); + } + + private static function getObjectKey($key) + { + $result = $key . rand(); + self::$keysToCleanup[] = $result; + return $result; + } + + public function test4ML() + { + $key = self::getObjectKey('resumePutFile4ML_'); + $upManager = new UploadManager(); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + null, + 'application/octet-stream', + false, + $resumeFile + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + unlink($tempFile); + } + + public function test4ML2() + { + $key = self::getObjectKey('resumePutFile4ML_'); + $cfg = new Config(); + $upManager = new UploadManager($cfg); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + null, + 'application/octet-stream', + false, + $resumeFile + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + unlink($tempFile); + } + + public function test4ML2WithProxy() + { + $key = self::getObjectKey('resumePutFile4ML_'); + $cfg = new Config(); + $upManager = new UploadManager($cfg); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + null, + 'application/octet-stream', + false, + $resumeFile, + 'v2', + Config::BLOCK_SIZE, + $this->makeReqOpt() + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + unlink($tempFile); + } + + // public function test8M() + // { + // $key = 'resumePutFile8M'; + // $upManager = new UploadManager(); + // $token = self::$auth->uploadToken(self::$bucketName, $key); + // $tempFile = qiniuTempFile(8*1024*1024+10); + // list($ret, $error) = $upManager->putFile($token, $key, $tempFile); + // $this->assertNull($error); + // $this->assertNotNull($ret['hash']); + // unlink($tempFile); + // } + + public function testFileWithFileType() + { + $config = new Config(); + $bucketManager = new BucketManager(self::$auth, $config); + + $testCases = array( + array( + "fileType" => 1, + "name" => "IA" + ), + array( + "fileType" => 2, + "name" => "Archive" + ), + array( + "fileType" => 3, + "name" => "DeepArchive" + ) + ); + + foreach ($testCases as $testCase) { + $key = self::getObjectKey('FileType' . $testCase["name"]); + $police = array( + "fileType" => $testCase["fileType"], + ); + $token = self::$auth->uploadToken(self::$bucketName, $key, 3600, $police); + $upManager = new UploadManager(); + list($ret, $error) = $upManager->putFile($token, $key, __file__, null, 'text/plain'); + $this->assertNull($error); + $this->assertNotNull($ret); + list($ret, $err) = $bucketManager->stat(self::$bucketName, $key); + $this->assertNull($err); + $this->assertEquals($testCase["fileType"], $ret["type"]); + } + } + + public function testResumeUploadWithParams() + { + $key = self::getObjectKey('resumePutFile4ML_'); + $upManager = new UploadManager(); + $policy = array('returnBody' => '{"hash":$(etag),"fname":$(fname),"var_1":$(x:var_1),"var_2":$(x:var_2)}'); + $token = self::$auth->uploadToken(self::$bucketName, $key, 3600, $policy); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + array("x:var_1" => "val_1", "x:var_2" => "val_2", "x-qn-meta-m1" => "val_1", "x-qn-meta-m2" => "val_2"), + 'application/octet-stream', + false, + $resumeFile + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + $this->assertEquals("val_1", $ret['var_1']); + $this->assertEquals("val_2", $ret['var_2']); + $this->assertEquals(basename($tempFile), $ret['fname']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + $headers = $response->headers(); + $this->assertEquals("val_1", $headers["X-Qn-Meta-M1"]); + $this->assertEquals("val_2", $headers["X-Qn-Meta-M2"]); + unlink($tempFile); + } + + public function testResumeUploadV2() + { + $cfg = new Config(); + $upManager = new UploadManager($cfg); + $testFileSize = array( + config::BLOCK_SIZE / 2, + config::BLOCK_SIZE, + config::BLOCK_SIZE + 10, + config::BLOCK_SIZE * 2, + config::BLOCK_SIZE * 2.5 + ); + $partSize = 5 * 1024 * 1024; + foreach ($testFileSize as $item) { + $key = self::getObjectKey('resumePutFile4ML_'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $tempFile = qiniuTempFile($item); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + null, + 'application/octet-stream', + false, + $resumeFile, + 'v2', + $partSize + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + unlink($tempFile); + } + } + + public function testResumeUploadV2WithParams() + { + $key = self::getObjectKey('resumePutFile4ML_'); + $upManager = new UploadManager(); + $policy = array('returnBody' => '{"hash":$(etag),"fname":$(fname),"var_1":$(x:var_1),"var_2":$(x:var_2)}'); + $token = self::$auth->uploadToken(self::$bucketName, $key, 3600, $policy); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + list($ret, $error) = $upManager->putFile( + $token, + $key, + $tempFile, + array("x:var_1" => "val_1", "x:var_2" => "val_2", "x-qn-meta-m1" => "val_1", "x-qn-meta-m2" => "val_2"), + 'application/octet-stream', + false, + $resumeFile, + 'v2' + ); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + $this->assertEquals("val_1", $ret['var_1']); + $this->assertEquals("val_2", $ret['var_2']); + $this->assertEquals(basename($tempFile), $ret['fname']); + + $domain = getenv('QINIU_TEST_DOMAIN'); + $response = Client::get("http://$domain/$key"); + $this->assertEquals(200, $response->statusCode); + $this->assertEquals(md5_file($tempFile, true), md5($response->body(), true)); + $headers = $response->headers(); + $this->assertEquals("val_1", $headers["X-Qn-Meta-M1"]); + $this->assertEquals("val_2", $headers["X-Qn-Meta-M2"]); + unlink($tempFile); + } + + // valid versions are tested above + // Use PHPUnit's Data Provider to test multiple Exception is better, + // but not match the test style of this project + public function testResumeUploadWithInvalidVersion() + { + $cfg = new Config(); + $upManager = new UploadManager($cfg); + $testFileSize = config::BLOCK_SIZE * 2; + $partSize = 5 * 1024 * 1024; + $testInvalidVersions = array( + // High probability invalid versions + 'v', + '1', + '2' + ); + + $expectExceptionCount = 0; + foreach ($testInvalidVersions as $invalidVersion) { + $key = self::getObjectKey('resumePutFile4ML_'); + $token = self::$auth->uploadToken(self::$bucketName, $key); + $tempFile = qiniuTempFile($testFileSize); + $resumeFile = tempnam(sys_get_temp_dir(), 'resume_file'); + $this->assertNotFalse($resumeFile); + try { + $upManager->putFile( + $token, + $key, + $tempFile, + null, + 'application/octet-stream', + false, + $resumeFile, + $invalidVersion, + $partSize + ); + } catch (\Exception $e) { + $isRightException = false; + $expectExceptionCount++; + while ($e) { + $isRightException = $e instanceof \UnexpectedValueException; + if ($isRightException) { + break; + } + $e = $e->getPrevious(); + } + $this->assertTrue($isRightException); + } + + unlink($tempFile); + } + $this->assertEquals(count($testInvalidVersions), $expectExceptionCount); + } + + private function makeReqOpt() + { + $reqOpt = new RequestOptions(); + $reqOpt->proxy = 'socks5://127.0.0.1:8080'; + $reqOpt->proxy_user_password = 'user:pass'; + return $reqOpt; + } +} diff --git a/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php new file mode 100644 index 0000000..fbab528 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php @@ -0,0 +1,136 @@ +bucketName = $bucketName; + + global $bucketNameBC; + $this->bucketNameBC = $bucketNameBC; + + global $bucketNameNA; + $this->bucketNameNA = $bucketNameNA; + + global $bucketNameFS; + $this->bucketNameFS = $bucketNameFS; + + global $bucketNameAS; + $this->bucketNameAS = $bucketNameAS; + + global $accessKey; + $this->ak = $accessKey; + + $this->zone = new Zone(); + $this->zoneHttps = new Zone('https'); + } + + public function testUpHosts() + { + list($ret, $err) = Zone::queryZone($this->ak, 'fakebucket'); + $this->assertNull($ret); + $this->assertNotNull($err); + + $zone = Zone::queryZone($this->ak, $this->bucketName); + $this->assertContains('upload.qiniup.com', $zone->cdnUpHosts); + + $zone = Zone::queryZone($this->ak, $this->bucketNameBC); + $this->assertContains('upload-z1.qiniup.com', $zone->cdnUpHosts); + + $zone = Zone::queryZone($this->ak, $this->bucketNameFS); + $this->assertContains('upload-z2.qiniup.com', $zone->cdnUpHosts); + + $zone = Zone::queryZone($this->ak, $this->bucketNameNA); + $this->assertContains('upload-na0.qiniup.com', $zone->cdnUpHosts); + + $zone = Zone::queryZone($this->ak, $this->bucketNameAS); + $this->assertContains('upload-as0.qiniup.com', $zone->cdnUpHosts); + } + + public function testIoHosts() + { + $zone = Zone::queryZone($this->ak, $this->bucketName); + $this->assertEquals($zone->iovipHost, 'iovip.qbox.me'); + + $zone = Zone::queryZone($this->ak, $this->bucketNameBC); + $this->assertEquals($zone->iovipHost, 'iovip-z1.qbox.me'); + + $zone = Zone::queryZone($this->ak, $this->bucketNameFS); + $this->assertEquals($zone->iovipHost, 'iovip-z2.qbox.me'); + + $zone = Zone::queryZone($this->ak, $this->bucketNameNA); + $this->assertEquals($zone->iovipHost, 'iovip-na0.qbox.me'); + + $zone = Zone::queryZone($this->ak, $this->bucketNameAS); + $this->assertEquals($zone->iovipHost, 'iovip-as0.qbox.me'); + } + + public function testZonez0() + { + $zone = Zone::zonez0(); + $this->assertContains('upload.qiniup.com', $zone->cdnUpHosts); + } + + public function testZonez1() + { + $zone = Zone::zonez1(); + $this->assertContains('upload-z1.qiniup.com', $zone->cdnUpHosts); + } + + public function testZonez2() + { + $zone = Zone::zonez2(); + $this->assertContains('upload-z2.qiniup.com', $zone->cdnUpHosts); + } + + public function testZoneCnEast2() + { + $zone = Zone::zoneCnEast2(); + $this->assertContains('upload-cn-east-2.qiniup.com', $zone->cdnUpHosts); + } + + public function testZoneNa0() + { + $zone = Zone::zoneNa0(); + $this->assertContains('upload-na0.qiniup.com', $zone->cdnUpHosts); + } + + public function testZoneAs0() + { + $zone = Zone::zoneAs0(); + $this->assertContains('upload-as0.qiniup.com', $zone->cdnUpHosts); + } + + public function testQvmZonez0() + { + $zone = Zone::qvmZonez0(); + $this->assertContains('free-qvm-z0-xs.qiniup.com', $zone->srcUpHosts); + } + + public function testQvmZonez1() + { + $zone = Zone::qvmZonez1(); + $this->assertContains('free-qvm-z1-zz.qiniup.com', $zone->srcUpHosts); + } +} diff --git a/vendor/qiniu/php-sdk/tests/bootstrap.php b/vendor/qiniu/php-sdk/tests/bootstrap.php new file mode 100644 index 0000000..9859a81 --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/bootstrap.php @@ -0,0 +1,61 @@ + 0) { + $length = min($rest_size, 4 * 1024); + if (fwrite($file, random_bytes($length)) == false) { + return false; + } + $rest_size -= $length; + } + } else if ($size > 0) { + fseek($file, $size - 1); + fwrite($file, ' '); + } + fclose($file); + return $fileName; +} diff --git a/vendor/qiniu/php-sdk/tests/mock-server/ok.php b/vendor/qiniu/php-sdk/tests/mock-server/ok.php new file mode 100644 index 0000000..5b0a65d --- /dev/null +++ b/vendor/qiniu/php-sdk/tests/mock-server/ok.php @@ -0,0 +1,3 @@ +