diff --git a/.editorconfig b/.editorconfig index 74505eb26..4d9e5bf78 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,5 +16,5 @@ indent_size = 2 [*.md] trim_trailing_whitespace = false -[{.version,.editorconfig}] +[{.version,.editorconfig,*.yml}] insert_final_newline = false \ No newline at end of file diff --git a/.eslintignore b/.eslintignore index e214a1171..d74497665 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,4 +3,5 @@ **/vendor/** **/js/*.js build/* -built/* \ No newline at end of file +built/* +docs/* diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 000000000..53baf3567 --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,23 @@ +name: Build Cloudinary Docs + +on: + push: + branches: + - master + +jobs: + hookdocs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: '12.x' + - run: npm install + - run: npm run build:docs + - name: Deploy to GH Pages + uses: maxheld83/ghpages@v0.3.0 + env: + BUILD_DIR: 'docs/' + GH_PAT: ${{ secrets.GH_PAT }} \ No newline at end of file diff --git a/.hookdoc/layout.tmpl b/.hookdoc/layout.tmpl new file mode 100644 index 000000000..46f764c5a --- /dev/null +++ b/.hookdoc/layout.tmpl @@ -0,0 +1,46 @@ + + + + + <?js= title ?> - Cloudinary Docs + + + + + + + + + + + + +
+ + +

+ + + +
+ + + +
+ + + + + + + diff --git a/docs/cloudinary_admin_image_settings.html b/docs/cloudinary_admin_image_settings.html new file mode 100644 index 000000000..33d0e7c2d --- /dev/null +++ b/docs/cloudinary_admin_image_settings.html @@ -0,0 +1,217 @@ + + + + + Filter: cloudinary_admin_image_settings - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_admin_image_settings

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_admin_image_settings', $settings ) → {array}

+ + + + + +
+ Filter the Cloudinary global transformations tab for images. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$settings + + +array + + + + The global transformations settings.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_admin_pages.html b/docs/cloudinary_admin_pages.html new file mode 100644 index 000000000..3fb0dd202 --- /dev/null +++ b/docs/cloudinary_admin_pages.html @@ -0,0 +1,217 @@ + + + + + Filter: cloudinary_admin_pages - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_admin_pages

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_admin_pages', $settings ) → {array}

+ + + + + +
+ Filter the Cloudinary admin pages. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$settings + + +array + + + + The admin pages settings.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_admin_video_settings.html b/docs/cloudinary_admin_video_settings.html new file mode 100644 index 000000000..d487569b3 --- /dev/null +++ b/docs/cloudinary_admin_video_settings.html @@ -0,0 +1,217 @@ + + + + + Filter: cloudinary_admin_video_settings - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_admin_video_settings

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_admin_video_settings', $settings ) → {array}

+ + + + + +
+ Filter the Cloudinary global transformations tab for video. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$settings + + +array + + + + The global transformations settings.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_allow_glb_upload.html b/docs/cloudinary_allow_glb_upload.html new file mode 100644 index 000000000..6d3149408 --- /dev/null +++ b/docs/cloudinary_allow_glb_upload.html @@ -0,0 +1,210 @@ + + + + + Filter: cloudinary_allow_glb_upload - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_allow_glb_upload

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_allow_glb_upload', bool )

+ + + + + +
+ Filter to allow GLB 3D model files to be uploaded. + +WARNING: This is an experimental hook. The only place where GLB files can be used + is in the Cloudinary Gallery block. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
bool + + $allow_glb_upload Whether to allow GLB files to be uploaded.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.6
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ bool +
+ + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_allowed_extensions.html b/docs/cloudinary_allowed_extensions.html new file mode 100644 index 000000000..29de90e2c --- /dev/null +++ b/docs/cloudinary_allowed_extensions.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_allowed_extensions - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_allowed_extensions

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_allowed_extensions', $allowed_types ) → {array}

+ + + + + +
+ Filter the allowed file extensions to be delivered. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$allowed_types + + +array + + + + Array of allowed file extensions.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_api_rest_endpoints.html b/docs/cloudinary_api_rest_endpoints.html new file mode 100644 index 000000000..a6a6b9b59 --- /dev/null +++ b/docs/cloudinary_api_rest_endpoints.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_api_rest_endpoints - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_api_rest_endpoints

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_api_rest_endpoints', $types ) → {array}

+ + + + + +
+ Filter the Cloudinary REST API endpoints. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$types + + +array + + + + The registered endpoints.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • array()
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_apply_breakpoints.html b/docs/cloudinary_apply_breakpoints.html new file mode 100644 index 000000000..239d247ef --- /dev/null +++ b/docs/cloudinary_apply_breakpoints.html @@ -0,0 +1,225 @@ + + + + + Filter: cloudinary_apply_breakpoints - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_apply_breakpoints

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_apply_breakpoints', $apply ) → {bool}

+ + + + + +
+ Filter to allow stopping default srcset generation. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$apply + + +bool + + + + True to apply, false to skip.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • {true}
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_apply_default_transformations.html b/docs/cloudinary_apply_default_transformations.html new file mode 100644 index 000000000..f97b3781d --- /dev/null +++ b/docs/cloudinary_apply_default_transformations.html @@ -0,0 +1,245 @@ + + + + + Filter: cloudinary_apply_default_transformations - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_apply_default_transformations

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_apply_default_transformations', $true, $attachment_id ) → {bool}

+ + + + + +
+ Filter to allow bypassing defaults. Return false to not apply defaults. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$true + + +bool + + + + True to apply defaults.
$attachment_id + + +int + + + + The current attachment ID.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • true
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_asset_payload.html b/docs/cloudinary_asset_payload.html new file mode 100644 index 000000000..6c9887216 --- /dev/null +++ b/docs/cloudinary_asset_payload.html @@ -0,0 +1,273 @@ + + + + + Filter: cloudinary_asset_payload - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_asset_payload

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_asset_payload', $asset, $data ) → {array}

+ + + + + +
+ Filter the asset payload. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$asset + + +array + + + + The asset payload.
$data + + +array + + + + The raw data from the request.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.3
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + +
Example
+ +
<?php
+
+// Extend Cloudinary support for extra data.
+// Contextual metadata is passed by default.
+add_filter(
+   'cloudinary_asset_payload',
+   static function ( $asset, $payload ) {
+       // The structured keys on Cloudinary to use in WordPress.
+       $key = 'structured_key';
+
+       // Structural metadata. Beware of key collision with contextual metadata.
+       if ( ! empty ( $payload['asset']['metadata'][ $key ] ) ) {
+           // The sanitize function to use should be adequate to the data type.
+           $asset['meta'][ $key ] = sanitize_text_field( $payload['asset']['metadata'][ $key ] );
+       }
+
+       // The Cloudinary tags.
+       if ( ! empty ( $payload['asset']['tags'] ) ) {
+           $asset['tags'] = array_map( 'sanitize_text_field', $payload['asset']['tags'] );
+       }
+
+       return $asset;
+   },
+   10,
+   2
+);
+ + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_asset_state.html b/docs/cloudinary_asset_state.html new file mode 100644 index 000000000..b7f96d8ee --- /dev/null +++ b/docs/cloudinary_asset_state.html @@ -0,0 +1,240 @@ + + + + + Filter: cloudinary_asset_state - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_asset_state

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_asset_state', $state, $attachment_id ) → {array}

+ + + + + +
+ Filter the state of the asset. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$state + + +int + + + + The attachment state.
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_autosync_threads.html b/docs/cloudinary_autosync_threads.html new file mode 100644 index 000000000..2b6237636 --- /dev/null +++ b/docs/cloudinary_autosync_threads.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_autosync_threads - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_autosync_threads

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_autosync_threads', $count ) → {int}

+ + + + + +
+ Filter the amount of background threads to process for auto syncing. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$count + + +int + + + + The number of autosync threads to use.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 2
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +int + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_build_queue_query.html b/docs/cloudinary_build_queue_query.html new file mode 100644 index 000000000..054d6c776 --- /dev/null +++ b/docs/cloudinary_build_queue_query.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_build_queue_query - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_build_queue_query

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_build_queue_query', $args ) → {array}

+ + + + + +
+ Filter the params for the query used to build a queue. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$args + + +array + + + + The arguments for the query.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.6
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_bypass_cache.html b/docs/cloudinary_bypass_cache.html new file mode 100644 index 000000000..71819260b --- /dev/null +++ b/docs/cloudinary_bypass_cache.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_bypass_cache - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_bypass_cache

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_bypass_cache', $bypass ) → {bool}

+ + + + + +
+ Filter to allow bypassing the cache. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$bypass + + +bool + + + + True to bypass, false to not.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • false
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_bypass_seo_url.html b/docs/cloudinary_bypass_seo_url.html new file mode 100644 index 000000000..58893dd1e --- /dev/null +++ b/docs/cloudinary_bypass_seo_url.html @@ -0,0 +1,566 @@ + + + + + Filter: cloudinary_bypass_seo_url - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_bypass_seo_url

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_bypass_seo_url', $bypass_seo_url ) → {bool}

+ + + + + +
+ Bypass Cloudinary's SEO URLs. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$bypass_seo_url + + +bool + + + + Whether to bypass SEO URLs.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.5
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_bypass_seo_url', $bypass_seo_url ) → {bool}

+ + + + + +
+ Bypass Cloudinary's SEO URLs. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$bypass_seo_url + + +bool + + + + Whether to bypass SEO URLs.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.5
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_bypass_seo_url', $bypass_seo_url ) → {bool}

+ + + + + +
+ Bypass Cloudinary's SEO URLs. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$bypass_seo_url + + +bool + + + + Whether to bypass SEO URLs.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.5
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_can_sync_asset.html b/docs/cloudinary_can_sync_asset.html new file mode 100644 index 000000000..25090916e --- /dev/null +++ b/docs/cloudinary_can_sync_asset.html @@ -0,0 +1,263 @@ + + + + + Filter: cloudinary_can_sync_asset - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_can_sync_asset

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_can_sync_asset', $can, $attachment_id, $type ) → {bool}

+ + + + + +
+ Filter to allow changing if an asset is allowed to be synced. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$can + + +bool + + + + Can sync.
$attachment_id + + +int + + + + The attachment post ID.
$type + + +string + + + + The type of sync to attempt.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_connected.html b/docs/cloudinary_connected.html new file mode 100644 index 000000000..1d7fd718e --- /dev/null +++ b/docs/cloudinary_connected.html @@ -0,0 +1,202 @@ + + + + + Action: cloudinary_connected - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_connected

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_connected', $plugin )

+ + + + + +
+ Action indicating that the cloudinary is connected. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$plugin + + +Plugin + + + + The core plugin object.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_content_url.html b/docs/cloudinary_content_url.html new file mode 100644 index 000000000..4ff25a526 --- /dev/null +++ b/docs/cloudinary_content_url.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_content_url - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_content_url

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_content_url', $url ) → {string}

+ + + + + +
+ The URL to be searched for and prepared to be delivered by Cloudinary. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$url + + +string + + + + The default content_url.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.6
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_context_options.html b/docs/cloudinary_context_options.html new file mode 100644 index 000000000..aba4c9a2d --- /dev/null +++ b/docs/cloudinary_context_options.html @@ -0,0 +1,263 @@ + + + + + Filter: cloudinary_context_options - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_context_options

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_context_options', $options, $post, $this ) → {array}

+ + + + + +
+ Filter the options to allow other plugins to add requested options for uploading. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$options + + +array + + + + The options array.
$post + + +WP_Post + + + + The attachment post.
$this + + +Media + + + + The media object instance.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_contextualized_post_id.html b/docs/cloudinary_contextualized_post_id.html new file mode 100644 index 000000000..08dcd9d4c --- /dev/null +++ b/docs/cloudinary_contextualized_post_id.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_contextualized_post_id - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_contextualized_post_id

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_contextualized_post_id', $post_id ) → {int}

+ + + + + +
+ Get the contextualized post id. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$post_id + + +int + + + + The post ID.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +int + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_convert_media_types.html b/docs/cloudinary_convert_media_types.html new file mode 100644 index 000000000..c2f2c1e35 --- /dev/null +++ b/docs/cloudinary_convert_media_types.html @@ -0,0 +1,217 @@ + + + + + Filter: cloudinary_convert_media_types - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_convert_media_types

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_convert_media_types', $base_types ) → {array}

+ + + + + +
+ Filter the base types for conversion. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$base_types + + +array + + + + The base conversion types array.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_converted_url.html b/docs/cloudinary_converted_url.html new file mode 100644 index 000000000..d35be73c1 --- /dev/null +++ b/docs/cloudinary_converted_url.html @@ -0,0 +1,263 @@ + + + + + Filter: cloudinary_converted_url - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_converted_url

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_converted_url', $url, $attachment_id, $pre_args ) → {string}

+ + + + + +
+ Filter the final Cloudinary URL. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$url + + +string + + + + The Cloudinary URL.
$attachment_id + + +int + + + + The id of the attachment.
$pre_args + + +array + + + + The arguments used to create the url.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_cron_frequency.html b/docs/cloudinary_cron_frequency.html new file mode 100644 index 000000000..836a1fa31 --- /dev/null +++ b/docs/cloudinary_cron_frequency.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_cron_frequency - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_cron_frequency

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_cron_frequency', $time ) → {int}

+ + + + + +
+ Filter the cron job frequency. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$time + + +int + + + + The filtered time.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 600
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +int + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_cron_start_offset.html b/docs/cloudinary_cron_start_offset.html new file mode 100644 index 000000000..fb30e6342 --- /dev/null +++ b/docs/cloudinary_cron_start_offset.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_cron_start_offset - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_cron_start_offset

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_cron_start_offset', $time ) → {int}

+ + + + + +
+ Filter the cron start offset. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$time + + +int + + + + The filtered time.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 60
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +int + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_current_post_id.html b/docs/cloudinary_current_post_id.html new file mode 100644 index 000000000..a0e241ab1 --- /dev/null +++ b/docs/cloudinary_current_post_id.html @@ -0,0 +1,176 @@ + + + + + Filter: cloudinary_current_post_id - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_current_post_id

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_current_post_id' )

+ + + + + +
+ Filter the post ID. +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • null
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +WP_Post +| + +null + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_default_freeform_transformations_%7B$type%7D.html b/docs/cloudinary_default_freeform_transformations_%7B$type%7D.html new file mode 100644 index 000000000..850b5fe90 --- /dev/null +++ b/docs/cloudinary_default_freeform_transformations_%7B$type%7D.html @@ -0,0 +1,245 @@ + + + + + Filter: cloudinary_default_freeform_transformations_{$type} - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_default_freeform_transformations_{$type}

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_default_freeform_transformations_{$type}', $defaults, $transformations ) → {array}

+ + + + + +
+ Filter the default Freeform transformations for the specific media type. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$defaults + + +array + + + + The default transformations array.
$transformations + + +array + + + + The current transformations array.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • array()
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_default_qf_transformations_%7B$type%7D.html b/docs/cloudinary_default_qf_transformations_%7B$type%7D.html new file mode 100644 index 000000000..370947e58 --- /dev/null +++ b/docs/cloudinary_default_qf_transformations_%7B$type%7D.html @@ -0,0 +1,245 @@ + + + + + Filter: cloudinary_default_qf_transformations_{$type} - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_default_qf_transformations_{$type}

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_default_qf_transformations_{$type}', $defaults, $transformations ) → {array}

+ + + + + +
+ Filter the default Quality and Format transformations for the specific media type. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$defaults + + +array + + + + The default transformations array.
$transformations + + +array + + + + The current transformations array.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • array()
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_default_transformations.html b/docs/cloudinary_default_transformations.html new file mode 100644 index 000000000..bf5f8552a --- /dev/null +++ b/docs/cloudinary_default_transformations.html @@ -0,0 +1,217 @@ + + + + + Filter: cloudinary_default_transformations - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_default_transformations

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_default_transformations', $defaults ) → {array}

+ + + + + +
+ Filter the default cloudinary transformations. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$defaults + + +array + + + + The default transformations array.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_delete_asset.html b/docs/cloudinary_delete_asset.html new file mode 100644 index 000000000..bc8aabe81 --- /dev/null +++ b/docs/cloudinary_delete_asset.html @@ -0,0 +1,153 @@ + + + + + Action: cloudinary_delete_asset - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_delete_asset

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_delete_asset' )

+ + + + + +
+ Action fired when deleting a synced asset. +
+ + + + + + + + + + + +
+ + + + +
Since:
+
  • 3.0.1
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_delivery_get_id.html b/docs/cloudinary_delivery_get_id.html new file mode 100644 index 000000000..f3b9df9a1 --- /dev/null +++ b/docs/cloudinary_delivery_get_id.html @@ -0,0 +1,246 @@ + + + + + Filter: cloudinary_delivery_get_id - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_delivery_get_id

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_delivery_get_id', $attachment_id, $tag_element ) → {int|false}

+ + + + + +
+ Filter id from the tag. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$attachment_id + + +int + + + + The attachment ID.
$tag_element + + +array + + + + The tag element.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.6
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +int +| + +false + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_delivery_searchable_url.html b/docs/cloudinary_delivery_searchable_url.html new file mode 100644 index 000000000..88d7be936 --- /dev/null +++ b/docs/cloudinary_delivery_searchable_url.html @@ -0,0 +1,266 @@ + + + + + Filter: cloudinary_delivery_searchable_url - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_delivery_searchable_url

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_delivery_searchable_url', $url, $item, $content_url ) → {string}

+ + + + + +
+ The URL to be searched for and prepared to be delivered by Cloudinary. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$url + + +string + + + + The URL to be searched for and prepared to be delivered by Cloudinary.
$item + + +array + + + + The found asset array.
$content_url + + +string + + + + The content URL.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.6
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_dns_prefetch_types.html b/docs/cloudinary_dns_prefetch_types.html new file mode 100644 index 000000000..49b74c80f --- /dev/null +++ b/docs/cloudinary_dns_prefetch_types.html @@ -0,0 +1,229 @@ + + + + + Filter: cloudinary_dns_prefetch_types - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_dns_prefetch_types

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_dns_prefetch_types', $types ) → {array}

+ + + + + +
+ Filter to provide option to omit prefetch. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$types + + +array + + + + The types of resource hints to use.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.12
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • array ( 'dns-prefetch', 'preconnect' )
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ The modified resource hints to use. +
+ + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_doing_upload.html b/docs/cloudinary_doing_upload.html new file mode 100644 index 000000000..6ddede6c3 --- /dev/null +++ b/docs/cloudinary_doing_upload.html @@ -0,0 +1,223 @@ + + + + + Filter: cloudinary_doing_upload - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_doing_upload

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_doing_upload', $false ) → {bool}

+ + + + + +
+ Filter doing upload. +If so, return the default attachment URL. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$false + + +bool + + + + Default false.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • false
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_download_asset.html b/docs/cloudinary_download_asset.html new file mode 100644 index 000000000..b23d68907 --- /dev/null +++ b/docs/cloudinary_download_asset.html @@ -0,0 +1,248 @@ + + + + + Action: cloudinary_download_asset - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_download_asset

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_download_asset', $asset, $return )

+ + + + + +
+ Action for the downloaded assets from Cloudinary Media Library. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$asset + + +array + + + + The default filters.
$return + + +array + + + + The return payload.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.3
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Example
+ +
<?php
+add_action(
+   'cloudinary_download_asset',
+   static function ( $asset ) {
+       // Store metadata. Contextual and Structured metadata should be similar.
+       $key = 'metadata_key';
+       if ( ! empty( $asset['meta'][ $key ] ) && ! empty( $asset['attachment_id'] ) && 'attachment' === get_post_type( $asset['attachment_id'] ) ) {
+           update_post_meta( $asset['attachment_id'],  $key , $asset['meta'][ $key ] );
+       }
+
+       // Store the tags. The taxonomy needs to be assigned to the post type.
+       if ( ! empty( $asset['tags'] ) && ! empty( $asset['attachment_id'] ) && 'attachment' === get_post_type( $asset['attachment_id'] ) ) {
+           wp_set_post_terms(
+               $asset['attachment_id'],
+               $asset['tags']
+           );
+       }
+   }
+);
+ + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_edit_asset_permalink.html b/docs/cloudinary_edit_asset_permalink.html new file mode 100644 index 000000000..815e339b1 --- /dev/null +++ b/docs/cloudinary_edit_asset_permalink.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_edit_asset_permalink - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_edit_asset_permalink

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + + + + + + + +
+ Filter the permalink for the edit asset link. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$permalink + + +string + + + + The permalink.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_enable_crop_and_gravity_control.html b/docs/cloudinary_enable_crop_and_gravity_control.html new file mode 100644 index 000000000..11cde8821 --- /dev/null +++ b/docs/cloudinary_enable_crop_and_gravity_control.html @@ -0,0 +1,687 @@ + + + + + Action: cloudinary_enable_crop_and_gravity_control - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_enable_crop_and_gravity_control

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_enable_crop_and_gravity_control', $enabeld )

+ + + + + +
+ Enable the Crop and Gravity control settings. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$enabeld + + +bool + + + + Is the Crop and Gravity control enabled?
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.3
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • {false}
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_enable_crop_and_gravity_control', $enabeld )

+ + + + + +
+ Enable the Crop and Gravity control settings. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$enabeld + + +bool + + + + Is the Crop and Gravity control enabled?
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.3
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • {false}
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_enable_crop_and_gravity_control', $enabeld )

+ + + + + +
+ Enable the Crop and Gravity control settings. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$enabeld + + +bool + + + + Is the Crop and Gravity control enabled?
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.3
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • {false}
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_enable_crop_and_gravity_control', $enabeld )

+ + + + + +
+ Enable the Crop and Gravity control settings. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$enabeld + + +bool + + + + Is the Crop and Gravity control enabled?
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.3
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • {false}
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_filter_out_local.html b/docs/cloudinary_filter_out_local.html new file mode 100644 index 000000000..878574506 --- /dev/null +++ b/docs/cloudinary_filter_out_local.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_filter_out_local - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_filter_out_local

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_filter_out_local', $can ) → {bool}

+ + + + + +
+ Filter to allow stopping filtering out local. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$can + + +bool + + + + True as default.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • true
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_filter_unknown_urls.html b/docs/cloudinary_filter_unknown_urls.html new file mode 100644 index 000000000..7d7c77482 --- /dev/null +++ b/docs/cloudinary_filter_unknown_urls.html @@ -0,0 +1,243 @@ + + + + + Filter: cloudinary_filter_unknown_urls - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_filter_unknown_urls

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_filter_unknown_urls', array, array, array )

+ + + + + +
+ Filter the list of truly unknown URLs after filtering out image size variations. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
array + + $truly_unknown The filtered list of unknown URLs.
array + + $urls The original list of all URLs.
array + + $known_keys The list of known URL keys.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.12
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ array The filtered list of unknown URLs. +
+ + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_flush_cache.html b/docs/cloudinary_flush_cache.html new file mode 100644 index 000000000..179ca66b4 --- /dev/null +++ b/docs/cloudinary_flush_cache.html @@ -0,0 +1,789 @@ + + + + + Action: cloudinary_flush_cache - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_flush_cache

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_flush_cache' )

+ + + + + +
+ Action to flush delivery caches. +
+ + + + + + + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_flush_cache' )

+ + + + + +
+ Action to flush delivery caches. +
+ + + + + + + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_flush_cache' )

+ + + + + +
+ Action to flush delivery caches. +
+ + + + + + + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_flush_cache' )

+ + + + + +
+ Action to flush delivery caches. +
+ + + + + + + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_flush_cache' )

+ + + + + +
+ Action to flush delivery caches. +
+ + + + + + + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_flush_cache' )

+ + + + + +
+ Action to flush delivery caches. +
+ + + + + + + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_flush_cache' )

+ + + + + +
+ Action to flush delivery caches. +
+ + + + + + + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_gallery_config.html b/docs/cloudinary_gallery_config.html new file mode 100644 index 000000000..a1994fe59 --- /dev/null +++ b/docs/cloudinary_gallery_config.html @@ -0,0 +1,217 @@ + + + + + Filter: cloudinary_gallery_config - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_gallery_config

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + + + + + + + +
+ Filter the gallery configuration. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$config + + +array + + + + The current gallery config.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_gallery_html_container.html b/docs/cloudinary_gallery_html_container.html new file mode 100644 index 000000000..e0dbee438 --- /dev/null +++ b/docs/cloudinary_gallery_html_container.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_gallery_html_container - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_gallery_html_container

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + + + + + + + +
+ Filter the gallery HTML container. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$selector + + +string + + + + The target HTML selector.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • ''
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_get_signature.html b/docs/cloudinary_get_signature.html new file mode 100644 index 000000000..877aa73bc --- /dev/null +++ b/docs/cloudinary_get_signature.html @@ -0,0 +1,240 @@ + + + + + Filter: cloudinary_get_signature - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_get_signature

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_get_signature', $return, $attachment_id ) → {array}

+ + + + + +
+ Filter the get signature of the asset. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$return + + +array + + + + The attachment signature.
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_home_url.html b/docs/cloudinary_home_url.html new file mode 100644 index 000000000..11a868dd7 --- /dev/null +++ b/docs/cloudinary_home_url.html @@ -0,0 +1,266 @@ + + + + + Filter: cloudinary_home_url - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_home_url

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_home_url', $home_url, $path, $scheme ) → {string}

+ + + + + +
+ Filter the home url. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$home_url + + +string + + + + The home url.
$path + + +string + + + + The path to be appended to the home URL.
$scheme + + +string + + + + The scheme to give the home URL context. Accepts 'http', 'https', or 'relative'.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_id.html b/docs/cloudinary_id.html new file mode 100644 index 000000000..0131f117b --- /dev/null +++ b/docs/cloudinary_id.html @@ -0,0 +1,228 @@ + + + + + Action: cloudinary_id - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_id

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_id', $cloudinary_id, $attachment_id )

+ + + + + +
+ Action the Cloudinary ID to allow extending it's availability. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$cloudinary_id + + +string +| + +bool + + + + The public ID from Cloudinary, or false if not found.
$attachment_id + + +int + + + + The id of the asset.
+ + + + + + +
+ + + + +
Since:
+
  • 2.1.9
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_ignored_class_keywords.html b/docs/cloudinary_ignored_class_keywords.html new file mode 100644 index 000000000..fbdb8544e --- /dev/null +++ b/docs/cloudinary_ignored_class_keywords.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_ignored_class_keywords - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_ignored_class_keywords

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_ignored_class_keywords', $lazy_classes ) → {array}

+ + + + + +
+ Filter the keywords in classes on tags to be ignored from lazy-loading. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$lazy_classes + + +array + + + + The built-in ignore class keywords.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.8
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_ignored_data_keywords.html b/docs/cloudinary_ignored_data_keywords.html new file mode 100644 index 000000000..520448430 --- /dev/null +++ b/docs/cloudinary_ignored_data_keywords.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_ignored_data_keywords - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_ignored_data_keywords

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_ignored_data_keywords', $lazy_keys ) → {array}

+ + + + + +
+ Filter the keywords in data-* attributes on tags to be ignored from lazy-loading. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$lazy_keys + + +array + + + + The built-in ignore data-* keywords.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.8
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_init_delivery.html b/docs/cloudinary_init_delivery.html new file mode 100644 index 000000000..2494af1cb --- /dev/null +++ b/docs/cloudinary_init_delivery.html @@ -0,0 +1,202 @@ + + + + + Action: cloudinary_init_delivery - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_init_delivery

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_init_delivery', $delivery )

+ + + + + +
+ Action indicating that the delivery is starting. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$delivery + + +Delivery + + + + The delivery object.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.5
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_init_settings.html b/docs/cloudinary_init_settings.html new file mode 100644 index 000000000..f6c664885 --- /dev/null +++ b/docs/cloudinary_init_settings.html @@ -0,0 +1,202 @@ + + + + + Action: cloudinary_init_settings - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_init_settings

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_init_settings', $plugin )

+ + + + + +
+ Action indicating that the Settings are initialised. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$plugin + + +Plugin + + + + The core plugin object.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.5
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_is_%7B$class_name%7D_active.html b/docs/cloudinary_is_%7B$class_name%7D_active.html new file mode 100644 index 000000000..d1bd1f9b3 --- /dev/null +++ b/docs/cloudinary_is_%7B$class_name%7D_active.html @@ -0,0 +1,202 @@ + + + + + Action: cloudinary_is_{$class_name}_active - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_is_{$class_name}_active

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_is_{$class_name}_active', $is_active )

+ + + + + +
+ Filter to check if the feature is active. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$is_active + + +bool + + + + Flag if active.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.4
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_is_content_dir.html b/docs/cloudinary_is_content_dir.html new file mode 100644 index 000000000..40b022a25 --- /dev/null +++ b/docs/cloudinary_is_content_dir.html @@ -0,0 +1,243 @@ + + + + + Filter: cloudinary_is_content_dir - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_is_content_dir

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_is_content_dir', $is_local, $url ) → {bool}

+ + + + + +
+ Filter if the url is a local asset. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$is_local + + +bool + + + + If the url is a local asset.
$url + + +string + + + + The url.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.6
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_is_deliverable.html b/docs/cloudinary_is_deliverable.html new file mode 100644 index 000000000..1cf5b25a1 --- /dev/null +++ b/docs/cloudinary_is_deliverable.html @@ -0,0 +1,240 @@ + + + + + Filter: cloudinary_is_deliverable - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_is_deliverable

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_is_deliverable', $is, $attachment_id ) → {bool}

+ + + + + +
+ Filter deliverable attachments. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$is + + +bool + + + + The default value.
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_is_folder_synced.html b/docs/cloudinary_is_folder_synced.html new file mode 100644 index 000000000..26d609741 --- /dev/null +++ b/docs/cloudinary_is_folder_synced.html @@ -0,0 +1,240 @@ + + + + + Filter: cloudinary_is_folder_synced - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_is_folder_synced

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_is_folder_synced', $is_folder_synced, $attachment_id ) → {bool}

+ + + + + +
+ Filter is folder synced flag. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$is_folder_synced + + +bool + + + + Flag value for is folder sync.
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_is_media.html b/docs/cloudinary_is_media.html new file mode 100644 index 000000000..4ec0ba235 --- /dev/null +++ b/docs/cloudinary_is_media.html @@ -0,0 +1,248 @@ + + + + + Filter: cloudinary_is_media - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_is_media

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_is_media', $is_media, $attachment_id ) → {bool}

+ + + + + +
+ Filter the check if post is media. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$is_media + + +bool + + + + Flag if is media.
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.6
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • false
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_is_uploadable_media.html b/docs/cloudinary_is_uploadable_media.html new file mode 100644 index 000000000..5abc27ffd --- /dev/null +++ b/docs/cloudinary_is_uploadable_media.html @@ -0,0 +1,243 @@ + + + + + Filter: cloudinary_is_uploadable_media - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_is_uploadable_media

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_is_uploadable_media', $is_local, $media_host ) → {bool}

+ + + + + +
+ Filter local media. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$is_local + + +bool + + + + The attachment ID.
$media_host + + +string + + + + The html tag.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.7
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_lazy_load_bypass.html b/docs/cloudinary_lazy_load_bypass.html new file mode 100644 index 000000000..2d35f897f --- /dev/null +++ b/docs/cloudinary_lazy_load_bypass.html @@ -0,0 +1,437 @@ + + + + + Filter: cloudinary_lazy_load_bypass - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_lazy_load_bypass

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_lazy_load_bypass', $short_circuit, $tag_element ) → {bool}

+ + + + + +
+ Short circuit the lazy load. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$short_circuit + + +bool + + + + The short circuit value.
$tag_element + + +array + + + + The tag element.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.9
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + +
Example
+ +
<?php
+
+// Bypass lazy load for images with ID `feature-image`.
+add_filter(
+   'cloudinary_lazy_load_bypass',
+   function( $bypass, $tag_element ) {
+       if ( 'feature-image! === $tag_element['id'] ) {
+           $bypass = true;
+       }
+       return $bypass;
+   }
+);
+ + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_lazy_load_bypass', $short_circuit, $tag_element )

+ + + + + +
+ Short circuit the lazy load. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$short_circuit + + +bool + + + + The short circuit value.
$tag_element + + +array + + + + The tag element.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.9
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_lazy_load_bypass_classes.html b/docs/cloudinary_lazy_load_bypass_classes.html new file mode 100644 index 000000000..9b8a02aaf --- /dev/null +++ b/docs/cloudinary_lazy_load_bypass_classes.html @@ -0,0 +1,234 @@ + + + + + Filter: cloudinary_lazy_load_bypass_classes - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_lazy_load_bypass_classes

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_lazy_load_bypass_classes', $classes ) → {bool}

+ + + + + +
+ Filter the classes that bypass lazy loading. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$classes + + +array + + + + Classes that bypass the Lazy Load.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.9
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + +
Example
+ +
<?php
+
+// Extend bypass lazy load classes to include `skip-lazy`.
+add_filter(
+   'cloudinary_lazy_load_bypass_classes',
+   function( $classes ) {
+        $classes[] = 'skip-lazy';
+        return $classes;
+   }
+);
+ + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_lazy_load_bypass_formats.html b/docs/cloudinary_lazy_load_bypass_formats.html new file mode 100644 index 000000000..4d418af89 --- /dev/null +++ b/docs/cloudinary_lazy_load_bypass_formats.html @@ -0,0 +1,215 @@ + + + + + Filter: cloudinary_lazy_load_bypass_formats - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_lazy_load_bypass_formats

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_lazy_load_bypass_formats', $formats ) → {array}

+ + + + + +
+ Filter out file formats for Lazy Load. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$formats + + {array) The list of formats to exclude.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_local_url.html b/docs/cloudinary_local_url.html new file mode 100644 index 000000000..b1bc5609d --- /dev/null +++ b/docs/cloudinary_local_url.html @@ -0,0 +1,249 @@ + + + + + Filter: cloudinary_local_url - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_local_url

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_local_url', $url, $attachment_id ) → {string|false}

+ + + + + +
+ Filter local URL. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$url + + +string +| + +false + + + + The local URL
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string +| + +false + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_max_files_import.html b/docs/cloudinary_max_files_import.html new file mode 100644 index 000000000..d73d43edd --- /dev/null +++ b/docs/cloudinary_max_files_import.html @@ -0,0 +1,238 @@ + + + + + Filter: cloudinary_max_files_import - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_max_files_import

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_max_files_import', $max_files ) → {int}

+ + + + + +
+ Filter the maximum number of files that can be imported from Cloudinary. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$max_files + + +int + + + + The maximum number of files that can be imported from Cloudinary.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.3
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 20
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +int + + +
+
+ + + + +
Example
+ +
<?php
+
+// Filter Cloudinary max files per import.
+add_filter(
+   'cloudinary_max_files_import',
+   static function() {
+       return 100;
+   }
+);
+ + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_media_context.html b/docs/cloudinary_media_context.html new file mode 100644 index 000000000..f8c3b4ecf --- /dev/null +++ b/docs/cloudinary_media_context.html @@ -0,0 +1,254 @@ + + + + + Filter: cloudinary_media_context - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_media_context

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_media_context', $media_context, $attachment_id ) → {string}

+ + + + + +
+ Filter the media context. + +This filter allows you to set a media context for the media for cases where the same asset is used in +different use cases, such as in a multilingual context. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$media_context + + +string + + + + The media context.
$attachment_id + + +int +| + +null + + + + The attachment ID.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.9
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • {'default'}
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_media_context_query.html b/docs/cloudinary_media_context_query.html new file mode 100644 index 000000000..1b6a39943 --- /dev/null +++ b/docs/cloudinary_media_context_query.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_media_context_query - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_media_context_query

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_media_context_query', $media_context_query ) → {string}

+ + + + + +
+ Filter the media context query. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$media_context_query + + +string + + + + The default media context query.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_media_context_things.html b/docs/cloudinary_media_context_things.html new file mode 100644 index 000000000..21119ee12 --- /dev/null +++ b/docs/cloudinary_media_context_things.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_media_context_things - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_media_context_things

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_media_context_things', $media_context_things ) → {array}

+ + + + + +
+ Filter the media context things. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$media_context_things + + +array + + + + The default media context things.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_media_filters.html b/docs/cloudinary_media_filters.html new file mode 100644 index 000000000..1cd9779ff --- /dev/null +++ b/docs/cloudinary_media_filters.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_media_filters - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_media_filters

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_media_filters', $filters ) → {array}

+ + + + + +
+ Filter the Cloudinary Media Library filters. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$filters + + +array + + + + The default filters.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_media_status.html b/docs/cloudinary_media_status.html new file mode 100644 index 000000000..3e0d2f711 --- /dev/null +++ b/docs/cloudinary_media_status.html @@ -0,0 +1,245 @@ + + + + + Filter: cloudinary_media_status - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_media_status

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_media_status', $status, $post_id ) → {array}

+ + + + + +
+ Filter the Cloudinary media status. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$status + + +array + + + + The media status.
$post_id + + +int + + + + The Post ID.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • array()
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_media_types.html b/docs/cloudinary_media_types.html new file mode 100644 index 000000000..d966b36cc --- /dev/null +++ b/docs/cloudinary_media_types.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_media_types - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_media_types

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_media_types', $types ) → {array}

+ + + + + +
+ Filter the default Cloudinary Media Types. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$types + + +array + + + + The default media types array.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • array( 'image', 'video', 'audio', 'application', 'text' )
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_meta_boxes.html b/docs/cloudinary_meta_boxes.html new file mode 100644 index 000000000..38ac2cf89 --- /dev/null +++ b/docs/cloudinary_meta_boxes.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_meta_boxes - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_meta_boxes

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_meta_boxes', $metaboxes ) → {array}

+ + + + + +
+ Filter the meta boxes. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$metaboxes + + +array + + + + Array of meta boxes to create.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.3
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_migrate_legacy_meta.html b/docs/cloudinary_migrate_legacy_meta.html new file mode 100644 index 000000000..aca4424ae --- /dev/null +++ b/docs/cloudinary_migrate_legacy_meta.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_migrate_legacy_meta - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_migrate_legacy_meta

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_migrate_legacy_meta', $attachment_id ) → {array}

+ + + + + +
+ Filter the meta if not found, in order to migrate from a legacy plugin. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.5
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_on_demand_sync_limit.html b/docs/cloudinary_on_demand_sync_limit.html new file mode 100644 index 000000000..825db94a6 --- /dev/null +++ b/docs/cloudinary_on_demand_sync_limit.html @@ -0,0 +1,225 @@ + + + + + Filter: cloudinary_on_demand_sync_limit - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_on_demand_sync_limit

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_on_demand_sync_limit', $value ) → {int}

+ + + + + +
+ Filter the on demand synced items limit. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$value + + +int + + + + The default number of static assets.
+ + + + + + +
+ + + + +
Since:
+
  • 2.8.0
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 100
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +int + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_parse_element.html b/docs/cloudinary_parse_element.html new file mode 100644 index 000000000..a7bb85e5e --- /dev/null +++ b/docs/cloudinary_parse_element.html @@ -0,0 +1,224 @@ + + + + + Filter: cloudinary_parse_element - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_parse_element

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_parse_element', $tag_element ) → {array}

+ + + + + +
+ Filter the tag element. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$tag_element + + +array + + + + The tag element.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.9
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ The tag element. +
+ + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_plugin_asset_cache_filters.html b/docs/cloudinary_plugin_asset_cache_filters.html new file mode 100644 index 000000000..9087362d5 --- /dev/null +++ b/docs/cloudinary_plugin_asset_cache_filters.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_plugin_asset_cache_filters - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_plugin_asset_cache_filters

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_plugin_asset_cache_filters', $default_filters ) → {array}

+ + + + + +
+ Filter types of files that can be cached. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$default_filters + + +array + + + + The types of files to be filtered.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • array()
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_plugin_asset_cache_inline_types.html b/docs/cloudinary_plugin_asset_cache_inline_types.html new file mode 100644 index 000000000..9ee5638bb --- /dev/null +++ b/docs/cloudinary_plugin_asset_cache_inline_types.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_plugin_asset_cache_inline_types - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_plugin_asset_cache_inline_types

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_plugin_asset_cache_inline_types', $inline_types ) → {array}

+ + + + + +
+ Filter types of files that can replaced inline with a base64 encoded data URI. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$inline_types + + +array + + + + The types of files to be encoded inline.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • array()
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_prepare_size.html b/docs/cloudinary_prepare_size.html new file mode 100644 index 000000000..4de7b0dff --- /dev/null +++ b/docs/cloudinary_prepare_size.html @@ -0,0 +1,246 @@ + + + + + Filter: cloudinary_prepare_size - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_prepare_size

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_prepare_size', $size, $attachment_id ) → {array|string}

+ + + + + +
+ Filter Cloudinary size and crops +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$size + + +array +| + +string + + + + The size array or slug.
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array +| + +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_preview_types.html b/docs/cloudinary_preview_types.html new file mode 100644 index 000000000..a44f2fe25 --- /dev/null +++ b/docs/cloudinary_preview_types.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_preview_types - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_preview_types

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_preview_types', $base_types ) → {array}

+ + + + + +
+ Filter the file types that are preview only. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$base_types + + +array + + + + The base preview types.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • array( 'pdf', 'psd' )
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_queue_action.html b/docs/cloudinary_queue_action.html new file mode 100644 index 000000000..9d6d339c2 --- /dev/null +++ b/docs/cloudinary_queue_action.html @@ -0,0 +1,1610 @@ + + + + + Action: cloudinary_queue_action - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_queue_action

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_queue_action', $action_message )

+ + + + + +
+ Do action on queue action. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$action_message + + +string + + + + The message.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_queue_action', $action_message, $thread )

+ + + + + +
+ Do action on queue action. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$action_message + + +string + + + + The message.
$thread + + +string + + + + The thread.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_queue_action', $action_message, $thread )

+ + + + + +
+ Do action on queue action. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$action_message + + +string + + + + The message.
$thread + + +string + + + + The thread.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_queue_action', $action_message )

+ + + + + +
+ Do action on queue action. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$action_message + + +string + + + + The message.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_queue_action', $action_message )

+ + + + + +
+ Do action on queue action. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$action_message + + +string + + + + The message.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_queue_action', $action_message )

+ + + + + +
+ Do action on queue action. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$action_message + + +string + + + + The message.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_queue_action', $action_message )

+ + + + + +
+ Do action on queue action. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$action_message + + +string + + + + The message.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_queue_action', $action_message, $thread )

+ + + + + +
+ Do action on queue action. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$action_message + + +string + + + + The message.
$thread + + +string + + + + The thread.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_queue_action' )

+ + + + + +
+ Do action on queue action. +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_queue_action', $action_message, $thread )

+ + + + + +
+ Do action on queue action. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$action_message + + +string + + + + The message.
$thread + + +string + + + + The thread.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_queue_threads.html b/docs/cloudinary_queue_threads.html new file mode 100644 index 000000000..7de42fcdc --- /dev/null +++ b/docs/cloudinary_queue_threads.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_queue_threads - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_queue_threads

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_queue_threads', $count ) → {int}

+ + + + + +
+ Filter the amount of background threads to process for manual syncing. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$count + + +int + + + + The number of manual sync threads to use.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 2
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +int + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_raw_url.html b/docs/cloudinary_raw_url.html new file mode 100644 index 000000000..56f0e982c --- /dev/null +++ b/docs/cloudinary_raw_url.html @@ -0,0 +1,249 @@ + + + + + Filter: cloudinary_raw_url - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_raw_url

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_raw_url', $url, $attachment_id ) → {string|false}

+ + + + + +
+ Filter a base Cloudinary URL (no transformations). +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$url + + +string +| + +false + + + + The local URL
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string +| + +false + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_ready.html b/docs/cloudinary_ready.html new file mode 100644 index 000000000..043fb70c8 --- /dev/null +++ b/docs/cloudinary_ready.html @@ -0,0 +1,202 @@ + + + + + Action: cloudinary_ready - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_ready

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_ready', $plugin )

+ + + + + +
+ Action indicating that the Cloudinary is ready and setup. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$plugin + + +Plugin + + + + The core plugin object.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_register_extensions.html b/docs/cloudinary_register_extensions.html new file mode 100644 index 000000000..1f5fc275f --- /dev/null +++ b/docs/cloudinary_register_extensions.html @@ -0,0 +1,243 @@ + + + + + Filter: cloudinary_register_extensions - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_register_extensions

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_register_extensions', $extensions, $plugin ) → {array}

+ + + + + +
+ Filter to register extensions. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$extensions + + +array + + + + The list of extensions to register.
$plugin + + +Plugin + + + + The core plugin object.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_register_sync_types.html b/docs/cloudinary_register_sync_types.html new file mode 100644 index 000000000..d53942adc --- /dev/null +++ b/docs/cloudinary_register_sync_types.html @@ -0,0 +1,199 @@ + + + + + Action: cloudinary_register_sync_types - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_register_sync_types

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_register_sync_types', $this )

+ + + + + +
+ Do action for setting up sync types. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$this + + +Sync + + + + The sync object.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_registered_sizes.html b/docs/cloudinary_registered_sizes.html new file mode 100644 index 000000000..54de9b4d7 --- /dev/null +++ b/docs/cloudinary_registered_sizes.html @@ -0,0 +1,370 @@ + + + + + Action: cloudinary_registered_sizes - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_registered_sizes

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_registered_sizes', array )

+ + + + + +
+ Filter the all sizes available. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
array + + $all_sizes All the registered sizes.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.3
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_registered_sizes', $demo_files ) → {array}

+ + + + + +
+ Filter the demo files. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$demo_files + + +array + + + + array of demo files.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.3
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_resource_type.html b/docs/cloudinary_resource_type.html new file mode 100644 index 000000000..47ed06d97 --- /dev/null +++ b/docs/cloudinary_resource_type.html @@ -0,0 +1,240 @@ + + + + + Filter: cloudinary_resource_type - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_resource_type

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_resource_type', $type, $attachment_id ) → {string}

+ + + + + +
+ Filter the Cloudinary resource type for the attachment. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$type + + +string + + + + The type.
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_responsive_images_bypass_formats.html b/docs/cloudinary_responsive_images_bypass_formats.html new file mode 100644 index 000000000..a9d775de0 --- /dev/null +++ b/docs/cloudinary_responsive_images_bypass_formats.html @@ -0,0 +1,197 @@ + + + + + Action: cloudinary_responsive_images_bypass_formats - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_responsive_images_bypass_formats

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_responsive_images_bypass_formats', $formats )

+ + + + + +
+ Filter out file formats for Responsive Images. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$formats + + {array) The list of formats to exclude.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.9
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_rest_url.html b/docs/cloudinary_rest_url.html new file mode 100644 index 000000000..4751586d4 --- /dev/null +++ b/docs/cloudinary_rest_url.html @@ -0,0 +1,266 @@ + + + + + Filter: cloudinary_rest_url - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_rest_url

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_rest_url', $rest_url, $path, $scheme ) → {string}

+ + + + + +
+ Filter the rest url. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$rest_url + + +string + + + + The rest url.
$path + + +string + + + + The path to be appended to the rest URL.
$scheme + + +string + + + + The scheme to give the rest URL context. Accepts 'http', 'https', or 'relative'.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.2
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_seo_public_id.html b/docs/cloudinary_seo_public_id.html new file mode 100644 index 000000000..27d50e9b1 --- /dev/null +++ b/docs/cloudinary_seo_public_id.html @@ -0,0 +1,266 @@ + + + + + Filter: cloudinary_seo_public_id - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_seo_public_id

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_seo_public_id', $sufix, $relationship, $attachment_id ) → {string}

+ + + + + +
+ Filter the SEO public ID. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$sufix + + +string + + + + The public_id suffix.
$relationship + + +Relationship + + + + The relationship.
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.5
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_set_usable_asset.html b/docs/cloudinary_set_usable_asset.html new file mode 100644 index 000000000..107d9647c --- /dev/null +++ b/docs/cloudinary_set_usable_asset.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_set_usable_asset - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_set_usable_asset

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_set_usable_asset', $item ) → {array}

+ + + + + +
+ Filter the found item to allow usability to be altered. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$item + + +array + + + + The found asset array.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.2
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_setting_get_value.html b/docs/cloudinary_setting_get_value.html new file mode 100644 index 000000000..8bf3d9d2d --- /dev/null +++ b/docs/cloudinary_setting_get_value.html @@ -0,0 +1,240 @@ + + + + + Filter: cloudinary_setting_get_value - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_setting_get_value

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_setting_get_value', $value, $slug ) → {mixed}

+ + + + + +
+ Filter the setting value. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$value + + +mixed + + + + The setting value.
$slug + + +string + + + + The setting slug.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +mixed + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_settings_save_setting.html b/docs/cloudinary_settings_save_setting.html new file mode 100644 index 000000000..7efd42bf6 --- /dev/null +++ b/docs/cloudinary_settings_save_setting.html @@ -0,0 +1,266 @@ + + + + + Filter: cloudinary_settings_save_setting - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_settings_save_setting

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_settings_save_setting', $new_value, $current_value, $setting ) → {mixed}

+ + + + + +
+ Pre-Filter the value before saving a setting. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$new_value + + +int + + + + The new setting value.
$current_value + + +string + + + + The setting current value.
$setting + + +Setting + + + + The setting object.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.6
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +mixed + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_setup_component_parts.html b/docs/cloudinary_setup_component_parts.html new file mode 100644 index 000000000..26dcdfc98 --- /dev/null +++ b/docs/cloudinary_setup_component_parts.html @@ -0,0 +1,240 @@ + + + + + Filter: cloudinary_setup_component_parts - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_setup_component_parts

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_setup_component_parts', $build_parts, $this ) → {array}

+ + + + + +
+ Filter the components build parts. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$build_parts + + +array + + + + The build parts.
$this + + +self + + + + The component object.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_site_url.html b/docs/cloudinary_site_url.html new file mode 100644 index 000000000..41c9d9892 --- /dev/null +++ b/docs/cloudinary_site_url.html @@ -0,0 +1,266 @@ + + + + + Filter: cloudinary_site_url - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_site_url

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_site_url', $site_url, $path, $scheme ) → {string}

+ + + + + +
+ Filter the site URL. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$site_url + + +string + + + + The site URL.
$path + + +string + + + + The path to be appended to the site URL.
$scheme + + +string + + + + The scheme to give the site URL context. Accepts 'http', 'https', or 'relative'.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.2
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_skip_parse_element.html b/docs/cloudinary_skip_parse_element.html new file mode 100644 index 000000000..481ba3d31 --- /dev/null +++ b/docs/cloudinary_skip_parse_element.html @@ -0,0 +1,248 @@ + + + + + Filter: cloudinary_skip_parse_element - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_skip_parse_element

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_skip_parse_element', $skip, $element ) → {bool}

+ + + + + +
+ Filter to skip parsing an element. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$skip + + +bool + + + + True to skip parsing.
$element + + +string + + + + The element to parse.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.6
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • {false}
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_skip_responsive_breakpoints.html b/docs/cloudinary_skip_responsive_breakpoints.html new file mode 100644 index 000000000..604edf1e4 --- /dev/null +++ b/docs/cloudinary_skip_responsive_breakpoints.html @@ -0,0 +1,248 @@ + + + + + Filter: cloudinary_skip_responsive_breakpoints - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_skip_responsive_breakpoints

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_skip_responsive_breakpoints', $skip_features, $tag_element ) → {bool}

+ + + + + +
+ Do not add the responsive breakpoint features. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$skip_features + + +bool + + + + True to skip adding in the features.
$tag_element + + +array + + + + The tag element.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.4
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • {false}
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_string_replace.html b/docs/cloudinary_string_replace.html new file mode 100644 index 000000000..d15cecc88 --- /dev/null +++ b/docs/cloudinary_string_replace.html @@ -0,0 +1,225 @@ + + + + + Action: cloudinary_string_replace - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_string_replace

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_string_replace', $content, $context )

+ + + + + +
+ Do replacement action. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$content + + +string + + + + The html of the page.
$context + + +string + + + + The render context.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.3 Added the `$context` argument.
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_sync_base.html b/docs/cloudinary_sync_base.html new file mode 100644 index 000000000..284cdc383 --- /dev/null +++ b/docs/cloudinary_sync_base.html @@ -0,0 +1,263 @@ + + + + + Filter: cloudinary_sync_base - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_sync_base

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_sync_base', $signatures, $post, $sync ) → {array}

+ + + + + +
+ Filter the sync base to allow other plugins to add requested sync components for the sync signature. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$signatures + + +array + + + + The attachments required signatures.
$post + + +WP_Post + + + + The attachment post.
$sync + + +Sync + + + + The sync object instance.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_sync_base_struct.html b/docs/cloudinary_sync_base_struct.html new file mode 100644 index 000000000..c6c8b3517 --- /dev/null +++ b/docs/cloudinary_sync_base_struct.html @@ -0,0 +1,217 @@ + + + + + Filter: cloudinary_sync_base_struct - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_sync_base_struct

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_sync_base_struct', $base_struct ) → {array}

+ + + + + +
+ Filter the sync base structure to allow other plugins to sync component callbacks. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$base_struct + + +array + + + + The base sync structure.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_syncable_delivery_types.html b/docs/cloudinary_syncable_delivery_types.html new file mode 100644 index 000000000..088a1cd29 --- /dev/null +++ b/docs/cloudinary_syncable_delivery_types.html @@ -0,0 +1,222 @@ + + + + + Filter: cloudinary_syncable_delivery_types - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_syncable_delivery_types

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_syncable_delivery_types', $types ) → {array}

+ + + + + +
+ Filter the delivery types that are able to sync. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$types + + +array + + + + The default syncable types.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • array( 'upload' )
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_tag_skip_classes.html b/docs/cloudinary_tag_skip_classes.html new file mode 100644 index 000000000..bb08a4d54 --- /dev/null +++ b/docs/cloudinary_tag_skip_classes.html @@ -0,0 +1,230 @@ + + + + + Action: cloudinary_tag_skip_classes - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_tag_skip_classes

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_tag_skip_classes', $skip, $tag_element )

+ + + + + +
+ Maybe skip the classes for th tag element. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$skip + + +bool + + + + True to unset attributes.
$tag_element + + +array + + + + The tag element.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.4
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • {false}
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_task_capability.html b/docs/cloudinary_task_capability.html new file mode 100644 index 000000000..488838054 --- /dev/null +++ b/docs/cloudinary_task_capability.html @@ -0,0 +1,307 @@ + + + + + Filter: cloudinary_task_capability - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_task_capability

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_task_capability', $capability, $task, $context, $args ) → {string}

+ + + + + +
+ Filter the capability required for Cloudinary tasks. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$capability + + +string + + + + The current capability for the task.
$task + + +string + + + + The task.
$context + + +string + + + + The context for the task.
$args + + +mixed + + + + The optional arguments.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.6. In 3.0.6 $context and $args added.
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + +
Example
+ +
<?php
+
+// Enforce `manage_options` to download an asset from Cloudinary.
+add_filter(
+    'cloudinary_task_capability',
+    function( $capability, $task, $context ) {
+        if ( 'manage_assets' === $task && 'download' === $context ) {
+            $capability = 'manage_options';
+        }
+        return $capability;
+    },
+    10,
+    3
+);
+ + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_task_capability_%7Btask%7D.html b/docs/cloudinary_task_capability_%7Btask%7D.html new file mode 100644 index 000000000..105d22f28 --- /dev/null +++ b/docs/cloudinary_task_capability_%7Btask%7D.html @@ -0,0 +1,289 @@ + + + + + Filter: cloudinary_task_capability_{task} - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_task_capability_{task}

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_task_capability_{task}', $capability, $context, $args ) → {string}

+ + + + + +
+ Filter the capability required for a specific Cloudinary task. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$capability + + +string + + + + The capability.
$context + + +string + + + + The context for the task.
$args + + +mixed + + + + The optional arguments.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.6. In 3.0.6 $context and $args added.
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • 'manage_options'
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + +
Example
+ +
<?php
+
+// Enforce `manage_options` to download an asset from Cloudinary.
+add_filter(
+    'cloudinary_task_capability_manage_assets',
+    function( $task, $context ) {
+        if ( 'download' === $context ) {
+            $capability = 'manage_options';
+        }
+        return $capability;
+    },
+    10,
+    2
+);
+ + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_thread_queue_details_query.html b/docs/cloudinary_thread_queue_details_query.html new file mode 100644 index 000000000..c5865ea2b --- /dev/null +++ b/docs/cloudinary_thread_queue_details_query.html @@ -0,0 +1,243 @@ + + + + + Filter: cloudinary_thread_queue_details_query - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_thread_queue_details_query

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_thread_queue_details_query', $args, $thread ) → {array}

+ + + + + +
+ Filter the params for the query used to get thread queue details. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$args + + +array + + + + The arguments for the query.
$thread + + +string + + + + The thread name.
+ + + + + + +
+ + + + +
Since:
+
  • 2.7.6
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_transformations.html b/docs/cloudinary_transformations.html new file mode 100644 index 000000000..3b82d527e --- /dev/null +++ b/docs/cloudinary_transformations.html @@ -0,0 +1,240 @@ + + + + + Filter: cloudinary_transformations - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_transformations

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_transformations', $transformations, $attachment_id ) → {array}

+ + + + + +
+ Filter the Cloudinary transformations. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$transformations + + +array + + + + Array of transformation options.
$attachment_id + + +int + + + + The id of the asset.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_unset_attributes.html b/docs/cloudinary_unset_attributes.html new file mode 100644 index 000000000..b117f106b --- /dev/null +++ b/docs/cloudinary_unset_attributes.html @@ -0,0 +1,230 @@ + + + + + Action: cloudinary_unset_attributes - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_unset_attributes

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_unset_attributes', $unset, $tag_element )

+ + + + + +
+ Unset the attributes to avoid block errors in the admin. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$unset + + +bool + + + + True to unset attributes.
$tag_element + + +array + + + + The tag element.
+ + + + + + +
+ + + + +
Since:
+
  • 3.2.4
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • {false}
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_unsync_asset.html b/docs/cloudinary_unsync_asset.html new file mode 100644 index 000000000..224bb3d93 --- /dev/null +++ b/docs/cloudinary_unsync_asset.html @@ -0,0 +1,202 @@ + + + + + Action: cloudinary_unsync_asset - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_unsync_asset

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_unsync_asset', $attachment_id )

+ + + + + +
+ Action unsyncing an attachment. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.0
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_upgrade_asset.html b/docs/cloudinary_upgrade_asset.html new file mode 100644 index 000000000..553afd579 --- /dev/null +++ b/docs/cloudinary_upgrade_asset.html @@ -0,0 +1,225 @@ + + + + + Action: cloudinary_upgrade_asset - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_upgrade_asset

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_upgrade_asset', $attachment_id, $version )

+ + + + + +
+ Action to trigger an upgrade on a synced asset. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$attachment_id + + +int + + + + The attachment ID.
$version + + +string + + + + The current plugin version.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.5
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_upgrade_sequence.html b/docs/cloudinary_upgrade_sequence.html new file mode 100644 index 000000000..2d851004f --- /dev/null +++ b/docs/cloudinary_upgrade_sequence.html @@ -0,0 +1,220 @@ + + + + + Filter: cloudinary_upgrade_sequence - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_upgrade_sequence

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_upgrade_sequence', $upgrade_sequence ) → {array}

+ + + + + +
+ Filter the upgrade sequence. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$upgrade_sequence + + +array + + + + The default sequence.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.1
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_upload_args.html b/docs/cloudinary_upload_args.html new file mode 100644 index 000000000..cb8d8197b --- /dev/null +++ b/docs/cloudinary_upload_args.html @@ -0,0 +1,225 @@ + + + + + Action: cloudinary_upload_args - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_upload_args

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_upload_args', $call_args, $attachment_id )

+ + + + + +
+ Filter Cloudinary upload args. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$call_args + + +array + + + + The default args.
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.1
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_upload_eager_formats.html b/docs/cloudinary_upload_eager_formats.html new file mode 100644 index 000000000..ad8c06836 --- /dev/null +++ b/docs/cloudinary_upload_eager_formats.html @@ -0,0 +1,243 @@ + + + + + Filter: cloudinary_upload_eager_formats - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_upload_eager_formats

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_upload_eager_formats', $formats, $type ) → {array}

+ + + + + +
+ Filter the upload eager formats. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$formats + + +array + + + + The default formats.
$type + + +string + + + + The asset type.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.6
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_upload_options.html b/docs/cloudinary_upload_options.html new file mode 100644 index 000000000..e17b8b2d4 --- /dev/null +++ b/docs/cloudinary_upload_options.html @@ -0,0 +1,263 @@ + + + + + Filter: cloudinary_upload_options - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_upload_options

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_upload_options', $options, $post, $this ) → {array}

+ + + + + +
+ Filter the options to allow other plugins to add requested options for uploading. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$options + + +array + + + + The options array.
$post + + +WP_Post + + + + The attachment post.
$this + + +Media + + + + The media object instance.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +array + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_uploaded_asset.html b/docs/cloudinary_uploaded_asset.html new file mode 100644 index 000000000..739321f86 --- /dev/null +++ b/docs/cloudinary_uploaded_asset.html @@ -0,0 +1,228 @@ + + + + + Action: cloudinary_uploaded_asset - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_uploaded_asset

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_uploaded_asset', $attachment_id, $result )

+ + + + + +
+ Action after uploaded asset. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$attachment_id + + +int + + + + The attachment ID.
$result + + +array +| + +WP_Error + + + + The upload result.
+ + + + + + +
+ + + + +
Since:
+
  • 3.0.1
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_use_original_image.html b/docs/cloudinary_use_original_image.html new file mode 100644 index 000000000..dca7504c2 --- /dev/null +++ b/docs/cloudinary_use_original_image.html @@ -0,0 +1,248 @@ + + + + + Filter: cloudinary_use_original_image - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_use_original_image

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_use_original_image', $use_original, $attachment_id ) → {bool}

+ + + + + +
+ Whether to use the original image URL. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$use_original + + +bool + + + + The default value.
$attachment_id + + +int + + + + The attachment ID.
+ + + + + + +
+ + + + +
Since:
+
  • 3.1.8
+ + + + + + + + + + + + + + + + + + + + + +
Default Value:
+
    +
  • true
  • +
+ + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_validate_cloudinary_id.html b/docs/cloudinary_validate_cloudinary_id.html new file mode 100644 index 000000000..07a92c5e0 --- /dev/null +++ b/docs/cloudinary_validate_cloudinary_id.html @@ -0,0 +1,246 @@ + + + + + Filter: cloudinary_validate_cloudinary_id - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Filter: cloudinary_validate_cloudinary_id

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

apply_filters( 'cloudinary_validate_cloudinary_id', $cloudinary_id, $attachment_id ) → {string|bool}

+ + + + + +
+ Filter to validate the Cloudinary ID to allow extending it's availability. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$cloudinary_id + + +string +| + +bool + + + + The public ID from Cloudinary, or false if not found.
$attachment_id + + +int + + + + The id of the asset.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string +| + +bool + + +
+
+ + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/cloudinary_version_upgrade.html b/docs/cloudinary_version_upgrade.html new file mode 100644 index 000000000..65e5424e6 --- /dev/null +++ b/docs/cloudinary_version_upgrade.html @@ -0,0 +1,225 @@ + + + + + Action: cloudinary_version_upgrade - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Action: cloudinary_version_upgrade

+ + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + +

do_action( 'cloudinary_version_upgrade', $new_version, $old_version )

+ + + + + +
+ Do action to allow upgrading of different areas. +
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
$new_version + + +string + + + + The version upgrading to.
$old_version + + +string + + + + The version upgrading from.
+ + + + + + +
+ + + + +
Since:
+
  • 2.3.1
+ + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Bold-webfont.eot b/docs/fonts/OpenSans-Bold-webfont.eot new file mode 100644 index 000000000..5d20d9163 Binary files /dev/null and b/docs/fonts/OpenSans-Bold-webfont.eot differ diff --git a/docs/fonts/OpenSans-Bold-webfont.svg b/docs/fonts/OpenSans-Bold-webfont.svg new file mode 100644 index 000000000..3ed7be4bc --- /dev/null +++ b/docs/fonts/OpenSans-Bold-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Bold-webfont.woff b/docs/fonts/OpenSans-Bold-webfont.woff new file mode 100644 index 000000000..1205787b0 Binary files /dev/null and b/docs/fonts/OpenSans-Bold-webfont.woff differ diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.eot b/docs/fonts/OpenSans-BoldItalic-webfont.eot new file mode 100644 index 000000000..1f639a15f Binary files /dev/null and b/docs/fonts/OpenSans-BoldItalic-webfont.eot differ diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.svg b/docs/fonts/OpenSans-BoldItalic-webfont.svg new file mode 100644 index 000000000..6a2607b9d --- /dev/null +++ b/docs/fonts/OpenSans-BoldItalic-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.woff b/docs/fonts/OpenSans-BoldItalic-webfont.woff new file mode 100644 index 000000000..ed760c062 Binary files /dev/null and b/docs/fonts/OpenSans-BoldItalic-webfont.woff differ diff --git a/docs/fonts/OpenSans-Italic-webfont.eot b/docs/fonts/OpenSans-Italic-webfont.eot new file mode 100644 index 000000000..0c8a0ae06 Binary files /dev/null and b/docs/fonts/OpenSans-Italic-webfont.eot differ diff --git a/docs/fonts/OpenSans-Italic-webfont.svg b/docs/fonts/OpenSans-Italic-webfont.svg new file mode 100644 index 000000000..e1075dcc2 --- /dev/null +++ b/docs/fonts/OpenSans-Italic-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Italic-webfont.woff b/docs/fonts/OpenSans-Italic-webfont.woff new file mode 100644 index 000000000..ff652e643 Binary files /dev/null and b/docs/fonts/OpenSans-Italic-webfont.woff differ diff --git a/docs/fonts/OpenSans-Light-webfont.eot b/docs/fonts/OpenSans-Light-webfont.eot new file mode 100644 index 000000000..14868406a Binary files /dev/null and b/docs/fonts/OpenSans-Light-webfont.eot differ diff --git a/docs/fonts/OpenSans-Light-webfont.svg b/docs/fonts/OpenSans-Light-webfont.svg new file mode 100644 index 000000000..11a472ca8 --- /dev/null +++ b/docs/fonts/OpenSans-Light-webfont.svg @@ -0,0 +1,1831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Light-webfont.woff b/docs/fonts/OpenSans-Light-webfont.woff new file mode 100644 index 000000000..e78607481 Binary files /dev/null and b/docs/fonts/OpenSans-Light-webfont.woff differ diff --git a/docs/fonts/OpenSans-LightItalic-webfont.eot b/docs/fonts/OpenSans-LightItalic-webfont.eot new file mode 100644 index 000000000..8f445929f Binary files /dev/null and b/docs/fonts/OpenSans-LightItalic-webfont.eot differ diff --git a/docs/fonts/OpenSans-LightItalic-webfont.svg b/docs/fonts/OpenSans-LightItalic-webfont.svg new file mode 100644 index 000000000..431d7e354 --- /dev/null +++ b/docs/fonts/OpenSans-LightItalic-webfont.svg @@ -0,0 +1,1835 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-LightItalic-webfont.woff b/docs/fonts/OpenSans-LightItalic-webfont.woff new file mode 100644 index 000000000..43e8b9e6c Binary files /dev/null and b/docs/fonts/OpenSans-LightItalic-webfont.woff differ diff --git a/docs/fonts/OpenSans-Regular-webfont.eot b/docs/fonts/OpenSans-Regular-webfont.eot new file mode 100644 index 000000000..6bbc3cf58 Binary files /dev/null and b/docs/fonts/OpenSans-Regular-webfont.eot differ diff --git a/docs/fonts/OpenSans-Regular-webfont.svg b/docs/fonts/OpenSans-Regular-webfont.svg new file mode 100644 index 000000000..25a395234 --- /dev/null +++ b/docs/fonts/OpenSans-Regular-webfont.svg @@ -0,0 +1,1831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Regular-webfont.woff b/docs/fonts/OpenSans-Regular-webfont.woff new file mode 100644 index 000000000..e231183dc Binary files /dev/null and b/docs/fonts/OpenSans-Regular-webfont.woff differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000..606187743 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,167 @@ + + + + + Home - Cloudinary Docs + + + + + + + + + + + + +
+ + + + + + + + +
+ +
+

+ +
+ +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+

Cloudinary's WordPress Plugin

+

Cloudinary is a cloud service that offers a solution to a web application's entire image and video management pipeline. +With Cloudinary, all your images are automatically uploaded, normalized, optimized and backed-up in the cloud instead of being hosted on your servers.

+

With Cloudinary, you can stop messing around with image editors. Cloudinary can manipulate and transform your images online, on-the-fly, directly from your WordPress console. Enhance your images using every possible filter and effect you can think of. All manipulations are done in the cloud using super-powerful hardware, and all resulting images are cached, optimized (smushed and more) and delivered via a lightning fast content delivery network (CDN).

+

WordPress Plugin

+

The plugin is available for installation via WordPress plugins directory. +The plugin is publicly available at: https://wordpress.org/plugins/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/

+

This Git repository is the development repository, while there's a mirror public SVN repository of the actual released WordPress plugin version: https://plugins.svn.wordpress.org/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/

+
+

Deprecation Note +The legacy WordPress Plugin version (v1.x) will be deprecated as of February 1st, 2021, after which support, updates and bug fixes for the legacy plugin will continue in limited fashion. +The legacy plugin will be made obsolete on August 1st, 2021 (end-of-life date), meaning, Version 1.x of the plugin will no longer function after that date. +We ask that you update to our latest WordPress Plugin v2.x before the August 1st deadline.

+
+

Additional resources

+

Additional resources are available at:

+ +

Support

+

You can open an issue through GitHub.

+

Contact us https://cloudinary.com/contact

+

Stay tuned for updates, tips and tutorials: Blog, Twitter, Facebook.

+

Development

+

Create a Plugin Release Package

+

Run npm run package to create the plugin release in the /build directory and package it as cloudinary-image-management-and-manipulation-in-the-cloud-cdn.zip in the root directory.

+

Files included in the release package are defined in the gruntfile.js under the copy task. Be sure to update this list of files and directories when you add new files to the project.

+

Deployment to WordPress.org

+
    +
  1. +

    Tag a release from the master branch on GitHub.

    +
  2. +
  3. +

    Run npm run deploy to deploy the version referenced in the cloudinary.php file of the current branch.

    +
  4. +
  5. +

    Run npm run deploy-assets to deploy just the WP.org plugin assets such as screenshots, icons and banners.

    +
  6. +
+

License

+

Released under the GPL license.

+
+ + + + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/php_cache_class-cache-point.php.html b/docs/php_cache_class-cache-point.php.html new file mode 100644 index 000000000..b4c05d63f --- /dev/null +++ b/docs/php_cache_class-cache-point.php.html @@ -0,0 +1,1042 @@ + + + + + Source: php/cache/class-cache-point.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/cache/class-cache-point.php

+ + + + + + + +
+
+
<?php
+/**
+ * Handles cache point management.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Cache;
+
+use Cloudinary\Cache;
+use Cloudinary\Cache\Cache_Controller;
+
+/**
+ * Class Cache Point.
+ *
+ * Handles managing cache points.
+ */
+class Cache_Point {
+
+	/**
+	 * The plugin instance.
+	 *
+	 * @var Cache
+	 */
+	protected $cache;
+
+	/**
+	 * Holds the list of active cache_points.
+	 *
+	 * @var \WP_Post[]
+	 */
+	protected $active_cache_points = array();
+
+	/**
+	 * Holds a list of pre-found cached urls before querying to find cached items
+	 *
+	 * @var array.
+	 */
+	protected $pre_cached = array();
+	/**
+	 * Holds the list of registered cache_points.
+	 *
+	 * @var \WP_Post[]
+	 */
+	protected $registered_cache_points = array();
+
+	/**
+	 * Holds the list of cache points requiring meta updates.
+	 *
+	 * @var array
+	 */
+	public $meta_updates = array();
+
+	/**
+	 * Post type.
+	 *
+	 * @var \WP_Post_Type
+	 */
+	protected $post_type;
+
+	/**
+	 * Holds the post type.
+	 */
+	const POST_TYPE_SLUG = 'cloudinary_asset';
+
+	/**
+	 * Holds the list of items to upload.
+	 *
+	 * @var array
+	 */
+	protected $to_upload = array();
+
+	/**
+	 * Holds the limit of items to sync per visitor.
+	 *
+	 * @var int
+	 */
+	protected $sync_limit;
+
+	/**
+	 * Holds the meta keys.
+	 *
+	 * @var array
+	 */
+	const META_KEYS = array(
+		'excluded_urls' => 'excluded_urls',
+		'cached_urls'   => 'cached_urls',
+		'src_path'      => 'src_path',
+		'url'           => 'url',
+		'base_url'      => 'base_url',
+		'src_file'      => 'src_file',
+		'last_updated'  => 'last_updated',
+		'upload_error'  => 'upload_error',
+		'version'       => 'version',
+	);
+
+	/**
+	 * Cache Point constructor.
+	 *
+	 * @param Cache $cache The plugin ache object.
+	 */
+	public function __construct( Cache $cache ) {
+		$this->cache = $cache;
+		/**
+		 * Filter the on demand synced items limit.
+		 *
+		 * @hook    cloudinary_on_demand_sync_limit
+		 * @default 100
+		 *
+		 * @param $value {int} The default number of static assets.
+		 *
+		 * @return {int}
+		 *
+		 * @since 2.8.0
+		 */
+		$this->sync_limit = apply_filters( 'cloudinary_on_demand_sync_limit', 100 );
+		$this->register_post_type();
+
+		add_filter( 'update_post_metadata', array( $this, 'update_meta' ), 10, 4 );
+		add_filter( 'get_post_metadata', array( $this, 'get_meta' ), 10, 3 );
+		add_filter( 'delete_post_metadata', array( $this, 'delete_meta' ), 10, 4 );
+		add_action( 'shutdown', array( $this, 'meta_updates' ) );
+		add_action( 'wp_resource_hints', array( $this, 'dns_prefetch' ), 10, 2 );
+	}
+
+	/**
+	 * Add DNS prefetch link tag for assets.
+	 *
+	 * @param array  $urls          URLs to print for resource hints.
+	 * @param string $relation_type The relation type the URLs are printed for, e.g. 'preconnect' or 'prerender'.
+	 *
+	 * @return array
+	 */
+	public function dns_prefetch( $urls, $relation_type ) {
+
+		if ( 'dns-prefetch' === $relation_type && ! empty( $this->active_cache_points ) ) {
+			$urls[] = $this->cache->media->base_url;
+		}
+
+		return $urls;
+	}
+
+	/**
+	 * Update our cache point meta data.
+	 *
+	 * @param null|bool $check      The check to allow short circuit of get_metadata.
+	 * @param int       $object_id  The object ID.
+	 * @param string    $meta_key   The meta key.
+	 * @param mixed     $meta_value The meta value.
+	 *
+	 * @return bool|null
+	 */
+	public function update_meta( $check, $object_id, $meta_key, $meta_value ) {
+
+		if ( self::POST_TYPE_SLUG === get_post_type( $object_id ) ) {
+			$check = true;
+			$meta  = $this->get_meta_cache( $object_id );
+			if ( ! isset( $meta[ $meta_key ] ) || $meta_value !== $meta[ $meta_key ] ) {
+				$meta[ $meta_key ] = $meta_value;
+				$check             = $this->set_meta_cache( $object_id, $meta );
+			}
+		}
+
+		return $check;
+	}
+
+	/**
+	 * Delete our cache point meta data.
+	 *
+	 * @param null|bool $check      The check to allow short circuit of get_metadata.
+	 * @param int       $object_id  The object ID.
+	 * @param string    $meta_key   The meta key.
+	 * @param mixed     $meta_value The meta value.
+	 *
+	 * @return bool
+	 */
+	public function delete_meta( $check, $object_id, $meta_key, $meta_value ) {
+
+		if ( self::POST_TYPE_SLUG === get_post_type( $object_id ) ) {
+			$check = false;
+			$meta  = $this->get_meta_cache( $object_id );
+			if ( isset( $meta[ $meta_key ] ) && $meta[ $meta_key ] === $meta_value || is_null( $meta_value ) ) {
+				unset( $meta[ $meta_key ] );
+				$check = $this->set_meta_cache( $object_id, $meta );
+			}
+		}
+
+		return $check;
+	}
+
+	/**
+	 * Get our cache point meta data.
+	 *
+	 * @param null|bool $check     The check to allow short circuit of get_metadata.
+	 * @param int       $object_id The object ID.
+	 * @param string    $meta_key  The meta key.
+	 *
+	 * @return mixed
+	 */
+	public function get_meta( $check, $object_id, $meta_key ) {
+
+		if ( self::POST_TYPE_SLUG === get_post_type( $object_id ) ) {
+			$meta  = $this->get_meta_cache( $object_id );
+			$value = '';
+
+			if ( empty( $meta_key ) ) {
+				$value = $meta;
+			} elseif ( isset( $meta[ $meta_key ] ) ) {
+				$value   = array();
+				$value[] = $meta[ $meta_key ];
+			}
+
+			return $value;
+		}
+
+		return $check;
+	}
+
+	/**
+	 * Get meta data for a cache point.
+	 *
+	 * @param int $object_id The post ID.
+	 *
+	 * @return mixed
+	 */
+	protected function get_meta_cache( $object_id ) {
+		$meta = wp_cache_get( $object_id, 'cloudinary_asset' );
+		if ( ! $meta ) {
+			$post = get_post( $object_id );
+			$meta = json_decode( $post->post_content, true );
+			wp_cache_add( $object_id, $meta, 'cloudinary_asset' );
+		}
+
+		return $meta;
+	}
+
+	/**
+	 * Set meta data for a cache point.
+	 *
+	 * @param int   $object_id The post ID.
+	 * @param mixed $meta      The meta to set.
+	 *
+	 * @return bool
+	 */
+	protected function set_meta_cache( $object_id, $meta ) {
+		if ( ! in_array( $object_id, $this->meta_updates, true ) ) {
+			$this->meta_updates[] = $object_id;
+		}
+
+		return wp_cache_replace( $object_id, $meta, 'cloudinary_asset' );
+	}
+
+	/**
+	 * Compiles all metadata and preps upload at shutdown.
+	 */
+	public function meta_updates() {
+		foreach ( $this->meta_updates as $id ) {
+			$meta   = $this->get_meta_cache( $id );
+			$params = array(
+				'ID'           => $id,
+				'post_content' => wp_json_encode( $meta ),
+			);
+			wp_update_post( $params );
+		}
+		// Prep the upload for un-synced items.
+		if ( ! empty( $this->to_upload ) ) {
+			$api = $this->cache->plugin->get_component( 'api' );
+			if ( $api ) {
+				$api->background_request( 'upload_cache', array( 'ids' => $this->to_upload ), 'POST' );
+			}
+		}
+	}
+
+	/**
+	 * Init the cache_points.
+	 */
+	public function init() {
+		$params = array(
+			'post_type'              => self::POST_TYPE_SLUG,
+			'post_status'            => array( 'enabled', 'disabled' ),
+			'post_parent'            => 0,
+			'posts_per_page'         => 100,
+			'no_found_rows'          => true,
+			'update_post_meta_cache' => false,
+			'update_post_term_cache' => false,
+		);
+		$query  = new \WP_Query( $params );
+		foreach ( $query->get_posts() as $post ) {
+			$this->registered_cache_points[ $post->post_title ] = $post;
+		}
+		do_action( 'cloudinary_cache_init_cache_points' );
+
+	}
+
+	/**
+	 * Checks if the cache point is registered.
+	 *
+	 * @param string $url the URL to check.
+	 *
+	 * @return bool
+	 */
+	protected function is_registered( $url ) {
+		$url = trailingslashit( $url );
+
+		return isset( $this->registered_cache_points[ $url ] );
+	}
+
+	/**
+	 * Register a cache path.
+	 *
+	 * @param string $url      The URL to register.
+	 * @param string $src_path The source path to register.
+	 * @param string $version  The version of the cache point.
+	 */
+	public function register_cache_path( $url, $src_path, $version ) {
+		$this->create_cache_point( $url, $src_path, $version );
+		$this->activate_cache_point( $url );
+	}
+
+	/**
+	 * Enable a cache path.
+	 *
+	 * @param string $url The path to enable.
+	 */
+	public function activate_cache_point( $url ) {
+		$url = trailingslashit( $url );
+		if ( $this->is_registered( $url ) ) {
+			$cache_point                       = $this->registered_cache_points[ $url ];
+			$this->active_cache_points[ $url ] = $cache_point;
+			// Init the metadata.
+			$this->get_meta_cache( $cache_point->ID );
+		}
+	}
+
+	/**
+	 * Add the url to the cache point's exclude list.
+	 *
+	 * @param int    $cache_point_id The cache point ID to add to.
+	 * @param string $url            The url to add.
+	 */
+	public function exclude_url( $cache_point_id, $url ) {
+		$excludes = get_post_meta( $cache_point_id, self::META_KEYS['excluded_urls'], true );
+		if ( empty( $excludes ) ) {
+			$excludes = array();
+		}
+		if ( ! in_array( $url, $excludes, true ) ) {
+			$excludes[] = $url;
+			update_post_meta( $cache_point_id, self::META_KEYS['excluded_urls'], $excludes );
+		}
+	}
+
+	/**
+	 * Add the url to the cache point's exclude list.
+	 *
+	 * @param int    $cache_point_id The cache point ID to add to.
+	 * @param string $url            The url to add.
+	 */
+	public function remove_excluded_url( $cache_point_id, $url ) {
+		$excludes = get_post_meta( $cache_point_id, self::META_KEYS['excluded_urls'], true );
+		if ( ! empty( $excludes ) ) {
+			$index = array_search( $url, (array) $excludes, true );
+			if ( false !== $index ) {
+				unset( $excludes[ $index ] );
+				update_post_meta( $cache_point_id, self::META_KEYS['excluded_urls'], $excludes );
+			}
+		}
+	}
+
+	/**
+	 * Checks if the file url is valid (exists).
+	 *
+	 * @param string $url The url to test.
+	 *
+	 * @return bool
+	 */
+	protected function is_valid_url( $url ) {
+		static $validated_urls = array();
+		if ( isset( $validated_urls[ $url ] ) ) {
+			return $validated_urls[ $url ];
+		}
+		$validated_urls[ $url ] = ! is_null( $this->url_to_path( $url ) );
+
+		return $validated_urls[ $url ];
+	}
+
+	/**
+	 * Get all active cache_points.
+	 *
+	 * @param bool $ids_only Flag to get only the ids.
+	 *
+	 * @return int[]|\WP_Post[]
+	 */
+	public function get_active_cache_points( $ids_only = false ) {
+		$return = $this->active_cache_points;
+		if ( $ids_only ) {
+			$return = array_map(
+				function ( $post ) {
+					return $post->ID;
+				},
+				$return
+			);
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Convert a URl to a path.
+	 *
+	 * @param string $url The URL to convert.
+	 *
+	 * @return string
+	 */
+	public function url_to_path( $url ) {
+		$url      = $this->clean_url( $url );
+		$src_path = $this->cache->file_system->get_src_path( $url );
+		if ( $this->cache->file_system->is_dir( $src_path ) ) {
+			$src_path = trailingslashit( $src_path );
+		}
+
+		return $src_path;
+	}
+
+	/**
+	 * Load a cache point from a url.
+	 *
+	 * @param string $url The cache point url to get.
+	 *
+	 * @return \WP_Post | null
+	 */
+	protected function load_cache_point( $url ) {
+		if ( ! isset( $this->registered_cache_points[ $url ] ) ) {
+			$key         = $this->get_key_name( $url );
+			$url         = trailingslashit( $url );
+			$cache_point = null;
+			$params      = array(
+				'name'             => $key,
+				'post_type'        => self::POST_TYPE_SLUG,
+				'posts_per_page'   => 1,
+				'suppress_filters' => false,
+			);
+			$found       = get_posts( $params ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.get_posts_get_posts
+			if ( ! empty( $found ) ) {
+				$cache_point                           = array_shift( $found );
+				$this->registered_cache_points[ $url ] = $cache_point;
+			}
+		}
+
+		return isset( $this->registered_cache_points[ $url ] ) ? $this->registered_cache_points[ $url ] : null;
+	}
+
+	/**
+	 * Get a cache point from a url.
+	 *
+	 * @param string $url The cache point url to get.
+	 *
+	 * @return \WP_Post
+	 */
+	public function get_cache_point( $url ) {
+		// Lets check if the cache_point is a file.
+		if ( pathinfo( $url, PATHINFO_EXTENSION ) ) {
+			return $this->get_parent_cache_point( $url );
+		}
+		$url         = trailingslashit( $url );
+		$cache_point = null;
+		if ( isset( $this->active_cache_points[ $url ] ) ) {
+			$cache_point = $this->active_cache_points[ $url ];
+		} else {
+			$cache_point = $this->load_cache_point( $url );
+		}
+
+		return $cache_point;
+	}
+
+	/**
+	 * Get the parent cache point for a file URL.
+	 *
+	 * @param string $url The url of the file.
+	 *
+	 * @return \WP_Post|null
+	 */
+	protected function get_parent_cache_point( $url ) {
+		$parent = null;
+		foreach ( $this->active_cache_points as $key => $cache_point ) {
+			if ( false !== strpos( $url, $key ) ) {
+				$excludes = (array) get_post_meta( $cache_point->ID, self::META_KEYS['excluded_urls'], true );
+				if ( ! in_array( $url, $excludes, true ) ) {
+					$parent = $cache_point;
+				}
+				break;
+			}
+		}
+
+		return $parent;
+	}
+
+	/**
+	 * Get all cache items for a cache point.
+	 *
+	 * @param string|int $cache_point_id_url The cache point ID or URL.
+	 * @param bool       $id_only            Flag to get ID's only.
+	 *
+	 * @return \WP_Post[]|int[]
+	 */
+	public function get_cache_items( $cache_point_id_url, $id_only = false ) {
+		$items = array();
+		if ( ! is_int( $cache_point_id_url ) ) {
+			$cache_point = $this->get_cache_point( $cache_point_id_url );
+		} else {
+			$cache_point = get_post( $cache_point_id_url );
+		}
+		if ( ! is_null( $cache_point ) ) {
+
+			$params = array(
+				'post_type'              => self::POST_TYPE_SLUG,
+				'posts_per_page'         => 100,
+				'post_status'            => array( 'enabled', 'disabled' ),
+				'post_parent'            => $cache_point->ID,
+				'no_found_rows'          => true,
+				'update_post_meta_cache' => false,
+				'update_post_term_cache' => false,
+				'paged'                  => 1,
+			);
+			if ( true === $id_only ) {
+				$params['fields'] = 'ids';
+			}
+			$posts = new \WP_Query( $params );
+			do {
+				$found = $posts->get_posts();
+				$items = array_merge( $items, $found );
+				$params['paged'] ++;
+				$posts = new \WP_Query( $params );
+			} while ( $posts->have_posts() );
+		}
+
+		return $items;
+	}
+
+	/**
+	 * Get a cache point from a url.
+	 *
+	 * @param Int         $id     The cache point ID to get cache for.
+	 * @param string|null $search Optional search.
+	 * @param int         $page   The page or results to load.
+	 *
+	 * @return array
+	 */
+	public function get_cache_point_cache( $id, $search = null, $page = 1 ) {
+		$cache_point = get_post( $id );
+		if ( is_null( $cache_point ) ) {
+			return array();
+		}
+		$cached_items = (array) get_post_meta( $cache_point->ID, self::META_KEYS['cached_urls'], true );
+		$excluded     = (array) get_post_meta( $cache_point->ID, self::META_KEYS['excluded_urls'], true );
+		$cached_items = array_filter( $cached_items );
+		$args         = array(
+			'post_type'      => self::POST_TYPE_SLUG,
+			'posts_per_page' => 20,
+			'paged'          => $page,
+			'post_parent'    => $id,
+			'post_status'    => array( 'enabled', 'disabled' ),
+		);
+		if ( ! empty( $search ) ) {
+			$args['s'] = $search;
+		}
+		$posts = new \WP_Query( $args );
+		$items = array();
+		foreach ( $posts->get_posts() as $post ) {
+			$meta = get_post_meta( $post->ID );
+
+			$has = array_intersect_key( $meta[ self::META_KEYS['cached_urls'] ], $cached_items );
+			if ( empty( $has ) ) {
+				continue; // Not yet uploaded.
+			}
+
+			$items[] = array(
+				'ID'        => $post->ID,
+				'key'       => $post->post_name,
+				'local_url' => $meta[ self::META_KEYS['base_url'] ],
+				'short_url' => str_replace( $cache_point->post_title, '', $meta[ self::META_KEYS['base_url'] ] ),
+				'active'    => ! in_array( $meta[ self::META_KEYS['base_url'] ], $excluded, true ),
+			);
+		}
+		$total_items = count( $items );
+		$pages       = ceil( $total_items / 20 );
+		// translators: The current page and total pages.
+		$description = sprintf( __( 'Page %1$d of %2$d', 'cloudinary' ), $page, $pages );
+
+		// translators: The number of files.
+		$totals = sprintf( _n( '%d cached file', '%d cached files', $total_items, 'cloudinary' ), $total_items );
+
+		$return = array(
+			'items'        => $items,
+			'total'        => $total_items,
+			'total_pages'  => $pages,
+			'current_page' => $page,
+			'nav_text'     => $totals . ' | ' . $description,
+		);
+		if ( empty( $items ) ) {
+			if ( ! empty( $search ) ) {
+				$return['nav_text'] = __( 'No items found.', 'cloudinary' );
+			} else {
+				$return['nav_text'] = __( 'No items cached.', 'cloudinary' );
+			}
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Create a new cache point from a url.
+	 *
+	 * @param string $url      The url to create the cache point for.
+	 * @param string $src_path The path to be cached.
+	 * @param string $version  The version of the cache point.
+	 */
+	public function create_cache_point( $url, $src_path, $version ) {
+		if ( ! $this->is_registered( $url ) ) {
+			$key      = $this->get_key_name( $url );
+			$url      = trailingslashit( $url );
+			$src_path = str_replace( ABSPATH, '', trailingslashit( $src_path ) );
+
+			// Add meta data.
+			$meta = array(
+				self::META_KEYS['excluded_urls'] => array(),
+				self::META_KEYS['cached_urls']   => array(),
+				self::META_KEYS['src_path']      => $src_path,
+				self::META_KEYS['url']           => $url,
+				self::META_KEYS['version']       => $version,
+			);
+			// Create new Cache point.
+			$params                                = array(
+				'post_name'    => $key,
+				'post_type'    => self::POST_TYPE_SLUG,
+				'post_title'   => $url,
+				'post_content' => wp_json_encode( $meta ),
+				'post_status'  => 'enabled',
+			);
+			$post_id                               = wp_insert_post( $params );
+			$this->registered_cache_points[ $url ] = get_post( $post_id );
+		}
+		$this->check_version( $url, $version );
+	}
+
+	/**
+	 * Check and update the version if needed.
+	 *
+	 * @param string $url     The url of the cache point.
+	 * @param string $version the version.
+	 */
+	protected function check_version( $url, $version ) {
+		$cache_point = $this->get_cache_point( $url );
+		if ( ! is_numeric( $cache_point ) ) {
+			$prev_version = get_post_meta( $cache_point->ID, self::META_KEYS['version'], true );
+			if ( $prev_version !== $version ) {
+				update_post_meta( $cache_point->ID, self::META_KEYS['version'], $version );
+			}
+		}
+	}
+
+	/**
+	 * Get a key name for a cache point.
+	 *
+	 * @param string $url The url to get the key name for.
+	 *
+	 * @return string
+	 */
+	protected function get_key_name( $url ) {
+		return md5( trailingslashit( $this->clean_url( $url ) ) );
+	}
+
+	/**
+	 * Checks to see if a url is cacheable.
+	 *
+	 * @param string $url The URL to check if it can sync.
+	 *
+	 * @return bool
+	 */
+	public function can_cache_url( $url ) {
+		return ! is_null( $this->get_parent_cache_point( $url ) );
+	}
+
+	/**
+	 * Clean URLs te remove any query arguments and fragments.
+	 *
+	 * @param string $url The URL to clean.
+	 *
+	 * @return string
+	 */
+	public function clean_url( $url ) {
+		$default = array(
+			'scheme' => '',
+			'host'   => '',
+			'path'   => '',
+		);
+		$parts   = wp_parse_args( wp_parse_url( $url ), $default );
+
+		return $parts['scheme'] . '://' . $parts['host'] . $parts['path'];
+	}
+
+	/**
+	 * Get cached urls from cachepoint cache.
+	 *
+	 * @param array $urls List of URLS to extract.
+	 *
+	 * @return array
+	 */
+	protected function pre_cache_urls( $urls ) {
+		foreach ( $urls as $index => $url ) {
+			$cache_point = $this->get_cache_point( $url );
+			if ( $cache_point ) {
+				$cached_urls = get_post_meta( $cache_point->ID, self::META_KEYS['cached_urls'], true );
+				if ( isset( $cached_urls[ $url ] ) ) {
+					$this->pre_cached[ $url ] = $cached_urls[ $url ];
+					unset( $urls[ $index ] );
+				}
+			}
+		}
+
+		return $urls;
+	}
+
+	/**
+	 * Purge the entire cache for a cache point.
+	 *
+	 * @param int $id The cache point post ID.
+	 *
+	 * @return bool
+	 */
+	public function purge_cache( $id ) {
+		$return      = false;
+		$cache_point = get_post( $id );
+		if ( ! is_null( $cache_point ) ) {
+			$items = $this->get_cache_items( $cache_point->ID, true );
+			foreach ( $items as $cache_item ) {
+				update_post_meta( $cache_item, self::META_KEYS['cached_urls'], array() );
+			}
+			update_post_meta( $cache_point->ID, self::META_KEYS['cached_urls'], array() );
+			$return = true;
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Filter out duplicate urls that have different query and fragments.
+	 * We should only have a single base url per asset to prevent creating duplicate base items.
+	 *
+	 * @param string $url The url to test.
+	 *
+	 * @return bool
+	 */
+	protected function filter_duplicate_base( $url ) {
+		static $urls = array();
+		$clean       = $this->clean_url( $url );
+		if ( isset( $urls[ $clean ] ) ) {
+			return false;
+		}
+		$urls[ $clean ] = true;
+
+		return true;
+	}
+
+	/**
+	 * Version a URL.
+	 *
+	 * @param string $url The url to add a version to.
+	 *
+	 * @return string
+	 */
+	protected function version_url( $url ) {
+		$url         = $this->clean_url( $url );
+		$cache_point = $this->get_cache_point( $url );
+		$version     = get_post_meta( $cache_point->ID, self::META_KEYS['version'], true );
+
+		return add_query_arg( 'version', $version, $url );
+	}
+
+	/**
+	 * Convert a list of local URLS to Cached.
+	 *
+	 * @param array $urls List of local URLS to get cached versions.
+	 *
+	 * @return array|null
+	 */
+	public function get_cached_urls( $urls ) {
+		$active_ids = $this->get_active_cache_points( true );
+		if ( empty( $active_ids ) ) {
+			return null;
+		}
+		$urls = array_filter( $urls, array( $this, 'can_cache_url' ) );
+		if ( empty( $urls ) ) {
+			return null;
+		}
+
+		$urls        = $this->pre_cache_urls( array_map( array( $this, 'version_url' ), $urls ) );
+		$found_posts = $this->pre_cached;
+		if ( ! empty( $urls ) ) {
+			$queried_items = $this->query_cached_items( $urls );
+			if ( ! empty( $queried_items ) ) {
+				$found_posts += $queried_items;
+			}
+		}
+		$missing = array_diff( $urls, array_keys( $found_posts ) );
+		$missing = array_filter( $missing, array( $this, 'filter_duplicate_base' ) );
+		if ( ! empty( $missing ) ) {
+			$this->prepare_cache( $missing );
+		}
+
+		// Remove urls that are local to improve replace performance.
+		$found_posts = array_filter(
+			$found_posts,
+			function ( $key, $value ) {
+				return $key !== $value;
+			},
+			ARRAY_FILTER_USE_BOTH
+		);
+
+		return $found_posts;
+	}
+
+	/**
+	 * Add item to be synced later.
+	 *
+	 * @param int $id The cloudinary_asset post type ID to sync.
+	 */
+	protected function prepare_for_sync( $id ) {
+		if ( count( $this->to_upload ) < $this->sync_limit ) {
+			$this->to_upload[] = $id;
+		}
+	}
+
+	/**
+	 * Query cached items that are not cached in the cache point meta (purged, new, evaluated).
+	 * This will add items to the to_upload to re-evaluate, and re-upload if needed.
+	 *
+	 * @param array $urls The urls to query.
+	 *
+	 * @return array
+	 */
+	public function query_cached_items( $urls ) {
+		$clean_urls = array_map( array( $this, 'clean_url' ), $urls );
+		$keys       = array_map( array( $this, 'get_key_name' ), $urls );
+		$params     = array(
+			'post_type'              => self::POST_TYPE_SLUG,
+			'post_name__in'          => array_unique( $keys ),
+			'posts_per_page'         => 100,
+			'post_status'            => array( 'enabled', 'disabled' ),
+			'post_parent__in'        => $this->get_active_cache_points( true ),
+			'no_found_rows'          => true,
+			'update_post_meta_cache' => false,
+			'update_post_term_cache' => false,
+			'paged'                  => 1,
+		);
+		$posts      = new \WP_Query( $params );
+		do {
+			$all         = $posts->get_posts();
+			$found_posts = array();
+			foreach ( $all as $index => $post ) {
+				$meta     = get_post_meta( $post->ID );
+				$excludes = get_post_meta( $post->post_parent, self::META_KEYS['excluded_urls'], true );
+				if ( in_array( $meta[ self::META_KEYS['base_url'] ], $excludes, true ) ) {
+					// Add it as local, since this is being ignored.
+					$found_posts[ $meta[ self::META_KEYS['base_url'] ] ] = $meta[ self::META_KEYS['base_url'] ];
+					continue;
+				}
+				$indexes = array_keys( $clean_urls, $meta[ self::META_KEYS['base_url'] ], true );
+				if ( empty( $indexes ) ) {
+					continue; // Shouldn't happen, but bail in case.
+				}
+				foreach ( $indexes as $key ) {
+					$url = $urls[ $key ];
+
+					if (
+						! isset( $meta[ self::META_KEYS['cached_urls'] ][ $url ] )
+						|| (
+							$url === $meta[ self::META_KEYS['cached_urls'] ][ $url ]
+							&& $meta[ self::META_KEYS['last_updated'] ] < time() - MINUTE_IN_SECONDS * 10
+						)
+					) {
+						// Send to upload prep.
+						$this->prepare_for_sync( $post->ID );
+						$meta[ self::META_KEYS['cached_urls'] ][ $url ] = $url;
+						update_post_meta( $post->ID, self::META_KEYS['cached_urls'], $meta[ self::META_KEYS['cached_urls'] ] );
+					}
+					$found_posts[ $url ] = $meta[ self::META_KEYS['cached_urls'] ][ $url ];
+				}
+			}
+			$params['paged'] ++;
+			$posts = new \WP_Query( $params );
+		} while ( $posts->have_posts() );
+
+		return $found_posts;
+	}
+
+	/**
+	 * Prepare a list of urls to be cached.
+	 *
+	 * @param array $urls List of urls to cache.
+	 */
+	public function prepare_cache( $urls ) {
+
+		foreach ( $urls as $url ) {
+			$base_url    = $this->clean_url( $url );
+			$cache_point = $this->get_cache_point( $base_url );
+			if ( is_null( $cache_point ) || $this->exists( $base_url ) ) {
+				continue;
+			}
+
+			$file = $this->url_to_path( $url );
+			if ( is_null( $file ) ) {
+				$this->exclude_url( $cache_point->ID, $url );
+				continue;
+			}
+			$meta = array(
+				self::META_KEYS['base_url']     => $base_url,
+				self::META_KEYS['cached_urls']  => array(
+					$url => $url,
+				),
+				self::META_KEYS['src_file']     => $file,
+				self::META_KEYS['last_updated'] => time(),
+			);
+
+			$args = array(
+				'post_type'    => self::POST_TYPE_SLUG,
+				'post_title'   => $base_url,
+				'post_content' => wp_json_encode( $meta ),
+				'post_name'    => $this->get_key_name( $base_url ), // Has the name for uniqueness, and length.
+				'post_status'  => 'enabled',
+				'post_parent'  => $cache_point->ID,
+			);
+
+			$id = wp_insert_post( $args );
+			$this->prepare_for_sync( $id );
+		}
+	}
+
+	/**
+	 * Check if the post exists to prevent creating duplicates.
+	 *
+	 * @param string $url The url to test.
+	 *
+	 * @return bool
+	 */
+	public function exists( $url ) {
+
+		$cache_name = $this->get_key_name( $url );
+		$args       = array(
+			'post_type'      => self::POST_TYPE_SLUG,
+			'post_status'    => array( 'enabled', 'disabled' ),
+			'posts_per_page' => 1,
+			'name'           => $cache_name,
+		);
+		$query      = new \WP_Query( $args );
+
+		return (bool) $query->found_posts;
+	}
+
+	/**
+	 * Register the cache point type.
+	 */
+	protected function register_post_type() {
+		$args            = array(
+			'label'               => __( 'Cloudinary Asset', 'cloudinary' ),
+			'description'         => __( 'Post type to represent a non-media library asset.', 'cloudinary' ),
+			'labels'              => array(),
+			'supports'            => false,
+			'hierarchical'        => true,
+			'public'              => false,
+			'show_ui'             => false,
+			'show_in_menu'        => false,
+			'show_in_admin_bar'   => false,
+			'show_in_nav_menus'   => false,
+			'can_export'          => false,
+			'has_archive'         => false,
+			'exclude_from_search' => true,
+			'publicly_queryable'  => false,
+			'rewrite'             => false,
+			'capability_type'     => 'page',
+		);
+		$this->post_type = register_post_type( self::POST_TYPE_SLUG, $args ); // phpcs:ignore WordPress.NamingConventions.ValidPostTypeSlug.NotStringLiteral
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-admin.php.html b/docs/php_class-admin.php.html new file mode 100644 index 000000000..5328fad11 --- /dev/null +++ b/docs/php_class-admin.php.html @@ -0,0 +1,599 @@ + + + + + Source: php/class-admin.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-admin.php

+ + + + + + + +
+
+
<?php
+/**
+ * Settings class for Cloudinary.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Traits\Params_Trait;
+use Cloudinary\UI\Component;
+use Cloudinary\Settings\Setting;
+use WP_REST_Server;
+use WP_REST_Request;
+use WP_REST_Response;
+
+/**
+ * Admin Class.
+ */
+class Admin {
+
+	use Params_Trait;
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @var Plugin
+	 */
+	protected $plugin;
+
+	/**
+	 * Holds main settings object.
+	 *
+	 * @var Settings
+	 */
+	protected $settings;
+
+	/**
+	 * Holds notices object.
+	 *
+	 * @var Settings
+	 */
+	protected $notices;
+
+	/**
+	 * Holds the pages.
+	 *
+	 * @var array
+	 */
+	protected $pages;
+
+	/**
+	 * Holds the page section.
+	 *
+	 * @var string
+	 */
+	protected $section = 'page';
+
+	/**
+	 * Holds the current page component.
+	 *
+	 * @var Component
+	 */
+	protected $component;
+
+	/**
+	 * Option name for settings based internal data.
+	 *
+	 * @var string
+	 */
+	const SETTINGS_DATA = '_settings_version';
+
+	/**
+	 * Slug for notices
+	 *
+	 * @var string
+	 */
+	const NOTICE_SLUG = '_cld_notices';
+
+	/**
+	 * Initiate the settings object.
+	 *
+	 * @param Plugin $plugin The main plugin instance.
+	 */
+	public function __construct( Plugin $plugin ) {
+		$this->plugin = $plugin;
+		add_action( 'cloudinary_init_settings', array( $this, 'init_settings' ) );
+		add_action( 'admin_init', array( $this, 'init_setting_save' ), PHP_INT_MAX );
+		add_action( 'admin_menu', array( $this, 'build_menus' ) );
+		add_filter( 'cloudinary_api_rest_endpoints', array( $this, 'rest_endpoints' ) );
+		$notice_params = array(
+			'storage' => 'transient',
+		);
+		$notices       = new Settings( self::NOTICE_SLUG, $notice_params );
+		$this->notices = $notices->add( 'cld_general', array() );
+		if ( ! defined( 'REST_REQUEST' ) || true !== REST_REQUEST ) {
+			add_action( 'shutdown', array( $notices, 'save' ) );
+		}
+	}
+
+	/**
+	 * Add endpoints to the \Cloudinary\REST_API::$endpoints array.
+	 *
+	 * @param array $endpoints Endpoints from the filter.
+	 *
+	 * @return array
+	 */
+	public function rest_endpoints( $endpoints ) {
+
+		$endpoints['dismiss_notice'] = array(
+			'method'   => WP_REST_Server::CREATABLE,
+			'callback' => array( $this, 'rest_dismiss_notice' ),
+			'args'     => array(),
+		);
+
+		$endpoints['save_settings'] = array(
+			'method'              => WP_REST_Server::CREATABLE,
+			'callback'            => array( $this, 'rest_save_settings' ),
+			'args'                => array(),
+			'permission_callback' => function () {
+				return Utils::user_can( 'manage_settings' );
+			},
+		);
+
+		return $endpoints;
+	}
+
+	/**
+	 * Set a transient with the duration using a token as an identifier.
+	 *
+	 * @param WP_REST_Request $request The request object.
+	 */
+	public function rest_dismiss_notice( WP_REST_Request $request ) {
+		$token    = $request->get_param( 'token' );
+		$duration = $request->get_param( 'duration' );
+
+		set_transient( $token, true, $duration );
+	}
+
+	/**
+	 * Set a transient with the duration using a token as an identifier.
+	 *
+	 * @param WP_REST_Request $request The request object.
+	 *
+	 * @return WP_REST_Response
+	 */
+	public function rest_save_settings( WP_REST_Request $request ) {
+		$data     = $request->get_params();
+		$settings = $this->settings;
+		$data     = array_filter(
+			$data,
+			function ( $key ) use ( $settings ) {
+				return $settings->get_setting( $key, false );
+			},
+			ARRAY_FILTER_USE_KEY
+		);
+
+		foreach ( $data as $submission => $package ) {
+			$this->save_settings( $submission, $package );
+		}
+		$results = $this->notices->get_value();
+		$this->notices->delete();
+
+		return rest_ensure_response( $results );
+	}
+
+	/**
+	 * Check settings version to allow settings to update or upgrade.
+	 *
+	 * @param string $slug The slug for the settings set to check.
+	 */
+	protected static function check_version( $slug ) {
+		$key              = '_' . $slug . self::SETTINGS_DATA;
+		$settings_version = get_option( $key, 2.4 );
+		$plugin_version   = get_plugin_instance()->version;
+		if ( version_compare( $settings_version, $plugin_version, '<' ) ) {
+			// Allow for updating.
+			do_action( "{$slug}_settings_upgrade", $settings_version, $plugin_version );
+			// Update version.
+			update_option( $key, $plugin_version );
+		}
+	}
+
+	/**
+	 * Register the page.
+	 */
+	public function build_menus() {
+		foreach ( $this->pages as $page ) {
+			$this->register_admin( $page );
+		}
+	}
+
+	/**
+	 * Register the page.
+	 *
+	 * @param array $page The page array to create pages.
+	 */
+	public function register_admin( $page ) {
+
+		$render_function = array( $this, 'render' );
+
+		// Setup the main page.
+		$page_handle = add_menu_page(
+			$page['page_title'],
+			$page['menu_title'],
+			$page['capability'],
+			$page['slug'],
+			'',
+			$page['icon'],
+			'81.5'
+		);
+		$connected   = $this->settings->get_param( 'connected' );
+		// Setup the Child page handles.
+		foreach ( $page['settings'] as $slug => $sub_page ) {
+			if ( empty( $sub_page ) ) {
+				continue;
+			}
+			// Check if the page contains settings that require connection.
+			if ( ! empty( $sub_page['requires_connection'] ) && empty( $connected ) ) {
+				continue;
+			}
+			$render_slug = $page['slug'] . '_' . $slug;
+			if ( ! isset( $first ) ) {
+				$render_slug = $page['slug'];
+				$first       = true;
+			}
+			if ( ! apply_filters( "cloudinary_settings_enabled_{$slug}", true ) ) {
+				continue;
+			}
+			// Add section page if defined.
+			if ( ! empty( $sub_page['section'] ) ) {
+				$this->set_param( $sub_page['section'], $sub_page );
+				// Section pages are more like tabs, so skip menu page registrations.
+				continue;
+			}
+			$capability = ! empty( $sub_page['capability'] ) ? $sub_page['capability'] : $page['capability'];
+			$page_title = ! empty( $sub_page['page_title'] ) ? $sub_page['page_title'] : $page['page_title'];
+			$menu_title = ! empty( $sub_page['menu_title'] ) ? $sub_page['menu_title'] : $page_title;
+			$position   = ! empty( $sub_page['position'] ) ? $sub_page['position'] : 50;
+			if ( isset( $sub_page['disconnected_title'] ) && ! $this->settings->get_param( 'connected' ) ) {
+				$page_title = $sub_page['disconnected_title'];
+				$menu_title = $sub_page['disconnected_title'];
+			}
+			$page_handle      = add_submenu_page(
+				$page['slug'],
+				$page_title,
+				$menu_title,
+				$capability,
+				$render_slug,
+				$render_function,
+				$position
+			);
+			$sub_page['slug'] = $slug;
+			$this->set_param( $page_handle, $sub_page );
+			// Dynamically call to set active setting.
+			add_action( "load-{$page_handle}", array( $this, $page_handle ) );
+		}
+	}
+
+	/**
+	 * Dynamically set the active page.
+	 *
+	 * @param string $name      The name called (page in this case).
+	 * @param array  $arguments Arguments passed to call.
+	 */
+	public function __call( $name, $arguments ) {
+
+		if ( $this->has_param( $name ) ) {
+
+			$page = $this->get_param( $name );
+			$this->settings->set_param( 'active_setting', $page['slug'] );
+			$section = Utils::get_sanitized_text( 'section' );
+			if ( $section && $this->has_param( $section ) ) {
+				$this->section = $section;
+				$this->set_param( 'current_section', $this->get_param( $section ) );
+			}
+			if ( 'page' === $this->section && ! $this->settings->get_param( 'connected' ) && 'help' !== $page['slug'] ) {
+				$args = array(
+					'page'    => $this->plugin->slug,
+					'section' => 'wizard',
+				);
+				$url  = add_query_arg( $args, 'admin.php' );
+				wp_safe_redirect( $url );
+				exit;
+			}
+		}
+	}
+
+	/**
+	 * Render a page.
+	 */
+	public function render() {
+		wp_enqueue_script( $this->plugin->slug );
+		$screen = get_current_screen();
+		$page   = $this->get_param( $screen->id );
+		// Check if a section page was set, and replace page structure with the section.
+		if ( $this->has_param( 'current_section' ) ) {
+			$page = $this->get_param( 'current_section' );
+		}
+
+		$this->set_param( 'active_slug', $page['slug'] );
+		$setting         = $this->init_components( $page, $screen->id );
+		$this->component = $setting->get_component();
+		$template        = $this->section;
+
+		$file = $this->plugin->dir_path . 'ui-definitions/components/page.php';
+		if ( file_exists( $this->plugin->dir_path . 'ui-definitions/components/' . $template . '.php' ) ) {
+			// If the section has a defined template, use that instead eg. wizard.
+			$file = $this->plugin->dir_path . 'ui-definitions/components/' . $template . '.php';
+		}
+		include $file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+	}
+
+	/**
+	 * Get the component.
+	 *
+	 * @return Component
+	 */
+	public function get_component() {
+		return $this->component;
+	}
+
+	/**
+	 * Initialise UI components.
+	 *
+	 * @param array  $template The template structure.
+	 * @param string $slug     The slug of the template ti init.
+	 *
+	 * @return Setting|null
+	 */
+	public function init_components( $template, $slug ) {
+		if ( ! empty( $template['requires_connection'] ) && ! $this->settings->get_param( 'connected' ) ) {
+			return null;
+		}
+		$setting = $this->settings->add( $slug, array(), $template );
+		foreach ( $template as $index => $component ) {
+			// Add setting components directly.
+			if ( $component instanceof Setting ) {
+				$setting->add( $component );
+				continue;
+			}
+
+			if ( ( ! is_array( $component ) || ! isset( $component['slug'] ) ) && ! self::filter_template( $index ) ) {
+				continue;
+			}
+
+			if ( ! isset( $component['type'] ) ) {
+				$component['type'] = 'frame';
+			}
+			$component_slug = $index;
+			if ( isset( $component['slug'] ) ) {
+				$component_slug = $component['slug'];
+			}
+			if ( ! isset( $component['setting'] ) ) {
+				$component['setting'] = $this->init_components( $component, $slug . $this->settings->separator . $component_slug );
+			} else {
+				$setting->add( $component['setting'] );
+			}
+		}
+
+		return $setting;
+	}
+
+	/**
+	 * Filter out non-setting params.
+	 *
+	 * @param numeric-string $key The key to filter out.
+	 *
+	 * @return bool
+	 */
+	public static function filter_template( $key ) {
+		return is_numeric( $key ) || 'settings' === $key;
+	}
+
+	/**
+	 * Register a setting page.
+	 *
+	 * @param string $slug   The new page slug.
+	 * @param array  $params The page parameters.
+	 */
+	public function register_page( $slug, $params = array() ) {
+		// Register the page.
+		$this->pages[ $slug ] = $params;
+	}
+
+	/**
+	 * Init the plugin settings.
+	 */
+	public function init_settings() {
+		$this->settings = $this->plugin->settings;
+	}
+
+	/**
+	 * Register settings with WordPress.
+	 */
+	public function init_setting_save() {
+
+		$submission = Utils::get_sanitized_text( 'cloudinary-active-slug', INPUT_POST );
+		if ( ! $submission ) {
+			return; // Bail.
+		}
+
+		$args = array(
+			'_cld_nonce'       => array(
+				'filter'  => FILTER_CALLBACK,
+				'options' => 'sanitize_text_field',
+			),
+			'_wp_http_referer' => FILTER_SANITIZE_URL,
+			$submission        => array(
+				'flags' => FILTER_REQUIRE_ARRAY,
+			),
+		);
+
+		$saving = filter_input_array( INPUT_POST, $args, false );
+		if ( ! empty( $saving ) && ! empty( $saving[ $submission ] ) && wp_verify_nonce( $saving['_cld_nonce'], 'cloudinary-settings' ) ) {
+			$referer = $saving['_wp_http_referer'];
+
+			$data = $saving[ $submission ];
+			$this->save_settings( $submission, $data );
+			wp_safe_redirect( $referer );
+			exit;
+		}
+	}
+
+	/**
+	 * Save a settings set.
+	 *
+	 * @param string $submission The settings slug to save.
+	 * @param array  $data       The data to save.
+	 */
+	protected function save_settings( $submission, $data ) {
+		$page    = $this->settings->get_setting( $submission );
+		$errors  = array();
+		$pending = false;
+		foreach ( $data as $key => $value ) {
+			$slug    = $submission . $page->separator . $key;
+			$current = $this->settings->get_value( $slug );
+			if ( $current === $value ) {
+				continue;
+			}
+			$capture_setting = $this->settings->get_setting( $key );
+			$value           = $capture_setting->get_component()->sanitize_value( $value );
+			$result          = $this->settings->set_pending( $key, $value, $current );
+			if ( is_wp_error( $result ) ) {
+				$this->add_admin_notice( $result->get_error_code(), $result->get_error_message(), $result->get_error_data() );
+				break;
+			}
+			$pending = true;
+		}
+
+		if ( empty( $errors ) && true === $pending ) {
+			$results = $this->settings->save();
+			if ( ! empty( $results ) ) {
+				$this->add_admin_notice( 'error_notice', __( 'Settings updated successfully', 'cloudinary' ), 'success' );
+			}
+		} else {
+			$this->add_admin_notice( 'error_notice', __( 'No changes to save', 'cloudinary' ), 'success' );
+		}
+		/**
+		 * Action to flush delivery caches.
+		 *
+		 * @hook   cloudinary_flush_cache
+		 * @since  3.0.0
+		 */
+		do_action( 'cloudinary_flush_cache' );
+	}
+
+	/**
+	 * Set an error/notice for a setting.
+	 *
+	 * @param string $error_code    The error code/slug.
+	 * @param string $error_message The error text/message.
+	 * @param string $type          The error type.
+	 * @param bool   $dismissible   If notice is dismissible.
+	 * @param int    $duration      How long it's dismissible for.
+	 * @param string $icon          Optional icon.
+	 */
+	public function add_admin_notice( $error_code, $error_message, $type = 'error', $dismissible = true, $duration = 0, $icon = null ) {
+
+		// Format message array into paragraphs.
+		if ( is_array( $error_message ) ) {
+			$message       = implode( "\n\r", $error_message );
+			$error_message = wpautop( $message );
+		}
+
+		$icons = array(
+			'success' => 'dashicons-yes-alt',
+			'created' => 'dashicons-saved',
+			'updated' => 'dashicons-saved',
+			'error'   => 'dashicons-no-alt',
+			'warning' => 'dashicons-warning',
+		);
+
+		if ( null === $icon && ! empty( $icons[ $type ] ) ) {
+			$icon = $icons[ $type ];
+		}
+		$notices = $this->notices->get_value();
+		// Set new notice.
+		$params                 = array(
+			'type'     => 'notice',
+			'level'    => $type,
+			'message'  => $error_message,
+			'code'     => $error_code,
+			'dismiss'  => $dismissible,
+			'duration' => $duration,
+			'icon'     => $icon,
+		);
+		$notices[ $error_code ] = $params;
+		$this->notices->set_pending( $notices );
+		$this->notices->set_value( $notices );
+	}
+
+	/**
+	 * Render the notices.
+	 */
+	public function render_notices() {
+
+		$notices = $this->notices->get_value();
+		if ( ! empty( $notices ) ) {
+			sort( $notices );
+			$notice = $this->init_components( $notices, self::NOTICE_SLUG );
+			$notice->get_component()->render( true );
+			$this->notices->delete();
+		}
+	}
+
+	/**
+	 * Get admin notices.
+	 *
+	 * @return Setting[]
+	 */
+	public function get_admin_notices() {
+		$setting_notices = get_settings_errors();
+		foreach ( $setting_notices as $key => $notice ) {
+			$this->add_admin_notice( $notice['code'], $notice['message'], $notice['type'], true );
+		}
+
+		return $setting_notices;
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-cache.php.html b/docs/php_class-cache.php.html new file mode 100644 index 000000000..db4b5c784 --- /dev/null +++ b/docs/php_class-cache.php.html @@ -0,0 +1,1143 @@ + + + + + Source: php/class-cache.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-cache.php

+ + + + + + + +
+
+
<?php
+/**
+ * Cloudinary Logger, to collect logs and debug data.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Cache\Cache_Point;
+use Cloudinary\Cache\File_System;
+use Cloudinary\Component\Setup;
+use Cloudinary\Settings\Setting;
+use WP_Error;
+use WP_HTTP_Response;
+use WP_REST_Request;
+use WP_REST_Response;
+
+/**
+ * Plugin report class.
+ */
+class Cache extends Settings_Component implements Setup {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @var Plugin Instance of the global plugin.
+	 */
+	public $plugin;
+
+	/**
+	 * Holds the Media component.
+	 *
+	 * @var Media
+	 */
+	public $media;
+
+	/**
+	 * File System
+	 *
+	 * @var File_System
+	 */
+	public $file_system;
+
+	/**
+	 * Holds the Connect component.
+	 *
+	 * @var Connect
+	 */
+	protected $connect;
+
+	/**
+	 * Holds the Rest API component.
+	 *
+	 * @var REST_API
+	 */
+	protected $api;
+
+	/**
+	 * Holds the setting slugs for the file paths that are selected.
+	 *
+	 * @var array
+	 */
+	public $cache_data_keys = array();
+
+	/**
+	 * Holds the retrieved cache points for recall to minimize DB hits.
+	 *
+	 * @var array
+	 */
+	protected $cache_points = array();
+
+	/**
+	 * Holds the folder in which to store cached items in Cloudinary.
+	 *
+	 * @var string
+	 */
+	public $cache_folder;
+
+	/**
+	 * Holds the Cache Point object.
+	 *
+	 * @var Cache_Point
+	 */
+	public $cache_point;
+
+	/**
+	 * Holds the meta keys to be used.
+	 *
+	 * @var array
+	 */
+	const META_KEYS = array(
+		'upload_method' => '_cloudinary_upload_method',
+	);
+
+	/**
+	 * Site Cache constructor.
+	 *
+	 * @param Plugin $plugin Global instance of the main plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+		parent::__construct( $plugin );
+		$this->file_system = new File_System( $plugin );
+		if ( $this->file_system->enabled() ) {
+			$this->cache_folder = wp_parse_url( Utils::site_url(), PHP_URL_HOST );
+			$this->media        = $this->plugin->get_component( 'media' );
+			$this->connect      = $this->plugin->get_component( 'connect' );
+			$this->api          = $this->plugin->get_component( 'api' );
+			$this->register_hooks();
+		}
+	}
+
+	/**
+	 * Rewrites urls in admin.
+	 */
+	public function admin_rewrite() {
+		ob_start( array( $this, 'html_rewrite' ) );
+		add_action(
+			'shutdown',
+			function () {
+				ob_get_flush();
+			},
+			- 1
+		);
+	}
+
+	/**
+	 * Rewrite urls on frontend.
+	 *
+	 * @param string $template The frontend template being loaded.
+	 *
+	 * @return string
+	 */
+	public function frontend_rewrite( $template ) {
+		$bypass = Utils::get_sanitized_text( 'bypass_cache' );
+
+		if ( ! empty( $bypass ) ) {
+			return $template;
+		}
+		ob_start( array( $this, 'html_rewrite' ) );
+		include $template; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+
+		return CLDN_PATH . 'php/cache/template.php';
+	}
+
+	/**
+	 * Rewrite HTML by replacing local URLS with Remote URLS.
+	 *
+	 * @param string $html The HTML to rewrite.
+	 *
+	 * @return string
+	 */
+	public function html_rewrite( $html ) {
+
+		$sources = $this->build_sources( $html );
+		// Replace all sources if we have some URLS.
+		if ( ! empty( $sources ) && ! empty( $sources['url'] ) ) {
+			$html = str_replace( $sources['url'], $sources['cld'], $html );
+		}
+
+		return $html;
+	}
+
+	/**
+	 * Find the URLs from HTML.
+	 *
+	 * @param string $html The HTML to find urls from.
+	 *
+	 * @return array
+	 */
+	public function find_urls( $html ) {
+		$types = $this->get_filetype_filters();
+
+		// Get all instances of paths from the page with version suffix. Keep it loose as to catch relative urls as well.
+		// wp_extract_urls() can also do this, however, we want to get only of the types specified.
+		preg_match_all( '/(?<=["\'\(\s])[^"\'\(\s;\)]+?\.{1}(' . implode( '|', $types ) . ')([-a-zA-Z0-9@:;%_\+.~\#?&=]+)?/i', $html, $urls );
+		$urls = array_unique( $urls[0] );
+
+		return $urls;
+	}
+
+	/**
+	 * Build sources for a set of paths and HTML.
+	 *
+	 * @param string $html The html to build against.
+	 *
+	 * @return array[]|null
+	 */
+	protected function build_sources( $html ) {
+
+		$found_urls = $this->find_urls( $html );
+
+		// Bail if not found.
+		if ( empty( $found_urls ) ) {
+			return null;
+		}
+
+		$found_urls  = array_unique( $found_urls );
+		$found_posts = $this->cache_point->get_cached_urls( $found_urls );
+		if ( empty( $found_posts ) ) {
+			return null;
+		}
+		// Clean locals/pending.
+		$found_posts = array_filter(
+			$found_posts,
+			static function ( $key, $value ) {
+				return $key !== $value;
+			},
+			ARRAY_FILTER_USE_BOTH
+		);
+
+		$source_urls    = array_map( array( $this->cache_point, 'clean_url' ), array_keys( $found_posts ) );
+		$sources        = array();
+		$sources['url'] = $source_urls;
+		$sources['cld'] = array_values( $found_posts );
+
+		return $sources;
+	}
+
+	/**
+	 * Register any hooks that this component needs.
+	 */
+	protected function register_hooks() {
+		$this->cache_point = new Cache_Point( $this );
+		if ( ! $this->bypass_cache() ) {
+			add_filter( 'template_include', array( $this, 'frontend_rewrite' ), PHP_INT_MAX );
+			add_action( 'admin_init', array( $this, 'admin_rewrite' ), 0 );
+		}
+		add_filter( 'cloudinary_api_rest_endpoints', array( $this, 'rest_endpoints' ) );
+		add_action( 'http_request_args', array( $this, 'prevent_caching_internal_requests' ), 10, 5 ); // phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args
+	}
+
+	/**
+	 * Prevent internal background requests from getting new cached items created.
+	 *
+	 * @param array  $args The request structure.
+	 * @param string $url  The URL being requested.
+	 *
+	 * @return array
+	 */
+	public function prevent_caching_internal_requests( $args, $url ) {
+		$home    = strtolower( wp_parse_url( Utils::home_url(), PHP_URL_HOST ) );
+		$request = strtolower( wp_parse_url( $url, PHP_URL_HOST ) );
+		if ( $home === $request ) {
+			$args['headers']['x-cld-cache'] = time();
+		}
+
+		return $args;
+	}
+
+	/**
+	 * Check if the cache needs to be bypassed.
+	 */
+	public function bypass_cache() {
+
+		$bypass = filter_input( INPUT_SERVER, 'HTTP_X_CLD_CACHE', FILTER_SANITIZE_NUMBER_INT );
+
+		/**
+		 * Filter to allow bypassing the cache.
+		 *
+		 * @hook    cloudinary_bypass_cache
+		 * @default false
+		 *
+		 * @param $bypass {bool} True to bypass, false to not.
+		 *
+		 * @return {bool}
+		 */
+		return apply_filters( 'cloudinary_bypass_cache', ! is_null( $bypass ) );
+	}
+
+	/**
+	 * Hook into the Cron event and process unsynced items.
+	 *
+	 * @param array $args The args passed to the cron event.
+	 *
+	 * @return int[]
+	 */
+	public function upload_cache( $args ) {
+		$return = array();
+		foreach ( (array) $args as $post_id ) {
+			$post = get_post( $post_id );
+			if ( Cache_Point::POST_TYPE_SLUG !== $post->post_type ) {
+				continue;
+			}
+			$meta        = get_post_meta( $post_id );
+			$cached_urls = get_post_meta( $post->post_parent, Cache_Point::META_KEYS['cached_urls'], true );
+			if ( empty( $cached_urls ) ) {
+				$cached_urls = array();
+			}
+
+			foreach ( $meta[ Cache_Point::META_KEYS['cached_urls'] ] as $url => &$cached_url ) {
+				$result = $this->sync_static( $meta[ Cache_Point::META_KEYS['src_file'] ], $meta[ Cache_Point::META_KEYS['base_url'] ] );
+				if ( is_wp_error( $result ) ) {
+					// If error, log it, and set item to draft.
+					update_post_meta( $post_id, Cache_Point::META_KEYS['upload_error'], $result );
+					$params = array(
+						'ID'          => $post_id,
+						'post_status' => 'disabled',
+					);
+					wp_update_post( $params );
+					continue;
+				}
+				$cached_url          = $result;
+				$cached_urls[ $url ] = $cached_url;
+			}
+			update_post_meta( $post_id, Cache_Point::META_KEYS['cached_urls'], $meta[ Cache_Point::META_KEYS['cached_urls'] ] );
+			update_post_meta( $post_id, Cache_Point::META_KEYS['last_updated'], time() );
+			// Update cache point, cache.
+			update_post_meta( $post->post_parent, Cache_Point::META_KEYS['cached_urls'], $cached_urls );
+			$return[] = $post_id;
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Register the sync endpoint.
+	 *
+	 * @param array $endpoints The endpoint to add to.
+	 *
+	 * @return array
+	 */
+	public function rest_endpoints( $endpoints ) {
+
+		$endpoints['show_cache']          = array(
+			'method'              => \WP_REST_Server::CREATABLE,
+			'callback'            => array( $this, 'rest_get_caches' ),
+			'permission_callback' => array( $this, 'rest_can_manage_options' ),
+			'args'                => array(),
+		);
+		$endpoints['disable_cache_items'] = array(
+			'method'              => \WP_REST_Server::CREATABLE,
+			'permission_callback' => array( $this, 'rest_can_manage_options' ),
+			'callback'            => array( $this, 'rest_disable_items' ),
+			'args'                => array(
+				'ids'   => array(
+					'type'        => 'array',
+					'default'     => array(),
+					'description' => __( 'The list of IDs to update.', 'cloudinary' ),
+				),
+				'state' => array(
+					'type'        => 'string',
+					'default'     => 'draft',
+					'description' => __( 'The state to update.', 'cloudinary' ),
+				),
+			),
+		);
+		$endpoints['purge_cache']         = array(
+			'method'              => \WP_REST_Server::CREATABLE,
+			'callback'            => array( $this, 'rest_purge_cache_point' ),
+			'permission_callback' => array( $this, 'rest_can_manage_options' ),
+			'args'                => array(),
+		);
+		$endpoints['upload_cache']        = array(
+			'method'   => \WP_REST_Server::CREATABLE,
+			'callback' => array( $this, 'rest_upload_cache' ),
+			'args'     => array(),
+		);
+
+		return $endpoints;
+	}
+
+	/**
+	 * Upload a set of assets..
+	 *
+	 * @param WP_REST_Request $request The request object.
+	 *
+	 * @return WP_Error|WP_HTTP_Response|WP_REST_Response
+	 */
+	public function rest_upload_cache( $request ) {
+
+		$assets_ids = $request->get_param( 'ids' );
+		$result     = $this->upload_cache( $assets_ids );
+
+		return rest_ensure_response( $result );
+	}
+
+	/**
+	 * Purges a cachepoint which forces the entire point to re-evaluate cached items when requested.
+	 *
+	 * @param WP_REST_Request $request The request object.
+	 *
+	 * @return WP_Error|WP_HTTP_Response|WP_REST_Response
+	 */
+	public function rest_purge_cache_point( $request ) {
+
+		$cache_point = $request->get_param( 'cachePoint' );
+		$result      = $this->cache_point->purge_cache( $cache_point );
+
+		return rest_ensure_response( $result );
+	}
+
+	/**
+	 * Get cached files for an cache point.
+	 *
+	 * @param WP_REST_Request $request The request object.
+	 *
+	 * @return WP_REST_Response
+	 */
+	public function rest_get_caches( $request ) {
+		$id           = $request->get_param( 'ID' );
+		$search       = $request->get_param( 'search' );
+		$page         = $request->get_param( 'page' );
+		$current_page = $page ? $page : 1;
+		$data         = $this->cache_point->get_cache_point_cache( $id, $search, $current_page );
+
+		return rest_ensure_response( $data );
+	}
+
+	/**
+	 * Admin permission callback.
+	 *
+	 * Explicitly defined to allow easier testability.
+	 *
+	 * @return bool
+	 */
+	public function rest_can_manage_options() {
+		return Utils::user_can( 'manage_cache' );
+	}
+
+	/**
+	 * Change the status of a cache_point.
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 *
+	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+	 */
+	public function rest_disable_items( $request ) {
+		$ids   = $request['ids'];
+		$state = $request['state'];
+		foreach ( $ids as $id ) {
+			$item         = get_post( $id );
+			$cached_items = get_post_meta( $item->post_parent, Cache_Point::META_KEYS['cached_urls'], true );
+			$item_meta    = get_post_meta( $id );
+			if ( 'delete' === $state ) {
+				if ( isset( $cached_items[ $item_meta[ Cache_Point::META_KEYS['base_url'] ] ] ) ) {
+					unset( $cached_items[ $item_meta[ Cache_Point::META_KEYS['base_url'] ] ] );
+					update_post_meta( $item->post_parent, Cache_Point::META_KEYS['cached_urls'], $cached_items );
+				}
+				update_post_meta( $id, Cache_Point::META_KEYS['cached_urls'], array() );
+			} elseif ( 'disable' === $state ) {
+				$this->cache_point->exclude_url( $item->post_parent, $item_meta[ Cache_Point::META_KEYS['base_url'] ] );
+			} elseif ( 'enable' === $state ) {
+				$this->cache_point->remove_excluded_url( $item->post_parent, $item_meta[ Cache_Point::META_KEYS['base_url'] ] );
+			}
+		}
+
+		return $ids;
+	}
+
+	/**
+	 * Gets the upload method: url or file upload by determining if the site is accessible from the outside.
+	 *
+	 * @return string
+	 */
+	protected function get_upload_method() {
+		$method = get_transient( self::META_KEYS['upload_method'] );
+		if ( empty( $method ) ) {
+			$test_url = $this->media->base_url . '/image/fetch/' . $this->plugin->dir_url . 'css/images/logo.svg';
+			$request  = wp_remote_head( $test_url );
+			$method   = 'direct';
+			if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
+				$method = 'url';
+			}
+			set_transient( self::META_KEYS['upload_method'], $method, DAY_IN_SECONDS );
+		}
+
+		return $method;
+	}
+
+	/**
+	 * Get an array of mimetypes that should be replaced inline with base64 encoding.
+	 *
+	 * @return array
+	 */
+	protected function get_inline_types() {
+		$inline_types = array(
+			'image/svg+xml',
+		);
+
+		/**
+		 * Filter types of files that can replaced inline with a base64 encoded data URI.
+		 *
+		 * @hook    cloudinary_plugin_asset_cache_inline_types
+		 * @default array()
+		 *
+		 * @param $inline_types {array} The types of files to be encoded inline.
+		 *
+		 * @return  {array}
+		 */
+		return apply_filters( 'cloudinary_plugin_asset_cache_inline_types', $inline_types );
+	}
+
+	/**
+	 * Upload a static file.
+	 *
+	 * @param string $file The file path to upload.
+	 * @param string $url  The file URL to upload.
+	 *
+	 * @return string|WP_Error
+	 */
+	public function sync_static( $file, $url ) {
+
+		$file_path    = $this->cache_folder . '/' . substr( $file, strlen( ABSPATH ) );
+		$public_id    = dirname( $file_path ) . '/' . Utils::pathinfo( $file, PATHINFO_FILENAME );
+		$type         = $this->media->get_file_type( $file );
+		$method       = $this->get_upload_method();
+		$upload_file  = $this->cache_point->clean_url( $url );
+		$inline_types = $this->get_inline_types();
+
+		// Check if file is to be inline encoded.
+		$mime_type = mime_content_type( $file );
+		if ( in_array( $mime_type, $inline_types, true ) ) {
+			$content = $this->file_system->wp_file_system->get_contents( $file );
+
+			return 'data:' . $mime_type . ';base64,' . base64_encode( $content );
+		}
+		if ( 'direct' === $method ) {
+			if ( function_exists( 'curl_file_create' ) ) {
+				$upload_file = curl_file_create( $file ); // phpcs:ignore
+				$upload_file->setPostFilename( $file );
+			} else {
+				$upload_file = '@' . $upload_file;
+			}
+		}
+		$options = array(
+			'file'          => $upload_file,
+			'resource_type' => 'auto',
+			'public_id'     => wp_normalize_path( $public_id ),
+		);
+
+		if ( 'image' === $type ) {
+			$options['eager'] = 'f_auto,q_auto:eco';
+		}
+		$data = $this->connect->api->upload_cache( $options );
+
+		if ( is_wp_error( $data ) ) {
+			return $data;
+		}
+
+		$url = $data['secure_url'];
+		if ( ! empty( $data['eager'] ) ) {
+			$url = $data['eager'][0]['secure_url'];
+		}
+
+		return $url;
+	}
+
+	/**
+	 * Get the file type filters that is used to determine what kind of files get cached.
+	 *
+	 * @return array
+	 */
+	protected function get_filetype_filters() {
+		$default_filters = array(
+			'jpg',
+			'jpeg',
+			'gif',
+			'png',
+			'svg',
+			'mp4',
+			'm4v',
+			'mov',
+			'wmv',
+			'avi',
+			'mpg',
+			'ogv',
+			'3gp',
+			'3g2',
+		);
+
+		/**
+		 * Filter types of files that can be cached.
+		 *
+		 * @hook    cloudinary_plugin_asset_cache_filters
+		 * @default array()
+		 *
+		 * @param $default_filters {array} The types of files to be filtered.
+		 *
+		 * @return  {array}
+		 */
+		return apply_filters( 'cloudinary_plugin_asset_cache_filters', $default_filters );
+	}
+
+	/**
+	 * Get the plugins table structure.
+	 *
+	 * @return array
+	 */
+	protected function get_plugins_table() {
+
+		$plugins = get_plugins();
+		$active  = wp_get_active_and_valid_plugins();
+		$rows    = array();
+		foreach ( $active as $plugin_path ) {
+			$dir    = wp_basename( dirname( $plugin_path ) );
+			$plugin = $dir . '/' . wp_basename( $plugin_path );
+			if ( ! isset( $plugins[ $plugin ] ) ) {
+				continue;
+			}
+			$slug          = sanitize_file_name( $plugin );
+			$plugin_url    = plugins_url( $plugin );
+			$details       = $plugins[ $plugin ];
+			$rows[ $slug ] = array(
+				'title'    => $details['Name'],
+				'url'      => dirname( $plugin_url ),
+				'src_path' => dirname( $plugin_path ),
+				'version'  => $details['Version'],
+			);
+		}
+
+		return array(
+			'slug'       => 'plugin_files',
+			'type'       => 'folder_table',
+			'title'      => __( 'Plugin', 'cloudinary' ),
+			'root_paths' => $rows,
+		);
+
+	}
+
+	/**
+	 * Get the settings structure for the theme table.
+	 *
+	 * @return array
+	 */
+	protected function get_theme_table() {
+
+		$theme  = wp_get_theme();
+		$themes = array(
+			$theme,
+		);
+		if ( $theme->parent() ) {
+			$themes[] = $theme->parent();
+		}
+		$rows = array();
+		// Active Theme.
+		foreach ( $themes as $theme ) {
+			$theme_location = $theme->get_stylesheet_directory();
+			$theme_slug     = wp_basename( dirname( $theme_location ) ) . '/' . wp_basename( $theme_location );
+			$slug           = sanitize_file_name( $theme_slug );
+			$rows[ $slug ]  = array(
+				'title'    => $theme->get( 'Name' ),
+				'url'      => $theme->get_stylesheet_directory_uri(),
+				'src_path' => $theme->get_stylesheet_directory(),
+				'version'  => $theme->get( 'Version' ),
+			);
+		}
+
+		return array(
+			'slug'       => 'theme_files',
+			'type'       => 'folder_table',
+			'title'      => __( 'Theme', 'cloudinary' ),
+			'root_paths' => $rows,
+		);
+	}
+
+	/**
+	 * Get the settings structure for the WordPress table.
+	 *
+	 * @return array
+	 */
+	protected function get_wp_table() {
+
+		$rows    = array();
+		$version = get_bloginfo( 'version' );
+		// Admin folder.
+		$rows['wp_admin'] = array(
+			'title'    => __( 'WordPress Admin', 'cloudinary' ),
+			'url'      => admin_url(),
+			'src_path' => $this->file_system->wp_admin_dir(),
+			'version'  => $version,
+		);
+		// Includes folder.
+		$rows['wp_includes'] = array(
+			'title'    => __( 'WordPress Includes', 'cloudinary' ),
+			'url'      => includes_url(),
+			'src_path' => $this->file_system->wp_includes_dir(),
+			'version'  => $version,
+		);
+
+		return array(
+			'slug'       => 'wordpress_files',
+			'type'       => 'folder_table',
+			'title'      => __( 'WordPress', 'cloudinary' ),
+			'root_paths' => $rows,
+		);
+	}
+
+	/**
+	 * Get the settings structure for the WordPress table.
+	 *
+	 * @return array
+	 */
+	protected function get_content_table() {
+
+		$rows               = array();
+		$uploads            = wp_get_upload_dir();
+		$rows['wp_content'] = array(
+			'title'    => __( 'Uploads', 'cloudinary' ),
+			'url'      => $uploads['baseurl'],
+			'src_path' => $uploads['basedir'],
+			'version'  => 0,
+		);
+
+		return array(
+			'slug'       => 'content_files',
+			'type'       => 'folder_table',
+			'title'      => __( 'Content', 'cloudinary' ),
+			'root_paths' => $rows,
+		);
+	}
+
+	/**
+	 * Setup the cache object.
+	 */
+	public function setup() {
+		$this->setup_setting_tabs();
+		$this->cache_point->init();
+	}
+
+	/**
+	 * Adds the individual setting tabs.
+	 */
+	protected function setup_setting_tabs() {
+		$cache_settings = $this->get_cache_settings();
+		foreach ( $cache_settings as $setting ) {
+			$callback = $setting->get_param( 'callback' );
+			if ( is_callable( $callback ) ) {
+				call_user_func( $callback ); // Init the settings.
+			}
+		}
+	}
+
+	/**
+	 * Get all the Cache settings.
+	 *
+	 * @return Setting[]
+	 */
+	public function get_cache_settings() {
+		static $settings = array();
+		if ( empty( $settings ) ) {
+			$main_setting = $this->settings->get_setting( 'cache_paths' );
+			foreach ( $main_setting->get_settings() as $slug => $setting ) {
+				$settings[ $slug ] = $setting;
+			}
+		}
+
+		return $settings;
+	}
+
+	/**
+	 * Check to see if cache setting is enabled.
+	 *
+	 * @param string $cache_setting The setting slug to check.
+	 *
+	 * @return bool
+	 */
+	protected function is_cache_setting_enabled( $cache_setting ) {
+
+		return 'on' === $this->settings->get_value( 'enable_full_site_cache' ) || 'on' === $this->settings->get_value( $cache_setting );
+	}
+
+	/**
+	 * Add paths for caching.
+	 *
+	 * @param string $setting             The setting to get paths from.
+	 * @param string $cache_point_setting The setting with the cache points.
+	 * @param string $all_cache_setting   The setting to define all on.
+	 */
+	public function add_cache_paths( $setting, $cache_point_setting, $all_cache_setting ) {
+
+		$settings     = $this->settings->find_setting( $setting );
+		$cache_points = $settings->find_setting( $cache_point_setting )->get_param( 'root_paths', array() );
+		foreach ( $cache_points as $slug => $cache_point ) {
+			$enable_full = $this->settings->get_value( 'enable_full_site_cache' );
+			$enable_all  = $settings->get_value();
+			// All on or Plugin is on.
+			if ( 'on' === $enable_full || 'on' === $enable_all[ $all_cache_setting ] || ( isset( $enable_all[ $slug ] ) && 'on' === $enable_all[ $slug ] ) ) {
+				$this->cache_point->register_cache_path( $cache_point['url'], $cache_point['src_path'], $cache_point['version'] );
+			}
+		}
+	}
+
+	/**
+	 * Add the plugin cache settings page.
+	 */
+	protected function add_plugin_settings() {
+
+		$plugins_setup = $this->get_plugins_table();
+		$params        = array(
+			'type'        => 'panel',
+			'title'       => __( 'Plugins', 'cloudinary' ),
+			'collapsible' => 'closed',
+			'attributes'  => array(
+				'header' => array(
+					'class' => array(
+						'full-width',
+					),
+				),
+				'wrap'   => array(
+					'class' => array(
+						'full-width',
+					),
+				),
+			),
+			array(
+				'type'        => 'on_off',
+				'slug'        => 'cache_all_plugins',
+				'description' => __( 'Deliver assets from all plugin folders', 'cloudinary' ),
+				'default'     => 'off',
+				'main'        => array(
+					'enable_full_site_cache',
+				),
+			),
+			array(
+				'type'      => 'group',
+				'condition' => array(
+					'cache_all_plugins' => false,
+				),
+				$plugins_setup,
+			),
+		);
+		$this->settings->create_setting( 'plugins_settings', $params, $this->settings->get_setting( 'cache_plugins' ) );
+		add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_plugin_cache_paths' ) );
+	}
+
+	/**
+	 * Add Plugin paths for caching.
+	 */
+	public function add_plugin_cache_paths() {
+
+		$this->add_cache_paths( 'cache_plugins', 'plugin_files', 'cache_all_plugins' );
+	}
+
+	/**
+	 * Add Theme Settings page.
+	 */
+	protected function add_theme_settings() {
+
+		$theme_setup = $this->get_theme_table();
+		$params      = array(
+			'type'        => 'panel',
+			'title'       => __( 'Themes', 'cloudinary' ),
+			'collapsible' => 'closed',
+			'attributes'  => array(
+				'header' => array(
+					'class' => array(
+						'full-width',
+					),
+				),
+				'wrap'   => array(
+					'class' => array(
+						'full-width',
+					),
+				),
+			),
+			array(
+				'type'        => 'on_off',
+				'slug'        => 'cache_all_themes',
+				'description' => __( 'Deliver all assets from active theme.', 'cloudinary' ),
+				'default'     => 'off',
+				'main'        => array(
+					'enable_full_site_cache',
+				),
+			),
+			array(
+				'type'      => 'group',
+				'condition' => array(
+					'cache_all_themes' => false,
+				),
+				$theme_setup,
+			),
+		);
+
+		$this->settings->create_setting( 'theme_settings', $params, $this->settings->get_setting( 'cache_themes' ) );
+		add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_theme_cache_paths' ) );
+	}
+
+	/**
+	 * Add Theme paths for caching.
+	 */
+	public function add_theme_cache_paths() {
+
+		$this->add_cache_paths( 'cache_themes', 'theme_files', 'cache_all_themes' );
+	}
+
+	/**
+	 * Add WP Settings page.
+	 */
+	protected function add_wp_settings() {
+
+		$wordpress_setup = $this->get_wp_table();
+		$params          = array(
+			'type'        => 'panel',
+			'title'       => __( 'WordPress', 'cloudinary' ),
+			'collapsible' => 'closed',
+			'attributes'  => array(
+				'header' => array(
+					'class' => array(
+						'full-width',
+					),
+				),
+				'wrap'   => array(
+					'class' => array(
+						'full-width',
+					),
+				),
+			),
+			array(
+				'type'        => 'on_off',
+				'slug'        => 'cache_all_wp',
+				'description' => __( 'Deliver all assets from WordPress core.', 'cloudinary' ),
+				'default'     => 'off',
+				'main'        => array(
+					'enable_full_site_cache',
+				),
+			),
+			array(
+				'type'      => 'group',
+				'condition' => array(
+					'cache_all_wp' => false,
+				),
+				$wordpress_setup,
+			),
+		);
+
+		$this->settings->create_setting( 'wordpress_settings', $params, $this->settings->get_setting( 'cache_wordpress' ) );
+		add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_wp_cache_paths' ) );
+	}
+
+	/**
+	 * Add Theme paths for caching.
+	 */
+	public function add_wp_cache_paths() {
+
+		$this->add_cache_paths( 'cache_wordpress', 'wordpress_files', 'cache_all_wp' );
+	}
+
+	/**
+	 * Add WP Settings page.
+	 */
+	protected function add_content_settings() {
+
+		$content_setup = $this->get_content_table();
+		$params        = array(
+			'type'        => 'panel',
+			'title'       => __( 'Content', 'cloudinary' ),
+			'collapsible' => 'closed',
+			'attributes'  => array(
+				'header' => array(
+					'class' => array(
+						'full-width',
+					),
+				),
+				'wrap'   => array(
+					'class' => array(
+						'full-width',
+					),
+				),
+			),
+			array(
+				'type'        => 'on_off',
+				'slug'        => 'cache_all_content',
+				'description' => __( 'Deliver all content assets from WordPress Media Library.', 'cloudinary' ),
+				'default'     => 'off',
+				'main'        => array(
+					'enable_full_site_cache',
+				),
+			),
+			array(
+				'type'      => 'group',
+				'condition' => array(
+					'cache_all_content' => false,
+				),
+				$content_setup,
+			),
+		);
+
+		$this->settings->create_setting( 'content_settings', $params, $this->settings->get_setting( 'cache_content' ) );
+		add_action( 'cloudinary_cache_init_cache_points', array( $this, 'add_content_cache_paths' ) );
+	}
+
+	/**
+	 * Add Content paths for caching.
+	 */
+	public function add_content_cache_paths() {
+		if ( ! is_admin() ) {
+			// Exclude content replacement in admin.
+			$this->add_cache_paths( 'cache_content', 'content_files', 'cache_all_content' );
+		};
+	}
+
+	/**
+	 * Enabled method for version if settings are enabled.
+	 *
+	 * @param bool $enabled Flag to enable.
+	 *
+	 * @return bool
+	 */
+	public function is_enabled( $enabled ) {
+		return ! is_null( $this->file_system ) && $this->connect->is_connected();
+	}
+
+	/**
+	 * Returns the setting definitions.
+	 *
+	 * @return array|null
+	 */
+	public function settings() {
+		if ( ! $this->file_system->enabled() ) {
+			return null;
+		}
+
+		$args = array(
+			'type'       => 'page',
+			'menu_title' => __( 'Site Cache', 'cloudinary' ),
+			'tabs'       => array(
+				'main_cache_page' => array(
+					'page_title' => __( 'Site Cache', 'cloudinary' ),
+					array(
+						'slug'       => 'cache_paths',
+						'type'       => 'panel',
+						'title'      => __( 'Cache Settings', 'cloudinary' ),
+						'attributes' => array(
+							'header' => array(
+								'class' => array(
+									'full-width',
+								),
+							),
+							'wrap'   => array(
+								'class' => array(
+									'full-width',
+								),
+							),
+						),
+						array(
+							'type'         => 'on_off',
+							'slug'         => 'enable_full_site_cache',
+							'title'        => __( 'Full CDN', 'cloudinary' ),
+							'tooltip_text' => __(
+								'Deliver all assets from Cloudinary.',
+								'cloudinary'
+							),
+							'description'  => __( 'Enable caching site assets.', 'cloudinary' ),
+							'default'      => 'off',
+						),
+						array(
+							'slug'     => 'cache_plugins',
+							'type'     => 'frame',
+							'callback' => array( $this, 'add_plugin_settings' ),
+						),
+						array(
+							'slug'     => 'cache_themes',
+							'type'     => 'frame',
+							'callback' => array( $this, 'add_theme_settings' ),
+						),
+						array(
+							'slug'     => 'cache_wordpress',
+							'type'     => 'frame',
+							'callback' => array( $this, 'add_wp_settings' ),
+						),
+						array(
+							'slug'     => 'cache_content',
+							'type'     => 'frame',
+							'callback' => array( $this, 'add_content_settings' ),
+						),
+					),
+					array(
+						'type'       => 'submit',
+						'attributes' => array(
+							'wrap' => array(
+								'class' => array(
+									'full-width',
+								),
+							),
+						),
+					),
+				),
+			),
+		);
+
+		return $args;
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-connect.php.html b/docs/php_class-connect.php.html new file mode 100644 index 000000000..8c77c1700 --- /dev/null +++ b/docs/php_class-connect.php.html @@ -0,0 +1,1125 @@ + + + + + Source: php/class-connect.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-connect.php

+ + + + + + + +
+
+
<?php
+/**
+ * Connect class for Cloudinary.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Component\Config;
+use Cloudinary\Component\Notice;
+use Cloudinary\Component\Setup;
+use Cloudinary\Connect\Api;
+use WP_Error;
+use WP_REST_Request;
+use WP_REST_Response;
+use WP_REST_Server;
+
+/**
+ * Cloudinary connection class.
+ *
+ * Sets up the initial cloudinary connection and makes the API object available for some uses.
+ */
+class Connect extends Settings_Component implements Config, Setup, Notice {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     Plugin Instance of the global plugin.
+	 */
+	protected $plugin;
+
+	/**
+	 * Holds the cloudinary API instance
+	 *
+	 * @since   0.1
+	 *
+	 * @var     Api
+	 */
+	public $api;
+
+	/**
+	 * Holds the cloudinary usage info.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     array
+	 */
+	public $usage;
+
+	/**
+	 * Holds the cloudinary credentials.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     array
+	 */
+	private $credentials = array();
+
+	/**
+	 * Holder of general notices.
+	 *
+	 * @var array
+	 */
+	protected $notices = array();
+
+	/**
+	 * Account Disabled Flag.
+	 *
+	 * @var bool
+	 */
+	public $disabled = false;
+
+	/**
+	 * Holds the meta keys for connect meta to maintain consistency.
+	 */
+	const META_KEYS = array(
+		'usage'      => '_cloudinary_usage',
+		'last_usage' => '_cloudinary_last_usage',
+		'signature'  => 'cloudinary_connection_signature',
+		'version'    => '_cloudinary_settings_version',
+		'url'        => 'cloudinary_url',
+		'connection' => 'cloudinary_connect',
+		'status'     => 'cloudinary_status',
+		'history'    => '_cloudinary_history',
+		'notices'    => 'rest_api_notices',
+	);
+
+	/**
+	 * Regex to match Cloudinary environment variable.
+	 */
+	const CLOUDINARY_VARIABLE_REGEX = '^(?:CLOUDINARY_URL=)?cloudinary://[0-9]+:[A-Za-z_\-0-9]+@[A-Za-z]+';
+
+	/**
+	 * Initiate the plugin resources.
+	 *
+	 * @param \Cloudinary\Plugin $plugin Instance of the plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+		$this->plugin        = $plugin;
+		$this->settings_slug = 'dashboard';
+		add_filter( 'pre_update_option_cloudinary_connect', array( $this, 'verify_connection' ) );
+		add_action( 'update_option_cloudinary_connect', array( $this, 'updated_option' ) );
+		add_action( 'cloudinary_status', array( $this, 'check_status' ) );
+		add_action( 'cloudinary_version_upgrade', array( $this, 'upgrade_connection' ) );
+		add_filter( 'cloudinary_setting_get_value', array( $this, 'maybe_connection_string_constant' ), 10, 2 );
+		add_filter( 'cloudinary_admin_pages', array( $this, 'register_meta' ) );
+		add_filter( 'cloudinary_api_rest_endpoints', array( $this, 'rest_endpoints' ) );
+	}
+
+	/**
+	 * Add endpoints to the \Cloudinary\REST_API::$endpoints array.
+	 *
+	 * @param array $endpoints Endpoints from the filter.
+	 *
+	 * @return array
+	 */
+	public function rest_endpoints( $endpoints ) {
+
+		$endpoints['test_connection'] = array(
+			'method'              => WP_REST_Server::CREATABLE,
+			'callback'            => array( $this, 'rest_test_connection' ),
+			'args'                => array(),
+			'permission_callback' => array( 'Cloudinary\REST_API', 'rest_can_connect' ),
+		);
+		$endpoints['save_wizard']     = array(
+			'method'              => WP_REST_Server::CREATABLE,
+			'callback'            => array( $this, 'rest_save_wizard' ),
+			'args'                => array(),
+			'permission_callback' => array( 'Cloudinary\REST_API', 'rest_can_connect' ),
+		);
+		$endpoints['test_rest_api']   = array(
+			'method'   => WP_REST_Server::READABLE,
+			'callback' => array( $this, 'rest_test_rest_api_connectivity' ),
+			'args'     => array(),
+		);
+
+		return $endpoints;
+	}
+
+	/**
+	 * Test a connection string.
+	 *
+	 * @param WP_REST_Request $request The request.
+	 *
+	 * @return WP_REST_Response
+	 */
+	public function rest_test_connection( WP_REST_Request $request ) {
+
+		$url    = $request->get_param( 'cloudinary_url' );
+		$result = $this->test_connection( $url );
+
+		return rest_ensure_response( $result );
+	}
+
+	/**
+	 * Test the REST API connectivity.
+	 *
+	 * @return WP_REST_Response
+	 */
+	public function rest_test_rest_api_connectivity() {
+		return new WP_REST_Response( null, 200 );
+	}
+
+	/**
+	 * Save the wizard setup.
+	 *
+	 * @param WP_REST_Request $request The request.
+	 *
+	 * @return WP_REST_Response
+	 */
+	public function rest_save_wizard( WP_REST_Request $request ) {
+
+		$url      = $request->get_param( 'cldString' );
+		$media    = true === $request->get_param( 'mediaLibrary' ) ? 'on' : 'off';
+		$nonmedia = true === $request->get_param( 'nonMedia' ) ? 'on' : 'off';
+		$advanced = true === $request->get_param( 'advanced' ) ? 'on' : 'off';
+
+		// Cloudinary URL.
+		if ( ! empty( $url ) && true !== $url ) {
+			$connect = $this->settings->get_setting( 'cloudinary_url' );
+			$connect->set_pending( $url );
+		}
+
+		// Autosync setup.
+		$autosync = $this->settings->get_setting( 'auto_sync' );
+		$autosync->set_pending( $media );
+		$image_optimisation = $this->settings->get_setting( 'image_optimization' );
+		$image_optimisation->set_pending( $media );
+		$video_optimisation = $this->settings->get_setting( 'video_optimization' );
+		$video_optimisation->set_pending( $media );
+
+		// Non-media setup.
+		$assets = $this->settings->get_setting( 'assets' );
+
+		foreach ( $assets->get_settings() as $asset ) {
+			$paths = $asset->get_setting( 'paths' );
+			foreach ( $paths->get_settings() as $path ) {
+				$path->set_pending( $nonmedia );
+			}
+		}
+		$enable_nonmedia = $this->settings->get_setting( 'cache.enable' );
+		$enable_nonmedia->set_pending( $nonmedia );
+
+		// Advanced.
+		$lazyload = $this->settings->get_setting( 'use_lazy_load' );
+		$lazyload->set_pending( $advanced );
+		$breakpoints = $this->settings->get_setting( 'enable_breakpoints' );
+		$breakpoints->set_pending( $advanced );
+
+		$this->settings->save();
+
+		if ( ! empty( $url ) ) {
+			// Warm the last uploaded items in the media library.
+			wp_remote_request(
+				Utils::rest_url( 'wp/v2/media' ),
+				array(
+					'timeout'  => 0.1,
+					'blocking' => false,
+				)
+			);
+		}
+
+		return rest_ensure_response( $this->settings->get_value() );
+	}
+
+	/**
+	 * Register meta data with the pages/settings.
+	 *
+	 * @param array $pages The pages array.
+	 *
+	 * @return array
+	 */
+	public function register_meta( $pages ) {
+
+		// Add data storage.
+		foreach ( self::META_KEYS as $slug => $option_name ) {
+			if ( 'url' === $slug || 'connection' === $slug ) {
+				continue; // URL and connection already set.
+			}
+			$pages['connect']['settings'][] = array(
+				'slug'        => $slug,
+				'option_name' => $option_name,
+				'type'        => 'data',
+			);
+		}
+
+		return $pages;
+	}
+
+	/**
+	 * Verify that the connection details are correct.
+	 *
+	 * @param array $data The submitted data to verify.
+	 *
+	 * @return array|WP_Error The data if cleared.
+	 */
+	public function verify_connection( $data ) {
+		$admin = $this->plugin->get_component( 'admin' );
+		if ( empty( $data['cloudinary_url'] ) ) {
+			delete_option( self::META_KEYS['signature'] );
+			$admin->add_admin_notice(
+				'connection_error',
+				__( 'Connection to Cloudinary has been removed.', 'cloudinary' ),
+				'error',
+				true
+			);
+			$this->plugin->settings->set_param( 'connected', false );
+
+			return $data;
+		}
+
+		$data['cloudinary_url'] = str_replace( 'CLOUDINARY_URL=', '', $data['cloudinary_url'] );
+		$current                = $this->plugin->settings->find_setting( 'connect' )->get_value();
+
+		// Same URL, return original data.
+		if ( $current['cloudinary_url'] === $data['cloudinary_url'] ) {
+			return $data;
+		}
+
+		// Pattern match to ensure validity of the provided url.
+		if ( ! preg_match( '~' . self::CLOUDINARY_VARIABLE_REGEX . '~', $data['cloudinary_url'] ) ) {
+			$admin->add_admin_notice(
+				'format_mismatch',
+				__( 'The environment variable URL must be in this format: cloudinary://API_KEY:API_SECRET@CLOUD_NAME', 'cloudinary' ),
+				'error'
+			);
+
+			return $current;
+		}
+
+		$result = $this->test_connection( $data['cloudinary_url'] );
+
+		if ( ! empty( $result['message'] ) ) {
+			$admin->add_admin_notice(
+				$result['type'],
+				$result['message'],
+				'error'
+			);
+
+			return $current;
+		}
+
+		$admin->add_admin_notice(
+			'connection_success',
+			__( 'Successfully connected to Cloudinary.', 'cloudinary' ),
+			'updated'
+		);
+
+		$this->settings->get_setting( 'signature' )->save_value( md5( $data['cloudinary_url'] ) );
+		$this->plugin->settings->set_param( 'connected', true );
+
+		return $data;
+	}
+
+	/**
+	 * Check whether a connection was established.
+	 *
+	 * @return boolean
+	 */
+	public function is_connected() {
+		$connected = $this->plugin->settings->get_param( 'connected', null );
+		if ( ! is_null( $connected ) ) {
+			return $connected;
+		}
+		$signature = $this->settings->get_value( 'signature' );
+
+		if ( null === $signature ) {
+			return false;
+		}
+
+		$connect_data = $this->settings->get_value( 'connect' );
+		$current_url  = isset( $connect_data['cloudinary_url'] ) ? $connect_data['cloudinary_url'] : null;
+
+		if ( null === $current_url ) {
+			return false;
+		}
+
+		if ( md5( $current_url ) !== $signature ) {
+			return false;
+		}
+
+		$status = $this->settings->get_value( 'status' );
+		if ( is_wp_error( $status ) ) {
+			// Error, we stop here.
+			if ( ! isset( $this->notices['__status'] ) ) {
+				$error   = $status->get_error_message();
+				$message = sprintf(
+				// translators: Placeholder refers the error from API.
+					__( 'Cloudinary Error: %s', 'cloudinary' ),
+					ucwords( $error )
+				);
+				if ( 'disabled account' === strtolower( $error ) ) {
+					// Flag general disabled.
+					$this->disabled = true;
+					$message        = sprintf(
+					// translators: Placeholders are <a> tags.
+						__( 'Cloudinary Account Disabled. %1$s Upgrade your plan %3$s or %2$s submit a support request %3$s for assistance.', 'cloudinary' ),
+						'<a href="https://cloudinary.com/console/upgrade_options" target="_blank">',
+						'<a href="https://support.cloudinary.com/hc/en-us/requests/new" target="_blank">',
+						'</a>'
+					);
+				}
+				$this->notices['__status'] = array(
+					'message'     => $message,
+					'type'        => 'error',
+					'dismissible' => true,
+				);
+			}
+
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Test the connection url.
+	 *
+	 * @param string $url The url to test.
+	 *
+	 * @return array
+	 */
+	public function test_connection( $url ) {
+		$result = array(
+			'type'    => 'connection_success',
+			'message' => null,
+			'url'     => $url,
+		);
+
+		$test  = wp_parse_url( $url );
+		$valid = array_filter(
+			array_keys( (array) $test ),
+			function ( $a ) {
+				return in_array( $a, array( 'scheme', 'host', 'user', 'pass' ), true );
+			}
+		);
+
+		if ( 4 > count( $valid ) ) {
+			$result['type']    = 'invalid_url';
+			$result['message'] = sprintf(
+			// translators: Placeholder refers to the expected URL format.
+				__( 'Incorrect Format. Expecting: %s', 'cloudinary' ),
+				'<code>cloudinary://API_KEY:API_SECRET@CLOUD_NAME</code>'
+			);
+
+			return $result;
+		}
+
+		$cname_str   = $this->extract_cname( $test );
+		$cname_valid = $this->validate_domain( $cname_str );
+
+		if ( $cname_str && ( ! substr_count( $cname_valid, '.' ) || false === $cname_valid ) ) {
+			$result['type']    = 'invalid_cname';
+			$result['message'] = __( 'CNAME is not a valid domain name.', 'cloudinary' );
+
+			return $result;
+		}
+
+		$this->config_from_url( $url );
+		$test_result = $this->check_status();
+
+		if ( is_wp_error( $test_result ) ) {
+			$error = $test_result->get_error_message();
+			if ( 'disabled account' !== strtolower( $error ) ) {
+				// Account Disabled, is still successful, so allow it, else we will never be able to change it.
+				$result['type'] = 'connection_error';
+			}
+			$result['message'] = ucwords( str_replace( '_', ' ', $test_result->get_error_message() ) );
+		} else {
+			$this->usage_stats( true );
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Get historical usage data.
+	 *
+	 * @param int $days Number of days to get.
+	 *
+	 * @return array
+	 */
+	public function history( $days = 1 ) {
+		$return  = array();
+		$history = get_option( self::META_KEYS['history'], array() );
+		$plan    = ! empty( $this->usage['plan'] ) ? $this->usage['plan'] : $this->credentials['cloud_name'];
+		for ( $i = 1; $i <= $days; $i++ ) {
+			$date = date_i18n( 'd-m-Y', strtotime( '- ' . $i . ' days' ) );
+			if ( ! isset( $history[ $plan ][ $date ] ) || is_wp_error( $history[ $plan ][ $date ] ) ) {
+				$history[ $plan ][ $date ] = $this->api->usage( $date );
+				uksort(
+					$history[ $plan ],
+					static function ( $a, $b ) {
+						$a = strtotime( $a );
+						$b = strtotime( $b );
+
+						if ( $a === $b ) {
+							return 0;
+						}
+
+						return $a < $b ? - 1 : 1;
+					}
+				);
+				$history[ $plan ] = array_slice( $history[ $plan ], -30 );
+			}
+			$return[ $date ] = $history[ $plan ][ $date ];
+		}
+		update_option( self::META_KEYS['history'], $history, false );
+
+		return $return;
+	}
+
+	/**
+	 * Upgrade method for version changes.
+	 *
+	 * @param string $previous_version The previous version number.
+	 * @param string $new_version      The New version number.
+	 */
+	public function upgrade_settings( $previous_version, $new_version ) {
+		// Check if we need to upgrade the history.
+		if ( version_compare( $previous_version, '3.1.0', '<' ) ) {
+			add_action(
+				'cloudinary_ready',
+				function () {
+					$history = get_option( self::META_KEYS['history'], array() );
+					$plan    = false;
+
+					if ( ! empty( $this->usage['plan'] ) ) {
+						$plan = $this->usage['plan'];
+					} elseif ( ! empty( $this->credentials['cloud_name'] ) ) {
+						$plan = $this->credentials['cloud_name'];
+					}
+
+					// Check whether history has migrated.
+					if ( ! empty( $plan ) && ! empty( $history[ $plan ] ) ) {
+						return;
+					}
+
+					// Cleanup history.
+					$new_history = array();
+
+					update_option( self::META_KEYS['history'], $new_history, false );
+				}
+			);
+		}
+	}
+
+	/**
+	 * After updating the cloudinary_connect option, remove flag.
+	 */
+	public function updated_option() {
+		if ( ! defined( 'REST_REQUEST' ) || true !== REST_REQUEST ) {
+			$page = 'cloudinary';
+			if ( $this->is_connected() ) {
+				$page .= '_connect';
+			}
+			wp_safe_redirect(
+				add_query_arg(
+					array(
+						'page' => $page,
+					),
+					admin_url( 'admin.php' )
+				)
+			);
+			exit;
+		}
+	}
+
+	/**
+	 * Check the status of Cloudinary.
+	 *
+	 * @return array|WP_Error
+	 */
+	public function check_status() {
+		$status = $this->test_ping();
+		$this->settings->get_setting( 'status' )->save_value( $status );
+
+		return $status;
+	}
+
+	/**
+	 * Do a ping test on the API.
+	 *
+	 * @return array|WP_Error
+	 */
+	public function test_ping() {
+		$test      = new Connect\Api( $this, $this->plugin->version );
+		$this->api = $test;
+
+		return $test->ping();
+	}
+
+	/**
+	 * Extracts the CNAME from a parsed connection URL.
+	 *
+	 * @param array $parsed_url Parsed URL.
+	 *
+	 * @return string|null
+	 */
+	protected function extract_cname( $parsed_url ) {
+		$cname = null;
+
+		if ( ! empty( $test['query'] ) ) {
+			$config_params = array();
+			wp_parse_str( $parsed_url['query'], $config_params );
+			$cname = isset( $config_params['cname'] ) ? $config_params['cname'] : $cname;
+		} elseif ( ! empty( $parsed_url['path'] ) ) {
+			$cname = ltrim( $parsed_url['path'], '/' );
+		}
+
+		return $cname;
+	}
+
+	/**
+	 * Safely validate a domain.
+	 *
+	 * @param string $domain The domain.
+	 *
+	 * @return bool
+	 */
+	protected function validate_domain( $domain ) {
+		$is_valid = false;
+
+		if ( defined( 'FILTER_VALIDATE_DOMAIN' ) ) { // phpcs:ignore PHPCompatibility.Constants.NewConstants.filter_validate_domainFound
+			$is_valid = filter_var( $domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME ); // phpcs:ignore PHPCompatibility.Constants.NewConstants
+		} else {
+			$domain   = 'https://' . $domain;
+			$is_valid = filter_var( $domain, FILTER_VALIDATE_URL );
+		}
+
+		return $is_valid;
+	}
+
+	/**
+	 * Get the Cloudinary credentials.
+	 *
+	 * @return array
+	 */
+	public function get_credentials() {
+		return $this->credentials;
+	}
+
+	/**
+	 * Get the cloud name if set.
+	 *
+	 * @return string|null
+	 */
+	public function get_cloud_name() {
+		return ! empty( $this->credentials['cloud_name'] ) ? $this->credentials['cloud_name'] : null;
+	}
+
+	/**
+	 * Set the config credentials from an array.
+	 *
+	 * @param array $data The config array data.
+	 *
+	 * @return array
+	 */
+	public function set_credentials( $data = array() ) {
+		$this->credentials = array_merge( $this->credentials, $data );
+
+		return $this->credentials;
+	}
+
+	/**
+	 * Set the credentials from the cloudinary url.
+	 *
+	 * @param string $url The Cloudinary URL.
+	 */
+	public function config_from_url( $url ) {
+		$parts = wp_parse_url( $url );
+		$creds = array();
+
+		foreach ( $parts as $type => $part ) {
+			switch ( $type ) {
+				case 'host':
+					$creds['cloud_name'] = $part;
+					break;
+				case 'user':
+					$creds['api_key'] = $part;
+					break;
+				case 'pass':
+					$creds['api_secret'] = $part;
+					break;
+			}
+		}
+
+		$this->set_credentials( $creds );
+
+		// Check for and Append query params.
+		if ( ! empty( $parts['query'] ) ) {
+			$config_params = array();
+			wp_parse_str( $parts['query'], $config_params );
+			if ( empty( $config_params['upload_prefix'] ) ) {
+				$this->set_credentials( array( 'upload_prefix' => reset( Api::$qualified_upload_prefixes ) ) );
+			}
+			if ( ! empty( $config_params ) ) {
+				$this->set_credentials( $config_params );
+			}
+		}
+
+		// Specifically set CNAME.
+		$cname = $this->extract_cname( $parts );
+		if ( ! empty( $cname ) ) {
+			$this->set_credentials( array( 'cname' => $cname ) );
+
+			if ( empty( $this->credentials['private_cdn'] ) ) {
+				$this->set_credentials( array( 'private_cdn' => 'true' ) );
+			}
+		}
+	}
+
+	/**
+	 * Setup connection
+	 *
+	 * @since  0.1
+	 */
+	public function setup() {
+		$this->setup_rest_api_cron();
+
+		// Get the cloudinary url from settings.
+		$cloudinary_url = $this->settings->get_value( 'cloudinary_url' );
+		if ( ! empty( $cloudinary_url ) ) {
+			$this->config_from_url( $cloudinary_url );
+			$this->api = new Connect\Api( $this, $this->plugin->version );
+			$this->usage_stats();
+			$this->setup_status_cron();
+			$this->plugin->settings->set_param( 'connected', $this->is_connected() );
+		}
+	}
+
+	/**
+	 * Setup Status cron.
+	 */
+	protected function setup_status_cron() {
+		Cron::register_process( 'check_status', array( $this, 'check_status' ), DAY_IN_SECONDS );
+	}
+
+	/**
+	 * Setup the REST API cron.
+	 *
+	 * @return void
+	 */
+	protected function setup_rest_api_cron() {
+		Cron::register_process( 'rest_api', array( $this, 'check_rest_api_connectivity' ), DAY_IN_SECONDS );
+	}
+
+	/**
+	 * Set the usage stats from the Cloudinary API.
+	 *
+	 * @param bool $refresh Flag to force a refresh.
+	 */
+	public function usage_stats( $refresh = false ) {
+		$stats = get_transient( self::META_KEYS['usage'] );
+		if ( empty( $stats ) || true === $refresh ) {
+			$last_usage = $this->settings->get_setting( 'last_usage' );
+			// Get users plan.
+			$stats = $this->api->usage();
+			if ( ! is_wp_error( $stats ) && ! empty( $stats['media_limits'] ) ) {
+				$stats['max_image_size'] = $stats['media_limits']['image_max_size_bytes'];
+				$stats['max_video_size'] = $stats['media_limits']['video_max_size_bytes'];
+				$last_usage->save_value( $stats );// Save the last successful call to prevgent crashing.
+			} else {
+				// Handle error by logging and fetching the last success.
+				// @todo : log issue.
+				$stats = $last_usage->get_value();
+			}
+			// Set useage state to the results, either new or the last, to prevent API hits.
+			set_transient( self::META_KEYS['usage'], $stats, HOUR_IN_SECONDS );
+		}
+		$this->usage = $stats;
+	}
+
+	/**
+	 * Get a usage stat for display.
+	 *
+	 * @param string      $type The type of stat to get.
+	 * @param string|null $stat The stat to get.
+	 *
+	 * @return bool|string
+	 */
+	public function get_usage_stat( $type, $stat = null ) {
+		$value = false;
+		if ( isset( $this->usage[ $type ] ) ) {
+			if ( is_string( $this->usage[ $type ] ) ) {
+				$value = $this->usage[ $type ];
+			} elseif ( is_array( $this->usage[ $type ] ) && isset( $this->usage[ $type ][ $stat ] ) ) {
+				$value = $this->usage[ $type ][ $stat ];
+			} elseif ( is_array( $this->usage[ $type ] ) ) {
+
+				if ( 'limit' === $stat && isset( $this->usage[ $type ]['usage'] ) ) {
+					$value = $this->usage[ $type ]['usage'];
+				} elseif (
+					'used_percent' === $stat
+					&& isset( $this->usage[ $type ]['credits_usage'] )
+					&& ! empty( $this->usage['credits']['limit'] )
+				) {
+					// Calculate percentage based on credit limit and usage.
+					$value = round( $this->usage[ $type ]['credits_usage'] / $this->usage['credits']['limit'] * 100, 2 );
+				}
+			}
+		}
+
+		return $value;
+	}
+
+	/**
+	 * Gets the config of a connection.
+	 */
+	public function get_config() {
+		$old_version = $this->settings->get_value( 'version' );
+		if ( empty( $old_version ) ) {
+			$old_version = '2.0.1';
+		}
+		if ( version_compare( $this->plugin->version, $old_version, '>' ) ) {
+			/**
+			 * Do action to allow upgrading of different areas.
+			 *
+			 * @since 2.3.1
+			 *
+			 * @hook cloudinary_version_upgrade
+			 *
+			 * @param $new_version {string} The version upgrading to.
+			 * @param $old_version {string} The version upgrading from.
+			 */
+			do_action( 'cloudinary_version_upgrade', $old_version, $this->plugin->version );
+		}
+	}
+
+	/**
+	 * Set usage notices if limits are towards higher end.
+	 */
+	public function usage_notices() {
+		if ( ! empty( $this->usage ) ) {
+			foreach ( $this->usage as $stat => $values ) {
+
+				if ( ! is_array( $values ) ) {
+					continue;
+				}
+				$usage = $this->get_usage_stat( $stat, 'used_percent' );
+				if ( empty( $usage ) ) {
+					continue;
+				}
+				$link      = 'https://cloudinary.com/console/lui/upgrade_options';
+				$link_text = __( 'upgrade your account', 'cloudinary' );
+				if ( 90 <= $usage ) {
+					// 90% used - show error.
+					$level = 'error';
+				} elseif ( 80 <= $usage ) {
+					$level = 'warning';
+				} elseif ( 70 <= $usage ) {
+					$level = 'neutral';
+				} else {
+					continue;
+				}
+
+				// translators: Placeholders are URLS and percentage values.
+				$message = sprintf(
+				/* translators: %1$s quota size, %2$s amount in percent, %3$s link URL, %4$s link anchor text. */
+					__(
+						'You are %2$s of the way through your monthly quota for %1$s on your Cloudinary account. If you exceed your quota, the Cloudinary plugin will be deactivated until your next billing cycle and your media assets will be served from your WordPress Media Library. You may wish to <a href="%3$s" target="_blank">%4$s</a> and increase your quota to ensure you maintain full functionality.',
+						'cloudinary'
+					),
+					ucwords( $stat ),
+					$usage . '%',
+					$link,
+					$link_text
+				);
+
+				$this->notices[] = array(
+					'icon'        => 'dashicons-cloudinary',
+					'message'     => $message,
+					'type'        => $level,
+					'dismissible' => true,
+					'duration'    => MONTH_IN_SECONDS,
+				);
+			}
+		}
+	}
+
+	/**
+	 * Setup the connection notices.
+	 *
+	 * @return void
+	 */
+	public function connectivity_notices() {
+		$plugin  = get_plugin_instance();
+		$notices = get_option( $plugin::KEYS['notices'], array() );
+
+		if ( ! empty( $notices[ self::META_KEYS['notices'] ] ) ) {
+			$this->notices[] = $notices[ self::META_KEYS['notices'] ];
+		}
+	}
+
+	/**
+	 * Get admin notices.
+	 */
+	public function get_notices() {
+		$this->usage_notices();
+		$this->connectivity_notices();
+
+		return array_filter( $this->notices );
+	}
+
+	/**
+	 * Upgrade connection settings.
+	 *
+	 * @param string $old_version The previous version.
+	 *
+	 * @uses action:cloudinary_version_upgrade
+	 */
+	public function upgrade_connection( $old_version ) {
+
+		if ( version_compare( $old_version, '2.0.0', '>' ) ) {
+			// Post V1 - quick check all details are valid.
+			$data = $this->settings->get_value( 'connect' );
+			if ( ! isset( $data['cloudinary_url'] ) || empty( $data['cloudinary_url'] ) ) {
+				return; // Not setup at all, abort upgrade.
+			}
+		} elseif ( version_compare( $old_version, '2.0.0', '<' ) ) {
+			// from V1 to V2.
+			$cld_url = get_option( self::META_KEYS['url'], null );
+			if ( empty( $cld_url ) ) {
+				return; // Upgrade from a non setup V1 nothing to upgrade.
+			}
+			$data = array(
+				'cloudinary_url' => $cld_url,
+			);
+			$key  = $this->settings->get_storage_key( $this->plugin->get_component( 'sync' )->settings_slug );
+			// Set auto sync off.
+			$sync = get_option( $key );
+			if ( empty( $sync ) ) {
+				$sync = array(
+					'auto_sync'         => '',
+					'cloudinary_folder' => '',
+				);
+			}
+			$sync['auto_sync'] = 'off';
+			update_option( $key, $sync );
+			delete_option( 'cloudinary_settings_cache' ); // remove the cache.
+		}
+
+		// Test upgraded details.
+		$data['cloudinary_url'] = str_replace( 'CLOUDINARY_URL=', '', $data['cloudinary_url'] );
+		$test                   = $this->test_connection( $data['cloudinary_url'] );
+
+		if ( 'connection_success' === $test['type'] ) {
+			$signature = md5( $data['cloudinary_url'] );
+
+			// remove filters as we've already verified it and 'add_settings_error()' isn't available yet.
+			remove_filter( 'pre_update_option_cloudinary_connect', array( $this, 'verify_connection' ) );
+			update_option( self::META_KEYS['connection'], $data );
+			update_option( self::META_KEYS['signature'], $signature );
+			update_option( self::META_KEYS['version'], $this->plugin->version );
+		}
+	}
+
+	/**
+	 * Checks if connection string constant is defined.
+	 *
+	 * @return bool
+	 */
+	public function has_connection_string_constant() {
+		return defined( 'CLOUDINARY_CONNECTION_STRING' );
+	}
+
+	/**
+	 * Filters the connection parts.
+	 *
+	 * @param mixed  $value   The default value.
+	 * @param string $setting The setting slug.
+	 *
+	 * @return mixed
+	 */
+	public function maybe_connection_string_constant( $value, $setting ) {
+		if ( ! $this->has_connection_string_constant() ) {
+			return $value;
+		}
+
+		static $url = null;
+
+		if ( empty( $url ) ) {
+			$url = str_replace( 'CLOUDINARY_URL=', '', CLOUDINARY_CONNECTION_STRING );
+		}
+
+		if ( 'cloudinary_url' === $setting ) {
+			$value = $url;
+		}
+
+		if ( 'signature' === $setting ) {
+			$value = md5( $url );
+		}
+
+		if ( 'connect' === $setting ) {
+			$value['cloudinary_url'] = $url;
+		}
+
+		return $value;
+	}
+
+	/**
+	 * Check if the switch account param is set.
+	 *
+	 * @return bool
+	 */
+	public function switch_account() {
+
+		$return = false;
+		if ( filter_input( INPUT_GET, 'switch-account', FILTER_VALIDATE_BOOLEAN ) ) {
+			return true;
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Check the REST API connectivity for Cloudinary's endpoints.
+	 *
+	 * @return array
+	 */
+	public static function check_rest_api_connectivity() {
+
+		$connectivity = self::test_rest_api_connectivity();
+		$plugin       = get_plugin_instance();
+		$notices      = get_option( $plugin::KEYS['notices'], array() );
+
+		if ( $connectivity['working'] ) {
+			unset( $notices[ self::META_KEYS['notices'] ] );
+			if ( empty( $notices ) ) {
+				delete_option( $plugin::KEYS['notices'] );
+			} else {
+				update_option( $plugin::KEYS['notices'], $notices, false );
+			}
+		} else {
+			update_option(
+				$plugin::KEYS['notices'],
+				array(
+					self::META_KEYS['notices'] => $connectivity,
+				),
+				false
+			);
+		}
+
+		return $connectivity;
+	}
+
+	/**
+	 * Test the REST API connectivity for Cloudinary's endpoints.
+	 *
+	 * @return array
+	 */
+	public static function test_rest_api_connectivity() {
+		$result = array(
+			'working' => true,
+			'message' => __( 'Cloudinary was able to connect to the WordPress REST API.', 'cloudinary' ),
+		);
+
+		$args = array(
+			'headers' => array(
+				'Cache-Control' => 'no-cache',
+			),
+		);
+
+		$url      = Utils::rest_url( REST_API::BASE . '/test_rest_api' );
+		$response = wp_remote_get( $url, $args );
+
+		if ( is_wp_error( $response ) ) {
+			$result = array(
+				'working' => false,
+				'message' => sprintf(
+					/* translators: 1: The WordPress error message. 2: The WordPress error code. */
+					__( 'The Cloudinary REST API endpoints are not available. Error: %1$s (%2$s)', 'cloudinary' ),
+					$response->get_error_message(),
+					$response->get_error_code()
+				),
+			);
+		} elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
+			$result = array(
+				'working' => false,
+				'message' => sprintf(
+					/* translators: 1: The WordPress error message. 2: The WordPress error code. */
+					__( 'The Cloudinary REST API endpoints are not available. Error: %1$s (%2$s)', 'cloudinary' ),
+					wp_remote_retrieve_response_message( $response ),
+					wp_remote_retrieve_response_code( $response )
+				),
+			);
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Get Connection String content for old settings.
+	 *
+	 * @return string
+	 */
+	protected function get_connection_string_content() {
+		ob_start();
+		include $this->plugin->dir_path . 'php/templates/connection-string.php'; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+
+		return ob_get_clean();
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-delivery-feature.php.html b/docs/php_class-delivery-feature.php.html new file mode 100644 index 000000000..bf5dc75ed --- /dev/null +++ b/docs/php_class-delivery-feature.php.html @@ -0,0 +1,270 @@ + + + + + Source: php/class-delivery-feature.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-delivery-feature.php

+ + + + + + + +
+
+
<?php
+/**
+ * Delivery Feature abstract.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Component\Assets;
+use Cloudinary\Settings\Setting;
+
+/**
+ * Class Delivery_Feature
+ *
+ * @package Cloudinary
+ */
+abstract class Delivery_Feature implements Assets {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     Plugin Instance of the global plugin.
+	 */
+	public $plugin;
+
+	/**
+	 * Holds the Media instance.
+	 *
+	 * @var Media
+	 */
+	protected $media;
+
+	/**
+	 * Holds the Delivery instance.
+	 *
+	 * @var Delivery
+	 */
+	protected $delivery;
+
+	/**
+	 * Holds the settings slug.
+	 *
+	 * @var string
+	 */
+	protected $settings_slug = 'delivery_feature';
+
+	/**
+	 * Holds the enabler slug.
+	 *
+	 * @var string
+	 */
+	protected $enable_slug = 'use_delivery_feature';
+
+	/**
+	 * Holds the settings.
+	 *
+	 * @var Setting
+	 */
+	protected $settings;
+
+	/**
+	 * Holds the config.
+	 *
+	 * @var array
+	 */
+	protected $config;
+
+	/**
+	 * The feature application priority.
+	 *
+	 * @var int
+	 */
+	protected $priority = 10;
+
+	/**
+	 * Holds the delivery type.
+	 *
+	 * @var string
+	 */
+	protected $type = 'image';
+
+	/**
+	 * Delivery_Feature constructor.
+	 *
+	 * @param Plugin $plugin Instance of the plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+
+		$this->plugin   = $plugin;
+		$this->media    = $plugin->get_component( 'media' );
+		$this->delivery = $plugin->get_component( 'delivery' );
+
+		add_action( 'cloudinary_admin_pages', array( $this, 'register_settings' ) );
+		add_action( 'cloudinary_init_settings', array( $this, 'setup' ) );
+	}
+
+	/**
+	 * Register hooks.
+	 */
+	public function init() {
+		$this->config = $this->settings->get_value( $this->settings_slug );
+		if ( $this->filter_is_active() ) {
+			$this->maybe_enqueue_assets();
+		}
+		if ( $this->is_enabled() ) {
+			$this->setup_hooks();
+		}
+	}
+
+	/**
+	 * Setup hooks used when enabled.
+	 */
+	protected function setup_hooks() {
+	}
+
+	/**
+	 * Enqueue assets if active and enabled.
+	 */
+	public function maybe_enqueue_assets() {
+		if ( $this->is_enabled() ) {
+			// Add filter to add features.
+			add_filter( "cloudinary_pre_{$this->type}_tag", array( $this, 'add_features' ), $this->priority );
+			add_action( 'wp_print_scripts', array( $this, 'enqueue_assets' ) );
+		}
+	}
+
+	/**
+	 * Add features to a tag element set.
+	 *
+	 * @param array $tag_element The tag element set.
+	 *
+	 * @return array
+	 */
+	public function add_features( $tag_element ) {
+		return $tag_element;
+	}
+
+	/**
+	 * Check if component is active.
+	 *
+	 * @return bool
+	 */
+	public function is_active() {
+		return ! Utils::is_admin();
+	}
+
+	/**
+	 * Register assets to be used for the class.
+	 */
+	public function register_assets() {
+	}
+
+	/**
+	 * Enqueue Assets.
+	 */
+	public function enqueue_assets() {
+
+	}
+
+	/**
+	 * Check to see if Breakpoints are enabled.
+	 *
+	 * @return bool
+	 */
+	public function is_enabled() {
+		return $this->filter_is_active() && 'on' === $this->config[ $this->enable_slug ];
+	}
+
+	/**
+	 * Add the settings.
+	 *
+	 * @param array $pages The pages to add to.
+	 *
+	 * @return array
+	 */
+	public function register_settings( $pages ) {
+		return $pages;
+	}
+
+	/**
+	 * Setup the class.
+	 */
+	public function setup() {
+		$this->settings = $this->plugin->settings;
+		$this->init();
+	}
+
+	/**
+	 * Filter is active.
+	 *
+	 * @return bool
+	 */
+	public function filter_is_active() {
+		$parts      = explode( '\\', get_called_class() );
+		$class_name = strtolower( array_pop( $parts ) );
+		$is_active  = $this->is_active();
+
+		/**
+		 * Filter to check if the feature is active.
+		 *
+		 * @hook  cloudinary_is_{$class_name}_active
+		 * @since 3.0.4
+		 *
+		 * @param $is_active {bool} Flag if active.
+		 */
+		return apply_filters( "cloudinary_is_{$class_name}_active", $is_active );
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-delivery.php.html b/docs/php_class-delivery.php.html new file mode 100644 index 000000000..12ddfb08c --- /dev/null +++ b/docs/php_class-delivery.php.html @@ -0,0 +1,2109 @@ + + + + + Source: php/class-delivery.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-delivery.php

+ + + + + + + +
+
+
<?php
+/**
+ * Cloudinary Delivery for delivery of cloudinary assets.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Component\Setup;
+use Cloudinary\Connect\Api;
+use Cloudinary\Media\Filter;
+use Cloudinary\Media\Global_Transformations;
+use Cloudinary\UI\Component\HTML;
+use Cloudinary\Delivery\Bypass;
+use Cloudinary\Relate\Relationship;
+
+/**
+ * Plugin Delivery class.
+ */
+class Delivery implements Setup {
+
+	/**
+	 * Holds the core plugin.
+	 *
+	 * @var Plugin
+	 */
+	protected $plugin;
+
+	/**
+	 * Holds the Media component.
+	 *
+	 * @var Media
+	 */
+	protected $media;
+
+	/**
+	 * Holds the Media\Filter component.
+	 *
+	 * @var Filter
+	 */
+	protected $filter;
+
+	/**
+	 * Holds the Sync component.
+	 *
+	 * @var Sync
+	 */
+	protected $sync;
+
+	/**
+	 * Hold the Post ID.
+	 *
+	 * @var null|int
+	 */
+	protected $current_post_id = null;
+
+	/**
+	 * Holds the Bypass instance.
+	 *
+	 * @var Bypass
+	 */
+	protected $bypass;
+
+	/**
+	 * Holds a list of found and valid urls.
+	 *
+	 * @var array
+	 */
+	public $found_urls = array();
+
+	/**
+	 * Holds a list of known urls.
+	 *
+	 * @var array
+	 */
+	public $known = array();
+
+	/**
+	 * Holds the list of unknown URLS.
+	 *
+	 * @var array
+	 */
+	public $unknown = array();
+
+	/**
+	 * Holds a list of known urls with public_ids.
+	 *
+	 * @var array
+	 */
+	public $usable = array();
+
+	/**
+	 * Holds a list of known urls without public_ids.
+	 *
+	 * @var array
+	 */
+	public $unusable = array();
+
+	/**
+	 * The meta data cache key to store URLS.
+	 *
+	 * @var string
+	 */
+	const META_CACHE_KEY = '_cld_replacements';
+
+	/**
+	 * Holds the captured post contexts
+	 *
+	 * @var array
+	 */
+	protected $post_contexts = array();
+
+	/**
+	 * Flag for doing metadata adds or updates.
+	 *
+	 * @var bool
+	 */
+	protected $doing_metadata = false;
+
+	/**
+	 * Component constructor.
+	 *
+	 * @param Plugin $plugin Global instance of the main plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+
+		$this->plugin = $plugin;
+		add_action( 'cloudinary_connected', array( $this, 'init' ) );
+	}
+
+	/**
+	 * Init the class when cloudinary is connected.
+	 */
+	public function init() {
+		$this->plugin->components['replace'] = new String_Replace( $this->plugin );
+		$this->media                         = $this->plugin->get_component( 'media' );
+		add_filter( 'cloudinary_filter_out_local', '__return_false' );
+		add_action( 'update_option_cloudinary_media_display', array( $this, 'clear_cache' ) );
+		add_action( 'cloudinary_flush_cache', array( $this, 'do_clear_cache' ) );
+		add_action( 'cloudinary_unsync_asset', array( $this, 'unsync_size_relationship' ) );
+		add_action( 'before_delete_post', array( $this, 'delete_size_relationship' ) );
+		add_action( 'delete_attachment', array( $this, 'delete_size_relationship' ) );
+		add_action( 'cloudinary_register_sync_types', array( $this, 'register_sync_type' ), 30 );
+		add_filter( 'rest_request_before_callbacks', array( $this, 'maybe_unset_attributes' ), 10, 3 );
+		add_action(
+			'the_post',
+			function ( $post ) {
+				$this->post_contexts[] = $post->ID;
+			}
+		);
+
+		// Add Bypass options.
+		$this->bypass = new Bypass( $this->plugin );
+
+		// Add relation checking on front.
+		if ( ! is_admin() ) {
+			add_filter( 'wp_get_attachment_url', array( $this, 'ensure_relation' ), 10, 2 );
+		}
+	}
+
+	/**
+	 * Determine if attributes should be added to image tags.
+	 *
+	 * @param WP_REST_Response     $response The response object.
+	 * @param WP_REST_Server       $handler  The request handler.
+	 * @param WP_REST_Request|null $request The request object, if available.
+	 *
+	 * @return WP_REST_Response
+	 */
+	public function maybe_unset_attributes( $response, $handler, $request ) {
+		$route = $request->get_route();
+
+		if (
+			(bool) $request->get_header( 'x-cld-fetch-from-editor' )
+			|| (
+				false !== strpos( $route, 'wp/v2/media' )
+				&& 'edit' === $request->get_param( 'context' )
+			)
+		) {
+			add_filter( 'cloudinary_skip_parse_element', '__return_true' );
+		}
+
+		return $response;
+	}
+
+	/**
+	 * Maybe filter out Cloudinary URLs in post meta.
+	 *
+	 * @param null|bool $check      Whether to allow adding metadata for the given type.
+	 * @param int       $object_id  The ID of the object metadata is for.
+	 * @param string    $meta_key   The Metadata key.
+	 * @param mixed     $meta_value Metadata value.
+	 *
+	 * @return null|bool
+	 */
+	public function maybe_filter_out_metadata( $check, $object_id, $meta_key, $meta_value ) {
+
+		$internal_keys = array_merge(
+			Sync::META_KEYS,
+			array(
+				self::META_CACHE_KEY,
+			)
+		);
+
+		// Don't filter out metadata if we're dealing with Cloudinary internals.
+		if ( in_array( $meta_key, $internal_keys, true ) ) {
+			return $check;
+		}
+
+		if ( $this->doing_metadata ) {
+			return $check;
+		}
+
+		$this->doing_metadata = true;
+		$current_filter       = current_filter();
+
+		list( $action, $object ) = explode( '_', $current_filter );
+
+		$process_meta_value = $this->filter_out_cloudinary( $meta_value );
+
+		if ( $process_meta_value !== $meta_value ) {
+			$meta_value = $process_meta_value;
+			$check      = call_user_func( "{$action}_{$object}_meta", $object_id, $meta_key, $meta_value );
+		}
+
+		$this->doing_metadata = false;
+
+		return $check;
+	}
+
+	/**
+	 * Filter out Cloudinary URLS and replace with local.
+	 *
+	 * @param string $content The content to filter.
+	 *
+	 * @return string
+	 */
+	public function filter_out_cloudinary( $content ) {
+
+		static $globals;
+
+		if ( ! $globals ) {
+			$image   = $this->media->apply_default_transformations( array(), 'image' );
+			$video   = $this->media->apply_default_transformations( array(), 'video' );
+			$globals = array(
+				'image' => Api::generate_transformation_string( $image, 'image' ),
+				'video' => Api::generate_transformation_string( $video, 'video' ),
+			);
+		}
+		$unslashed       = false;
+		$working_content = $content;
+		if ( is_string( $working_content ) && ! is_numeric( $working_content ) ) {
+			$maybe_encoded = json_decode( $working_content, false );
+			if ( ! is_null( $maybe_encoded ) ) {
+				$working_content = $maybe_encoded;
+			}
+		}
+		if ( String_Replace::is_iterable( $working_content ) ) {
+			$working_content = $this->plugin->components['replace']->flatten( $working_content );
+		} else {
+			$unslash_maybe = wp_unslash( $working_content );
+			$unslashed     = $unslash_maybe !== $working_content;
+			if ( $unslashed ) {
+				$working_content = $unslash_maybe;
+			}
+		}
+		$base_urls       = array_unique( Utils::extract_urls( $working_content ) );
+		$cloudinary_urls = array_filter( $base_urls, array( $this->media, 'is_cloudinary_url' ) ); // clean out empty urls.
+		$urls            = array();
+		if ( empty( $cloudinary_urls ) ) {
+			return $content;
+		}
+		foreach ( $cloudinary_urls as $url ) {
+			$public_id = $this->media->get_public_id_from_url( $url );
+			if ( ! empty( $public_id ) ) {
+				$urls[ $public_id ] = $url;
+			}
+		}
+
+		$results = Utils::query_relations( array_keys( $urls ) );
+		String_Replace::reset();
+		foreach ( $results as $result ) {
+			if ( ! isset( $urls[ $result['public_id'] ] ) ) {
+				continue;
+			}
+
+			$original_url = $urls[ $result['public_id'] ];
+			if ( ! empty( $result['transformations'] ) ) {
+				$original_url = str_replace( $result['transformations'] . '/', '/', $original_url );
+			}
+			$size            = $this->media->get_size_from_url( $original_url );
+			$transformations = $this->media->get_transformations_from_string( $original_url );
+			if ( 'image' === $this->media->get_resource_type( $result['post_id'] ) ) {
+				$attachment_url = wp_get_attachment_image_url( $result['post_id'], $size );
+			} else {
+				$attachment_url = wp_get_attachment_url( $result['post_id'] );
+			}
+			$query_args = array();
+			wp_parse_str( wp_parse_url( $original_url, PHP_URL_QUERY ), $query_args );
+			if ( ! empty( $query_args['cld_overwrite'] ) ) {
+				$attachment_url = add_query_arg( 'cld_overwrite', true, $attachment_url );
+			}
+			if ( ! empty( $transformations ) ) {
+				$transformations = array_filter(
+					$transformations,
+					static function ( $item ) {
+						return ! isset( $item['crop'] ) && ! isset( $item['width'] ) && ! isset( $item['height'] );
+					}
+				);
+				$transformations = Api::generate_transformation_string( $transformations );
+				if ( ! empty( $transformations ) && ! in_array( $transformations, $globals, true ) ) {
+					$attachment_url = add_query_arg( 'cld_params', $transformations, $attachment_url );
+				}
+			}
+
+			String_Replace::replace( $urls[ $result['public_id'] ], $attachment_url );
+		}
+
+		$content = String_Replace::do_replace( $content );
+
+		return $content;
+	}
+
+	/**
+	 * Ensure that an asset has a relation on front end.
+	 *
+	 * @param string $url           The URL of the asset.
+	 * @param int    $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public function ensure_relation( $url, $attachment_id ) {
+		static $urls = array();
+		if ( empty( $urls[ $attachment_id ] ) && ! $this->media->get_post_meta( $attachment_id, Sync::META_KEYS['relationship'], true ) ) {
+			$urls[ $attachment_id ] = true;
+			$this->sync->get_sync_type( $attachment_id );
+		}
+
+		return $url;
+	}
+
+	/**
+	 * Generate the delivery signature.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public function generate_signature( $attachment_id ) {
+		static $sql;
+		if ( ! $sql ) {
+			$sql = Utils::get_table_sql();
+		}
+		$public_id    = null;
+		$relationship = Relationship::get_relationship( $attachment_id );
+		if ( $relationship instanceof Relationship ) {
+			$public_id = $relationship->public_id;
+		}
+
+		$sizes              = $this->get_sized( $attachment_id );
+		$settings_signature = self::get_settings_signature();
+		$relation_signature = $this->media->get_post_meta( $attachment_id, Sync::META_KEYS['relationship'], true );
+
+		return wp_json_encode( $sizes ) . $public_id . $sql . $settings_signature . $relation_signature . Utils::get_media_context( $attachment_id );
+	}
+
+	/**
+	 * Is attachment deliverable in the FE.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return bool
+	 */
+	public function is_deliverable( $attachment_id ) {
+		$is = false;
+
+		if ( wp_attachment_is_image( $attachment_id ) && 'on' === $this->plugin->settings->get_value( 'image_delivery' ) ) {
+			$is = true;
+		}
+
+		if ( ! $is && wp_attachment_is( 'video', $attachment_id ) && 'on' === $this->plugin->settings->get_value( 'video_delivery' ) ) {
+			$is = true;
+		}
+
+		// Ensure that the attachment has dimensions to be delivered.
+		if ( $is ) {
+			$meta = wp_get_attachment_metadata( $attachment_id, true );
+			$is   = ! empty( $meta['width'] ) && ! empty( $meta['height'] );
+		}
+
+		if ( ! $is ) {
+			$is = ! wp_attachment_is_image( $attachment_id ) && ! wp_attachment_is( 'video', $attachment_id );
+		}
+
+		$svg = $this->plugin->get_component( 'svg' );
+
+		if ( ! $is && $svg->is_active() && $svg::is_svg( $attachment_id ) && 'on' === $this->plugin->settings->get_value( 'image_delivery' ) ) {
+			$is = true;
+		}
+
+		/**
+		 * Filter deliverable attachments.
+		 *
+		 * @hook   cloudinary_is_deliverable
+		 *
+		 * @param $is            {bool} The default value.
+		 * @param $attachment_id {int}  The attachment ID.
+		 *
+		 * @return {bool}
+		 */
+		return apply_filters( 'cloudinary_is_deliverable', $is, $attachment_id );
+	}
+
+	/**
+	 * Create delivery entries.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 */
+	public function create_delivery( $attachment_id ) {
+		$relationship    = Relationship::get_relationship( $attachment_id );
+		$transformations = null;
+		// Preserve pre-existing transformations.
+		if ( $relationship instanceof Relationship ) {
+			$data            = $relationship->get_data();
+			$transformations = isset( $data['transformations'] ) ? $data['transformations'] : null;
+		}
+		$this->delete_size_relationship( $attachment_id );
+		$size      = $this->get_sized( $attachment_id );
+		$public_id = $this->media->has_public_id( $attachment_id ) ? $this->media->get_public_id( $attachment_id ) : null;
+		$base      = $this->get_content_path();
+		$sized_url = '';
+		$wh        = '0x0'; // phpcs:ignore PHPCompatibility.Numbers.RemovedHexadecimalNumericStrings.Found, PHPCompatibility.Miscellaneous.ValidIntegers.HexNumericStringFound
+		// Some attachments do not have Sizes.
+		if ( ! empty( $size ) ) {
+			$sized_url = $size['sized_url'];
+			$wh        = $size['size'];
+		}
+
+		if ( empty( $sized_url ) ) {
+			$sized_url = Utils::get_path_from_url( wp_get_attachment_url( $attachment_id ), true );
+		}
+		self::create_size_relation( $attachment_id, $sized_url, $wh, $base );
+		// Update public ID and type.
+		self::update_size_relations_public_id( $attachment_id, $public_id );
+		self::update_size_relations_state( $attachment_id, 'inherit' );
+		self::update_size_relations_transformations( $attachment_id, $transformations );
+		$this->sync->set_signature_item( $attachment_id, 'delivery' );
+	}
+
+	/**
+	 * Sync method for the relation type.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 */
+	public function update_relation( $attachment_id ) {
+		$public_id = $this->media->has_public_id( $attachment_id ) ? $this->media->get_public_id( $attachment_id ) : null;
+		// Update public ID and type.
+		self::update_size_relations_public_id( $attachment_id, $public_id );
+		$this->sync->set_signature_item( $attachment_id, 'relation' );
+	}
+
+	/**
+	 * Add our delivery sync type.
+	 */
+	public function register_sync_type() {
+		$structure = array(
+			'asset_state' => 0,
+			'generate'    => '__return_false',
+			'priority'    => 0.5,
+			'sync'        => array( $this, 'create_delivery' ),
+			'validate'    => array( $this, 'is_deliverable' ),
+			'state'       => '',
+			'note'        => '',
+			'realtime'    => true,
+		);
+		$this->sync->register_sync_type( 'delivery', $structure );
+
+		$structure = array(
+			'asset_state' => 0,
+			'generate'    => array( $this, 'generate_signature' ), // Method to generate a signature.
+			'priority'    => 50,
+			'sync'        => array( $this, 'update_relation' ),
+			'validate'    => array( $this, 'is_deliverable' ),
+			'state'       => '',
+			'note'        => '',
+			'realtime'    => true,
+		);
+		$this->sync->register_sync_type( 'relation', $structure );
+	}
+
+	/**
+	 * Get the base content path.
+	 *
+	 * @return string
+	 */
+	protected function get_content_path() {
+		$dirs = wp_get_upload_dir();
+
+		return ltrim( wp_parse_url( trailingslashit( $dirs['baseurl'] ), PHP_URL_PATH ), '/' );
+	}
+
+	/**
+	 * Remove a delivery relationship on delete of a post.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 */
+	public function delete_size_relationship( $attachment_id ) {
+		$relationship = Relationship::get_relationship( $attachment_id );
+		$relationship->delete();
+
+		/**
+		 * Action to flush delivery caches.
+		 *
+		 * @hook   cloudinary_flush_cache
+		 * @since  3.0.0
+		 */
+		do_action( 'cloudinary_flush_cache' );
+	}
+
+	/**
+	 * Disable a delivery relationship on unsync.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 */
+	public function unsync_size_relationship( $attachment_id ) {
+		self::update_size_relations_public_id( $attachment_id, null );
+		self::update_size_relations_state( $attachment_id, 'disable' );
+		self::update_size_relations_transformations( $attachment_id, null );
+	}
+
+	/**
+	 * Get the different sizes for an attachment.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return array
+	 */
+	public function get_sized( $attachment_id ) {
+		static $sizes = array(), $registered_sizes;
+		if ( ! $registered_sizes && is_callable( 'wp_get_registered_image_subsizes' ) ) {
+			$registered_sizes = wp_get_registered_image_subsizes();
+		}
+		if ( empty( $sizes[ $attachment_id ] ) ) {
+			$sizes[ $attachment_id ] = array();
+			$meta                    = wp_get_attachment_metadata( $attachment_id, true );
+			if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) {
+				// Keep the full URL for cloudinary_assets.
+				if ( Assets::POST_TYPE_SLUG === get_post_type( $attachment_id ) ) {
+					$local_url = Utils::clean_url( $this->media->local_url( $attachment_id ), true );
+				} else {
+					$local_url = Utils::get_path_from_url( $this->media->local_url( $attachment_id ), true );
+				}
+				$sizes[ $attachment_id ] = array(
+					'sized_url' => $local_url,
+					'size'      => $meta['width'] . 'x' . $meta['height'],
+				);
+
+				return $sizes[ $attachment_id ];
+			}
+		}
+
+		return $sizes[ $attachment_id ];
+	}
+
+	/**
+	 * Update relationship public ID.
+	 *
+	 * @param int         $attachment_id The attachment ID.
+	 * @param null|string $public_id     The public ID.
+	 */
+	public static function update_size_relations_public_id( $attachment_id, $public_id ) {
+		$relationship = Relationship::get_relationship( $attachment_id );
+
+		if ( $relationship instanceof Relationship ) {
+			$relationship->public_id     = $public_id;
+			$relationship->public_hash   = md5( (string) $public_id );
+			$relationship->signature     = self::get_settings_signature();
+			$relationship->media_context = Utils::get_media_context( $attachment_id );
+			$relationship->save();
+		}
+	}
+
+	/**
+	 * Update relationship status.
+	 *
+	 * @param int    $attachment_id The attachment ID.
+	 * @param string $state         The state to set.
+	 */
+	public static function update_size_relations_state( $attachment_id, $state ) {
+		$relationship = Relationship::get_relationship( $attachment_id );
+
+		if ( $relationship instanceof Relationship ) {
+			$relationship->post_state = $state;
+			$relationship->save();
+		}
+
+		/**
+		 * Action to flush delivery caches.
+		 *
+		 * @hook   cloudinary_flush_cache
+		 * @since  3.0.0
+		 */
+		do_action( 'cloudinary_flush_cache' );
+	}
+
+	/**
+	 * Update relationship transformations.
+	 *
+	 * @param int         $attachment_id   The attachment ID.
+	 * @param string|null $transformations The transformations to set.
+	 */
+	public static function update_size_relations_transformations( $attachment_id, $transformations ) {
+		Relate::update_transformations( $attachment_id, $transformations );
+	}
+
+	/**
+	 * Delete unneeded sizes in bulk by ID.
+	 *
+	 * @param array $ids The IDs to delete.
+	 */
+	public static function delete_bulk_size_relations( $ids ) {
+		global $wpdb;
+		$ids       = (array) $ids;
+		$list      = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
+		$tablename = Utils::get_relationship_table();
+		$sql       = "DELETE from {$tablename} WHERE id IN( {$list} )";
+		$prepared  = $wpdb->prepare( $sql, $ids ); // phpcs:ignore WordPress.DB
+
+		$wpdb->query( $prepared );// phpcs:ignore WordPress.DB
+
+		/**
+		 * Action to flush delivery caches.
+		 *
+		 * @hook   cloudinary_flush_cache
+		 * @since  3.0.0
+		 */
+		do_action( 'cloudinary_flush_cache' );
+	}
+
+	/**
+	 * Create a size relationship.
+	 *
+	 * @param int    $attachment_id The attachment ID.
+	 * @param string $sized_url     The sized url.
+	 * @param string $size          The size in (width)x(height) format.
+	 * @param string $parent_path   The path of the parent if external.
+	 *
+	 * @return false|int
+	 */
+	public static function create_size_relation( $attachment_id, $sized_url, $size = '0x0', $parent_path = '' ) { // phpcs:ignore PHPCompatibility.Numbers.RemovedHexadecimalNumericStrings.Found, PHPCompatibility.Miscellaneous.ValidIntegers.HexNumericStringFound
+		global $wpdb;
+		static $media;
+		if ( ! $media ) {
+			$media = get_plugin_instance()->get_component( 'media' );
+		}
+		$type            = 'attachment' === get_post_type( $attachment_id ) ? 'media' : 'asset';
+		$resource        = $media->get_resource_type( $attachment_id );
+		$width_height    = explode( 'x', $size );
+		$transformations = $media->get_post_meta( $attachment_id, Sync::META_KEYS['transformation'], true );
+		if ( ! is_null( $transformations ) ) {
+			$media->delete_post_meta( $attachment_id, Sync::META_KEYS['transformation'] );
+		}
+
+		$data = array(
+			'post_id'         => $attachment_id,
+			'parent_path'     => $parent_path,
+			'sized_url'       => $sized_url,
+			'media_context'   => Utils::get_media_context( $attachment_id ),
+			'width'           => $width_height[0] ? $width_height[0] : 0,
+			'height'          => $width_height[1] ? $width_height[1] : 0,
+			'format'          => Utils::pathinfo( $sized_url, PATHINFO_EXTENSION ),
+			'sync_type'       => $type,
+			'post_state'      => 'inherit',
+			'transformations' => ! empty( $transformations ) ? Api::generate_transformation_string( $transformations, $resource ) : null,
+			'signature'       => self::get_settings_signature(),
+			'url_hash'        => md5( $sized_url ),
+			'parent_hash'     => md5( $parent_path ),
+		);
+
+		$insert_id = false;
+		$created   = $wpdb->replace( Utils::get_relationship_table(), $data ); // phpcs:ignore WordPress.DB
+		if ( 0 < $created ) {
+			$insert_id = $wpdb->insert_id;
+			$media->update_post_meta( $attachment_id, Sync::META_KEYS['relationship'], self::get_settings_signature() );
+		}
+
+		return $insert_id;
+	}
+
+	/**
+	 * Get a signature of the current settings that result in a sync check.
+	 *
+	 * @return string string
+	 */
+	public static function get_settings_signature() {
+		static $signature;
+		if ( ! $signature ) {
+			$settings  = get_plugin_instance()->settings->get_value( 'cloudinary_url', 'sync_media' );
+			$signature = md5( wp_json_encode( $settings ) );
+		}
+
+		return $signature;
+	}
+
+	/**
+	 * Setup early needed hooks.
+	 */
+	protected function setup_hooks() {
+		// Add filters.
+		add_filter( 'content_save_pre', array( $this, 'filter_out_cloudinary' ) );
+		add_action( 'save_post', array( $this, 'remove_replace_cache' ) );
+		add_action( 'cloudinary_string_replace', array( $this, 'catch_urls' ), 10, 2 );
+		add_filter( 'post_thumbnail_html', array( $this, 'process_featured_image' ), 100, 3 );
+
+		add_filter( 'cloudinary_current_post_id', array( $this, 'get_current_post_id' ) );
+		add_filter( 'the_content', array( $this, 'add_post_id' ) );
+		add_action( 'wp_resource_hints', array( $this, 'dns_prefetch' ), 10, 2 );
+
+		$metadata = Utils::METADATA;
+
+		foreach ( $metadata['actions'] as $action ) {
+			foreach ( $metadata['objects'] as $object ) {
+				$inline_action = str_replace( '{object}', $object, $action );
+				add_action( $inline_action, array( $this, 'maybe_filter_out_metadata' ), 10, 4 );
+			}
+		}
+
+		// Clear cache on taxonomy update.
+		$taxonomies = get_taxonomies( array( 'show_ui' => true ) );
+		foreach ( $taxonomies as $taxonomy ) {
+			add_action( "saved_{$taxonomy}", array( $this, 'clear_cache' ) );
+		}
+	}
+
+	/**
+	 * Add DNS prefetch link tag for assets.
+	 *
+	 * @param array  $urls          URLs to print for resource hints.
+	 * @param string $relation_type The relation type the URLs are printed for, e.g. 'preconnect' or 'prerender'.
+	 *
+	 * @return array
+	 */
+	public function dns_prefetch( $urls, $relation_type ) {
+
+		/**
+		 * Filter to provide option to omit prefetch.
+		 *
+		 * @hook   cloudinary_dns_prefetch_types
+		 * @since 3.2.12
+		 * @default array ( 'dns-prefetch', 'preconnect' )
+		 *
+		 * @param $types {array} The types of resource hints to use.
+		 *
+		 * @return {array} The modified resource hints to use.
+		 */
+		$resource_hints = apply_filters( 'cloudinary_dns_prefetch_types', array( 'dns-prefetch', 'preconnect' ) );
+
+		if ( in_array( $relation_type, $resource_hints, true ) ) {
+			$urls[] = $this->media->base_url;
+		}
+
+		return $urls;
+	}
+
+	/**
+	 * Clear cached meta.
+	 */
+	public function clear_cache() {
+
+		/**
+		 * Action to flush delivery caches.
+		 *
+		 * @hook   cloudinary_flush_cache
+		 * @since  3.0.0
+		 */
+		do_action( 'cloudinary_flush_cache' );
+	}
+
+	/**
+	 * Delete cached metadata.
+	 *
+	 * @param bool $hard Whether to hard flush the cache.
+	 */
+	public function do_clear_cache( $hard = true ) {
+		delete_post_meta_by_key( self::META_CACHE_KEY );
+
+		if ( $hard ) {
+			wp_cache_flush();
+		}
+	}
+
+	/**
+	 * Add the Post ID to images and videos.
+	 *
+	 * @param string $content The content.
+	 *
+	 * @return string
+	 */
+	public function add_post_id( $content ) {
+
+		return str_replace(
+			array(
+				'wp-image-',
+				'wp-video-',
+			),
+			array(
+				'wp-post-' . get_the_ID() . ' wp-image-',
+				'wp-post-' . get_the_ID() . ' wp-video-',
+			),
+			$content
+		);
+	}
+
+	/**
+	 * Get the current post ID.
+	 *
+	 * @return int|null
+	 */
+	public function get_current_post_id() {
+
+		return $this->current_post_id ? $this->current_post_id : null;
+	}
+
+	/**
+	 * Setup component.
+	 */
+	public function setup() {
+
+		$this->filter = $this->media->filter;
+		$this->sync   = $this->media->sync;
+
+		$this->setup_hooks();
+	}
+
+	/**
+	 * Init delivery.
+	 */
+	protected function init_delivery() {
+
+		// Reset internals.
+		$this->known      = array();
+		$this->unknown    = array();
+		$this->found_urls = array();
+		$this->unusable   = array();
+
+		add_filter( 'wp_calculate_image_srcset', array( $this->media, 'image_srcset' ), 10, 5 );
+
+		/**
+		 * Action indicating that the delivery is starting.
+		 *
+		 * @hook  cloudinary_init_delivery
+		 * @since 2.7.5
+		 *
+		 * @param $delivery {Delivery} The delivery object.
+		 */
+		do_action( 'cloudinary_init_delivery', $this );
+	}
+
+	/**
+	 * Add classes to the featured image tag.
+	 *
+	 * @param string $html          The image the HTML to add to.
+	 * @param int    $post_id       Ignored.
+	 * @param int    $attachment_id The attachment_id.
+	 *
+	 * @return string
+	 */
+	public function process_featured_image( $html, $post_id, $attachment_id ) {
+
+		if ( empty( $html ) ) {
+			return $html; // Ignore empty tags.
+		}
+
+		$tags = $this->get_media_tags( $html, 'img' );
+		$tags = array_map( array( $this, 'parse_element' ), $tags );
+		$tags = array_filter( $tags );
+		foreach ( $tags as $tag_element ) {
+			// Get tag element.
+			$tag_element['id']              = $attachment_id;
+			$tag_element['context']         = $post_id;
+			$tag_element['atts']['class'][] = 'wp-image-' . $attachment_id;
+			$tag_element['atts']['class'][] = 'wp-post-' . $post_id;
+
+			if ( true === (bool) get_post_meta( $post_id, Global_Transformations::META_FEATURED_IMAGE_KEY, true ) ) {
+				$tag_element['atts']['class'][] = 'cld-overwrite';
+			}
+
+			$new_tag = HTML::build_tag( $tag_element['tag'], $tag_element['atts'] );
+			$html    = str_replace( $tag_element['original'], $new_tag, $html );
+		}
+
+		return $html;
+	}
+
+	/**
+	 * Delete the content replacement cache data.
+	 *
+	 * @param int $post_id The post ID to remove cache from.
+	 */
+	public function remove_replace_cache( $post_id ) {
+
+		delete_post_meta( $post_id, self::META_CACHE_KEY );
+	}
+
+	/**
+	 * Find the attachment sizes from a list of URLS.
+	 */
+	public function find_attachment_size_urls() {
+
+		global $wpdb;
+		$dirs    = wp_get_upload_dir();
+		$baseurl = Utils::clean_url( $dirs['baseurl'] );
+		$search  = array();
+
+		foreach ( $this->unknown as $url ) {
+			$url      = ltrim( str_replace( $baseurl, '', $url ), '/' );
+			$search[] = $url;
+		}
+
+		$search = array_unique( $search );
+		$in     = implode( ',', array_fill( 0, count( $search ), '%s' ) );
+
+		// Prepare a query to find all in a single request.
+		$sql = $wpdb->prepare(
+			"SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value IN ({$in}) limit 1000", // phpcs:ignore WordPress.DB
+			$search
+		);
+
+		$key       = md5( $sql );
+		$cached    = wp_cache_get( $key );
+		$auto_sync = $this->sync->is_auto_sync_enabled();
+
+		if ( false === $cached ) {
+			$cached  = array();
+			$results = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
+
+			if ( $results ) {
+				foreach ( $results as $result ) {
+					/**
+					 * Get the contextualized post id.
+					 *
+					 * @hook cloudinary_contextualized_post_id
+					 * @since 3.2.0
+					 *
+					 * @param $post_id {int} The post ID.
+					 *
+					 * @return {int}
+					 */
+					$post_id = apply_filters( 'cloudinary_contextualized_post_id', $result->post_id );
+
+					if ( ! $this->is_deliverable( $post_id ) ) {
+						continue;
+					}
+					// If we are here, it means that an attachment in the media library doesn't have a delivery for the url.
+					// Reset the signature for delivery and add to sync, to update it.
+					$this->create_delivery( $post_id );
+
+					if ( true === $auto_sync ) {
+						$this->sync->add_to_sync( $post_id );
+					}
+					$size           = $this->get_sized( $post_id );
+					$key            = ! empty( $size['sized_url'] ) ? $size['sized_url'] : wp_get_attachment_url( $post_id );
+					$cached[ $key ] = (int) $post_id;
+				}
+			}
+			wp_cache_add( $key, $cached );
+		}
+
+		$this->known   = array_merge( $this->known, $cached );
+		$this->unknown = array_diff_key( $this->unknown, $this->known );
+	}
+
+	/**
+	 * Get all the caches from found contexts.
+	 *
+	 * @return array
+	 */
+	protected function get_context_cache() {
+		$cached = array();
+		foreach ( $this->post_contexts as $id ) {
+			$has_cache = get_post_meta( $id, self::META_CACHE_KEY, true );
+			if ( ! empty( $has_cache ) ) {
+				foreach ( $has_cache as $type => $cache ) {
+					if ( ! isset( $cached[ $type ] ) ) {
+						$cached[ $type ] = array();
+					}
+					$cached[ $type ] = array_merge( $cached[ $type ], $cache );
+				}
+			}
+		}
+
+		return $cached;
+	}
+
+	/**
+	 * Get all image and video tags that match our found urls.
+	 *
+	 * @param string $content HTML content.
+	 * @param string $tags    List of tags to get.
+	 *
+	 * @return array The media tags found.
+	 */
+	public function get_media_tags( $content, $tags = 'img|video' ) {
+		$media = array();
+		if ( preg_match_all( '#(?P<tags><(' . $tags . ')[^>]*\>){1}#is', $content, $found ) ) {
+			$count = count( $found[0] );
+			for ( $i = 0; $i < $count; $i++ ) {
+				$media[ $i ] = $found['tags'][ $i ];
+			}
+		}
+
+		return $media;
+	}
+
+	/**
+	 * Convert media tags from Local to Cloudinary, and register with String_Replace.
+	 *
+	 * @param string $content The HTML to find tags and prep replacement in.
+	 * @param string $context The content of the content.
+	 *
+	 * @return array
+	 */
+	public function convert_tags( $content, $context = 'view' ) {
+		$has_cache = $this->get_context_cache();
+		$type      = is_ssl() ? 'https' : 'http';
+		if ( Utils::is_amp() ) {
+			$type = 'amp';
+		}
+		if ( 'view' === $context && ! empty( $has_cache[ $type ] ) ) {
+			$cached = $has_cache[ $type ];
+		}
+
+		$tags = $this->get_media_tags( $content, 'img|video|article|source' );
+		$tags = array_map( array( $this, 'parse_element' ), $tags );
+		$tags = array_filter( $tags );
+
+		$replacements = array();
+		$aliases      = array();
+		foreach ( $tags as $set ) {
+
+			// Check cache and skip if needed.
+			if ( isset( $replacements[ $set['original'] ] ) ) {
+				continue;
+			}
+			/**
+			 * Filter id from the tag.
+			 *
+			 * @hook   cloudinary_delivery_get_id
+			 * @since  2.7.6
+			 *
+			 * @param $attachment_id {int}    The attachment ID.
+			 * @param $tag_element   {array}  The tag element.
+			 *
+			 * @return {int|false}
+			 */
+			$set['id'] = apply_filters( 'cloudinary_delivery_get_id', $set['id'], $set );
+			if ( empty( $set['id'] ) ) {
+				continue;
+			}
+			$this->current_post_id = $set['context'];
+
+			// We only rebuild tags in the view context.
+			if ( 'view' === $context ) {
+				// Use cached item if found.
+				if ( isset( $cached[ $set['original'] ] ) ) {
+					$replacements[ $set['original'] ] = $cached[ $set['original'] ];
+				} else {
+					// Register replacement.
+					$replacements[ $set['original'] ] = $this->rebuild_tag( $set );
+				}
+			}
+			$this->current_post_id = null;
+
+		}
+
+		// Create aliases for urls where were found, but not found with an ID in a tag.
+		// Create the Full/Scaled items first.
+		foreach ( $this->known as $url => $relation ) {
+			if ( empty( $relation['public_id'] || $url === $relation['public_id'] ) ) {
+				continue; // We don't need the public_id relation item.
+			}
+			$base           = $type . ':' . $url;
+			$public_id      = ! is_admin() ? $relation['public_id'] . '.' . $relation['format'] : null;
+			$cloudinary_url = $this->media->cloudinary_url( $relation['post_id'], array(), $relation['transformations'], $public_id );
+			if ( empty( $cloudinary_url ) ) {
+				continue;
+			}
+			if ( ! empty( $relation['slashed'] ) && $relation['slashed'] ) {
+				$aliases[ $base . '?_i=AA' ] = addcslashes( $cloudinary_url, '/' );
+				$aliases[ $base . '?' ]      = addcslashes( $cloudinary_url . '&', '/' );
+				$aliases[ $base ]            = addcslashes( $cloudinary_url, '/' );
+			} else {
+				$aliases[ $base . '?_i=AA' ] = $cloudinary_url;
+				$aliases[ $base . '?' ]      = $cloudinary_url . '&';
+				$aliases[ $base ]            = $cloudinary_url;
+			}
+		}
+
+		// Create the sized found relations second.
+		foreach ( $this->found_urls as $url => $sizes ) {
+			if ( ! isset( $this->known[ $url ] ) || empty( $this->known[ $url ]['public_id'] ) ) {
+				continue;
+			}
+			$base      = $type . ':' . $url;
+			$relation  = $this->known[ $url ];
+			$public_id = ! is_admin() ? $relation['public_id'] . '.' . $relation['format'] : null;
+			foreach ( $sizes as $size => $file_name ) {
+				$local_url = path_join( dirname( $base ), $file_name );
+				if ( isset( $cached[ $local_url ] ) ) {
+					$aliases[ $local_url ] = $cached[ $local_url ];
+					continue;
+				}
+
+				$cloudinary_url = $this->media->cloudinary_url( $relation['post_id'], explode( 'x', $size ), $relation['transformations'], $public_id );
+				// The asset is not ready. Carry on.
+				if ( empty( $cloudinary_url ) ) {
+					continue;
+				}
+
+				$aliases[ $local_url . '?' ] = $cloudinary_url . '&';
+				$aliases[ $local_url ]       = $cloudinary_url;
+
+				// Some URLs might be slashed, but the found_urls does not have that information.
+				$aliases[ addcslashes( $local_url . '?', '/' ) ] = addcslashes( $cloudinary_url . '&', '/' );
+				$aliases[ addcslashes( $local_url, '/' ) ]       = addcslashes( $cloudinary_url, '/' );
+			}
+		}
+
+		// Move aliases to the end of the run, after images.
+		if ( ! empty( $aliases ) ) {
+			$replacements = array_merge( $replacements, $aliases );
+		}
+
+		// Sort by length, so we replace the longest first and prevent early replacements.
+		$keys = array_map( 'strlen', array_keys( $replacements ) );
+		array_multisort( $keys, SORT_DESC, $replacements );
+
+		// Update the post meta cache.
+		if ( is_singular() ) {
+			$has_cache          = array();
+			$has_cache[ $type ] = $replacements;
+			update_post_meta( get_the_ID(), self::META_CACHE_KEY, $has_cache );
+		}
+
+		return $replacements;
+	}
+
+	/**
+	 * Cleanup and standardize the tag element structure.
+	 *
+	 * @param array $tag_element The tag element.
+	 *
+	 * @return array
+	 */
+	protected function standardize_tag( $tag_element ) {
+
+		$default = array(
+			'width'  => $tag_element['width'],
+			'height' => $tag_element['height'],
+		);
+		if ( 'video' === $tag_element['tag'] ) {
+			// Video is handled different with sizes, so we dont set default widths and heights.
+			$default = array();
+		}
+		// Add default.
+		$tag_element['atts'] = wp_parse_args( $tag_element['atts'], $default );
+		// Add format if it's set.
+		if ( isset( $tag_element['format'] ) ) {
+			$tag_element['atts']['data-format'] = $tag_element['format'];
+		}
+
+		/**
+		 * Maybe skip the classes for th tag element.
+		 *
+		 * @hook  cloudinary_tag_skip_classes
+		 * @since 3.2.4
+		 * @default {false}
+		 *
+		 * @param $skip        {bool} True to unset attributes.
+		 * @param $tag_element {array} The tag element.
+		 *
+		 * @retun {bool}
+		 */
+		$skip_classes = apply_filters( 'cloudinary_tag_skip_classes', false, $tag_element );
+
+		// Add wp-{media-type}-{id} class name.
+		if (
+			! $skip_classes
+			&&
+			(
+			empty( $tag_element['atts']['class'] )
+			||
+			(
+				is_array( $tag_element['atts']['class'] )
+				&&
+				! in_array( 'wp-' . $tag_element['type'] . '-' . $tag_element['id'], $tag_element['atts']['class'], true )
+			)
+			)
+		) {
+			$tag_element['atts']['class']   = ! empty( $tag_element['atts']['class'] ) ? $tag_element['atts']['class'] : array();
+			$tag_element['atts']['class'][] = 'wp-' . $tag_element['type'] . '-' . $tag_element['id'];
+		}
+
+		$size = array();
+
+		// Get size.
+		if ( 'video' !== $tag_element['tag'] ) {
+			$size = array(
+				$tag_element['atts']['width'],
+				$tag_element['atts']['height'],
+			);
+		}
+
+		if ( ! empty( $tag_element['atts']['src'] ) ) {
+			$has_wp_size = $this->media->get_crop( $tag_element['atts']['src'], $tag_element['id'] );
+			if ( ! empty( $has_wp_size ) ) {
+				$size = $has_wp_size;
+			}
+		}
+		// Unset srcset and sizes.
+		unset( $tag_element['atts']['srcset'], $tag_element['atts']['sizes'] );
+
+		$public_id = $tag_element['atts']['data-public-id'];
+
+		// Get cloudinary URL.
+		$url = $this->media->cloudinary_url(
+			$tag_element['id'],
+			$size,
+			$tag_element['transformations'],
+			$public_id,
+			$tag_element['overwrite_transformations']
+		);
+
+		// Set the src.
+		$tag_element['atts']['src'] = $url;
+		// Convert any URLS that exist in attributes ( 3rd party lazyload etc..).
+		foreach ( $tag_element['atts'] as &$att ) {
+			if ( is_array( $att ) ) {
+				continue;
+			}
+			$parts = array_filter( explode( ' ', $att ) );
+			foreach ( $parts as &$part ) {
+				if ( $this->validate_url( $part ) ) {
+					$has_wp_size = $this->media->get_crop( $part, $tag_element['id'] );
+					$size        = array();
+					if ( ! empty( $has_wp_size ) ) {
+						$size = $has_wp_size;
+					}
+					$part = $this->media->cloudinary_url(
+						$tag_element['id'],
+						$size,
+						$tag_element['transformations'],
+						$public_id,
+						$tag_element['overwrite_transformations']
+					);
+				}
+			}
+			$att = implode( ' ', $parts );
+		}
+
+		/**
+		 * Unset the attributes to avoid block errors in the admin.
+		 *
+		 * @hook  cloudinary_unset_attributes
+		 * @since 3.2.4
+		 * @default {false}
+		 *
+		 * @param $unset       {bool} True to unset attributes.
+		 * @param $tag_element {array} The tag element.
+		 *
+		 * @retun {bool}
+		 */
+		if ( apply_filters( 'cloudinary_unset_attributes', false, $tag_element ) ) {
+			unset(
+				$tag_element['atts']['data-public-id'],
+				$tag_element['atts']['data-format'],
+				$tag_element['atts']['width'],
+				$tag_element['atts']['height'],
+				$tag_element['atts']['data-crop'],
+				$tag_element['atts']['srcset'],
+				$tag_element['atts']['data-responsive']
+			);
+
+			return $tag_element;
+		}
+
+		// Add transformations attribute.
+		$transformations = $this->media->get_transformations_from_string( $tag_element['atts']['src'] );
+		if ( false !== $this->media->get_crop_from_transformation( $transformations ) ) {
+			array_shift( $transformations );
+		}
+		$tag_element['atts']['data-transformations'] = API::generate_transformation_string( $transformations, $tag_element['type'] );
+
+		if ( Utils::user_can( 'status' ) && 'on' === $this->plugin->settings->image_settings->_overlay ) {
+			$local_size = get_post_meta( $tag_element['id'], Sync::META_KEYS['local_size'], true );
+			if ( empty( $local_size ) && file_exists( get_attached_file( $tag_element['id'] ) ) ) {
+				$local_size = filesize( get_attached_file( $tag_element['id'] ) );
+			}
+			$remote_size                           = get_post_meta( $tag_element['id'], Sync::META_KEYS['remote_size'], true );
+			$tag_element['atts']['data-filesize']  = size_format( $local_size );
+			$tag_element['atts']['data-optsize']   = size_format( $remote_size );
+			$tag_element['atts']['data-optformat'] = get_post_meta( $tag_element['id'], Sync::META_KEYS['remote_format'], true );
+			if ( ! empty( $local_size ) && ! empty( $remote_size ) ) {
+				$diff                                = $local_size - $remote_size;
+				$tag_element['atts']['data-percent'] = round( $diff / $local_size * 100, 1 );
+			} elseif ( 'image' === $tag_element['type'] ) {
+				$this->plugin->get_component( 'storage' )->size_sync( $tag_element['id'] );
+			}
+
+			$base_url = $this->plugin->settings->get_url( 'edit_asset' );
+
+			/**
+			 * Filter the permalink for the edit asset link.
+			 *
+			 * @hook   cloudinary_edit_asset_permalink
+			 * @since  3.2.0
+			 *
+			 * @param $permalink {string} The permalink.
+			 *
+			 * @return {string}
+			 */
+			$permalink = apply_filters( 'cloudinary_edit_asset_permalink', add_query_arg( 'asset', $tag_element['id'], $base_url ) );
+
+			$tag_element['atts']['data-permalink'] = $permalink;
+		}
+
+		$tag_element['atts']['data-version'] = $this->media->get_cloudinary_version( $tag_element['id'] );
+
+		/**
+		 * Bypass Cloudinary's SEO URLs.
+		 *
+		 * @hook   cloudinary_bypass_seo_url
+		 * @since  3.1.5
+		 *
+		 * @param $bypass_seo_url {bool} Whether to bypass SEO URLs.
+		 *
+		 * @return {bool}
+		 */
+		$bypass_seo_url = apply_filters( 'cloudinary_bypass_seo_url', false );
+
+		$tag_element['atts']['data-seo'] = ! $bypass_seo_url;
+
+		$resource_type = in_array( $tag_element['type'], array( 'image', 'video' ), true ) ? $tag_element['type'] : 'raw';
+
+		$args = array(
+			'delivery'      => $this->media->get_media_delivery( $tag_element['id'] ),
+			'resource_type' => $resource_type,
+		);
+
+		$tag_element['atts']['data-public-id'] = $this->plugin->get_component( 'connect' )->api->get_public_id( $tag_element['id'], $args );
+
+		return $tag_element;
+	}
+
+	/**
+	 * Rebuild a tag with cloudinary urls.
+	 *
+	 * @param array $tag_element The original HTML tag.
+	 *
+	 * @return string
+	 */
+	public function rebuild_tag( $tag_element ) {
+
+		$tag_element = $this->standardize_tag( $tag_element );
+
+		/**
+		 * Filter to allow stopping default srcset generation.
+		 *
+		 * @hook   cloudinary_apply_breakpoints
+		 * @since  3.0.0
+		 * @default {true}
+		 *
+		 * @param $apply {bool}  True to apply, false to skip.
+		 *
+		 * @return {bool}
+		 */
+		if ( apply_filters( 'cloudinary_apply_breakpoints', true ) ) {
+			$meta = wp_get_attachment_metadata( $tag_element['id'] );
+			if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) {
+				$relationship = Relationship::get_relationship( $tag_element['id'] );
+				// Check overwrite.
+				$meta['overwrite_transformations'] = $tag_element['overwrite_transformations'];
+				$meta['cloudinary_id']             = $relationship->public_id;
+				$meta['transformations']           = $tag_element['transformations'];
+				// Add new srcset.
+				$element = wp_image_add_srcset_and_sizes( $tag_element['original'], $meta, $tag_element['id'] );
+
+				$atts = Utils::get_tag_attributes( $element );
+				if ( ! empty( $atts['srcset'] ) ) {
+					$tag_element['atts']['srcset'] = $atts['srcset'];
+				}
+				if ( ! empty( $atts['sizes'] ) ) {
+					$tag_element['atts']['sizes'] = $atts['sizes'];
+				}
+			}
+		}
+
+		/**
+		 * Filter the tag element.
+		 *
+		 * @hook   cloudinary_pre_image_tag | cloudinary_pre_video_tag
+		 * @since  2.7.5
+		 *
+		 * @param $tag_element {array}  The tag_element (tag + attributes array).
+		 *
+		 * @return {array}
+		 */
+		$tag_element = apply_filters( "cloudinary_pre_{$tag_element['type']}_tag", $tag_element );
+
+		// Setup new tag.
+		$replace = HTML::build_tag( $tag_element['tag'], $tag_element['atts'] );
+
+		// If the original tag used single quotes, we need to use them in the new tag.
+		$single_quotes = (bool) preg_match( '/=\s*\'/', $tag_element['original'] );
+		if ( $single_quotes ) {
+			$replace = str_replace( '"', "'", $replace );
+		}
+
+		/**
+		 * Filter the new built tag element.
+		 *
+		 * @hook   cloudinary_image_tag | cloudinary_video_tag
+		 * @since  3.0.0
+		 *
+		 * @param $replace     {string} The new HTML tag.
+		 * @param $tag_element {array}  The tag_element (tag + attributes array).
+		 *
+		 * @return {array}
+		 */
+		return apply_filters( "cloudinary_{$tag_element['type']}_tag", $replace, $tag_element );
+	}
+
+	/**
+	 * Parse an html element into tag, and attributes.
+	 *
+	 * @param string $element The HTML element.
+	 *
+	 * @return array|null
+	 */
+	public function parse_element( $element ) {
+		/**
+		 * Filter to skip parsing an element.
+		 *
+		 * @hook  cloudinary_skip_parse_element
+		 * @since 3.2.6
+		 * @default {false}
+		 *
+		 * @param $skip    {bool}  True to skip parsing.
+		 * @param $element {string} The element to parse.
+		 *
+		 * @return {bool}
+		 */
+		if ( apply_filters( 'cloudinary_skip_parse_element', false, $element ) ) {
+			return null;
+		}
+
+		static $post_context = 0;
+
+		$config = $this->plugin->settings->get_value( 'image_settings' );
+
+		/**
+		 * Enable the Crop and Gravity control settings.
+		 *
+		 * @hook  cloudinary_enable_crop_and_gravity_control
+		 * @since 3.1.3
+		 * @default {false}
+		 *
+		 * @param $enabeld {bool} Is the Crop and Gravity control enabled?
+		 *
+		 * @retrun {bool}
+		 */
+		$enabled_crop_gravity     = apply_filters( 'cloudinary_enable_crop_and_gravity_control', false );
+		$has_sized_transformation = $enabled_crop_gravity && ! empty( $config['crop_sizes'] );
+
+		$tag_element = array(
+			'tag'                       => '',
+			'atts'                      => array(),
+			'original'                  => $element,
+			'overwrite_transformations' => false,
+			'context'                   => 0,
+			'id'                        => 0,
+			'type'                      => '',
+			'delivery'                  => 'wp',
+			'breakpoints'               => true,
+			'transformations'           => array(),
+			'width'                     => 0,
+			'height'                    => 0,
+			'base_url'                  => '',
+		);
+		// Cleanup element.
+		$element = trim( $element, '</>' );
+
+		// Break element up.
+		$attributes         = shortcode_parse_atts( $element );
+		$tag_element['tag'] = array_shift( $attributes );
+		// Context Switch Check.
+		if ( 'article' === $tag_element['tag'] ) {
+			if ( ! empty( $attributes['id'] ) && false !== strpos( $attributes['id'], 'post-' ) ) {
+				$post_context = intval( substr( $attributes['id'], 5 ) );
+			}
+
+			return null;
+		}
+		$tag_element['type'] = in_array( $tag_element['tag'], array( 'img', 'source' ), true ) ? 'image' : $tag_element['tag'];
+		$third_party_change  = Utils::maybe_get_third_party_changes( $attributes, $tag_element['tag'] );
+		if ( ! empty( $third_party_change ) ) {
+			Utils::log( $third_party_change, 'third-party-loading' );
+
+			return null;
+		}
+		$raw_url                 = 'source' === $tag_element['tag'] && ! empty( $attributes['srcset'] ) ? $attributes['srcset'] : $attributes['src'];
+		$url                     = $this->maybe_unsize_url( Utils::clean_url( $this->sanitize_url( $raw_url ) ) );
+		$tag_element['base_url'] = $url;
+		// Track back the found URL.
+		if ( $this->media->is_cloudinary_url( $raw_url ) ) {
+			$public_id = $this->media->get_public_id_from_url( $raw_url );
+			if ( isset( $this->known[ $public_id ] ) ) {
+
+				$url               = $this->known[ $public_id ]['sized_url'];
+				$attributes['src'] = $url;
+			}
+		}
+		$tag_element['context'] = $post_context;
+		if ( ! empty( $this->known[ $url ] ) && ! empty( $this->known[ $url ]['public_id'] ) ) {
+			$item = $this->known[ $url ];
+			if ( ! empty( $item['transformations'] ) ) {
+				$tag_element['transformations'] = $this->media->get_transformations_from_string( $item['transformations'], $tag_element['type'] );
+			}
+			// Get the public ID and append the extension if it's missing.
+			$public_id = $item['public_id'];
+			if ( strrchr( $public_id, '.' ) !== '.' . $item['format'] ) {
+				$public_id .= '.' . $item['format'];
+			}
+			$tag_element['id']            = (int) $item['post_id'];
+			$tag_element['width']         = ! empty( $attributes['width'] ) ? $attributes['width'] : $item['width'];
+			$tag_element['height']        = ! empty( $attributes['height'] ) ? $attributes['height'] : $item['height'];
+			$attributes['data-public-id'] = $public_id;
+			$tag_element['format']        = $item['format'];
+
+			if ( in_array( $tag_element['tag'], array( 'img', 'source' ), true ) ) {
+				// Check if this is a crop or a scale.
+				$has_size = $this->media->get_size_from_url( $this->sanitize_url( $raw_url ) );
+				if ( ! empty( $has_size ) && ! empty( $item['height'] ) ) {
+					$file_ratio     = round( $has_size[0] / $has_size[1], 2 );
+					$original_ratio = round( $item['width'] / $item['height'], 2 );
+					if ( $file_ratio !== $original_ratio ) {
+						$attributes['data-crop'] = $file_ratio;
+					}
+					if ( $has_sized_transformation ) {
+						$crop_size             = array(
+							'width'  => $has_size[0],
+							'height' => $has_size[1],
+						);
+						$image_transformations = $this->media->get_crop_transformations( $tag_element['id'], $crop_size );
+						if ( $image_transformations ) {
+							$attributes['data-transformation-crop'] = $image_transformations;
+						}
+					}
+				}
+			}
+		}
+
+		if ( ! empty( $attributes['class'] ) ) {
+			if ( preg_match( '/wp-post-(\d+)/', $attributes['class'], $match ) ) {
+				$tag_element['context'] = (int) $match[1];
+				$post_context           = $tag_element['context'];
+			}
+
+			$attributes['class'] = explode( ' ', $attributes['class'] );
+			if ( in_array( 'cld-overwrite', $attributes['class'], true ) ) {
+				$tag_element['overwrite_transformations'] = true;
+			}
+		} else {
+			$attributes['class'] = array(
+				'wp-post-' . $tag_element['context'],
+			);
+		}
+		// Add overwrite transformations class if needed.
+		if ( false !== strpos( $raw_url, 'cld_overwrite' ) ) {
+			$attributes['class'][]                    = 'cld-overwrite';
+			$tag_element['overwrite_transformations'] = true;
+		}
+
+		$inline_transformations = $this->get_transformations_maybe( $raw_url );
+		if ( $inline_transformations ) {
+			// Ensure that we don't get duplicated transformations.
+			$tag_element['transformations'] = array_unique( array_merge( $tag_element['transformations'], $inline_transformations ), SORT_REGULAR );
+		}
+
+		// Check if ID was found, and upgrade if needed.
+		if ( empty( $tag_element['id'] ) ) {
+			// Old method to aid in upgrade from 2.7.7.
+			$maybe_id = $this->media->filter->get_id_from_tag( $tag_element['original'] );
+			if ( ! empty( $maybe_id ) ) {
+				$cloudinary_id_maybe = $this->media->cloudinary_id( $maybe_id );
+				if ( ! empty( $cloudinary_id_maybe ) ) {
+					$tag_element['id']                     = $maybe_id;
+					$meta                                  = wp_get_attachment_metadata( $maybe_id );
+					$tag_element['width']                  = ! empty( $meta['width'] ) ? $meta['width'] : 0;
+					$tag_element['height']                 = ! empty( $meta['height'] ) ? $meta['height'] : 0;
+					$tag_element['atts']['data-public-id'] = $cloudinary_id_maybe;
+					$tag_element['format']                 = Utils::pathinfo( $cloudinary_id_maybe, PATHINFO_EXTENSION );
+				}
+			}
+		}
+
+		// Check for loading attribute.
+		if ( ! empty( $attributes['loading'] ) ) {
+			$tag_element['loading'] = $attributes['loading'];
+		}
+
+		// Set atts.
+		$tag_element['atts'] = wp_parse_args( $attributes, $tag_element['atts'] );
+
+		/**
+		 * Filter the tag element.
+		 *
+		 * @hook   cloudinary_parse_element
+		 * @since  3.0.9
+		 *
+		 * @param $tag_element {array} The tag element.
+		 *
+		 * @return {array} The tag element.
+		 */
+		$tag_element = apply_filters( 'cloudinary_parse_element', $tag_element );
+
+		return $tag_element;
+	}
+
+	/**
+	 * Maybe get the inline transformations from an image url.
+	 *
+	 * @param string $url The image src url.
+	 *
+	 * @return array|null
+	 */
+	protected function get_transformations_maybe( $url ) {
+
+		$transformations = null;
+		$query           = wp_parse_url( $url, PHP_URL_QUERY );
+		if ( ! empty( $query ) && false !== strpos( $query, 'cld_params' ) ) {
+			// Has params in src.
+			$args = array();
+			wp_parse_str( $query, $args );
+			$transformations = $this->media->get_transformations_from_string( $args['cld_params'] );
+		}
+
+		return $transformations;
+	}
+
+	/**
+	 * Checks if a url path is for a local content directory.
+	 *
+	 * @param string $url The url to check.
+	 *
+	 * @return bool
+	 */
+	protected function is_content_dir( $url ) {
+		static $base = '';
+
+		if ( empty( $base ) ) {
+			$dirs = wp_upload_dir();
+			$base = wp_parse_url( $dirs['baseurl'], PHP_URL_PATH );
+		}
+
+		$path     = wp_parse_url( dirname( $url ), PHP_URL_PATH );
+		$is_local = 0 === strpos( $path, $base );
+
+		if ( $is_local ) {
+			$dirname = trim( substr( $path, strlen( $base ), 8 ), DIRECTORY_SEPARATOR );
+			if ( empty( $dirname ) || preg_match( '/\d{4}\/\d{2}/', $dirname ) ) {
+				$is_local = true;
+			} elseif ( ! empty( $dirname ) ) {
+				$is_local = false;
+			}
+		}
+
+		/**
+		 * Filter if the url is a local asset.
+		 *
+		 * @hook   cloudinary_is_content_dir
+		 * @since  2.7.6
+		 *
+		 * @param $is_local {bool}   If the url is a local asset.
+		 * @param $url      {string} The url.
+		 *
+		 * @return {bool}
+		 */
+		return apply_filters( 'cloudinary_is_content_dir', $is_local, $url );
+	}
+
+	/**
+	 * Check if the file type is allowed to be uploaded.
+	 *
+	 * @param string $ext The filetype extension.
+	 *
+	 * @return bool
+	 */
+	protected function is_allowed_type( $ext ) {
+		static $allowed_types = array();
+		if ( empty( $allowed_types ) ) {
+			$compatible_types = $this->media->get_compatible_media_types();
+			// Check with paths.
+			$types = wp_get_ext_types();
+			foreach ( $compatible_types as $type ) {
+				if ( isset( $types[ $type ] ) ) {
+					$allowed_types = array_merge( $allowed_types, $types[ $type ] );
+				}
+			}
+			/**
+			 * Filter the allowed file extensions to be delivered.
+			 *
+			 * @hook   cloudinary_allowed_extensions
+			 * @since  3.0.0
+			 *
+			 * @param $allowed_types {array}  Array of allowed file extensions.
+			 *
+			 * @return {array}
+			 */
+			$allowed_types = apply_filters( 'cloudinary_allowed_extensions', $allowed_types );
+		}
+
+		return in_array( $ext, $allowed_types, true );
+	}
+
+	/**
+	 * Filter out excluded urls.
+	 *
+	 * @param string $url The url to filter out.
+	 *
+	 * @return bool
+	 */
+	public function validate_url( $url ) {
+		static $home;
+		if ( ! $home ) {
+			$home = wp_parse_url( Utils::home_url( '/' ) );
+		}
+		$parts = wp_parse_url( $url );
+		if ( empty( $parts['host'] ) ) {
+			return false; // If host is empty, it's a false positive url.
+		}
+		if ( empty( $parts['path'] ) || '/' === $parts['path'] ) {
+			return false; // exclude base domains.
+		}
+		$ext = Utils::pathinfo( $parts['path'], PATHINFO_EXTENSION );
+		if ( empty( $ext ) || ! $this->is_allowed_type( $ext ) ) {
+			return false;
+		}
+
+		return $parts['host'] === $home['host'] ? $this->is_content_dir( $url ) : $this->media->can_upload_from_host( $parts['host'] );
+	}
+
+	/**
+	 * Set url usability.
+	 *
+	 * @param object    $item      The item object result.
+	 * @param null|bool $auto_sync If auto_sync is on.
+	 */
+	protected function set_usability( $item, $auto_sync = null ) {
+
+		/**
+		 * Filter the found item to allow usability to be altered.
+		 *
+		 * @hook   cloudinary_set_usable_asset
+		 * @since  3.0.2
+		 *
+		 * @param $item {array} The found asset array.
+		 *
+		 * @return {array}
+		 */
+		$item = apply_filters( 'cloudinary_set_usable_asset', $item );
+
+		/**
+		 * The URL to be searched for and prepared to be delivered by Cloudinary.
+		 *
+		 * @hook   cloudinary_content_url
+		 * @since  3.1.6
+		 *
+		 * @param $url {string} The default content_url.
+		 *
+		 * @return {string}
+		 */
+		$content_url = apply_filters( 'cloudinary_content_url', content_url() );
+
+		// Cloudinary assets have a full URL.
+		if ( 'asset' === $item['sync_type'] ) {
+			$url = $item['sized_url'];
+		} else {
+			$url = Utils::clean_url( $content_url ) . $item['sized_url'];
+		}
+
+		/**
+		 * The URL to be searched for and prepared to be delivered by Cloudinary.
+		 *
+		 * @hook   cloudinary_delivery_searchable_url
+		 * @since  3.1.6
+		 *
+		 * @param $url         {string} The URL to be searched for and prepared to be delivered by Cloudinary.
+		 * @param $item        {array}  The found asset array.
+		 * @param $content_url {string} The content URL.
+		 *
+		 * @return {string}
+		 */
+		$url = apply_filters( 'cloudinary_delivery_searchable_url', $url, $item, $content_url );
+
+		$found = array();
+
+		// If there's no public ID then don't pollute the found items.
+		if ( ! empty( $item['public_id'] ) ) {
+			$found[ $item['public_id'] ] = $item;
+		}
+		$scaled                     = Utils::make_scaled_url( $url );
+		$descaled                   = Utils::descaled_url( $url );
+		$scaled_slashed             = addcslashes( $scaled, '/' );
+		$descaled_slashed           = addcslashes( $descaled, '/' );
+		$found[ $scaled ]           = $item;
+		$found[ $descaled ]         = $item;
+		$found[ $scaled_slashed ]   = array_merge( $item, array( 'slashed' => true ) );
+		$found[ $descaled_slashed ] = array_merge( $item, array( 'slashed' => true ) );
+
+		if ( ! $this->is_deliverable( $item['post_id'] ) ) {
+			$this->unusable = array_merge( $this->unusable, $found );
+
+			return;
+		}
+
+		$this->known = array_merge( $this->known, $found );
+
+		if ( 'disable' === $item['post_state'] ) {
+			return;
+		}
+
+		$is_media = 'media' === $item['sync_type'];
+		$is_asset = 'inherit' !== $item['post_state'];
+
+		if ( true === $auto_sync && true === $is_media && true === $is_asset ) {
+			// Auto sync on - synced as asset - take over.
+			$this->sync->delete_cloudinary_meta( $item['post_id'] );
+			$this->sync->add_to_sync( $item['post_id'] );
+		} elseif ( true === $auto_sync && true === $is_media && empty( $item['public_id'] ) ) {
+			// Un-synced media item with auto sync on. Add to sync.
+			$this->sync->add_to_sync( $item['post_id'] );
+		} elseif ( ! empty( $item['public_id'] ) ) {
+			// Most likely an asset with a public ID.
+			$this->usable[ $url ] = $url;
+			if ( self::get_settings_signature() !== $item['signature'] ) {
+				$sync_type = $this->sync->get_sync_type( $item['post_id'] );
+				if ( $sync_type ) {
+					$this->sync->add_to_sync( $item['post_id'] );
+					if ( $this->sync->is_required( $sync_type, $item['post_id'] ) ) {
+						// Can't render this, so lets remove it from usable list.
+						unset( $this->usable[ $url ] );
+					}
+				}
+			}
+		} else {
+			// This is an asset or media without a public id.
+			$this->unusable[ $item['sized_url'] ] = $item;
+		}
+	}
+
+	/**
+	 * Sanitize a url.
+	 *
+	 * @param string $url URL to sanitize.
+	 *
+	 * @return string|null
+	 */
+	protected function sanitize_url( $url ) {
+
+		// Catch mixed URLs.
+		if ( 5 < strlen( $url ) && false !== strpos( $url, 'https://', 5 ) ) {
+			$url = substr( $url, strpos( $url, 'https://', 5 ) );
+		}
+
+		// Remove Query string.
+		$url = strtok( $url, '?' );
+
+		// check the url is more than a domain.
+		if ( ! filter_var( utf8_uri_encode( $url ), FILTER_VALIDATE_URL ) || 3 > substr_count( rtrim( $url, '/' ), '/' ) ) {
+			$url = null;
+		}
+
+		return $url;
+	}
+
+	/**
+	 * Maybe remove a size from a URL.
+	 *
+	 * @param string $url The url to remove size from.
+	 *
+	 * @return string
+	 */
+	public function maybe_unsize_url( $url ) {
+		$file = Utils::pathinfo( $url, PATHINFO_FILENAME );
+		$dash = ltrim( strrchr( $file, '-' ), '-' );
+		if (
+			! empty( $dash )
+			&& 1 === substr_count( $dash, 'x' )
+			&& is_numeric( str_replace( 'x', '', $dash ) )
+			&& 2 === count( array_filter( explode( 'x', $dash ) ) )
+		) {
+			$sized                                = wp_basename( $url );
+			$url                                  = str_replace( '-' . $dash, '', $url );
+			$scaled                               = Utils::make_scaled_url( $url );
+			$this->found_urls[ $url ][ $dash ]    = $sized;
+			$this->found_urls[ $scaled ][ $dash ] = $sized;
+		}
+
+		return $url;
+	}
+
+	/**
+	 * Prepare the delivery for filtering URLS.
+	 *
+	 * @param string $content The content html.
+	 */
+	public function prepare_delivery( $content ) {
+		$content    = wp_unslash( $content );
+		$all_urls   = array_unique( Utils::extract_urls( $content ) );
+		$base_urls  = array_filter( array_map( array( $this, 'sanitize_url' ), $all_urls ) );
+		$clean_urls = array_map( array( 'Cloudinary\Utils', 'clean_url' ), $base_urls );
+		$urls       = array_filter( $clean_urls, array( $this, 'validate_url' ) );
+		$decoded    = array_map( 'urldecode', $urls );
+
+		// De-size.
+		$desized = array_unique( array_map( array( $this, 'maybe_unsize_url' ), $urls ) );
+		$scaled  = array_unique( array_map( array( Utils::class, 'make_scaled_url' ), $desized ) );
+		$urls    = array_unique( array_merge( $desized, $scaled, $decoded ) );
+		$urls    = array_values( $urls ); // resets the index.
+
+		$public_ids = array();
+		// Lets only look for Cloudinary URLs on the frontend.
+		if ( ! Utils::is_admin() ) {
+			// clean out empty urls.
+			$cloudinary_urls = array_filter( $base_urls, array( $this->media, 'is_cloudinary_url' ) ); // clean out empty urls.
+			// Clean URLS for search.
+			$all_public_ids = array_filter( array_map( array( $this->media, 'get_public_id_from_url' ), $cloudinary_urls ) );
+			$public_ids     = array_unique( $all_public_ids );
+		}
+		if ( empty( $urls ) && empty( $public_ids ) ) {
+			return; // Bail since theres nothing.
+		}
+
+		$paths = array_map( array( Utils::class, 'get_path_from_url' ), $urls );
+
+		// Get the results that include the public IDs, the paths (from Media Library), and urls for additional assets.
+		$results = Utils::query_relations( $public_ids, array_merge( $paths, $urls ) );
+
+		$auto_sync = $this->sync->is_auto_sync_enabled();
+		foreach ( $results as $result ) {
+			$this->set_usability( $result, $auto_sync );
+		}
+		// Set unknowns.
+		$this->unknown = $this->filter_unknown_urls( $urls );
+	}
+
+	/**
+	 * Filter URLs to determine which are truly unknown, considering image size variations.
+	 *
+	 * This method treats image size variations (e.g., example-300x224.png) as "known"
+	 * if their base image (e.g., example.png) exists in the known URLs, while still
+	 * catching genuinely unknown URLs.
+	 *
+	 * @param array $urls All URLs found in content.
+	 *
+	 * @return array Array of genuinely unknown URLs.
+	 */
+	protected function filter_unknown_urls( $urls ) {
+		$known_keys = array_keys( $this->known );
+
+		if ( empty( $known_keys ) ) {
+			return $urls;
+		}
+
+		$known_lookup = array_flip( $known_keys );
+		$potential_unknown = array_diff( $urls, $known_keys );
+
+		if ( empty( $potential_unknown ) ) {
+			return array();
+		}
+
+		$truly_unknown = array();
+
+		foreach ( $potential_unknown as $url ) {
+			// Check if this might be a sized variation of a known image.
+			$base_url = $this->maybe_unsize_url( $url );
+
+			// If base image is known, skip this variation.
+			if ( isset( $known_lookup[ $base_url ] ) ) {
+				continue;
+			}
+
+			// Check scaled version if base wasn't found and URL was actually "unsized".
+			if ( $base_url !== $url ) {
+				$scaled_url = Utils::make_scaled_url( $base_url );
+				if ( isset( $known_lookup[ $scaled_url ] ) ) {
+					continue; // Scaled version is known, skip this variation.
+				}
+			}
+
+			// This URL is genuinely unknown.
+			$truly_unknown[] = $url;
+		}
+
+		/**
+		 * Filter the list of truly unknown URLs after filtering out image size variations.
+		 *
+		 * @hook   cloudinary_filter_unknown_urls
+		 * @since  3.2.12
+		 *
+		 * @param array $truly_unknown The filtered list of unknown URLs.
+		 * @param array $urls          The original list of all URLs.
+		 * @param array $known_keys    The list of known URL keys.
+		 *
+		 * @return array The filtered list of unknown URLs.
+		 */
+		return apply_filters( 'cloudinary_filter_unknown_urls', $truly_unknown, $urls, $known_keys );
+	}
+
+	/**
+	 * Catch attachment URLS from HTML content.
+	 *
+	 * @param string $content The HTML to catch URLS from.
+	 * @param string $context The content of the content.
+	 */
+	public function catch_urls( $content, $context = 'view' ) {
+
+		// Check if we are saving. If so, bail.
+		// This is to prevent the replacement from happening in the shutdown, signaling content changes in the editor.
+		if ( $this->plugin->get_component( 'replace' )->doing_save() ) {
+			return;
+		}
+		$this->init_delivery();
+		$this->prepare_delivery( $content );
+
+		if ( ! empty( $this->known ) ) {
+			$known = $this->convert_tags( $content, $context );
+			// Replace the knowns.
+			foreach ( $known as $src => $replace ) {
+				String_Replace::replace( $src, $replace );
+			}
+		}
+		// Attempt to get the unknowns.
+		if ( ! empty( $this->unknown ) ) {
+			$this->find_attachment_size_urls();
+		}
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-extensions.php.html b/docs/php_class-extensions.php.html new file mode 100644 index 000000000..0b5f4c62e --- /dev/null +++ b/docs/php_class-extensions.php.html @@ -0,0 +1,334 @@ + + + + + Source: php/class-extensions.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-extensions.php

+ + + + + + + +
+
+
<?php
+/**
+ * Extensions class for the Cloudinary plugin.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Component\Setup;
+
+/**
+ * Class Extensions
+ */
+class Extensions extends Settings_Component implements Setup {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @var Plugin Instance of the global plugin.
+	 */
+	public $plugin;
+
+	/**
+	 * Holds the core plugin settings.
+	 *
+	 * @var Settings
+	 */
+	protected $settings;
+
+	/**
+	 * Holds various keys used.
+	 */
+	const KEYS = array(
+		'extension' => '@extension',
+		'register'  => '@register',
+	);
+
+	/**
+	 * Extensions constructor.
+	 *
+	 * @param Plugin $plugin Global instance of the main plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+		parent::__construct( $plugin );
+		add_action( 'cloudinary_connected', array( $this, 'init' ) );
+	}
+
+	/**
+	 * Init component on connection.
+	 */
+	public function init() {
+		add_filter( 'cloudinary_api_rest_endpoints', array( $this, 'rest_endpoints' ) );
+		add_action( 'cloudinary_init_settings', array( $this, 'register_extensions' ) );
+	}
+
+	/**
+	 * Add endpoints to the \Cloudinary\REST_API::$endpoints array.
+	 *
+	 * @param array $endpoints Endpoints from the filter.
+	 *
+	 * @return array
+	 */
+	public function rest_endpoints( $endpoints ) {
+
+		$endpoints['extension'] = array(
+			'method'              => \WP_REST_Server::CREATABLE,
+			'callback'            => array( $this, 'rest_toggle_extension' ),
+			'args'                => array(),
+			'permission_callback' => array( $this, 'rest_can_manage_options' ),
+		);
+
+		return $endpoints;
+	}
+
+	/**
+	 * Admin permission callback.
+	 *
+	 * Explicitly defined to allow easier testability.
+	 *
+	 * @return bool
+	 */
+	public function rest_can_manage_options() {
+		return Utils::user_can( 'manage_extensions' );
+	}
+
+	/**
+	 * Handles the enable and disable of an extension..
+	 *
+	 * @param \WP_REST_Request $request The request.
+	 *
+	 * @return \WP_REST_Response
+	 */
+	public function rest_toggle_extension( \WP_REST_Request $request ) {
+		$extension = $request->get_param( 'extension' );
+		$value     = true === $request->get_param( 'enabled' ) ? 'on' : 'off';
+
+		$extension_setting = $this->settings->get_setting( $extension );
+		$saved             = $extension_setting->save_value( $value );
+
+		$return = array(
+			// Translators: placeholder is number of active extensions.
+			'active_settings' => $this->get_active_count_text(),
+		);
+
+		return rest_ensure_response( $return );
+	}
+
+	/**
+	 * Get the active extensions count.
+	 *
+	 * @return string
+	 */
+	public function get_active_count_text() {
+		$active = 0;
+		foreach ( $this->settings->get_value( $this->settings_slug ) as $value ) {
+			if ( 'on' === $value ) {
+				$active ++;
+			}
+		}
+
+		// Translators: placeholders are number of active extensions.
+		return sprintf( _n( '%s Active extension', '%s Active extensions', $active, 'cloudinary' ), $active );
+	}
+
+	/**
+	 * Get a list of internal extensions.
+	 *
+	 * @return array[]
+	 */
+	protected function get_internal_extensions() {
+		$internal = array(
+			'media-library' => array(
+				'name'        => __( 'Cloudinary DAM', 'cloudinary' ),
+				'description' => sprintf(
+					// translators: The Link for Learn more.
+					__( 'Cloudinary’s Digital Asset Management solutions is designed to meet the unique needs of today focusing on flexibility, intelligent automation features, and delivery at scale. %1$sLearn More%2$s.', 'cloudinary' ),
+					'<br><a href="https://cloudinary.com/products/digital_asset_management" target="_blank">',
+					'</a>'
+				),
+				'icon'        => $this->plugin->dir_url . 'css/images/dam-icon.svg',
+				'handler'     => '\\Cloudinary\\Media_Library',
+				'default'     => 'off',
+			),
+		);
+
+		return $internal;
+	}
+
+	/**
+	 * Register extensions.
+	 */
+	public function register_extensions() {
+		$extensions = $this->get_internal_extensions();
+
+		$panel = array(
+			'type'        => 'panel',
+			'slug'        => 'extensions',
+			'title'       => __( 'Extensions', 'cloudinary' ),
+			'description' => $this->get_active_count_text(),
+			'collapsible' => 'closed',
+			'attributes'  => array(
+				'description' => array(
+					'data-text' => 'active_settings',
+				),
+			),
+		);
+
+		$this->settings->set_param( 'sidebar' . $this->settings->separator . $this->settings_slug, $panel );
+
+		/**
+		 * Filter to register extensions.
+		 *
+		 * @hook  cloudinary_register_extensions
+		 * @since 3.0.0
+		 *
+		 * @param $extensions {array}  The list of extensions to register.
+		 * @param $plugin     {Plugin} The core plugin object.
+		 *
+		 * @return {array}
+		 */
+		$extensions = apply_filters( 'cloudinary_register_extensions', $extensions, $this->plugin );
+		foreach ( $extensions as $slug => $extension ) {
+
+			$instance = null;
+			if ( isset( $extension['handler'] ) ) {
+				$try = array( $extension['handler'], 'get_instance' );
+				if ( is_callable( $try ) ) {
+					$instance = call_user_func( $try );
+				}
+
+				// Add to plugin components.
+				$this->plugin->components[ 'extension_' . $slug ] = $instance;
+			}
+
+			$extension_slug    = $this->settings_slug . $this->settings->separator . $slug;
+			$extension_setting = $this->settings->add(
+				$extension_slug,
+				isset( $extension['default'] ) ? $extension['default'] : 'off',
+				array(
+					'type'       => 'on_off',
+					'slug'       => 'enable',
+					'instance'   => $instance,
+					'attributes' => array(
+						'data-extension' => $slug,
+					),
+				)
+			);
+
+			// Create panel in sidebar.
+			$params = array(
+				'type'       => 'info_box',
+				'icon'       => $extension['icon'],
+				'slug'       => $slug,
+				'title'      => $extension['name'],
+				'text'       => $extension['description'],
+				'attributes' => array(
+					'wrap' => array(
+						'class' => array(
+							'extension-item',
+						),
+					),
+				),
+				$extension_setting,
+			);
+			$this->settings->set_param( 'sidebar.extensions.' . $slug, $params );
+		}
+
+		// Add page re-loader button.
+		$params = array(
+			'type'       => 'tag',
+			'element'    => 'a',
+			'content'    => __( 'Reload page', 'cloudinary' ),
+			'attributes' => array(
+				'style' => 'width:100%; display:none; text-align:center;',
+				'id'    => 'page-reloader',
+				'class' => array(
+					'button',
+					'button-small',
+				),
+				'href'  => add_query_arg( 'reload', '1' ),
+			),
+		);
+		$this->settings->set_param( 'sidebar.extensions.settings', $params );
+		// Set settings to own.
+		$this->settings = $this->settings->get_setting( $this->settings_slug );
+	}
+
+	/**
+	 * Setup Extensions.
+	 */
+	public function setup() {
+		$data = array(
+			'url'   => Utils::rest_url( REST_API::BASE . '/extension' ),
+			'nonce' => wp_create_nonce( 'wp_rest' ),
+		);
+		foreach ( $this->settings->get_settings() as $setting ) {
+			if ( 'on' === $setting->get_value() && $setting->has_param( 'instance' ) ) {
+				$setting->get_param( 'instance' )->setup();
+			}
+		}
+		$this->plugin->add_script_data( 'extensions', $data );
+	}
+
+	/**
+	 * Init the settings.
+	 *
+	 * @param Settings $settings The core settings for the plugin.
+	 */
+	public function init_settings( $settings ) {
+		parent::init_settings( $settings );
+		// Register setting point.
+		$settings->add( $this->settings_slug );
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-media.php.html b/docs/php_class-media.php.html new file mode 100644 index 000000000..2ea6d0d2a --- /dev/null +++ b/docs/php_class-media.php.html @@ -0,0 +1,3383 @@ + + + + + Source: php/class-media.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-media.php

+ + + + + + + +
+
+
<?php
+/**
+ * Media class for the Cloudinary plugin.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Assets;
+use Cloudinary\Component\Setup;
+use Cloudinary\Connect\Api;
+use Cloudinary\Media\Filter;
+use Cloudinary\Media\Global_Transformations;
+use Cloudinary\Media\Upgrade;
+use Cloudinary\Media\Video;
+use Cloudinary\Media\WooCommerceGallery;
+use Cloudinary\Relate\Relationship;
+use WP_Error;
+use WP_Query;
+
+/**
+ * Class Media
+ */
+class Media extends Settings_Component implements Setup {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     Plugin Instance of the global plugin.
+	 */
+	public $plugin;
+
+	/**
+	 * Holds the base Cloudinary url.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     string.
+	 */
+	public $base_url;
+
+	/**
+	 * Holds the Cloudinary folder.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     string.
+	 */
+	private $cloudinary_folder;
+
+	/**
+	 * Holds the found Cloudinary ID's
+	 *
+	 * @since   0.1
+	 *
+	 * @var     array.
+	 */
+	private $cloudinary_ids = array();
+
+	/**
+	 * Cloudinary credentials.
+	 *
+	 * @var array.
+	 */
+	public $credentials;
+
+	/**
+	 * Cloudinary url filtering instance.
+	 *
+	 * @var \Cloudinary\Media\Filter.
+	 */
+	public $filter;
+
+	/**
+	 * Cloudinary upgrade instance.
+	 *
+	 * @var \Cloudinary\Media\Upgrade.
+	 */
+	public $upgrade;
+
+	/**
+	 * Cloudinary global transformations.
+	 *
+	 * @var \Cloudinary\Media\Global_Transformations.
+	 */
+	public $global_transformations;
+
+	/**
+	 * Video filter instance.
+	 *
+	 * @var \Cloudinary\Media\Video.
+	 */
+	public $video;
+
+	/**
+	 * Gallery instance.
+	 *
+	 * @var \Cloudinary\Media\Gallery.
+	 */
+	public $gallery;
+
+	/**
+	 * WooCommerceGallery instance.
+	 *
+	 * @var \Cloudinary\Media\WooCommerceGallery
+	 */
+	public $woocommerce_gallery;
+
+	/**
+	 * Sync instance.
+	 *
+	 * @var \Cloudinary\Sync
+	 */
+	public $sync;
+
+	/**
+	 * Flag if in image_downsize function to prevent overload.
+	 *
+	 * @var bool
+	 */
+	private $in_downsize = false;
+
+	/**
+	 * Flag to determine if the Featured Image is currently being rendered.
+	 *
+	 * @var bool|int
+	 */
+	private $doing_featured_image = false;
+
+	/**
+	 * Holds the media settings slug.
+	 *
+	 * @var string
+	 */
+	const MEDIA_SETTINGS_SLUG = 'media_display';
+
+	/**
+	 * Holds the Global Video Transformations option key.
+	 *
+	 * @var string
+	 */
+	const GLOBAL_VIDEO_TRANSFORMATIONS = 'cloudinary_global_video_transformations';
+
+	/**
+	 * The Cloudinary Media Library filters.
+	 *
+	 * @var array
+	 */
+	protected $cloudinary_filters;
+
+	/**
+	 * Media constructor.
+	 *
+	 * @param Plugin $plugin The global plugin instance.
+	 */
+	public function __construct( Plugin $plugin ) {
+		$this->plugin = $plugin;
+
+		add_action( 'init', array( $this, 'init_hook' ) );
+
+		// Add upgrade hook, since setup methods are called after the connect upgrade has run.
+		add_action( 'cloudinary_version_upgrade', array( $this, 'upgrade_media_settings' ) );
+	}
+
+	/**
+	 * Initialize WordPress hooks for the Cloudinary media integration.
+	 *
+	 * @return void
+	 */
+	public function init_hook() {
+		/**
+		 * Filter the Cloudinary Media Library filters.
+		 *
+		 * @hook  cloudinary_media_filters
+		 * @since 3.0.0
+		 *
+		 * @param $filters {array} The default filters.
+		 *
+		 * @returns {array}
+		 */
+		$this->cloudinary_filters = apply_filters(
+			'cloudinary_media_filters',
+			array(
+				SYNC::META_KEYS['sync_error'] => __( 'Error', 'cloudinary' ),
+				SYNC::META_KEYS['unsynced']   => __( 'Unsynced', 'cloudinary' ),
+			)
+		);
+	}
+
+	/**
+	 * Get an array of compatible media types that are used by Cloudinary.
+	 *
+	 * @return array
+	 */
+	public function get_compatible_media_types() {
+
+		$media_types = array(
+			'image',
+			'video',
+			'audio',
+			'application',
+			'text',
+			'document',
+			'archive',
+			'spreadsheet',
+			'interactive',
+		);
+
+		/**
+		 * Filter the default Cloudinary Media Types.
+		 *
+		 * @hook    cloudinary_media_types
+		 * @default array( 'image', 'video', 'audio', 'application', 'text' )
+		 *
+		 * @param $types {array} The default media types array.
+		 *
+		 * @return {array}
+		 */
+		return apply_filters( 'cloudinary_media_types', $media_types );
+	}
+
+	/**
+	 * Get an array of syncable delivery types.
+	 *
+	 * @return array
+	 */
+	public function get_syncable_delivery_types() {
+		$types = array(
+			'upload',
+		);
+
+		/**
+		 * Filter the delivery types that are able to sync.
+		 *
+		 * @hook    cloudinary_syncable_delivery_types
+		 * @default array( 'upload' )
+		 *
+		 * @param $types {array} The default syncable types.
+		 *
+		 * @return {array}
+		 */
+		return apply_filters( 'cloudinary_syncable_delivery_types', $types );
+	}
+
+	/**
+	 * Get convertible extensions and converted file types.
+	 *
+	 * @return array
+	 */
+	public function get_convertible_extensions() {
+
+		// Add preferred formats in future.
+		$base_types = array(
+			'psd'  => 'jpg',
+			'ai'   => 'jpg',
+			'eps'  => 'jpg',
+			'ps'   => 'jpg',
+			'ept'  => 'jpg',
+			'eps3' => 'jpg',
+			'indd' => 'jpg',
+			'webp' => 'gif',
+			'bmp'  => 'jpg',
+			'flif' => 'jpg',
+			'gltf' => 'jpg',
+			'heif' => 'jpg',
+			'heic' => 'jpg',
+			'ico'  => 'png',
+			'svg'  => 'png',
+			'tga'  => 'jpg',
+			'tiff' => 'jpg',
+			'tif'  => 'jpg',
+		);
+
+		/**
+		 * Filter the base types for conversion.
+		 *
+		 * @hook cloudinary_convert_media_types
+		 *
+		 * @param $base_types {array} The base conversion types array.
+		 *
+		 * @return  {array}
+		 */
+		return apply_filters( 'cloudinary_convert_media_types', $base_types );
+	}
+
+	/**
+	 * Check if a file type is compatible with Cloudinary & WordPress.
+	 *
+	 * @param string $file The file to check.
+	 *
+	 * @return bool
+	 */
+	public function is_file_compatible( $file ) {
+		require_once ABSPATH . 'wp-admin/includes/image.php';
+		$original_file = $file;
+		if ( $this->is_cloudinary_url( $file ) ) {
+			$file = Utils::download_fragment( $file );
+		}
+		if ( file_is_displayable_image( $file ) ) {
+			return true;
+		}
+		$types        = $this->get_compatible_media_types();
+		$file         = wp_parse_url( $original_file, PHP_URL_PATH );
+		$filename     = Utils::pathinfo( $file, PATHINFO_BASENAME );
+		$mime         = wp_check_filetype( $filename );
+		$type         = strstr( $mime['type'], '/', true );
+		$conversions  = $this->get_convertible_extensions();
+		$convertibles = array_keys( $conversions );
+
+		return in_array( $type, $types, true ) && ! in_array( $mime['ext'], $convertibles, true );
+	}
+
+	/**
+	 * Check if the attachment is a media file.
+	 *
+	 * @param int $attachment_id The attachment ID to check.
+	 *
+	 * @return bool
+	 */
+	public function is_media( $attachment_id ) {
+		$is_media = false;
+		if ( 'attachment' === get_post_type( $attachment_id ) ) {
+			$media_types = $this->get_compatible_media_types();
+			$type        = $this->get_media_type( $attachment_id );
+			$is_media    = in_array( $type, $media_types, true );
+		}
+
+		/**
+		 * Filter the check if post is media.
+		 *
+		 * @hook    cloudinary_is_media
+		 * @since   2.7.6
+		 * @default false
+		 *
+		 * @param $is_media      {bool} Flag if is media.
+		 * @param $attachment_id {int}  The attachment ID.
+		 *
+		 * @return  {bool}
+		 */
+		return apply_filters( 'cloudinary_is_media', $is_media, $attachment_id );
+	}
+
+	/**
+	 * Check if the attachment is local.
+	 *
+	 * @param int $attachment_id The attachment ID to check.
+	 *
+	 * @return bool
+	 */
+	public function is_local_media( $attachment_id ) {
+		$local_host = wp_parse_url( Utils::site_url(), PHP_URL_HOST );
+		$guid       = get_the_guid( $attachment_id );
+
+		// Maybe GUID is a path.
+		if ( ! filter_var( $guid, FILTER_VALIDATE_URL ) ) {
+			$url = Utils::home_url( $guid );
+			if ( $this->maybe_file_exist_in_url( $url ) ) {
+				$guid = Utils::home_url( $guid );
+			}
+		}
+
+		$media_host = wp_parse_url( $guid, PHP_URL_HOST );
+
+		return $local_host === $media_host || $this->is_cloudinary_url( $guid );
+	}
+
+	/**
+	 * Checks if asset URL is valid.
+	 *
+	 * @param string $url The URL to test.
+	 *
+	 * @return bool
+	 */
+	public function maybe_file_exist_in_url( $url ) {
+		if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
+			return false;
+		}
+
+		$head = wp_safe_remote_head( $url );
+
+		if ( is_wp_error( $head ) ) {
+			return false;
+		}
+
+		$code = wp_remote_retrieve_response_code( $head );
+
+		return 200 === $code;
+	}
+
+	/**
+	 * Check if the URL can use used to upload to Cloudinary.
+	 *
+	 * @param string $url_host The url host to check.
+	 *
+	 * @return bool
+	 */
+	public function can_upload_from_host( $url_host ) {
+		static $additional_urls;
+
+		$is_uploadable = false;
+
+		if ( ! $additional_urls ) {
+			$additional_urls = $this->settings->get_value( 'uploadable_domains' );
+		}
+
+		if ( ! empty( $additional_urls ) ) {
+			$is_uploadable = in_array( $url_host, $additional_urls, true );
+		}
+
+		return $is_uploadable;
+	}
+
+	/**
+	 * Check if the attachment is uploadable.
+	 *
+	 * @param int $attachment_id The attachment ID to check.
+	 *
+	 * @return bool
+	 */
+	public function is_uploadable_media( $attachment_id ) {
+		$is_uploadable = $this->is_local_media( $attachment_id );
+		$guid          = get_the_guid( $attachment_id );
+		$media_host    = wp_parse_url( $guid, PHP_URL_HOST );
+
+		if ( ! $is_uploadable ) {
+			$is_uploadable = $this->can_upload_from_host( $media_host );
+		}
+
+		/**
+		 * Filter local media.
+		 *
+		 * @hook   cloudinary_is_uploadable_media
+		 * @since  2.7.7
+		 *
+		 * @param $is_local   {bool}   The attachment ID.
+		 * @param $media_host {string} The html tag.
+		 *
+		 * @return {bool}
+		 */
+		return apply_filters( 'cloudinary_is_uploadable_media', $is_uploadable, $media_host );
+	}
+
+	/**
+	 * Is the media item size allowed to be uploaded.
+	 * Checks against the account limits.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return bool
+	 */
+	public function is_oversize_media( $attachment_id ) {
+		static $is_oversize = array();
+
+		if ( isset( $is_oversize[ $attachment_id ] ) ) {
+			return $is_oversize[ $attachment_id ];
+		}
+
+		$file_size = $this->get_attachment_file_size( $attachment_id );
+		$max_size  = ( wp_attachment_is_image( $attachment_id ) ? 'image_max_size_bytes' : 'video_max_size_bytes' );
+		$limit     = $this->plugin->components['connect']->usage['media_limits'][ $max_size ];
+
+		$is_oversize[ $attachment_id ] = $file_size > $limit;
+
+		if ( $is_oversize[ $attachment_id ] ) {
+			$max_size    = ( wp_attachment_is_image( $attachment_id ) ? 'image_max_size_bytes' : 'video_max_size_bytes' );
+			$max_size_hr = size_format( $this->plugin->components['connect']->usage['media_limits'][ $max_size ] );
+			// translators: variable is file size.
+			$message = sprintf( __( 'File size exceeds the maximum of %s. This media asset will be served from WordPress.', 'cloudinary' ), $max_size_hr );
+			update_post_meta( $attachment_id, Sync::META_KEYS['sync_error'], $message );
+		}
+
+		return $is_oversize[ $attachment_id ];
+	}
+
+	/**
+	 * Get the filesize of an attachment.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return int
+	 */
+	public function get_attachment_file_size( $attachment_id ) {
+
+		$callback = 'get_attached_file';
+		if (
+			function_exists( 'wp_get_original_image_path' )
+			&& wp_attachment_is_image( $attachment_id )
+		) {
+			$callback = 'wp_get_original_image_path';
+		}
+
+		$file = $callback( $attachment_id );
+		if ( ! file_exists( $file ) ) {
+			return 0;
+		}
+		$file_size = $this->get_post_meta( $attachment_id, Sync::META_KEYS['file_size'], true );
+		if ( empty( $file_size ) ) {
+			$file_size = filesize( $file );
+			$this->update_post_meta( $attachment_id, Sync::META_KEYS['file_size'], $file_size );
+		}
+
+		return $file_size;
+	}
+
+	/**
+	 * Get the Cloudinary delivery type.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public function get_media_delivery( $attachment_id ) {
+		$delivery = $this->get_post_meta( $attachment_id, Sync::META_KEYS['delivery'], true );
+
+		if ( ! empty( $delivery ) ) {
+			return $delivery;
+		}
+
+		return 'upload';
+	}
+
+	/**
+	 * Check if an attachment has a delivery type.
+	 *
+	 * @param int $attachment_id The attachment to check.
+	 *
+	 * @return bool
+	 */
+	public function has_delivery_type( $attachment_id ) {
+		return ! empty( $this->get_post_meta( $attachment_id, Sync::META_KEYS['delivery'], true ) );
+	}
+
+	/**
+	 * Convert media extension.
+	 *
+	 * @param string $filename The file to convert.
+	 *
+	 * @return string|null
+	 */
+	public function convert_media_extension( $filename ) {
+
+		$conversion_types = $this->get_convertible_extensions();
+		$info             = Utils::pathinfo( $filename );
+		$convert          = 'jpg'; // Default handler.
+
+		if ( ! empty( $info['extension'] ) ) {
+			$extension = strtolower( $info['extension'] );
+
+			if ( ! empty( $conversion_types[ $extension ] ) ) {
+				$convert = $conversion_types[ $extension ];
+			}
+		}
+
+		$filename = trailingslashit( $info['dirname'] ) . $info['filename'] . '.' . $convert;
+
+		return $filename;
+	}
+
+	/**
+	 * Checks if the file is for preview only. True = only render sizes converted.
+	 *
+	 * @param int $attachment_id The attachment ID to check.
+	 *
+	 * @return bool
+	 */
+	public function is_preview_only( $attachment_id ) {
+		$base_types = array(
+			'pdf',
+			'psd',
+		);
+
+		/**
+		 * Filter the file types that are preview only.
+		 *
+		 * @hook    cloudinary_preview_types
+		 * @default array( 'pdf', 'psd' )
+		 *
+		 * @param $base_types {array} The base preview types.
+		 *
+		 * @return {array}
+		 */
+		$preview_types = apply_filters( 'cloudinary_preview_types', $base_types );
+		$mime          = wp_check_filetype( get_attached_file( $attachment_id ) );
+
+		return in_array( $mime['ext'], $preview_types, true );
+	}
+
+	/**
+	 * Get a resource type based on file. (Cloudinary v1 remove mime type in post data).
+	 *
+	 * @param string $file The file to get type for.
+	 *
+	 * @return string
+	 */
+	public function get_file_type( $file ) {
+		$file = wp_parse_url( $file, PHP_URL_PATH );
+		$file = Utils::pathinfo( $file, PATHINFO_BASENAME );
+		$mime = wp_check_filetype( $file );
+
+		return strstr( $mime['type'], '/', true );
+	}
+
+	/**
+	 * Get a resource type based on attachment_id.
+	 *
+	 * @param \WP_Post|int $attachment_id The attachment ID or object.
+	 *
+	 * @return string
+	 */
+	public function get_media_type( $attachment_id ) {
+		return $this->get_file_type( get_attached_file( $attachment_id ) );
+	}
+
+	/**
+	 * Get the resource type.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public function get_resource_type( $attachment_id ) {
+		$media_type = $this->get_media_type( $attachment_id );
+
+		switch ( $media_type ) {
+			case 'application':
+			case 'text':
+				$type = 'raw';
+				break;
+
+			case 'audio':
+				$type = 'video';
+				break;
+
+			default:
+				$type = $media_type;
+		}
+
+		/**
+		 * Filter the Cloudinary resource type for the attachment.
+		 *
+		 * @hook cloudinary_resource_type
+		 *
+		 * @param $type          {string} The type.
+		 * @param $attachment_id {int}    The attachment ID.
+		 *
+		 * @return {string}
+		 */
+		$type = apply_filters( 'cloudinary_resource_type', $type, $attachment_id );
+
+		return $type;
+	}
+
+	/**
+	 * Remove the crop size from a url.
+	 *
+	 * @param string $url The url to remove the crop from.
+	 *
+	 * @return string The uncropped url.
+	 */
+	public function uncropped_url( $url ) {
+		$cropped = $this->get_size_from_url( $url );
+		if ( false !== $cropped ) {
+			$file             = Utils::pathinfo( $url );
+			$crop             = '-' . implode( 'x', $cropped );
+			$file['filename'] = substr( $file['filename'], 0, strlen( $file['filename'] ) - strlen( $crop ) );
+			$url              = $file['dirname'] . '/' . $file['filename'] . '.' . $file['extension'];
+		}
+
+		return $url;
+	}
+
+	/**
+	 * Fetch a public id from a cloudinary url.
+	 *
+	 * @param string $url         The url to fetch the public id from.
+	 * @param bool   $as_sync_key Whether to return a plugin-based sync key, which is used to fetch an attachment id.
+	 *
+	 * @return string|null
+	 */
+	public function get_public_id_from_url( $url, $as_sync_key = false ) {
+		if ( ! $this->is_cloudinary_url( $url ) ) {
+			return null;
+		}
+
+		$path  = wp_parse_url( $url, PHP_URL_PATH );
+		$parts = explode( '/', ltrim( $path, '/' ) );
+
+		$maybe_seo = array();
+		$public_id = null;
+
+		// Need to find the version part as anything after this is the public id.
+		foreach ( $parts as $part ) {
+			$maybe_seo[] = array_shift( $parts ); // Get rid of the first element.
+			if ( 'v' === substr( $part, 0, 1 ) && is_numeric( substr( $part, 1 ) ) ) {
+				break; // Stop removing elements.
+			}
+		}
+
+		// Bail on incomplete url.
+		if ( empty( $parts ) ) {
+			return null;
+		}
+
+		// The remaining items should be the file.
+		$file      = implode( '/', $parts );
+		$path_info = Utils::pathinfo( $file );
+
+		// Is SEO friendly URL.
+		if ( in_array( 'images', $maybe_seo, true ) ) {
+			$public_id = $path_info['dirname'];
+		} elseif ( false !== strpos( $url, '/image/fetch/' ) ) {
+			// Maybe the $file is already the URL - $url has the version.
+			if ( filter_var( $file, FILTER_VALIDATE_URL ) ) {
+				$public_id = $file;
+			} else {
+				// Capture the url without the version.
+				$parts = explode( '/image/fetch/', $url );
+				if ( 1 < count( $parts ) ) {
+					$public_id = end( $parts );
+				}
+			}
+		} else {
+			$public_id = isset( $path_info['dirname'] ) && '.' !== $path_info['dirname'] ? $path_info['dirname'] . DIRECTORY_SEPARATOR . $path_info['filename'] : $path_info['filename'];
+
+			if ( ! empty( $path_info['extension'] ) && in_array( 'raw', $maybe_seo, true ) ) {
+				$public_id .= '.' . $path_info['extension'];
+			}
+		}
+		$public_id = trim( $public_id, './' );
+
+		if ( $as_sync_key ) {
+			$transformations = $this->get_transformations_from_string( $url );
+			$public_id      .= ! empty( $transformations ) ? wp_json_encode( $transformations ) : '';
+		}
+
+		return rawurldecode( $public_id );
+	}
+
+	/**
+	 * Attempt to get an attachment_id from a url.
+	 *
+	 * @param string $url The url of the file.
+	 *
+	 * @return int The attachment id or 0 if not found.
+	 */
+	public function get_id_from_url( $url ) {
+		if ( $this->is_cloudinary_url( $url ) ) {
+			$sync_key      = $this->get_public_id_from_url( $url, true );
+			$attachment_id = $this->get_id_from_sync_key( $sync_key );
+		} else {
+			// Clear out any params.
+			if ( wp_parse_url( $url, PHP_URL_QUERY ) ) {
+				$url = strstr( $url, '?', true );
+			}
+			// Local URL.
+			$url = $this->uncropped_url( $url );
+
+			// Remove the base URL so we can match it to the post meta.
+			$dirs = wp_get_upload_dir();
+			$file = ltrim( substr( $url, strlen( $dirs['baseurl'] ) + 1 ), '/' ); // Keep the slash off.
+
+			if ( function_exists( 'wpcom_vip_attachment_url_to_postid' ) ) {
+				$attachment_id = wpcom_vip_attachment_url_to_postid( $file );
+			} else {
+				$attachment_id = attachment_url_to_postid( $file ); //phpcs:ignore
+			}
+		}
+
+		return $attachment_id;
+	}
+
+	/**
+	 * Attempt to get an attachment_id from a sync key.
+	 *
+	 * @param string $sync_key Key for matching a post_id.
+	 * @param bool   $all      Flag to return all found ID's.
+	 *
+	 * @return int|array|false The attachment id or id's, or false if not found.
+	 */
+	public function get_id_from_sync_key( $sync_key, $all = false ) {
+
+		$meta_query = array(
+			array(
+				'key'     => '_' . md5( $sync_key ),
+				'compare' => 'EXISTS',
+			),
+		);
+		$query_args = array(
+			'post_type'   => 'attachment',
+			'post_status' => 'inherit',
+			'fields'      => 'ids',
+			'meta_query'  => $meta_query, // phpcs:ignore
+		);
+
+		$query         = new \WP_Query( $query_args );
+		$ids           = $query->get_posts();
+		$attachment_id = $ids;
+
+		if ( ! empty( $ids ) && false === $all ) {
+			// Essentially we should only have a single so use the first.
+			$attachment_id = array_shift( $ids );
+		}
+
+		return $attachment_id;
+	}
+
+	/**
+	 * Get all ID's linked to a public_id.
+	 *
+	 * @param string $public_id Key for matching a post_id.
+	 *
+	 * @return array
+	 */
+	public function get_linked_attachments( $public_id ) {
+
+		$meta_query = array(
+			array(
+				'key'     => '_' . md5( $public_id ),
+				'compare' => 'EXISTS',
+			),
+		);
+		$query_args = array(
+			'post_type'   => 'attachment',
+			'post_status' => 'inherit',
+			'fields'      => 'ids',
+			'meta_query'  => $meta_query, // phpcs:ignore
+		);
+
+		$query = new \WP_Query( $query_args );
+		$ids   = $query->get_posts();
+
+		return $ids;
+	}
+
+	/**
+	 * Determine crop based on filename.
+	 *
+	 * @param string $url The url to get sizes from.
+	 *
+	 * @return array | bool Array of width and height else false if not found.
+	 */
+	public function get_size_from_url( $url ) {
+		$return = false;
+		// Check if its a cloudinary URL.
+		if ( $this->is_cloudinary_url( $url ) ) {
+			$transformations = $this->get_transformations_from_string( $url );
+			foreach ( $transformations as $transformation ) {
+				if ( ! empty( $transformation['crop'] ) && ! empty( $transformation['width'] ) && ! empty( $transformation['height'] ) ) {
+					$return = array(
+						$transformation['width'],
+						$transformation['height'],
+					);
+					break;
+				}
+			}
+		} else {
+			$file     = Utils::pathinfo( $url );
+			$end_part = substr( strrchr( $file['filename'], '-' ), 1 );
+			if ( false !== $end_part && 1 === substr_count( $end_part, 'x' ) && is_numeric( str_replace( 'x', '', $end_part ) ) ) {
+
+				$size_parts = explode( 'x', $end_part );
+				$size_int   = array_map( 'intval', $size_parts );
+				$size       = array_filter( $size_int );
+				if ( ! empty( $size ) && 2 === count( $size ) ) {
+					$return = $size;
+				}
+			}
+		}
+
+		if ( $return ) {
+			$return = array_values( $return );
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Get crop size of an image.
+	 *
+	 * @param string $url           The url to get crop for.
+	 * @param int    $attachment_id image attachment id.
+	 *
+	 * @return array|bool The width and height of the crop, or false if size is custom.
+	 */
+	public function get_crop( $url, $attachment_id ) {
+		$meta = wp_get_attachment_metadata( $attachment_id );
+		if ( ! empty( $meta['sizes'] ) ) {
+			// Try and match the file name from the sizes meta data to prevent false positives from filenames that have numbers separated by an x.
+			$file             = wp_basename( $url ); // We only need the base name to check.
+			$additional_sizes = wp_get_additional_image_sizes();
+			foreach ( $meta['sizes'] as $size_name => $size ) {
+				if ( $file === $size['file'] ) {
+					$cropped = ! wp_image_matches_ratio(
+					// PDFs do not always have width and height, but they do have full sizes.
+					// This is important for the thumbnail crops on the media library.
+						! empty( $meta['width'] ) ? $meta['width'] : ( ! empty( $meta['sizes']['full']['width'] ) ? $meta['sizes']['full']['width'] : 0 ),
+						! empty( $meta['height'] ) ? $meta['height'] : ( ! empty( $meta['sizes']['full']['height'] ) ? $meta['sizes']['full']['height'] : 0 ),
+						$size['width'],
+						$size['height']
+					);
+					if ( isset( $additional_sizes[ $size_name ]['crop'] ) ) {
+						$cropped = $additional_sizes[ $size_name ]['crop'];
+					}
+					// Make the WP Size array.
+					$wp_size = array(
+						'wpsize'         => $size_name,
+						'file'           => $size['file'],
+						'width'          => $size['width'],
+						'height'         => $size['height'],
+						'transformation' => 'c_scale',
+					);
+					if ( $cropped ) {
+						// Special thumbnail size.
+						if ( 'thumbnail' === $size_name ) {
+							$wp_size['transformation'] = 'c_thumb,g_auto';
+						}
+					}
+
+					return $wp_size;
+				}
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Set a transformation that is a single type only: quality, format.
+	 *
+	 * @param array    $transformations The transformation set to check.
+	 * @param string   $type            The type of transformation to set.
+	 * @param string   $value           The value of the transformation.
+	 * @param int|bool $index           The index of the transformation array to set at.
+	 */
+	public function set_transformation( &$transformations, $type, $value, $index = false ) {
+		if ( false === $index ) {
+			$index = $this->get_transformation( $transformations, $type );
+			if ( false === $index ) {
+				$index = count( $transformations ); // Not found and no index set, append to transformation chain.
+			}
+		}
+		$transformations[ $index ][ $type ] = $value;
+	}
+
+	/**
+	 * Check if a transformation exists.
+	 *
+	 * @param array  $transformations The transformation set to check.
+	 * @param string $type            The type of transformation to check for.
+	 *
+	 * @return bool
+	 */
+	public function get_transformation( $transformations, $type ) {
+		foreach ( $transformations as $index => $transformation ) {
+			if ( isset( $transformation[ $type ] ) ) {
+				return $index;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Get transformations for an attachment to use in a final URL.
+	 *
+	 * @param int   $attachment_id             The attachment ID.
+	 * @param array $transformations           Base/starter set of transformations.
+	 * @param bool  $overwrite_transformations Flag to indicate if default transformations should not be applied.
+	 *
+	 * @return array
+	 */
+	public function get_transformations( $attachment_id, $transformations = array(), $overwrite_transformations = false ) {
+		static $cache = array();
+
+		$key = $this->get_cache_key( func_get_args() );
+		if ( isset( $cache[ $key ] ) ) {
+			return $cache[ $key ];
+		}
+
+		// If not provided, get transformations from the attachment meta.
+		if ( empty( $transformations ) ) {
+			$transformations = $this->get_transformation_from_meta( $attachment_id );
+		}
+		if ( false === $overwrite_transformations ) {
+			$overwrite_transformations = $this->maybe_overwrite_featured_image( $attachment_id );
+		}
+
+		// Defaults are only to be added on front, main images ( not breakpoints, since these are adapted down), and videos.
+		if ( false === $overwrite_transformations && ! Utils::is_admin() ) {
+			$transformations = $this->apply_default_transformations( $transformations, $attachment_id );
+		}
+
+		/**
+		 * Filter the Cloudinary transformations.
+		 *
+		 * @hook cloudinary_transformations
+		 *
+		 * @param $transformations {array} Array of transformation options.
+		 * @param $attachment_id   {int} The id of the asset.
+		 *
+		 * @return {array}
+		 */
+		$cache[ $key ] = apply_filters( 'cloudinary_transformations', $transformations, $attachment_id );
+
+		return $cache[ $key ];
+	}
+
+	/**
+	 * Get the crop transformation for the attachment.
+	 *
+	 * @param int|string $attachment_id The attachment ID or type.
+	 * @param array      $size          The requested size width and height.
+	 *
+	 * @return string
+	 */
+	public function get_crop_transformations( $attachment_id, $size ) {
+		static $transformations = array();
+		$size_dim               = $size['width'] . 'x' . $size['height'];
+		$key                    = $attachment_id . $size_dim;
+		if ( empty( $transformations[ $key ] ) ) {
+
+			if ( empty( $size['transformation'] ) ) {
+				$size['transformation'] = 'c_scale';
+			}
+			$crops = $this->settings->get_value( 'crop_sizes' );
+			if ( ! empty( $crops[ $size_dim ] ) ) {
+				if ( '--' === $crops[ $size_dim ] ) {
+					$size['transformation'] = '';
+				} else {
+					$size['transformation'] = $crops[ $size_dim ];
+				}
+			}
+
+			/**
+			 * Enable the Crop and Gravity control settings.
+			 *
+			 * @hook  cloudinary_enable_crop_and_gravity_control
+			 * @since 3.1.3
+			 * @default {false}
+			 *
+			 * @param $enabeld {bool} Is the Crop and Gravity control enabled?
+			 *
+			 * @retrun {bool}
+			 */
+			$enabled_crop_and_gravity = apply_filters( 'cloudinary_enable_crop_and_gravity_control', false );
+
+			// Check for custom crop.
+			if ( is_numeric( $attachment_id ) && $enabled_crop_and_gravity ) {
+				$meta_sizes = $this->get_post_meta( $attachment_id, 'cloudinary_metaboxes_crop_meta', true );
+				if ( ! empty( $meta_sizes['single_crop_and_gravity']['single_sizes'] ) ) {
+					$custom_sizes = $meta_sizes['single_crop_and_gravity']['single_sizes'];
+					if ( ! empty( $custom_sizes[ $size_dim ] ) ) {
+						if ( '--' === $custom_sizes[ $size_dim ] ) {
+							$size['transformation'] = '';
+						} else {
+							$size['transformation'] = $custom_sizes[ $size_dim ];
+						}
+					}
+				}
+			}
+			$transformations[ $key ] = 'w_' . $size['width'] . ',h_' . $size['height'];
+			if ( ! empty( $size['transformation'] ) ) {
+				$transformations[ $key ] .= ',' . $size['transformation'];
+			}
+		}
+
+		return $transformations[ $key ];
+	}
+
+	/**
+	 * Extract the crop size part of a transformation that was done in the DAM widget.
+	 *
+	 * @param array      $transformations The transformations to get crop from.
+	 * @param array|bool $crop            Optional crop size with width and height to balance transformations against.
+	 *
+	 * @return array|bool
+	 */
+	public function get_crop_from_transformation( $transformations, $crop = false ) {
+		if ( empty( $transformations ) ) {
+			return false;
+		}
+		$viable_parts = array_filter(
+			$transformations,
+			function ( $part ) {
+				$keys   = array_keys( $part );
+				$return = false; // phpcs:ignore
+				foreach ( $keys as $key ) {
+					if ( in_array( $key, array( 'overlay', 'underlay' ), true ) ) {
+						return false; // end immediately since overlay and underlay has internal crops.
+					}
+					if ( in_array( $key, array( 'crop', 'width', 'height' ), true ) ) {
+						$return = true;
+					}
+				}
+
+				return $return;
+			}
+		);
+		if ( ! empty( $viable_parts ) ) {
+			// A final image size is determined by the last crop element.
+			$size = array_pop( $viable_parts );
+			if ( ! empty( $crop ) ) {
+				$size = $this->balance_crop( $crop, $size );
+			}
+
+			return $size;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Extract transformations from string..
+	 *
+	 * @param string $str  The transformation string.
+	 * @param string $type The type of transformation string.
+	 *
+	 * @return array The array of found transformations within the string.
+	 */
+	public static function extract_transformations_from_string( $str, $type = 'image' ) {
+		static $media;
+		if ( ! $media ) {
+			$media = get_plugin_instance()->get_component( 'media' );
+		}
+
+		return $media->get_transformations_from_string( $str, $type );
+	}
+
+	/**
+	 * Convert a url param based transformation string into an array.
+	 *
+	 * @param string $str  The transformation string.
+	 * @param string $type The type of transformation string.
+	 *
+	 * @return array The array of found transformations within the string.
+	 */
+	public function get_transformations_from_string( $str, $type = 'image' ) {
+		if ( ! isset( Api::$transformation_index[ $type ] ) ) {
+			return array();
+		}
+
+		$params = Api::$transformation_index[ $type ];
+
+		$transformation_chains = explode( '/', $str );
+		$transformations       = array();
+		foreach ( $transformation_chains as $index => $chain ) {
+			$items = explode( ',', $chain );
+			foreach ( $items as $item ) {
+				$item = trim( $item );
+				// After the asset version, there are no further transformations.
+				if ( ! empty( $item ) && 'v' === $item[0] && is_numeric( substr( $item, 1 ) ) ) {
+					break 2;
+				}
+				foreach ( $params as $param => $transformation ) {
+					if ( substr( $item, 0, strlen( $param ) + 1 ) === $param . '_' ) {
+						$transformations[ $index ][ $transformation ] = substr( $item, strlen( $param ) + 1 );
+					}
+				}
+			}
+		}
+
+		return array_values( $transformations ); // Reset the keys.
+	}
+
+	/**
+	 * Get a cloudinary URL for an attachment.
+	 *
+	 * @param string $url           The current url.
+	 * @param int    $attachment_id The attachment ID.
+	 *
+	 * @return string Cloudinary URL.
+	 */
+	public function attachment_url( $url, $attachment_id ) {
+
+		if ( ! $this->plugin->get_component( 'delivery' )->is_deliverable( $attachment_id ) ) {
+			return $url;
+		}
+
+		// Previous v1 and Cloudinary only storage.
+		if ( false !== strpos( $url, 'https://', 5 ) ) {
+			$dirs = wp_get_upload_dir();
+
+			return str_replace( trailingslashit( $dirs['baseurl'] ), '', $url );
+		}
+
+		if (
+			false === $this->in_downsize
+			&& ! doing_filter( 'content_save_pre' )
+			&& ! Utils::is_saving_metadata()
+			/**
+			 * Filter doing upload.
+			 * If so, return the default attachment URL.
+			 *
+			 * @hook    cloudinary_doing_upload
+			 * @default false
+			 *
+			 * @param $false {bool} Default false.
+			 *
+			 * @return {bool}
+			 */
+			&& ! apply_filters( 'cloudinary_doing_upload', false )
+		) {
+			if ( ! $this->is_cloudinary_url( $url ) && $this->cloudinary_id( $attachment_id ) ) {
+				$url = $this->cloudinary_url( $attachment_id );
+			}
+		}
+
+		return $url;
+	}
+
+	/**
+	 * Get the original size URL, when original_attachment_url is called, and it's a Cloudinary URL.
+	 *
+	 * @param string $url           The current url.
+	 * @param int    $attachment_id The attachment ID.
+	 *
+	 * @return string Cloudinary URL.
+	 */
+	public function original_attachment_url( $url, $attachment_id ) {
+		if ( $this->is_cloudinary_url( $url ) ) {
+			$url = $this->raw_cloudinary_url( $attachment_id );
+		}
+
+		return $url;
+	}
+
+	/**
+	 * Apply default image transformations before building the URL.
+	 *
+	 * @param array      $transformations    The set of transformations.
+	 * @param int|string $attachment_id_type The attachment ID | or attachment type.
+	 *
+	 * @return array
+	 */
+	public function apply_default_transformations( array $transformations, $attachment_id_type ) {
+		static $cache = array(), $freeform = array();
+
+		$key = $this->get_cache_key( func_get_args() );
+		if ( isset( $cache[ $key ] ) ) {
+			return $cache[ $key ];
+		}
+		/**
+		 * Filter to allow bypassing defaults. Return false to not apply defaults.
+		 *
+		 * @hook    cloudinary_apply_default_transformations
+		 * @default true
+		 *
+		 * @param $true          {bool} True to apply defaults.
+		 * @param $attachment_id {int}  The current attachment ID.
+		 *
+		 * @return {bool}
+		 */
+		if ( false === apply_filters( 'cloudinary_apply_default_transformations', true, $attachment_id_type ) ) {
+			return $transformations;
+		}
+		$type = $attachment_id_type;
+		if ( is_numeric( $attachment_id_type ) ) {
+			$type = $this->get_media_type( $attachment_id_type );
+		}
+		// Base image level.
+		$new_transformations = array(
+			'video'  => array(),
+			'image'  => Api::generate_transformation_string( $transformations, $type ),
+			'tax'    => array(),
+			'global' => array(),
+			'qf'     => array(),
+		);
+		// Get Taxonomies.
+		$new_transformations['tax'] = $this->global_transformations->get_taxonomy_transformations( $type );
+		if ( ! $this->global_transformations->is_taxonomy_overwrite() ) {
+			/**
+			 * Filter the default Quality and Format transformations for the specific media type.
+			 *
+			 * @hook    cloudinary_default_qf_transformations_{$type}
+			 * @default array()
+			 *
+			 * @param $defaults        {array} The default transformations array.
+			 * @param $transformations {array} The current transformations array.
+			 *
+			 * @return {array}
+			 */
+			$default                   = apply_filters( "cloudinary_default_qf_transformations_{$type}", array(), $transformations );
+			$default                   = array_filter( $default ); // Clear out empty settings.
+			$new_transformations['qf'] = Api::generate_transformation_string( array( $default ), $type );
+
+			if ( empty( $freeform[ $type ] ) ) {
+				/**
+				 * Filter the default Freeform transformations for the specific media type.
+				 *
+				 * @hook    cloudinary_default_freeform_transformations_{$type}
+				 * @default array()
+				 *
+				 * @param $defaults        {array} The default transformations array.
+				 * @param $transformations {array} The current transformations array.
+				 *
+				 * @return {array}
+				 */
+				$freeform[ $type ] = apply_filters( "cloudinary_default_freeform_transformations_{$type}", array(), $transformations );
+				$freeform[ $type ] = array_filter( $freeform[ $type ] ); // Clear out empty settings.
+			}
+			// Add freeform global transformations.
+			if ( ! empty( $freeform[ $type ] ) ) {
+				$new_transformations['global'] = implode( '/', $freeform[ $type ] );
+			}
+		}
+
+		$streaming = $this->get_settings()->get_value( 'adaptive_streaming', 'adaptive_streaming_mode' );
+
+		if ( 'video' === $type && 'on' === $streaming['adaptive_streaming'] ) {
+			unset( $new_transformations['qf'] );
+		}
+
+		// Clean out empty parts, and join into a sectioned string.
+		$new_transformations = array_filter( $new_transformations );
+		$new_transformations = implode( '/', $new_transformations );
+		// Take sectioned string, and create a transformation array set.
+		$transformations = $this->get_transformations_from_string( $new_transformations, $type );
+		/**
+		 * Filter the default cloudinary transformations.
+		 *
+		 * @hook cloudinary_default_transformations
+		 *
+		 * @param $defaults {array} The default transformations array.
+		 *
+		 * @return {array}
+		 */
+		$cache[ $key ] = apply_filters(
+			'cloudinary_default_transformations',
+			$transformations
+		);
+
+		return $cache[ $key ];
+	}
+
+	/**
+	 * Apply default  quality anf format image transformations.
+	 *
+	 * @param array $default The current default transformations.
+	 *
+	 * @return array
+	 */
+	public function default_image_transformations( $default ) {
+
+		$config = $this->settings->get_value( 'image_settings' );
+
+		if ( 'on' === $config['image_optimization'] ) {
+			if ( ! empty( $config['image_format'] ) && 'none' !== $config['image_format'] ) {
+				$default['fetch_format'] = $config['image_format'];
+			}
+			if ( isset( $config['image_quality'] ) ) {
+				$default['quality'] = 'none' !== $config['image_quality'] ? $config['image_quality'] : null;
+			} else {
+				$default['quality'] = 'auto';
+			}
+		}
+
+		return $default;
+	}
+
+	/**
+	 * Apply default image freeform transformations.
+	 *
+	 * @param array $default The current default transformations.
+	 *
+	 * @return array
+	 */
+	public function default_image_freeform_transformations( $default ) {
+		$config = $this->settings->get_value( 'image_settings' );
+		if ( ! empty( $config['image_freeform'] ) ) {
+			$default[] = trim( $config['image_freeform'] );
+		}
+
+		return $default;
+	}
+
+	/**
+	 * Get a cache key for static caching.
+	 *
+	 * @param array $args The arguments array to generate a key with.
+	 *
+	 * @return string
+	 */
+	protected function get_cache_key( $args ) {
+		$args[] = $this->global_transformations->get_current_post();
+
+		return md5( wp_json_encode( $args ) );
+	}
+
+	/**
+	 * Generate a Cloudinary URL based on attachment ID and required size.
+	 *
+	 * @param int          $attachment_id             The id of the attachment.
+	 * @param array|string $size                      The wp size to set for the URL.
+	 * @param array|string $transformations           Set of transformations to apply to this url.
+	 * @param string|null  $cloudinary_id             Optional forced cloudinary ID.
+	 * @param bool         $overwrite_transformations Flag url is a breakpoint URL to stop re-applying default transformations.
+	 *
+	 * @return string The converted URL.
+	 */
+	public function cloudinary_url( $attachment_id, $size = array(), $transformations = array(), $cloudinary_id = null, $overwrite_transformations = false ) {
+		static $cache = array();
+
+		if ( ! $cloudinary_id ) {
+			$cloudinary_id = $this->cloudinary_id( $attachment_id );
+			if ( ! $cloudinary_id ) {
+				return null;
+			}
+		}
+
+		$public_id = $this->get_post_meta( $attachment_id, Sync::META_KEYS['public_id'], true );
+
+		if ( ! empty( $public_id ) ) {
+			$cloudinary_id = $public_id;
+		}
+
+		$args = array(
+			$attachment_id,
+			$size,
+			$transformations,
+			$cloudinary_id,
+			$overwrite_transformations,
+		);
+
+		$key = $this->get_cache_key( array_filter( $args ) );
+		if ( isset( $cache[ $key ] ) ) {
+			return $cache[ $key ];
+		}
+
+		// Get the attachment resource type.
+		$resource_type = $this->get_resource_type( $attachment_id );
+		// Setup initial args for cloudinary_url.
+		$delivery = $this->get_media_delivery( $attachment_id );
+		$pre_args = array(
+			'secure'        => is_ssl(),
+			'version'       => $this->get_cloudinary_version( $attachment_id ),
+			'resource_type' => $resource_type,
+			'delivery'      => $delivery,
+		);
+		$set_size = array();
+		if ( 'upload' === $delivery ) {
+			$set_size = $this->prepare_size( $attachment_id, $size );
+		}
+		// Prepare transformations.
+		if ( ! empty( $transformations ) && is_string( $transformations ) ) {
+			$transformations = $this->get_transformations_from_string( $transformations, $resource_type );
+		}
+		$pre_args['transformation'] = $this->get_transformations( $attachment_id, $transformations, $overwrite_transformations );
+
+		// Make a copy as not to destroy the options in \Cloudinary::cloudinary_url().
+		$args = $pre_args;
+		$url  = $this->plugin->components['connect']->api->cloudinary_url( $cloudinary_id, $args, $set_size, $attachment_id );
+
+		// Check if this type is a preview only type. i.e PDF.
+		if ( ! empty( $set_size ) && $this->is_preview_only( $attachment_id ) ) {
+			$url = $this->convert_media_extension( $url );
+		}
+
+		/**
+		 * Filter the final Cloudinary URL.
+		 *
+		 * @hook cloudinary_converted_url
+		 *
+		 * @param $url           {string} The Cloudinary URL.
+		 * @param $attachment_id {int}    The id of the attachment.
+		 * @param $pre_args      {array}  The arguments used to create the url.
+		 *
+		 * @return {string}
+		 */
+		$url = apply_filters( 'cloudinary_converted_url', $url, $attachment_id, $pre_args );
+
+		// Early bail for admin AJAX requests.
+		if ( defined( 'DOING_AJAX' ) && DOING_AJAX && is_admin() ) {
+			$cache[ $key ] = $url;
+
+			return $cache[ $key ];
+		}
+
+		// Add Cloudinary analytics.
+		$cache[ $key ] = add_query_arg(
+			array(
+				'_i' => 'AA',
+			),
+			$url
+		);
+
+		return $cache[ $key ];
+	}
+
+	/**
+	 * Get the local URL for an attachment.
+	 *
+	 * @param int $attachment_id The attachment ID to get.
+	 *
+	 * @return string|false
+	 */
+	public function local_url( $attachment_id ) {
+		static $urls = array();
+		if ( ! empty( $urls[ $attachment_id ] ) ) {
+			return $urls[ $attachment_id ];
+		}
+		$this->in_downsize      = true;
+		$urls[ $attachment_id ] = wp_get_attachment_url( $attachment_id );
+		$this->in_downsize      = false;
+
+		/**
+		 * Filter local URL.
+		 *
+		 * @hook    cloudinary_local_url
+		 * @since   3.0.0
+		 *
+		 * @param $url           {string|false} The local URL
+		 * @param $attachment_id {int}          The attachment ID.
+		 *
+		 * @return  {string|false}
+		 */
+		return apply_filters( 'cloudinary_local_url', $urls[ $attachment_id ], $attachment_id );
+	}
+
+	/**
+	 * Get the local URL for an attachment.
+	 *
+	 * @param int $attachment_id The attachment ID to get.
+	 *
+	 * @return string|false
+	 */
+	public function raw_cloudinary_url( $attachment_id ) {
+		static $api;
+		if ( ! $api ) {
+			$api = $this->plugin->components['connect']->api;
+		}
+
+		$url = $this->get_post_meta( $attachment_id, Sync::META_KEYS['raw_url'], true );
+		if ( empty( $url ) ) {
+			$transformations = $this->get_transformation_from_meta( $attachment_id );
+			$parts           = array(
+				'https:/',
+				$api->asset_url,
+			);
+
+			// We should use the cloud name on cname accounts.
+			if ( empty( $this->credentials['cname'] ) ) {
+				$parts[] = $api->credentials['cloud_name'];
+			}
+
+			$parts = array_merge(
+				$parts,
+				array(
+					$this->get_resource_type( $attachment_id ),
+					$this->get_media_delivery( $attachment_id ),
+					$api::generate_transformation_string( $transformations ),
+					'v' . $this->get_cloudinary_version( $attachment_id ),
+					$this->get_cloudinary_id( $attachment_id ),
+				)
+			);
+
+			$url = implode( '/', array_filter( $parts ) );
+			$this->update_post_meta( $attachment_id, Sync::META_KEYS['raw_url'], $url );
+		}
+
+		/**
+		 * Filter a base Cloudinary URL (no transformations).
+		 *
+		 * @hook    cloudinary_raw_url
+		 * @since   3.0.0
+		 *
+		 * @param $url           {string|false} The local URL
+		 * @param $attachment_id {int}  The attachment ID.
+		 *
+		 * @return  {string|false}
+		 */
+		return apply_filters( 'cloudinary_raw_url', $url, $attachment_id );
+	}
+
+	/**
+	 * Prepare the Size array for the Cloudinary URL API.
+	 *
+	 * @param int          $attachment_id The attachment ID.
+	 * @param array|string $size          The size array or slug.
+	 *
+	 * @return array|string
+	 */
+	public function prepare_size( $attachment_id, $size ) {
+		if ( 'raw' === $size ) {
+			return array();
+		}
+		// Check size and correct if string or size.
+		if ( empty( $size ) || 'full' === $size ) {
+			// Maybe get full size if scaled.
+			$meta = wp_get_attachment_metadata( $attachment_id, true );
+			if ( ! empty( $meta['original_image'] ) ) {
+				$size = array(
+					'width'  => $meta['width'],
+					'height' => $meta['height'],
+					'full'   => true,
+				);
+			}
+		} elseif ( is_string( $size ) || ( is_array( $size ) && 3 === count( $size ) ) ) {
+			$intermediate = image_get_intermediate_size( $attachment_id, $size );
+			// PDF's do not have intermediate URL.
+			if ( is_array( $intermediate ) && ! empty( $intermediate['url'] ) ) {
+				$size = $this->get_crop( $intermediate['url'], $attachment_id );
+			}
+		} elseif ( array_keys( $size ) === array( 0, 1 ) ) {
+			$size = array(
+				'width'  => $size[0],
+				'height' => $size[1],
+			);
+			if ( $size['width'] === $size['height'] ) {
+				$size['transformation'] = 'c_fill,g_auto';
+			}
+		}
+
+		// add Global crops.
+		if ( ! empty( $size['width'] ) && ! empty( $size['height'] ) ) {
+			$size['transformation'] = $this->get_crop_transformations( $attachment_id, $size );
+		}
+		/**
+		 * Filter Cloudinary size and crops
+		 *
+		 * @hook cloudinary_prepare_size
+		 *
+		 * @param $size          {array|string} The size array or slug.
+		 * @param $attachment_id {int}          The attachment ID.
+		 *
+		 * @return {array|string}
+		 */
+		$size = apply_filters( 'cloudinary_prepare_size', $size, $attachment_id );
+
+		return $size;
+	}
+
+	/**
+	 * Add domain to subdir.
+	 *
+	 * @param array $dirs The internal directory structures.
+	 *
+	 * @return array Altered array of paths.
+	 */
+	public function upload_dir( $dirs ) {
+
+		$dirs['cloudinary_folder'] = $this->get_cloudinary_folder();
+
+		return $dirs;
+	}
+
+	/**
+	 * Get the setup Cloudinary Folder.
+	 *
+	 * @param bool $add_trailing_slash Whether to add trailing slash or not.
+	 *
+	 * @return string
+	 */
+	public function get_cloudinary_folder( $add_trailing_slash = true ) {
+		$folder = '';
+
+		if ( ! empty( $this->cloudinary_folder ) && '/' !== $this->cloudinary_folder ) {
+			$folder = trim( $this->cloudinary_folder, '/' );
+
+			if ( $add_trailing_slash ) {
+				$folder = trailingslashit( $folder );
+			}
+		}
+
+		return $folder;
+	}
+
+	/**
+	 * Get a public ID.
+	 *
+	 * @param int  $attachment_id The Attachment ID.
+	 * @param bool $suffixed      Flag to get suffixed version of ID.
+	 *
+	 * @return string
+	 */
+	public function get_public_id( $attachment_id, $suffixed = false ) {
+		// Check for a public_id.
+		if ( $this->has_public_id( $attachment_id ) ) {
+			$public_id = $this->get_post_meta( $attachment_id, Sync::META_KEYS['public_id'], true );
+			if ( true === $suffixed && ! empty( $this->get_post_meta( $attachment_id, Sync::META_KEYS['suffix'], true ) ) ) {
+				$suffix = $this->get_post_meta( $attachment_id, Sync::META_KEYS['suffix'], true );
+				if ( false === strrpos( $public_id, $suffix ) ) {
+					$public_id .= $suffix;
+					$this->update_post_meta( $attachment_id, Sync::META_KEYS['public_id'], $public_id );
+				}
+				$this->delete_post_meta( $attachment_id, Sync::META_KEYS['suffix'] );
+			}
+		} else {
+			$public_id = $this->sync->generate_public_id( $attachment_id );
+		}
+
+		return $public_id;
+	}
+
+	/**
+	 * Check if an attachment has a public ID.
+	 *
+	 * @param int $attachment_id The Attachment ID.
+	 *
+	 * @return bool
+	 */
+	public function has_public_id( $attachment_id ) {
+		$new_id = $this->get_post_meta( $attachment_id, Sync::META_KEYS['public_id'], true );
+		$id     = get_post_meta( $attachment_id, Sync::META_KEYS['public_id'], true );
+
+		return ! empty( $new_id ) || ! empty( $id );
+	}
+
+	/**
+	 * Get a Cloudinary ID which includes the file format extension.
+	 *
+	 * @param int $attachment_id The Attachment ID.
+	 *
+	 * @return string|null
+	 */
+	public function get_cloudinary_id( $attachment_id ) {
+
+		$cloudinary_id = null;
+		// A cloudinary_id is a public_id with a file extension.
+		if ( $this->has_public_id( $attachment_id ) ) {
+			$public_id = $this->get_public_id( $attachment_id, true );
+			// Get the file, and use the same extension.
+			$file = get_attached_file( $attachment_id );
+			// @todo: Make this use the globals, overrides, and application conversion.
+			$extension     = Utils::pathinfo( $file, PATHINFO_EXTENSION );
+			$cloudinary_id = $public_id;
+			$type          = $this->get_resource_type( $attachment_id );
+			if ( in_array( $type, array( 'image', 'video' ), true ) ) {
+				$format = $this->settings->find_setting( $type . '_format' )->get_value();
+				if ( ! in_array( $format, array( 'none', 'auto' ), true ) ) {
+					$extension = $format;
+				}
+				if ( 'fetch' !== $this->get_media_delivery( $attachment_id ) ) {
+					$cloudinary_id = $public_id . '.' . $extension;
+				}
+			} elseif ( empty( Utils::pathinfo( $public_id, PATHINFO_EXTENSION ) ) ) {
+				$cloudinary_id = $public_id . '.' . $extension;
+			}
+		}
+
+		return $cloudinary_id;
+	}
+
+	/**
+	 * Get a Cloudinary ID
+	 *
+	 * @param int $attachment_id The ID to get Cloudinary id for.
+	 *
+	 * @return string|false the ID or false if not existing.
+	 */
+	public function cloudinary_id( $attachment_id ) {
+		static $cloudinary_ids = array();
+
+		// Return cached ID if we've already gotten it before.
+		if ( isset( $cloudinary_ids[ $attachment_id ] ) ) {
+			return $cloudinary_ids[ $attachment_id ];
+		}
+
+		if ( ! $this->is_media( $attachment_id ) ) {
+			$cloudinary_ids[ $attachment_id ] = false;
+
+			return false;
+		}
+
+		if ( ! $this->sync->is_synced( $attachment_id ) && ! defined( 'REST_REQUEST' ) ) {
+			$sync_type = $this->sync->maybe_prepare_sync( $attachment_id );
+			// Check sync type allows for continued rendering. i.e meta update, breakpoints etc, will still allow the URL to work,
+			// Where is type "file" will not since it's still being uploaded.
+			if ( $this->sync->is_required( $sync_type, $attachment_id ) ) {
+				// Cache ID to prevent multiple lookups.
+				$cloudinary_ids[ $attachment_id ] = false;
+
+				return false; // Return and render local URLs.
+			}
+		}
+
+		$cloudinary_id = $this->get_cloudinary_id( $attachment_id );
+
+		/**
+		 * Filter to validate the Cloudinary ID to allow extending it's availability.
+		 *
+		 * @hook cloudinary_validate_cloudinary_id
+		 *
+		 * @param $cloudinary_id {string|bool} The public ID from Cloudinary, or false if not found.
+		 * @param $attachment_id {int}         The id of the asset.
+		 *
+		 * @return {string|bool}
+		 */
+		$cloudinary_id = apply_filters( 'cloudinary_validate_cloudinary_id', $cloudinary_id, $attachment_id );
+
+		/**
+		 * Action the Cloudinary ID to allow extending it's availability.
+		 *
+		 * @hook  cloudinary_id
+		 *
+		 * @param $cloudinary_id {string|bool} The public ID from Cloudinary, or false if not found.
+		 * @param $attachment_id {int}         The id of the asset.
+		 *
+		 * @since 2.1.9
+		 */
+		do_action( 'cloudinary_id', $cloudinary_id, $attachment_id );
+
+		$cloudinary_ids[ $attachment_id ] = $cloudinary_id;
+
+		return $cloudinary_id;
+	}
+
+	/**
+	 * Filter the requested image and return image source.
+	 *
+	 * @param null         $image         The null image value for short circuit check.
+	 * @param int          $attachment_id The ID of the attachment.
+	 * @param string|array $size          The requested size of the image.
+	 *
+	 * @return array The image array of size and url.
+	 * @uses filter:image_downsize
+	 */
+	public function filter_downsize( $image, $attachment_id, $size ) {
+		if ( ! $this->plugin->get_component( 'delivery' )->is_deliverable( $attachment_id ) ) {
+			return $image;
+		}
+
+		// Don't do this while saving.
+		if ( true === $this->in_downsize || doing_filter( 'content_save_pre' ) || wp_attachment_is( 'video', $attachment_id ) || Utils::is_saving_metadata() ) {
+			return $image;
+		}
+
+		$cloudinary_id = $this->cloudinary_id( $attachment_id );
+
+		if ( $cloudinary_id ) {
+			$this->in_downsize = true;
+			$intermediate      = image_get_intermediate_size( $attachment_id, $size );
+			if ( is_array( $intermediate ) ) {
+				// Found an intermediate size.
+				$image = array(
+					$this->convert_url( $intermediate['file'], $attachment_id, array(), false ),
+					$intermediate['width'],
+					$intermediate['height'],
+					true,
+				);
+			}
+			$this->in_downsize = false;
+
+			// Preview formats.
+			if ( empty( $image ) && $this->is_preview_only( $attachment_id ) ) {
+				$image = array(
+					$this->cloudinary_url( $attachment_id, $size, array(), $cloudinary_id ),
+					$size[0],
+					$size[1],
+					false,
+				);
+			}
+		}
+
+		return $image;
+	}
+
+	/**
+	 * At the point of running wp_get_attachment_image_srcset, the $image_src Should be a Cloudinary URL, unless not synced.
+	 * This will fix the $image_meta so that the there's a match $src_matched on wp_calculate_image_srcset.
+	 *
+	 * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
+	 * @param int[]  $size_array    {
+	 *     An array of width and height values.
+	 *
+	 *     @type int $0 The width in pixels.
+	 *     @type int $1 The height in pixels.
+	 * }
+	 * @param string $image_src     The 'src' of the image.
+	 *
+	 * @return array
+	 */
+	public function calculate_image_srcset_meta( $image_meta, $size_array, $image_src ) {
+		if ( $this->is_cloudinary_url( $image_src ) ) {
+			$image_meta['file'] = wp_parse_url( $image_src, PHP_URL_PATH );
+		}
+
+		// PDFs don't have sizes, so we need to inject them.
+		if ( empty( $image_meta['width'] ) ) {
+			$image_meta['width'] = $size_array[0];
+		}
+
+		if ( empty( $image_meta['height'] ) ) {
+			$image_meta['height'] = $size_array[1];
+		}
+
+		return $image_meta;
+	}
+
+	/**
+	 * Convert an attachment URL to a Cloudinary one.
+	 *
+	 * @param string      $url                       Url to convert.
+	 * @param int         $attachment_id             Attachment ID.
+	 * @param array       $transformations           Optional transformations.
+	 * @param bool        $overwrite_transformations Flag url as having an overwrite transformation.
+	 * @param string|null $cloudinary_id             The cloudinary ID if have one.
+	 *
+	 * @return string Converted URL.
+	 */
+	public function convert_url( $url, $attachment_id, $transformations = array(), $overwrite_transformations = true, $cloudinary_id = null ) {
+
+		if ( $this->is_cloudinary_url( $url ) ) {
+			return $url; // Already is a cloudinary URL, just return.
+		}
+		$size = $this->get_crop( $url, $attachment_id );
+
+		return $this->cloudinary_url( $attachment_id, $size, $transformations, $cloudinary_id, $overwrite_transformations );
+	}
+
+	/**
+	 * Get the responsive breakpoints for the image.
+	 *
+	 * @param array  $sources       The original sources array.
+	 * @param array  $size_array    The size array.
+	 * @param string $image_src     The original image source.
+	 * @param array  $image_meta    The image meta array.
+	 * @param int    $attachment_id The attachment id.
+	 *
+	 * @return array Altered or same sources array.
+	 */
+	public function image_srcset( $sources, $size_array, $image_src, $image_meta, $attachment_id ) {
+
+		if ( ! $this->plugin->get_component( 'delivery' )->is_deliverable( $attachment_id ) ) {
+			return $sources;
+		}
+
+		$cloudinary_id = isset( $image_meta['cloudinary_id'] ) ? $image_meta['cloudinary_id'] : $this->cloudinary_id( $attachment_id );
+		if ( ! $cloudinary_id ) {
+			return $sources; // Return WordPress default sources.
+		}
+
+		// Get transformations if any.
+		$transformations = Relate::get_transformations( $attachment_id );
+
+		// For cases where transformations are added via cld_params.
+		if ( ! empty( $image_meta['transformations'] ) ) {
+			$transformations = array_filter( array_merge( $transformations, $image_meta['transformations'] ) );
+		}
+
+		// Use Cloudinary breakpoints for same ratio.
+		$image_meta['overwrite_transformations'] = ! empty( $image_meta['overwrite_transformations'] ) ? $image_meta['overwrite_transformations'] : false;
+
+		if ( ! empty( $image_meta['width'] ) && ! empty( $image_meta['height'] ) && 'on' === $this->settings->get_setting( 'enable_breakpoints' )->get_value() && wp_image_matches_ratio( $image_meta['width'], $image_meta['height'], $size_array[0], $size_array[1] ) ) {
+			$meta = $this->get_post_meta( $attachment_id, Sync::META_KEYS['breakpoints'], true );
+			if ( ! empty( $meta ) ) {
+				// Since srcset is primary and src is a fallback, we need to set the first srcset with the main image.
+				$sources = array(
+					$size_array[0] => array(
+						'url'        => $image_src,
+						'descriptor' => 'w',
+						'value'      => $size_array[0],
+					),
+				);
+				// Check if the image has a crop.
+				$crop = $this->get_crop_from_transformation( $transformations );
+				if ( ! empty( $crop ) ) {
+					// Remove the crop from the transformation.
+					$transformations = array_filter(
+						$transformations,
+						function ( $item ) use ( $crop ) {
+							return $item !== $crop;
+						}
+					);
+				}
+				foreach ( $meta as $breakpoint ) {
+
+					$size                            = array(
+						'crop'  => 'scale',
+						'width' => $breakpoint['width'],
+					);
+					$sources[ $breakpoint['width'] ] = array(
+						'url'        => $this->cloudinary_url( $attachment_id, $size, $transformations, $cloudinary_id, $image_meta['overwrite_transformations'] ),
+						'descriptor' => 'w',
+						'value'      => $breakpoint['width'],
+					);
+				}
+				krsort( $sources, SORT_NUMERIC );
+
+				return $sources;
+			}
+		}
+
+		// Handle unexpected sources variable type.
+		if ( ! is_array( $sources ) ) {
+			return $sources;
+		}
+
+		// Add the main size as the largest srcset src.
+		$crop = $this->get_crop_from_transformation( $transformations );
+		if ( ! empty( $crop ) ) {
+			// A valid crop could be just a crop mode and an edge size. Either width or height, or both.
+			$size             = ! empty( $crop['width'] ) ? $crop['width'] : $crop['height'];
+			$type             = ! empty( $crop['width'] ) ? 'w' : 'h';
+			$sources[ $size ] = array(
+				'url'        => $image_src,
+				'descriptor' => $type,
+				'value'      => $size,
+			);
+		}
+		// Use current sources, but convert the URLS.
+		foreach ( $sources as &$source ) {
+			if ( ! $this->is_cloudinary_url( $source['url'] ) ) {
+				$size          = $this->get_size_from_url( $source['url'] );
+				$source['url'] = $this->cloudinary_url( $attachment_id, $size, $transformations, $cloudinary_id, $image_meta['overwrite_transformations'] );
+			}
+		}
+
+		return $sources;
+	}
+
+	/**
+	 * Check if a url is a cloudinary url or not.
+	 *
+	 * @param string $url The url in question.
+	 *
+	 * @return bool
+	 */
+	public function is_cloudinary_url( $url ) {
+		if ( ! filter_var( utf8_uri_encode( $url ), FILTER_VALIDATE_URL ) ) {
+			return false;
+		}
+		$test_parts = wp_parse_url( $url );
+		$cld_url    = wp_parse_url( $this->base_url, PHP_URL_HOST );
+
+		return isset( $test_parts['path'] ) && false !== strpos( $test_parts['host'], $cld_url );
+	}
+
+	/**
+	 * Check if a url is pointing to Cloudinary sync folder.
+	 *
+	 * @param string $url The tested URL.
+	 *
+	 * @return bool
+	 */
+	public function is_cloudinary_sync_folder( $url ) {
+		$path  = wp_parse_url( $url, PHP_URL_PATH );
+		$parts = explode( '/', $path );
+
+		// Remove public id and file name.
+		array_splice( $parts, - 2 );
+
+		foreach ( $parts as $part ) {
+			array_shift( $parts );
+			if ( empty( $part ) ) {
+				continue;
+			}
+			if ( 'v' === $part[0] && is_numeric( substr( $part, 1 ) ) ) {
+				break;
+			}
+		}
+
+		// Check for the Cloudinary folder.
+		return implode( '/', $parts ) === $this->get_cloudinary_folder( false );
+	}
+
+	/**
+	 * Add media tab template.
+	 */
+	public function media_template() {
+		?>
+		<script type="text/html" id="tmpl-cloudinary-dam">
+			<div id="cloudinary-dam-{{ data.controller.cid }}" class="cloudinary-widget-wrapper"></div>
+		</script>
+		<?php
+	}
+
+	/**
+	 * Setup and include cloudinary assets for DAM widget.
+	 */
+	public function editor_assets() {
+		$deps = wp_script_is( 'cld-core', 'registered' ) ? array( 'cld-core' ) : array();
+		$this->plugin->register_assets(); // Ensure assets are registered.
+
+		/**
+		 * Filter the maximum number of files that can be imported from Cloudinary.
+		 *
+		 * @hook    cloudinary_max_files_import
+		 * @since   3.1.3
+		 *
+		 * @param $max_files {int} The maximum number of files that can be imported from Cloudinary.
+		 *
+		 * @default 20
+		 *
+		 * @return  {int}
+		 *
+		 * @example
+		 * <?php
+		 *
+		 * // Filter Cloudinary max files per import.
+		 * add_filter(
+		 *    'cloudinary_max_files_import',
+		 *    static function() {
+		 *        return 100;
+		 *    }
+		 * );
+		 */
+		$max_files = apply_filters( 'cloudinary_max_files_import', 20 );
+
+		// External assets.
+		wp_enqueue_script( 'cloudinary-media-modal', $this->plugin->dir_url . '/js/media-modal.js', null, $this->plugin->version, true );
+		wp_enqueue_script( 'cloudinary-media-library', CLOUDINARY_ENDPOINTS_MEDIA_LIBRARY, $deps, $this->plugin->version, true );
+		wp_enqueue_script( 'cloudinary-terms-order', $this->plugin->dir_url . '/js/terms-order.js', array( 'jquery' ), $this->plugin->version, true );
+		wp_enqueue_style( 'cloudinary' );
+		$params = array(
+			'nonce'     => wp_create_nonce( 'wp_rest' ),
+			'storage'   => $this->plugin->components['storage']->get_settings( 'offload' ),
+			'mloptions' => array(
+				'cloud_name'     => $this->credentials['cloud_name'],
+				'api_key'        => $this->credentials['api_key'],
+				'cms_type'       => 'wordpress',
+				'insert_caption' => __( 'Import', 'cloudinary' ),
+				'remove_header'  => true,
+				'max_files'      => $max_files,
+				'integration'    => array(
+					'type'     => 'wordpress_plugin',
+					'platform' => 'WordPress ' . get_bloginfo( 'version' ),
+					'version'  => $this->plugin->version,
+				),
+			),
+		);
+
+		// Set folder if needed.
+		$folder = $this->get_cloudinary_folder( false );
+		if ( ! empty( $folder ) ) {
+			$params['mloptions']['folder'] = array( 'path' => $folder );
+		}
+
+		$params['mloptions']['insert_transformation'] = true;
+		$params['mloptions']['inline_container']      = '#cloudinary-dam';
+
+		wp_add_inline_script( 'cloudinary-media-library', 'var CLDN = ' . wp_json_encode( $params ), 'before' );
+	}
+
+	/**
+	 * Create a new attachment post item.
+	 *
+	 * @param array  $asset     The asset array data.
+	 * @param string $public_id The cloudinary public id.
+	 *
+	 * @return int|WP_Error
+	 */
+	private function create_attachment( $asset, $public_id ) {
+
+		// Create an attachment post.
+		$file_path        = $asset['url'];
+		$file_name        = wp_basename( $file_path );
+		$file_type        = wp_check_filetype( $file_name, null );
+		$attachment_title = sanitize_file_name( Utils::pathinfo( $file_name, PATHINFO_FILENAME ) );
+		$post_args        = array(
+			'post_mime_type' => $file_type['type'],
+			'post_title'     => $attachment_title,
+			'post_content'   => '',
+			'post_status'    => 'inherit',
+		);
+
+		// Capture the Caption Text.
+		if ( ! empty( $asset['meta']['caption'] ) ) {
+			$post_args['post_excerpt'] = wp_strip_all_tags( $asset['meta']['caption'] );
+		}
+
+		// Disable Upload_Sync to avoid sync loop.
+		add_filter( 'cloudinary_upload_sync_enabled', '__return_false' );
+		// Create the attachment.
+		$attachment_id = wp_insert_attachment( $post_args, false );
+
+		$sync_key = $asset['sync_key'];
+		// Capture public_id. Use core update_post_meta since this attachment data doesnt exist yet.
+		$this->update_post_meta( $attachment_id, Sync::META_KEYS['public_id'], $public_id );
+
+		// Capture version number.
+		$this->update_post_meta( $attachment_id, Sync::META_KEYS['version'], $asset['version'] );
+		if ( ! empty( $asset['transformations'] ) ) {
+			// Save a combined key.
+			$sync_key .= wp_json_encode( $asset['transformations'] );
+			// Use post meta temporarily to store the transformations until the attachment gets a sync relationship.
+			$this->update_post_meta( $attachment_id, Sync::META_KEYS['transformation'], $asset['transformations'] );
+		}
+
+		// Create a trackable key in post meta to allow getting the attachment id from URL with transformations.
+		update_post_meta( $attachment_id, '_' . md5( $sync_key ), true );
+
+		// Create a trackable key in post meta to allow getting the attachment id from URL.
+		update_post_meta( $attachment_id, '_' . md5( 'base_' . $public_id ), true );
+
+		// capture the delivery type.
+		$this->update_post_meta( $attachment_id, Sync::META_KEYS['delivery'], $asset['type'] );
+		// Capture the ALT Text.
+		if ( ! empty( $asset['meta']['alt'] ) ) {
+			$alt_text = wp_strip_all_tags( $asset['meta']['alt'] );
+			update_post_meta( $attachment_id, '_wp_attachment_image_alt', $alt_text );
+		}
+
+		return $attachment_id;
+	}
+
+	/**
+	 * Balance a resize crop that's missing a height or width.
+	 *
+	 * @param array $size       The current size.
+	 * @param array $shift_size The size to balance to.
+	 *
+	 * @return array
+	 */
+	public function balance_crop( $size, $shift_size ) {
+
+		// Check if both width and height are present, and add missing dimension.
+		if ( empty( $shift_size['height'] ) ) {
+			$ratio_size           = wp_constrain_dimensions( $size['width'], $size['height'], $shift_size['width'] );
+			$shift_size['height'] = $ratio_size[1];// Set the height.
+		} elseif ( empty( $shift_size['width'] ) ) {
+			// wp_constrain_dimensions only deals with width, so we pretend the image is a portrait to compensate.
+			$ratio_size          = wp_constrain_dimensions( $size['height'], $size['width'], $shift_size['height'] );
+			$shift_size['width'] = $ratio_size[0];// Set the width.
+		}
+
+		return $shift_size;
+	}
+
+	/**
+	 * Get the asset payload and process it for importing.
+	 *
+	 * @return array
+	 */
+	public function get_asset_payload() {
+		$args  = array(
+			'asset' => array(
+				'flags' => FILTER_REQUIRE_ARRAY,
+			),
+		);
+		$data  = filter_input_array( INPUT_POST, $args );
+		$asset = array(
+			'version'         => (int) filter_var( $data['asset']['version'], FILTER_SANITIZE_NUMBER_INT ),
+			'public_id'       => sanitize_text_field( $data['asset']['public_id'] ),
+			'type'            => sanitize_text_field( $data['asset']['type'] ),
+			'format'          => sanitize_text_field( $data['asset']['format'] ),
+			'src'             => filter_var( $data['asset']['secure_url'], FILTER_SANITIZE_URL ),
+			'url'             => filter_var( $data['asset']['secure_url'], FILTER_SANITIZE_URL ),
+			'transformations' => array(),
+			'meta'            => array(),
+		);
+		// Set sync key.
+		$asset['sync_key'] = $asset['public_id'];
+		if ( ! empty( $data['asset']['derived'] ) ) {
+			$asset['url'] = filter_var( $data['asset']['derived'][0]['secure_url'], FILTER_SANITIZE_URL );
+		}
+
+		// convert_media_extension.
+		if ( ! $this->is_file_compatible( $asset['url'] ) ) {
+			$asset['url'] = $this->convert_media_extension( $asset['url'] );
+		}
+
+		// Move all context data into the meta key.
+		if ( ! empty( $data['asset']['context'] ) ) {
+			array_walk_recursive(
+				$data['asset']['context'],
+				function ( $value, $key ) use ( &$asset ) {
+					$asset['meta'][ $key ] = sanitize_text_field( $value );
+				}
+			);
+		}
+
+		// Check for transformations.
+		$transformations = $this->get_transformations_from_string( $asset['url'] );
+		if ( ! empty( $transformations ) ) {
+			$asset['sync_key']       .= wp_json_encode( $transformations );
+			$asset['transformations'] = $transformations;
+		}
+
+		// Check Format.
+		$url_format = Utils::pathinfo( $asset['url'], PATHINFO_EXTENSION );
+		if ( strtolower( $url_format ) !== strtolower( $asset['format'] ) ) {
+			$asset['format']    = $url_format;
+			$asset['sync_key'] .= $url_format;
+		}
+
+		// Attempt to find attachment ID.
+		$asset['attachment_id'] = $this->get_id_from_sync_key( $asset['sync_key'] );
+		$asset['instances']     = Relationship::get_ids_by_public_id( $asset['public_id'] );
+
+		/**
+		 * Filter the asset payload.
+		 *
+		 * @hook   cloudinary_asset_payload
+		 * @since  3.1.3
+		 *
+		 * @param $asset {array} The asset payload.
+		 * @param $data  {array} The raw data from the request.
+		 *
+		 * @return {array}
+		 *
+		 * @example
+		 * <?php
+		 *
+		 * // Extend Cloudinary support for extra data.
+		 * // Contextual metadata is passed by default.
+		 * add_filter(
+		 *    'cloudinary_asset_payload',
+		 *    static function ( $asset, $payload ) {
+		 *        // The structured keys on Cloudinary to use in WordPress.
+		 *        $key = 'structured_key';
+		 *
+		 *        // Structural metadata. Beware of key collision with contextual metadata.
+		 *        if ( ! empty ( $payload['asset']['metadata'][ $key ] ) ) {
+		 *            // The sanitize function to use should be adequate to the data type.
+		 *            $asset['meta'][ $key ] = sanitize_text_field( $payload['asset']['metadata'][ $key ] );
+		 *        }
+		 *
+		 *        // The Cloudinary tags.
+		 *        if ( ! empty ( $payload['asset']['tags'] ) ) {
+		 *            $asset['tags'] = array_map( 'sanitize_text_field', $payload['asset']['tags'] );
+		 *        }
+		 *
+		 *        return $asset;
+		 *    },
+		 *    10,
+		 *    2
+		 * );
+		 */
+		$asset = apply_filters( 'cloudinary_asset_payload', $asset, $data );
+
+		return $asset;
+	}
+
+	/**
+	 * Create and prepare a down sync asset from Cloudinary.
+	 */
+	public function down_sync_asset() {
+		$nonce = Utils::get_sanitized_text( 'nonce', INPUT_POST );
+		if ( wp_verify_nonce( $nonce, 'wp_rest' ) ) {
+
+			$asset = $this->get_asset_payload();
+			// Set a base array for pulling an asset if needed.
+			$base_return = array(
+				'fetch'         => Utils::rest_url( REST_API::BASE . '/asset' ),
+				'uploading'     => true,
+				'src'           => $asset['src'],
+				'url'           => $asset['url'],
+				'filename'      => wp_basename( $asset['src'] ),
+				'attachment_id' => $asset['attachment_id'],
+				'public_id'     => $asset['public_id'],
+			);
+			if ( empty( $asset['attachment_id'] ) ) {
+				$return                  = $base_return;
+				$asset['attachment_id']  = $this->create_attachment( $asset, $asset['public_id'] );
+				$return['attachment_id'] = $asset['attachment_id'];
+			} else {
+				// Capture the ALT Text.
+				if ( ! empty( $asset['meta']['alt'] ) ) {
+					$alt_text = wp_strip_all_tags( $asset['meta']['alt'] );
+					foreach ( $asset['instances'] as $id ) {
+						update_post_meta( $id, '_wp_attachment_image_alt', $alt_text );
+					}
+				}
+				// Capture the Caption Text.
+				if ( ! empty( $asset['meta']['caption'] ) ) {
+					$caption = wp_strip_all_tags( $asset['meta']['caption'] );
+					foreach ( $asset['instances'] as $id ) {
+						wp_update_post(
+							array(
+								'ID'           => $id,
+								'post_excerpt' => $caption,
+							)
+						);
+					}
+				}
+				// Compare Version.
+				$current_version = $this->get_cloudinary_version( $asset['attachment_id'] );
+				if ( $current_version !== $asset['version'] ) {
+					// Difference version, remove files, and downsync new files related to this asset.
+					// If this is a different version, we should try find attachments with the base sync key and update the source.
+					$ids    = $this->get_id_from_sync_key( 'base_' . $asset['public_id'], true );
+					$resync = array();
+					foreach ( $ids as $id ) {
+						// Update the version to the asset.
+						$this->update_post_meta( $id, Sync::META_KEYS['version'], $asset['version'] );
+						// Get the storage state, and only set storage signature if we have local copies.
+						$storage_state = $this->get_post_meta( $id, Sync::META_KEYS['storage'], true );
+						if ( 'cld' !== $storage_state ) {
+							// State is local and Cloudinary. So lets force the storage to downsync again.
+							$this->update_post_meta( $id, Sync::META_KEYS['storage'], 'resync' );
+							// Set signature for storage, since this will be more effective at downloading or not.
+							$this->sync->set_signature_item( $id, 'storage', '' );
+						}
+						if ( $id !== $asset['attachment_id'] ) {
+							$resync[] = wp_prepare_attachment_for_js( $id );
+						}
+					}
+					// Use the primary ID as the main return, and add the resynced assets to that.
+					$return           = wp_prepare_attachment_for_js( $asset['attachment_id'] );
+					$return['resync'] = $resync;
+				} else {
+					$return              = wp_prepare_attachment_for_js( $asset['attachment_id'] );
+					$return['public_id'] = $asset['public_id'];
+				}
+			}
+			$return['transformations'] = $asset['transformations'];
+
+			/**
+			 * Action for the downloaded assets from Cloudinary Media Library.
+			 *
+			 * @hook  cloudinary_download_asset
+			 * @since 3.1.3
+			 *
+			 * @param $asset  {array} The default filters.
+			 * @param $return {array} The return payload.
+			 *
+			 * @example
+			 * <?php
+			 * add_action(
+			 *    'cloudinary_download_asset',
+			 *    static function ( $asset ) {
+			 *        // Store metadata. Contextual and Structured metadata should be similar.
+			 *        $key = 'metadata_key';
+			 *        if ( ! empty( $asset['meta'][ $key ] ) && ! empty( $asset['attachment_id'] ) && 'attachment' === get_post_type( $asset['attachment_id'] ) ) {
+			 *            update_post_meta( $asset['attachment_id'],  $key , $asset['meta'][ $key ] );
+			 *        }
+			 *
+			 *        // Store the tags. The taxonomy needs to be assigned to the post type.
+			 *        if ( ! empty( $asset['tags'] ) && ! empty( $asset['attachment_id'] ) && 'attachment' === get_post_type( $asset['attachment_id'] ) ) {
+			 *            wp_set_post_terms(
+			 *                $asset['attachment_id'],
+			 *                $asset['tags']
+			 *            );
+			 *        }
+			 *    }
+			 * );
+			 */
+			do_action( 'cloudinary_download_asset', $asset, $return );
+
+			wp_send_json_success( $return );
+		}
+
+		return wp_send_json_error();
+	}
+
+	/**
+	 * Insert the cloudinary status column.
+	 *
+	 * @param array $cols Array of columns.
+	 *
+	 * @return array
+	 */
+	public function media_column( $cols ) {
+
+		$custom = array(
+			'cld_status' => '<span class="dashicons-cloudinary"><span class="screen-reader-text">' . __( 'Cloudinary', 'cloudinary' ) . '</span></span>',
+		);
+		$offset = array_search( 'parent', array_keys( $cols ), true );
+		if ( empty( $offset ) ) {
+			$offset = 3; // Default location some where after author, in case another plugin removes parent column.
+		}
+		$cols = array_slice( $cols, 0, $offset ) + $custom + array_slice( $cols, $offset );
+
+		return $cols;
+	}
+
+	/**
+	 * Display the Cloudinary Column.
+	 *
+	 * @param string $column_name   The column name.
+	 * @param int    $attachment_id The attachment id.
+	 */
+	public function media_column_value( $column_name, $attachment_id ) {
+		if ( 'cld_status' === $column_name ) {
+			if ( ! $this->plugin->get_component( 'delivery' )->is_deliverable( $attachment_id ) ) :
+				?>
+				<span class="dashicons-cloudinary info" title="<?php esc_attr_e( 'The delivery for this asset is disabled.', 'cloudinary' ); ?>"></span>
+				<?php
+			elseif ( ! $this->is_uploadable_media( $attachment_id ) ) :
+				?>
+				<span class="dashicons-cloudinary info" title="<?php esc_attr_e( 'Not syncable. This is an external media.', 'cloudinary' ); ?>"></span>
+				<?php
+			elseif ( 'fetch' === $this->get_media_delivery( $attachment_id ) ) :
+				?>
+				<span class="dashicons-cloudinary info" title="<?php esc_attr_e( 'This media is Fetch type.', 'cloudinary' ); ?>"></span>
+				<?php
+			elseif ( 'sprite' === $this->get_media_delivery( $attachment_id ) ) :
+				?>
+				<span class="dashicons-cloudinary info" title="<?php esc_attr_e( 'This media is Sprite type.', 'cloudinary' ); ?>"></span>
+				<?php
+			elseif ( get_post_meta( $attachment_id, Sync::META_KEYS['sync_error'], true ) || $this->is_oversize_media( $attachment_id ) ) :
+				$title = get_post_meta( $attachment_id, Sync::META_KEYS['sync_error'], true )
+				?>
+				<span class="dashicons-cloudinary error" title="<?php echo esc_attr( $title ); ?>"></span>
+				<?php
+			elseif ( $this->sync->is_syncable( $attachment_id ) && $this->is_uploadable_media( $attachment_id ) ) :
+				$status = array(
+					'state' => 'inactive',
+					'note'  => esc_html__( 'Not Synced', 'cloudinary' ),
+				);
+				if ( $this->cloudinary_id( $attachment_id ) && 'upload' === $this->get_media_delivery( $attachment_id ) ) {
+					$status = array(
+						'state' => 'success',
+						'note'  => esc_html__( 'Synced', 'cloudinary' ),
+					);
+
+					if ( wp_attachment_is_image( $attachment_id ) ) {
+						if ( empty( get_post_meta( $attachment_id, Sync::META_KEYS['remote_size'], true ) ) ) {
+							$this->plugin->get_component( 'storage' )->size_sync( $attachment_id );
+						}
+					}
+				}
+				// filter status.
+				$status = apply_filters( 'cloudinary_media_status', $status, $attachment_id );
+				?>
+				<span class="dashicons-cloudinary <?php echo esc_attr( $status['state'] ); ?>" title="<?php echo esc_attr( $status['note'] ); ?>"></span>
+				<?php
+			endif;
+		}
+	}
+
+	/**
+	 * Sanitize the Cloudinary Folder, and if empty, return the sanitized default.
+	 *
+	 * @param string $value The value to sanitize.
+	 * @param array  $field The field settings array.
+	 *
+	 * @return string
+	 */
+	public static function sanitize_cloudinary_folder( $value, $field ) {
+		$value = trim( $value );
+		if ( empty( $value ) && ! empty( $field['default'] ) ) {
+			$value = $field['default'];
+		}
+
+		return sanitize_text_field( $value );
+	}
+
+	/**
+	 * Sanitize the breakpoints, and if empty, return the sanitized default.
+	 *
+	 * @param string $value The value to sanitize.
+	 * @param array  $field The field settings array.
+	 *
+	 * @return int
+	 */
+	public static function sanitize_breakpoints( $value, $field ) {
+		if ( ! is_numeric( $value ) ) {
+			$value = $field['default'];
+		} elseif ( $value > $field['max'] ) {
+			$value = $field['max'];
+		} elseif ( $value < $field['min'] ) {
+			$value = $field['min'];
+		}
+
+		return intval( $value );
+	}
+
+	/**
+	 * Get the max image width registered in WordPress.
+	 *
+	 * @return int
+	 */
+	public function default_max_width() {
+		$core_sizes       = array( 'thumbnail', 'medium', 'large', 'medium_large', 'large' );
+		$additional_sizes = wp_get_additional_image_sizes();
+		foreach ( $core_sizes as $size ) {
+			$additional_sizes[ $size ] = get_option( $size . '_size_w' );
+		}
+		$sizes = array_map(
+			function ( $item ) {
+				if ( is_array( $item ) ) {
+					$item = $item['width'];
+				}
+
+				return intval( $item );
+			},
+			$additional_sizes
+		);
+		rsort( $sizes );
+		$max_width = array_shift( $sizes );
+
+		return $max_width;
+	}
+
+	/**
+	 * Get the max image width registered in WordPress.
+	 *
+	 * @return int
+	 */
+	public function get_max_width() {
+		return $this->settings->get_setting( 'max_width' )->get_value();
+	}
+
+	/**
+	 * Get transformations from post meta for an attachment.
+	 *
+	 * @param int $post_id The post to get meta for.
+	 *
+	 * @return array
+	 */
+	public function get_transformation_from_meta( $post_id ) {
+		return Relate::get_transformations( $post_id );
+	}
+
+	/**
+	 * Get Cloudinary related Post meta.
+	 *
+	 * @param int    $post_id The attachment ID.
+	 * @param string $key     The meta key to get.
+	 * @param bool   $single  If single or not.
+	 * @param mixed  $default The default value if empty.
+	 *
+	 * @return mixed
+	 */
+	public function get_post_meta( $post_id, $key = '', $single = false, $default = null ) {
+
+		$meta = get_post_meta( $post_id, Sync::META_KEYS['cloudinary'], true );
+		if ( empty( $meta ) ) {
+			/**
+			 * Filter the meta if not found, in order to migrate from a legacy plugin.
+			 *
+			 * @hook   cloudinary_migrate_legacy_meta
+			 * @since  2.7.5
+			 *
+			 * @param $attachment_id {int} The attachment ID.
+			 *
+			 * @return {array}
+			 */
+			$meta = apply_filters( 'cloudinary_migrate_legacy_meta', $post_id );
+		}
+		if ( '' !== $key ) {
+			$meta = isset( $meta[ $key ] ) ? $meta[ $key ] : $default;
+		}
+
+		return $single ? $meta : (array) $meta;
+	}
+
+	/**
+	 * Gets the process logs for the attachment.
+	 *
+	 * @param int  $attachment_id The attachment ID.
+	 * @param bool $raw           The errors expanded and no readable time.
+	 *
+	 * @return array|mixed|null
+	 */
+	public function get_process_logs( $attachment_id, $raw = false ) {
+		$logs = get_post_meta( $attachment_id, Sync::META_KEYS['process_log'], true );
+
+		if ( empty( $logs ) ) {
+			$logs = (array) $this->get_post_meta( $attachment_id, Sync::META_KEYS['process_log_legacy'], true, array() );
+			add_post_meta( $attachment_id, Sync::META_KEYS['process_log'], $logs, true );
+
+			$this->delete_post_meta( $attachment_id, Sync::META_KEYS['process_log_legacy'] );
+		}
+
+		foreach ( $logs as $signature => $log ) {
+			if ( empty( $log ) ) {
+				$logs[ $signature ] = array();
+				continue;
+			}
+			if ( is_wp_error( $log ) ) {
+				$logs[ $signature ]                 = array();
+				$logs[ $signature ][ '_' . time() ] = array(
+					'code'    => $log->get_error_code(),
+					'message' => $log->get_error_message(),
+				);
+				continue;
+			}
+			foreach ( $log as $time => $entry ) {
+				$time = ltrim( $time, '_' );
+
+				// Cleanup 0'd logs.
+				if ( 0 === (int) $time ) {
+					unset( $logs[ $signature ][ "_{$time}" ] );
+					continue;
+				}
+
+				$to_unset = null;
+
+				// If timestamped request.
+				if ( $raw ) {
+					// Fix stored expanded time.
+					if ( ! is_numeric( $time ) ) {
+						$to_unset = $time;
+						$time     = strtotime( $time );
+					}
+					$time = "_{$time}";
+				} else { // Readable request.
+					$to_unset = "_{$time}";
+					$time     = gmdate( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $time );
+				}
+
+				// Maybe cleanup log entries.
+				if ( $to_unset ) {
+					unset( $logs[ $signature ][ $to_unset ] );
+				}
+
+				$logs[ $signature ][ $time ] = $entry;
+
+				if (
+					is_array( $entry )
+					&& ! empty( $entry['code'] )
+					&& ! empty( $entry['message'] )
+				) {
+					$logs[ $signature ][ $time ] = $raw ? $entry : new WP_Error( $entry['code'], $entry['message'] );
+				}
+			}
+		}
+
+		return $logs;
+	}
+
+	/**
+	 * Build and return a cached cloudinary meta value.
+	 *
+	 * @param int    $post_id The attachment ID.
+	 * @param string $key     The meta key to get.
+	 * @param bool   $single  If single or not.
+	 *
+	 * @return mixed
+	 */
+	public function build_cached_meta( $post_id, $key, $single ) {
+		$data = get_post_meta( $post_id, $key, $single );
+		if ( '' !== $data ) {
+			$this->update_post_meta( $post_id, $key, $data );
+		}
+
+		return $data;
+	}
+
+	/**
+	 * Update cloudinary metadata.
+	 *
+	 * @param int          $post_id The attachment ID.
+	 * @param string       $key     The meta key to get.
+	 * @param string|array $data    $the meta data to update.
+	 *
+	 * @return bool
+	 */
+	public function update_post_meta( $post_id, $key, $data ) {
+
+		$meta = $this->get_post_meta( $post_id );
+		if ( ! isset( $meta[ $key ] ) ) {
+			$meta[ $key ] = '';
+		}
+
+		if ( $meta[ $key ] !== $data ) {
+			$meta[ $key ] = $data;
+		}
+
+		return update_post_meta( $post_id, Sync::META_KEYS['cloudinary'], $meta );
+	}
+
+	/**
+	 * Delete cloudinary metadata.
+	 *
+	 * @param int    $post_id The attachment ID.
+	 * @param string $key     The meta key to get.
+	 *
+	 * @return bool
+	 */
+	public function delete_post_meta( $post_id, $key ) {
+
+		$meta = $this->get_post_meta( $post_id );
+		if ( isset( $meta[ $key ] ) ) {
+			unset( $meta[ $key ] );
+		}
+
+		return update_post_meta( $post_id, Sync::META_KEYS['cloudinary'], $meta );
+	}
+
+	/**
+	 * Get the breakpoint generation options for an attachment.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return array
+	 */
+	public function get_breakpoint_options( $attachment_id ) {
+		// Add breakpoints if we have an image.
+		$breakpoints = array();
+		$settings    = $this->settings->get_value( 'responsive' );
+
+		if ( 'on' === $settings['enable_breakpoints'] && wp_attachment_is_image( $attachment_id ) ) {
+			$meta = wp_get_attachment_metadata( $attachment_id, true );
+			// Get meta image size if non exists.
+			if ( empty( $meta ) ) {
+				$meta          = array();
+				$imagesize     = getimagesize( get_attached_file( $attachment_id ) );
+				$meta['width'] = isset( $imagesize[0] ) ? $imagesize[0] : 0;
+			}
+			$max_width = $this->get_max_width();
+			// Add breakpoints request options.
+			$breakpoint_options = array(
+				'create_derived' => true,
+				'bytes_step'     => $settings['bytes_step'],
+				'max_images'     => $settings['breakpoints'],
+				'max_width'      => $meta['width'] < $max_width ? $meta['width'] : $max_width,
+				'min_width'      => $settings['min_width'],
+			);
+			$transformations    = $this->get_transformation_from_meta( $attachment_id );
+			if ( ! empty( $transformations ) ) {
+				$breakpoints['transformation'] = Api::generate_transformation_string( $transformations, 'image' );
+			}
+			$breakpoints = array(
+				'public_id'              => $this->get_public_id( $attachment_id, true ),
+				'type'                   => 'upload',
+				'responsive_breakpoints' => $breakpoint_options,
+				'context'                => $this->get_context_options( $attachment_id ),
+			);
+			// Check for suffix.
+			$breakpoints['public_id'] .= $this->get_post_meta( $attachment_id, Sync::META_KEYS['suffix'], true );
+
+		}
+
+		return $breakpoints;
+	}
+
+	/**
+	 * Get the context options for an asset.
+	 *
+	 * @param int $attachment_id The ID of the attachment.
+	 *
+	 * @return array
+	 */
+	public function get_context_options( $attachment_id ) {
+		$caption = get_post( $attachment_id )->post_excerpt;
+
+		if ( empty( $caption ) ) {
+			$caption = get_the_title( $attachment_id );
+		}
+		$media_library_context = array(
+			'caption' => esc_attr( $caption ),
+			'alt'     => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
+			'guid'    => md5( Utils::get_path_from_url( get_the_guid( $attachment_id ), true ) ),
+		);
+		$context_options       = array(
+			'cld_wp_plugin' => 1,
+			'wp_context'    => Utils::get_media_context( $attachment_id ),
+		);
+		if ( $this->is_folder_synced( $attachment_id ) ) {
+			$context_options = wp_parse_args( $media_library_context, $context_options );
+		}
+
+		/**
+		 * Filter the options to allow other plugins to add requested options for uploading.
+		 *
+		 * @hook cloudinary_context_options
+		 *
+		 * @param $options {array}   The options array.
+		 * @param $post    {WP_Post} The attachment post.
+		 * @param $this    {Media}   The media object instance.
+		 *
+		 * @return {array}
+		 */
+		$context_options = apply_filters( 'cloudinary_context_options', $context_options, get_post( $attachment_id ), $this );
+		foreach ( $context_options as $option => &$value ) {
+			$value = str_replace( '=', '\=', $value );
+			$value = str_replace( '|', '\|', $value );
+			$value = $option . '=' . $value;
+		}
+
+		return implode( '|', $context_options );
+	}
+
+	/**
+	 * Check if an asset is folder synced.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return bool
+	 */
+	public function is_folder_synced( $attachment_id ) {
+
+		$is_folder_synced = true; // By default all assets in WordPress will be synced.
+		if ( $this->sync->been_synced( $attachment_id ) ) {
+			$is_folder_synced = ! empty( $this->get_post_meta( $attachment_id, Sync::META_KEYS['folder_sync'], true ) );
+		}
+
+		/**
+		 * Filter is folder synced flag.
+		 *
+		 * @hook cloudinary_is_folder_synced
+		 *
+		 * @param $is_folder_synced {bool} Flag value for is folder sync.
+		 * @param $attachment_id    {int}  The attachment ID.
+		 *
+		 * @return {bool}
+		 */
+		$is_folder_synced = apply_filters( 'cloudinary_is_folder_synced', $is_folder_synced, $attachment_id );
+
+		return (bool) $is_folder_synced;
+	}
+
+	/**
+	 * Get the media upload options as connected to Cloudinary.
+	 *
+	 * @param int    $attachment_id The attachment ID.
+	 * @param string $context       The context to use.
+	 *
+	 * @return array
+	 */
+	public function get_upload_options( $attachment_id, $context = '' ) {
+
+		// Prepare upload options.
+		$public_id = $this->get_public_id( $attachment_id, true );
+		$folder    = ltrim( dirname( $public_id ), '.' );
+		$options   = array(
+			'unique_filename' => true,
+			'overwrite'       => false,
+			'resource_type'   => $this->get_resource_type( $attachment_id ),
+			'public_id'       => wp_basename( $public_id ),
+			'context'         => $this->get_context_options( $attachment_id ),
+		);
+
+		if ( 'image' === $options['resource_type'] || 'video' === $options['resource_type'] ) {
+			$options['eager']       = Api::generate_transformation_string( $this->apply_default_transformations( array(), $attachment_id ), $options['resource_type'], $context );
+			$options['eager_async'] = 'video' === $options['resource_type'];
+		}
+		/**
+		 * Filter the options to allow other plugins to add requested options for uploading.
+		 *
+		 * @hook cloudinary_upload_options
+		 *
+		 * @param $options {array}   The options array.
+		 * @param $post    {WP_Post} The attachment post.
+		 * @param $this    {Media}   The media object instance.
+		 *
+		 * @return {array}
+		 */
+		$options = apply_filters( 'cloudinary_upload_options', $options, get_post( $attachment_id ), $this );
+		// Add folder to prevent folder contamination.
+		if ( $this->is_folder_synced( $attachment_id ) ) {
+			$cld_folder              = $this->get_cloudinary_folder();
+			$options['public_id']    = $cld_folder . wp_basename( $options['public_id'] );
+			$options['asset_folder'] = $cld_folder;
+		} elseif ( ! empty( $folder ) ) {
+			// add in folder if not empty (not in root).
+			$options['public_id'] = trailingslashit( $folder ) . wp_basename( $options['public_id'] );
+		}
+		$options['public_id'] = trim( $options['public_id'], '/.' );
+
+		return $options;
+	}
+
+	/**
+	 * Check if the current image is to be have the transformations overwritten.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return bool
+	 */
+	public function maybe_overwrite_featured_image( $attachment_id ) {
+		$overwrite = false;
+		if ( $this->doing_featured_image && $this->doing_featured_image === (int) $attachment_id ) {
+			$overwrite = (bool) get_post_meta( get_the_ID(), Global_Transformations::META_FEATURED_IMAGE_KEY, true );
+		}
+
+		return $overwrite;
+	}
+
+	/**
+	 * Set the flag indicating if the featured image is being done.
+	 *
+	 * @param int $post_id       The current post ID.
+	 * @param int $attachment_id The thumbnail ID.
+	 */
+	public function set_doing_featured( $post_id, $attachment_id ) {
+		if ( $this->sync->is_synced( $attachment_id ) ) {
+			$this->doing_featured_image = (int) $attachment_id;
+		}
+	}
+
+	/**
+	 * Maybe add responsive images to a post thumbnail.
+	 *
+	 * @param string $content       The content to alter.
+	 * @param int    $post_id       The current post ID (unused).
+	 * @param int    $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public function maybe_srcset_post_thumbnail( $content, $post_id, $attachment_id ) {
+		if ( ! $this->plugin->get_component( 'delivery' )->is_deliverable( $attachment_id ) ) {
+			return $content;
+		}
+
+		// Check the attachment is synced and does not already have a srcset (some themes do this already).
+		if ( $this->doing_featured_image === $attachment_id ) {
+			$overwrite_transformations  = $this->maybe_overwrite_featured_image( $attachment_id );
+			$content                    = $this->apply_srcset( $content, $attachment_id, $overwrite_transformations );
+			$this->doing_featured_image = false; // Reset featured.
+		}
+
+		return $content;
+	}
+
+	/**
+	 * Apply srcset to an image tag.
+	 *
+	 * @param string $content                   The image tag.
+	 * @param int    $attachment_id             The attachment ID.
+	 * @param bool   $overwrite_transformations Flag to overwrite transformations.
+	 *
+	 * @return string
+	 */
+	public function apply_srcset( $content, $attachment_id, $overwrite_transformations = false ) {
+		$cloudinary_id                           = $this->get_cloudinary_id( $attachment_id );
+		$image_meta                              = wp_get_attachment_metadata( $attachment_id );
+		$image_meta['file']                      = Utils::pathinfo( $cloudinary_id, PATHINFO_FILENAME ) . '/' . Utils::pathinfo( $cloudinary_id, PATHINFO_BASENAME );
+		$image_meta['overwrite_transformations'] = $overwrite_transformations;
+
+		return wp_image_add_srcset_and_sizes( $content, $image_meta, $attachment_id );
+	}
+
+	/**
+	 * Get the cloudinary version of an attachment.
+	 *
+	 * @param int $attachment_id The attachment_ID.
+	 *
+	 * @return int
+	 */
+	public function get_cloudinary_version( $attachment_id ) {
+		$version = (int) $this->get_post_meta( $attachment_id, Sync::META_KEYS['version'], true );
+
+		if ( empty( $version ) ) {
+			// This might be also an asset from the hidden post type (Assets::POST_TYPE_SLUG).
+			$attachment = get_post( $attachment_id );
+
+			if ( ! empty( $attachment ) && Assets::POST_TYPE_SLUG === $attachment->post_type && ! empty( $attachment->post_parent ) ) {
+				$version = (int) preg_replace( '/\D/', '', $this->get_post_meta( (int) $attachment->post_parent, Sync::META_KEYS['version'], true ) );
+			}
+		}
+
+		return $version ? $version : 1;
+	}
+	/**
+	 * Upgrade media related settings, including global transformations etc.
+	 *
+	 * @uses action:cloudinary_version_upgrade
+	 */
+	public function upgrade_media_settings() {
+		// Check that transformations is in default (hasn't been saved before).
+		if ( empty( get_option( self::GLOBAL_VIDEO_TRANSFORMATIONS, null ) ) ) {
+			// Setup default to CLD, since default changed from WP to CLD after 2.0.3.
+			$video = array(
+				'video_player' => 'cld',
+			);
+			update_option( self::GLOBAL_VIDEO_TRANSFORMATIONS, $video );
+		}
+	}
+
+	/**
+	 * Checks if local URLS can be filtered out.
+	 *
+	 * @return bool
+	 */
+	public function can_filter_out_local() {
+		$can = true;
+		if ( 'cld' !== $this->plugin->settings->find_setting( 'offload' )->get_value() ) {
+			/**
+			 * Filter to allow stopping filtering out local.
+			 *
+			 * @hook    cloudinary_filter_out_local
+			 * @default true
+			 *
+			 * @param $can {bool} True as default.
+			 *
+			 * @return {bool}
+			 */
+			$can = apply_filters( 'cloudinary_filter_out_local', $can );
+		}
+
+		return $can;
+	}
+
+	/**
+	 * Filters the new sizes to ensure non upload (sprites), don't get resized.
+	 *
+	 * @param array    $new_sizes     Array of sizes.
+	 * @param array    $image_meta    Image metadata.
+	 * @param int|null $attachment_id The attachment ID.
+	 *
+	 * @return array
+	 */
+	public function manage_sizes( $new_sizes, $image_meta, $attachment_id = null ) {
+		if ( is_null( $attachment_id ) ) {
+			$attachment_id = $this->plugin->settings->get_param( '_currrent_attachment', 0 );
+		}
+		if ( $this->has_public_id( $attachment_id ) ) {
+			// Get delivery type.
+			$delivery = $this->get_media_delivery( $attachment_id );
+			if ( 'upload' !== $delivery ) {
+				// Only upload based deliveries will get intermediate sizes.
+				$new_sizes = array();
+			}
+		}
+
+		return $new_sizes;
+	}
+
+	/**
+	 * Fix the PDF resource type.
+	 *
+	 * @link https://cloudinary.com/cookbook/convert_pdf_to_jpg
+	 *
+	 * @param string $type          The default type.
+	 * @param int    $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 **/
+	public function pdf_resource_type( $type, $attachment_id ) {
+
+		if ( 'application/pdf' === get_post_mime_type( $attachment_id ) ) {
+			$type = 'image';
+		}
+
+		return $type;
+	}
+
+	/**
+	 * Update the Query with the Cloudinary filters.
+	 *
+	 * @param WP_Query $query The query instance.
+	 */
+	public function apply_media_library_filters( $query ) {
+		if ( is_admin() && $query->is_main_query() ) {
+			$request = Utils::get_sanitized_text( 'cloudinary-filter' );
+
+			if ( SYNC::META_KEYS['sync_error'] === $request ) {
+				$meta_query = $query->get( 'meta_query' );
+				if ( ! is_array( $meta_query ) ) {
+					$meta_query = array();
+				}
+				$meta_query[] = array(
+					'relation' => 'AND',
+				);
+				$meta_query[] = array(
+					'key'     => $request,
+					'compare' => 'EXISTS',
+				);
+				$query->set( 'meta_query', $meta_query );
+			}
+
+			if ( SYNC::META_KEYS['unsynced'] === $request ) {
+				global $wpdb;
+				$wpdb->cld_table = Utils::get_relationship_table();
+				$result          = $wpdb->get_col( "SELECT post_id FROM $wpdb->cld_table WHERE public_id IS NULL" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
+
+				// phpcs:disable WordPressVIPMinimum.Hooks.PreGetPosts.PreGetPosts
+				if ( ! empty( $result ) ) {
+					$query->set( 'post__in', $result );
+				} else {
+					$query->set( 'post__in', array( 0 ) );
+				}
+				// phpcs:enable
+			}
+		}
+	}
+
+	/**
+	 * The the Cloudinary's Media Library filters markup.
+	 *
+	 * @param string $post_type The post type slug.
+	 */
+	public function filter_media_library( $post_type ) {
+		if ( 'attachment' === $post_type ) {
+			$request = Utils::get_sanitized_text( 'cloudinary-filter' );
+			?>
+			<select name="cloudinary-filter" id="cloudinary-filter">
+				<option value="none"><?php esc_html_e( 'No Cloudinary filters', 'cloudinary' ); ?></option>
+				<?php foreach ( $this->cloudinary_filters as $value => $label ) : ?>
+					<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $value, $request ); ?>><?php echo esc_html( $label ); ?></option>
+				<?php endforeach; ?>
+			</select>
+			<?php
+		}
+	}
+
+	/**
+	 * Filter live URLS.
+	 * Used in admin and in the REST API.
+	 */
+	public function add_live_url_filters() {
+		add_filter( 'wp_calculate_image_srcset', array( $this, 'image_srcset' ), 10, 5 );
+		add_filter( 'wp_get_attachment_url', array( $this, 'attachment_url' ), 10, 2 );
+		add_filter( 'wp_get_original_image_url', array( $this, 'original_attachment_url' ), 10, 2 );
+		add_filter( 'image_downsize', array( $this, 'filter_downsize' ), 10, 3 );
+		add_filter( 'wp_calculate_image_srcset_meta', array( $this, 'calculate_image_srcset_meta' ), 10, 3 );
+
+		// Hook into Featured Image cycle.
+		add_action( 'begin_fetch_post_thumbnail_html', array( $this, 'set_doing_featured' ), 10, 2 );
+		add_filter( 'post_thumbnail_html', array( $this, 'maybe_srcset_post_thumbnail' ), 10, 3 );
+	}
+
+	/**
+	 * Setup the hooks and base_url if configured.
+	 */
+	public function setup() {
+		if ( $this->plugin->settings->get_param( 'connected' ) ) {
+
+			$this->base_url          = $this->plugin->components['connect']->api->cloudinary_url();
+			$this->credentials       = $this->plugin->components['connect']->get_credentials();
+			$this->cloudinary_folder = $this->settings->get_value( 'cloudinary_folder' );
+			$this->sync              = $this->plugin->components['sync'];
+
+			// Internal components.
+			$this->global_transformations = new Global_Transformations( $this );
+			$this->gallery                = $this->plugin->get_component( 'gallery' );
+			$this->woocommerce_gallery    = new WooCommerceGallery( $this->gallery );
+			$this->filter                 = new Filter( $this );
+			$this->upgrade                = new Upgrade( $this );
+			$this->video                  = new Video( $this );
+
+			// Set the max image size registered in WordPress.
+			$this->get_max_width();
+
+			// Add media templates and assets.
+			add_action( 'print_media_templates', array( $this, 'media_template' ) );
+			add_action( 'wp_enqueue_media', array( $this, 'editor_assets' ) );
+			add_action( 'wp_ajax_cloudinary-down-sync', array( $this, 'down_sync_asset' ) );
+
+			// Filter to add cloudinary folder.
+			add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
+
+			// Filter live URLS. (functions that return a URL).
+			if ( Utils::is_admin() ) {
+				$this->add_live_url_filters();
+			}
+			// Filter default image Quality and Format transformations.
+			add_filter( 'cloudinary_default_qf_transformations_image', array( $this, 'default_image_transformations' ), 10 );
+			add_filter( 'cloudinary_default_freeform_transformations_image', array( $this, 'default_image_freeform_transformations' ), 10 );
+
+			// Filter and action the custom column.
+			add_filter( 'manage_media_columns', array( $this, 'media_column' ) );
+			add_action( 'manage_media_custom_column', array( $this, 'media_column_value' ), 10, 2 );
+
+			// Handle other delivery types.
+			add_filter( 'intermediate_image_sizes_advanced', array( $this, 'manage_sizes' ), PHP_INT_MAX, 3 ); // High level to avoid other plugins breaking it.
+
+			// Filter PDF resource type.
+			add_filter( 'cloudinary_resource_type', array( $this, 'pdf_resource_type' ), 10, 2 );
+
+			add_action( 'restrict_manage_posts', array( $this, 'filter_media_library' ) );
+			add_action( 'pre_get_posts', array( $this, 'apply_media_library_filters' ) );
+		}
+	}
+
+	/**
+	 * Register sync settings.
+	 *
+	 * @return array
+	 */
+	public function settings() {
+
+		$image_settings      = array();
+		$video_settings      = array();
+		$image_settings_file = $this->plugin->dir_path . 'ui-definitions/settings-image.php';
+		$video_settings_file = $this->plugin->dir_path . 'ui-definitions/settings-video.php';
+
+		if ( file_exists( $image_settings_file ) ) {
+			$image_settings = include $image_settings_file; //phpcs:ignore
+		}
+
+		if ( file_exists( $video_settings_file ) ) {
+			$video_settings = include $video_settings_file; //phpcs:ignore
+		}
+
+		$args = array(
+			'type'       => 'page',
+			'menu_title' => __( 'Media Settings', 'cloudinary' ),
+			'tabs'       => array(
+				self::MEDIA_SETTINGS_SLUG => array(
+					'page_title' => __( 'Media Display', 'cloudinary' ),
+					array(
+						'type'      => 'info_box',
+						'icon'      => $this->plugin->dir_url . 'css/images/transformation.svg',
+						'title'     => __( 'Transformations', 'cloudinary' ),
+						'text'      => __(
+							'Cloudinary allows you to easily transform your images on-the-fly to any required format, style and dimension, and also optimizes images for minimal file size alongside high visual quality for an improved user experience and minimal bandwidth. You can do all of this by implementing dynamic image transformation and delivery URLs.',
+							'cloudinary'
+						),
+						'url'       => 'https://cloudinary.com/documentation/transformation_reference',
+						'link_text' => __( 'See examples', 'cloudinary' ),
+					),
+					$image_settings,
+					$video_settings,
+				),
+			),
+		);
+
+		return $args;
+	}
+
+	/**
+	 * Enabled method for version if settings are enabled.
+	 *
+	 * @param bool $enabled Flag to enable.
+	 *
+	 * @return bool
+	 */
+	public function is_enabled( $enabled ) {
+		return $this->plugin->settings->get_param( 'connected' );
+	}
+
+	/**
+	 * Upgrade settings from 2.4 to 2.5.
+	 *
+	 * @param string $previous_version Previous version.
+	 * @param string $new_version      New version.
+	 */
+	public function upgrade_settings( $previous_version, $new_version ) {
+
+		if ( 2.4 === $previous_version ) {
+			// Setup new data from old.
+			$images    = get_option( 'cloudinary_global_transformations', array() );
+			$video     = get_option( self::GLOBAL_VIDEO_TRANSFORMATIONS, array() );
+			$old_media = array_merge( $images, $video );
+			$setting   = $this->settings->get_setting( 'media_display' );
+			// Get the current defaults.
+			$default = $setting->get_value();
+
+			$media = wp_parse_args( $old_media, $default );
+			// Update value.
+			$setting->set_value( $media );
+			// Save to DB.
+			$setting->save_value();
+		}
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-plugin.php.html b/docs/php_class-plugin.php.html new file mode 100644 index 000000000..d3133c87e --- /dev/null +++ b/docs/php_class-plugin.php.html @@ -0,0 +1,820 @@ + + + + + Source: php/class-plugin.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-plugin.php

+ + + + + + + +
+
+
<?php
+/**
+ * Bootstraps the Cloudinary plugin.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Component\Assets;
+use Cloudinary\Component\Config;
+use Cloudinary\Component\Notice;
+use Cloudinary\Component\Setup;
+use Cloudinary\Delivery\Lazy_Load;
+use Cloudinary\Delivery\Responsive_Breakpoints;
+use Cloudinary\Assets as CLD_Assets;
+use Cloudinary\Integrations\WPML;
+use Cloudinary\Media\Gallery;
+use Cloudinary\Sync\Storage;
+use Cloudinary\UI\State;
+use const E_USER_WARNING;
+use const WPCOM_IS_VIP_ENV;
+
+/**
+ * Main plugin bootstrap file.
+ */
+final class Plugin {
+
+	/**
+	 * Holds the components of the plugin
+	 *
+	 * @since   0.1
+	 *
+	 * @var     Admin|CLD_Assets|Connect|Dashboard|Deactivation|Delivery|Extensions|Gallery|Lazy_Load|Media|Meta_Box|Relate|Report|Responsive_Breakpoints|REST_API|State|Storage|SVG|Sync|URL[]|WPML|null
+	 */
+	public $components;
+	/**
+	 * Plugin config.
+	 *
+	 * @var array
+	 */
+	public $config = array();
+
+	/**
+	 * The core Settings object.
+	 *
+	 * @var Settings
+	 */
+	public $settings;
+
+	/**
+	 * Plugin slug.
+	 *
+	 * @var string
+	 */
+	public $slug;
+
+	/**
+	 * Plugin version.
+	 *
+	 * @var string
+	 */
+	public $version;
+
+	/**
+	 * Plugin directory path.
+	 *
+	 * @var string
+	 */
+	public $dir_path;
+
+	/**
+	 * Plugin templates path.
+	 *
+	 * @var string
+	 */
+	public $template_path;
+
+	/**
+	 * Plugin directory URL.
+	 *
+	 * @var string
+	 */
+	public $dir_url;
+
+	/**
+	 * The plugin file.
+	 *
+	 * @var string
+	 */
+	public $plugin_file;
+
+	/**
+	 * Holds the list of keys.
+	 */
+	const KEYS = array(
+		'notices' => 'cloudinary_notices',
+	);
+
+	/**
+	 * Plugin_Base constructor.
+	 */
+	public function __construct() {
+		$this->setup_endpoints();
+		spl_autoload_register( array( $this, 'autoload' ) );
+		$this->register_hooks();
+	}
+
+	/**
+	 * Initiate the plugin resources.
+	 *
+	 * Priority is 9 because WP_Customize_Widgets::register_settings() happens at
+	 * after_setup_theme priority 10. This is especially important for plugins
+	 * that extend the Customizer to ensure resources are available in time.
+	 */
+	public function plugins_loaded() {
+		Cron::get_instance();
+		$this->components['admin']                  = new Admin( $this );
+		$this->components['state']                  = new State( $this );
+		$this->components['connect']                = new Connect( $this );
+		$this->components['deactivation']           = new Deactivation( $this );
+		$this->components['sync']                   = new Sync( $this );
+		$this->components['media']                  = new Media( $this );
+		$this->components['gallery']                = new Gallery( $this );
+		$this->components['api']                    = new REST_API( $this );
+		$this->components['storage']                = new Storage( $this );
+		$this->components['report']                 = new Report( $this );
+		$this->components['delivery']               = new Delivery( $this );
+		$this->components['lazy_load']              = new Lazy_Load( $this );
+		$this->components['responsive_breakpoints'] = new Responsive_Breakpoints( $this );
+		$this->components['assets']                 = new CLD_Assets( $this );
+		$this->components['dashboard']              = new Dashboard( $this );
+		$this->components['extensions']             = new Extensions( $this );
+		$this->components['svg']                    = new SVG( $this );
+		$this->components['relate']                 = new Relate( $this );
+		$this->components['metabox']                = new Meta_Box( $this );
+		$this->components['url']                    = new URL( $this );
+		$this->components['wpml']                   = new WPML( $this );
+		$this->components['special_offer']          = new Special_Offer( $this );
+	}
+
+	/**
+	 * Get a plugin component.
+	 *
+	 * @param mixed $component The component.
+	 *
+	 * @return Admin|CLD_Assets|Connect|Dashboard|Deactivation|Delivery|Extensions|Gallery|Lazy_Load|Media|Meta_Box|Relate|Report|Responsive_Breakpoints|REST_API|State|Storage|SVG|Sync|URL|null
+	 */
+	public function get_component( $component ) {
+		$return = null;
+		if ( isset( $this->components[ $component ] ) ) {
+			$return = $this->components[ $component ];
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Get the core settings page structure for settings.
+	 *
+	 * @return array
+	 */
+	private function get_settings_page_structure() {
+
+		$parts = array(
+			'pages' => array(),
+		);
+
+		foreach ( $parts as $slug => $part ) {
+			if ( file_exists( $this->dir_path . "ui-definitions/settings-{$slug}.php" ) ) {
+				$parts[ $slug ] = include $this->dir_path . "ui-definitions/settings-{$slug}.php"; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+			}
+		}
+
+		$structure = array(
+			'version'    => $this->version,
+			'page_title' => __( 'Cloudinary', 'cloudinary' ),
+			'menu_title' => __( 'Cloudinary', 'cloudinary' ),
+			'capability' => Utils::user_can( 'manage_settings' ) ? 'exist' : false,
+			'icon'       => 'dashicons-cloudinary',
+			'slug'       => $this->slug,
+			'settings'   => $parts['pages'],
+			'sidebar'    => include CLDN_PATH . 'ui-definitions/settings-sidebar.php',
+		);
+
+		return $structure;
+	}
+
+	/**
+	 * Setup settings.
+	 */
+	public function setup_settings() {
+		$params         = $this->get_settings_page_structure();
+		$this->settings = new Settings( $this->slug, $params );
+		$components     = array_filter( $this->components, array( $this, 'is_setting_component' ) );
+		$this->init_component_settings( $components );
+
+		// Setup connection.
+		$connection = $this->get_component( 'connect' )->is_connected();
+		if ( false === $connection ) {
+			$count      = sprintf( ' <span class="update-plugins count-%d"><span class="update-count">%d</span></span>', 1, number_format_i18n( 1 ) );
+			$main_title = $this->settings->get_param( 'menu_title' ) . $count;
+			$this->settings->set_param( 'menu_title', $main_title );
+			$this->settings->set_param( 'connect_count', $count );
+		} else {
+			$this->settings->set_param( 'connected', true );
+			/**
+			 * Action indicating that the cloudinary is connected.
+			 *
+			 * @hook  cloudinary_connected
+			 * @since 3.0.0
+			 *
+			 * @param $plugin {Plugin} The core plugin object.
+			 */
+			do_action( 'cloudinary_connected', $this );
+		}
+		/**
+		 * Action indicating that the Settings are initialised.
+		 *
+		 * @hook  cloudinary_init_settings
+		 * @since 2.7.5
+		 *
+		 * @param $plugin {Plugin} The core plugin object.
+		 */
+		do_action( 'cloudinary_init_settings', $this );
+
+		// Register with admin.
+		$this->components['admin']->register_page( $this->slug, $this->settings->get_params() );
+	}
+
+	/**
+	 * Init component settings objects.
+	 *
+	 * @param Settings_Component[] $components of components to init settings for.
+	 */
+	private function init_component_settings( $components ) {
+		$version = get_option( Connect::META_KEYS['version'] );
+		foreach ( $components as $slug => $component ) {
+			/**
+			 * Component that implements Settings.
+			 *
+			 * @var  Component\Settings $component
+			 */
+			$component->init_settings( $this->settings );
+
+			// Upgrade settings if needed.
+			if ( $version < $this->version ) {
+				$component->upgrade_settings( $version, $this->version );
+			}
+		}
+		// Update settings version, if needed.
+		if ( $version < $this->version ) {
+			update_option( Connect::META_KEYS['version'], $this->version );
+		}
+	}
+
+	/**
+	 * Register Hooks for the plugin.
+	 */
+	public function set_config() {
+		$this->setup_settings();
+		$components = array_filter( $this->components, array( $this, 'is_config_component' ) );
+
+		foreach ( $components as $slug => $component ) {
+			$component->get_config();
+		}
+	}
+
+	/**
+	 * Register Hooks for the plugin.
+	 */
+	public function register_hooks() {
+		add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 9 );
+		add_action( 'admin_enqueue_scripts', array( $this, 'register_enqueue_styles' ), 11 );
+		add_action( 'init', array( $this, 'init' ) );
+		// Move to 100 and 200 to allow other plugins/systems to add cloudinary filters and actions that are fired within the init hooks.
+		add_action( 'init', array( $this, 'setup' ), 100 );
+		add_action( 'init', array( $this, 'register_assets' ), 200 );
+
+		add_action( 'admin_notices', array( $this, 'admin_notices' ) );
+		add_filter( 'plugin_row_meta', array( $this, 'force_visit_plugin_site_link' ), 10, 4 );
+		add_action( 'admin_print_footer_scripts', array( $this, 'print_script_data' ), 1 );
+		add_action( 'wp_print_footer_scripts', array( $this, 'print_script_data' ), 1 );
+
+		add_action( 'cloudinary_version_upgrade', array( Utils::class, 'install' ) );
+	}
+
+	/**
+	 * Register scripts and enqueue styles.
+	 */
+	public function register_enqueue_styles() {
+		wp_enqueue_style( 'cloudinary' );
+		$components = array_filter( $this->components, array( $this, 'is_active_asset_component' ) );
+
+		// Enqueue components.
+		array_map(
+			function ( $component ) {
+				/**
+				 * Component that implements Component\Assets.
+				 *
+				 * @var  Component\Assets $component
+				 */
+				$component->enqueue_assets();
+			},
+			$components
+		);
+	}
+
+	/**
+	 * Enqueue the core scripts and styles as needed.
+	 */
+	public function enqueue_assets() {
+		wp_enqueue_script( 'cloudinary' );
+	}
+
+	/**
+	 * Register Assets
+	 *
+	 * @since  0.1
+	 */
+	public function register_assets() {
+		// Register Main.
+		wp_register_script( 'cloudinary', $this->dir_url . 'js/cloudinary.js', array( 'jquery', 'wp-util' ), $this->version, true );
+		wp_register_style( 'cloudinary', $this->dir_url . 'css/cloudinary.css', null, $this->version );
+
+		$components = array_filter( $this->components, array( $this, 'is_asset_component' ) );
+		array_map(
+			function ( $component ) {
+				/**
+				 * Component that implements Component\Assets.
+				 *
+				 * @var  Component\Assets $component
+				 */
+				$component->register_assets();
+			},
+			$components
+		);
+	}
+
+	/**
+	 * Check if component is an asset implementing component.
+	 *
+	 * @since  0.1
+	 *
+	 * @param object $component The component to check.
+	 *
+	 * @return bool If the component is an asset impmented object or not.
+	 */
+	private function is_asset_component( $component ) {
+		return $component instanceof Assets;
+	}
+
+	/**
+	 * Check if an asset component is active.
+	 *
+	 * @since  0.1
+	 *
+	 * @param object $component The component to check.
+	 *
+	 * @return bool If the component is an asset implemented object or not.
+	 */
+	private function is_active_asset_component( $component ) {
+		return $this->is_asset_component( $component ) && $component->is_active();
+	}
+
+	/**
+	 * Check if component is a setup implementing component.
+	 *
+	 * @since  0.1
+	 *
+	 * @param object $component The component to check.
+	 *
+	 * @return bool If the component implements Setup.
+	 */
+	private function is_setup_component( $component ) {
+		return $component instanceof Setup;
+	}
+
+	/**
+	 * Check if component is a config implementing component.
+	 *
+	 * @since  0.1
+	 *
+	 * @param object $component The component to check.
+	 *
+	 * @return bool If the component implements Config.
+	 */
+	private function is_config_component( $component ) {
+		return $component instanceof Config;
+	}
+
+	/**
+	 * Check if component is a settings implementing component.
+	 *
+	 * @since  0.1
+	 *
+	 * @param object $component The component to check.
+	 *
+	 * @return bool If the component implements Setting.
+	 */
+	private function is_setting_component( $component ) {
+		return $component instanceof Settings_Component;
+	}
+
+	/**
+	 * Check if component is a notice implementing component.
+	 *
+	 * @since  0.1
+	 *
+	 * @param object $component The component to check.
+	 *
+	 * @return bool If the component implements Notice.
+	 */
+	private function is_notice_component( $component ) {
+		return $component instanceof Notice;
+	}
+
+	/**
+	 * Init the plugin properties.
+	 *
+	 * @return void
+	 */
+	public function init() {
+		require_once ABSPATH . 'wp-admin/includes/plugin.php';
+
+		$plugin              = get_plugin_data( CLDN_CORE );
+		$location            = $this->locate_plugin();
+		$this->slug          = ! empty( $plugin['TextDomain'] ) ? $plugin['TextDomain'] : $location['dir_basename'];
+		$this->version       = $plugin['Version'];
+		$this->dir_path      = $location['dir_path'];
+		$this->template_path = $this->dir_path . 'php/templates/';
+		$this->dir_url       = $location['dir_url'];
+		$this->plugin_file   = pathinfo( dirname( CLDN_CORE ), PATHINFO_BASENAME ) . '/' . wp_basename( CLDN_CORE );
+	}
+
+	/**
+	 * Setup hooks
+	 *
+	 * @since  0.1
+	 */
+	public function setup() {
+		$this->set_config();
+
+		if ( $this->settings->get_param( 'connected' ) ) {
+			/**
+			 * Component that implements Component\Setup.
+			 *
+			 * @var  Component\Setup $component
+			 */
+			foreach ( $this->components as $key => $component ) {
+				if ( ! $this->is_setup_component( $component ) ) {
+					continue;
+				}
+
+				$component->setup();
+			}
+		}
+
+		/**
+		 * Action indicating that the Cloudinary is ready and setup.
+		 *
+		 * @hook  cloudinary_ready
+		 * @since 3.0.0
+		 *
+		 * @param $plugin {Plugin} The core plugin object.
+		 */
+		do_action( 'cloudinary_ready', $this );
+	}
+
+	/**
+	 * Load admin notices where needed.
+	 *
+	 * @since  0.1
+	 */
+	public function admin_notices() {
+
+		$setting = Utils::get_active_setting();
+		/**
+		 * An array of classes that implement the Notice interface.
+		 *
+		 * @var $components Notice[]
+		 */
+		$components = array_filter( $this->components, array( $this, 'is_notice_component' ) );
+		$default    = array(
+			'message'     => '',
+			'type'        => 'error',
+			'dismissible' => false,
+			'duration'    => 10, // Default dismissible duration is 10 Seconds for save notices etc...
+			'icon'        => null,
+		);
+
+		foreach ( $components as $component ) {
+			$notices = $component->get_notices();
+			foreach ( $notices as $notice ) {
+				$notice = wp_parse_args( $notice, $default );
+				$this->components['admin']->add_admin_notice( 'cld_general', $notice['message'], $notice['type'], $notice['dismissible'], $notice['duration'], $notice['icon'] );
+			}
+		}
+	}
+
+	/**
+	 * Setup the Cloudinary endpoints.
+	 */
+	protected function setup_endpoints() {
+
+		/**
+		 * The Cloudinary API URL.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_API' ) ) {
+			define( 'CLOUDINARY_ENDPOINTS_API', 'api.cloudinary.com' );
+		}
+
+		/**
+		 * The Cloudinary endpoint for the Core.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_CORE' ) ) {
+			// The %s stands for the version of Core. Use the constant CLOUDINARY_ENDPOINTS_CORE_VERSION to set it.
+			define( 'CLOUDINARY_ENDPOINTS_CORE', 'https://unpkg.com/cloudinary-core@%s/cloudinary-core-shrinkwrap.min.js' );
+		}
+
+		/**
+		 * The Cloudinary Core version.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_CORE_VERSION' ) ) {
+			define( 'CLOUDINARY_ENDPOINTS_CORE_VERSION', '2.6.3' );
+		}
+
+		/**
+		 * The Cloudinary endpoint to submit the deactivation feedback.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_DEACTIVATION' ) ) {
+			define( 'CLOUDINARY_ENDPOINTS_DEACTIVATION', 'https://analytics-api.cloudinary.com/wp_deactivate_reason' );
+		}
+
+		/**
+		 * The Cloudinary Gallery widget lib cdn url.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_GALLERY' ) ) {
+			define( 'CLOUDINARY_ENDPOINTS_GALLERY', 'https://product-gallery.cloudinary.com/all.js' );
+		}
+
+		/**
+		 * The Cloudinary endpoint for the Media Library.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_MEDIA_LIBRARY' ) ) {
+			define( 'CLOUDINARY_ENDPOINTS_MEDIA_LIBRARY', 'https://media-library.cloudinary.com/global/all.js' );
+		}
+
+		/**
+		 * The Cloudinary endpoint for the Preview Image.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_PREVIEW_IMAGE' ) ) {
+			define( 'CLOUDINARY_ENDPOINTS_PREVIEW_IMAGE', 'https://res.cloudinary.com/demo/image/upload/' );
+		}
+
+		/**
+		 * The Cloudinary endpoint for the Preview Video.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_PREVIEW_VIDEO' ) ) {
+			define( 'CLOUDINARY_ENDPOINTS_PREVIEW_VIDEO', 'https://res.cloudinary.com/demo/video/upload/' );
+		}
+
+		/**
+		 * The Cloudinary endpoint for the Video Player Embed.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_VIDEO_PLAYER_EMBED' ) ) {
+			define( 'CLOUDINARY_ENDPOINTS_VIDEO_PLAYER_EMBED', 'https://player.cloudinary.com/embed/' );
+		}
+
+		/**
+		 * The Cloudinary endpoint for the Video Player Script.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_VIDEO_PLAYER_SCRIPT' ) ) {
+			// The %s stands for the version of Video Player. Use the constant CLOUDINARY_ENDPOINTS_VIDEO_PLAYER_VERSION to set it.
+			define( 'CLOUDINARY_ENDPOINTS_VIDEO_PLAYER_SCRIPT', 'https://unpkg.com/cloudinary-video-player@%s/dist/cld-video-player.min.js' );
+		}
+
+		/**
+		 * The Cloudinary endpoint for the Video Player Style.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_VIDEO_PLAYER_STYLE' ) ) {
+			// The %s stands for the version of Video Player. Use the constant CLOUDINARY_ENDPOINTS_VIDEO_PLAYER_VERSION to set it.
+			define( 'CLOUDINARY_ENDPOINTS_VIDEO_PLAYER_STYLE', 'https://unpkg.com/cloudinary-video-player@%s/dist/cld-video-player.min.css' );
+		}
+
+		/**
+		 * The Cloudinary Video Player version.
+		 */
+		if ( ! defined( 'CLOUDINARY_ENDPOINTS_VIDEO_PLAYER_VERSION' ) ) {
+			define( 'CLOUDINARY_ENDPOINTS_VIDEO_PLAYER_VERSION', '3.0.2' );
+		}
+	}
+
+	/**
+	 * Autoload for classes that are in the same namespace as $this.
+	 *
+	 * @param string $class Class name.
+	 *
+	 * @return void
+	 */
+	public function autoload( $class ) {
+		// Assume we're using namespaces (because that's how the plugin is structured).
+		$namespace = explode( '\\', $class );
+		$root      = array_shift( $namespace );
+
+		// If a class ends with "Trait" then prefix the filename with 'trait-', else use 'class-'.
+		$class_trait = preg_match( '/Trait$/', $class ) ? 'trait-' : 'class-';
+
+		// If we're not in the plugin's namespace then just return.
+		if ( 'Cloudinary' !== $root ) {
+			return;
+		}
+
+		// Class name is the last part of the FQN.
+		$class_name = array_pop( $namespace );
+
+		// Remove "Trait" from the class name.
+		if ( 'trait-' === $class_trait ) {
+			$class_name = str_replace( '_Trait', '', $class_name );
+		}
+
+		// For file naming, the namespace is everything but the class name and the root namespace.
+		$namespace = trim( implode( DIRECTORY_SEPARATOR, $namespace ) );
+
+		// Get the path to our files.
+		$directory = __DIR__ . DIRECTORY_SEPARATOR . '../php';
+		if ( ! empty( $namespace ) ) {
+			$directory .= DIRECTORY_SEPARATOR . strtolower( $namespace );
+		}
+
+		// Because WordPress file naming conventions are odd.
+		$file = strtolower( str_replace( '_', '-', $class_name ) );
+
+		$file = $directory . DIRECTORY_SEPARATOR . $class_trait . $file . '.php';
+
+		if ( file_exists( $file ) ) {
+			require_once $file; // phpcs:ignore
+		}
+	}
+
+	/**
+	 * Version of plugin_dir_url() which works for plugins installed in the plugins directory,
+	 * and for plugins bundled with themes.
+	 *
+	 * @return array
+	 */
+	public function locate_plugin() {
+
+		$dir_url      = plugin_dir_url( CLDN_CORE );
+		$dir_path     = CLDN_PATH;
+		$dir_basename = wp_basename( CLDN_PATH );
+
+		return compact( 'dir_url', 'dir_path', 'dir_basename' );
+	}
+
+	/**
+	 * Relative Path
+	 *
+	 * Returns a relative path from a specified starting position of a full path
+	 *
+	 * @param string $path  The full path to start with.
+	 * @param string $start The directory after which to start creating the relative path.
+	 * @param string $sep   The directory separator.
+	 *
+	 * @return string
+	 */
+	public function relative_path( $path, $start, $sep ) {
+		$path = explode( $sep, untrailingslashit( $path ) );
+		if ( count( $path ) > 0 ) {
+			foreach ( $path as $p ) {
+				array_shift( $path );
+				if ( $p === $start ) {
+					break;
+				}
+			}
+		}
+
+		return implode( $sep, $path );
+	}
+
+	/**
+	 * Return whether we're on WordPress.com VIP production.
+	 *
+	 * @return bool
+	 */
+	public function is_wpcom_vip_prod() {
+		return ( defined( '\WPCOM_IS_VIP_ENV' ) && WPCOM_IS_VIP_ENV );
+	}
+
+	/**
+	 * Call trigger_error() if not on VIP production.
+	 *
+	 * @param string $message Warning message.
+	 * @param int    $code    Warning code.
+	 */
+	public function trigger_warning( $message, $code = E_USER_WARNING ) {
+		if ( ! $this->is_wpcom_vip_prod() ) {
+			// @phpcs:disable
+			trigger_error( esc_html( get_class( $this ) . ': ' . $message ), $code );
+			// @phpcs:enable
+		}
+	}
+
+	/**
+	 * Add Script data.
+	 *
+	 * @param string      $slug   The slug to add.
+	 * @param mixed       $value  The value to set.
+	 * @param string|null $handle $the optional script handle to add data for.
+	 */
+	public function add_script_data( $slug, $value, $handle = null ) {
+		if ( null === $handle ) {
+			$handle = $this->slug;
+		}
+		$this->settings->set_param( '@script' . $this->settings->separator . $handle . $this->settings->separator . $slug, $value );
+	}
+
+	/**
+	 * Output script data if set.
+	 */
+	public function print_script_data() {
+		$handles = $this->settings->get_param( '@script' );
+		if ( ! empty( $handles ) ) {
+			foreach ( $handles as $handle => $data ) {
+				// We should never be using multiple handles. This is just for cases where data needs to be added where the main script is not loaded.
+				$json = wp_json_encode( $data );
+				wp_add_inline_script( $handle, 'var cldData = ' . $json, 'before' );
+			}
+		}
+	}
+
+	/**
+	 * Force Visit Plugin Site Link
+	 *
+	 * If the plugin slug is set and the current user can install plugins, only the "View Details" link is shown.
+	 * This method forces the "Visit plugin site" link to appear.
+	 *
+	 * @see wp-admin/includes/class-wp-plugins-list-table.php
+	 *
+	 * @param array  $plugin_meta An array of the plugin's metadata.
+	 * @param string $plugin_file Path to the plugin file, relative to the plugins directory.
+	 * @param array  $plugin_data An array of plugin data.
+	 * @param string $status      Status of the plugin.
+	 *
+	 * @return array
+	 */
+	public function force_visit_plugin_site_link( $plugin_meta, $plugin_file, $plugin_data, $status ) {
+		if ( 'Cloudinary' === $plugin_data['Name'] ) {
+			$plugin_site_link = sprintf(
+				'<a href="%s">%s</a>',
+				esc_url( $plugin_data['PluginURI'] ),
+				__( 'Visit plugin site', 'cloudinary' )
+			);
+			if ( ! in_array( $plugin_site_link, $plugin_meta, true ) ) {
+				$plugin_meta[] = $plugin_site_link;
+			}
+		}
+
+		return $plugin_meta;
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-rest-api.php.html b/docs/php_class-rest-api.php.html new file mode 100644 index 000000000..5f576bade --- /dev/null +++ b/docs/php_class-rest-api.php.html @@ -0,0 +1,187 @@ + + + + + Source: php/class-rest-api.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-rest-api.php

+ + + + + + + +
+
+
<?php
+/**
+ * REST_API is the parent component for the Cloudinary plugin endpoints.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+/**
+ * Class REST_API
+ */
+class REST_API {
+
+	const BASE = 'cloudinary/v1';
+
+	/**
+	 * Plugin REST API endpoints.
+	 *
+	 * @var array
+	 */
+	public $endpoints;
+
+	/**
+	 * REST_API constructor.
+	 *
+	 * @param Plugin $plugin Instance of the global Plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+		add_action( 'rest_api_init', array( $this, 'rest_api_init' ), PHP_INT_MAX );
+	}
+
+	/**
+	 * Init the REST API endpoints.
+	 */
+	public function rest_api_init() {
+
+		$defaults = array(
+			'method'              => \WP_REST_Server::READABLE,
+			'callback'            => __return_empty_array(),
+			'args'                => array(),
+			'permission_callback' => '__return_true',
+		);
+
+		/**
+		 * Filter the Cloudinary REST API endpoints.
+		 *
+		 * @hook    cloudinary_api_rest_endpoints
+		 * @default array()
+		 *
+		 * @param $types {array} The registered endpoints.
+		 *
+		 * @return {array}
+		 */
+		$this->endpoints = apply_filters( 'cloudinary_api_rest_endpoints', array() );
+
+		foreach ( $this->endpoints as $route => $endpoint ) {
+			$endpoint = wp_parse_args( $endpoint, $defaults );
+			register_rest_route(
+				static::BASE,
+				$route,
+				array(
+					'methods'             => $endpoint['method'],
+					'callback'            => $endpoint['callback'],
+					'args'                => $endpoint['args'],
+					'permission_callback' => $endpoint['permission_callback'],
+				)
+			);
+		}
+	}
+
+	/**
+	 * Basic permission callback.
+	 *
+	 * Explicitly defined to allow easier testability.
+	 *
+	 * @return bool
+	 */
+	public static function rest_can_connect() {
+		return Utils::user_can( 'connect' );
+	}
+
+	/**
+	 * Initilize a background request.
+	 *
+	 * @param string $endpoint The REST API endpoint to call.
+	 * @param array  $params   Array of parameters to send.
+	 * @param string $method   The method to use in the call.
+	 */
+	public function background_request( $endpoint, $params = array(), $method = 'POST' ) {
+
+		$url = Utils::rest_url( static::BASE . '/' . $endpoint );
+		// Setup a call for a background sync.
+		$params['nonce'] = wp_create_nonce( 'wp_rest' );
+		$args            = array(
+			'timeout'   => 0.1,
+			'blocking'  => false,
+			/** This filter is documented in wp-includes/class-wp-http-streams.php */
+			'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
+			'method'    => $method,
+			'headers'   => array(),
+			'body'      => $params,
+		);
+		if ( is_user_logged_in() ) {
+			// Setup cookie.
+			$logged_cookie = wp_parse_auth_cookie( '', 'logged_in' );
+			if ( ! empty( $logged_cookie ) ) {
+				array_pop( $logged_cookie ); // remove the scheme.
+
+				// Add logged in cookie to request.
+				$args['cookies'] = array(
+					new \WP_Http_Cookie(
+						array(
+							'name'    => LOGGED_IN_COOKIE,
+							'value'   => implode( '|', $logged_cookie ),
+							'expires' => '+ 1 min', // Expire after a min only.
+						),
+						$url
+					),
+				);
+			}
+		}
+		$args['headers']['X-WP-Nonce'] = $params['nonce'];
+
+		// Send request.
+		wp_remote_request( $url, $args );
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-settings.php.html b/docs/php_class-settings.php.html new file mode 100644 index 000000000..7c12f47ae --- /dev/null +++ b/docs/php_class-settings.php.html @@ -0,0 +1,765 @@ + + + + + Source: php/class-settings.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-settings.php

+ + + + + + + +
+
+
<?php
+/**
+ * Cloudinary Settings represents a collection of settings.
+ *
+ * @package   Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Settings\Setting;
+use Cloudinary\Traits\Params_Trait;
+use Cloudinary\Settings\Storage\Storage;
+
+/**
+ * Class Settings
+ *
+ * @package Cloudinary
+ */
+class Settings {
+
+	use Params_Trait;
+
+	/**
+	 * Holds the child settings.
+	 *
+	 * @var Setting[]
+	 */
+	protected $settings = array();
+
+	/**
+	 * Holds the storage objects.
+	 *
+	 * @var Storage
+	 */
+	protected $storage;
+
+	/**
+	 * Holds the list of storage keys.
+	 *
+	 * @var array
+	 */
+	protected $storage_keys = array();
+
+	/**
+	 * Holds the slug.
+	 *
+	 * @var string
+	 */
+	protected $slug;
+
+	/**
+	 * Holds the keys for meta storage.
+	 *
+	 * @var array
+	 */
+	const META_KEYS = array(
+		'submission' => '@submission',
+		'pending'    => '@pending',
+		'data'       => '@data',
+		'storage'    => 'storage_path',
+	);
+
+	/**
+	 * Setting constructor.
+	 *
+	 * @param string $slug   The slug/name of the settings set.
+	 * @param array  $params Optional params for the setting.
+	 */
+	public function __construct( $slug, $params = array() ) {
+		$this->slug = $slug;
+
+		if ( isset( $params['storage'] ) ) {
+			// Test if shorthand was used.
+			if ( class_exists( 'Cloudinary\\Settings\\Storage\\' . $params['storage'] ) ) {
+				$params['storage'] = 'Cloudinary\\Settings\\Storage\\' . $params['storage'];
+			}
+		} else {
+			// Default.
+			$params['storage'] = 'Cloudinary\\Settings\\Storage\\Options';
+		}
+
+		// Set the storage.
+		$this->set_param( 'storage', $params['storage'] );
+		$this->init();
+
+		// Build the settings from params.
+		if ( ! empty( $params['settings'] ) ) {
+
+			foreach ( $params['settings'] as $key => &$param ) {
+				$param['type'] = 'page';// Hard set root items as pages.
+				$param         = $this->get_default_settings( $param, $key, $key );
+			}
+
+			$this->set_params( $params );
+		}
+	}
+
+	/**
+	 * Get the default settings based on the Params.
+	 *
+	 * @param array       $params  The params to get defaults from.
+	 * @param null|string $initial The initial slug to be pre-pended..
+	 * @param string|bool $root    Flag to indicate we're ata root item for storage.
+	 *
+	 * @return array
+	 */
+	public function get_default_settings( $params, $initial = null, $root = false ) {
+		static $storage_name;
+
+		// Reset the storage name.
+		if ( ! empty( $root ) ) {
+			$storage_name = $initial;
+		}
+
+		// If we have an option_name, lets set the storage name to that.
+		if ( ! empty( $params['option_name'] ) ) {
+			$storage_name = $params['option_name'];
+		}
+
+		if ( isset( $params['slug'] ) ) {
+			$initial .= $this->separator . $params['slug'];
+		}
+
+		foreach ( $params as $key => &$param ) {
+			if ( ! is_numeric( $key ) && 'settings' !== $key ) {
+				continue;
+			}
+			if ( ! isset( $param['type'] ) ) {
+				$param['type'] = 'tag'; // Set the default.
+			}
+			if ( isset( $param[0] ) || isset( $param['settings'] ) ) {
+				$param = $this->get_default_settings( $param, $initial );
+			} elseif ( isset( $param['slug'] ) ) {
+
+				$default = '';
+				if ( isset( $param['default'] ) ) {
+					$default = $param['default'];
+				}
+
+				// Set the slug path.
+				$slug          = $initial . $this->separator . $param['slug'];
+				$storage_parts = explode( $this->separator, $slug, 2 );
+				// Append the slug to the storage path.
+				$param[ self::META_KEYS['storage'] ] = $storage_name . $this->separator . $storage_parts[1];
+				$param['setting']                    = $this->add( $slug, $default, $param );
+			}
+		}
+
+		return $params;
+	}
+
+	/**
+	 * Magic method to get a chainable setting.
+	 *
+	 * @param string $name The name of the setting to get dynamically.
+	 *
+	 * @return Setting|null
+	 */
+	public function __get( $name ) {
+		$setting = null;
+		if ( isset( $this->settings[ $name ] ) ) {
+			$setting = $this->settings[ $name ];
+		}
+		if ( ! $setting ) {
+			$setting = $this->find_setting( $name );
+		}
+
+		return $setting;
+	}
+
+	/**
+	 * Remove a setting.
+	 *
+	 * @param string $slug The setting to remove.
+	 *
+	 * @return bool
+	 */
+	public function delete( $slug ) {
+		$this->remove_param( self::META_KEYS['data'] . $this->separator . $slug );
+
+		return $this->storage->delete( $slug );
+	}
+
+	/**
+	 * Init the settings.
+	 */
+	protected function init() {
+		$storage       = $this->get_param( 'storage' );
+		$this->storage = new $storage( $this->slug );
+	}
+
+	/**
+	 * Register a settings storage point.
+	 *
+	 * @param string $slug The key (option-name) to register the storage as.
+	 */
+	protected function register_storage( $slug ) {
+		// Get the root key.
+		if ( ! $this->has_param( self::META_KEYS['data'] . $this->separator . $slug ) ) {
+			$slug = explode( $this->separator, $slug, 2 )[0];
+
+			$data     = $this->storage->get( $slug );
+			$defaults = $this->get_param( self::META_KEYS['data'] . $this->separator . $slug, null );
+			if ( ! empty( $data ) ) {
+				if ( ! empty( $defaults ) && is_array( $data ) ) {
+					$data = wp_parse_args( $data, (array) $defaults );
+				}
+				$this->set_param( self::META_KEYS['data'] . $this->separator . $slug, $data );
+			}
+			$this->storage_keys[ $slug ] = $data;
+		}
+	}
+
+	/**
+	 * Get the storage key.
+	 *
+	 * @param string $slug The slug to get.
+	 * @param string $type The setting type.
+	 *
+	 * @return string
+	 */
+	public function get_storage_key( $slug, $type = null ) {
+		if ( null === $type ) {
+			$type = $this->get_setting( $slug )->get_param( 'type' );
+		}
+		$prefix = null;
+		if ( 'data' !== $type ) {
+			// Data types are stored and retrieved without prefixes so we can handle external or legacy options.
+			$prefix = $this->slug . '_';
+		}
+
+		return $prefix . $slug;
+	}
+
+	/**
+	 * Add a setting.
+	 *
+	 * @param string $slug    The setting slug.
+	 * @param mixed  $default The default value.
+	 * @param array  $params  The params.
+	 *
+	 * @return Setting|\WP_Error
+	 */
+	public function add( $slug, $default = array(), $params = array() ) {
+		$default_params = array(
+			'type'                     => 'tag',
+			self::META_KEYS['storage'] => $slug,
+		);
+		$params         = wp_parse_args( $params, $default_params );
+		$parts          = explode( $this->separator, trim( $slug, $this->separator ) );
+		$storage_paths  = explode( $this->separator, trim( $params[ self::META_KEYS['storage'] ], $this->separator ) );
+		$path           = array();
+		$value          = array();
+		$storage        = array();
+		$last_child     = null;
+
+		// If we have an option_name, in a single field, lets set the storage name for that item only.
+		if ( ! empty( $params['option_name'] ) ) {
+			array_pop( $storage_paths ); // Knockoff the end slug.
+			$storage_key      = $this->get_storage_key( $params['option_name'], $params['type'] );
+			$storage_paths[0] = $storage_key; // Set the base storage.
+		}
+
+		while ( ! empty( $parts ) ) {
+			$path[] = array_shift( $parts );
+			if ( ! empty( $storage_paths ) ) {
+				$storage[] = array_shift( $storage_paths );
+			}
+			if ( empty( $parts ) ) {
+				$value = $default;
+			}
+			$name                                 = implode( $this->separator, $path );
+			$params[ self::META_KEYS['storage'] ] = implode( $this->separator, $storage );
+			$child                                = $this->register( $name, $value, $params );
+			if ( is_wp_error( $child ) ) {
+				return $child;
+			}
+
+			if ( $last_child ) {
+				$last_child->add( $child );
+			}
+			$last_child = $child;
+		}
+
+		return $this->settings[ $slug ];
+	}
+
+	/**
+	 * Register a new setting with internals.
+	 *
+	 * @param string $slug    The setting slug.
+	 * @param mixed  $default The default value.
+	 * @param array  $params  The params.
+	 *
+	 * @return mixed|Setting
+	 */
+	protected function register( $slug, $default, $params ) {
+
+		if ( isset( $this->settings[ $slug ] ) ) {
+			return $this->settings[ $slug ];
+		}
+
+		$slug_parts   = explode( $this->separator, $slug );
+		$params['id'] = array_pop( $slug_parts );
+		$parent       = implode( $this->separator, $slug_parts );
+
+		$setting = $this->create_child( $slug, $params );
+		$setting->set_type( gettype( $default ) );
+		if ( ! empty( $parent ) ) {
+			$setting->set_parent( $parent );
+		}
+		$this->settings[ $slug ] = $setting;
+
+		// Register storage.
+		$this->register_storage( $params[ self::META_KEYS['storage'] ] );
+
+		// Set default.
+		if ( ! $this->has_param( self::META_KEYS['data'] . $this->separator . $params[ self::META_KEYS['storage'] ] ) ) {
+			$this->set_param( self::META_KEYS['data'] . $this->separator . $params[ self::META_KEYS['storage'] ], $default );
+		}
+
+		return $this->settings[ $slug ];
+	}
+
+	/**
+	 * Create a new child.
+	 *
+	 * @param string $slug   The slug.
+	 * @param array  $params Optional Params.
+	 *
+	 * @return Setting
+	 */
+	protected function create_child( $slug, $params ) {
+
+		return new Settings\Setting( $slug, $this, $params );
+	}
+
+	/**
+	 * Get a setting value.
+	 *
+	 * @param [string] ...$slugs Additional slugs to get settings for.
+	 *
+	 * @return mixed
+	 */
+	public function get_value( ...$slugs ) {
+		if ( empty( $slugs ) ) {
+			$slugs = array( '' );
+		}
+		$return = array();
+		foreach ( $slugs as $slug ) {
+			$key = self::META_KEYS['data'];
+			if ( ! empty( $slug ) ) {
+				$setting      = $this->get_setting( $slug );
+				$storage_path = $setting->get_param( self::META_KEYS['storage'], $setting->get_slug() );
+				$key         .= $this->separator . $storage_path;
+			}
+			$value = $this->get_param( $key );
+			if ( ! $slug ) {
+				$slug = $this->slug;
+			}
+			$base_slug = explode( $this->separator, $slug );
+			$base_slug = array_pop( $base_slug );
+
+			/**
+			 * Filter the setting value.
+			 *
+			 * @hook cloudinary_setting_get_value
+			 *
+			 * @param $value {mixed} The setting value.
+			 * @param $slug  {string}  The setting slug.
+			 *
+			 * @return {mixed}
+			 */
+			$return[ $slug ] = apply_filters( 'cloudinary_setting_get_value', $value, $slug );
+		}
+
+		return 1 === count( $slugs ) ? array_shift( $return ) : $return;
+	}
+
+	/**
+	 * Get the slug.
+	 *
+	 * @return string
+	 */
+	public function get_slug() {
+		return $this->slug;
+	}
+
+	/**
+	 * Get the URL for a root page setting.
+	 *
+	 * @param string $slug The page slug to get URL for.
+	 *
+	 * @return string
+	 */
+	public function get_url( $slug ) {
+		$struct = $this->get_param( 'settings' . $this->separator . $slug );
+		$args   = array(
+			'page' => $this->get_storage_key( $slug ),
+		);
+		if ( isset( $struct['section'] ) ) {
+			$args['page']    = $this->get_slug();
+			$args['section'] = $struct['section'];
+		}
+		$path = add_query_arg( $args, 'admin.php' );
+
+		return admin_url( $path );
+	}
+
+	/**
+	 * Find a Setting.
+	 *
+	 * @param string $slug   The setting slug.
+	 * @param bool   $create Flag to create a setting if not found.
+	 *
+	 * @return self|Setting
+	 */
+	public function find_setting( $slug, $create = true ) {
+		$setting = null;
+		$try     = str_pad( $slug, strlen( $slug ) + 2, $this->separator, STR_PAD_BOTH );
+		foreach ( array_keys( $this->settings ) as $key ) {
+			$try_key = str_pad( $key, strlen( $key ) + 2, $this->separator, STR_PAD_BOTH );
+			if ( false !== strpos( $try_key, $try ) ) {
+				$maybe = trim( strstr( $try_key, $try, true ) . $this->separator . $slug, $this->separator );
+				if ( isset( $this->settings[ $maybe ] ) ) {
+					$setting = $this->settings[ $maybe ];
+					break;
+				}
+			}
+		}
+
+		if ( ! $setting && true === $create ) {
+			$setting = $this->add( $slug, null, array( 'type' => 'dynamic' ) );
+		}
+
+		return $setting;
+	}
+
+	/**
+	 * Get a setting.
+	 *
+	 * @param string $slug   The slug to get.
+	 * @param bool   $create Flag to create setting if not found.
+	 *
+	 * @return Setting|null
+	 */
+	public function get_setting( $slug, $create = true ) {
+		$found = null;
+		if ( isset( $this->settings[ $slug ] ) ) {
+			$found = $this->settings[ $slug ];
+		}
+
+		if ( empty( $found ) ) {
+			$found = $this->find_setting( $slug );
+			if ( false === $create && 'dynamic' === $found->get_param( 'type' ) ) {
+				$found = null;
+			}
+		}
+
+		return $found;
+	}
+
+	/**
+	 * Get settings.
+	 *
+	 * @return Setting[]
+	 */
+	public function get_settings() {
+		$settings = array();
+		foreach ( $this->settings as $slug => $setting ) {
+			if ( false === strpos( $slug, $this->separator ) ) {
+				$settings[ $slug ] = $setting;
+			}
+		}
+
+		return $settings;
+	}
+
+	/**
+	 * Get the root setting.
+	 *
+	 * @return self
+	 */
+	public function get_root_setting() {
+		return $this;
+	}
+
+	/**
+	 * Set a setting's value.
+	 *
+	 * @param string $slug  The slag of the setting to set.
+	 * @param mixed  $value The value to set.
+	 *
+	 * @return bool
+	 */
+	public function set_value( $slug, $value ) {
+		$set = false;
+		if ( isset( $this->settings[ $slug ] ) ) {
+			$storage_path = $this->settings[ $slug ]->get_param( self::META_KEYS['storage'] );
+			$current      = $this->get_param( self::META_KEYS['data'] . $this->separator . $storage_path );
+			if ( $current !== $value ) {
+				$this->set_param( self::META_KEYS['data'] . $this->separator . $storage_path, $value );
+				$set = true;
+			}
+		} else {
+			$found = $this->find_setting( $slug );
+			if ( $found ) {
+				$storage_path = $found->get_param( self::META_KEYS['storage'], $found->get_slug() );
+				$set          = $this->set_value( $storage_path, $value );
+			}
+		}
+
+		return $set;
+	}
+
+	/**
+	 * Pend a setting's value, for prep to update.
+	 *
+	 * @param string $slug          The slag of the setting to pend set.
+	 * @param mixed  $new_value     The value to set.
+	 * @param mixed  $current_value The optional current value to compare.
+	 *
+	 * @return bool|\WP_Error
+	 */
+	public function set_pending( $slug, $new_value, $current_value = null ) {
+
+		$setting = $this->get_setting( $slug );
+		/**
+		 * Pre-Filter the value before saving a setting.
+		 *
+		 * @hook   cloudinary_settings_save_setting_{$slug}
+		 * @hook   cloudinary_settings_save_setting
+		 * @since  2.7.6
+		 *
+		 * @param $new_value     {int}     The new setting value.
+		 * @param $current_value {string}  The setting current value.
+		 * @param $setting       {Setting} The setting object.
+		 *
+		 * @return {mixed}
+		 */
+		$new_value = apply_filters( "cloudinary_settings_save_setting_{$slug}", $new_value, $current_value, $setting );
+		$new_value = apply_filters( 'cloudinary_settings_save_setting', $new_value, $current_value, $setting );
+		if ( is_wp_error( $new_value ) ) {
+			return $new_value;
+		}
+		$path  = $setting->get_param( self::META_KEYS['storage'] );
+		$store = explode( $this->separator, $path, 2 )[0];
+		if ( ! $this->has_param( self::META_KEYS['pending'] . $this->separator . $store ) ) {
+			$parent = $this->get_param( self::META_KEYS['data'] . $this->separator . $store );
+			$this->set_param( self::META_KEYS['pending'] . $this->separator . $store, $parent );
+		}
+		$this->set_param( self::META_KEYS['pending'] . $this->separator . $path, $new_value );
+
+		return true;
+	}
+
+	/**
+	 * Get a setting's pending value for update.
+	 *
+	 * @param string $slug The slug to get the pending data for.
+	 *
+	 * @return mixed
+	 */
+	public function get_pending( $slug = null ) {
+		$slug = $slug ? $this->separator . $slug : null;
+
+		return $this->get_param( self::META_KEYS['pending'] . $slug, array() );
+	}
+
+	/**
+	 * Check if a slug has a pending set of changes.
+	 *
+	 * @param string $slug The slug to get the pending data for.
+	 *
+	 * @return bool
+	 */
+	public function has_pending( $slug ) {
+		return $this->has_param( self::META_KEYS['pending'] . $this->separator . $slug );
+	}
+
+	/**
+	 * Remove a pending set.
+	 *
+	 * @param string $slug The slug to get the pending data for.
+	 */
+	public function remove_pending( $slug ) {
+		$this->remove_param( self::META_KEYS['pending'] . $this->separator . $slug );
+	}
+
+	/**
+	 * Save settings.
+	 *
+	 * @return bool[]|\WP_Error[]
+	 */
+	public function save() {
+		$pending   = array_keys( $this->get_pending() );
+		$responses = array();
+		foreach ( $pending as $slug ) {
+			if ( $this->save_setting( $slug ) ) {
+				$responses[] = $slug;
+			}
+		}
+
+		return $responses;
+	}
+
+	/**
+	 * Save the settings values to the storage.
+	 *
+	 * @param string $storage_key The storage_key slug to save.
+	 *
+	 * @return bool|\WP_Error
+	 */
+	public function save_setting( $storage_key ) {
+
+		$pending = $this->get_pending( $storage_key );
+		$this->remove_pending( $storage_key );
+		$this->storage->set( $storage_key, $pending );
+		$saved = $this->storage->save( $storage_key );
+		if ( true === $saved ) {
+			$this->set_value( $storage_key, $pending );
+		}
+
+		return $saved;
+	}
+
+	/**
+	 * Capture a submission if there is one.
+	 */
+	protected function capture_raw_submission() {
+		$args = array();
+		foreach ( array_keys( $this->get_settings() ) as $slug ) {
+			$args[ $slug ] = array(
+				'filter'  => FILTER_CALLBACK,
+				'options' => function ( $value ) {
+					return $value;
+				},
+			);
+		}
+		$raw_submission = filter_input_array( INPUT_POST, $args );
+		if ( $raw_submission ) {
+			$submission = array_filter( $raw_submission );
+			if ( ! empty( $submission ) ) {
+				foreach ( $submission as $key => $value ) {
+					$this->set_param( self::META_KEYS['submission'] . $this->separator . $key, $value );
+				}
+			}
+		}
+
+		return $this->has_param( self::META_KEYS['submission'] );
+	}
+
+	/**
+	 * Get a raw (un-sanitised) submission for all settings, or by a setting slug.
+	 *
+	 * @param string|null $slug The slug of the submitted value to get.
+	 *
+	 * @return mixed
+	 */
+	public function get_submitted_value( $slug = null ) {
+		$key = self::META_KEYS['submission'];
+		if ( ! $this->has_param( $key ) && ! $this->capture_raw_submission() ) {
+			return null;
+		}
+		if ( ! empty( $slug ) ) {
+			$setting = isset( $this->settings[ $slug ] ) ? $this->settings[ $slug ] : $this->find_setting( $slug );
+			$key     = $key . $this->separator . $setting->get_slug();
+
+			return $this->get_param( $key );
+		}
+		$value = array();
+		foreach ( $this->get_settings() as $slug => $setting ) {
+			if ( ! $this->has_param( $key . $this->separator . $slug ) ) {
+				continue; // Ignore bases that don't exist.
+			}
+			$submission = $setting->get_submitted_value();
+			if ( null !== $submission ) {
+				$value[ $slug ] = $submission;
+			}
+		}
+
+		return $value;
+	}
+
+	/**
+	 * Get the storage keys.
+	 *
+	 * @return array
+	 */
+	public function get_storage_keys() {
+		return $this->storage->get_keys();
+	}
+
+	/**
+	 * Get the storage parent.
+	 *
+	 * @param string $slug The slug to get storage object for.
+	 *
+	 * @return string
+	 */
+	protected function get_storage_parent( $slug ) {
+		$parts = explode( $this->separator, $slug );
+
+		return array_shift( $parts );
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-string-replace.php.html b/docs/php_class-string-replace.php.html new file mode 100644 index 000000000..4993e83e7 --- /dev/null +++ b/docs/php_class-string-replace.php.html @@ -0,0 +1,412 @@ + + + + + Source: php/class-string-replace.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-string-replace.php

+ + + + + + + +
+
+
<?php
+/**
+ * Cloudinary string replace class, to replace URLS and other strings on shutdown.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Component\Setup;
+
+/**
+ * String replace class.
+ */
+class String_Replace implements Setup {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @var Plugin Instance of the global plugin.
+	 */
+	public $plugin;
+
+	/**
+	 * Holds the context.
+	 *
+	 * @var string
+	 */
+	protected $context;
+
+	/**
+	 * Holds the list of strings and replacements.
+	 *
+	 * @var array
+	 */
+	protected static $replacements = array();
+
+	/**
+	 * Holds the list of strings and replacements.
+	 *
+	 * @var bool
+	 */
+	protected static $doing_save = false;
+
+	/**
+	 * Site Cache constructor.
+	 *
+	 * @param Plugin $plugin Global instance of the main plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+		$this->plugin = $plugin;
+	}
+
+	/**
+	 * Setup the object.
+	 */
+	public function setup() {
+		if ( Utils::is_admin() ) {
+			$this->admin_filters();
+		} elseif ( Utils::is_frontend_ajax() || Utils::is_rest_api() ) {
+			$this->context = 'view';
+			$this->start_capture();
+		} else {
+			$this->public_filters();
+		}
+		$this->add_rest_filters();
+	}
+
+	/**
+	 * Add admin filters.
+	 */
+	protected function admin_filters() {
+		// Admin filters can call String_Replace frequently, which is fine, as performance is not an issue.
+		add_filter( 'media_send_to_editor', array( $this, 'replace_strings' ), 10, 2 );
+		add_filter( 'the_editor_content', array( $this, 'replace_strings' ), 10, 2 );
+		add_filter( 'wp_prepare_attachment_for_js', array( $this, 'replace_strings' ), 11 );
+		add_action( 'admin_init', array( $this, 'start_capture' ) );
+	}
+
+	/**
+	 * Add Public Filters.
+	 */
+	protected function public_filters() {
+		add_action( 'template_include', array( $this, 'init_debug' ), PHP_INT_MAX );
+		if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) { // Not needed on REST API.
+			add_action( 'parse_request', array( $this, 'init' ), - 1000 ); // Not crazy low, but low enough to catch most cases, but not too low that it may break AMP.
+		}
+	}
+
+	/**
+	 * Add filters for REST API.
+	 */
+	protected function add_rest_filters() {
+		$types = get_post_types();
+		foreach ( $types as $type ) {
+			$post_type = get_post_type_object( $type );
+			// Check if this is a rest supported type.
+			if ( property_exists( $post_type, 'show_in_rest' ) && true === $post_type->show_in_rest ) {
+				// Add filter only to rest supported types.
+				add_filter( 'rest_prepare_' . $type, array( $this, 'pre_filter_rest_content' ), 10, 3 );
+			}
+		}
+		add_filter( 'rest_pre_echo_response', array( $this, 'pre_filter_rest_echo' ), 900, 3 );
+	}
+
+	/**
+	 * Filter the result of a rest Request before it's echoed.
+	 *
+	 * @param array            $result  The result of the REST API.
+	 * @param \WP_REST_Server  $server  The REST server instance.
+	 * @param \WP_REST_Request $request The original request.
+	 *
+	 * @return array
+	 */
+	public function pre_filter_rest_echo( $result, $server, $request ) {
+		$route = trim( $request->get_route(), '/' );
+		if ( 0 !== strpos( $route, REST_API::BASE ) ) {
+			// Only for non-Cloudinary requests.
+			$context = $request->get_param( 'context' );
+			if ( ! $context || 'view' === $context ) {
+				$result = $this->replace_strings( $result, $context );
+			}
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Filter out local urls in an 'edit' context rest request ( i.e for Gutenberg ).
+	 *
+	 * @param \WP_REST_Response $response The post data array to save.
+	 * @param \WP_Post          $post     The current post.
+	 * @param \WP_REST_Request  $request  The request object.
+	 *
+	 * @return \WP_REST_Response
+	 */
+	public function pre_filter_rest_content( $response, $post, $request ) {
+		$context = $request->get_param( 'context' );
+
+		if ( 'edit' === $context ) {
+			// Updating or creating a post.
+			if ( in_array( $request->get_method(), array( 'PUT', 'POST' ), true ) ) {
+				static::$doing_save = true;
+			}
+			$data = $response->get_data();
+			$data = $this->replace_strings( $data, $context );
+			$response->set_data( $data );
+		}
+
+		return $response;
+	}
+
+	/**
+	 * Init the buffer capture and set the output callback.
+	 */
+	public function init() {
+		if ( ! defined( 'CLD_DEBUG' ) || false === CLD_DEBUG ) {
+			$this->context = 'view';
+			$this->start_capture();
+		}
+	}
+
+	/**
+	 * Stop the buffer capture and set the output callback.
+	 */
+	public function start_capture() {
+		ob_start( array( $this, 'replace_strings' ) );
+		ob_start( array( $this, 'replace_strings' ) ); // Second call to catch early buffer flushing.
+	}
+
+	/**
+	 * Init the buffer capture in debug mode.
+	 *
+	 * @param string $template The template being loaded.
+	 *
+	 * @return null|string
+	 */
+	public function init_debug( $template ) {
+		if ( defined( 'CLD_DEBUG' ) && true === CLD_DEBUG && ! Utils::get_sanitized_text( '_bypass' ) ) {
+			$this->context = 'view';
+			ob_start();
+			include $template; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+			$html = ob_get_clean();
+			echo $this->replace_strings( $html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+			$template = $this->plugin->template_path . 'blank-template.php';
+		}
+
+		return $template;
+	}
+
+	/**
+	 * Check if a string is set for replacement.
+	 *
+	 * @param string $string String to check.
+	 *
+	 * @return bool
+	 */
+	public static function string_set( $string ) {
+		return isset( self::$replacements[ $string ] );
+	}
+
+	/**
+	 * Check if a string is not set for replacement.
+	 *
+	 * @param string $string String to check.
+	 *
+	 * @return bool
+	 */
+	public static function string_not_set( $string ) {
+		return ! self::string_set( $string );
+	}
+
+	/**
+	 * Replace a string.
+	 *
+	 * @param string $search  The string to be replaced.
+	 * @param string $replace The string replacement.
+	 */
+	public static function replace( $search, $replace ) {
+		self::$replacements[ $search ] = $replace;
+	}
+
+	/**
+	 * Flatten an array into content.
+	 *
+	 * @param array|string $content The array to flatten.
+	 *
+	 * @return string
+	 */
+	public function flatten( $content ) {
+		$flat = '';
+		if ( Utils::looks_like_json( $content ) ) {
+			$maybe_content = json_decode( $content, true );
+			if ( ! empty( $maybe_content ) ) {
+				$content = $maybe_content;
+			}
+		}
+		if ( self::is_iterable( $content ) ) {
+			foreach ( $content as $item ) {
+				$flat .= "\r\n" . $this->flatten( $item );
+			}
+		} else {
+			$flat = $content;
+		}
+
+		return $flat;
+	}
+
+	/**
+	 * Check if the item is iterable.
+	 *
+	 * @param mixed $thing Thing to check.
+	 *
+	 * @return bool
+	 */
+	public static function is_iterable( $thing ) {
+
+		return is_array( $thing ) || is_object( $thing );
+	}
+
+	/**
+	 * Prime replacement strings.
+	 *
+	 * @param mixed  $content The content to prime replacements for.
+	 * @param string $context The context to use.
+	 */
+	protected function prime_replacements( $content, $context = 'view' ) {
+
+		if ( self::is_iterable( $content ) ) {
+			$content = $this->flatten( $content );
+		}
+		/**
+		 * Do replacement action.
+		 *
+		 * @hook  cloudinary_string_replace
+		 * @since 3.0.3 Added the `$context` argument.
+		 *
+		 * @param $content {string} The html of the page.
+		 * @param $context {string} The render context.
+		 */
+		do_action( 'cloudinary_string_replace', $content, $context );
+	}
+
+	/**
+	 * Replace string in HTML.
+	 *
+	 * @param string|array $content The HTML.
+	 * @param string       $context The context to use.
+	 *
+	 * @return string
+	 */
+	public function replace_strings( $content, $context = 'view' ) {
+		static $last_content;
+		if ( empty( $content ) || $last_content === $content ) {
+			return $content; // Bail if nothing to replace.
+		}
+		// Captured a front end request, since the $context will be an int.
+		if ( ! empty( $this->context ) ) {
+			$context = $this->context;
+		}
+		if ( Utils::looks_like_json( $content ) ) {
+			$json_maybe = json_decode( $content, true );
+			if ( ! empty( $json_maybe ) ) {
+				$content = $json_maybe;
+			}
+		}
+		$this->prime_replacements( $content, $context );
+		if ( ! empty( self::$replacements ) ) {
+			$content = self::do_replace( $content );
+		}
+		self::reset();
+		$last_content = ! empty( $json_maybe ) ? wp_json_encode( $content ) : $content;
+
+		return $last_content;
+	}
+
+	/**
+	 * Do string replacements.
+	 *
+	 * @param array|string $content The content to do replacements on.
+	 *
+	 * @return array|string
+	 */
+	public static function do_replace( $content ) {
+
+		if ( self::is_iterable( $content ) ) {
+			foreach ( $content as &$item ) {
+				$item = self::do_replace( $item );
+			}
+		} elseif ( is_string( $content ) ) {
+			$content = str_replace( array_keys( self::$replacements ), array_values( self::$replacements ), $content );
+		}
+
+		return $content;
+	}
+
+	/**
+	 * Check if we are currently saving a REST request (i.e. Gutenberg).
+	 * This is used to prevent double replacements.
+	 *
+	 * @return bool
+	 */
+	public function doing_save() {
+		return static::$doing_save;
+	}
+
+	/**
+	 * Reset internal replacements.
+	 */
+	public static function reset() {
+		self::$replacements = array();
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-sync.php.html b/docs/php_class-sync.php.html new file mode 100644 index 000000000..cd506d926 --- /dev/null +++ b/docs/php_class-sync.php.html @@ -0,0 +1,1430 @@ + + + + + Source: php/class-sync.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-sync.php

+ + + + + + + +
+
+
<?php
+/**
+ * Sync manages all of the sync components for the Cloudinary plugin.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Component\Assets;
+use Cloudinary\Component\Setup;
+use Cloudinary\Settings\Setting;
+use Cloudinary\Sync\Delete_Sync;
+use Cloudinary\Sync\Download_Sync;
+use Cloudinary\Sync\Push_Sync;
+use Cloudinary\Sync\Sync_Queue;
+use Cloudinary\Sync\Unsync;
+use Cloudinary\Sync\Upload_Sync;
+use WP_Error;
+
+/**
+ * Class Sync
+ */
+class Sync implements Setup, Assets {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     Plugin Instance of the global plugin.
+	 */
+	public $plugin;
+
+	/**
+	 * Contains all the different sync components.
+	 *
+	 * @var Delete_Sync[]|Push_Sync[]|Upload_Sync[]|Media[]|Unsync[]|Download_Sync[]
+	 */
+	public $managers;
+
+	/**
+	 * Contains the sync base structure and callbacks.
+	 *
+	 * @var array
+	 */
+	protected $sync_base_struct;
+
+	/**
+	 * Contains the sync types and  callbacks.
+	 *
+	 * @var array
+	 */
+	protected $sync_types;
+
+	/**
+	 * Holds a list of unsynced images to push on end.
+	 *
+	 * @var array
+	 */
+	private $to_sync = array();
+
+	/**
+	 * Holds the settings slug.
+	 *
+	 * @var string
+	 */
+	public $settings_slug = 'sync_media';
+
+	/**
+	 * Holds the sync settings object.
+	 *
+	 * @var Setting
+	 */
+	public $settings;
+
+	/**
+	 * Holds the meta keys for sync meta to maintain consistency.
+	 */
+	const META_KEYS = array(
+		'breakpoints'         => '_cloudinary_breakpoints',
+		'bypass'              => '_bypass_delivery',
+		'cloudinary'          => '_cloudinary',
+		'cloudinary_legacy'   => '_cloudinary_v2',
+		'dashboard_cache'     => '_cloudinary_dashboard_stats',
+		'delay'               => '_cloudinary_sync_delay',
+		'delivery'            => '_cloudinary_delivery',
+		'downloading'         => '_cloudinary_downloading',
+		'file_size'           => '_file_size',
+		'folder_sync'         => '_folder_sync',
+		'last_oversize_check' => '_last_oversize_check',
+		'local_size'          => '_cld_local_size',
+		'pending'             => '_cloudinary_pending',
+		'plugin_version'      => '_plugin_version',
+		'process_log'         => '_cloudinary_process_log',
+		'process_log_legacy'  => '_process_log',
+		'public_id'           => '_public_id',
+		'queued'              => '_cloudinary_sync_queued',
+		'relationship'        => '_cld_relationship',
+		'remote_format'       => '_cld_remote_format',
+		'remote_size'         => '_cld_remote_size',
+		'signature'           => '_sync_signature',
+		'storage'             => '_cloudinary_storage',
+		'suffix'              => '_suffix',
+		'sync_error'          => '_cld_error',
+		'synced'              => '_cld_synced',
+		'syncing'             => '_cloudinary_syncing',
+		'transformation'      => '_transformations',
+		'unsupported'         => '_cld_unsupported',
+		'unsynced'            => '_cld_unsynced',
+		'upgrading'           => '_cloudinary_upgrading',
+		'version'             => '_cloudinary_version',
+		'raw_url'             => '_cloudinary_url',
+		'db_version'          => '_cloudinary_db_version',
+		'debug'               => '_cloudinary_debug',
+	);
+
+	/**
+	 * Holds the Sync Media option key.
+	 */
+	const SYNC_MEDIA = 'cloudinary_sync_media';
+
+	/**
+	 * Push_Sync constructor.
+	 *
+	 * @param Plugin $plugin Global instance of the main plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+		$this->plugin               = $plugin;
+		$this->managers['push']     = new Push_Sync( $this->plugin );
+		$this->managers['upload']   = new Upload_Sync( $this->plugin );
+		$this->managers['download'] = new Download_Sync( $this->plugin );
+		$this->managers['delete']   = new Delete_Sync( $this->plugin );
+		$this->managers['queue']    = new Sync_Queue( $this->plugin );
+		$this->managers['unsync']   = new Unsync( $this->plugin );
+
+		// Register Settings.
+		add_filter( 'cloudinary_admin_pages', array( $this, 'settings' ) );
+	}
+
+	/**
+	 * Setup assets/scripts.
+	 */
+	public function enqueue_assets() {
+		if ( $this->plugin->settings->get_param( 'connected' ) ) {
+			$data = array(
+				'restUrl' => esc_url_raw( Utils::rest_url() ),
+				'nonce'   => wp_create_nonce( 'wp_rest' ),
+			);
+			wp_add_inline_script( 'cloudinary', 'var cloudinaryApi = ' . wp_json_encode( $data ), 'before' );
+		}
+	}
+
+	/**
+	 * Register Assets.
+	 */
+	public function register_assets() {
+		if ( $this->plugin->settings->get_param( 'connected' ) ) {
+			// Setup the sync_base_structure.
+			$this->setup_sync_base_struct();
+			// Setup sync types.
+			$this->setup_sync_types();
+		}
+	}
+
+	/**
+	 * Is the component Active.
+	 */
+	public function is_active() {
+		return $this->settings && $this->settings->has_param( 'is_active' );
+	}
+
+	/**
+	 * Checks if an asset has been synced and up to date.
+	 *
+	 * @param int $attachment_id The attachment id to check.
+	 *
+	 * @return bool
+	 */
+	public function been_synced( $attachment_id ) {
+		$been = false;
+
+		if ( $this->plugin->components['delivery']->is_deliverable( $attachment_id ) ) {
+			$public_id = $this->managers['media']->has_public_id( $attachment_id );
+			$meta      = wp_get_attachment_metadata( $attachment_id, true );
+			$been      = ! empty( $public_id ) || ! empty( $meta['cloudinary'] ); // From v1.
+		}
+
+		return $been;
+	}
+
+	/**
+	 * Checks if an asset is synced and up to date.
+	 *
+	 * @param int  $post_id  The post id to check.
+	 * @param bool $re_check Flag to bypass cache and recheck.
+	 *
+	 * @return bool
+	 */
+	public function is_synced( $post_id, $re_check = false ) {
+		static $synced = array();
+		if ( isset( $synced[ $post_id ] ) && false === $re_check ) {
+			return $synced[ $post_id ];
+		}
+		$return = false;
+		if ( $this->managers['media']->has_public_id( $post_id ) && $this->been_synced( $post_id ) ) {
+			$expecting = $this->generate_signature( $post_id );
+			if ( ! is_wp_error( $expecting ) ) {
+				$signature = $this->get_signature( $post_id );
+				// Sort to align orders for comparison.
+				ksort( $signature );
+				ksort( $expecting );
+				if ( ! empty( $signature ) && ! empty( $expecting ) && $expecting === $signature ) {
+					$return = true;
+				}
+			}
+		}
+		$synced[ $post_id ] = $return;
+
+		return $synced[ $post_id ];
+	}
+
+	/**
+	 * Log a sync result.
+	 *
+	 * @param int    $attachment_id The attachment id.
+	 * @param string $type          The sync type.
+	 * @param mixed  $result        The result.
+	 */
+	public function log_sync_result( $attachment_id, $type, $result ) {
+		$log  = $this->managers['media']->get_process_logs( $attachment_id, true );
+		$keys = $this->sync_base_struct;
+		if ( empty( $log ) || count( $log ) !== count( $keys ) ) {
+			$missing_keys = array_diff_key( $keys, $log );
+			$log          = array_merge(
+				$log,
+				array_fill_keys( array_keys( $missing_keys ), array() )
+			);
+		}
+		if ( isset( $log[ $type ] ) ) {
+			if ( is_wp_error( $result ) ) {
+				$result = array(
+					'code'    => $result->get_error_code(),
+					'message' => $result->get_error_message(),
+				);
+				update_post_meta( $attachment_id, self::META_KEYS['sync_error'], $result['message'] );
+			}
+
+			$log[ $type ][ '_' . time() ] = $result;
+			if ( 5 < count( $log[ $type ] ) ) {
+				array_shift( $log[ $type ] );
+			}
+			update_post_meta( $attachment_id, self::META_KEYS['process_log'], $log );
+		}
+	}
+
+	/**
+	 * Check if sync type is required for rendering a Cloudinary URL.
+	 *
+	 * @param string|null $type          The type to check.
+	 * @param int         $attachment_id The attachment ID.
+	 *
+	 * @return bool
+	 */
+	public function is_required( $type, $attachment_id ) {
+		$return = false;
+		if ( ! empty( $type ) && isset( $this->sync_base_struct[ $type ]['required'] ) ) {
+			if ( is_callable( $this->sync_base_struct[ $type ]['required'] ) ) {
+				$return = call_user_func( $this->sync_base_struct[ $type ]['required'], $attachment_id );
+			} else {
+				$return = $this->sync_base_struct[ $type ]['required'];
+			}
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Generate a signature based on whats required for a full sync.
+	 *
+	 * @param int  $attachment_id The Attachment id to generate a signature for.
+	 * @param bool $cache         Flag to specify if a cached signature is to be used or build a new one.
+	 *
+	 * @return string|bool
+	 */
+	public function generate_signature( $attachment_id, $cache = true ) {
+		static $signatures = array(); // cache signatures.
+		if ( ! empty( $signatures[ $attachment_id ] ) && true === $cache ) {
+			$return = $signatures[ $attachment_id ];
+		} else {
+			$return = $this->sync_base( $attachment_id );
+			// Add to signature cache.
+			$signatures[ $attachment_id ] = $return;
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Check if an asset can be synced.
+	 *
+	 * @param int    $attachment_id The attachment ID to check if it  can be synced.
+	 * @param string $type          The type of sync to attempt.
+	 *
+	 * @return bool
+	 */
+	public function can_sync( $attachment_id, $type = 'file' ) {
+
+		$can = $this->is_auto_sync_enabled();
+
+		if ( $this->is_pending( $attachment_id ) ) {
+			$can = false;
+		} elseif ( $this->been_synced( $attachment_id ) ) {
+			$can = true;
+		}
+
+		if ( ! $this->managers['media']->is_uploadable_media( $attachment_id ) ) {
+			$can = false;
+		}
+
+		// Can sync only syncable delivery types.
+		if ( ! $this->is_syncable( $attachment_id ) ) {
+			$can = false;
+		}
+
+		// Only sync deliverable attachments.
+		if ( $can && ! $this->plugin->get_component( 'delivery' )->is_deliverable( $attachment_id ) ) {
+			$can = false;
+		}
+
+		/**
+		 * Filter to allow changing if an asset is allowed to be synced.
+		 *
+		 * @hook cloudinary_can_sync_asset
+		 *
+		 * @param $can           {bool} Can sync.
+		 * @param $attachment_id {int} The attachment post ID.
+		 * @param $type          {string} The type of sync to attempt.
+		 *
+		 * @return {bool}
+		 */
+		return apply_filters( 'cloudinary_can_sync_asset', $can, $attachment_id, $type );
+	}
+
+	/**
+	 * Get the last version this asset was synced with.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return mixed
+	 */
+	public function get_sync_version( $attachment_id ) {
+		$version = $this->managers['media']->get_post_meta( $attachment_id, self::META_KEYS['plugin_version'], true );
+
+		return $version . '-' . $this->plugin->version;
+	}
+
+	/**
+	 * Get the current sync signature of an asset.
+	 *
+	 * @param int  $attachment_id The attachment ID.
+	 * @param bool $cached        Flag to specify if a cached signature is to be used or build a new one.
+	 *
+	 * @return array
+	 */
+	public function get_signature( $attachment_id, $cached = true ) {
+		static $signatures = array(); // Cache signatures already fetched.
+
+		$return = array();
+		if ( ! empty( $signatures[ $attachment_id ] ) && true === $cached ) {
+			$return = $signatures[ $attachment_id ];
+		} else {
+			$signature = $this->managers['media']->get_post_meta( $attachment_id, self::META_KEYS['signature'], true );
+			if ( empty( $signature ) ) {
+				$signature = array();
+			}
+
+			// Remove any old or outdated signature items. against the expected.
+			$signature                    = array_intersect_key( $signature, $this->sync_types );
+			$signatures[ $attachment_id ] = $return;
+			$return                       = wp_parse_args( $signature, $this->sync_types );
+		}
+
+		/**
+		 * Filter the get signature of the asset.
+		 *
+		 * @hook   cloudinary_get_signature
+		 *
+		 * @param $return        {array} The attachment signature.
+		 * @param $attachment_id {int}   The attachment ID.
+		 *
+		 * @return {array}
+		 */
+		$return = apply_filters( 'cloudinary_get_signature', $return, $attachment_id );
+
+		return $return;
+	}
+
+	/**
+	 * Generate a new Public ID for an asset.
+	 *
+	 * @param int $attachment_id The attachment ID for the new public ID.
+	 *
+	 * @return string|null
+	 */
+	public function generate_public_id( $attachment_id ) {
+
+		$cld_folder = $this->managers['media']->get_cloudinary_folder();
+		if ( function_exists( 'wp_get_original_image_path' ) && wp_attachment_is_image( $attachment_id ) ) {
+			$file = wp_get_original_image_path( $attachment_id );
+		} else {
+			$file = get_attached_file( $attachment_id );
+		}
+		$filename  = Utils::pathinfo( $file, PATHINFO_FILENAME );
+		$public_id = $cld_folder . $filename;
+
+		return ltrim( $public_id, '/' );
+	}
+
+	/**
+	 * Is syncable asset.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return bool
+	 */
+	public function is_syncable( $attachment_id ) {
+		$syncable = false;
+		if (
+			$this->managers['media']->is_media( $attachment_id )
+			&& in_array(
+				$this->managers['media']->get_media_delivery( $attachment_id ),
+				$this->managers['media']->get_syncable_delivery_types(),
+				true
+			)
+		) {
+			$syncable = true;
+		}
+
+		if ( true === $syncable && $this->managers['media']->is_oversize_media( $attachment_id ) ) {
+			$syncable = false;
+		}
+
+		return $syncable;
+	}
+
+	/**
+	 * Register a new sync type.
+	 *
+	 * @param string $type      Sync type key. Must not exceed 20 characters and may
+	 *                          only contain lowercase alphanumeric characters, dashes,
+	 *                          and underscores. See sanitize_key().
+	 * @param array  $structure {
+	 *                          Array of arguments for registering a sync type.
+	 *
+	 * @type   callable      $generate  Callback method that generates the values to be used to sign a state.
+	 *                                    Returns a string or array.
+	 *
+	 * @type   callable      $validate  Optional Callback method that validates the need to have the sync type applied.
+	 *                                    returns Bool.
+	 *
+	 * @type   int           $priority  Priority in which the type takes place. Lower is higher priority.
+	 *                                    i.e a download should happen before an upload so download is lower in the chain.
+	 *
+	 * @type   callable      $sync      Callback method that handles the sync. i.e uploads the file, adds meta data, etc..
+	 *
+	 * @type   string        $state     State class to be added to the status icon in media library.
+	 *
+	 * @type string|callback $note      The status text displayed next to a syncing asset in the media library.
+	 *                                    Can be a callback if the note needs to be dynamic. see type folder.
+	 *
+	 * }
+	 */
+	public function register_sync_type( $type, $structure ) {
+
+		// Apply a default to ensure parts exist.
+		$default = array(
+			'generate'    => '__return_null',
+			'validate'    => null,
+			'priority'    => 50,
+			'sync'        => '__return_null',
+			'state'       => 'sync',
+			'note'        => __( 'Synchronizing asset with Cloudinary', 'cloudinary' ),
+			'asset_state' => 1,
+		);
+
+		$this->sync_base_struct[ $type ] = wp_parse_args( $structure, $default );
+	}
+
+	/**
+	 * Get built-in structures that form an assets entire sync state. This holds methods for building signatures for each state of synchronization.
+	 * These can be extended via 3rd parties by adding to the structures with custom types and generation and sync methods.
+	 */
+	public function setup_sync_base_struct() {
+
+		$base_struct = array(
+			'upgrade'      => array(
+				'generate' => array( $this, 'get_sync_version' ), // Method to generate a signature.
+				'validate' => array( $this, 'been_synced' ),
+				'priority' => 0.2,
+				'sync'     => array( $this->managers['media']->upgrade, 'convert_cloudinary_version' ),
+				'state'    => 'info syncing',
+				'note'     => __( 'Upgrading from previous version', 'cloudinary' ),
+				'realtime' => true,
+			),
+			'download'     => array(
+				'generate' => '__return_false',
+				'validate' => function ( $attachment_id ) {
+					return (bool) $this->managers['media']->get_post_meta( $attachment_id, self::META_KEYS['upgrading'], true );
+				},
+				'priority' => 1,
+				'sync'     => array( $this->managers['download'], 'download_asset' ),
+				'state'    => 'info downloading',
+				'note'     => __( 'Downloading from Cloudinary', 'cloudinary' ),
+			),
+			'file'         => array(
+				'asset_state' => 0,
+				'generate'    => array( $this, 'generate_file_signature' ),
+				'priority'    => 5.1,
+				'sync'        => array( $this->managers['upload'], 'upload_asset' ),
+				'validate'    => function ( $attachment_id ) {
+					$valid = ! $this->managers['media']->has_public_id( $attachment_id ) && ! $this->managers['media']->is_oversize_media( $attachment_id );
+					if ( $valid ) {
+						$valid = $this->plugin->get_component( 'delivery' )->is_deliverable( $attachment_id );
+					}
+					return $valid;
+				},
+				'state'       => 'uploading',
+				'note'        => __( 'Uploading to Cloudinary', 'cloudinary' ),
+				'required'    => true, // Required to complete URL render flag.
+			),
+			'edit'         => array(
+				'generate' => array( $this, 'generate_edit_signature' ),
+				'priority' => 5.2,
+				'sync'     => array( $this->managers['upload'], 'edit_upload' ),
+				'validate' => function ( $attachment_id ) {
+					$valid  = false;
+					$backup = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
+					if ( ! empty( $backup ) ) {
+						$valid = true;
+					}
+
+					return $valid;
+				},
+				'state'    => 'uploading',
+				'note'     => __( 'Uploading to Cloudinary', 'cloudinary' ),
+				'required' => false, // Required to complete URL render flag.
+				'realtime' => true,
+			),
+			'folder'       => array(
+				'generate' => array( $this->managers['media'], 'get_cloudinary_folder' ),
+				'validate' => array( $this->managers['media'], 'is_folder_synced' ),
+				'priority' => 10,
+				'sync'     => array( $this->managers['upload'], 'upload_asset' ),
+				'state'    => 'info syncing',
+				'note'     => function () {
+					return sprintf(
+					/* translators: %s folder name */
+						__( 'Copying to folder %s.', 'cloudinary' ),
+						untrailingslashit( $this->managers['media']->get_cloudinary_folder() )
+					);
+				},
+			),
+			'public_id'    => array(
+				'generate' => array( $this->managers['media'], 'get_public_id' ),
+				'validate' => function ( $attachment_id ) {
+					$public_id = $this->managers['media']->has_public_id( $attachment_id );
+
+					return false === $public_id;
+				},
+				'priority' => 20,
+				'sync'     => array( $this->managers['media']->upgrade, 'convert_cloudinary_version' ), // Rename.
+				'state'    => 'info syncing',
+				'note'     => __( 'Updating metadata', 'cloudinary' ),
+				'required' => true,
+			),
+			'breakpoints'  => array(
+				'generate' => array( $this->managers['media'], 'get_breakpoint_options' ),
+				'priority' => 25,
+				'sync'     => array( $this->managers['upload'], 'explicit_update' ),
+				'validate' => function ( $attachment_id ) {
+					$delivery = $this->managers['media']->get_post_meta( $attachment_id, self::META_KEYS['delivery'], true );
+
+					return empty( $delivery ) || 'upload' === $delivery;
+				},
+				'state'    => 'info syncing',
+				'note'     => __( 'Updating breakpoints', 'cloudinary' ),
+			),
+			'options'      => array(
+				'asset_state' => 0,
+				'generate'    => function ( $attachment_id ) {
+					$options = $this->managers['media']->get_upload_options( $attachment_id );
+					unset( $options['eager'], $options['eager_async'] );
+
+					return $options;
+				},
+				'validate'    => array( $this, 'been_synced' ),
+				'priority'    => 30,
+				'sync'        => array( $this->managers['upload'], 'context_update' ),
+				'state'       => 'info syncing',
+				'note'        => __( 'Updating metadata', 'cloudinary' ),
+			),
+			'cloud_name'   => array(
+				'generate' => array( $this->managers['connect'], 'get_cloud_name' ),
+				'validate' => function ( $attachment_id ) {
+
+					$valid       = true;
+					$credentials = $this->managers['connect']->get_credentials();
+					if ( isset( $credentials['cname'] ) ) {
+						$url = get_post_meta( $attachment_id, '_wp_attached_file', true );
+						if ( wp_http_validate_url( $url ) ) {
+							$domain = wp_parse_url( $url, PHP_URL_HOST );
+							$valid  = $domain !== $credentials['cname'];
+						}
+					}
+
+					return $valid;
+				},
+				'priority' => 5.5,
+				'sync'     => array( $this->managers['upload'], 'upload_asset' ),
+				'state'    => 'uploading',
+				'note'     => __( 'Uploading to new cloud name.', 'cloudinary' ),
+				'required' => true,
+			),
+			'meta_cleanup' => array(
+				'generate' => function ( $attachment_id ) {
+					$meta = $this->managers['media']->get_post_meta( $attachment_id );
+
+					$return = false;
+					foreach ( $meta as $key => $value ) {
+						if ( get_post_meta( $attachment_id, $key, true ) === $value ) {
+							$return = true;
+							break;
+						}
+					}
+
+					return $return;
+				},
+				'priority' => 100, // Always be the highest.
+				'sync'     => function ( $attachment_id ) {
+					$meta         = $this->managers['media']->get_post_meta( $attachment_id );
+					$cleanup_keys = array(
+						self::META_KEYS['cloudinary'],
+						self::META_KEYS['upgrading'],
+					);
+					foreach ( $meta as $key => $value ) {
+						if ( in_array( $key, $cleanup_keys, true ) ) {
+							$this->managers['media']->delete_post_meta( $attachment_id, $key );
+							continue;
+						}
+						delete_post_meta( $attachment_id, $key, $value );
+					}
+					$this->set_signature_item( $attachment_id, 'meta_cleanup' );
+				},
+				'required' => true,
+				'realtime' => true,
+			),
+		);
+
+		/**
+		 * Filter the sync base structure to allow other plugins to sync component callbacks.
+		 *
+		 * @hook cloudinary_sync_base_struct
+		 *
+		 * @param $base_struct {array} The base sync structure.
+		 *
+		 * @return {array}
+		 */
+		$base_struct = apply_filters( 'cloudinary_sync_base_struct', $base_struct );
+
+		// Register each sync type.
+		foreach ( $base_struct as $type => $structure ) {
+			$this->register_sync_type( $type, $structure );
+		}
+
+		/**
+		 * Do action for setting up sync types.
+		 *
+		 * @hook cloudinary_register_sync_types
+		 *
+		 * @param $this {Sync} The sync object.
+		 */
+		do_action( 'cloudinary_register_sync_types', $this );
+	}
+
+	/**
+	 * Setup the sync types in priority order based on sync struct.
+	 */
+	public function setup_sync_types() {
+
+		$sync_types = array();
+		foreach ( $this->sync_base_struct as $type => $struct ) {
+			if ( is_callable( $struct['sync'] ) ) {
+				$sync_types[ $type ] = floatval( $struct['priority'] );
+			}
+		}
+
+		asort( $sync_types );
+
+		$this->sync_types = $sync_types;
+	}
+
+	/**
+	 * Get a method from a sync type.
+	 *
+	 * @param string $type   The sync type to get from.
+	 * @param string $method The method to get from the sync type.
+	 *
+	 * @return callable|null
+	 */
+	public function get_sync_type_method( $type, $method ) {
+		$return = null;
+		if ( isset( $this->sync_base_struct[ $type ][ $method ] ) && is_callable( $this->sync_base_struct[ $type ][ $method ] ) ) {
+			$return = $this->sync_base_struct[ $type ][ $method ];
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Run a sync method on and attachment_id.
+	 *
+	 * @param string $type          The sync type to run.
+	 * @param string $method        The method to run.
+	 * @param int    $attachment_id The attachment ID to run method against.
+	 *
+	 * @return mixed
+	 */
+	public function run_sync_method( $type, $method, $attachment_id ) {
+		$return     = null;
+		$run_method = $this->get_sync_type_method( $type, $method );
+		if ( $run_method ) {
+			$return = call_user_func( $run_method, $attachment_id );
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Generate a single sync type signature for an asset.
+	 *
+	 * @param string $type          The sync type to run.
+	 * @param int    $attachment_id The attachment ID to run method against.
+	 *
+	 * @return mixed
+	 */
+	public function generate_type_signature( $type, $attachment_id ) {
+		$return     = null;
+		$run_method = $this->get_sync_type_method( $type, 'generate' );
+		if ( $run_method ) {
+			$value = call_user_func( $run_method, $attachment_id );
+			if ( ! is_wp_error( $value ) ) {
+				if ( is_array( $value ) ) {
+					$value = wp_json_encode( $value );
+				}
+				$return = md5( $value );
+			}
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Get the asset state ( sync level ) of an attachment.
+	 *
+	 * @param int $attachment_id The attachment ID to get.
+	 *
+	 * @return int
+	 */
+	public function get_asset_state( $attachment_id ) {
+
+		$state = $this->been_synced( $attachment_id ) ? 1 : 0;
+
+		/**
+		 * Filter the state of the asset.
+		 *
+		 * @hook   cloudinary_asset_state
+		 *
+		 * @param $state         {int} The attachment state.
+		 * @param $attachment_id {int}   The attachment ID.
+		 *
+		 * @return {array}
+		 */
+		return apply_filters( 'cloudinary_asset_state', $state, $attachment_id );
+	}
+
+	/**
+	 * Prepares and asset for sync comparison by getting all sync types
+	 * and running the generate methods for each type.
+	 *
+	 * @param int $attachment_id Attachment ID to prepare.
+	 *
+	 * @return array
+	 */
+	public function sync_base( $attachment_id ) {
+
+		$return      = array();
+		$asset_state = $this->get_asset_state( $attachment_id );
+		foreach ( array_keys( $this->sync_types ) as $type ) {
+			if ( $asset_state >= $this->sync_base_struct[ $type ]['asset_state'] ) {
+				$return[ $type ] = $this->generate_type_signature( $type, $attachment_id );
+			}
+		}
+
+		/**
+		 * Filter the sync base to allow other plugins to add requested sync components for the sync signature.
+		 *
+		 * @hook   cloudinary_sync_base
+		 *
+		 * @param $signatures {array}   The attachments required signatures.
+		 * @param $post       {WP_Post} The attachment post.
+		 * @param $sync       {Sync}    The sync object instance.
+		 *
+		 * @return {array}
+		 */
+		$return = apply_filters( 'cloudinary_sync_base', $return, get_post( $attachment_id ), $this );
+
+		return $return;
+	}
+
+	/**
+	 * Prepare an asset to be synced, maybe.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return string | null
+	 */
+	public function maybe_prepare_sync( $attachment_id ) {
+		$type = $this->get_sync_type( $attachment_id );
+		$can  = $this->can_sync( $attachment_id, $type );
+		if ( $type && true === $can ) {
+			$this->add_to_sync( $attachment_id );
+		}
+
+		return $type;
+	}
+
+	/**
+	 * Get the type of sync, with the lowest priority for this asset.
+	 *
+	 * @param int  $attachment_id The attachment ID.
+	 * @param bool $cached        Flag to specify if a cached signature is to be used or build a new one.
+	 *
+	 * @return string|null
+	 */
+	public function get_sync_type( $attachment_id, $cached = true ) {
+		if ( ! $this->managers['media']->is_media( $attachment_id ) || ! empty( get_post_meta( $attachment_id, self::META_KEYS['sync_error'], true ) ) ) {
+			return null; // Ignore non media items or errored syncs.
+		}
+		$return               = null;
+		$required_signature   = $this->generate_signature( $attachment_id, $cached );
+		$attachment_signature = $this->get_signature( $attachment_id, $cached );
+		$attachment_signature = array_intersect_key( $attachment_signature, $required_signature );
+		if ( is_array( $required_signature ) ) {
+			$sync_items = array_filter(
+				$attachment_signature,
+				function ( $item, $key ) use ( $required_signature ) {
+					return $item !== $required_signature[ $key ];
+				},
+				ARRAY_FILTER_USE_BOTH
+			);
+			$ordered    = array_intersect_key( $this->sync_types, $sync_items );
+			if ( ! empty( $ordered ) ) {
+				$types  = array_keys( $ordered );
+				$type   = array_shift( $types );
+				$return = $this->validate_sync_type( $type, $attachment_id );
+			}
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Validate the asset needs the sync type to be run, and generate a valid signature if not.
+	 *
+	 * @param string $type          The sync type to validate.
+	 * @param int    $attachment_id The attachment ID to validate against.
+	 *
+	 * @return string|null
+	 */
+	public function validate_sync_type( $type, $attachment_id ) {
+		// Validate that this sync type applied (for optional types like upgrade).
+		if ( false === $this->run_sync_method( $type, 'validate', $attachment_id ) ) {
+			// If invalid, save the new signature.
+			$this->set_signature_item( $attachment_id, $type );
+
+			$type = $this->get_sync_type( $attachment_id, false ); // Set cache to false to get the new signature.
+
+			// Check if this is a realtime process.
+		} elseif ( ! empty( $this->sync_base_struct[ $type ]['realtime'] ) ) {
+			$result = $this->run_sync_method( $type, 'sync', $attachment_id );
+			if ( ! empty( $result ) ) {
+				$this->log_sync_result( $attachment_id, $type, $result );
+			}
+			$type = $this->get_sync_type( $attachment_id, false ); // Set cache to false to get the new signature.
+		}
+
+		return $type;
+	}
+
+	/**
+	 * Checks the status of the media item.
+	 *
+	 * @param array $status        Array of state and note.
+	 * @param int   $attachment_id The attachment id.
+	 *
+	 * @return array
+	 */
+	public function filter_status( $status, $attachment_id ) {
+
+		if ( $this->been_synced( $attachment_id ) || ( $this->is_pending( $attachment_id ) && $this->get_sync_type( $attachment_id ) ) ) {
+			$sync_type = $this->get_sync_type( $attachment_id );
+			if ( ! empty( $sync_type ) && isset( $this->sync_base_struct[ $sync_type ] ) ) {
+				// check process log in case theres an error.
+				$log = $this->managers['media']->get_process_logs( $attachment_id );
+				if ( ! empty( $log[ $sync_type ] ) && is_wp_error( $log[ $sync_type ] ) ) {
+					// Use error instead of sync note.
+					$status['state'] = 'error';
+					$status['note']  = $log[ $sync_type ]->get_error_message();
+				} else {
+					$status['state'] = $this->sync_base_struct[ $sync_type ]['state'];
+					$status['note']  = $this->sync_base_struct[ $sync_type ]['note'];
+					if ( is_callable( $status['note'] ) ) {
+						$status['note'] = call_user_func( $status['note'], $attachment_id );
+					}
+				}
+			}
+
+			// Check if there's an error.
+			$has_error = get_post_meta( $attachment_id, self::META_KEYS['sync_error'], true );
+			if ( ! empty( $has_error ) && $this->get_sync_type( $attachment_id ) ) {
+				$status['state'] = 'error';
+				$status['note']  = $has_error;
+			}
+		}
+
+		return $status;
+	}
+
+	/**
+	 * Add media state to display syncing info.
+	 *
+	 * @param array    $media_states List of the states.
+	 * @param \WP_Post $post         The current attachment post.
+	 *
+	 * @return array
+	 */
+	public function filter_media_states( $media_states, $post ) {
+
+		/**
+		 * Filter the Cloudinary media status.
+		 *
+		 * @hook cloudinary_media_status
+		 * @default array()
+		 *
+		 * @param $status  {array} The media status.
+		 * @param $post_id {int} The Post ID.
+		 *
+		 * @return {array}
+		 */
+		$status = apply_filters( 'cloudinary_media_status', array(), $post->ID );
+		if ( ! empty( $status ) ) {
+			$media_states[] = $status['note'];
+		}
+
+		return $media_states;
+	}
+
+	/**
+	 * Check if the attachment is pending an upload sync.
+	 *
+	 * @param int $attachment_id The attachment ID to check.
+	 *
+	 * @return bool
+	 */
+	public function is_pending( $attachment_id ) {
+		// Check if it's not already in the to sync array.
+		if ( ! in_array( $attachment_id, $this->to_sync, true ) ) {
+			$is_pending = get_post_meta( $attachment_id, self::META_KEYS['pending'], true );
+			if ( empty( $is_pending ) || $is_pending < time() - 5 * 60 ) {
+				// No need to delete pending meta, since it will be updated with the new timestamp anyway.
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * Set an attachment as pending.
+	 *
+	 * @param int $attachment_id The attachment ID to set as pending.
+	 */
+	public function set_pending( $attachment_id ) {
+		update_post_meta( $attachment_id, self::META_KEYS['pending'], time() );
+	}
+
+	/**
+	 * Add an attachment ID to the to_sync array.
+	 *
+	 * @param int $attachment_id The attachment ID to add.
+	 */
+	public function add_to_sync( $attachment_id ) {
+		if ( ! in_array( (int) $attachment_id, $this->to_sync, true ) ) {
+
+			// There are cases where we do not check can_sync. This is to make sure we don't add to the to_sync array if we can't sync.
+			if ( ! $this->plugin->get_component( 'delivery' )->is_deliverable( $attachment_id ) ) {
+				return;
+			}
+
+			// Flag image as pending to prevent duplicate upload.
+			$this->set_pending( $attachment_id );
+			$this->to_sync[] = $attachment_id;
+		}
+	}
+
+	/**
+	 * Update signatures of types that match the specified types sync method. This prevents running the same method repeatedly.
+	 *
+	 * @param int    $attachment_id The attachment ID.
+	 * @param string $type          The type of sync.
+	 */
+	public function sync_signature_by_type( $attachment_id, $type ) {
+		$current_sync_method = $this->sync_base_struct[ $type ]['sync'];
+
+		// Go over all other types that share the same sync method and include them here.
+		foreach ( $this->sync_base_struct as $sync_type => $struct ) {
+			if ( $struct['sync'] === $current_sync_method ) {
+				$this->set_signature_item( $attachment_id, $sync_type );
+			}
+		}
+	}
+
+	/**
+	 * Set an item to the signature set.
+	 *
+	 * @param int    $attachment_id The attachment ID.
+	 * @param string $type          The sync type.
+	 * @param null   $value         The value.
+	 */
+	public function set_signature_item( $attachment_id, $type, $value = null ) {
+
+		// Get the core meta.
+		$meta = $this->managers['media']->get_post_meta( $attachment_id, self::META_KEYS['signature'], true );
+		if ( empty( $meta ) ) {
+			$meta = array();
+		}
+		// Set the specific value.
+		if ( is_null( $value ) ) {
+			// Generate a new value based on generator.
+			$value = $this->generate_type_signature( $type, $attachment_id );
+		}
+		$meta[ $type ] = $value;
+		$this->managers['media']->update_post_meta( $attachment_id, self::META_KEYS['signature'], $meta );
+	}
+
+	/**
+	 * Initialize the background sync on requested images needing to be synced.
+	 */
+	public function init_background_upload() {
+		if ( ! empty( $this->to_sync ) ) {
+			$this->managers['queue']->add_to_queue( $this->to_sync, 'autosync' );
+			$this->managers['queue']->start_threads( 'autosync' );
+		}
+	}
+
+	/**
+	 * Filter the Cloudinary Folder.
+	 *
+	 * @param string $value The set folder.
+	 * @param string $slug  The setting slug.
+	 *
+	 * @return string
+	 */
+	public function filter_get_cloudinary_folder( $value, $slug ) {
+		if ( '.' === $value && 'cloudinary_folder' === $slug ) {
+			$value = '';
+		}
+
+		return $value;
+	}
+
+	/**
+	 * Filter the signature.
+	 *
+	 * @param array $signature     The signature array.
+	 * @param int   $attachment_id The attachment ID.
+	 *
+	 * @return array|bool|string|WP_Error
+	 */
+	public function get_signature_syncable_type( $signature, $attachment_id ) {
+
+		if ( ! $this->is_syncable( $attachment_id ) ) {
+			$signature = $this->generate_signature( $attachment_id );
+		}
+
+		return $signature;
+	}
+
+	/**
+	 * Cleanup errored syncs.
+	 */
+	public function maybe_cleanup_errored() {
+		if ( 'cloudinary' !== Utils::get_sanitized_text( 'page' ) ) {
+			return;
+		}
+
+		if ( 'cleaned_up' === Utils::get_sanitized_text( 'action' ) ) {
+			$this->plugin->get_component( 'admin' )->add_admin_notice( 'error_notice', __( 'Sync errors cleaned up', 'cloudinary' ), 'success' );
+		}
+
+		if ( 'clean_up' !== Utils::get_sanitized_text( 'action' ) ) {
+			return;
+		}
+
+		if ( ! wp_verify_nonce( Utils::get_sanitized_text( 'nonce' ), 'clean_up' ) ) {
+			return;
+		}
+
+		if ( ! current_user_can( 'manage_options' ) ) {
+			return;
+		}
+
+		delete_post_meta_by_key( self::META_KEYS['sync_error'] );
+
+		wp_redirect(
+			add_query_arg(
+				array(
+					'page'    => 'cloudinary',
+					'action'  => 'cleaned_up',
+				),
+				admin_url( 'admin.php' )
+			)
+		);
+
+		exit;
+	}
+
+	/**
+	 * Checks if auto sync feature is enabled.
+	 *
+	 * @return bool
+	 */
+	public function is_auto_sync_enabled() {
+
+		if ( 'on' === $this->plugin->settings->get_value( 'auto_sync' ) ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Delete Cloudinary meta for the attachment ID.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 */
+	public function delete_cloudinary_meta( $attachment_id ) {
+		// Update attachment meta.
+		$meta = wp_get_attachment_metadata( $attachment_id, true );
+		if ( ! empty( $meta[ self::META_KEYS['cloudinary'] ] ) ) {
+			unset( $meta[ self::META_KEYS['cloudinary'] ] );
+		}
+		wp_update_attachment_metadata( $attachment_id, $meta );
+
+		// Cleanup postmeta.
+		$queued = get_post_meta( $attachment_id, self::META_KEYS['queued'] );
+		delete_post_meta( $attachment_id, self::META_KEYS['sync_error'] );
+		delete_post_meta( $attachment_id, self::META_KEYS['pending'] );
+		delete_post_meta( $attachment_id, self::META_KEYS['queued'] );
+		delete_post_meta( $attachment_id, self::META_KEYS['suffix'] );
+		delete_post_meta( $attachment_id, self::META_KEYS['public_id'] );
+		delete_post_meta( $attachment_id, $queued );
+
+		// Signatures cleanup.
+		$signatures = $this->get_signature( $attachment_id );
+		foreach ( $signatures as $signature ) {
+			delete_post_meta( $attachment_id, "_{$signature}" );
+		}
+
+		// Delete main meta.
+		delete_post_meta( $attachment_id, self::META_KEYS['cloudinary'] );
+	}
+
+	/**
+	 * Register the `is_cloudinary_synced` rest field.
+	 *
+	 * @return void
+	 */
+	public function rest_api_is_synced_field() {
+		register_rest_field(
+			'attachment',
+			'is_cloudinary_synced',
+			array(
+				'get_callback' => function ( $attachment ) {
+					return $this->is_synced( $attachment['id'] );
+				},
+				'schema'       => array(
+					'description' => __( 'Is Cloudinary synced.', 'cloudinary' ),
+					'type'        => 'bool',
+				),
+			)
+		);
+	}
+
+	/**
+	 * Additional component setup.
+	 */
+	public function setup() {
+
+		if ( $this->plugin->settings->get_param( 'connected' ) ) {
+
+			// Show sync status.
+			add_filter( 'cloudinary_media_status', array( $this, 'filter_status' ), 10, 2 );
+			add_filter( 'display_media_states', array( $this, 'filter_media_states' ), 10, 2 );
+			// Hook for on demand upload push.
+			add_action( 'shutdown', array( $this, 'init_background_upload' ) );
+
+			$this->managers['upload']->setup();
+			$this->managers['delete']->setup();
+			$this->managers['download']->setup();
+			$this->managers['push']->setup();
+			$this->managers['unsync']->setup();
+			// Setup additional components.
+			$this->managers['media']   = $this->plugin->components['media'];
+			$this->managers['connect'] = $this->plugin->components['connect'];
+			$this->managers['api']     = $this->plugin->components['api'];
+
+			// Setup sync queue.
+			$this->managers['queue']->setup( $this );
+
+			add_filter( 'cloudinary_setting_get_value', array( $this, 'filter_get_cloudinary_folder' ), 10, 2 );
+			add_filter( 'cloudinary_get_signature', array( $this, 'get_signature_syncable_type' ), 10, 2 );
+
+			add_action( 'admin_init', array( $this, 'maybe_cleanup_errored' ), 10, 2 );
+
+			add_action( 'rest_api_init', array( $this, 'rest_api_is_synced_field' ) );
+		}
+	}
+
+	/**
+	 * Define the settings on the general settings page.
+	 *
+	 * @param array $pages The pages to add to.
+	 *
+	 * @return array
+	 */
+	public function settings( $pages ) {
+
+		$pages['connect']['settings'][] = array(
+			array(
+				'type'                => 'frame',
+				'requires_connection' => true,
+				array(
+					'type'        => 'panel',
+					'title'       => __( 'Media Library Sync Settings', 'cloudinary' ),
+					'option_name' => 'sync_media',
+					'collapsible' => 'open',
+					array(
+						'type'         => 'radio',
+						'title'        => __( 'Sync method', 'cloudinary' ),
+						'tooltip_text' => sprintf(
+							// translators: The HTML break line, the link to Cloudinary documentation and closing tag.
+							__( 'Defines how your WordPress assets sync with Cloudinary.%1$s“Auto” will sync assets automatically.%2$s“Manual” requires triggering via the WordPress Media Library.%3$s%4$sLearn more%5$s', 'cloudinary' ),
+							'<ul><li>',
+							'</li><li>',
+							'</li></ul>',
+							'<a href="https://cloudinary.com/documentation/wordpress_integration#syncing_media" target="_blank" rel="noopener noreferrer">',
+							'</a>'
+						),
+						'slug'         => 'auto_sync',
+						'no_cached'    => true,
+						'default'      => 'on',
+						'options'      => array(
+							'on'  => __( 'Auto sync', 'cloudinary' ),
+							'off' => __( 'Manual sync', 'cloudinary' ),
+						),
+					),
+					array(
+						'type'              => 'text',
+						'slug'              => 'cloudinary_folder',
+						'title'             => __( 'Cloudinary folder path', 'cloudinary' ),
+						'default'           => '',
+						'attributes'        => array(
+							'input' => array(
+								'placeholder' => __( 'e.g.: wordpress_assets/', 'cloudinary' ),
+							),
+						),
+						'tooltip_text'      => __(
+							'The folder in your Cloudinary account that WordPress assets are uploaded to. Leave blank to use the root of your Cloudinary library.',
+							'cloudinary'
+						),
+						'sanitize_callback' => array( '\Cloudinary\Media', 'sanitize_cloudinary_folder' ),
+					),
+					array(
+						'type'         => 'select',
+						'slug'         => 'offload',
+						'title'        => __( 'Storage', 'cloudinary' ),
+						'tooltip_text' => sprintf(
+							// translators: the HTML for opening and closing list and its items.
+							__(
+								'Choose where your assets are stored.%1$sCloudinary and WordPress: Stores assets in both locations. Enables local WordPress delivery if the Cloudinary plugin is disabled or uninstalled.%2$sCloudinary and WordPress (low resolution):  Stores original assets in Cloudinary and low resolution versions in WordPress. Enables low resolution local WordPress delivery if the plugin is disabled or uninstalled.%3$sCloudinary only: Stores assets in Cloudinary only. Requires additional steps to enable backwards compatibility.%4$s%5$sLearn more%6$s',
+								'cloudinary'
+							),
+							'<ul><li class="dual_full">',
+							'</li><li class="dual_low">',
+							'</li><li class="cld">',
+							'</li></ul>',
+							'<a href="https://cloudinary.com/documentation/wordpress_integration#storage" target="_blank" rel="noopener noreferrer">',
+							'</a>'
+						),
+						'default'      => 'dual_full',
+						'options'      => array(
+							'dual_full' => __( 'Cloudinary and WordPress', 'cloudinary' ),
+							'dual_low'  => __( 'Cloudinary and WordPress (low resolution)', 'cloudinary' ),
+							'cld'       => __( 'Cloudinary only', 'cloudinary' ),
+						),
+					),
+					array(
+						'type'  => 'info_box',
+						'icon'  => $this->plugin->dir_url . 'css/images/academy-icon.svg',
+						'title' => __( 'Need help?', 'cloudinary' ),
+						'text'  => sprintf(
+							// Translators: The HTML for opening and closing link tags.
+							__(
+								'Watch free lessons on how to use the Media Library Sync Settings in the %1$sCloudinary Academy%2$s.',
+								'cloudinary'
+							),
+							'<a href="https://training.cloudinary.com/learn/course/introduction-to-cloudinary-for-wordpress-administrators-70-minute-course-1h85/lessons/uploading-and-syncing-cloudinary-assets-1624?page=1" target="_blank" rel="noopener noreferrer">',
+							'</a>'
+						),
+					),
+				),
+			),
+		);
+
+		return $pages;
+	}
+
+	/**
+	 * Generate the real file attachment path for the file sync type signature.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public function generate_file_signature( $attachment_id ) {
+		$path = get_attached_file( $attachment_id );
+		$str  = ! $this->managers['media']->is_oversize_media( $attachment_id ) ? wp_basename( $path ) : $attachment_id;
+
+		return $str . $this->is_auto_sync_enabled();
+	}
+
+	/**
+	 * Generate a signature for a file edit.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public function generate_edit_signature( $attachment_id ) {
+		$backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
+		$path         = get_attached_file( $attachment_id );
+		$str          = wp_basename( $path );
+		if ( ! empty( $backup_sizes ) ) {
+			$str .= wp_json_encode( $backup_sizes );
+		}
+
+		return $str;
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_class-utils.php.html b/docs/php_class-utils.php.html new file mode 100644 index 000000000..7bb61c58c --- /dev/null +++ b/docs/php_class-utils.php.html @@ -0,0 +1,1363 @@ + + + + + Source: php/class-utils.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/class-utils.php

+ + + + + + + +
+
+
<?php
+/**
+ * Utilities for Cloudinary.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary;
+
+use Cloudinary\Misc\Image_Sizes_No_Textdomain;
+use Cloudinary\Settings\Setting;
+use Google\Web_Stories\Story_Post_Type;
+use WP_Post;
+
+/**
+ * Class that includes utility methods.
+ *
+ * @package Cloudinary
+ */
+class Utils {
+
+	/**
+	 * Holds a list of temp files to be purged.
+	 *
+	 * @var array
+	 */
+	public static $file_fragments = array();
+
+	const METADATA = array(
+		'actions' => array(
+			'add_{object}_metadata',
+			'update_{object}_metadata',
+		),
+		'objects' => array(
+			'post',
+			'term',
+			'user',
+		),
+	);
+
+	/**
+	 * Filter an array recursively
+	 *
+	 * @param array         $input    The array to filter.
+	 * @param callable|null $callback The callback to run for filtering.
+	 *
+	 * @return array
+	 */
+	public static function array_filter_recursive( array $input, $callback = null ) {
+		// PHP array_filter does this, so we'll do it too.
+		if ( null === $callback ) {
+			$callback = static function ( $item ) {
+				return ! empty( $item );
+			};
+		}
+
+		foreach ( $input as &$value ) {
+			if ( is_array( $value ) ) {
+				$value = self::array_filter_recursive( $value, $callback );
+			}
+		}
+
+		return array_filter( $input, $callback );
+	}
+
+	/**
+	 * Gets the active child setting.
+	 *
+	 * @return Setting
+	 */
+	public static function get_active_setting() {
+		$settings = get_plugin_instance()->settings;
+		$active   = null;
+		if ( $settings->has_param( 'active_setting' ) ) {
+			$active = $settings->get_setting( $settings->get_param( 'active_setting' ) );
+		}
+
+		return $active;
+	}
+
+	/**
+	 * Detects array keys with dot notation and expands them to form a new multi-dimensional array.
+	 *
+	 * @param array  $input     The array that will be processed.
+	 * @param string $separator Separator string.
+	 *
+	 * @return array
+	 */
+	public static function expand_dot_notation( array $input, $separator = '.' ) {
+		$result = array();
+		foreach ( $input as $key => $value ) {
+			if ( is_array( $value ) ) {
+				$value = self::expand_dot_notation( $value );
+			}
+
+			foreach ( array_reverse( explode( $separator, $key ) ) as $inner_key ) {
+				$value = array( $inner_key => $value );
+			}
+
+			// phpcs:ignore
+			/** @noinspection SlowArrayOperationsInLoopInspection */
+			$result = array_merge_recursive( $result, $value );
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Check whether the inputted HTML string is powered by AMP, or if the request is an amp page.
+	 * Reference on how to detect an AMP page: https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/?format=websites#ampd.
+	 *
+	 * @param string|null $html_string Optional: The specific HTML string to check.
+	 *
+	 * @return bool
+	 */
+	public static function is_amp( $html_string = null ) {
+		if ( ! empty( $html_string ) ) {
+			return preg_match( '/<html.+(amp|⚡)+[^>]/', substr( $html_string, 0, 200 ), $found );
+		}
+		$is_amp = false;
+		if ( function_exists( 'amp_is_request' ) ) {
+			$is_amp = amp_is_request();
+		}
+
+		return $is_amp;
+	}
+
+	/**
+	 * Check whether the inputted post type is a webstory.
+	 *
+	 * @param string $post_type The post type to compare to.
+	 *
+	 * @return bool
+	 */
+	public static function is_webstory_post_type( $post_type ) {
+		return class_exists( Story_Post_Type::class ) && Story_Post_Type::POST_TYPE_SLUG === $post_type;
+	}
+
+	/**
+	 * Get all the attributes from an HTML tag.
+	 *
+	 * @param string $tag HTML tag to get attributes from.
+	 *
+	 * @return array
+	 */
+	public static function get_tag_attributes( $tag ) {
+		$tag    = strstr( $tag, ' ', false );
+		$tag    = trim( $tag, '> ' );
+		$args   = shortcode_parse_atts( $tag );
+		$return = array();
+		foreach ( $args as $key => $value ) {
+			if ( is_int( $key ) ) {
+				$return[ $value ] = 'true';
+				continue;
+			}
+			$return[ $key ] = $value;
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Get the depth of an array.
+	 *
+	 * @param array $data The array to check.
+	 *
+	 * @return int
+	 */
+	public static function array_depth( array $data ) {
+		$depth = 0;
+
+		foreach ( $data as $value ) {
+			if ( is_array( $value ) ) {
+				$level = self::array_depth( $value ) + 1;
+
+				if ( $level > $depth ) {
+					$depth = $level;
+				}
+			}
+		}
+
+		return $depth;
+	}
+
+	/**
+	 * Check if the current user can perform a task.
+	 *
+	 * @param string $task       The task to check.
+	 * @param string $capability The default capability.
+	 * @param string $context    The context for the task.
+	 * @param mixed  ...$args    Optional further parameters.
+	 *
+	 * @return bool
+	 */
+	public static function user_can( $task, $capability = 'manage_options', $context = '', ...$args ) {
+
+		// phpcs:disable WordPress.WhiteSpace.DisallowInlineTabs.NonIndentTabsUsed
+		/**
+		 * Filter the capability required for a specific Cloudinary task.
+		 *
+		 * @hook    cloudinary_task_capability_{task}
+		 * @since   2.7.6. In 3.0.6 $context and $args added.
+		 *
+		 * @example
+		 * <?php
+		 *
+		 * // Enforce `manage_options` to download an asset from Cloudinary.
+		 * add_filter(
+		 *     'cloudinary_task_capability_manage_assets',
+		 *     function( $task, $context ) {
+		 *         if ( 'download' === $context ) {
+		 *             $capability = 'manage_options';
+		 *         }
+		 *         return $capability;
+		 *     },
+		 *     10,
+		 *     2
+		 * );
+		 *
+		 * @param $capability {string} The capability.
+		 * @param $context    {string} The context for the task.
+		 * @param $args       {mixed}  The optional arguments.
+		 *
+		 * @default 'manage_options'
+		 * @return  {string}
+		 */
+		$capability = apply_filters( "cloudinary_task_capability_{$task}", $capability, $context, ...$args );
+
+		/**
+		 * Filter the capability required for Cloudinary tasks.
+		 *
+		 * @hook    cloudinary_task_capability
+		 * @since   2.7.6. In 3.0.6 $context and $args added.
+		 *
+		 * @example
+		 * <?php
+		 *
+		 * // Enforce `manage_options` to download an asset from Cloudinary.
+		 * add_filter(
+		 *     'cloudinary_task_capability',
+		 *     function( $capability, $task, $context ) {
+		 *         if ( 'manage_assets' === $task && 'download' === $context ) {
+		 *             $capability = 'manage_options';
+		 *         }
+		 *         return $capability;
+		 *     },
+		 *     10,
+		 *     3
+		 * );
+		 *
+		 * @param $capability {string} The current capability for the task.
+		 * @param $task       {string} The task.
+		 * @param $context    {string} The context for the task.
+		 * @param $args       {mixed}  The optional arguments.
+		 *
+		 * @return  {string}
+		 */
+		$capability = apply_filters( 'cloudinary_task_capability', $capability, $task, $context, ...$args );
+		// phpcs:enable WordPress.WhiteSpace.DisallowInlineTabs.NonIndentTabsUsed
+
+		return current_user_can( $capability, ...$args );
+	}
+
+	/**
+	 * Get the Cloudinary relationships table name.
+	 *
+	 * @return string
+	 */
+	public static function get_relationship_table() {
+		global $wpdb;
+
+		return $wpdb->prefix . 'cloudinary_relationships';
+	}
+
+	/**
+	 * Get the table create SQL.
+	 *
+	 * @return string
+	 */
+	public static function get_table_sql() {
+		global $wpdb;
+
+		$table_name      = self::get_relationship_table();
+		$charset_collate = $wpdb->get_charset_collate();
+		// Setup the sql.
+		$sql = "CREATE TABLE $table_name (
+	  id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+	  post_id bigint(20) DEFAULT NULL,
+	  public_id varchar(1000) DEFAULT NULL,
+	  parent_path varchar(1000) DEFAULT NULL,
+	  sized_url varchar(1000) DEFAULT NULL,
+	  media_context varchar(12) DEFAULT 'default',
+	  width int(11) DEFAULT NULL,
+	  height int(11) DEFAULT NULL,
+	  format varchar(12) DEFAULT NULL,
+	  sync_type varchar(45) DEFAULT NULL,
+	  post_state varchar(12) DEFAULT NULL,
+	  transformations text DEFAULT NULL,
+	  signature varchar(45) DEFAULT NULL,
+	  public_hash varchar(45) DEFAULT NULL,
+	  url_hash varchar(45) DEFAULT NULL,
+	  parent_hash varchar(45) DEFAULT NULL,
+	  PRIMARY KEY (id),
+	  UNIQUE KEY media (url_hash, media_context),
+	  KEY post_id (post_id),
+	  KEY parent_hash (parent_hash),
+	  KEY public_hash (public_hash),
+	  KEY sync_type (sync_type)
+	) ENGINE=InnoDB $charset_collate";
+
+		return $sql;
+	}
+
+	/**
+	 * Check if table exists.
+	 *
+	 * @return bool
+	 */
+	protected static function table_installed() {
+		global $wpdb;
+		$exists     = false;
+		$table_name = self::get_relationship_table();
+		$name       = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+		if ( $table_name === $name ) {
+			$exists = true;
+		}
+
+		return $exists;
+	}
+
+	/**
+	 * Install our custom table.
+	 */
+	public static function install() {
+		// Ensure that the plugin bootstrap is loaded.
+		get_plugin_instance()->init();
+
+		$sql = self::get_table_sql();
+
+		if ( false === self::table_installed() ) {
+			require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+			dbDelta( $sql ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.dbDelta_dbdelta
+			update_option( Sync::META_KEYS['db_version'], get_plugin_instance()->version );
+		} else {
+			self::upgrade_install();
+		}
+	}
+
+	/**
+	 * Upgrade the installation.
+	 */
+	protected static function upgrade_install() {
+		$sequence = self::get_upgrade_sequence();
+		foreach ( $sequence as $callable ) {
+			if ( is_callable( $callable ) ) {
+				call_user_func( $callable );
+			}
+		}
+	}
+
+	/**
+	 * Get the DB upgrade sequence.
+	 *
+	 * @return array
+	 */
+	protected static function get_upgrade_sequence() {
+		$upgrade_sequence = array();
+		$sequences        = array(
+			'3.0.0' => array(
+				'range'  => array( '3.0.0' ),
+				'method' => array( 'Cloudinary\Utils', 'upgrade_3_0_1' ),
+			),
+			'3.1.9' => array(
+				'range'  => array( '3.0.1', '3.1.9' ),
+				'method' => array( 'Cloudinary\Utils', 'upgrade_3_1_9' ),
+			),
+
+		);
+		$previous_version = get_option( Sync::META_KEYS['db_version'], '3.0.0' );
+		$current_version  = get_plugin_instance()->version;
+		foreach ( $sequences as $sequence ) {
+			if (
+				version_compare( $current_version, $previous_version, '>' )
+				&& version_compare( $previous_version, reset( $sequence['range'] ), '>=' )
+				&& version_compare( $previous_version, end( $sequence['range'] ), '<' )
+			) {
+				$upgrade_sequence[] = $sequence['method'];
+			}
+		}
+
+		/**
+		 * Filter the upgrade sequence.
+		 *
+		 * @hook   cloudinary_upgrade_sequence
+		 * @since  3.0.1
+		 *
+		 * @param $upgrade_sequence {array} The default sequence.
+		 *
+		 * @return {array}
+		 */
+		return apply_filters( 'cloudinary_upgrade_sequence', $upgrade_sequence );
+	}
+
+	/**
+	 * Upgrade DB from v3.0.0 to v3.0.1.
+	 */
+	public static function upgrade_3_0_1() {
+		global $wpdb;
+		$tablename = self::get_relationship_table();
+
+		// Drop old indexes.
+		$wpdb->query( "ALTER TABLE {$tablename} DROP INDEX sized_url" ); // phpcs:ignore WordPress.DB
+		$wpdb->query( "ALTER TABLE {$tablename} DROP INDEX parent_path" ); // phpcs:ignore WordPress.DB
+		$wpdb->query( "ALTER TABLE {$tablename} DROP INDEX public_id" ); // phpcs:ignore WordPress.DB
+		// Add new columns.
+		$wpdb->query( "ALTER TABLE {$tablename} ADD `public_hash` VARCHAR(45)  NULL  DEFAULT NULL" ); // phpcs:ignore WordPress.DB
+		$wpdb->query( "ALTER TABLE {$tablename} ADD `url_hash` VARCHAR(45)  NULL  DEFAULT NULL" ); // phpcs:ignore WordPress.DB
+		$wpdb->query( "ALTER TABLE {$tablename} ADD `parent_hash` VARCHAR(45)  NULL  DEFAULT NULL" ); // phpcs:ignore WordPress.DB
+		// Add new indexes.
+		$wpdb->query( "ALTER TABLE {$tablename} ADD UNIQUE INDEX url_hash (url_hash)" ); // phpcs:ignore WordPress.DB
+		$wpdb->query( "ALTER TABLE {$tablename} ADD INDEX public_hash (public_hash)" ); // phpcs:ignore WordPress.DB
+		$wpdb->query( "ALTER TABLE {$tablename} ADD INDEX parent_hash (parent_hash)" ); // phpcs:ignore WordPress.DB
+		// Alter sizes.
+		$wpdb->query( "ALTER TABLE {$tablename} CHANGE public_id public_id varchar(1000) DEFAULT NULL" ); // phpcs:ignore WordPress.DB
+		$wpdb->query( "ALTER TABLE {$tablename} CHANGE parent_path parent_path varchar(1000) DEFAULT NULL" ); // phpcs:ignore WordPress.DB
+		$wpdb->query( "ALTER TABLE {$tablename} CHANGE sized_url sized_url varchar(1000) DEFAULT NULL" ); // phpcs:ignore WordPress.DB
+		// Alter engine.
+		$wpdb->query( "ALTER TABLE {$tablename} ENGINE=InnoDB;" );// phpcs:ignore WordPress.DB
+
+		// Set DB Version.
+		update_option( Sync::META_KEYS['db_version'], get_plugin_instance()->version );
+	}
+
+	/**
+	 * Upgrade DB from v3.0.1 to v3.1.9.
+	 */
+	public static function upgrade_3_1_9() {
+		global $wpdb;
+		$tablename = self::get_relationship_table();
+
+		// Add new columns.
+		$wpdb->query( "ALTER TABLE {$tablename} ADD COLUMN `media_context` VARCHAR(12) DEFAULT 'default' AFTER `sized_url`" ); // phpcs:ignore WordPress.DB
+
+		// Update indexes.
+		$wpdb->query( "ALTER TABLE {$tablename} DROP INDEX url_hash" ); // phpcs:ignore WordPress.DB
+		$wpdb->query( "ALTER TABLE {$tablename} ADD UNIQUE INDEX media (url_hash, media_context)" ); // phpcs:ignore WordPress.DB
+
+		// Set DB Version.
+		update_option( Sync::META_KEYS['db_version'], get_plugin_instance()->version );
+	}
+
+	/**
+	 * Gets the URL for opening a Support Request.
+	 *
+	 * @param array $args The arguments.
+	 *
+	 * @return string
+	 */
+	public static function get_support_link( $args = array() ) {
+		$user   = wp_get_current_user();
+		$plugin = get_plugin_instance();
+		$url    = 'https://support.cloudinary.com/hc/en-us/requests/new';
+
+		$default_args = array(
+			'tf_anonymous_requester_email' => $user->user_email,
+			'tf_22246877'                  => $user->display_name,
+			'tf_360007219560'              => $plugin->components['connect']->get_cloud_name(),
+			'tf_360017815680'              => 'other_help_needed',
+			'tf_subject'                   => esc_attr(
+				sprintf(
+					// translators: The plugin version.
+					__( 'I need help with Cloudinary WordPress plugin version %s', 'cloudinary' ),
+					$plugin->version
+				)
+			),
+			'tf_description'               => esc_attr( __( 'Please, provide more details on your request, and if possible, attach a System Report', 'cloudinary' ) ),
+		);
+
+		$args = wp_parse_args(
+			$args,
+			$default_args
+		);
+
+		return add_query_arg( array_filter( $args ), $url );
+	}
+
+	/**
+	 * Wrapper function to core wp_get_inline_script_tag.
+	 *
+	 * @param string $javascript Inline JavaScript code.
+	 */
+	public static function print_inline_tag( $javascript ) {
+		if ( function_exists( 'wp_print_inline_script_tag' ) ) {
+			wp_print_inline_script_tag( $javascript );
+
+			return;
+		}
+
+		$javascript = "\n" . trim( $javascript, "\n\r " ) . "\n";
+
+		printf( "<script type='text/javascript'>%s</script>\n", $javascript ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+	}
+
+	/**
+	 * Get a sanitized input text field.
+	 *
+	 * @param string $var_name The value to get.
+	 * @param int    $type     The type to get.
+	 *
+	 * @return mixed
+	 */
+	public static function get_sanitized_text( $var_name, $type = INPUT_GET ) {
+		return filter_input( $type, $var_name, FILTER_CALLBACK, array( 'options' => 'sanitize_text_field' ) );
+	}
+
+	/**
+	 * Returns information about a file path by normalizing the locale.
+	 *
+	 * @param string $path  The path to be parsed.
+	 * @param int    $flags Specifies a specific element to be returned.
+	 *                      Defaults to 15 which stands for PATHINFO_ALL.
+	 *
+	 * @return array|string|string[]
+	 */
+	public static function pathinfo( $path, $flags = 15 ) {
+
+		/**
+		 * Approach based on wp_basename.
+		 *
+		 * @see wp-includes/formatting.php
+		 */
+		$path = str_replace( array( '%2F', '%5C' ), '/', urlencode( $path ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.urlencode_urlencode
+
+		$pathinfo = pathinfo( $path, $flags );
+
+		return is_array( $pathinfo ) ? array_map( 'urldecode', $pathinfo ) : urldecode( $pathinfo );
+	}
+
+	/**
+	 * Check if a thing looks like a json string.
+	 *
+	 * @param mixed $thing The thing to check.
+	 *
+	 * @return bool
+	 */
+	public static function looks_like_json( $thing ) {
+		if ( ! is_string( $thing ) ) {
+			return false;
+		}
+
+		$thing = trim( $thing );
+
+		if ( empty( $thing ) ) {
+			return false;
+		}
+
+		if ( ! in_array( $thing[0], array( '{', '[' ), true ) ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Check if we're in a REST API request.
+	 *
+	 * @return bool
+	 */
+	public static function is_rest_api() {
+		$is = defined( 'REST_REQUEST' ) && REST_REQUEST;
+		if ( ! $is ) {
+			$is = ! empty( $GLOBALS['wp']->query_vars['rest_route'] );
+		}
+		if ( ! $is ) {
+			// Fallback if rest engine is not setup yet.
+			$rest_base   = wp_parse_url( static::rest_url( '/' ), PHP_URL_PATH );
+			$request_uri = filter_input( INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL );
+			$is          = is_string( $request_uri ) && strpos( $request_uri, $rest_base ) === 0;
+		}
+
+		return $is;
+	}
+
+	/**
+	 * Check if we are in WordPress ajax.
+	 *
+	 * @return bool
+	 */
+	public static function is_frontend_ajax() {
+		$referer    = wp_get_referer();
+		$admin_base = admin_url();
+		$is_admin   = $referer ? 0 === strpos( $referer, $admin_base ) : false;
+		// Check if this is a frontend ajax request.
+		$is_frontend_ajax = ! $is_admin && defined( 'DOING_AJAX' ) && DOING_AJAX;
+		// If it's not an obvious WP ajax request, check if it's a custom frontend ajax request.
+		if ( ! $is_frontend_ajax && ! $is_admin ) {
+			// Catch the content type of the $_SERVER['CONTENT_TYPE'] variable.
+			$type             = filter_input( INPUT_SERVER, 'CONTENT_TYPE', FILTER_CALLBACK, array( 'options' => 'sanitize_text_field' ) );
+			$is_frontend_ajax = $type && false !== strpos( $type, 'json' );
+		}
+
+		return $is_frontend_ajax;
+	}
+
+	/**
+	 * Check if this is an admin request, but not an ajax one.
+	 *
+	 * @return bool
+	 */
+	public static function is_admin() {
+		return is_admin() && ! self::is_frontend_ajax();
+	}
+
+	/**
+	 * Inspected on wp_extract_urls.
+	 * However, there's a shortcoming on some transformations where the core extractor will fail to fully parse such URLs.
+	 *
+	 * @param string $content The content.
+	 *
+	 * @return array
+	 */
+	public static function extract_urls( $content ) {
+		preg_match_all(
+			"#([\"']?)("
+				. '(?:[\w-]+:)?//?'
+				. '[^\s()<>"\']+'
+				. '[.,]'
+				. '(?:'
+					. '\([\w\d]+\)|'
+					. '(?:'
+						. "[^`!()\[\]{};:'\".,<>«»“”‘’\s]|"
+						. '(?:[:]\w+)?/?'
+					. ')+'
+				. ')'
+			. ")\\1#",
+			$content,
+			$post_links
+		);
+
+		$post_links = array_unique( array_map( 'html_entity_decode', $post_links[2] ) );
+
+		return array_values( $post_links );
+	}
+
+	/**
+	 * Is saving metadata.
+	 *
+	 * @return bool
+	 */
+	public static function is_saving_metadata() {
+		$saving   = false;
+		$metadata = self::METADATA;
+
+		foreach ( $metadata['actions'] as $action ) {
+			foreach ( $metadata['objects'] as $object ) {
+				$inline_action = str_replace( array( '{object}', 'metadata' ), array( $object, 'meta' ), $action );
+				if ( did_action( $inline_action ) ) {
+					$saving = true;
+					break;
+				}
+			}
+		}
+
+		return $saving;
+	}
+
+	/**
+	 * Encode SVG placeholder.
+	 *
+	 * @param string $width  The SVG width.
+	 * @param string $height The SVG height.
+	 * @param string $color  The SVG color.
+	 *
+	 * @return string
+	 */
+	public static function svg_encoded( $width = '600px', $height = '400px', $color = '-color-' ) {
+		$svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $width . '" height="' . $height . '"><rect width="100%" height="100%"><animate attributeName="fill" values="' . $color . '" dur="2s" repeatCount="indefinite" /></rect></svg>';
+
+		return 'data:image/svg+xml;base64,' . base64_encode( $svg );
+	}
+
+	/**
+	 * Wrapper for get_post_parent.
+	 *
+	 * @param int|WP_Post|null $post The post.
+	 *
+	 * @return WP_Post|null
+	 */
+	public static function get_post_parent( $post = null ) {
+		if ( is_callable( 'get_post_parent' ) ) {
+			return get_post_parent( $post );
+		}
+
+		$wp_post = get_post( $post );
+		return ! empty( $wp_post->post_parent ) ? get_post( $wp_post->post_parent ) : null;
+	}
+
+	/**
+	 * Download a fragment of a file URL to a temp file and return the file URI.
+	 *
+	 * @param string $url  The URL to download.
+	 * @param int    $size The size of the fragment to download.
+	 *
+	 * @return string|false
+	 */
+	public static function download_fragment( $url, $size = 1048576 ) {
+
+		$temp_file = wp_tempnam( basename( $url ) );
+		$pointer   = fopen( $temp_file, 'wb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
+		$file      = false;
+		if ( $pointer ) {
+			// Prep to purge.
+			$index = count( self::$file_fragments );
+			if ( empty( $index ) ) {
+				add_action( 'shutdown', array( __CLASS__, 'purge_fragments' ) );
+			}
+			self::$file_fragments[ $index ] = array(
+				'pointer' => $pointer,
+				'file'    => $temp_file,
+			);
+			// Get the metadata of the stream.
+			$data = stream_get_meta_data( $pointer );
+			// Stream the content to the temp file.
+			$response = wp_safe_remote_get(
+				$url,
+				array(
+					'timeout'             => 300, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
+					'stream'              => true,
+					'filename'            => $data['uri'],
+					'limit_response_size' => $size,
+				)
+			);
+			if ( ! is_wp_error( $response ) ) {
+				$file = $data['uri'];
+			} else {
+				// Clean up if there was an error.
+				self::purge_fragment( $index );
+			}
+		}
+
+		return $file;
+	}
+
+	/**
+	 * Purge fragment temp files on shutdown.
+	 */
+	public static function purge_fragments() {
+		foreach ( array_keys( self::$file_fragments ) as $index ) {
+			self::purge_fragment( $index );
+		}
+	}
+
+	/**
+	 * Purge a fragment temp file.
+	 *
+	 * @param int $index The index of the fragment to purge.
+	 */
+	public static function purge_fragment( $index ) {
+		if ( isset( self::$file_fragments[ $index ] ) ) {
+			fclose( self::$file_fragments[ $index ]['pointer'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
+			unlink( self::$file_fragments[ $index ]['file'] );
+		}
+	}
+
+	/**
+	 * Log a debug message.
+	 *
+	 * @param string      $message The message to log.
+	 * @param string|null $key     The key to log the message under.
+	 */
+	public static function log( $message, $key = null ) {
+		if ( get_plugin_instance()->get_component( 'report' )->enabled() ) {
+			$messages = get_option( Sync::META_KEYS['debug'], array() );
+			if ( $key ) {
+				$hash                      = md5( $message );
+				$messages[ $key ][ $hash ] = $message;
+			} else {
+				$messages[] = $message;
+			}
+			update_option( Sync::META_KEYS['debug'], $messages, false );
+		}
+	}
+
+	/**
+	 * Get the debug messages.
+	 *
+	 * @return array
+	 */
+	public static function get_debug_messages() {
+		return get_option( Sync::META_KEYS['debug'], array( __( 'Debug log is empty', 'cloudinary' ) ) );
+	}
+
+	/**
+	 * Check if the tag attributes contain possible third party manipulated data, and return found data.
+	 *
+	 * @param array  $attributes The tag attributes.
+	 * @param string $tag        The tag.
+	 *
+	 * @return string|false
+	 */
+	public static function maybe_get_third_party_changes( $attributes, $tag ) {
+		static $filtered_keys, $filtered_classes;
+		$lazy_keys    = array(
+			'src',
+			'lazyload',
+			'lazy',
+			'loading',
+		);
+		$lazy_classes = array(
+			'lazyload',
+			'lazy',
+			'loading',
+		);
+		if ( ! $filtered_keys ) {
+			/**
+			 * Filter the keywords in data-* attributes on tags to be ignored from lazy-loading.
+			 *
+			 * @hook   cloudinary_ignored_data_keywords
+			 * @since  3.0.8
+			 *
+			 * @param $lazy_keys {array} The built-in ignore data-* keywords.
+			 *
+			 * @return {array}
+			 */
+			$filtered_keys = apply_filters( 'cloudinary_ignored_data_keywords', $lazy_keys );
+
+			/**
+			 * Filter the keywords in classes on tags to be ignored from lazy-loading.
+			 *
+			 * @hook   cloudinary_ignored_class_keywords
+			 * @since  3.0.8
+			 *
+			 * @param $lazy_classes {array} The built-in ignore class keywords.
+			 *
+			 * @return {array}
+			 */
+			$filtered_classes = apply_filters( 'cloudinary_ignored_class_keywords', $lazy_classes );
+		}
+		$is = false;
+
+		// Source tag on Picture tags are not lazy-loaded.
+		if ( 'source' !== $tag ) {
+			if ( ! isset( $attributes['src'] ) ) {
+				$is = __( 'Missing SRC attribute.', 'cloudinary' );
+			} elseif ( false !== strpos( $attributes['src'], 'data:image' ) ) {
+				$is = $attributes['src'];
+			} elseif ( isset( $attributes['class'] ) ) {
+				$classes = explode( '-', str_replace( ' ', '-', $attributes['class'] ) );
+				if ( ! empty( array_intersect( $filtered_classes, $classes ) ) ) {
+					$is = $attributes['class'];
+				}
+			}
+		}
+
+		// If the above didn't find anything, check the data-* attributes.
+		if ( ! $is ) {
+			foreach ( $attributes as $key => $value ) {
+				if ( 'data-' !== substr( $key, 0, 5 ) ) {
+					continue;
+				}
+				$parts = explode( '-', $key );
+				if ( ! empty( array_intersect( $parts, $filtered_keys ) ) ) {
+					$is = $key;
+					break;
+				}
+			}
+		}
+
+		return $is;
+	}
+
+	/**
+	 * Clean up meta after sync.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return void
+	 */
+	public static function clean_up_sync_meta( $attachment_id ) {
+
+		// translators: The attachment ID.
+		$action_message = sprintf( __( 'Clean up sync metadata for %d', 'cloudinary' ), $attachment_id );
+		do_action( '_cloudinary_queue_action', $action_message );
+
+		// remove pending.
+		delete_post_meta( $attachment_id, Sync::META_KEYS['pending'] );
+
+		// Remove processing flag.
+		delete_post_meta( $attachment_id, Sync::META_KEYS['syncing'] );
+
+		$sync_thread = get_post_meta( $attachment_id, Sync::META_KEYS['queued'], true );
+		if ( ! empty( $sync_thread ) ) {
+			delete_post_meta( $attachment_id, Sync::META_KEYS['queued'] );
+			delete_post_meta( $attachment_id, $sync_thread );
+		}
+	}
+
+	/**
+	 * Get the registered image sizes, the labels and crop settings.
+	 *
+	 * @param null|int $attachment_id The attachment ID to get the sizes. Defaults to generic registered sizes.
+	 *
+	 * @return array
+	 */
+	public static function get_registered_sizes( $attachment_id = null ) {
+		$additional_sizes   = wp_get_additional_image_sizes();
+		$all_sizes          = array();
+		$labels             = array();
+		$intermediate_sizes = array();
+
+		if ( is_null( $attachment_id ) ) {
+			$intermediate_sizes = get_intermediate_image_sizes(); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.get_intermediate_image_sizes_get_intermediate_image_sizes
+		} else {
+			$meta = wp_get_attachment_metadata( $attachment_id );
+			if ( ! empty( $meta['sizes'] ) ) {
+				$additional_sizes   = wp_parse_args( $additional_sizes, $meta['sizes'] );
+				$intermediate_sizes = array_keys( $meta['sizes'] );
+			}
+		}
+
+		foreach ( $intermediate_sizes as $size ) {
+			$labels[ $size ] = ucwords( str_replace( array( '-', '_' ), ' ', $size ) );
+		}
+
+		/** This filter is documented in wp-admin/includes/media.php */
+		$image_sizes = apply_filters(
+			'image_size_names_choose',
+			Image_Sizes_No_Textdomain::get_image_sizes()
+		);
+
+		$labels = wp_parse_args( $labels, $image_sizes );
+
+		foreach ( $intermediate_sizes as $size ) {
+			if ( isset( $additional_sizes[ $size ] ) ) {
+				$all_sizes[ $size ] = array(
+					'label'  => $labels[ $size ],
+					'width'  => $additional_sizes[ $size ]['width'],
+					'height' => $additional_sizes[ $size ]['height'],
+				);
+			} else {
+				$all_sizes[ $size ] = array(
+					'label'  => $labels[ $size ],
+					'width'  => (int) get_option( "{$size}_size_w" ),
+					'height' => (int) get_option( "{$size}_size_h" ),
+				);
+			}
+
+			if ( ! empty( $additional_sizes[ $size ]['crop'] ) ) {
+				$all_sizes[ $size ]['crop'] = $additional_sizes[ $size ]['crop'];
+			} else {
+				$all_sizes[ $size ]['crop'] = (bool) get_option( "{$size}_crop" );
+			}
+		}
+
+		/**
+		 * Filter the all sizes available.
+		 *
+		 * @param array $all_sizes All the registered sizes.
+		 *
+		 * @since 3.1.3
+		 *
+		 * @hook  cloudinary_registered_sizes
+		 */
+		return apply_filters( 'cloudinary_registered_sizes', $all_sizes );
+	}
+
+	/**
+	 * Get the attachment ID from the attachment URL.
+	 *
+	 * @param string $url The attachment URL.
+	 *
+	 * @return int|null
+	 */
+	public static function attachment_url_to_postid( $url ) {
+		$key = "postid_{$url}";
+
+		if ( function_exists( 'wpcom_vip_attachment_url_to_postid' ) ) {
+			$attachment_id = wpcom_vip_attachment_url_to_postid( $url );
+		} else {
+			$attachment_id = wp_cache_get( $key, 'cloudinary' );
+		}
+
+		if ( empty( $attachment_id ) ) {
+			$attachment_id = attachment_url_to_postid( $url ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.attachment_url_to_postid_attachment_url_to_postid
+			wp_cache_set( $key, $attachment_id, 'cloudinary' );
+		}
+
+		if ( empty( $attachment_id ) ) {
+			$media           = get_plugin_instance()->get_component( 'media' );
+			$maybe_public_id = $media->get_public_id_from_url( $url );
+			$relations       = self::query_relations( array( $maybe_public_id ) );
+			foreach ( $relations as $relation ) {
+				if ( ! empty( $relation['post_id'] ) ) {
+					$attachment_id = (int) $relation['post_id'];
+					wp_cache_set( $key, $attachment_id, 'cloudinary' );
+				}
+			}
+		}
+
+		return $attachment_id;
+	}
+
+	/**
+	 * Run a query with Public_id's and or local urls.
+	 *
+	 * @param array $public_ids List of Public_IDs qo query.
+	 * @param array $urls       List of URLS to query.
+	 *
+	 * @return array
+	 */
+	public static function query_relations( $public_ids, $urls = array() ) {
+		global $wpdb;
+
+		$wheres          = array();
+		$searched_things = array();
+
+		/**
+		 * Filter the media context query.
+		 *
+		 * @hook   cloudinary_media_context_query
+		 * @since  3.2.0
+		 *
+		 * @param $media_context_query {string} The default media context query.
+		 *
+		 * @return {string}
+		 */
+		$media_context_query = apply_filters( 'cloudinary_media_context_query', 'media_context = %s' );
+
+		/**
+		 * Filter the media context things.
+		 *
+		 * @hook   cloudinary_media_context_things
+		 * @since  3.2.0
+		 *
+		 * @param $media_context_things {array} The default media context things.
+		 *
+		 * @return {array}
+		 */
+		$media_context_things = apply_filters( 'cloudinary_media_context_things', array( 'default' ) );
+
+		if ( ! empty( $urls ) ) {
+			// Do the URLS.
+			$list            = implode( ', ', array_fill( 0, count( $urls ), '%s' ) );
+			$where           = "(url_hash IN( {$list} ) AND {$media_context_query} )";
+			$searched_things = array_merge( $searched_things, array_map( 'md5', $urls ), $media_context_things );
+			$wheres[]        = $where;
+		}
+		if ( ! empty( $public_ids ) ) {
+			// Do the public_ids.
+			$list            = implode( ', ', array_fill( 0, count( $public_ids ), '%s' ) );
+			$where           = "(public_hash IN( {$list} ) AND {$media_context_query} )";
+			$searched_things = array_merge( $searched_things, array_map( 'md5', $public_ids ), $media_context_things );
+			$wheres[]        = $where;
+		}
+
+		$results = array();
+
+		if ( ! empty( array_filter( $wheres ) ) ) {
+			$tablename = self::get_relationship_table();
+			$sql       = "SELECT * from {$tablename} WHERE " . implode( ' OR ', $wheres );
+			$prepared  = $wpdb->prepare( $sql, $searched_things ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+			$cache_key = md5( $prepared );
+			$results   = wp_cache_get( $cache_key, 'cld_delivery' );
+			if ( empty( $results ) ) {
+				$results = $wpdb->get_results( $prepared, ARRAY_A );// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery
+				wp_cache_add( $cache_key, $results, 'cld_delivery' );
+			}
+		}
+
+		return $results;
+	}
+
+	/**
+	 * Clean a url: adds scheme if missing, removes query and fragments.
+	 *
+	 * @param string $url         The URL to clean.
+	 * @param bool   $scheme_less Flag to clean out scheme.
+	 *
+	 * @return string
+	 */
+	public static function clean_url( $url, $scheme_less = true ) {
+		$default = array(
+			'scheme' => '',
+			'host'   => '',
+			'path'   => '',
+			'port'   => '',
+		);
+		$parts   = wp_parse_args( wp_parse_url( $url ), $default );
+		$host    = $parts['host'];
+		if ( ! empty( $parts['port'] ) ) {
+			$host .= ':' . $parts['port'];
+		}
+		$url = '//' . $host . $parts['path'];
+
+		if ( false === $scheme_less ) {
+			$url = $parts['scheme'] . ':' . $url;
+		}
+
+		return $url;
+	}
+
+	/**
+	 * Get the path from a url.
+	 *
+	 * @param string $url            The url.
+	 * @param bool   $bypass_filters Flag to bypass the filters.
+	 *
+	 * @return string
+	 */
+	public static function get_path_from_url( $url, $bypass_filters = false ) {
+		$content_url = content_url();
+
+		if ( ! $bypass_filters ) {
+			$content_url = apply_filters( 'cloudinary_content_url', $content_url );
+		}
+		$path = explode( self::clean_url( $content_url ), $url );
+		$path = end( $path );
+
+		return $path;
+	}
+
+	/**
+	 * Make a scaled version.
+	 *
+	 * @param string $url The url to make scaled.
+	 *
+	 * @return string
+	 */
+	public static function make_scaled_url( $url ) {
+		$file = self::pathinfo( $url );
+		$dash = strrchr( $file['filename'], '-' );
+		if ( '-scaled' === $dash ) {
+			return $url;
+		}
+
+		return $file['dirname'] . '/' . $file['filename'] . '-scaled.' . $file['extension'];
+	}
+
+	/**
+	 * Make a descaled version.
+	 *
+	 * @param string $url The url to descaled.
+	 *
+	 * @return string
+	 */
+	public static function descaled_url( $url ) {
+		$file = self::pathinfo( $url );
+		$dash = strrchr( $file['filename'], '-' );
+		if ( '-scaled' === $dash ) {
+			$file['basename'] = str_replace( '-scaled.', '.', $file['basename'] );
+			$url              = $file['dirname'] . '/' . $file['basename'];
+		}
+
+		return $url;
+	}
+
+	/**
+	 * Get the media context.
+	 *
+	 * @param int|null $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public static function get_media_context( $attachment_id = null ) {
+		/**
+		 * Filter the media context.
+		 *
+		 * This filter allows you to set a media context for the media for cases where the same asset is used in
+		 * different use cases, such as in a multilingual context.
+		 *
+		 * @hook    cloudinary_media_context
+		 * @since   3.1.9
+		 * @default {'default'}
+		 *
+		 * @param $media_context {string}   The media context.
+		 * @param $attachment_id {int|null} The attachment ID.
+		 *
+		 * @return {string}
+		 */
+		$context = apply_filters( 'cloudinary_media_context', 'default', $attachment_id );
+
+		return sanitize_key( $context );
+	}
+
+	/**
+	 * Get the home URL.
+	 *
+	 * @param string $path   The path to be appended to the home URL.
+	 * @param string $scheme The scheme to give the home URL context. Accepts 'http', 'https', or 'relative'.
+	 *
+	 * @return string
+	 */
+	public static function home_url( $path = '', $scheme = null ) {
+		$blog_id = null;
+		if ( is_multisite() ) {
+			$blog_id = get_current_blog_id();
+		}
+		$home_url = get_home_url( $blog_id, $path, $scheme );
+
+		/**
+		 * Filter the home url.
+		 *
+		 * @hook cloudinary_home_url
+		 * @since 3.2.0
+		 *
+		 * @param $home_url {string} The home url.
+		 * @param $path     {string} The path to be appended to the home URL.
+		 * @param $scheme   {string} The scheme to give the home URL context. Accepts 'http', 'https', or 'relative'.
+		 *
+		 * @return {string}
+		 */
+		return apply_filters( 'cloudinary_home_url', $home_url, $path, $scheme );
+	}
+
+	/**
+	 * Get the site URL.
+	 *
+	 * @param string $path   The path to be appended to the site URL.
+	 * @param string $scheme The scheme to give the site URL context. Accepts 'http', 'https', or 'relative'.
+	 *
+	 * @return string
+	 */
+	public static function site_url( $path = '', $scheme = null ) {
+		$blog_id = null;
+		if ( is_multisite() ) {
+			$blog_id = get_current_blog_id();
+		}
+		$site_url = get_site_url( $blog_id, $path, $scheme );
+
+		/**
+		 * Filter the site URL.
+		 *
+		 * @hook cloudinary_site_url
+		 * @since 3.2.2
+		 *
+		 * @param $site_url {string} The site URL.
+		 * @param $path     {string} The path to be appended to the site URL.
+		 * @param $scheme   {string} The scheme to give the site URL context. Accepts 'http', 'https', or 'relative'.
+		 *
+		 * @return {string}
+		 */
+		return apply_filters( 'cloudinary_site_url', $site_url, $path, $scheme );
+	}
+
+	/**
+	 * Get the rest URL.
+	 *
+	 * @param string $path   The path to be appended to the rest URL.
+	 * @param string $scheme The scheme to give the rest URL context. Accepts 'http', 'https', or 'relative'.
+	 *
+	 * @return string
+	 */
+	public static function rest_url( $path = '', $scheme = null ) {
+		$rest_url = rest_url( $path, $scheme );
+
+		/**
+		 * Filter the rest url.
+		 *
+		 * @hook cloudinary_rest_url
+		 * @since 3.2.2
+		 *
+		 * @param $rest_url {string} The rest url.
+		 * @param $path     {string} The path to be appended to the rest URL.
+		 * @param $scheme   {string} The scheme to give the rest URL context. Accepts 'http', 'https', or 'relative'.
+		 *
+		 * @return {string}
+		 */
+		return apply_filters( 'cloudinary_rest_url', $rest_url, $path, $scheme );
+	}
+
+	/**
+	 * Get the transformations title.
+	 *
+	 * @param string $context The context.
+	 *
+	 * @return string
+	 */
+	public static function get_transformations_title( $context ) {
+		$transformations_title = __( 'Cloudinary global transformations', 'cloudinary' );
+		$taxonomy_slug         = static::get_sanitized_text( 'taxonomy' );
+
+		if ( $taxonomy_slug ) {
+			$taxonomy              = get_taxonomy( $taxonomy_slug );
+			$transformations_title = sprintf(
+				// translators: %1$s is the taxonomy label and the %2$s is the context of the use.
+				__( '%1$s %2$s transformations', 'cloudinary' ),
+				$taxonomy->labels->singular_name,
+				$context
+			);
+
+			$taxonomy_id = static::get_sanitized_text( 'tag_ID' );
+
+			if ( $taxonomy_id ) {
+				$transformations_title = sprintf(
+					// translators: %s is the term name.
+					__( '%s transformations', 'cloudinary' ),
+					get_term( $taxonomy_id )->name
+				);
+			}
+		}
+
+		return $transformations_title;
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_connect_class-api.php.html b/docs/php_connect_class-api.php.html new file mode 100644 index 000000000..3055385f5 --- /dev/null +++ b/docs/php_connect_class-api.php.html @@ -0,0 +1,1091 @@ + + + + + Source: php/connect/class-api.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/connect/class-api.php

+ + + + + + + +
+
+
<?php
+/**
+ * Cloudinary API wrapper.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Connect;
+
+use Cloudinary\Relate\Relationship;
+use Cloudinary\Sync;
+use Cloudinary\Utils;
+use Cloudinary\Plugin;
+use Cloudinary\Media;
+use function Cloudinary\get_plugin_instance;
+
+/**
+ * Class API.
+ *
+ * Push media to Cloudinary on upload.
+ */
+class Api {
+
+	/**
+	 * The cloudinary credentials array.
+	 *
+	 * @var array
+	 */
+	public $credentials;
+
+	/**
+	 * Cloudinary Asset URL.
+	 *
+	 * @var string
+	 */
+	public $asset_url = 'res.cloudinary.com';
+
+	/**
+	 * Cloudinary API Version.
+	 *
+	 * @var string
+	 */
+	public $api_version = 'v1_1';
+
+	/**
+	 * Plugin Version
+	 *
+	 * @var string
+	 */
+	public $plugin_version;
+
+	/**
+	 * Holds the media instance.
+	 *
+	 * @var Media
+	 */
+	protected $media;
+
+	/**
+	 * List of cloudinary transformations.
+	 *
+	 * @var array
+	 */
+	public static $transformation_index = array(
+		'image' => array(
+			'a'   => 'angle',
+			'ar'  => 'aspect_ratio',
+			'b'   => 'background',
+			'bo'  => 'border',
+			'c'   => 'crop',
+			'co'  => 'color',
+			'dpr' => 'dpr',
+			'du'  => 'duration',
+			'e'   => 'effect',
+			'eo'  => 'end_offset',
+			'fl'  => 'flags',
+			'h'   => 'height',
+			'l'   => 'overlay',
+			'o'   => 'opacity',
+			'q'   => 'quality',
+			'r'   => 'radius',
+			'so'  => 'start_offset',
+			't'   => 'named_transformation',
+			'u'   => 'underlay',
+			'vc'  => 'video_codec',
+			'w'   => 'width',
+			'x'   => 'x',
+			'y'   => 'y',
+			'z'   => 'zoom',
+			'ac'  => 'audio_codec',
+			'af'  => 'audio_frequency',
+			'br'  => 'bit_rate',
+			'cs'  => 'color_space',
+			'd'   => 'default_image',
+			'dl'  => 'delay',
+			'dn'  => 'density',
+			'f'   => 'fetch_format',
+			'g'   => 'gravity',
+			'p'   => 'prefix',
+			'pg'  => 'page',
+			'sp'  => 'streaming_profile',
+			'vs'  => 'video_sampling',
+			'if'  => 'if',
+		),
+		'video' => array(
+			'w'   => 'width',
+			'h'   => 'height',
+			'c'   => 'crop',
+			'ar'  => 'aspect_ratio',
+			'g'   => 'gravity',
+			'b'   => 'background',
+			'e'   => 'effect',
+			'l'   => 'overlay',
+			'so'  => 'start_offset',
+			'eo'  => 'end_offset',
+			'du'  => 'duration',
+			'a'   => 'angle',
+			'vs'  => 'video_sampling',
+			'dl'  => 'delay',
+			'vc'  => 'video_codec',
+			'fps' => 'fps',
+			'dpr' => 'dpr',
+			'br'  => 'bit_rate',
+			'ki'  => 'keyframe_interval',
+			'sp'  => 'streaming_profile',
+			'ac'  => 'audio_codec',
+			'af'  => 'audio_frequency',
+			'fl'  => 'flags',
+			'f'   => 'fetch_format',
+			'q'   => 'quality',
+			'if'  => 'if',
+			'y' => 'y_axis',
+			'x' => 'x_axis',
+		),
+	);
+
+	/**
+	 * Cloudinary qualified upload prefixes to date.
+	 *
+	 * @var string[]
+	 */
+	public static $qualified_upload_prefixes = array(
+		'api.cloudinary.com',
+		'api-eu.cloudinary.com',
+		'api-ap.cloudinary.com',
+	);
+
+	/**
+	 * Current pending url to overide the fields to post.
+	 *
+	 * @var string|null
+	 */
+	private $pending_url = array();
+
+	/**
+	 * API constructor.
+	 *
+	 * @param \Cloudinary\Connect $connect The connect object.
+	 * @param string              $version The plugin version.
+	 */
+	public function __construct( $connect, $version ) {
+		$this->credentials    = $connect->get_credentials();
+		$this->plugin_version = $version;
+		// Use CNAME.
+		if ( ! empty( $this->credentials['cname'] ) ) {
+			$this->asset_url = $this->credentials['cname'];
+		}
+		add_action( 'cloudinary_ready', array( $this, 'setup' ) );
+	}
+
+	/**
+	 * Setup the API
+	 *
+	 * @param Plugin $plugin The plugin instance.
+	 */
+	public function setup( Plugin $plugin ) {
+		$this->media = $plugin->get_component( 'media' );
+	}
+
+	/**
+	 * Return an endpoint for a specific resource type.
+	 *
+	 * @param string $resource The resource type for the endpoint.
+	 * @param string $function The function of the endpoint.
+	 * @param bool   $endpoint Flag to get an endpoint or an asset url.
+	 *
+	 * @return string
+	 */
+	public function url( $resource, $function = null, $endpoint = false ) {
+		$parts = array();
+
+		if ( $endpoint ) {
+			$parts[] = $this->get_upload_prefix();
+			$parts[] = $this->api_version;
+		} else {
+			$parts[] = $this->asset_url;
+		}
+
+		if ( empty( $this->credentials['cname'] ) || $endpoint || 'false' === $this->credentials['private_cdn'] ) {
+			$parts[] = $this->credentials['cloud_name'];
+		}
+
+		/**
+		 * Bypass Cloudinary's SEO URLs.
+		 *
+		 * @hook   cloudinary_bypass_seo_url
+		 * @since  3.1.5
+		 *
+		 * @param $bypass_seo_url {bool} Whether to bypass SEO URLs.
+		 *
+		 * @return {bool}
+		 */
+		$bypass_seo_url = apply_filters( 'cloudinary_bypass_seo_url', false );
+
+		if ( false === $endpoint && 'image' === $resource && 'upload' === $function && ! $bypass_seo_url ) {
+			$parts[] = 'images';
+		} else {
+			$parts[] = $resource;
+			$parts[] = $function;
+		}
+
+		$parts = array_filter( $parts );
+		$url   = implode( '/', $parts );
+
+		return $url;
+	}
+
+	/**
+	 * Generate a transformation string.
+	 *
+	 * @param array  $options The transformation options to generate from.
+	 * @param string $type    The asset Type.
+	 * @param string $context The context.
+	 *
+	 * @return string
+	 */
+	public static function generate_transformation_string( array $options, $type = 'image', $context = '' ) {
+		if ( ! isset( self::$transformation_index[ $type ] ) ) {
+			return '';
+		}
+		$transformation_index = self::$transformation_index[ $type ];
+		$transformations      = array_map(
+			function ( $item ) use ( $transformation_index ) {
+				$transform = array();
+				if ( is_string( $item ) ) {
+					return $item;
+				}
+
+				foreach ( $item as $type => $value ) { // phpcs:ignore
+					$key = array_search( $type, $transformation_index, true );
+					if ( false !== $key ) {
+						$transform[] = $key . '_' . $value;
+					} elseif ( '$' === $type[0] ) {
+						$transform[] = $type . '_' . $value;
+					}
+				}
+
+				return implode( ',', $transform );
+			},
+			$options
+		);
+
+		// Prepare the eager transformations for the upload.
+		if ( 'upload' === $context ) {
+			foreach ( $transformations as &$transformation ) {
+				if ( 0 <= strpos( $transformation, 'f_auto' ) ) {
+					$parts = explode( ',', $transformation );
+					unset( $parts[ array_search( 'f_auto', $parts, true ) ] );
+					$remaining_transformations = implode( ',', $parts );
+					$formats                   = array();
+
+					if ( 'image' === $type ) {
+						$formats = array(
+							'f_avif',
+							'f_webp',
+							'f_webp,fl_awebp',
+							'f_wdp',
+							'f_jp2',
+						);
+					} elseif ( 'video' === $type ) {
+						$formats = array(
+							'f_webm,vc_vp9',
+							'f_mp4,vc_h265',
+							'f_mp4,vc_h264',
+						);
+					}
+
+					/**
+					 * Filter the upload eager formats.
+					 *
+					 * @hook cloudinary_upload_eager_formats
+					 * @since 3.1.6
+					 *
+					 * @param $formats {array} The default formats.
+					 * @param $type    {string} The asset type.
+					 *
+					 * @return {array}
+					 */
+					$formats = apply_filters( 'cloudinary_upload_eager_formats', $formats, $type );
+
+					array_walk(
+						$formats,
+						static function ( &$i ) use ( $remaining_transformations ) {
+							if ( $remaining_transformations ) {
+								$i .= ",{$remaining_transformations}";
+							}
+						}
+					);
+
+					$transformation = implode( '|', $formats );
+				}
+			}
+		}
+
+		// Clear out empty parts.
+		$transformations = array_filter( $transformations );
+
+		return implode( '/', $transformations );
+	}
+
+	/**
+	 * Generate a Cloudinary URL.
+	 *
+	 * @param string|null $public_id     The Public ID to get a url for.
+	 * @param array       $args          Additional args.
+	 * @param array       $size          The WP Size array.
+	 * @param int|null    $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public function cloudinary_url( $public_id = null, $args = array(), $size = array(), $attachment_id = null ) {
+
+		if ( null === $public_id ) {
+			return 'https://' . $this->url( null, null );
+		}
+		$defaults = array(
+			'resource_type' => 'image',
+			'delivery'      => 'upload',
+			'version'       => 'v1',
+		);
+		$args     = wp_parse_args( array_filter( $args ), $defaults );
+
+		// check for version.
+		if ( ! empty( $args['version'] ) && is_numeric( $args['version'] ) ) {
+			$args['version'] = 'v' . $args['version'];
+		}
+
+		// Determine if we're dealing with a fetched.
+		// ...or uploaded image and update the URL accordingly.
+		$asset_endpoint = filter_var( $public_id, FILTER_VALIDATE_URL ) ? 'fetch' : $args['delivery'];
+
+		$url_parts = array(
+			'https:/',
+			$this->url( $args['resource_type'], $asset_endpoint ),
+		);
+
+		$base = Utils::pathinfo( $public_id );
+		// Add size.
+		if ( ! empty( $size ) && is_array( $size ) ) {
+			if ( ! empty( $size['transformation'] ) ) {
+				$url_parts[] = $size['transformation'];
+			}
+			// add size to ID if scaled.
+			if ( ! empty( $size['file'] ) ) {
+				$public_id = str_replace( $base['basename'], $size['file'], $public_id );
+			}
+		}
+		if ( ! empty( $args['transformation'] ) ) {
+			$url_parts[] = self::generate_transformation_string( $args['transformation'], $args['resource_type'] );
+		}
+
+		if ( $attachment_id ) {
+			$public_id = $this->get_public_id( $attachment_id, $args, $public_id );
+		}
+
+		$url_parts[] = $args['version'];
+		$url_parts[] = $public_id;
+
+		// Clear out empty parts.
+		$url_parts = array_filter( $url_parts );
+
+		return implode( '/', $url_parts );
+	}
+
+	/**
+	 * Get the details of an asset by public ID.
+	 *
+	 * @param string $public_id The public_id to check.
+	 * @param string $type      The asset type.
+	 *
+	 * @return array|\WP_Error
+	 */
+	public function get_asset_details( $public_id, $type ) {
+		$url = $this->url( 'resources', $type . '/upload/' . $public_id, true );
+
+		return $this->call( $url, array( 'body' => array() ), 'get' );
+	}
+
+	/**
+	 * Upload a large asset in chunks.
+	 *
+	 * @param int   $attachment_id The attachment ID.
+	 * @param array $args          Array of upload options.
+	 *
+	 * @return array|\WP_Error
+	 */
+	public function upload_large( $attachment_id, $args ) {
+		// Ensure we have the right file.
+		if ( empty( $args['file'] ) ) {
+			$args['file'] = get_attached_file( $attachment_id );
+		}
+		$tempfile = false;
+		if ( false !== strpos( $args['file'], 'vip://' ) ) {
+			$args['file'] = $this->create_local_copy( $args['file'] );
+			if ( is_wp_error( $args['file'] ) ) {
+				return $args['file'];
+			}
+			$tempfile = true;
+		}
+
+		require_once ABSPATH . 'wp-admin/includes/file.php';
+
+		WP_Filesystem();
+		global $wp_filesystem;
+
+		if ( ! in_array( $wp_filesystem->method, array( 'vip', 'direct' ), true ) ) {
+			// We'll need to have direct file access to be able to read a chunked version to upload.
+			// Perhaps we could use the URL upload method in this case?
+			return new \WP_Error( 'upload_error', __( 'No direct access to file system.', 'cloudinary' ) );
+		}
+
+		// Since WP_Filesystem doesn't have a fread, we need to do it manually. However we'll still use it for writing.
+		$src            = fopen( $args['file'], 'r' ); // phpcs:ignore
+		$temp_file_name = wp_tempnam( uniqid( time() ) . '.' . Utils::pathinfo( $args['file'], PATHINFO_EXTENSION ) );
+		$upload_id      = substr( sha1( uniqid( $this->credentials['api_secret'] . wp_rand() ) ), 0, 16 );
+		$chunk_size     = 20000000;
+		$index          = 0;
+		$file_size      = filesize( $args['file'] );
+		while ( ! feof( $src ) ) {
+			$current_loc = $index * $chunk_size;
+			if ( $current_loc >= $file_size ) {
+				break;
+			}
+			$data = fread( $src, $chunk_size ); // phpcs:ignore
+			file_put_contents( $temp_file_name, $data ); //phpcs:ignore
+
+			clearstatcache( true, $temp_file_name );
+
+			$temp_file_size = filesize( $temp_file_name );
+			$range          = 'bytes ' . $current_loc . '-' . ( $current_loc + $temp_file_size - 1 ) . '/' . $file_size;
+
+			$headers      = array(
+				'Content-Range'      => $range,
+				'X-Unique-Upload-Id' => $upload_id,
+			);
+			$args['file'] = $temp_file_name;
+			$result       = $this->upload( $attachment_id, $args, $headers );
+			if ( is_wp_error( $result ) ) {
+				break;
+			}
+			++$index;
+		}
+		fclose( $src ); //phpcs:ignore
+		unlink( $temp_file_name ); //phpcs:ignore
+		if ( true === $tempfile ) {
+			unlink( $args['file'] ); //phpcs:ignore
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Copy an asset from one Cloudinary location to another.
+	 *
+	 * @param int   $attachment_id Attachment ID to upload.
+	 * @param array $args          Array of upload options.
+	 *
+	 * @return array|\WP_Error
+	 */
+	public function copy( $attachment_id, $args ) {
+		$resource     = ! empty( $args['resource_type'] ) ? $args['resource_type'] : 'image';
+		$url          = $this->url( $resource, 'upload', true );
+		$args         = $this->clean_args( $args );
+		$args['file'] = $this->media->raw_cloudinary_url( $attachment_id );
+		$call_args    = array(
+			'headers' => array(),
+			'body'    => $args,
+		);
+
+		return $this->call( $url, $call_args, 'post' );
+	}
+
+	/**
+	 * Upload an asset.
+	 *
+	 * @param int   $attachment_id Attachment ID to upload.
+	 * @param array $args          Array of upload options.
+	 * @param array $headers       Additional headers to use in upload.
+	 * @param bool  $try_remote    Flag to try_remote upload.
+	 *
+	 * @return array|\WP_Error
+	 */
+	public function upload( $attachment_id, $args, $headers = array(), $try_remote = true ) {
+
+		$resource            = ! empty( $args['resource_type'] ) ? $args['resource_type'] : 'image';
+		$url                 = $this->url( $resource, 'upload', true );
+		$args                = $this->clean_args( $args );
+		$disable_https_fetch = get_transient( '_cld_disable_http_upload' );
+
+		/**
+		 * Whether to use the original image URL.
+		 *
+		 * @hook    cloudinary_use_original_image
+		 * @since   3.1.8
+		 * @default true
+		 *
+		 * @param $use_original  {bool} The default value.
+		 * @param $attachment_id {int}  The attachment ID.
+		 *
+		 * @return  {bool}
+		 */
+		$use_original = apply_filters( 'cloudinary_use_original_image', true, $attachment_id );
+		if ( $use_original && function_exists( 'wp_get_original_image_url' ) && wp_attachment_is_image( $attachment_id ) ) {
+			$file_url = wp_get_original_image_url( $attachment_id );
+		} else {
+			$file_url = wp_get_attachment_url( $attachment_id );
+		}
+
+		if ( empty( $file_url ) ) {
+			$disable_https_fetch = true;
+		}
+
+		if ( ! $this->media ) {
+			$this->media = get_plugin_instance()->get_component( 'media' );
+		}
+
+		if ( $this->media && ! $this->media->is_uploadable_media( $attachment_id ) ) {
+			$disable_https_fetch = false; // Remote can upload via url.
+			// translators: variable is thread name and queue size.
+			$action_message = sprintf( __( 'Uploading remote url:  %1$s.', 'cloudinary' ), $file_url );
+			do_action( '_cloudinary_queue_action', $action_message );
+		}
+		$tempfile = false;
+		if ( $this->media && $this->media->is_cloudinary_url( $file_url ) ) {
+			// If this is a Cloudinary URL, then we can use it to fetch from that location.
+			$disable_https_fetch = false;
+		}
+		// Check if we can try http file upload.
+		if ( ( empty( $headers ) && empty( $disable_https_fetch ) && true === $try_remote ) || ! $this->media->is_uploadable_media( $attachment_id ) ) {
+			$args['file'] = $file_url;
+		} elseif ( ! $this->media->is_local_media( $attachment_id ) ) {
+			$args['file'] = $file_url;
+		} else {
+			// We should have the file in args at this point, but if the transient was set, it will be defaulting here.
+			if ( empty( $args['file'] ) ) {
+				if ( wp_attachment_is_image( $attachment_id ) ) {
+					$get_path_func = $use_original && function_exists( 'wp_get_original_image_path' ) ? 'wp_get_original_image_path' : 'get_attached_file';
+					$args['file']  = call_user_func( $get_path_func, $attachment_id );
+				} else {
+					$args['file'] = get_attached_file( $attachment_id );
+				}
+			}
+			// Headers indicate chunked upload.
+			if ( empty( $headers ) && file_exists( $args['file'] ) ) {
+				$size = filesize( $args['file'] );
+				if ( 'video' === $resource || $size > 100000000 ) {
+					return $this->upload_large( $attachment_id, $args );
+				}
+			}
+			if ( false !== strpos( $args['file'], 'vip://' ) ) {
+				$args['file'] = $this->create_local_copy( $args['file'] );
+				if ( is_wp_error( $args['file'] ) ) {
+					return $args['file'];
+				}
+				$tempfile = true;
+			}
+			// Attach File.
+			if ( function_exists( 'curl_file_create' ) ) {
+				$file         = $args['file'];
+				$args['file'] = curl_file_create( $file ); // phpcs:ignore
+				$args['file']->setPostFilename( $file );
+			} else {
+				$args['file'] = '@' . $args['file'];
+			}
+		}
+
+		$call_args = array(
+			'headers' => $headers,
+			'body'    => $args,
+		);
+
+		/**
+		 * Filter Cloudinary upload args.
+		 *
+		 * @hook cloudinary_upload_args
+		 * @since 3.0.1
+		 *
+		 * @param $call_args     {array} The default args.
+		 * @param $attachment_id {int}   The attachment ID.
+		 */
+		$call_args = apply_filters( 'cloudinary_upload_args', $call_args, $attachment_id );
+
+		$result = $this->call( $url, $call_args, 'post' );
+		// Hook in flag to allow for non accessible URLS.
+		if ( is_wp_error( $result ) ) {
+			$error = $result->get_error_message();
+			$code  = $result->get_error_code();
+			/**
+			 * If there's an error and the file is a URL in the error message,
+			 * it's likely due to CURL or the location does not support URL file attachments.
+			 * In this case, we'll flag and disable it and try again with a local file.
+			 */
+			if ( 404 !== $code && empty( $disable_https_fetch ) && false !== strpos( urldecode( $error ), $args['file'] ) ) {
+				// URLS are not remotely available, try again as a file.
+				set_transient( '_cld_disable_http_upload', true, DAY_IN_SECONDS );
+				// Remove URL file.
+				unset( $args['file'] );
+
+				return $this->upload( $attachment_id, $args );
+			}
+		}
+		if ( true === $tempfile && is_string( $args['file'] ) ) {
+			unlink( $args['file'] ); //phpcs:ignore
+		}
+
+		/**
+		 * Action after uploaded asset.
+		 *
+		 * @hook  cloudinary_uploaded_asset
+		 * @since 3.0.1
+		 *
+		 * @param $attachment_id {int}            The attachment ID.
+		 * @param $result        {array|WP_Error} The upload result.
+		 */
+		do_action( 'cloudinary_uploaded_asset', $attachment_id, $result );
+
+		return $result;
+	}
+
+	/**
+	 * Upload an cache item.
+	 *
+	 * @param array $args The upload parameters.
+	 *
+	 * @return array $the url to the cached item.
+	 */
+	public function upload_cache( $args ) {
+		$call_args = array(
+			'headers' => array(),
+			'body'    => $args,
+		);
+		$url       = $this->url( 'auto', 'upload', true );
+
+		return $this->call( $url, $call_args, 'post' );
+	}
+
+	/**
+	 * Create a local copy of the file if stored remotely in VIP.
+	 *
+	 * @param string $file File name to copy.
+	 *
+	 * @return string|\WP_Error
+	 */
+	public function create_local_copy( $file ) {
+		$file_copy = wp_tempnam( wp_basename( $file ) );
+		$content   = file_get_contents( $file ); //phpcs:ignore
+
+		if ( file_put_contents( $file_copy, $content ) ) { //phpcs:ignore
+			$file = $file_copy;
+		} else {
+			return new \WP_Error( 'upload_fail', __( 'Could not get VIP file content', 'cloudinary' ) );
+		}
+
+		return $file;
+	}
+
+	/**
+	 * Expicit update of an asset.
+	 *
+	 * @param array $args Array of options to update.
+	 *
+	 * @return array|\WP_Error
+	 */
+	public function explicit( $args ) {
+
+		$url  = $this->url( 'image', 'explicit', true );
+		$args = $this->clean_args( $args );
+
+		return $this->call( $url, array( 'body' => $args ), 'post' );
+	}
+
+	/**
+	 * Destroy an asset.
+	 *
+	 * @param string $type    The resource type to destroy.
+	 * @param array  $options Array of options.
+	 *
+	 * @return array|\WP_Error
+	 */
+	public function destroy( $type, $options ) {
+
+		$url = $this->url( $type, 'destroy', true );
+
+		return $this->call( $url, array( 'body' => $options ), 'post' );
+	}
+
+	/**
+	 * Rename an asset.
+	 *
+	 * @param string $type    The resource type to rename.
+	 * @param array  $options Array of options.
+	 *
+	 * @return array|\WP_Error
+	 */
+	public function rename( $type, $options ) {
+
+		$url = $this->url( $type, 'rename', true );
+
+		return $this->call( $url, array( 'body' => $options ), 'post' );
+	}
+
+	/**
+	 * Context update of an asset.
+	 *
+	 * @param array $args Array of options to update.
+	 *
+	 * @return array|\WP_Error
+	 */
+	public function context( $args ) {
+
+		$url     = $this->url( $args['resource_type'], 'context', true );
+		$options = array(
+			'public_ids' => $args['public_id'],
+			'context'    => $args['context'],
+			'command'    => 'add',
+		);
+		$options = $this->clean_args( $options );
+
+		return $this->call( $url, array( 'body' => $options ), 'post' );
+	}
+
+	/**
+	 * Clean the args before sending to endpoint.
+	 *
+	 * @param array $args Array of args to clean.
+	 *
+	 * @return array
+	 */
+	public function clean_args( $args ) {
+
+		return array_map(
+			function ( $value ) {
+				if ( is_array( $value ) ) {
+					$value = wp_json_encode( $value );
+				}
+				if ( is_bool( $value ) ) {
+					$value = true === $value ? '1' : '0';
+				}
+
+				return $value;
+			},
+			$args
+		);
+	}
+
+	/**
+	 * General Call based on tag.
+	 *
+	 * @param string $name Name of method to call.
+	 * @param array  $args Array of parameters to pass to call.
+	 *
+	 * @return array|\WP_Error
+	 */
+	public function __call( $name, $args ) {
+		$function = null;
+		if ( ! empty( $args[0] ) ) {
+			$function = $args[0];
+		}
+		$url       = $this->url( $name, $function, true );
+		$method    = 'get';
+		$send_args = array( 'body' => $args );
+		if ( ! empty( $args[1] ) ) {
+			$method = $args[1];
+		}
+		if ( ! empty( $args[2] ) ) {
+			$send_args['body'] = $args[2];
+		}
+
+		return $this->call( $url, $send_args, $method );
+	}
+
+	/**
+	 * Sign a request
+	 *
+	 * @param array $args Array of parameters to sign.
+	 *
+	 * @return array|\WP_Error
+	 */
+	public function sign( $args ) {
+
+		// Sort parameters.
+		ksort( $args );
+		$args    = array_map(
+			function ( $value, $key ) {
+				$remove = array( 'file', 'resource_type', 'api_key' );
+				if ( in_array( $key, $remove, true ) || '' === $value ) {
+					return null;
+				}
+				if ( is_array( $value ) ) {
+					$value = implode( ',', array_values( $value ) );
+				}
+
+				return $key . '=' . $value;
+			},
+			$args,
+			array_keys( $args )
+		);
+		$to_sign = array_filter( $args );
+		$string  = implode( '&', $to_sign );
+
+		return sha1( $string . $this->credentials['api_secret'] );
+	}
+
+	/**
+	 * Set the POSTFIELDS to the correct array type, not the string based.
+	 *
+	 * @param \Requests_Transport_cURL $handle  The transport handle to set.
+	 * @param array                    $request The request array.
+	 * @param string                   $url     The url to send to.
+	 */
+	public function set_data( $handle, $request, $url ) {
+		// Ensure that this request is in fact ours.
+		if ( $this->pending_url === $url ) {
+			curl_setopt( $handle, CURLOPT_POSTFIELDS, $request['body'] ); // phpcs:ignore
+			$this->pending_url = null;
+		}
+	}
+
+	/**
+	 * Get Cloudinary upload prefix.
+	 *
+	 * @return string
+	 */
+	public function get_upload_prefix() {
+		static $upload_prefix;
+
+		if ( is_null( $upload_prefix ) ) {
+			// Defaults to first hardcoded qualified domain.
+			$upload_prefix = reset( self::$qualified_upload_prefixes );
+
+			// Maybe use constant.
+			if ( self::is_qualified_upload_prefix( CLOUDINARY_ENDPOINTS_API ) ) {
+				$upload_prefix = self::get_hostname( CLOUDINARY_ENDPOINTS_API );
+			}
+
+			// Maybe use query argument.
+			if (
+				! empty( $this->credentials['upload_prefix'] )
+				&& self::is_qualified_upload_prefix( $this->credentials['upload_prefix'] )
+			) {
+				$upload_prefix = self::get_hostname( $this->credentials['upload_prefix'] );
+			}
+		}
+
+		return $upload_prefix;
+	}
+
+	/**
+	 * Get the Cloudinary public_id.
+	 *
+	 * @param int         $attachment_id      The attachment ID.
+	 * @param array       $args               The args.
+	 * @param null|string $original_public_id The original public ID.
+	 *
+	 * @return string
+	 */
+	public function get_public_id( $attachment_id, $args = array(), $original_public_id = null ) {
+
+		$relationship = Relationship::get_relationship( $attachment_id );
+		$public_id    = null;
+
+		if ( $relationship instanceof Relationship ) {
+			$public_id = $relationship->public_id;
+		}
+
+		// On cases like the initial sync, we might not have a relationship, so we need to trust to requested public_id.
+		if ( empty( $public_id ) ) {
+			$public_id = $original_public_id;
+		}
+
+		/**
+		 * Bypass Cloudinary's SEO URLs.
+		 *
+		 * @hook   cloudinary_bypass_seo_url
+		 * @since  3.1.5
+		 *
+		 * @param $bypass_seo_url {bool} Whether to bypass SEO URLs.
+		 *
+		 * @return {bool}
+		 */
+		$bypass_seo_url = apply_filters( 'cloudinary_bypass_seo_url', false );
+
+		if (
+			$public_id
+			&& ! $bypass_seo_url
+			&& (
+				// Get the SEO `public_id` for images with `upload` delivery.
+				(
+					! empty( $args['resource_type'] ) && 'image' === $args['resource_type']
+					&& ! empty( $args['delivery'] ) && 'upload' === $args['delivery']
+				)
+				// Get the SEO `public_id` for PDFs as they are regarded as images.
+				|| (
+					! empty( $args['resource_type'] ) && 'image' === $args['resource_type']
+					&& 'application' === $relationship->asset_type
+				)
+			)
+		) {
+
+			$parts    = explode( '/', $public_id );
+			$filename = end( $parts );
+
+			/**
+			 * Filter the SEO public ID.
+			 *
+			 * @hook   cloudinary_seo_public_id
+			 * @since  3.1.5
+			 *
+			 * @param $sufix          {string}       The public_id suffix.
+			 * @param $relationship  {Relationship} The relationship.
+			 * @param $attachment_id {int}          The attachment ID.
+			 *
+			 * @return {string}
+			 */
+			$suffix = apply_filters( 'cloudinary_seo_public_id', "{$filename}.{$relationship->format}", $relationship, $attachment_id );
+
+			$public_id .= "/{$suffix}";
+		}
+
+		if ( 'video' === $relationship->asset_type ) {
+			$public_id .= ".{$relationship->format}";
+		}
+
+		return $public_id;
+	}
+
+	/**
+	 * Check if a string is a qualified `upload_prefix`.
+	 *
+	 * @param string $upload_prefix Upload prefix to check.
+	 *
+	 * @return bool
+	 */
+	protected static function is_qualified_upload_prefix( $upload_prefix ) {
+		$hostname = self::get_hostname( $upload_prefix );
+
+		return in_array( $hostname, self::$qualified_upload_prefixes, true );
+	}
+
+	/**
+	 * Get the hostname from a string.
+	 *
+	 * @param string $upload_prefix Upload prefix to check.
+	 *
+	 * @return string
+	 */
+	protected static function get_hostname( $upload_prefix ) {
+		$hostname = $upload_prefix;
+
+		if ( filter_var( $upload_prefix, FILTER_VALIDATE_URL ) ) {
+			$hostname = wp_parse_url( $upload_prefix, PHP_URL_HOST );
+		}
+
+		return $hostname;
+	}
+
+	/**
+	 * Calls the API request.
+	 *
+	 * @param string $url    The url to call.
+	 * @param array  $args   The optional arguments to send.
+	 * @param string $method The call HTTP method.
+	 *
+	 * @return array|\WP_Error
+	 */
+	private function call( $url, $args = array(), $method = 'get' ) {
+		$args['method']             = strtoupper( $method );
+		$args['user-agent']         = 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) . ' (' . $this->plugin_version . ')';
+		$args['headers']['referer'] = Utils::site_url();
+		if ( 'GET' === $args['method'] ) {
+			$url = 'https://' . $this->credentials['api_key'] . ':' . $this->credentials['api_secret'] . '@' . $url;
+		} else {
+			$url                       = 'https://' . $url;
+			$args['body']['api_key']   = $this->credentials['api_key'];
+			$args['body']['timestamp'] = time();
+			// Sign request.
+			$args['body']['signature'] = $this->sign( $args['body'] );
+			ksort( $args['body'] );
+			// Fix the data to not be a flattend fields string.
+			add_action( 'http_api_curl', array( $this, 'set_data' ), 10, 3 );
+			// Add url to list to allow it to be fixed.
+			$this->pending_url = $url;
+		}
+
+		// Set a long-ish timeout since uploads can be 20mb+.
+		$args['timeout'] = 90; // phpcs:ignore
+
+		// Adjust timeout for additional eagers if image_freeform or video_freeform is set.
+		if ( ! empty( $args['body']['resource_type'] ) ) {
+			$freeform = $this->media->get_settings()->get_value( $args['body']['resource_type'] . '_freeform' );
+			if ( ! empty( $freeform ) ) {
+				$timeout_multiplier = explode( '/', $freeform );
+				$args['timeout']   += 60 * count( $timeout_multiplier ); // phpcs:ignore
+			}
+		}
+
+		$request = wp_remote_request( $url, $args );
+		if ( is_wp_error( $request ) ) {
+			return $request;
+		}
+		$body   = wp_remote_retrieve_body( $request );
+		$result = json_decode( $body, ARRAY_A );
+		if ( empty( $result ) && ! empty( $body ) ) {
+			return $body; // not json.
+		}
+		if ( ! empty( $result['error'] ) && ! empty( $result['error']['message'] ) ) {
+			return new \WP_Error( $request['response']['code'], $result['error']['message'] );
+		}
+
+		return $result;
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_delivery_class-bypass.php.html b/docs/php_delivery_class-bypass.php.html new file mode 100644 index 000000000..b41f2f873 --- /dev/null +++ b/docs/php_delivery_class-bypass.php.html @@ -0,0 +1,357 @@ + + + + + Source: php/delivery/class-bypass.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/delivery/class-bypass.php

+ + + + + + + +
+
+
<?php
+/**
+ * Bypass class for the Cloudinary plugin.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Delivery;
+
+use Cloudinary\Plugin;
+use Cloudinary\Sync;
+use Cloudinary\Media;
+use Cloudinary\Settings;
+use WP_Post;
+
+/**
+ * Class Bypass
+ */
+class Bypass {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @var Plugin Instance of the global plugin.
+	 */
+	public $plugin;
+
+	/**
+	 * Holds the Media instance.
+	 *
+	 * @var Media
+	 */
+	protected $media;
+
+	/**
+	 * Holds the main settings.
+	 *
+	 * @var Settings
+	 */
+	protected $settings;
+
+	/**
+	 * Flag to determine if bypassing is allowed.
+	 *
+	 * @var bool
+	 */
+	protected $enabled;
+
+	/**
+	 * Holds the bypass action keys.
+	 */
+	const BYPASS_KEYS = array(
+		'wp'  => 'wp-delivery',
+		'cld' => 'cld-delivery',
+	);
+
+	/**
+	 * Bypass constructor.
+	 *
+	 * @param Plugin $plugin Global instance of the main plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+		$this->plugin = $plugin;
+
+		// Hook into settings init, to get settings ans start hooks.
+		add_action( 'cloudinary_init_settings', array( $this, 'init_settings' ) );
+	}
+
+	/**
+	 * Setup the filters and actions.
+	 */
+	protected function setup_hooks() {
+		// Start delivery bypasses if enabled.
+		if ( $this->enabled ) {
+			add_filter( 'handle_bulk_actions-upload', array( $this, 'handle_bulk_actions' ), 10, 3 );
+			add_filter( 'media_row_actions', array( $this, 'add_inline_action' ), 10, 2 );
+			add_filter( 'bulk_actions-upload', array( $this, 'add_bulk_actions' ) );
+			add_action( 'attachment_submitbox_misc_actions', array( $this, 'delivery_actions' ), 11 );
+			add_filter( 'wp_insert_attachment_data', array( $this, 'handle_save_attachment_delivery' ), 10, 2 );
+			add_filter( 'cloudinary_can_sync_asset', array( $this, 'can_sync' ), 10, 2 );
+			add_filter( 'cloudinary_cache_media_asset', array( $this, 'can_sync' ), 10, 2 );
+			add_filter( 'cloudinary_media_status', array( $this, 'filter_status' ), 11, 2 );
+		}
+	}
+
+	/**
+	 * Init the main settings.
+	 *
+	 * @param Plugin $plugin The plugin instance.
+	 */
+	public function init_settings( $plugin ) {
+		$this->media    = $plugin->get_component( 'media' );
+		$this->settings = $plugin->settings;
+		$this->enabled  = 'on' === $this->settings->get_value( 'auto_sync' ) && 'cld' !== $this->settings->get_value( 'offload' );
+		$this->setup_hooks();
+	}
+
+	/**
+	 * Handles bulk actions for attachments.
+	 *
+	 * @param string $location The location to redirect after.
+	 * @param string $action   The action to handle.
+	 * @param array  $post_ids Post ID's to action.
+	 *
+	 * @return string
+	 */
+	public function handle_bulk_actions( $location, $action, $post_ids ) {
+		$delivery = null;
+		switch ( $action ) {
+			case self::BYPASS_KEYS['wp']:
+				$delivery = true;
+				break;
+			case self::BYPASS_KEYS['cld']:
+				$delivery = false;
+				break;
+		}
+
+		if ( ! is_null( $delivery ) ) {
+			foreach ( $post_ids as $id ) {
+				if ( true === $delivery ) {
+					$this->media->sync->managers['unsync']->unsync_attachment( $id );
+				}
+				$this->set_attachment_delivery( $id, $delivery );
+			}
+
+			/**
+			 * Action to flush delivery caches.
+			 *
+			 * @hook   cloudinary_flush_cache
+			 * @since  3.0.0
+			 */
+			do_action( 'cloudinary_flush_cache' );
+		}
+
+		return $location;
+	}
+
+	/**
+	 * Get the actions for delivery types.
+	 *
+	 * @param int|null $attachment_id The attachment ID to get action or generic action.
+	 *
+	 * @return array
+	 */
+	protected function get_actions( $attachment_id = null ) {
+		$actions = array();
+
+		if (
+			null === $attachment_id
+			|| $this->plugin->get_component( 'delivery' )->is_deliverable( $attachment_id )
+		) {
+			$actions = array(
+				self::BYPASS_KEYS['wp'] => __( 'Deliver from WordPress', 'cloudinary' ),
+			);
+		}
+
+		return $actions;
+	}
+
+	/**
+	 * Add an inline action for manual sync.
+	 *
+	 * @param array    $actions All actions.
+	 * @param \WP_Post $post    The current post object.
+	 *
+	 * @return array
+	 */
+	public function add_inline_action( $actions, $post ) {
+
+		$bypassed = $this->is_bypassed( $post->ID );
+		$action   = $bypassed ? self::BYPASS_KEYS['cld'] : self::BYPASS_KEYS['wp'];
+		$messages = $this->get_actions( $post->ID );
+		if ( ! empty( $messages[ $action ] ) ) {
+
+			// Set url for action handling.
+			$action_url = add_query_arg(
+				array(
+					'action'   => $action,
+					'media[]'  => $post->ID,
+					'_wpnonce' => wp_create_nonce( 'bulk-media' ),
+				),
+				'upload.php'
+			);
+
+			// Add link th actions.
+			$actions[ $action ] = sprintf(
+				'<a href="%1$s" aria-label="%2$s">%2$s</a>',
+				$action_url,
+				$messages[ $action ]
+			);
+		}
+
+		return $actions;
+	}
+
+	/**
+	 * Handle saving the deliver setting in single attachment edit.
+	 *
+	 * @param array $post_data The post array (unused).
+	 * @param array $data      The submitted data to save.
+	 *
+	 * @return array
+	 */
+	public function handle_save_attachment_delivery( $post_data, $data ) {
+		$this->set_attachment_delivery( $data['ID'], isset( $data['attachment_delivery'] ) );
+
+		return $post_data;
+	}
+
+	/**
+	 * Update an attachments delivery.
+	 *
+	 * @param int  $attachment_id The attachment ID to update.
+	 * @param bool $bypass        True to bypass and deliver from WordPress, false deliver from Cloudinary.
+	 */
+	public function set_attachment_delivery( $attachment_id, $bypass ) {
+		$this->media->update_post_meta( $attachment_id, Sync::META_KEYS['bypass'], $bypass );
+		$this->media->sync->set_signature_item( $attachment_id, 'delivery' );
+	}
+
+	/**
+	 * Add bulk actions.
+	 *
+	 * @param array $actions Current actions to add to.
+	 *
+	 * @return array
+	 */
+	public function add_bulk_actions( $actions ) {
+		$cloudinary_actions = $this->get_actions();
+
+		return array_merge( $cloudinary_actions, $actions );
+	}
+
+	/**
+	 * Filter states to indicate delivery when bypassed.
+	 *
+	 * @param array $status        The current status.
+	 * @param int   $attachment_id The attachment ID.
+	 *
+	 * @return array
+	 */
+	public function filter_status( $status, $attachment_id ) {
+		if ( ! empty( $status ) && $this->is_bypassed( $attachment_id ) ) {
+			$actions = $this->get_actions( $attachment_id );
+			$status  = array(
+				'state' => 'info',
+				'note'  => $actions[ self::BYPASS_KEYS['wp'] ],
+			);
+		}
+
+		return $status;
+	}
+
+	/**
+	 * Adds the deliver checkbox on the single image edit screen.
+	 *
+	 * @param WP_Post $attachment The attachment post object.
+	 */
+	public function delivery_actions( $attachment ) {
+		$actions = $this->get_actions( $attachment->ID );
+		if ( ! empty( $actions[ self::BYPASS_KEYS['wp'] ] ) ) :
+			?>
+			<div class="misc-pub-section misc-pub-delivery">
+				<label><input type="checkbox" name="attachment_delivery" value="true" <?php checked( true, $this->is_bypassed( $attachment->ID ) ); ?> />
+					<?php echo esc_html( $actions[ self::BYPASS_KEYS['wp'] ] ); ?>
+				</label>
+			</div>
+			<?php
+		endif;
+	}
+
+	/**
+	 * Filter if the asset can be synced if it's bypassed.
+	 *
+	 * @param bool $can           The current flag to sync.
+	 * @param int  $attachment_id The attachment ID.
+	 *
+	 * @return bool
+	 */
+	public function can_sync( $can, $attachment_id ) {
+		if ( $this->is_bypassed( $attachment_id ) ) {
+			$can = false;
+		}
+
+		return $can;
+	}
+
+	/**
+	 * Check if an attachment is bypassing Cloudinary.
+	 *
+	 * @param int $attachment_id The attachment ID.
+	 *
+	 * @return bool
+	 */
+	public function is_bypassed( $attachment_id ) {
+		return (bool) $this->media->get_post_meta( $attachment_id, Sync::META_KEYS['bypass'], true );
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_delivery_class-lazy-load.php.html b/docs/php_delivery_class-lazy-load.php.html new file mode 100644 index 000000000..551ebb461 --- /dev/null +++ b/docs/php_delivery_class-lazy-load.php.html @@ -0,0 +1,621 @@ + + + + + Source: php/delivery/class-lazy-load.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/delivery/class-lazy-load.php

+ + + + + + + +
+
+
<?php
+/**
+ * Lazy Load.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Delivery;
+
+use Cloudinary\Connect\Api;
+use Cloudinary\Delivery_Feature;
+use Cloudinary\Plugin;
+use Cloudinary\String_Replace;
+use Cloudinary\UI\Component\HTML;
+use Cloudinary\Utils;
+
+/**
+ * Class Responsive_Breakpoints
+ *
+ * @package Cloudinary
+ */
+class Lazy_Load extends Delivery_Feature {
+
+	/**
+	 * Holds the settings slug.
+	 *
+	 * @var string
+	 */
+	protected $settings_slug = 'media_display';
+
+	/**
+	 * Holds the enabler slug.
+	 *
+	 * @var string
+	 */
+	protected $enable_slug = 'use_lazy_load';
+
+	/**
+	 * Lazy_Load constructor.
+	 *
+	 * @param \Cloudinary\Plugin $plugin The main instance of the plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+		parent::__construct( $plugin );
+		add_filter( 'cloudinary_image_tag-disabled', array( $this, 'js_noscript' ), 10, 2 );
+	}
+
+	/**
+	 * Setup hooks.
+	 */
+	public function setup_hooks() {
+		add_action( 'wp', array( $this, 'init_lazy_script_maybe' ) );
+		add_filter( 'cloudinary_lazy_load_bypass', array( $this, 'bypass_lazy_load' ), 10, 2 );
+	}
+
+	/**
+	 * Check if the page request is a dynamic lazy load request.
+	 */
+	public function init_lazy_script_maybe() {
+
+		$flag = Utils::get_sanitized_text( 'cloudinary_lazy_load_loader' );
+		if ( $flag ) {
+			$expires = HOUR_IN_SECONDS;
+			header( 'Cache-Control: max-age=' . $expires );
+			cache_javascript_headers();
+			echo $this->get_inline_script(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+			exit;
+		}
+	}
+
+	/**
+	 * Maybe bypass the lazy load.
+	 *
+	 * @param bool  $bypass      Whether to bypass lazy load.
+	 * @param array $tag_element The TAG element.
+	 *
+	 * @return bool
+	 */
+	public function bypass_lazy_load( $bypass, $tag_element ) {
+
+		// Bypass if eager loading.
+		if ( ! empty( $tag_element['loading'] ) && 'eager' === $tag_element['loading'] ) {
+			$bypass = true;
+		}
+
+		// Sources on Picture tag are not lazyloaded.
+		if ( 'source' === $tag_element['tag'] ) {
+			$bypass = true;
+		}
+
+		/**
+		 * Filter the classes that bypass lazy loading.
+		 *
+		 * @hook   cloudinary_lazy_load_bypass_classes
+		 * @since  3.0.9
+		 *
+		 * @param $classes {array} Classes that bypass the Lazy Load.
+		 *
+		 * @return {bool}
+		 *
+		 * @example
+		 * <?php
+		 *
+		 * // Extend bypass lazy load classes to include `skip-lazy`.
+		 * add_filter(
+		 *    'cloudinary_lazy_load_bypass_classes',
+		 *    function( $classes ) {
+		 *         $classes[] = 'skip-lazy';
+		 *         return $classes;
+		 *    }
+		 * );
+		 */
+		$bypass_classes = apply_filters( 'cloudinary_lazy_load_bypass_classes', array( 'cld-bypass-lazy' ) );
+
+		if ( ! $bypass && ! empty( array_intersect( $bypass_classes, $tag_element['atts']['class'] ) ) ) {
+			$bypass = true;
+		}
+
+		return $bypass;
+	}
+
+	/**
+	 * Get the inline script for lazy load.
+	 *
+	 * @retrun string
+	 */
+	public function get_inline_script() {
+		$config = $this->get_config();
+
+		return 'var CLDLB = ' . wp_json_encode( $config ) . ';' . file_get_contents( $this->plugin->dir_path . 'js/inline-loader.js' );
+	}
+
+	/**
+	 * Get the config for lazy load.
+	 *
+	 * @return array
+	 */
+	public function get_config() {
+		$config = $this->config; // Get top most config.
+		if ( 'off' !== $config['lazy_placeholder'] ) {
+			$config['placeholder'] = API::generate_transformation_string( $this->get_placeholder_transformations( $config['lazy_placeholder'] ) );
+		}
+		$config['base_url'] = $this->media->base_url;
+
+		return $config;
+	}
+
+	/**
+	 * Wrap image tags in noscript to allow no-javascript browsers to get images.
+	 *
+	 * @param string $tag         The original html tag.
+	 * @param array  $tag_element The original tag_element.
+	 *
+	 * @return string
+	 */
+	public function js_noscript( $tag, $tag_element ) {
+
+		$options          = $tag_element['atts'];
+		$options['class'] = implode( ' ', $options['class'] );
+
+		unset(
+			$options['srcset'],
+			$options['sizes'],
+			$options['loading'],
+			$options['src'],
+			$options['class']
+		);
+		$atts = array(
+			'data-image' => wp_json_encode( $options ),
+		);
+
+		return HTML::build_tag( 'noscript', $atts ) . $tag . HTML::build_tag( 'noscript', null, 'close' );
+	}
+
+	/**
+	 * Get the placeholder generation transformations.
+	 *
+	 * @param string $placeholder The placeholder to get.
+	 *
+	 * @return array
+	 */
+	public function get_placeholder_transformations( $placeholder ) {
+
+		$transformations = array(
+			'predominant' => array(
+				array(
+					'$currWidth'  => 'w',
+					'$currHeight' => 'h',
+				),
+				array(
+					'width'        => 'iw_div_2',
+					'aspect_ratio' => 1,
+					'crop'         => 'pad',
+					'background'   => 'auto',
+				),
+				array(
+					'crop'    => 'crop',
+					'width'   => 10,
+					'height'  => 10,
+					'gravity' => 'north_east',
+				),
+				array(
+					'width'  => '$currWidth',
+					'height' => '$currHeight',
+					'crop'   => 'fill',
+				),
+				array(
+					'fetch_format' => 'auto',
+					'quality'      => 'auto',
+				),
+			),
+			'vectorize'   => array(
+				array(
+					'effect'       => 'vectorize:3:0.1',
+					'fetch_format' => 'svg',
+				),
+			),
+			'blur'        => array(
+				array(
+					'effect'       => 'blur:2000',
+					'quality'      => 1,
+					'fetch_format' => 'auto',
+				),
+			),
+			'pixelate'    => array(
+				array(
+					'effect'       => 'pixelate',
+					'quality'      => 1,
+					'fetch_format' => 'auto',
+				),
+			),
+		);
+
+		return $transformations[ $placeholder ];
+	}
+
+	/**
+	 * Add features to a tag element set.
+	 *
+	 * @param array $tag_element The tag element set.
+	 *
+	 * @return array
+	 */
+	public function add_features( $tag_element ) {
+		if ( Utils::is_amp() ) {
+			return $tag_element;
+		}
+		static $has_loader = false;
+		// Bypass file formats that shouldn't be lazy loaded.
+		if (
+			in_array(
+				$tag_element['format'],
+				/**
+				 * Filter out file formats for Lazy Load.
+				 *
+				 * @hook  cloudinary_lazy_load_bypass_formats
+				 * @since 3.0.0
+				 *
+				 * @param $formats {array) The list of formats to exclude.
+				 *
+				 * @return {array}
+				 */
+				apply_filters( 'cloudinary_lazy_load_bypass_formats', array( 'svg' ) ),
+				true
+			)
+		) {
+			return $tag_element;
+		}
+
+		/**
+		 * Short circuit the lazy load.
+		 *
+		 * @hook  cloudinary_lazy_load_bypass
+		 *
+		 * @since 3.0.9
+		 *
+		 * @param $short_circuit {bool}  The short circuit value.
+		 * @param $tag_element   {array} The tag element.
+		 *
+		 * @return {bool}
+		 *
+		 * @example
+		 * <?php
+		 *
+		 * // Bypass lazy load for images with ID `feature-image`.
+		 * add_filter(
+		 *    'cloudinary_lazy_load_bypass',
+		 *    function( $bypass, $tag_element ) {
+		 *        if ( 'feature-image! === $tag_element['id'] ) {
+		 *            $bypass = true;
+		 *        }
+		 *        return $bypass;
+		 *    }
+		 * );
+		 */
+		if ( apply_filters( 'cloudinary_lazy_load_bypass', false, $tag_element ) ) {
+			return $tag_element;
+		}
+
+		$sizes = array(
+			$tag_element['width'],
+			$tag_element['height'],
+		);
+
+		// Capture the original size.
+		$tag_element['atts']['data-size'] = array_filter( $sizes );
+
+		$colors    = $this->config['lazy_custom_color'];
+		$animation = array(
+			$colors,
+		);
+		if ( 'on' === $this->config['lazy_animate'] ) {
+			preg_match_all( '/(\d\.*)+/', $colors, $matched );
+			$fade        = $matched[0];
+			$fade[3]     = 0.1;
+			$fade        = 'rgba(' . implode( ',', $fade ) . ')';
+			$animation[] = $fade;
+			$animation[] = $colors;
+		}
+		$colors = implode( ';', $animation );
+
+		// Add svg placeholder.
+		$tag_element['atts']['src'] = Utils::svg_encoded( $tag_element['atts']['width'], $tag_element['atts']['height'], $colors );
+		if ( isset( $tag_element['atts']['srcset'] ) ) {
+			$tag_element['atts']['data-srcset'] = $tag_element['atts']['srcset'];
+			$tag_element['atts']['data-sizes']  = $tag_element['atts']['sizes'];
+			unset( $tag_element['atts']['srcset'], $tag_element['atts']['sizes'] );
+		}
+		if ( ! Utils::is_admin() ) {
+			$tag_element['atts']['data-delivery'] = $this->media->get_media_delivery( $tag_element['id'] );
+			if ( empty( $tag_element['atts']['onload'] ) ) {
+				$tag_element['atts']['onload'] = '';
+			}
+			$loader = 'null;';
+			if ( ! $has_loader && Utils::is_frontend_ajax() ) {
+				$has_loader = true;
+				$url        = add_query_arg( 'cloudinary_lazy_load_loader', true, trailingslashit( Utils::home_url() ) );
+				$loader     = 'document.body.appendChild(document.createElement(\'script\')).src=\'' . $url . '\';this.onload=null;';
+			}
+			// Since we're appending to the onload, check it isn't already in, as it may run twice i.e full page caching.
+			if ( false === strpos( $tag_element['atts']['onload'], 'CLDBind' ) ) {
+				$tag_element['atts']['data-cloudinary'] = 'lazy';
+				$tag_element['atts']['onload']         .= ';window.CLDBind?CLDBind(this):' . $loader;
+			}
+		}
+
+		return $tag_element;
+	}
+
+	/**
+	 * Register front end hooks.
+	 */
+	public function register_assets() {
+		wp_register_script( 'cld-lazy-load', $this->plugin->dir_url . 'js/lazy-load.js', null, $this->plugin->version, false );
+	}
+
+	/**
+	 * Apply front end filters on the enqueue_assets hook.
+	 */
+	public function enqueue_assets() {
+		if ( Utils::is_frontend_ajax() ) {
+			return;
+		}
+
+		ob_start();
+		Utils::print_inline_tag( $this->get_inline_script() );
+		$script        = ob_get_clean();
+		$script_holder = '<meta name="cld-loader">';
+		$allow         = array(
+			'meta' => array(
+				'name' => true,
+			),
+		);
+		echo wp_kses( $script_holder, $allow );
+		String_Replace::replace( $script_holder, $script );
+	}
+
+	/**
+	 * Enqueue assets if not AMP.
+	 */
+	public function maybe_enqueue_assets() {
+		if ( ! Utils::is_amp() ) {
+			parent::maybe_enqueue_assets();
+		}
+	}
+
+	/**
+	 * Check if component is active.
+	 *
+	 * @return bool
+	 */
+	public function is_active() {
+
+		return ( ! Utils::is_admin() && ! Utils::is_rest_api() ) || Utils::is_frontend_ajax();
+	}
+
+	/**
+	 * Add the settings.
+	 *
+	 * @param array $pages The pages to add to.
+	 *
+	 * @return array
+	 */
+	public function register_settings( $pages ) {
+
+		$pages['lazy_loading'] = array(
+			'page_title'          => __( 'Lazy loading', 'cloudinary' ),
+			'menu_title'          => __( 'Lazy loading', 'cloudinary' ),
+			'priority'            => 5,
+			'requires_connection' => true,
+			'sidebar'             => true,
+			'settings'            => array(
+				array(
+					'type'        => 'panel',
+					'title'       => __( 'Lazy Loading', 'cloudinary' ),
+					'priority'    => 9,
+					'option_name' => 'media_display',
+					array(
+						'type' => 'tabs',
+						'tabs' => array(
+							'image_setting' => array(
+								'text' => __( 'Settings', 'cloudinary' ),
+								'id'   => 'settings',
+							),
+							'image_preview' => array(
+								'text' => __( 'Preview', 'cloudinary' ),
+								'id'   => 'preview',
+							),
+						),
+					),
+					array(
+						'type' => 'row',
+						array(
+							'type'   => 'column',
+							'tab_id' => 'settings',
+							array(
+								'type'               => 'on_off',
+								'description'        => __( 'Enable lazy loading', 'cloudinary' ),
+								'tooltip_text'       => __( 'Lazy loading delays the initialization of your web assets to improve page load times.', 'cloudinary' ),
+								'optimisation_title' => __( 'Lazy loading', 'cloudinary' ),
+								'slug'               => 'use_lazy_load',
+								'default'            => 'on',
+							),
+							array(
+								'type'      => 'group',
+								'condition' => array(
+									'use_lazy_load' => true,
+								),
+								array(
+									'type'         => 'text',
+									'title'        => __( 'Lazy loading threshold', 'cloudinary' ),
+									'tooltip_text' => __( 'How far down the page to start lazy loading assets.', 'cloudinary' ),
+									'slug'         => 'lazy_threshold',
+									'attributes'   => array(
+										'style'            => array(
+											'width:100px;display:block;',
+										),
+										'data-auto-suffix' => '*px;em;rem;vh',
+									),
+									'default'      => '100px',
+								),
+								array(
+									'type'    => 'tag',
+									'element' => 'hr',
+								),
+								array(
+									'type'        => 'color',
+									'title'       => __( 'Pre-loader color', 'cloudinary' ),
+									'description' => __(
+										'On page load, the pre-loader is used to fill the space while the image is downloaded, preventing content shift.',
+										'cloudinary'
+									),
+									'slug'        => 'lazy_custom_color',
+									'default'     => 'rgba(153,153,153,0.5)',
+								),
+								array(
+									'type'        => 'on_off',
+									'description' => __( 'Pre-loader animation', 'cloudinary' ),
+									'slug'        => 'lazy_animate',
+									'default'     => 'on',
+								),
+								array(
+									'type'    => 'tag',
+									'element' => 'hr',
+								),
+								array(
+									'type'        => 'radio',
+									'title'       => __( 'Placeholder generation type', 'cloudinary' ),
+									'description' => __(
+										"Placeholders are low-res representations of the image, that's loaded below the fold. They are then replaced with the actual image, just before it comes into view.",
+										'cloudinary'
+									),
+									'slug'        => 'lazy_placeholder',
+									'default'     => 'blur',
+									'condition'   => array(
+										'use_lazy_load' => true,
+									),
+									'options'     => array(
+										'blur'        => __( 'Blur', 'cloudinary' ),
+										'pixelate'    => __( 'Pixelate', 'cloudinary' ),
+										'vectorize'   => __( 'Vectorize', 'cloudinary' ),
+										'predominant' => __( 'Dominant Color', 'cloudinary' ),
+										'off'         => __( 'Off', 'cloudinary' ),
+									),
+								),
+								array(
+									'type'    => 'tag',
+									'element' => 'hr',
+								),
+								array(
+									'type'         => 'select',
+									'slug'         => 'dpr',
+									'priority'     => 8,
+									'title'        => __( 'DPR settings', 'cloudinary' ),
+									'tooltip_text' => __( 'The device pixel ratio to use for your generated images.', 'cloudinary' ),
+									'default'      => '2X',
+									'options'      => array(
+										'off' => __( 'Off', 'cloudinary' ),
+										'2X'  => __( 'Auto (2x)', 'cloudinary' ),
+										'max' => __( 'Max DPR', 'cloudinary' ),
+									),
+								),
+							),
+						),
+						array(
+							'type'      => 'column',
+							'tab_id'    => 'preview',
+							'class'     => array(
+								'cld-ui-preview',
+							),
+							'condition' => array(
+								'use_lazy_load' => true,
+							),
+							array(
+								'type'    => 'lazyload_preview',
+								'title'   => __( 'Preview', 'cloudinary' ),
+								'slug'    => 'lazyload_preview',
+								'default' => CLOUDINARY_ENDPOINTS_PREVIEW_IMAGE . 'w_600/sample.jpg',
+							),
+						),
+					),
+					array(
+						'type'  => 'info_box',
+						'icon'  => $this->plugin->dir_url . 'css/images/academy-icon.svg',
+						'title' => __( 'Need help?', 'cloudinary' ),
+						'text'  => sprintf(
+							// Translators: The HTML for opening and closing link tags.
+							__(
+								'Watch free lessons on how to use the Lazy Load Settings in the %1$sCloudinary Academy%2$s.',
+								'cloudinary'
+							),
+							'<a href="https://training.cloudinary.com/learn/course/introduction-to-cloudinary-for-wordpress-administrators-70-minute-course-1h85/lessons/lazily-loading-and-delivering-responsive-images-1003?page=1" target="_blank" rel="noopener noreferrer">',
+							'</a>'
+						),
+					),
+				),
+			),
+		);
+
+		return $pages;
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_delivery_class-responsive-breakpoints.php.html b/docs/php_delivery_class-responsive-breakpoints.php.html new file mode 100644 index 000000000..3981498ce --- /dev/null +++ b/docs/php_delivery_class-responsive-breakpoints.php.html @@ -0,0 +1,268 @@ + + + + + Source: php/delivery/class-responsive-breakpoints.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/delivery/class-responsive-breakpoints.php

+ + + + + + + +
+
+
<?php
+/**
+ * Responsive breakpoints.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Delivery;
+
+use Cloudinary\Delivery_Feature;
+use Cloudinary\Connect\Api;
+use Cloudinary\Utils;
+
+/**
+ * Class Responsive_Breakpoints
+ *
+ * @package Cloudinary
+ */
+class Responsive_Breakpoints extends Delivery_Feature {
+
+	/**
+	 * The feature application priority.
+	 *
+	 * @var int
+	 */
+	protected $priority = 9;
+
+	/**
+	 * Holds the settings slug.
+	 *
+	 * @var string
+	 */
+	protected $settings_slug = 'media_display';
+
+	/**
+	 * Holds the enabler slug.
+	 *
+	 * @var string
+	 */
+	protected $enable_slug = 'enable_breakpoints';
+
+	/**
+	 * Setup hooks used when enabled.
+	 */
+	protected function setup_hooks() {
+		add_action( 'cloudinary_init_delivery', array( $this, 'remove_srcset_filter' ) );
+		add_filter( 'cloudinary_apply_breakpoints', '__return_false' );
+	}
+
+	/**
+	 * Add features to a tag element set.
+	 *
+	 * @param array $tag_element The tag element set.
+	 *
+	 * @return array
+	 */
+	public function add_features( $tag_element ) {
+		if ( 'upload' !== $this->media->get_media_delivery( $tag_element['id'] ) ) {
+			return $tag_element;
+		}
+
+		/**
+		 * Do not add the responsive breakpoint features.
+		 *
+		 * @hook   cloudinary_skip_responsive_breakpoints
+		 * @since  3.2.4
+		 * @default {false}
+		 *
+		 * @param $skip_features {bool}  True to skip adding in the features.
+		 * @param $tag_element   {array} The tag element.
+		 *
+		 * @return {bool}
+		 */
+		if ( apply_filters( 'cloudinary_skip_responsive_breakpoints', false, $tag_element ) ) {
+			return $tag_element;
+		}
+
+		// Bypass file formats that shouldn't have Responsive Images.
+		if (
+			in_array(
+				$tag_element['format'],
+				/**
+				 * Filter out file formats for Responsive Images.
+				 *
+				 * @hook  cloudinary_responsive_images_bypass_formats
+				 * @since 3.0.9
+				 *
+				 * @param $formats {array) The list of formats to exclude.
+				 */
+				apply_filters( 'cloudinary_responsive_images_bypass_formats', array( 'svg' ) ),
+				true
+			)
+		) {
+			return $tag_element;
+		}
+
+		if ( Utils::is_amp() ) {
+			$tag_element['atts']['layout'] = 'responsive';
+		} else {
+			$tag_element['atts']['data-responsive'] = true;
+		}
+		unset( $tag_element['atts']['srcset'], $tag_element['atts']['sizes'] );
+
+		$lazy = $this->plugin->get_component( 'lazy_load' );
+
+		/**
+		 * Short circuit the lazy load.
+		 *
+		 * @hook  cloudinary_lazy_load_bypass
+		 * @since 3.0.9
+		 *
+		 * @param $short_circuit {bool}  The short circuit value.
+		 * @param $tag_element   {array} The tag element.
+		 */
+		if ( is_null( $lazy ) || ! $lazy->is_enabled() || Utils::is_amp() || apply_filters( 'cloudinary_lazy_load_bypass', false, $tag_element ) ) {
+			$tag_element = $this->apply_breakpoints( $tag_element );
+		}
+
+		return $tag_element;
+	}
+
+	/**
+	 * Apply srcset breakpoints if lazy loading is off.
+	 *
+	 * @param array $tag_element The tag element array.
+	 *
+	 * @return array
+	 */
+	protected function apply_breakpoints( $tag_element ) {
+		if ( empty( $tag_element['id'] ) ) {
+			return $tag_element;
+		}
+
+		$settings  = $this->settings->get_value( 'media_display' );
+		$max       = $settings['max_width'];
+		$min       = $settings['min_width'];
+		$width     = (int) $tag_element['width'];
+		$height    = (int) $tag_element['height'];
+		$debug_key = 'responsive_breakpoints_' . $tag_element['id'];
+
+		if ( ! $width || ! $height ) {
+			Utils::log( 'Missing width or height for image with ID: ' . $tag_element['id'], $debug_key );
+			return $tag_element;
+		}
+
+		$size_tag            = '-' . $width . 'x' . $height . '.';
+		$step                = $settings['pixel_step'];
+		$ratio               = $width / $height;
+		$src                 = $tag_element['atts']['src'];
+		$size_transformation = $this->media->get_crop_from_transformation( $this->media->get_transformations_from_string( $src ) );
+		$size_string         = Api::generate_transformation_string( array( $size_transformation ) );
+		$breakpoints         = array();
+		while ( $max >= $min ) {
+			if ( $width >= $max ) {
+				$size_transformation['width']  = $max;
+				$size_transformation['height'] = floor( $max / $ratio );
+				$new_size_tag                  = '-' . $size_transformation['width'] . 'x' . $size_transformation['height'] . '.';
+				$new_size                      = Api::generate_transformation_string( array( $size_transformation ) );
+				$new_url                       = str_replace( $size_string, $new_size, $src );
+				$new_url                       = str_replace( $size_tag, $new_size_tag, $new_url );
+				$breakpoints[]                 = $new_url . ' ' . $max . 'w';
+			}
+			$max -= $step;
+		}
+
+		if ( ! empty( $breakpoints ) ) {
+			array_unshift( $breakpoints, $src . ' ' . $width . 'w' );
+			$tag_element['atts']['srcset'] = implode( ', ', $breakpoints );
+			$tag_element['atts']['sizes']  = '(max-width: ' . $width . 'px) 100vw, ' . $width . 'px';
+		}
+
+		return $tag_element;
+	}
+
+	/**
+	 * Remove the legacy breakpoints sync type and filters.
+	 *
+	 * @param array $structs The sync types structure.
+	 *
+	 * @return array
+	 */
+	public function remove_legacy_breakpoints( $structs ) {
+		unset( $structs['breakpoints'] );
+
+		return $structs;
+	}
+
+	/**
+	 * Remove the srcset filter.
+	 */
+	public function remove_srcset_filter() {
+		remove_filter( 'wp_calculate_image_srcset', array( $this->media, 'image_srcset' ), 10 );
+	}
+
+	/**
+	 * Setup the class.
+	 */
+	public function setup() {
+		parent::setup();
+		add_filter( 'cloudinary_sync_base_struct', array( $this, 'remove_legacy_breakpoints' ) );
+	}
+
+	/**
+	 * Create Settings.
+	 */
+	protected function create_settings() {
+		$this->settings = $this->media->get_settings()->get_setting( 'image_display' );
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_media_class-gallery.php.html b/docs/php_media_class-gallery.php.html new file mode 100644 index 000000000..c64162386 --- /dev/null +++ b/docs/php_media_class-gallery.php.html @@ -0,0 +1,786 @@ + + + + + Source: php/media/class-gallery.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/media/class-gallery.php

+ + + + + + + +
+
+
<?php
+/**
+ * Manages Gallery Widget and Block settings.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Media;
+
+use Cloudinary\Plugin;
+use Cloudinary\Settings\Setting;
+use Cloudinary\Media;
+use Cloudinary\REST_API;
+use Cloudinary\Settings_Component;
+use Cloudinary\Utils;
+use WP_Screen;
+
+/**
+ * Class Gallery.
+ *
+ * Handles gallery.
+ */
+class Gallery extends Settings_Component {
+
+	/**
+	 * The enqueue script handle for the gallery widget lib.
+	 *
+	 * @var string
+	 */
+	const GALLERY_LIBRARY_HANDLE = 'cld-gallery';
+
+	/**
+	 * This is a "false" mime type for GLB files.
+	 *
+	 * Canonically, the correct mime type for GLB files is `model/gltf-binary`,
+	 * however we need to fake an `image/` mime type to make the gallery
+	 * picker to list GLB files.
+	 */
+	const GALLERY_GLB_MIME_TYPE = 'image/glb';
+
+	/**
+	 * Holds the settings slug.
+	 *
+	 * @var string
+	 */
+	public $settings_slug = 'gallery';
+
+	/**
+	 * Holds the sync settings object.
+	 *
+	 * @var Setting
+	 */
+	public $settings;
+
+	/**
+	 * The default config in case no settings are saved.
+	 *
+	 * @var array
+	 */
+	public static $default_config = array(
+		'mediaAssets'      => array(),
+		'transition'       => 'fade',
+		'aspectRatio'      => '3:4',
+		'navigation'       => 'always',
+		'zoom'             => true,
+		'carouselLocation' => 'top',
+		'carouselOffset'   => 5,
+		'carouselStyle'    => 'thumbnails',
+		'displayProps'     => array(
+			'mode'    => 'classic',
+			'columns' => 1,
+		),
+		'indicatorProps'   => array( 'shape' => 'round' ),
+		'themeProps'       => array(
+			'primary'   => '#cf2e2e',
+			'onPrimary' => '#000000',
+			'active'    => '#777777',
+		),
+		'zoomProps'        => array(
+			'type'           => 'popup',
+			'viewerPosition' => 'bottom',
+			'trigger'        => 'click',
+		),
+		'thumbnailProps'   => array(
+			'width'                  => 64,
+			'height'                 => 64,
+			'navigationShape'        => 'radius',
+			'selectedStyle'          => 'gradient',
+			'selectedBorderPosition' => 'all',
+			'selectedBorderWidth'    => 4,
+			'mediaSymbolShape'       => 'round',
+		),
+		'customSettings'   => '',
+	);
+
+	/**
+	 * Holds instance of the Media class.
+	 *
+	 * @var Media
+	 */
+	public $media;
+
+	/**
+	 * Holds the current config.
+	 *
+	 * @var array
+	 */
+	protected $config = array();
+
+	/**
+	 * Init gallery.
+	 *
+	 * @param Plugin $plugin Main class instance.
+	 */
+	public function __construct( Plugin $plugin ) {
+		parent::__construct( $plugin );
+		$this->media = $plugin->get_component( 'media' );
+		$this->setup_hooks();
+	}
+
+	/**
+	 * Gets the gallery settings in the expected json format.
+	 *
+	 * @return array
+	 */
+	public function get_config() {
+		$screen = null;
+
+		if ( function_exists( 'get_current_screen' ) ) {
+			$screen = get_current_screen();
+		}
+
+		$config = ! empty( $this->settings->get_value( 'gallery_config' ) ) ?
+			$this->settings->get_value( 'gallery_config' ) :
+			self::$default_config;
+
+		$this->config = $this->maybe_decode_config( $config );
+		$config       = Utils::array_filter_recursive( $this->config ); // Remove empty values.
+
+		$config['cloudName'] = $this->plugin->components['connect']->get_cloud_name();
+
+		/**
+		 * Filter the gallery HTML container.
+		 *
+		 * @hook    cloudinary_gallery_html_container
+		 * @default ''
+		 *
+		 * @param $selector {string} The target HTML selector.
+		 *
+		 * @return {string}
+		 */
+		$config['container'] = apply_filters( 'cloudinary_gallery_html_container', '' );
+
+		$credentials = $this->plugin->components['connect']->get_credentials();
+
+		// Do not use privateCdn and secureDistribution on gallery settings page.
+		if (
+			! empty( $credentials['cname'] )
+			&& ( ! $screen instanceof WP_Screen || 'cloudinary_page_cloudinary_gallery' !== $screen->id )
+		) {
+			$config['secureDistribution'] = $credentials['cname'];
+			$config['privateCdn']         = true;
+		}
+
+		/**
+		 * Filter the gallery configuration.
+		 *
+		 * @hook cloudinary_gallery_config
+		 *
+		 * @param $config {array} The current gallery config.
+		 *
+		 * @return {array}
+		 */
+		$config = apply_filters( 'cloudinary_gallery_config', $config );
+
+		$config['queryParam'] = 'AA';
+
+		// Make sure custom settings are a json string.
+		if ( ! empty( $config['customSettings'] ) && is_array( $config['customSettings'] ) ) {
+			$config['customSettings'] = wp_json_encode( $config['customSettings'] );
+		}
+
+		return $config;
+	}
+
+	/**
+	 * Register frontend assets for the gallery.
+	 */
+	public function enqueue_gallery_library() {
+		// Bail enqueuing the scripts if conditions aren't met.
+		if ( ! $this->maybe_enqueue_scripts() ) {
+			return;
+		}
+
+		wp_enqueue_script(
+			self::GALLERY_LIBRARY_HANDLE,
+			CLOUDINARY_ENDPOINTS_GALLERY,
+			array(),
+			$this->plugin->version,
+			true
+		);
+
+		$json_config = wp_json_encode( $this->get_config() );
+		wp_add_inline_script( self::GALLERY_LIBRARY_HANDLE, "var CLD_GALLERY_CONFIG = {$json_config};" );
+
+		wp_enqueue_script(
+			'cloudinary-gallery-init',
+			$this->plugin->dir_url . 'js/gallery-init.js',
+			array( self::GALLERY_LIBRARY_HANDLE ),
+			$this->plugin->version,
+			true
+		);
+	}
+
+	/**
+	 * Enqueue admin UI scripts if needed.
+	 */
+	public function enqueue_admin_scripts() {
+		if ( Utils::get_active_setting() !== $this->settings->get_setting( $this->settings_slug ) ) {
+			return;
+		}
+
+		$this->block_editor_scripts_styles();
+
+		wp_enqueue_style(
+			'cloudinary-gallery-settings-css',
+			$this->plugin->dir_url . 'css/gallery-ui.css',
+			array(),
+			$this->plugin->version
+		);
+
+		$script = array(
+			'slug'      => 'gallery_config',
+			'src'       => $this->plugin->dir_url . 'js/gallery.js',
+			'in_footer' => true,
+		);
+
+		$asset = $this->get_asset();
+		wp_enqueue_script( $script['slug'], $script['src'], $asset['dependencies'], $asset['version'], $script['in_footer'] );
+
+		$color_palette = array_filter( (array) get_theme_support( 'editor-color-palette' ) );
+		if ( ! empty( $color_palette ) ) {
+			$color_palette = array_shift( $color_palette );
+		}
+		$color_palette = wp_json_encode( $color_palette );
+		wp_add_inline_script( $script['slug'], "var CLD_THEME_COLORS = $color_palette;", 'before' );
+	}
+
+	/**
+	 * Retrieve asset dependencies.
+	 *
+	 * @return array
+	 */
+	private function get_asset() {
+		$asset = require $this->plugin->dir_path . 'js/gallery.asset.php'; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+
+		$asset['dependencies'] = array_filter(
+			$asset['dependencies'],
+			static function ( $dependency ) {
+				return false === strpos( $dependency, '/' );
+			}
+		);
+
+		return $asset;
+	}
+
+	/**
+	 * Register blocked editor assets for the gallery.
+	 */
+	public function block_editor_scripts_styles() {
+		$this->enqueue_gallery_library();
+
+		wp_enqueue_style(
+			'cloudinary-gallery-block-css',
+			$this->plugin->dir_url . 'css/gallery-block.css',
+			array(),
+			$this->plugin->version
+		);
+
+		wp_enqueue_script(
+			'cloudinary-gallery-block-js',
+			$this->plugin->dir_url . 'js/gallery-block.js',
+			array( 'wp-blocks', 'wp-editor', 'wp-element', self::GALLERY_LIBRARY_HANDLE ),
+			$this->plugin->version,
+			true
+		);
+
+		wp_add_inline_script( 'cloudinary-gallery-block-js', 'var CLD_REST_ENDPOINT = "/' . REST_API::BASE . '";', 'before' );
+	}
+
+	/**
+	 * Fetches image public id and transformations.
+	 *
+	 * @param array|int[]|array[] $images An array of image IDs or a multi-dimensional array with url and id keys.
+	 *
+	 * @return array
+	 */
+	public function get_image_data( array $images ) {
+		$image_data = array();
+
+		foreach ( $images as $index => $image ) {
+			$image_id = is_int( $image ) ? $image : $image['id'];
+
+			$transformations = null;
+			$data            = array();
+
+			// Send back the attachment id.
+			$data['attachmentId'] = $image_id;
+
+			// Fetch the public id by either syncing NOW or getting the current public id.
+			if ( ! $this->media->has_public_id( $image_id ) ) {
+				$res = $this->media->sync->managers['upload']->upload_asset( $image_id );
+
+				if ( is_wp_error( $res ) ) {
+					// Skip and move on to the next image as this is unlikely to get synced.
+					continue;
+				}
+			}
+			// If synced now, the ID will be available in the meta.
+			$data['publicId'] = $this->media->get_public_id( $image_id, true );
+			$transformations  = $this->media->get_transformations( $image_id );
+			if ( $transformations ) {
+				$data['transformation'] = array( 'transformation' => $transformations );
+			}
+
+			// Add to output array.
+			$image_data[] = $data;
+		}
+
+		return $image_data;
+	}
+
+	/**
+	 * This rest endpoint handler will fetch the public_id and transformations off of a list of images.
+	 *
+	 * @param \WP_REST_Request $request The request.
+	 *
+	 * @return \WP_REST_Response|\WP_Error
+	 */
+	public function rest_cloudinary_image_data( \WP_REST_Request $request ) {
+		$request_body = json_decode( $request->get_body(), true );
+
+		if ( empty( $request_body['images'] ) ) {
+			return new \WP_Error( 400, 'The "images" key must be present in the request body.' );
+		}
+
+		$image_data = $this->get_image_data( $request_body['images'] );
+
+		return new \WP_REST_Response( $image_data );
+	}
+
+	/**
+	 * Add endpoints to the \Cloudinary\REST_API::$endpoints array.
+	 *
+	 * @param array $endpoints Endpoints from the filter.
+	 *
+	 * @return array
+	 */
+	public function rest_endpoints( $endpoints ) {
+
+		$endpoints['image_data'] = array(
+			'method'              => \WP_REST_Server::CREATABLE,
+			'callback'            => array( $this, 'rest_cloudinary_image_data' ),
+			'args'                => array(),
+			'permission_callback' => function () {
+				return Utils::user_can( 'add_gallery', 'edit_posts' );
+			},
+		);
+
+		return $endpoints;
+	}
+
+	/**
+	 * Add settings to pages.
+	 *
+	 * @param array $pages The pages to add to.
+	 *
+	 * @return array
+	 */
+	public function register_settings( $pages ) {
+
+		$pages['gallery'] = array(
+			'page_title'          => __( 'Gallery settings', 'cloudinary' ),
+			'menu_title'          => __( 'Gallery settings', 'cloudinary' ),
+			'priority'            => 5,
+			'requires_connection' => true,
+			'sidebar'             => true,
+		);
+
+		$panel = array(
+			'type'        => 'panel',
+			'title'       => __( 'Gallery Settings', 'cloudinary' ),
+			'option_name' => 'gallery',
+		);
+
+		if ( WooCommerceGallery::woocommerce_active() ) {
+			$panel[] = array(
+				'type' => 'group',
+				array(
+					'type'         => 'on_off',
+					'slug'         => 'gallery_woocommerce_enabled',
+					'title'        => __( 'Replace WooCommerce Gallery', 'cloudinary' ),
+					'tooltip_text' => __( 'Replace the default WooCommerce gallery with the Cloudinary Gallery on product pages.', 'cloudinary' ),
+				),
+			);
+		} else {
+			$panel[] = array(
+				array(
+					'type'    => 'tag',
+					'element' => 'h3',
+					'content' => __( 'Cloudinary Gallery block defaults', 'cloudinary' ),
+				),
+				array(
+					'content' => __(
+						'The Cloudinary Gallery is available as a new block type which can be inserted to any post or page. Note, this is not available when using the classic editor.',
+						'cloudinary'
+					),
+				),
+			);
+		}
+
+		$panel[] = array(
+			'type'   => 'react',
+			'slug'   => 'gallery_config',
+			'script' => array(
+				'slug' => 'gallery-widget',
+				'src'  => $this->plugin->dir_url . 'js/gallery.js',
+			),
+		);
+
+		$panel[] = array(
+			'type'  => 'info_box',
+			'icon'  => $this->plugin->dir_url . 'css/images/academy-icon.svg',
+			'title' => __( 'Need help?', 'cloudinary' ),
+			'text'  => sprintf(
+				// Translators: The HTML for opening and closing link tags.
+				__(
+					'Watch free lessons on how to use the Gallery Settings in the %1$sCloudinary Academy%2$s.',
+					'cloudinary'
+				),
+				'<a href="https://training.cloudinary.com/learn/course/introduction-to-cloudinary-for-wordpress-administrators-70-minute-course-1h85/lessons/using-cloudinary-for-product-galleries-in-woocommerce-and-page-content-906?page=1" target="_blank" rel="noopener noreferrer">',
+				'</a>'
+			),
+		);
+
+		$pages['gallery']['settings'] = array(
+			$panel,
+		);
+
+		return $pages;
+	}
+
+	/**
+	 * Inits the cloudinary gallery using block attributes.
+	 *
+	 * @param string $content The post content.
+	 * @param array  $block   Block data.
+	 *
+	 * @return string
+	 */
+	public function prepare_block_render( $content, $block ) {
+		if ( 'cloudinary/gallery' !== $block['blockName'] ) {
+			return $content;
+		}
+
+		// Ensure library is enqueued. Deals with archive pages that render the content.
+		$this->enqueue_gallery_library();
+
+		$attributes = Utils::expand_dot_notation( $block['attrs'], '_' );
+		$attributes = array_merge( self::$default_config, $attributes );
+
+		// Gallery without images. Don't render.
+		if ( empty( $attributes['selectedImages'] ) ) {
+			return $content;
+		}
+
+		$attributes['mediaAssets'] = array();
+		foreach ( $attributes['selectedImages'] as $attachment ) {
+			// Since the gallery widget uses public ID's, if an attachment is deleted, it can still remain working in a gallery.
+			if ( ! wp_get_attachment_url( $attachment['attachmentId'] ) ) {
+				continue;
+			}
+
+			// Initialize 3D model support.
+			if ( self::GALLERY_GLB_MIME_TYPE === get_post_mime_type( $attachment['attachmentId'] ) ) {
+				$attachment['mediaType'] = '3d';
+			}
+
+			$transformations = $this->media->get_transformations( $attachment['attachmentId'] );
+			if ( ! empty( $transformations ) ) {
+				$attachment['transformation'] = array( 'transformation' => $transformations );
+			}
+			$attributes['mediaAssets'][] = $attachment;
+		}
+
+		$attributes['cloudName'] = $this->plugin->components['connect']->get_cloud_name();
+
+		$credentials = $this->plugin->components['connect']->get_credentials();
+
+		if ( ! empty( $credentials['cname'] ) ) {
+			$attributes['secureDistribution'] = $credentials['cname'];
+			$attributes['privateCdn']         = true;
+		}
+
+		if ( ! empty( $attributes['customSettings'] ) ) {
+			if ( is_string( $attributes['customSettings'] ) && ! is_admin() ) {
+				$attributes['customSettings'] = json_decode( $attributes['customSettings'], true );
+			}
+			$attributes = wp_parse_args( $attributes['customSettings'], $attributes );
+		}
+
+		unset( $attributes['selectedImages'], $attributes['customSettings'] );
+
+		$attributes['queryParam'] = 'AA';
+
+		ob_start();
+		?>
+		<script>
+			window.addEventListener( 'load', function() {
+				if ( cloudinary && cloudinary.galleryWidget ) {
+					var attributes = <?php echo wp_json_encode( $attributes ); ?>;
+					attributes.container = '.' + attributes.container;
+					cloudinary.galleryWidget( attributes ).render();
+				}
+			}, false );
+		</script>
+		<?php
+
+		return $content . ob_get_clean();
+	}
+
+	/**
+	 * Maybe decode the gallery configuration.
+	 * This has historical reasons, as it was used to be stored as encoded information.
+	 *
+	 * @param array|string $config The configuration for the gallery.
+	 *
+	 * @return array
+	 */
+	protected function maybe_decode_config( $config ) {
+		if ( ! is_array( $config ) ) {
+			$config = json_decode( $config, true );
+		}
+
+		return $config;
+	}
+
+	/**
+	 * Maybe enqueue gallery scripts.
+	 *
+	 * @return bool
+	 */
+	protected function maybe_enqueue_scripts() {
+		$can = false;
+
+		// Can if front end and have the block.
+		if (
+			function_exists( 'has_block' ) && has_block( 'cloudinary/gallery' )
+			&&
+			! is_admin()
+		) {
+			$can = true;
+		}
+
+		// Can on back end on block editor and gallery settings page.
+		if ( is_admin() && function_exists( 'get_current_screen' ) ) {
+			$screen = get_current_screen();
+			if (
+				! is_null( $screen )
+				&& (
+					'cloudinary_page_cloudinary_gallery' === $screen->id || ( method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() )
+				)
+			) {
+				$can = true;
+			}
+		}
+
+		// Bail enqueuing the script several times.
+		if ( wp_script_is( self::GALLERY_LIBRARY_HANDLE ) ) {
+			$can = false;
+		}
+
+		/**
+		 * Filter the enqueue of gallery script.
+		 *
+		 * @param bool $can Default value.
+		 *
+		 * @return bool
+		 */
+		$can = apply_filters( 'cloudinary_enqueue_gallery_script', $can );
+
+		return $can;
+	}
+
+	/**
+	 * Allow GLB files to be uploaded.
+	 *
+	 * @param array $mimes The mime types.
+	 *
+	 * @return array
+	 */
+	public function add_glb_mime( $mimes ) {
+		$mimes['glb'] = self::GALLERY_GLB_MIME_TYPE;
+
+		return $mimes;
+	}
+
+	/**
+	 * Pass the GLB file type check.
+	 *
+	 * @param array  $file_data The file data.
+	 * @param string $file      The file.
+	 * @param string $filename  The filename.
+	 *
+	 * @return array
+	 */
+	public function pass_glb_filetype_check( $file_data, $file, $filename ) {
+		if ( 'glb' === pathinfo( $filename, PATHINFO_EXTENSION ) ) {
+			$file_data['ext']  = 'glb';
+			$file_data['type'] = self::GALLERY_GLB_MIME_TYPE;
+		}
+
+		return $file_data;
+	}
+
+	/**
+	 * Allow GLB files to be uploaded to Cloudinary.
+	 *
+	 * @param array $types The allowed extensions.
+	 *
+	 * @return array
+	 */
+	public function allow_glb_for_cloudinary( $types ) {
+		$types[] = 'glb';
+
+		return $types;
+	}
+
+	/**
+	 * Allow GLB files to be delivered from Cloudinary.
+	 *
+	 * @param bool $is Whether the file is deliverable.
+	 * @param int  $attachment_id The attachment ID.
+	 *
+	 * @return bool
+	 */
+	public function allow_glb_delivery_from_cloudinary( $is, $attachment_id ) {
+		if ( self::GALLERY_GLB_MIME_TYPE === get_post_mime_type( $attachment_id ) ) {
+			$is = true;
+		}
+
+		return $is;
+	}
+
+	/**
+	 * Replace image urls from .glb to .png.
+	 *
+	 * This allows the GLB files to be served as PNG files in the image context.
+	 *
+	 * @param string $url The URL.
+	 * @param int    $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public function admin_replace_glb_url( $url, $attachment_id ) {
+		if ( ! is_admin() ) {
+			return $url;
+		}
+
+		if ( self::GALLERY_GLB_MIME_TYPE === get_post_mime_type( $attachment_id ) ) {
+			$url = str_replace( '.glb', '.png', $url );
+		}
+		return $url;
+	}
+
+	/**
+	 * We pretend that GLB files are images with dimensions of 512x512.
+	 *
+	 * This allows WordPress to correctly render the GLB files as images in the media library.
+	 *
+	 * @param array $metadata The attachment metadata.
+	 * @param int   $attachment_id The attachment ID.
+	 *
+	 * @return array
+	 */
+	public function inject_glb_metadata( $metadata, $attachment_id ) {
+		if ( self::GALLERY_GLB_MIME_TYPE === get_post_mime_type( $attachment_id ) ) {
+			$metadata['width']  = 512;
+			$metadata['height'] = 512;
+		}
+		return $metadata;
+	}
+
+	/**
+	 * Setup hooks for the gallery.
+	 */
+	public function setup_hooks() {
+		add_filter( 'cloudinary_api_rest_endpoints', array( $this, 'rest_endpoints' ) );
+		add_action( 'enqueue_block_editor_assets', array( $this, 'block_editor_scripts_styles' ) );
+		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_gallery_library' ) );
+		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
+		add_filter( 'render_block', array( $this, 'prepare_block_render' ), 10, 2 );
+		add_filter( 'cloudinary_admin_pages', array( $this, 'register_settings' ) );
+
+		/**
+		 * Filter to allow GLB 3D model files to be uploaded.
+		 *
+		 * WARNING: This is an experimental hook. The only place where GLB files can be used
+		 *          is in the Cloudinary Gallery block.
+		 *
+		 * @hook   cloudinary_allow_glb_upload
+		 * @since  3.2.6
+		 *
+		 * @param bool $allow_glb_upload Whether to allow GLB files to be uploaded.
+		 *
+		 * @return bool
+		 */
+		if ( apply_filters( 'cloudinary_allow_glb_upload', false ) ) {
+			add_filter( 'upload_mimes', array( $this, 'add_glb_mime' ), 20, 1 );
+			add_filter( 'wp_check_filetype_and_ext', array( $this, 'pass_glb_filetype_check' ), 10, 3 );
+			add_filter( 'cloudinary_allowed_extensions', array( $this, 'allow_glb_for_cloudinary' ) );
+			add_filter( 'cloudinary_is_deliverable', array( $this, 'allow_glb_delivery_from_cloudinary' ), 10, 2 );
+			add_filter( 'wp_get_attachment_url', array( $this, 'admin_replace_glb_url' ), 11, 2 );
+			add_filter( 'wp_generate_attachment_metadata', array( $this, 'inject_glb_metadata' ), 10, 2 );
+		}
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_media_class-global-transformations.php.html b/docs/php_media_class-global-transformations.php.html new file mode 100644 index 000000000..9ad8d3646 --- /dev/null +++ b/docs/php_media_class-global-transformations.php.html @@ -0,0 +1,766 @@ + + + + + Source: php/media/class-global-transformations.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/media/class-global-transformations.php

+ + + + + + + +
+
+
<?php
+/**
+ * Global Transformations class for Cloudinary.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Media;
+
+use Cloudinary\Connect\Api;
+use Cloudinary\Relate;
+use Cloudinary\Settings\Setting;
+use Cloudinary\Sync;
+use Cloudinary\REST_API;
+use Cloudinary\Utils;
+use WP_Post;
+
+/**
+ * Class Global Transformations.
+ *
+ * Handles Contextual Globals transformations for content.
+ */
+class Global_Transformations {
+
+	/**
+	 * Holds the Media instance.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     \Cloudinary\Media Instance of the plugin.
+	 */
+	private $media;
+
+	/**
+	 * Holds the taxonomy fields defined in settings.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     array
+	 */
+	private $taxonomy_fields;
+
+	/**
+	 * Holds the global settings (lowest level).
+	 *
+	 * @since   0.1
+	 *
+	 * @var     array
+	 */
+	public $globals;
+
+	/**
+	 * Holds the order meta key to maintain consistency.
+	 */
+	const META_ORDER_KEY = 'cloudinary_transformations';
+
+	/**
+	 * Holds the apply type meta key to maintain consistency.
+	 */
+	const META_APPLY_KEY = 'cloudinary_apply_type';
+
+	/**
+	 * Holds the overwrite transformations for featured images meta key.
+	 */
+	const META_FEATURED_IMAGE_KEY = '_cloudinary_featured_overwrite';
+
+	/**
+	 * Holds the media settings.
+	 *
+	 * @var Setting
+	 */
+	protected $media_settings;
+
+	/**
+	 * Global Transformations constructor.
+	 *
+	 * @param \Cloudinary\Media $media The plugin.
+	 */
+	public function __construct( \Cloudinary\Media $media ) {
+		$this->media            = $media;
+		$this->media_settings   = $this->media->get_settings();
+		$this->globals['image'] = $this->media_settings->get_setting( 'image_settings' );
+		$this->globals['video'] = $this->media_settings->get_setting( 'video_settings' );
+		// Set value to null, to rebuild data to get defaults.
+		$field_slugs = array_keys( $this->globals['image']->get_value() );
+		$field_slugs = array_merge( $field_slugs, array_keys( $this->globals['image']->get_value() ) );
+		foreach ( $field_slugs as $slug ) {
+			$setting = $this->media_settings->get_setting( $slug );
+			if ( $setting->has_param( 'taxonomy_field' ) ) {
+				$context = $setting->get_param( 'taxonomy_field.context', 'global' );
+				if ( isset( $this->taxonomy_fields[ $context ] ) && in_array( $setting, $this->taxonomy_fields[ $context ], true ) ) {
+					continue;
+				}
+				$priority = intval( $setting->get_param( 'taxonomy_field.priority', 10 ) ) * 1000;
+				while ( isset( $this->taxonomy_fields[ $context ][ $priority ] ) ) {
+					++$priority;
+				}
+				if ( ! isset( $this->taxonomy_fields[ $context ] ) ) {
+					$this->taxonomy_fields[ $context ] = array();
+				}
+				$this->taxonomy_fields[ $context ][ $priority ] = $setting;
+			}
+		}
+
+		foreach ( $this->taxonomy_fields as $context => $set ) {
+			ksort( $this->taxonomy_fields[ $context ] );
+		}
+		$this->setup_hooks();
+	}
+
+	/**
+	 * Add fields to Add taxonomy term screen.
+	 */
+	public function add_taxonomy_fields() {
+		$template_file = $this->media->plugin->template_path . 'taxonomy-transformation-fields.php';
+		if ( file_exists( $template_file ) ) {
+			// Initialise the settings to be within the terms context, and not contain or alter the global setting value.
+			$this->init_term_transformations();
+			include $template_file; // phpcs:ignore
+		}
+	}
+
+	/**
+	 * Add fields to Edit taxonomy term screen.
+	 */
+	public function edit_taxonomy_fields() {
+		$template_file = $this->media->plugin->template_path . 'taxonomy-term-transformation-fields.php';
+		if ( file_exists( $template_file ) ) {
+			// Initialise the settings to be within the terms context, and not contain or alter the global setting value.
+			$this->init_term_transformations();
+			include $template_file; // phpcs:ignore
+		}
+	}
+
+	/**
+	 * Save the meta data for the term.
+	 *
+	 * @param int $term_id The term ID.
+	 */
+	public function save_taxonomy_custom_meta( $term_id ) {
+
+		foreach ( $this->taxonomy_fields as $context => $set ) {
+
+			foreach ( $set as $setting ) {
+
+				$meta_key = self::META_ORDER_KEY . '_' . $setting->get_param( 'slug' );
+				$value    = $setting->get_submitted_value();
+
+				// Check if it's option based.
+				if ( $setting->has_param( 'options' ) ) {
+					$options = $setting->get_param( 'options', array() );
+					if ( ! in_array( $value, $options, true ) ) {
+						$value = null;
+					}
+				}
+
+				// If null, skip it.
+				if ( is_null( $value ) ) {
+					continue;
+				}
+				// Update the metadata.
+				update_term_meta( $term_id, $meta_key, $value );
+			}
+		}
+	}
+
+	/**
+	 * Get transformations for a term.
+	 *
+	 * @param int    $term_id The term ID to get transformations for.
+	 * @param string $type    The default transformations type.
+	 *
+	 * @return array
+	 */
+	private function get_term_transformations( $term_id, $type ) {
+		$meta_data = array();
+		if ( ! empty( $this->taxonomy_fields[ $type ] ) ) {
+			foreach ( $this->taxonomy_fields[ $type ] as $setting ) {
+				$slug               = $setting->get_param( 'slug' );
+				$meta_key           = self::META_ORDER_KEY . '_' . $slug;
+				$value              = get_term_meta( $term_id, $meta_key, true );
+				$meta_data[ $slug ] = $value;
+			}
+
+			// Clear out empty items.
+			$meta_data = array_filter( $meta_data );
+		}
+
+		return $meta_data;
+	}
+
+	/**
+	 * Resets the taxonomy fields values.
+	 */
+	protected function reset_taxonomy_field_values() {
+		foreach ( $this->taxonomy_fields as $context => $set ) {
+			foreach ( $set as $setting ) {
+				$setting->set_value( null );
+			}
+		}
+	}
+
+	/**
+	 * Init term meta field values.
+	 */
+	public function init_term_transformations() {
+		// Enqueue Cloudinary.
+		$this->media->plugin->enqueue_assets();
+
+		$this->reset_taxonomy_field_values();
+
+		$types = array_keys( $this->taxonomy_fields );
+		foreach ( $types as $type ) {
+			$transformations = $this->get_transformations( $type );
+			foreach ( $transformations as $slug => $transformation ) {
+				$this->media_settings->get_setting( $slug )->set_value( $transformation );
+			}
+		}
+	}
+
+	/**
+	 * Get the transformations.
+	 *
+	 * @param string $type The context type to get transformations for.
+	 *
+	 * @return array
+	 */
+	public function get_transformations( $type ) {
+
+		$transformations = isset( $this->globals[ $type ] ) ? $this->globals[ $type ] : array();
+		if ( function_exists( 'get_current_screen' ) ) {
+			$screen = get_current_screen();
+			if ( $screen instanceof \WP_Screen ) {
+				// check screen context.
+				switch ( $screen->base ) {
+					case 'term':
+						$term_id         = filter_input( INPUT_GET, 'tag_ID', FILTER_SANITIZE_NUMBER_INT );
+						$transformations = $this->get_term_transformations( $term_id, $type );
+						break;
+					default:
+						$transformations = array();
+						break;
+				}
+			}
+		}
+
+		return $transformations;
+	}
+
+	/**
+	 * Get the transformations of a posts taxonomies.
+	 *
+	 * @param string $type The type to get.
+	 *
+	 * @return string
+	 */
+	public function get_taxonomy_transformations( $type ) {
+		static $cache = array();
+
+		$post = $this->get_current_post();
+		$key  = wp_json_encode( func_get_args() ) . ( $post ? $post->ID : 0 );
+		if ( isset( $cache[ $key ] ) ) {
+			return $cache[ $key ];
+		}
+		$return_transformations = '';
+		if ( $post ) {
+			$transformations = array();
+			$terms           = $this->get_terms( $post->ID );
+			if ( ! empty( $terms ) ) {
+				foreach ( $terms as $item ) {
+					$transformation = $this->get_term_transformations( $item['term']->term_id, $type );
+					if ( ! empty( $transformation[ $type . '_freeform' ] ) ) {
+						$transformations[] = trim( $transformation[ $type . '_freeform' ] );
+					}
+				}
+				// Join the freeform.
+				$return_transformations = implode( '/', (array) $transformations );
+			}
+		}
+
+		$cache[ $key ] = $return_transformations;
+
+		return $cache[ $key ];
+	}
+
+	/**
+	 * Check if the image has a post taxonomy overwrite.
+	 *
+	 * @return bool
+	 */
+	public function is_taxonomy_overwrite() {
+		$apply_type = false;
+		$post       = $this->get_current_post();
+		if ( $post ) {
+			$apply_type = get_post_meta( $post->ID, self::META_APPLY_KEY . '_terms', true );
+		}
+
+		return ! empty( $apply_type );
+	}
+
+	/**
+	 * Load the preview field.
+	 *
+	 * @param bool $video Flag if this is a video preview.
+	 */
+	public function load_preview( $video = false ) {
+		$file = 'transformation-preview';
+		if ( true === $video ) {
+			$file .= '-video';
+		}
+		require $this->media->plugin->template_path . $file . '.php'; // phpcs:ignore
+	}
+
+	/**
+	 * Register Taxonomy Ordering.
+	 *
+	 * @param string   $type The post type (unused).
+	 * @param \WP_Post $post The current post.
+	 */
+	public function taxonomy_ordering( $type, $post ) {
+		if ( $this->has_public_taxonomies( $post ) ) {
+			add_meta_box( 'cld-taxonomy-order', __( 'Cloudinary terms transformations', 'cloudinary' ), array( $this, 'render_ordering_box' ), null, 'side', 'core' );
+		}
+	}
+
+	/**
+	 * Check if the post has any public taxonomies.
+	 *
+	 * @param \WP_POST $post The post to check.
+	 *
+	 * @return bool
+	 */
+	public function has_public_taxonomies( $post ) {
+		$taxonomies = get_object_taxonomies( $post, 'objects' );
+		// Only get taxonomies that have a UI.
+		$taxonomies = array_filter(
+			$taxonomies,
+			function ( $tax ) {
+				return $tax->show_ui;
+			}
+		);
+
+		return ! empty( $taxonomies );
+	}
+
+	/**
+	 * Render the ordering metabox.
+	 *
+	 * @param \WP_Post $post the current Post.
+	 */
+	public function render_ordering_box( $post ) {
+		// Show UI if has taxonomies.
+		if ( $this->has_public_taxonomies( $post ) ) {
+			echo $this->init_taxonomy_manager( $post ); // phpcs:ignore
+		}
+	}
+
+	/**
+	 * Get terms for the current post that has transformations.
+	 *
+	 * @param int $post_id The post ID.
+	 *
+	 * @return array|false|int|\WP_Error|\WP_Term[]
+	 */
+	public function get_terms( $post_id ) {
+		// Get terms for this post on load.
+		$items = get_post_meta( $post_id, self::META_ORDER_KEY . '_terms', true );
+		$terms = array();
+		if ( ! empty( $items ) ) {
+			$items = array_map(
+				function ( $item ) {
+					// Get the id.
+					if ( false !== strpos( $item, ':' ) ) {
+						$parts = explode( ':', $item );
+						$term  = get_term_by( 'id', $parts[1], $parts[0] );
+
+						if ( ! $term ) {
+							$term = get_term_by( 'term_taxonomy_id', $parts[1], $parts[0] );
+						}
+					} else {
+						// Something went wrong, and value was not an int and didn't contain a tax:slug string.
+						return null;
+					}
+
+					// Return if term is valid.
+					if ( $term instanceof \WP_Term ) {
+						return array(
+							'term'  => $term,
+							'value' => $item,
+						);
+					}
+
+					return null;
+				},
+				$items
+			);
+			$terms = array_filter( $items );
+		} else {
+			$taxonomies    = get_object_taxonomies( get_post_type( $post_id ) );
+			$current_terms = wp_get_object_terms( $post_id, $taxonomies );
+			if ( ! empty( $current_terms ) ) {
+				$terms = array_map(
+					function ( $term ) {
+						$value = $term->taxonomy . ':' . $term->term_id;
+
+						$item = array(
+							'term'  => $term,
+							'value' => $value,
+						);
+
+						return $item;
+					},
+					$current_terms
+				);
+			}
+		}
+
+		return $terms;
+	}
+
+	/**
+	 * Make an item for ordering.
+	 *
+	 * @param int    $id   The term id.
+	 * @param string $name The term name.
+	 *
+	 * @return string
+	 */
+	public function make_term_sort_item( $id, $name ) {
+		$out = array(
+			'<li class="cld-tax-order-list-item" data-item="' . esc_attr( $id ) . '">',
+			'<span class="dashicons dashicons-menu cld-tax-order-list-item-handle"></span>',
+			'<input class="cld-tax-order-list-item-input" type="hidden" name="cld_tax_order[]" value="' . $id . '">' . $name,
+			'</li>',
+		);
+
+		return implode( $out );
+	}
+
+	/**
+	 * Init the taxonomy ordering metabox.
+	 *
+	 * @param \WP_Post $post The current Post.
+	 *
+	 * @return string
+	 */
+	private function init_taxonomy_manager( $post ) {
+		wp_enqueue_script( 'wp-api' );
+
+		$terms = $this->get_terms( $post->ID );
+
+		$out   = array();
+		$out[] = '<div class="cld-tax-order">';
+		$out[] = '<p style="font-size: 12px; font-style: normal; color: rgb( 117, 117, 117 );">' . esc_html__( 'If you placed custom transformations on these terms you may order them below. ', 'cloudinary' ) . '</li>';
+		$out[] = '<ul class="cld-tax-order-list" id="cld-tax-items">';
+		$out[] = '<li class="cld-tax-order-list-item no-items">' . esc_html__( 'No terms added', 'cloudinary' ) . '</li>';
+		if ( ! empty( $terms ) ) {
+			foreach ( (array) $terms as $item ) {
+				$out[] = $this->make_term_sort_item( $item['value'], $item['term']->name );
+			}
+		}
+		$out[] = '</ul>';
+
+		// Get apply Type.
+		if ( ! empty( $terms ) ) {
+			$type  = get_post_meta( $post->ID, self::META_APPLY_KEY . '_terms', true );
+			$out[] = '<label class="cld-tax-order-list-type"><input ' . checked( 'overwrite', $type, false ) . ' type="checkbox" value="overwrite" name="cld_apply_type" />' . esc_html__( 'Disable Cloudinary global transformations', 'cloudinary' ) . '</label>';
+		}
+
+		$out[] = '</div>';
+
+		return implode( $out );
+	}
+
+	/**
+	 * Save the taxonomy ordering meta.
+	 *
+	 * @param int $post_id The post ID.
+	 */
+	public function save_taxonomy_ordering( $post_id ) {
+		$args = array(
+			'cld_tax_order'  => array(
+				'filter'  => FILTER_CALLBACK,
+				'flags'   => FILTER_REQUIRE_ARRAY,
+				'options' => 'sanitize_text_field',
+			),
+			'cld_apply_type' => array(
+				'filter'  => FILTER_CALLBACK,
+				'options' => 'sanitize_text_field',
+			),
+		);
+
+		$taxonomy_order = filter_input_array( INPUT_POST, $args );
+
+		if ( ! empty( $taxonomy_order['cld_tax_order'] ) ) {
+			// Map to ID's where needed.
+			$order = array_map(
+				function ( $line ) {
+					$parts = explode( ':', $line );
+					if ( ! empty( $parts[1] ) && ! is_numeric( $parts[1] ) ) {
+						// Tag based, find term ID.
+						$line = null;
+						$term = get_term_by( 'name', $parts[1], $parts[0] );
+						if ( ! empty( $term ) ) {
+							$line = $term->taxonomy . ':' . $term->term_id;
+						}
+					} elseif ( empty( $parts[1] ) ) {
+						// strange '0' based section, remove to be safe.
+						$line = null;
+					}
+
+					return $line;
+				},
+				$taxonomy_order['cld_tax_order']
+			);
+			$order = array_filter( $order );
+			update_post_meta( $post_id, self::META_ORDER_KEY . '_terms', $order );
+		} else {
+			delete_post_meta( $post_id, self::META_ORDER_KEY . '_terms' );
+		}
+		if ( ! empty( $taxonomy_order['cld_apply_type'] ) ) {
+			update_post_meta( $post_id, self::META_APPLY_KEY . '_terms', $taxonomy_order['cld_apply_type'] );
+		} else {
+			delete_post_meta( $post_id, self::META_APPLY_KEY . '_terms' );
+		}
+	}
+
+	/**
+	 * Register meta for featured image transformations overwriting.
+	 *
+	 * @return void
+	 */
+	public function register_featured_overwrite() {
+		register_meta(
+			'post',
+			self::META_FEATURED_IMAGE_KEY,
+			array(
+				'show_in_rest'  => true,
+				'single'        => true,
+				'default'       => false,
+				'type'          => 'boolean',
+				'description'   => esc_html__( 'Flag on whether transformation should be overwritten for a featured image.', 'cloudinary' ),
+				'auth_callback' => function () {
+					return Utils::user_can( 'override_transformation', 'edit_posts' );
+				},
+			)
+		);
+	}
+
+	/**
+	 * Add checkbox to override transformations for featured image.
+	 *
+	 * @param string $content       The content to be saved.
+	 * @param int    $post_id       The post ID.
+	 * @param int    $attachment_id The ID of the attachment.
+	 *
+	 * @return string
+	 */
+	public function classic_overwrite_transformations_featured_image( $content, $post_id, $attachment_id ) {
+		if ( ! empty( $attachment_id ) ) {
+			// Get the current value.
+			$field_value = get_post_meta( $post_id, self::META_FEATURED_IMAGE_KEY, true );
+			// Add hidden field and checkbox to the HTML.
+			$content .= sprintf(
+				'<p><label for="%1$s"><input type="hidden" name="%1$s" value="0" /><input type="checkbox" name="%1$s" id="%1$s" value="1" %2$s /> %3$s</label></p>',
+				esc_attr( self::META_FEATURED_IMAGE_KEY ),
+				checked( $field_value, 1, false ),
+				esc_html__( 'Overwrite Global Transformations', 'cloudinary' )
+			);
+		}
+
+		return $content;
+	}
+
+	/**
+	 * Updates appropriate meta for overwriting transformations of a featured image.
+	 *
+	 * @param int $post_id The post ID.
+	 */
+	public function save_overwrite_transformations_featured_image( $post_id ) {
+		$field_value = filter_input( INPUT_POST, self::META_FEATURED_IMAGE_KEY, FILTER_VALIDATE_BOOLEAN );
+		if ( ! is_null( $field_value ) ) {
+			update_post_meta( $post_id, self::META_FEATURED_IMAGE_KEY, $field_value );
+		}
+	}
+
+	/**
+	 * Get the current post.
+	 *
+	 * @return WP_Post|null
+	 */
+	public function get_current_post() {
+		/**
+		 * Filter the post ID.
+		 *
+		 * @hook    cloudinary_current_post_id
+		 * @default null
+		 *
+		 * @return  {WP_Post|null}
+		 */
+		$post_id = apply_filters( 'cloudinary_current_post_id', null );
+
+		if ( is_null( $post_id ) && ! in_the_loop() ) {
+			return null;
+		}
+
+		return get_post( $post_id );
+	}
+
+	/**
+	 * Insert the cloudinary status column.
+	 *
+	 * @param array $cols Array of columns.
+	 *
+	 * @return array
+	 */
+	public function transformations_column( $cols ) {
+
+		$custom = array(
+			'cld_transformations' => __( 'Transformations', 'cloudinary' ),
+		);
+		$offset = array_search( 'parent', array_keys( $cols ), true );
+		if ( empty( $offset ) ) {
+			$offset = 4; // Default location some where after author, in case another plugin removes parent column.
+		}
+		$cols = array_slice( $cols, 0, $offset ) + $custom + array_slice( $cols, $offset );
+
+		return $cols;
+	}
+
+	/**
+	 * Display the Cloudinary Column.
+	 *
+	 * @param string $column_name   The column name.
+	 * @param int    $attachment_id The attachment id.
+	 */
+	public function transformations_column_value( $column_name, $attachment_id ) {
+		if ( 'cld_transformations' === $column_name && $this->media->sync->is_synced( $attachment_id, true ) ) {
+
+			// Transformations are only available for Images and Videos.
+			if (
+				! in_array(
+					$this->media->get_media_type( $attachment_id ),
+					array(
+						'image',
+						'video',
+					),
+					true
+				)
+			) {
+				return;
+			}
+
+			// If asset isn't deliverable, don't show transformations.
+			if ( ! $this->media->plugin->get_component( 'delivery' )->is_deliverable( $attachment_id ) ) {
+				return;
+			}
+
+			$item = $this->media->plugin->get_component( 'assets' )->get_asset( $attachment_id, 'dataset' );
+			if ( ! empty( $item['data']['public_id'] ) ) {
+				$text            = __( 'Add transformations', 'cloudinary' );
+				$transformations = Relate::get_transformations( $attachment_id, true );
+				if ( ! empty( $transformations ) ) {
+					$text = $transformations;
+				}
+				$args = array(
+					'page'    => 'cloudinary',
+					'section' => 'edit-asset',
+					'asset'   => $attachment_id,
+				);
+				?>
+				<a href="<?php echo esc_url( add_query_arg( $args, 'admin.php' ) ); ?>" data-transformation-item="<?php echo esc_attr( wp_json_encode( $item ) ); ?>"><?php echo esc_html( $text ); ?></a>
+				<?php
+			}
+		}
+	}
+
+	/**
+	 * Setup hooks for the filters.
+	 */
+	public function setup_hooks() {
+		$taxonomies = get_taxonomies( array( 'show_ui' => true ) );
+		$global     = $this;
+		array_map(
+			function ( $taxonomy ) use ( $global ) {
+				add_action( $taxonomy . '_add_form_fields', array( $global, 'add_taxonomy_fields' ) );
+				add_action( $taxonomy . '_edit_form_fields', array( $global, 'edit_taxonomy_fields' ) );
+				add_action( 'create_' . $taxonomy, array( $global, 'save_taxonomy_custom_meta' ) );
+				add_action( 'edited_' . $taxonomy, array( $global, 'save_taxonomy_custom_meta' ) );
+			},
+			$taxonomies
+		);
+
+		// Add ordering metaboxes and featured overwrite.
+		add_action( 'add_meta_boxes', array( $this, 'taxonomy_ordering' ), 10, 2 );
+		add_action( 'save_post', array( $this, 'save_taxonomy_ordering' ), 10, 1 );
+		add_action( 'save_post', array( $this, 'save_overwrite_transformations_featured_image' ), 10, 3 );
+		add_filter( 'admin_post_thumbnail_html', array( $this, 'classic_overwrite_transformations_featured_image' ), 10, 3 );
+
+		// Filter and action the custom column.
+		add_filter( 'manage_media_columns', array( $this, 'transformations_column' ), 11 );
+		add_action( 'manage_media_custom_column', array( $this, 'transformations_column_value' ), 10, 2 );
+
+		// Register Meta.
+		$this->register_featured_overwrite();
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_media_class-upgrade.php.html b/docs/php_media_class-upgrade.php.html new file mode 100644 index 000000000..1a56b64dc --- /dev/null +++ b/docs/php_media_class-upgrade.php.html @@ -0,0 +1,350 @@ + + + + + Source: php/media/class-upgrade.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/media/class-upgrade.php

+ + + + + + + +
+
+
<?php
+/**
+ * Upgrades from a Legecy version of Cloudinary.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Media;
+
+use Cloudinary\Relate;
+use Cloudinary\Sync;
+use Cloudinary\Utils;
+
+/**
+ * Class Filter.
+ *
+ * Handles filtering of HTML content.
+ */
+class Upgrade {
+
+	/**
+	 * Holds the Media instance.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     \Cloudinary\Media Instance of the plugin.
+	 */
+	private $media;
+
+	/**
+	 * Holds the Sync instance.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     \Cloudinary\Sync Instance of the plugin.
+	 */
+	private $sync;
+
+	/**
+	 * Filter constructor.
+	 *
+	 * @param \Cloudinary\Media $media The plugin.
+	 */
+	public function __construct( \Cloudinary\Media $media ) {
+		$this->media = $media;
+		$this->sync  = $media->plugin->components['sync'];
+		$this->setup_hooks();
+	}
+
+	/**
+	 * Convert an image post that was created from Cloudinary v1.
+	 *
+	 * @param int $attachment_id The attachment ID to convert.
+	 *
+	 * @return string Cloudinary ID
+	 */
+	public function convert_cloudinary_version( $attachment_id ) {
+
+		if ( ! empty( get_post_meta( $attachment_id, Sync::META_KEYS['cloudinary'], true ) ) ) {
+			// V2.5 changed the meta. if it had, theres no upgrades needed.
+			/**
+			 * Action to trigger an upgrade on a synced asset.
+			 *
+			 * @hook  cloudinary_upgrade_asset
+			 * @since 3.0.5
+			 *
+			 * @param $attachment_id {int} The attachment ID.
+			 * @param $version       {string} The current plugin version.
+			 */
+			do_action( 'cloudinary_upgrade_asset', $attachment_id, $this->media->plugin->version );
+
+			$this->media->update_post_meta( $attachment_id, Sync::META_KEYS['plugin_version'], $this->media->plugin->version );
+			$this->sync->set_signature_item( $attachment_id, 'upgrade' );
+
+			return $this->media->get_public_id( $attachment_id );
+		}
+		$file = get_post_meta( $attachment_id, '_wp_attached_file', true );
+		if ( ! $this->media->get_post_meta( $attachment_id, Sync::META_KEYS['public_id'], true ) && wp_http_validate_url( $file ) ) {
+			// Version 1 upgrade.
+			$path                  = wp_parse_url( $file, PHP_URL_PATH );
+			$media                 = $this->media;
+			$parts                 = explode( '/', ltrim( $path, '/' ) );
+			$cloud_name            = null;
+			$asset_version         = 1;
+			$asset_transformations = array();
+			$id_parts              = array();
+			$public_id             = $this->get_fetch_public_id( $path, $attachment_id );
+			foreach ( $parts as $val ) {
+				if ( empty( $val ) ) {
+					continue;
+				}
+				if ( is_null( $cloud_name ) ) {
+					// Cloudname will always be the first item.
+					$cloud_name = md5( $val );
+					continue;
+				}
+				if ( in_array( $val, array( 'images', 'image', 'video', 'upload', 'fetch' ), true ) ) {
+					continue;
+				}
+				$transformation_maybe = $media->get_transformations_from_string( $val );
+				if ( ! empty( $transformation_maybe ) ) {
+					$asset_transformations = $transformation_maybe;
+					continue;
+				}
+				if ( substr( $val, 0, 1 ) === 'v' && is_numeric( substr( $val, 1 ) ) ) {
+					$asset_version = substr( $val, 1 );
+					continue;
+				}
+
+				// Filter out file name.
+				$path = Utils::pathinfo( $val, PATHINFO_FILENAME );
+				if ( ! in_array( $path, $id_parts, true ) ) {
+					$id_parts[] = Utils::pathinfo( $val, PATHINFO_FILENAME );
+				}
+			}
+			// Build public_id.
+			$parts = array_filter( $id_parts );
+			if ( empty( $public_id ) ) {
+				$public_id = implode( '/', $parts );
+			}
+			$this->media->update_post_meta( $attachment_id, Sync::META_KEYS['public_id'], $public_id );
+			$this->media->update_post_meta( $attachment_id, Sync::META_KEYS['version'], $asset_version );
+			$this->media->update_post_meta( $attachment_id, Sync::META_KEYS['upgrading'], true );
+			if ( ! empty( $asset_transformations ) ) {
+				Relate::update_transformations( $attachment_id, $asset_transformations );
+			}
+			$this->sync->set_signature_item( $attachment_id, 'cloud_name', $cloud_name );
+		} else {
+			// v2 upgrade.
+			$public_id = $this->media->get_public_id( $attachment_id, true );
+			$suffix    = $this->media->get_post_meta( $attachment_id, Sync::META_KEYS['suffix'], true );
+			if ( ! empty( $suffix ) ) {
+				// Has suffix. Get delete and cleanup public ID.
+				if ( false !== strpos( $public_id, $suffix ) ) {
+					$public_id = str_replace( $suffix, '', $public_id );
+				}
+				$public_id .= $suffix;
+				$this->media->delete_post_meta( $attachment_id, Sync::META_KEYS['suffix'] );
+				$this->media->update_post_meta( $attachment_id, Sync::META_KEYS['public_id'], $public_id );
+			}
+			// Check folder sync in order and if it's not a URL.
+			if ( ! wp_http_validate_url( $file ) && $this->media->is_folder_synced( $attachment_id ) ) {
+				$public_id_folder = ltrim( dirname( $this->media->get_public_id( $attachment_id ) ) );
+				$test_signature   = md5( false );
+				$folder_signature = md5( $public_id_folder );
+				$signature        = $this->sync->get_signature( $attachment_id );
+				if ( $folder_signature !== $test_signature && $test_signature === $signature['folder'] ) {
+					// The test signature is a hashed false, which is how non-folder-synced items got hashed.
+					// Indicating this is broken link.
+					$this->media->delete_post_meta( $attachment_id, Sync::META_KEYS['folder_sync'] );
+					$this->media->delete_post_meta( $attachment_id, Sync::META_KEYS['sync_error'] ); // Remove any errors from upgrade. they are outdated.
+					delete_post_meta( $attachment_id, Sync::META_KEYS['sync_error'] ); // Remove any errors from upgrade. they are outdated.
+					$this->sync->set_signature_item( $attachment_id, 'folder' );
+				}
+			}
+		}
+		$this->media->update_post_meta( $attachment_id, Sync::META_KEYS['plugin_version'], $this->media->plugin->version );
+		$this->sync->set_signature_item( $attachment_id, 'upgrade' );
+		$this->sync->set_signature_item( $attachment_id, 'public_id' );
+		$this->sync->set_signature_item( $attachment_id, 'storage' );
+		// Update Sync keys.
+		$sync_key        = $public_id;
+		$transformations = $this->media->get_transformation_from_meta( $attachment_id );
+		if ( ! empty( $transformations ) ) {
+			$sync_key .= wp_json_encode( $transformations );
+		}
+		update_post_meta( $attachment_id, '_' . md5( $sync_key ), true );
+		update_post_meta( $attachment_id, '_' . md5( 'base_' . $public_id ), true );
+		// Get a new uncached signature.
+		$this->sync->get_signature( $attachment_id, true );
+
+		return $public_id;
+	}
+
+	/**
+	 * Maybe the upgraded attachment is a fetch image.
+	 *
+	 * @param string $path          The attachment path.
+	 * @param int    $attachment_id The attachment ID.
+	 *
+	 * @return string
+	 */
+	public function get_fetch_public_id( $path, $attachment_id ) {
+		$parts = explode( '/image/fetch/', $path );
+
+		if ( ! empty( $parts[1] ) ) {
+			$this->media->update_post_meta( $attachment_id, Sync::META_KEYS['delivery'], 'fetch' );
+
+			return $parts[1];
+		}
+
+		return '';
+	}
+
+	/**
+	 * Migrate legacy meta data to new meta.
+	 *
+	 * @param int $attachment_id The attachment ID to migrate.
+	 *
+	 * @return array();
+	 */
+	public function migrate_legacy_meta( $attachment_id ) {
+
+		$old_meta = wp_get_attachment_metadata( $attachment_id, true );
+		$v2_meta  = get_post_meta( $attachment_id, Sync::META_KEYS['cloudinary_legacy'], true );
+		$v3_meta  = array();
+
+		// Direct from old meta to v3, create v2 to chain the upgrade path.
+		if ( isset( $old_meta[ Sync::META_KEYS['cloudinary_legacy'] ] ) && empty( $v2_meta ) ) {
+			$v2_meta = $old_meta[ Sync::META_KEYS['cloudinary_legacy'] ];
+			// Add public ID.
+			$public_id                               = get_post_meta( $attachment_id, Sync::META_KEYS['public_id'], true );
+			$v2_meta[ Sync::META_KEYS['public_id'] ] = $public_id;
+			delete_post_meta( $attachment_id, Sync::META_KEYS['public_id'] );
+		}
+
+		// Handle v2 upgrade.
+		if ( ! empty( $v2_meta ) ) {
+			// Migrate to v3.
+			update_post_meta( $attachment_id, Sync::META_KEYS['cloudinary'], $v2_meta );
+			delete_post_meta( $attachment_id, Sync::META_KEYS['cloudinary_legacy'] );
+			$v3_meta = $v2_meta;
+			if ( ! empty( $v3_meta[ Sync::META_KEYS['public_id'] ] ) ) {
+				// Cleanup from v2.7.7.
+				if ( ! empty( $v3_meta[ Sync::META_KEYS['storage'] ] ) && 'cld' === $v3_meta[ Sync::META_KEYS['storage'] ] ) {
+					$file = get_post_meta( $attachment_id, '_wp_attached_file', true );
+					if ( $this->media->is_cloudinary_url( $file ) ) {
+						$file = path_join( dirname( $old_meta['file'] ), wp_basename( $file ) );
+						update_post_meta( $attachment_id, '_wp_attached_file', $file );
+						update_post_meta( $attachment_id, '_' . md5( $file ), $file );
+					}
+				}
+			}
+			// Remove old data style.
+			unset( $old_meta[ Sync::META_KEYS['cloudinary_legacy'] ] );
+		}
+
+		// Attempt to update old meta, which will fail if nothing changed.
+		update_post_meta( $attachment_id, '_wp_attachment_metadata', $old_meta );
+
+		// migrate from pre v2 meta.
+		if ( empty( $v2_meta ) && empty( $v3_meta ) ) {
+			// Attempt old post meta.
+			$public_id = get_post_meta( $attachment_id, Sync::META_KEYS['public_id'], true );
+			if ( ! empty( $public_id ) ) {
+				// Loop through all types and create new meta item.
+				$v3_meta = array(
+					Sync::META_KEYS['public_id'] => $public_id,
+				);
+				update_post_meta( $attachment_id, Sync::META_KEYS['cloudinary'], $v3_meta );
+				foreach ( Sync::META_KEYS as $meta_key ) {
+					if ( Sync::META_KEYS['cloudinary'] === $meta_key ) {
+						// Dont use the root as it will be an infinite loop.
+						continue;
+					}
+					$value = get_post_meta( $attachment_id, $meta_key, true );
+					if ( ! empty( $value ) ) {
+						$v3_meta[ $meta_key ] = $value;
+						$this->media->update_post_meta( $attachment_id, $meta_key, $value );
+					}
+				}
+			}
+		}
+
+		return $v3_meta;
+	}
+
+	/**
+	 * Setup hooks for the filters.
+	 */
+	public function setup_hooks() {
+
+		// Add filter to manage legacy items.
+		// @todo: cleanup `convert_cloudinary_version` by v2 upgrades to here.
+		add_filter( 'cloudinary_migrate_legacy_meta', array( $this, 'migrate_legacy_meta' ) );
+
+		// Add a redirection to the new plugin settings, from the old plugin.
+		if ( is_admin() ) {
+			add_action(
+				'admin_menu',
+				function () {
+					global $plugin_page;
+					if ( ! empty( $plugin_page ) && false !== strpos( $plugin_page, 'cloudinary-image-management-and-manipulation-in-the-cloud-cdn' ) ) {
+						wp_safe_redirect( admin_url( '?page=cloudinary' ) );
+						die;
+					}
+				}
+			);
+		}
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_sync_class-delete-sync.php.html b/docs/php_sync_class-delete-sync.php.html new file mode 100644 index 000000000..142cf5ee0 --- /dev/null +++ b/docs/php_sync_class-delete-sync.php.html @@ -0,0 +1,192 @@ + + + + + Source: php/sync/class-delete-sync.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/sync/class-delete-sync.php

+ + + + + + + +
+
+
<?php
+/**
+ * Delete Sync to Cloudinary.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Sync;
+
+use Cloudinary\Sync;
+use Cloudinary\Utils;
+
+/**
+ * Class Delete_Sync.
+ *
+ * Push media to Cloudinary on upload.
+ */
+class Delete_Sync {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     \Cloudinary\Plugin Instance of the global plugin.
+	 */
+	protected $plugin;
+
+	/**
+	 * Delete_Sync constructor.
+	 *
+	 * @param \Cloudinary\Plugin $plugin The plugin.
+	 */
+	public function __construct( \Cloudinary\Plugin $plugin ) {
+		$this->plugin = $plugin;
+	}
+
+	/**
+	 * Register any hooks that this component needs.
+	 */
+	private function register_hooks() {
+		add_action( 'delete_attachment', array( $this, 'delete_asset' ), 10 );
+		add_filter( 'user_has_cap', array( $this, 'can_delete_asset' ), 10, 3 );
+	}
+
+	/**
+	 * Checks if an image is synced before allowing a user to have rights to delete the file.
+	 *
+	 * @param array $all_caps All capabilities for the user.
+	 * @param array $caps     Current requested capabilities.
+	 * @param array $args     Additional args for the check.
+	 *
+	 * @return array
+	 */
+	public function can_delete_asset( $all_caps, $caps, $args ) {
+
+		if ( 3 === count( $args ) ) {
+			// The args are indexed, list them in named variables to better understand.
+			list( $request_cap, , $post_id ) = $args;
+
+			if ( $this->plugin->components['media']->is_media( $post_id ) && 'delete_post' === $request_cap && ! empty( $all_caps['delete_posts'] ) ) {
+
+				// Check if is pending.
+				if ( ! $this->plugin->components['sync']->is_synced( $post_id ) && $this->plugin->components['sync']->is_pending( $post_id ) ) {
+					// Check for errors.
+					$has_error = get_post_meta( $post_id, Sync::META_KEYS['sync_error'], true );
+					if ( empty( $has_error ) ) {
+						$all_caps['delete_posts'] = false;
+						$action                   = Utils::get_sanitized_text( 'action' );
+						if ( ! empty( $action ) && 'delete' === $action ) {
+							wp_die( esc_html__( 'Sorry, you can’t delete an asset until it has fully synced with Cloudinary. Try again once syncing is complete.', 'cloudinary' ) );
+						}
+					}
+				}
+			}
+		}
+
+		return $all_caps;
+	}
+
+	/**
+	 * Delete an asset on Cloudinary.
+	 *
+	 * @param int $post_id The post id to delete asset for.
+	 */
+	public function delete_asset( $post_id ) {
+		// In some environments, the $post_id is a string, failing ahead on a strict compare.
+		// For that reason we need to ensure the variable type.
+		$post_id = absint( $post_id );
+
+		if ( $this->plugin->components['sync']->is_synced( $post_id ) ) {
+
+			// check if this is not a transformation base image.
+			$public_id = $this->plugin->components['media']->get_public_id( $post_id, true );
+			$linked    = $this->plugin->components['media']->get_linked_attachments( $public_id );
+			if ( count( $linked ) > 1 ) {
+				// There are other attachments sharing this public_id, so skip it.
+				return;
+			}
+			if ( count( $linked ) === 1 && $post_id !== $linked[0] ) {
+				// Something odd is up. skip it in case.
+				return;
+			}
+			// Next we need to check that the file is in the cloudinary folder.
+			$path              = trim( Utils::pathinfo( $public_id, PATHINFO_DIRNAME ), '.' );
+			$cloudinary_folder = $this->plugin->settings->get_value( 'cloudinary_folder' );
+			if ( $cloudinary_folder === $path ) {
+				$type    = $this->plugin->components['media']->get_resource_type( $post_id );
+				$options = array(
+					'public_id'  => $public_id,
+					'invalidate' => true, // clear from CDN cache as well.
+				);
+				// Not a background request, since the post could be deleted before the background request hits causing it to not find the post and therefore not finding the public_id
+				// using the public_id directly in a background call, would make validation complicated since there is no longer a post to validate against.
+				$this->plugin->components['connect']->api->destroy( $type, $options );
+			}
+			/**
+			 * Action fired when deleting a synced asset.
+			 *
+			 * @hook   cloudinary_delete_asset
+			 * @since  3.0.1
+			 */
+			do_action( 'cloudinary_delete_asset', $post_id );
+		}
+	}
+
+	/**
+	 * Setup this component.
+	 */
+	public function setup() {
+		$this->register_hooks();
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_sync_class-push-sync.php.html b/docs/php_sync_class-push-sync.php.html new file mode 100644 index 000000000..695b4666d --- /dev/null +++ b/docs/php_sync_class-push-sync.php.html @@ -0,0 +1,362 @@ + + + + + Source: php/sync/class-push-sync.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/sync/class-push-sync.php

+ + + + + + + +
+
+
<?php
+/**
+ * Push Sync to Cloudinary.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Sync;
+
+use Cloudinary\Sync;
+use Cloudinary\Utils;
+
+/**
+ * Class Push_Sync
+ *
+ * Push media library to Cloudinary.
+ */
+class Push_Sync {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     \Cloudinary\Plugin Instance of the global plugin.
+	 */
+	public $plugin;
+
+	/**
+	 * Holds the ID of the last attachment synced.
+	 *
+	 * @var int
+	 */
+	protected $post_id;
+
+	/**
+	 * Holds the media component.
+	 *
+	 * @var \Cloudinary\Media
+	 */
+	protected $media;
+
+	/**
+	 * Holds the sync component.
+	 *
+	 * @var \Cloudinary\Sync
+	 */
+	protected $sync;
+
+	/**
+	 * Holds the connect component.
+	 *
+	 * @var \Cloudinary\Connect
+	 */
+	protected $connect;
+
+	/**
+	 * Holds the Rest_API component.
+	 *
+	 * @var \Cloudinary\REST_API
+	 */
+	protected $api;
+
+	/**
+	 * Holds the sync queue object.
+	 *
+	 * @var \Cloudinary\Sync\Sync_Queue
+	 */
+	public $queue;
+
+	/**
+	 * Push_Sync constructor.
+	 *
+	 * @param \Cloudinary\Plugin $plugin Global instance of the main plugin.
+	 */
+	public function __construct( \Cloudinary\Plugin $plugin ) {
+		$this->plugin = $plugin;
+		$this->register_hooks();
+	}
+
+	/**
+	 * Register any hooks that this component needs.
+	 */
+	private function register_hooks() {
+		add_filter( 'cloudinary_api_rest_endpoints', array( $this, 'rest_endpoints' ) );
+	}
+
+	/**
+	 * Setup this component.
+	 */
+	public function setup() {
+		// Setup components.
+		$this->media   = $this->plugin->components['media'];
+		$this->sync    = $this->plugin->components['sync'];
+		$this->connect = $this->plugin->components['connect'];
+		$this->api     = $this->plugin->components['api'];
+		$this->queue   = $this->sync->managers['queue'];
+
+		add_action( 'cloudinary_run_queue', array( $this, 'process_queue' ) );
+		add_action( 'cloudinary_sync_items', array( $this, 'process_assets' ) );
+	}
+
+	/**
+	 * Add endpoints to the \Cloudinary\REST_API::$endpoints array.
+	 *
+	 * @param array $endpoints Endpoints from the filter.
+	 *
+	 * @return array
+	 */
+	public function rest_endpoints( $endpoints ) {
+
+		$endpoints['attachments'] = array(
+			'method'              => \WP_REST_Server::READABLE,
+			'callback'            => array( $this, 'rest_get_queue_status' ),
+			'args'                => array(),
+			'permission_callback' => array( $this, 'rest_can_manage_assets' ),
+		);
+
+		$endpoints['sync'] = array(
+			'method'              => \WP_REST_Server::CREATABLE,
+			'callback'            => array( $this, 'rest_start_sync' ),
+			'args'                => array(),
+			'permission_callback' => array( $this, 'rest_can_manage_assets' ),
+		);
+
+		$endpoints['queue'] = array(
+			'method'   => \WP_REST_Server::CREATABLE,
+			'callback' => array( $this, 'process_queue' ),
+			'args'     => array(),
+		);
+		$endpoints['stats'] = array(
+			'method'   => \WP_REST_Server::READABLE,
+			'callback' => array( $this->queue, 'get_total_synced_media' ),
+			'args'     => array(),
+		);
+
+		return $endpoints;
+	}
+
+	/**
+	 * Admin permission callback.
+	 *
+	 * Explicitly defined to allow easier testability.
+	 *
+	 * @return bool
+	 */
+	public function rest_can_manage_assets() {
+		return Utils::user_can( 'manage_assets', 'manage_options', 'push_sync' );
+	}
+
+	/**
+	 * Get status of the current queue via REST API.
+	 *
+	 * @return \WP_REST_Response
+	 */
+	public function rest_get_queue_status() {
+
+		return rest_ensure_response(
+			array(
+				'success' => true,
+				'data'    => $this->queue->is_running(),
+			)
+		);
+	}
+
+	/**
+	 * Starts a sync backbround process.
+	 *
+	 * @param \WP_REST_Request $request The request.
+	 *
+	 * @return \WP_REST_Response
+	 */
+	public function rest_start_sync( \WP_REST_Request $request ) {
+
+		$type  = $request->get_param( 'type' );
+		$start = $this->queue->is_enabled();
+		$state = array(
+			'success' => false,
+		);
+		if ( empty( $start ) ) {
+			$this->queue->stop_queue();
+		} else {
+			$state['success'] = $this->queue->start_queue( $type );
+		}
+
+		return rest_ensure_response( $state );
+	}
+
+	/**
+	 * Process asset sync.
+	 *
+	 * @param int|array $attachments An attachment ID or an array of ID's.
+	 *
+	 * @return array
+	 */
+	public function process_assets( $attachments = array() ) {
+
+		$stat = array();
+		// If a single specified ID, push and return response.
+		$ids    = array_map( 'intval', (array) $attachments );
+		$thread = $this->plugin->settings->get_param( 'current_sync_thread' );
+		// Handle based on Sync Type.
+		foreach ( $ids as $attachment_id ) {
+
+			// Skip non uploadable media.
+			if ( ! $this->media->is_uploadable_media( $attachment_id ) ) {
+				continue;
+			}
+			// Skip unsyncable delivery types.
+			if ( ! $this->sync->is_syncable( $attachment_id ) ) {
+				continue;
+			}
+			// Flag attachment as being processed.
+			update_post_meta( $attachment_id, Sync::META_KEYS['syncing'], time() );
+			$stat[ $attachment_id ] = array();
+			while ( $type = $this->sync->get_sync_type( $attachment_id, false ) ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition
+				// translators: variable is sync type.
+				$action_message = sprintf( __( 'Sync type: %s', 'cloudinary' ), $type );
+				do_action( '_cloudinary_queue_action', $action_message, $thread );
+				if ( isset( $stat[ $attachment_id ][ $type ] ) ) {
+					// Loop prevention.
+					break;
+				}
+				$stat[ $attachment_id ][ $type ] = true;
+				$result                          = $this->sync->run_sync_method( $type, 'sync', $attachment_id );
+				if ( ! empty( $result ) ) {
+					$this->sync->log_sync_result( $attachment_id, $type, $result );
+				}
+			}
+
+			Utils::clean_up_sync_meta( $attachment_id );
+		}
+
+		return $stat;
+	}
+
+	/**
+	 * Attempts to restart an auto-sync thread if needed.
+	 */
+	public function init_autosync_restart() {
+		$thread = $this->plugin->settings->get_param( 'current_sync_thread' );
+		if ( 1 !== $this->queue->get_thread_state( $thread ) && $this->queue->get_post( $thread ) ) {
+			do_action( '_cloudinary_queue_action', __( 'Starting new thread.', 'cloudinary' ) );
+			$this->plugin->components['api']->background_request( 'queue', array( 'thread' => $thread ) );
+		}
+	}
+
+	/**
+	 * Resume the bulk sync.
+	 *
+	 * @param \WP_REST_Request $request The request.
+	 */
+	public function process_queue( \WP_REST_Request $request ) {
+
+		$thread = $request->get_param( 'thread' );
+
+		// A second thread would technically overwrite this, however, the manual queue is out in v3.
+		$this->plugin->settings->set_param( 'current_sync_thread', $thread );
+		$thread_type = $this->queue->get_thread_type( $thread );
+		if ( 'autosync' === $thread_type ) {
+			add_action( 'shutdown', array( $this, 'init_autosync_restart' ) );
+		}
+		$queue   = $this->queue->get_thread_queue( $thread );
+		$runs    = 0;
+		$last_id = 0;
+		if ( ! empty( $queue['next'] ) && $this->queue->is_running( $thread_type ) ) {
+			while ( ( $attachment_id = $this->queue->get_post( $thread ) ) && $runs < 10 ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition
+				if ( $last_id === $attachment_id ) {
+					update_post_meta( $attachment_id, Sync::META_KEYS['sync_error'], __( 'Asset in sync loop.', 'cloudinary' ) );
+					delete_post_meta( $attachment_id, $thread );
+					continue;
+				}
+
+				// translators: variable is thread name and asset ID.
+				$action_message = sprintf( __( '%1$s - cycle %3$s: Syncing asset %2$d', 'cloudinary' ), $thread, $attachment_id, $runs );
+				/**
+				 * Do action on queue action.
+				 *
+				 * @hook cloudinary_queue_action
+				 *
+				 * @param $action_message {string} The message.
+				 */
+				do_action( 'cloudinary_queue_action', $action_message );
+				$this->process_assets( $attachment_id );
+				$runs ++;
+				$last_id = $attachment_id;
+			}
+			$this->queue->stop_maybe( $thread_type );
+		}
+
+		// translators: variable is thread name.
+		$action_message = sprintf( __( 'Ending thread %s', 'cloudinary' ), $thread );
+		/**
+		 * Do action on queue action.
+		 *
+		 * @hook cloudinary_queue_action
+		 *
+		 * @param $action_message {string} The message.
+		 * @param $thread         {string} The thread.
+		 */
+		do_action( 'cloudinary_queue_action', $action_message, $thread );
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_sync_class-sync-queue.php.html b/docs/php_sync_class-sync-queue.php.html new file mode 100644 index 000000000..b91618464 --- /dev/null +++ b/docs/php_sync_class-sync-queue.php.html @@ -0,0 +1,1075 @@ + + + + + Source: php/sync/class-sync-queue.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/sync/class-sync-queue.php

+ + + + + + + +
+
+
<?php
+/**
+ * Sync queuing to Cloudinary.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Sync;
+
+use Cloudinary\Sync;
+use Cloudinary\Settings\Setting;
+use Cloudinary\Utils;
+
+/**
+ * Class Sync_Queue.
+ *
+ * Queue assets for Cloudinary sync.
+ */
+class Sync_Queue {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @since   0.1
+	 *
+	 * @var     \Cloudinary\Plugin Instance of the global plugin.
+	 */
+	protected $plugin;
+	/**
+	 * Holds the Sync instance.
+	 *
+	 * @since   2.5
+	 *
+	 * @var     Sync
+	 */
+	protected $sync;
+
+	/**
+	 * Holds the key for saving the queue.
+	 *
+	 * @var     string
+	 */
+	private static $queue_key = '_cloudinary_sync_queue';
+
+	/**
+	 * Holds the key for bulk queue state.
+	 *
+	 * @var     string
+	 */
+	private static $queue_enabled = '_cloudinary_bulk_sync_enabled';
+
+	/**
+	 * The cron frequency to ensure that the queue is progressing.
+	 *
+	 * @var int
+	 */
+	protected $cron_frequency;
+
+	/**
+	 * The cron offset since the last update.
+	 *
+	 * @var int
+	 */
+	protected $cron_start_offset;
+
+	/**
+	 * Holds the queue threads.
+	 *
+	 * @var array
+	 */
+	public $queue_threads = array();
+
+	/**
+	 * Holds all the threads.
+	 *
+	 * @var array
+	 */
+	public $threads;
+
+	/**
+	 * Holds the list of autosync threads.
+	 *
+	 * @var array
+	 */
+	protected $autosync_threads = array();
+
+	/**
+	 * Upload_Queue constructor.
+	 *
+	 * @param \Cloudinary\Plugin $plugin The plugin.
+	 */
+	public function __construct( \Cloudinary\Plugin $plugin ) {
+		$this->plugin = $plugin;
+		/**
+		 * Filter the cron job frequency.
+		 *
+		 * @hook    cloudinary_cron_frequency
+		 * @default 600
+		 *
+		 * @param $time {int} The filtered time.
+		 *
+		 * @return {int}
+		 */
+		$this->cron_frequency = apply_filters( 'cloudinary_cron_frequency', 10 * MINUTE_IN_SECONDS );
+		/**
+		 * Filter the cron start offset.
+		 *
+		 * @hook    cloudinary_cron_start_offset
+		 * @default 60
+		 *
+		 * @param $time {int} The filtered time.
+		 *
+		 * @return {int}
+		 */
+		$this->cron_start_offset = apply_filters( 'cloudinary_cron_start_offset', MINUTE_IN_SECONDS );
+		$this->load_hooks();
+	}
+
+	/**
+	 * Setup the sync queue.
+	 *
+	 * @param Sync $sync The sync instance.
+	 */
+	public function setup( $sync ) {
+		$this->sync = $sync;
+
+		/**
+		 * Filter the amount of background threads to process for manual syncing.
+		 *
+		 * @hook    cloudinary_queue_threads
+		 * @default 2
+		 *
+		 * @param $count {int} The number of manual sync threads to use.
+		 *
+		 * @return {int}
+		 */
+		$queue_threads_count = apply_filters( 'cloudinary_queue_threads', 2 );
+		for ( $i = 0; $i < $queue_threads_count; ++$i ) {
+			$this->queue_threads[] = 'queue_sync_thread_' . $i;
+		}
+
+		/**
+		 * Filter the amount of background threads to process for auto syncing.
+		 *
+		 * @hook    cloudinary_autosync_threads
+		 * @default 2
+		 *
+		 * @param $count {int} The number of autosync threads to use.
+		 *
+		 * @return {int}
+		 */
+		$autosync_thread_count = apply_filters( 'cloudinary_autosync_threads', 2 );
+		for ( $i = 0; $i < $autosync_thread_count; ++$i ) {
+			$this->autosync_threads[] = 'auto_sync_thread_' . $i;
+		}
+		$this->threads = array_merge( $this->queue_threads, $this->autosync_threads );
+
+		// Catch Queue actions.
+		// Enable sync queue.
+		if ( filter_input( INPUT_GET, 'enable-bulk', FILTER_VALIDATE_BOOLEAN ) ) {
+			$this->bulk_sync( true );
+			wp_safe_redirect( $this->sync->settings->get_component()->get_url() );
+			exit;
+		}
+		// Stop sync queue.
+		if ( filter_input( INPUT_GET, 'disable-bulk', FILTER_VALIDATE_BOOLEAN ) ) {
+			$this->bulk_sync( false );
+			wp_safe_redirect( $this->sync->settings->get_component()->get_url() );
+			exit;
+		}
+
+		// Periodically restart auto-sync.
+		if ( 'on' === $this->plugin->settings->get_value( 'auto_sync' ) && empty( get_transient( '_autosync_check' ) ) ) {
+			set_transient( '_autosync_check', true, $this->cron_frequency );
+			$this->start_threads( 'autosync' );
+		}
+	}
+
+	/**
+	 * Prepare and push the bulk sync start.
+	 *
+	 * @param bool $start Flag to start or stop the queue.
+	 */
+	protected function bulk_sync( $start ) {
+		if ( true === $start ) {
+			update_option( self::$queue_enabled, true, false );
+		} else {
+			delete_option( self::$queue_enabled );
+		}
+		$params = array(
+			'type' => 'queue',
+		);
+		$this->plugin->components['api']->background_request( 'sync', $params );
+	}
+
+	/**
+	 * Check if the sync is enabled.
+	 *
+	 * @return bool
+	 */
+	public function is_enabled() {
+		return get_option( self::$queue_enabled, false );
+	}
+
+	/**
+	 * Load the Upload Queue hooks.
+	 *
+	 * @return void
+	 */
+	public function load_hooks() {
+		add_action( 'cloudinary_resume_queue', array( $this, 'maybe_resume_queue' ) );
+		add_action( 'cloudinary_settings_save_setting_auto_sync', array( $this, 'change_setting_state' ), 10, 3 );
+	}
+
+	/**
+	 * Filter the setting in order to disable the bulk sync if the autosync is disabled.
+	 *
+	 * @param mixed   $new_value     The new value.
+	 * @param mixed   $current_value The current value.
+	 * @param Setting $setting       The setting object.
+	 *
+	 * @return mixed
+	 */
+	public function change_setting_state( $new_value, $current_value, $setting ) {
+		// shutdown queues if needed.
+		if ( 'on' === $current_value && 'off' === $new_value ) {
+			if ( $this->is_running() ) {
+				$this->shutdown_queue( 'queue' );
+				add_settings_error( $setting->get_option_name(), 'disabled_sync', __( 'Bulk sync has been disabled.', 'cloudinary' ), 'warning' );
+			}
+			// Shutdown autosync queue.
+			$this->shutdown_queue( 'autosync' );
+		}
+
+		return $new_value;
+	}
+
+	/**
+	 * Get the current Queue.
+	 *
+	 * @param string $type The type of queue to get.
+	 *
+	 * @return array
+	 */
+	public function get_queue( $type = 'queue' ) {
+		$default = array(
+			'threads' => array(),
+			'running' => false,
+		);
+		switch ( $type ) {
+			case 'queue':
+				wp_cache_delete( self::$queue_key, 'options' );
+				$return = get_option( self::$queue_key, $default );
+				break;
+			case 'autosync':
+				$return            = $default;
+				$return['running'] = $this->is_running( 'autosync' );
+				if ( true === $return['running'] ) {
+					foreach ( $this->autosync_threads as $thread ) {
+						if ( 2 <= $this->get_thread_state( $thread ) ) {
+							$return['threads'][] = $thread;
+						}
+					}
+				}
+				break;
+			default:
+				$return = $default;
+				break;
+		}
+
+		$return = wp_parse_args( $return, $default );
+
+		return $return;
+	}
+
+	/**
+	 * Get a set of pending items.
+	 *
+	 * @param string $thread The thread ID.
+	 *
+	 * @return int|false
+	 */
+	public function get_post( $thread ) {
+
+		$return = false;
+		if ( ( $this->is_running( $this->get_thread_type( $thread ) ) ) ) {
+			$thread_queue = $this->get_thread_queue( $thread );
+			// translators: variable is thread name and queue size.
+			$action_message = sprintf( __( '%1$s : Queue size :  %2$s.', 'cloudinary' ), $thread, $thread_queue['count'] );
+			/**
+			 * Do action on queue action.
+			 *
+			 * @hook cloudinary_queue_action
+			 *
+			 * @param $action_message {string} The message.
+			 * @param $thread         {string} The thread.
+			 */
+			do_action( 'cloudinary_queue_action', $action_message, $thread );
+			if ( empty( $thread_queue['next'] ) ) {
+				// Nothing left to sync.
+				return $return;
+			}
+			$return               = $thread_queue['next'];
+			$thread_queue['next'] = 0;
+			$thread_queue['ping'] = time();
+			$this->set_thread_queue( $thread, $thread_queue );
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Check if the queue is running.
+	 *
+	 * @param string $type Queue type to check if is running.
+	 *
+	 * @return bool
+	 */
+	public function is_running( $type = 'queue' ) {
+		if ( 'autosync' === $type ) {
+			return true; // Autosync always runs, however if off, auto sync queue building is off.
+		}
+		$queue = $this->get_queue();
+
+		return $queue['running'];
+	}
+
+
+	/**
+	 * The number of optimized media assets.
+	 *
+	 * @return int
+	 */
+	public static function get_optimized_assets() {
+		global $wpdb;
+
+		$wpdb->cld_table = Utils::get_relationship_table();
+
+		return (int) $wpdb->get_var( "SELECT COUNT( DISTINCT post_id ) as total FROM {$wpdb->cld_table} WHERE sync_type = 'media';" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+	}
+
+
+	/**
+	 * Get the data from _cloudinary_relationships. We need to get a count of all assets, which have public_id's, group by type etc.
+	 *
+	 * @return array
+	 */
+	protected function query_unsynced_data() {
+		global $wpdb;
+
+		$cached = get_transient( Sync::META_KEYS['dashboard_cache'] );
+		if ( empty( $cached ) ) {
+			$return                       = array();
+			$wpdb->cld_table              = Utils::get_relationship_table();
+			$query_string                 = "SELECT COUNT( DISTINCT post_id ) as total FROM {$wpdb->cld_table} JOIN {$wpdb->posts} on( {$wpdb->cld_table}.post_id = {$wpdb->posts}.ID ) WHERE {$wpdb->posts}.post_type = 'attachment'";
+			$return['total_assets']       = (int) $wpdb->get_var( $query_string ); // phpcs:ignore WordPress.DB
+			$return['unoptimized_assets'] = (int) $wpdb->get_var( $query_string . ' AND public_id IS NULL' ); // phpcs:ignore WordPress.DB
+
+			$asset_sizes           = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+				$wpdb->prepare(
+					"SELECT
+			SUM( meta_value ) as total, meta_key as type
+		FROM
+		{$wpdb->cld_table} AS cld
+		RIGHT JOIN {$wpdb->postmeta} AS wp ON ( cld.post_id = wp.post_id )
+		WHERE
+			meta_key IN ( %s,%s )
+		GROUP BY meta_key",
+					Sync::META_KEYS['local_size'],
+					Sync::META_KEYS['remote_size']
+				),
+				ARRAY_A
+			);
+			$return['asset_sizes'] = array(
+				'local'  => 0,
+				'remote' => 0,
+			);
+			foreach ( $asset_sizes as $set ) {
+				$type                           = Sync::META_KEYS['local_size'] === $set['type'] ? 'local' : 'remote';
+				$return['asset_sizes'][ $type ] = (int) $set['total'];
+			}
+
+			$threads                = $this->autosync_threads;
+			$primary_queue          = array_shift( $threads );
+			$return['total_queued'] = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(post_id) FROM {$wpdb->postmeta} WHERE meta_key = %s;", $primary_queue ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+			$return['errors']       = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(post_id) FROM {$wpdb->postmeta} WHERE meta_key = %s;", Sync::META_KEYS['sync_error'] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
+
+			set_transient( Sync::META_KEYS['dashboard_cache'], $return, 15 );
+			$cached = $return;
+		}
+
+		return $cached;
+	}
+
+	/**
+	 * Get the sync status.
+	 *
+	 * @return array
+	 */
+	public function get_total_synced_media() {
+
+		$data = $this->query_unsynced_data();
+
+		if ( empty( $data['asset_sizes'] ) || empty( $data['asset_sizes']['local'] ) ) {
+			return array(); // Nothing - don't do calculations.
+		}
+		// Lets calculate byte sizes.
+		$total_local_size  = $data['asset_sizes']['local'];
+		$total_remote_size = $data['asset_sizes']['remote'];
+		$total_assets      = $data['total_assets'];
+		$total_unoptimized = $data['unoptimized_assets'] - $data['total_queued']; // Queued is removed from unoptimized count.
+		$total_optimized   = $data['total_assets'] - $data['unoptimized_assets'];
+		$total_queued      = $data['total_queued'];
+		$errors_count      = $data['errors'];
+		// Remove negative.
+		if ( 0 > $total_unoptimized ) {
+			$total_unoptimized = 0;
+		}
+		$unoptimized_text = __( 'All assets optimized.', 'cloudinary' );
+		if ( 0 < $total_queued && 0 === $total_unoptimized ) {
+			$unoptimized_text = __( 'Optimizing assets.', 'cloudinary' );
+		}
+		// Prepare the package.
+		$return = array(
+			// Original sizes.
+			'original_size'           => $total_local_size,
+			'original_size_percent'   => 0 !== $total_assets ? '100%' : '0%', // The original will always be 100%, as it's the comparison to optimized.
+			'original_size_hr'        => size_format( $total_local_size ),
+
+			// Optimized size. We use the `original_size` to determine the percentage between for the progress bar.
+			'optimized_size'          => $total_remote_size,
+			'optimized_size_percent'  => $total_local_size > 0 ? round( abs( $total_remote_size ) / abs( $total_local_size ) * 100 ) . '%' : '0%', // This is the percentage difference.
+			'optimized_diff_percent'  => $total_local_size > 0 ? round( ( $total_local_size - $total_remote_size ) / $total_local_size * 100 ) . '%' : '0%', // We use this for the "Size saved.." status text.
+			'optimized_size_hr'       => size_format( $total_remote_size ), // This is the formatted byte size.
+
+			// Optimized is the % optimized vs unoptimized.
+			'optimized_percent'       => $total_assets > 0 ? round( $total_optimized / $total_assets, 4 ) : 0,
+			'optimized_percent_hr'    => $total_assets > 0 ? round( $total_optimized / $total_assets * 100, 1 ) . '%' : '0%',
+			'optimized_info'          => __( 'Optimized assets', 'cloudinary' ),
+
+			// Error size: No mockups on what to display here.
+			'error_count'             => $errors_count,
+			// translators: placeholders are the number of errors.
+			'error_count_hr'          => 0 === $errors_count ? '' : sprintf( _n( '%s error with assets', '%s errors with assets', $errors_count, 'cloudinary' ), number_format_i18n( $errors_count ) ),
+			'error_clean_up'          => 0 === $errors_count ? '' : __( 'Fix Sync Errors', 'cloudinary' ),
+
+			// Number of assets.
+			'total_assets'            => $total_assets, // This is a count of the assets in _cloudinary_relationships.
+			'total_unoptimized'       => $total_optimized, // This is the number of assets that dont have a public_id in _cloudinary_relationships.
+
+			// Status text.
+			// translators: placeholders are the number of assets unoptimized.
+			'unoptimized_status_text' => 0 === $total_unoptimized ? $unoptimized_text : sprintf( _n( '%s asset excluded from optimization.', '%s assets excluded from optimization.', $total_unoptimized, 'cloudinary' ), number_format_i18n( $total_unoptimized ) ),
+			// translators: placeholders are the number of assets unoptimized.
+			'optimized_status_text'   => 0 !== $total_queued ? sprintf( __( '%1$s assets of %2$s currently syncing with Cloudinary.', 'cloudinary' ), number_format_i18n( $total_queued ), number_format_i18n( $total_assets ) ) : '', // This will be shown when items are pending (check queue).
+		);
+
+		return $return;
+	}
+
+	/**
+	 * Build the upload sync queue.
+	 */
+	public function build_queue() {
+
+		$args = array(
+			'post_type'           => 'attachment',
+			'post_mime_type'      => array(),
+			'post_status'         => 'inherit',
+			'paged'               => 1,
+			'posts_per_page'      => 100,
+			'fields'              => 'ids',
+			// phpcs:ignore WordPress.DB.SlowDBQuery
+			'meta_query'          => array(
+				'relation' => 'AND',
+				array(
+					'key'     => Sync::META_KEYS['sync_error'],
+					'compare' => 'NOT EXISTS',
+				),
+				array(
+					'key'     => Sync::META_KEYS['cloudinary'],
+					'compare' => 'NOT EXISTS',
+				),
+				array(
+					'key'     => Sync::META_KEYS['queued'],
+					'compare' => 'NOT EXISTS',
+				),
+			),
+			'ignore_sticky_posts' => false,
+			'no_found_rows'       => true,
+		);
+
+		if ( 'on' === $this->plugin->settings->get_value( 'image_delivery' ) ) {
+			$args['post_mime_type'][] = 'image';
+		}
+
+		if ( 'on' === $this->plugin->settings->get_value( 'video_delivery' ) ) {
+			$args['post_mime_type'][] = 'video';
+		}
+
+		/**
+		 * Filter the params for the query used to build a queue.
+		 *
+		 * @hook   cloudinary_build_queue_query
+		 * @since  2.7.6
+		 *
+		 * @param $args {array} The arguments for the query.
+		 *
+		 * @return {array}
+		 */
+		$args = apply_filters( 'cloudinary_build_queue_query', $args );
+
+		if ( empty( $args['post_mime_type'] ) ) {
+			$action_message = __( 'No mime types to query.', 'cloudinary' );
+			do_action( '_cloudinary_queue_action', $action_message );
+
+			return;
+		}
+
+		// translators: variable is page number.
+		$action_message = __( 'Building Queue.', 'cloudinary' );
+		/**
+		 * Do action on queue action.
+		 *
+		 * @hook cloudinary_queue_action
+		 *
+		 * @param $action_message {string} The message.
+		 */
+		do_action( 'cloudinary_queue_action', $action_message );
+
+		$query = new \WP_Query( $args );
+		if ( ! $query->have_posts() ) {
+			// translators: variable is page number.
+			$action_message = __( 'No posts', 'cloudinary' );
+			/**
+			 * Do action on queue action.
+			 *
+			 * @hook cloudinary_queue_action
+			 *
+			 * @param $action_message {string} The message.
+			 */
+			do_action( 'cloudinary_queue_action', $action_message );
+
+			return;
+		}
+		$ids = array();
+		do {
+			$ids  = array_merge( $ids, $query->get_posts() );
+			$args = $query->query_vars;
+			++$args['paged'];
+			$query = new \WP_Query( $args );
+		} while ( $query->have_posts() );
+
+		$threads          = $this->add_to_queue( $ids );
+		$queue            = array();
+		$queue['total']   = array_sum( $threads );
+		$queue['threads'] = array_keys( $threads );
+		$queue['started'] = current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
+		wp_cache_delete( self::$queue_enabled, 'options' );
+		$queue['running'] = get_option( self::$queue_enabled );
+		// Set the queue option.
+		update_option( self::$queue_key, $queue, false );
+	}
+
+	/**
+	 * Maybe stop the queue.
+	 *
+	 * @param string $type The type to maybe stop.
+	 */
+	public function stop_maybe( $type = 'queue' ) {
+		$queue = $this->get_queue( $type );
+		foreach ( $queue['threads'] as $thread ) {
+			if ( 2 <= $this->get_thread_state( $thread ) ) {
+				return; // Only 1 thread still needs to be running.
+			}
+		}
+		// Stop the queue.
+		$this->stop_queue( $type );
+		// Restart the queue to make sure there are no new items added after the last start.
+		$this->start_queue( $type );
+	}
+
+	/**
+	 * Shuts down the queue and disable sync bulk.
+	 *
+	 * @param string $type The type of queue to shutdown.
+	 */
+	protected function shutdown_queue( $type = 'queue' ) {
+		if ( 'queue' === $type ) {
+			delete_option( self::$queue_enabled );
+		} elseif ( 'autosync' === $type ) {
+			// Remove pending flag.
+			delete_post_meta_by_key( Sync::META_KEYS['pending'] );
+		}
+		$this->stop_queue( $type );
+	}
+
+	/**
+	 * Stop the current queue cycle. Will restart once cycle is freed up.
+	 *
+	 * @param string $type The type of queue to stop.
+	 */
+	public function stop_queue( $type = 'queue' ) {
+
+		// translators: variable is queue type.
+		$action_message = sprintf( __( 'Stopping queue:  %s.', 'cloudinary' ), $type );
+		/**
+		 * Do action on queue action.
+		 *
+		 * @hook cloudinary_queue_action
+		 *
+		 * @param $action_message {string} The message.
+		 */
+		do_action( 'cloudinary_queue_action', $action_message );
+		if ( 'queue' === $type ) {
+			delete_post_meta_by_key( Sync::META_KEYS['queued'] );
+		} else {
+			delete_post_meta_by_key( Sync::META_KEYS['pending'] );
+		}
+		$threads = $this->get_threads( $type );
+		foreach ( $threads as $thread ) {
+			$this->reset_thread_queue( $thread );
+			delete_post_meta_by_key( $thread );
+		}
+
+		if ( 'queue' === $type ) {
+			delete_option( self::$queue_key );
+			wp_unschedule_hook( 'cloudinary_resume_queue' );
+		}
+	}
+
+	/**
+	 * Start the queue by setting the started flag.
+	 *
+	 * @param string $type The type of queue to start.
+	 *
+	 * @return bool
+	 */
+	public function start_queue( $type = 'queue' ) {
+		$started = false;
+		if ( ! $this->is_running( $type ) ) {
+			if ( 'queue' === $type ) {
+				$this->build_queue();
+				$this->schedule_resume();
+			}
+			$started = $this->start_threads( $type );
+			if ( ! $started ) {
+				$this->shutdown_queue( $type );
+			}
+		} else {
+			// translators: variable is queue type.
+			$action_message = sprintf( __( 'Queue:  %s - not running.', 'cloudinary' ), $type );
+			/**
+			 * Do action on queue action.
+			 *
+			 * @hook cloudinary_queue_action
+			 *
+			 * @param $action_message {string} The message.
+			 */
+			do_action( 'cloudinary_queue_action', $action_message );
+		}
+
+		return $started;
+	}
+
+	/**
+	 * Check if thread is autosync thread.
+	 *
+	 * @param string $thread Thread name.
+	 *
+	 * @return bool
+	 */
+	public function is_autosync_thread( $thread ) {
+		return in_array( $thread, $this->autosync_threads, true );
+	}
+
+	/**
+	 * Start all threads.
+	 *
+	 * @param string $type The type of threads to start.
+	 *
+	 * @return bool
+	 */
+	public function start_threads( $type = 'queue' ) {
+		$queue           = $this->get_queue( $type );
+		$threads_started = false;
+		foreach ( $queue['threads'] as $thread ) {
+			if ( 2 !== $this->start_thread( $thread ) ) {
+				$this->reset_thread_queue( $thread );
+				continue;
+			}
+			$threads_started = true;
+			usleep( 500 ); // Slight pause to prevent server overload.
+		}
+
+		return $threads_started;
+	}
+
+	/**
+	 * Start a thread to process.
+	 *
+	 * @param string $thread Thread ID.
+	 *
+	 * @return int State of thread.
+	 */
+	public function start_thread( $thread ) {
+		// Check thread is still running.
+		$sync_state = $this->get_thread_state( $thread );
+		if ( 3 === $sync_state ) {
+			// translators: variable is thread name.
+			$action_message = sprintf( __( 'Starting thread %s.', 'cloudinary' ), $thread );
+			do_action( '_cloudinary_queue_action', $action_message, $thread );
+			/**
+			 * Do action on queue action.
+			 *
+			 * @hook cloudinary_queue_action
+			 *
+			 * @param $action_message {string} The message.
+			 * @param $thread         {string} The thread.
+			 */
+			do_action( 'cloudinary_queue_action', $action_message, $thread );
+			$this->plugin->components['api']->background_request( 'queue', array( 'thread' => $thread ) );
+			$sync_state = 2; // Set as started.
+		}
+
+		return $sync_state;
+	}
+
+	/**
+	 * Get the option name for a thread.
+	 *
+	 * @param string $thread Thread name.
+	 *
+	 * @return string
+	 */
+	protected function get_thread_option( $thread ) {
+		return self::$queue_key . '_' . $thread;
+	}
+
+	/**
+	 * Get the thread type fora thread name..
+	 *
+	 * @param string $thread Thread name.
+	 *
+	 * @return string
+	 */
+	public function get_thread_type( $thread ) {
+
+		return $this->is_autosync_thread( $thread ) ? 'autosync' : 'queue';
+	}
+
+	/**
+	 * Get a threads queue.
+	 *
+	 * @param string $thread Thread ID.
+	 *
+	 * @return array
+	 */
+	public function get_thread_queue( $thread ) {
+		$return = array();
+		if ( in_array( $thread, $this->threads, true ) ) {
+			$thread_option = $this->get_thread_option( $thread );
+			$default       = array(
+				'ping' => 0, // set to 0 to ready to start.
+				'next' => 0,
+			);
+			wp_cache_delete( $thread_option, 'options' );
+			$return = get_option( $thread_option );
+			if ( empty( $return ) ) {
+				// Set option to remove notoption and default fro  cache.
+				$this->set_thread_queue( $thread, $default );
+				$return = $default;
+			}
+			$return = array_merge( $return, $this->get_thread_queue_details( $thread ) );
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Get the count of posts and the next post to be synced.
+	 *
+	 * @param string $thread Thread name.
+	 *
+	 * @return array
+	 */
+	protected function get_thread_queue_details( $thread ) {
+
+		$args = array(
+			'post_type'      => 'attachment',
+			'post_status'    => 'inherit',
+			'posts_per_page' => 1,
+			'fields'         => 'ids',
+			'cache_results'  => false,
+			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+			'meta_query'     => array(
+				array(
+					'key'     => $thread,
+					'compare' => 'EXISTS',
+				),
+			),
+		);
+
+		/**
+		 * Filter the params for the query used to get thread queue details.
+		 *
+		 * @hook   cloudinary_thread_queue_details_query
+		 * @since  2.7.6
+		 *
+		 * @param $args   {array}  The arguments for the query.
+		 * @param $thread {string} The thread name.
+		 *
+		 * @return {array}
+		 */
+		$args = apply_filters( 'cloudinary_thread_queue_details_query', $args, $thread );
+
+		$query = new \WP_Query( $args );
+
+		$return = array(
+			'count' => 0,
+			'next'  => 0,
+		);
+		if ( ! empty( $query->have_posts() ) ) {
+			$return['count'] = $query->found_posts;
+			$return['next']  = $query->next_post();
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Add to a threads queue.
+	 *
+	 * @param int   $thread         Thread ID.
+	 * @param array $attachment_ids The ID to add.
+	 */
+	public function add_to_thread_queue( $thread, array $attachment_ids ) {
+
+		if ( in_array( $thread, $this->threads, true ) ) {
+			foreach ( $attachment_ids as $id ) {
+				$previous_thread = null;
+				if ( metadata_exists( 'post', $id, Sync::META_KEYS['queued'] ) ) {
+					if ( 'queue' === $this->get_thread_type( $thread ) ) {
+						continue;
+					}
+					$previous_thread = get_post_meta( $id, Sync::META_KEYS['queued'], true );
+					delete_post_meta( $id, $previous_thread, true );
+					delete_post_meta( $id, Sync::META_KEYS['queued'], $previous_thread );
+				}
+				add_post_meta( $id, Sync::META_KEYS['queued'], $thread, true );
+				add_post_meta( $id, $thread, true, true );
+			}
+		}
+	}
+
+	/**
+	 * Set the threads queue;
+	 *
+	 * @param string $thread       The thread to set.
+	 * @param array  $thread_queue The queue to set.
+	 */
+	protected function set_thread_queue( $thread, $thread_queue ) {
+		update_option( $this->get_thread_option( $thread ), $thread_queue, false );
+	}
+
+	/**
+	 * Get threads of a type.
+	 *
+	 * @param string $type The type to get.
+	 *
+	 * @return array
+	 */
+	public function get_threads( $type = 'queue' ) {
+		$types = array(
+			'queue'    => $this->queue_threads,
+			'autosync' => $this->autosync_threads,
+		);
+
+		if ( 'all' === $type ) {
+			return $types;
+		}
+
+		if ( isset( $types[ $type ] ) ) {
+			return $types[ $type ];
+		}
+
+		return array();
+	}
+
+	/**
+	 * Add to the autosync queue.
+	 *
+	 * @param array  $attachment_ids Array of IDs to add to autosync.
+	 * @param string $type           The type of queue to add to.
+	 *
+	 * @return array
+	 */
+	public function add_to_queue( array $attachment_ids, $type = 'queue' ) {
+
+		$been_synced    = array_filter( $attachment_ids, array( $this->sync, 'been_synced' ) );
+		$new_items      = array_diff( $attachment_ids, $been_synced );
+		$threads        = $this->get_threads( $type );
+		$new_thread     = array_shift( $threads );
+		$active_threads = array();
+		if ( ! empty( $new_items ) ) {
+			$this->add_to_thread_queue( $new_thread, $new_items );
+			$active_threads[ $new_thread ] = count( $new_items );
+		}
+
+		$attachment_ids = $been_synced;
+		if ( ! empty( $attachment_ids ) ) {
+			$chunk_size = ceil( count( $attachment_ids ) / count( $threads ) );
+			$chunks     = array_chunk( $attachment_ids, $chunk_size );
+			foreach ( $chunks as $index => $chunk ) {
+				$thread = array_shift( $threads );
+				$this->add_to_thread_queue( $thread, $chunk );
+				$active_threads[ $thread ] = count( $chunk );
+			}
+		}
+
+		return $active_threads;
+	}
+
+	/**
+	 * Reset a threads queue.
+	 *
+	 * @param string $thread Thread name.
+	 */
+	public function reset_thread_queue( $thread ) {
+		delete_option( $this->get_thread_option( $thread ) );
+	}
+
+	/**
+	 * Schedule a resume queue check.
+	 */
+	protected function schedule_resume() {
+		$now = current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
+		wp_schedule_single_event( $now + $this->cron_frequency, 'cloudinary_resume_queue' );
+	}
+
+	/**
+	 * Get the state of the thread.
+	 *
+	 * @param string $thread Thread name to check.
+	 *
+	 * @return int  0 = disabled, 1 = ended, 2 = active, 3 = stalled/ready to start.
+	 */
+	public function get_thread_state( $thread ) {
+
+		$return = 0; // Default state is disabled.
+
+		if ( $this->is_running( $this->get_thread_type( $thread ) ) ) {
+			$thread_queue = $this->get_thread_queue( $thread );
+			$offset       = time() - $thread_queue['ping'];
+			$return       = 3; // If autosync is running, default is ready/stalled.
+			if ( empty( $thread_queue['next'] ) && empty( $thread_queue['count'] ) ) {
+				$return = 1; // Queue is empty, so nothing to sync, set as ended.
+			} elseif ( ! empty( $thread_queue['ping'] ) && $offset < $this->cron_start_offset ) {
+				$return = 2; // If the last ping is within the time frame, it's still active.
+			}
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Maybe resume the queue.
+	 * This is a fallback mechanism to resume the queue when it stops unexpectedly.
+	 *
+	 * @return void
+	 */
+	public function maybe_resume_queue() {
+
+		/**
+		 * Do action on queue action.
+		 *
+		 * @hook cloudinary_queue_action
+		 */
+		do_action( 'cloudinary_queue_action', __( 'Resuming Maybe', 'cloudinary' ) );
+		$stopped = array();
+		if ( $this->is_running() ) {
+			// Check each thread.
+			foreach ( $this->threads as $thread ) {
+				if ( 3 === $this->get_thread_state( $thread ) ) {
+					// Possible that thread has stopped.
+					$stopped[] = $thread;
+					// translators: variable is thread name.
+					$action_message = sprintf( __( 'Thread %s Stopped.', 'cloudinary' ), $thread );
+					/**
+					 * Do action on queue action.
+					 *
+					 * @hook cloudinary_queue_action
+					 *
+					 * @param $action_message {string} The message.
+					 * @param $thread         {string} The thread.
+					 */
+					do_action( 'cloudinary_queue_action', $action_message, $thread );
+				}
+			}
+
+			if ( count( $stopped ) === count( $this->threads ) ) {
+				// All threads have stopped. Stop Queue to prevent overload in case of a slow sync.
+				$this->stop_queue();
+				sleep( 5 ); // give it 5 seconds to allow the stop and maybe threads to catchup.
+				// Start a new sync.
+				$this->start_queue();
+			} elseif ( ! empty( $stopped ) ) {
+				// Just start the threads that have stopped.
+				array_map( array( $this, 'start_thread' ), $stopped );
+				$this->schedule_resume();
+			} else {
+				$this->schedule_resume();
+			}
+		}
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_sync_class-unsync.php.html b/docs/php_sync_class-unsync.php.html new file mode 100644 index 000000000..76759724b --- /dev/null +++ b/docs/php_sync_class-unsync.php.html @@ -0,0 +1,325 @@ + + + + + Source: php/sync/class-unsync.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/sync/class-unsync.php

+ + + + + + + +
+
+
<?php
+/**
+ * Unsync class for the Cloudinary plugin.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\Sync;
+
+use Cloudinary\Plugin;
+use Cloudinary\Sync;
+use Cloudinary\Media;
+use Cloudinary\Sync\Storage;
+use WP_Post;
+
+/**
+ * Class Unsync
+ */
+class Unsync {
+
+	/**
+	 * Holds the plugin instance.
+	 *
+	 * @var Plugin Instance of the global plugin.
+	 */
+	public $plugin;
+
+	/**
+	 * Holds the Media instance.
+	 *
+	 * @var Media
+	 */
+	protected $media;
+
+	/**
+	 * Holds the Sync instance.
+	 *
+	 * @var Sync
+	 */
+	protected $sync;
+
+	/**
+	 * Holds the Storage instance.
+	 *
+	 * @var Storage
+	 */
+	protected $storage;
+
+	/**
+	 * Unsync constructor.
+	 *
+	 * @param Plugin $plugin Global instance of the main plugin.
+	 */
+	public function __construct( Plugin $plugin ) {
+		$this->plugin = $plugin;
+	}
+
+	/**
+	 * Holds the unsync action keys.
+	 */
+	const UNSYNC_ACTION = 'cloudinary-unsync';
+
+	/**
+	 * Holds the unsync/resync toggle action keys.
+	 */
+	const UNSYNC_TOGGLE = 'cloudinary-sync-toggle';
+
+	/**
+	 * Register any hooks that this component needs.
+	 */
+	public function setup() {
+		$this->media   = $this->plugin->get_component( 'media' );
+		$this->sync    = $this->plugin->get_component( 'sync' );
+		$this->storage = $this->plugin->get_component( 'storage' );
+
+		if ( 'off' === $this->plugin->settings->get_value( 'auto_sync' ) ) {
+			add_action( 'attachment_submitbox_misc_actions', array( $this, 'single_action' ), 11 );
+			add_filter( 'handle_bulk_actions-upload', array( $this, 'handle_bulk_actions' ), 11, 3 );
+			add_filter( 'media_row_actions', array( $this, 'add_inline_action' ), 10, 2 );
+			add_filter( 'bulk_actions-upload', array( $this, 'add_bulk_actions' ) );
+		}
+	}
+
+	/**
+	 * Adds the deliver checkbox on the single image edit screen.
+	 *
+	 * @param WP_Post $attachment The attachment post object.
+	 */
+	public function single_action( $attachment ) {
+		// Set url for action handling.
+		$action_url = add_query_arg(
+			array(
+				'action'   => self::UNSYNC_TOGGLE,
+				'media[]'  => $attachment->ID,
+				'_wpnonce' => wp_create_nonce( 'bulk-media' ),
+			),
+			'upload.php'
+		);
+		$link_text  = $this->sync->been_synced( $attachment->ID ) ? $this->get_action_text() : __( 'Sync with Cloudinary', 'cloudinary' );
+		$status     = $this->sync->filter_media_states( array(), $attachment );
+		?>
+		<div class="misc-pub-section misc-pub-sync-unsync">
+			<?php if ( ! empty( $status ) ) : ?>
+				<?php echo esc_html( array_shift( $status ) ); ?>
+			<?php else : ?>
+				<a href="<?php echo esc_url( $action_url ); ?>"><?php echo esc_html( $link_text ); ?></a>
+			<?php endif; ?>
+		</div>
+		<?php
+	}
+
+	/**
+	 * Handles bulk actions for attachments.
+	 *
+	 * @param string $location The location to redirect after.
+	 * @param string $action   The action to handle.
+	 * @param array  $post_ids Post ID's to action.
+	 *
+	 * @return string
+	 */
+	public function handle_bulk_actions( $location, $action, $post_ids ) {
+
+		$actions = array(
+			self::UNSYNC_ACTION,
+			self::UNSYNC_TOGGLE,
+		);
+
+		if ( in_array( $action, $actions, true ) ) {
+			switch ( $action ) {
+				case self::UNSYNC_ACTION:
+					$post_ids = array_filter( $post_ids, array( $this->sync, 'been_synced' ) );
+					foreach ( $post_ids as $id ) {
+						$this->unsync_attachment( $id );
+					}
+					break;
+				case self::UNSYNC_TOGGLE:
+					$attachment_id = array_shift( $post_ids );
+					if ( $this->sync->been_synced( $attachment_id ) ) {
+						$this->unsync_attachment( $attachment_id );
+					} else {
+						$this->sync->add_to_sync( $attachment_id );
+					}
+					$location = get_edit_post_link( $attachment_id, 'edit' );
+					break;
+
+			}
+
+			/**
+			 * Action to flush delivery caches.
+			 *
+			 * @hook   cloudinary_flush_cache
+			 * @since  3.0.0
+			 */
+			do_action( 'cloudinary_flush_cache' );
+		}
+
+		return $location;
+	}
+
+	/**
+	 * Add an inline action for manual sync.
+	 *
+	 * @param array    $actions All actions.
+	 * @param \WP_Post $post    The current post object.
+	 *
+	 * @return array
+	 */
+	public function add_inline_action( $actions, $post ) {
+		if ( $this->sync->been_synced( $post->ID ) ) {
+
+			// Set url for action handling.
+			$action_url = add_query_arg(
+				array(
+					'action'   => self::UNSYNC_ACTION,
+					'media[]'  => $post->ID,
+					'_wpnonce' => wp_create_nonce( 'bulk-media' ),
+				),
+				'upload.php'
+			);
+
+			// Add link th actions.
+			$actions[ self::UNSYNC_ACTION ] = sprintf(
+				'<a href="%1$s" aria-label="%2$s">%2$s</a>',
+				$action_url,
+				$this->get_action_text()
+			);
+		}
+
+		return $actions;
+	}
+
+	/**
+	 * Add bulk actions.
+	 *
+	 * @param array $actions Current actions to add to.
+	 *
+	 * @return array
+	 */
+	public function add_bulk_actions( $actions ) {
+		$new_action = array(
+			self::UNSYNC_ACTION => $this->get_action_text(),
+		);
+		$actions    = array_merge( $new_action, $actions );
+
+		return $actions;
+	}
+
+	/**
+	 * Get the action text.
+	 *
+	 * @return string
+	 */
+	protected function get_action_text() {
+		return __( 'Unsync from Cloudinary', 'cloudinary' );
+	}
+
+	/**
+	 * Unsync an attachment from cloudinary.
+	 *
+	 * @param int $attachment_id The attachment to unsync.
+	 */
+	public function unsync_attachment( $attachment_id ) {
+
+		$this->sync->set_pending( $attachment_id );
+		$cloudinary_id = $this->media->get_cloudinary_id( $attachment_id );
+		$url           = $this->media->cloudinary_url( $attachment_id, 'raw', array(), $cloudinary_id, true );
+		$url           = remove_query_arg( '_i', $url );
+		$storage       = $this->media->get_post_meta( $attachment_id, Sync::META_KEYS['storage'], true );
+		if ( 'cld' === $storage || 'dual_low' === $storage ) {
+			if ( 'dual_low' === $storage ) {
+				$this->storage->remove_local_assets( $attachment_id );
+			}
+
+			$file_maybe = get_attached_file( $attachment_id );
+			if ( ! file_exists( $file_maybe ) ) {
+				remove_filter( 'wp_unique_filename', array( $this->storage, 'unique_filename' ), 10 );
+				$date     = get_post_datetime( $attachment_id );
+				$download = $this->sync->managers['download']->download_asset( $attachment_id, $url, $date->format( 'Y/m' ) );
+				add_filter( 'wp_unique_filename', array( $this->storage, 'unique_filename' ), 10, 3 );
+				if ( is_wp_error( $download ) ) {
+					wp_die( esc_html( $download->get_error_message() ) );
+				}
+			}
+		}
+
+		// Remove meta data.
+		$sync_key  = $this->media->get_public_id_from_url( $url, true );
+		$public_id = $this->media->get_public_id( $attachment_id );
+		// Delete sync keys.
+		delete_post_meta( $attachment_id, '_' . md5( $sync_key ), true );
+		delete_post_meta( $attachment_id, '_' . md5( 'base_' . $public_id ), true );
+		foreach ( Sync::META_KEYS as $key ) {
+			delete_post_meta( $attachment_id, $key );
+		}
+		$this->sync->set_signature_item( $attachment_id, 'file' );
+		/**
+		 * Action unsyncing an attachment.
+		 *
+		 * @hook   cloudinary_unsync_asset
+		 * @since  3.0.0
+		 *
+		 * @param $attachment_id {int}    The attachment ID.
+		 */
+		do_action( 'cloudinary_unsync_asset', $attachment_id );
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_ui_class-component.php.html b/docs/php_ui_class-component.php.html new file mode 100644 index 000000000..920d923be --- /dev/null +++ b/docs/php_ui_class-component.php.html @@ -0,0 +1,970 @@ + + + + + Source: php/ui/class-component.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/ui/class-component.php

+ + + + + + + +
+
+
<?php
+/**
+ * Abstract UI Component.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\UI;
+
+use Cloudinary\Settings\Setting;
+use Cloudinary\UI\State;
+use function Cloudinary\get_plugin_instance;
+
+/**
+ * Abstract Component.
+ *
+ * @package Cloudinary\UI
+ */
+abstract class Component {
+
+	/**
+	 * Holds the components type.
+	 *
+	 * @var string
+	 */
+	protected $type;
+
+	/**
+	 * Holds the parent setting for this component.
+	 *
+	 * @var Setting
+	 */
+	protected $setting;
+
+	/**
+	 * Holds the components build parts.
+	 *
+	 * @var array
+	 */
+	protected $build_parts;
+
+	/**
+	 * Holds the components build blueprint.
+	 *
+	 * @var string
+	 */
+	protected $blueprint = 'wrap|header|icon/|title/|collapse/|/header|body|clear/|/body|settings/|/wrap';
+
+	/**
+	 * Holds a list of the Components used parts.
+	 *
+	 * @var array
+	 */
+	protected $used_parts;
+	/**
+	 * Holds the components built HTML parts.
+	 *
+	 * @var array
+	 */
+	protected $html = array();
+
+	/**
+	 * Flag if component is a capture type.
+	 *
+	 * @var bool
+	 */
+	protected static $capture = false;
+
+	/**
+	 * Holds the conditional logic sequence.
+	 *
+	 * @var array
+	 */
+	protected static $condition = array();
+
+	/**
+	 * Holds the UI state.
+	 *
+	 * @var State
+	 */
+	protected $state;
+
+	/**
+	 * Render component for a setting.
+	 * Component constructor.
+	 *
+	 * @param Setting $setting The parent Setting.
+	 */
+	public function __construct( $setting ) {
+		$this->setting = $setting;
+		$this->state   = get_plugin_instance()->get_component( 'state' );
+		$class         = strtolower( get_class( $this ) );
+		$class_name    = substr( strrchr( $class, '\\' ), 1 );
+		$this->type    = str_replace( '_', '-', $class_name );
+
+		// Setup blueprint.
+		$this->blueprint = $this->setting->get_param( 'blueprint', $this->blueprint );
+
+		// Setup the components parts for render.
+		$this->setup_component_parts();
+
+		// Add scripts.
+		$this->enqueue_scripts();
+	}
+
+	/**
+	 * Setup the component.
+	 */
+	public function setup() {
+		$this->setup_conditions();
+	}
+
+	/**
+	 * Setup the conditions.
+	 */
+	public function setup_conditions() {
+		// Setup conditional logic.
+		if ( $this->setting->has_param( 'condition' ) ) {
+			$condition = $this->setting->get_param( 'condition' );
+			foreach ( $condition as $slug => $value ) {
+				$bound = $this->setting->get_root_setting()->get_setting( $slug, false );
+				if ( ! is_null( $bound ) ) {
+					$path = array(
+						'attributes',
+						'input',
+						'data-bind-trigger',
+					);
+					$bound->set_param( implode( $this->setting->separator, $path ), $bound->get_slug() );
+				} else {
+					$this->setting->set_param( 'condition', null );
+				}
+			}
+		}
+	}
+
+	/**
+	 * Enqueue scripts this component may use.
+	 */
+	public function enqueue_scripts() {
+	}
+
+	/**
+	 * Magic caller to filter component parts dynamically.
+	 *
+	 * @param string $name The part name.
+	 * @param array  $args array of args to pass to the filter.
+	 *
+	 * @return mixed
+	 */
+	public function __call( $name, $args ) {
+		if ( empty( $args ) ) {
+			return null;
+		}
+		$struct = $args[0];
+		if ( $this->setting->has_param( $name ) ) {
+			$struct['content'] = $this->setting->get_param( $name );
+		}
+
+		// Apply type to each structs.
+		$struct['attributes']['class'][] = 'cld-' . $this->type;
+
+		return $struct;
+	}
+
+	/**
+	 * Setup the components build parts.
+	 */
+	protected function setup_component_parts() {
+
+		$default_input_atts = array(
+			'type'  => $this->type,
+			'class' => array(),
+		);
+		$input_atts         = $this->setting->get_param(
+			'attributes',
+			array()
+		);
+
+		$build_parts = array(
+			'wrap'        => array(
+				'element'    => 'div',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'header'      => array(
+				'element'    => 'div',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'icon'        => array(
+				'element'    => 'div',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'title'       => array(
+				'element'    => 'div',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'collapse'    => array(
+				'element'    => 'div',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'tooltip'     => array(
+				'element'    => 'div',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'body'        => array(
+				'element'    => 'p',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'clear'       => array(
+				'element'    => 'div',
+				'attributes' => array(
+					'class' => array(
+						'clear',
+					),
+				),
+			),
+			'input'       => array(
+				'element'    => 'input',
+				'render'     => 'true',
+				'attributes' => wp_parse_args( $input_atts, $default_input_atts ),
+			),
+			'settings'    => array(
+				'element'    => 'div',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'prefix'      => array(
+				'element'    => 'span',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'suffix'      => array(
+				'element'    => 'span',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'description' => array(
+				'element'    => 'div',
+				'attributes' => array(
+					'class' => array(),
+				),
+			),
+			'conditional' => array(
+				'element'    => 'div',
+				'attributes' => array(
+					'data-condition' => wp_json_encode( $this->setting->get_param( 'condition', array() ) ),
+				),
+			),
+		);
+
+		/**
+		 * Filter the components build parts.
+		 *
+		 * @hook cloudinary_setup_component_parts
+		 *
+		 * @param $build_parts {array} The build parts.
+		 * @param $this        {self}  The component object.
+		 *
+		 * @return {array}
+		 */
+		$structs = apply_filters( 'cloudinary_setup_component_parts', $build_parts, $this );
+		foreach ( $structs as $name => $struct ) {
+			$struct['attributes']['class'][] = 'cld-ui-' . $name;
+			$this->register_component_part( $name, $struct );
+		}
+	}
+
+	/**
+	 * Registers a new component part type.
+	 *
+	 * @param string $name   Name for the part type.
+	 * @param array  $struct The array structure for the part type.
+	 */
+	public function register_component_part( $name, $struct ) {
+		$base                       = array(
+			'element'    => 'div',
+			'attributes' => array(),
+			'children'   => array(),
+			'content'    => null,
+		);
+		$this->build_parts[ $name ] = wp_parse_args( $struct, $base );
+	}
+
+	/**
+	 * Sanitize the value.
+	 *
+	 * @param string $value The value to sanitize.
+	 *
+	 * @return string
+	 */
+	public function sanitize_value( $value ) {
+		return wp_kses_post( $value );
+	}
+
+	/**
+	 * Check if component is enabled.
+	 *
+	 * @return bool
+	 */
+	protected function is_enabled() {
+		$enabled = $this->setting->get_param( 'enabled', true );
+		if ( is_callable( $enabled ) ) {
+			$enabled = call_user_func( $enabled, $this );
+		}
+
+		return $enabled;
+	}
+
+	/**
+	 * Renders the component.
+	 *
+	 * @param bool $echo Flag to echo output or return it.
+	 *
+	 * @return string
+	 */
+	public function render( $echo = false ) {
+		// Setup the component.
+		$this->pre_render();
+
+		// Check if component is enabled.
+		$enabled = $this->is_enabled();
+		if ( false === $enabled ) {
+			return null;
+		}
+		// Build the blueprint parts list.
+		$blueprint = $this->setting->get_param( 'blueprint', $this->blueprint );
+		if ( empty( $blueprint ) ) {
+			return null;
+		}
+		$build_parts = explode( '|', $blueprint );
+
+		// Build the multi-dimensional array.
+		$struct = $this->build_struct( $build_parts );
+		$this->compile_structures( $struct );
+
+		// Output html.
+		$return = self::compile_html( $this->html );
+		if ( false === $echo ) {
+			return $return;
+		}
+		echo $return; // phpcs:ignore WordPress.Security.EscapeOutput
+	}
+
+	/**
+	 * Build the structures from the build parts.
+	 *
+	 * @param array $parts Array of build parts to build.
+	 *
+	 * @return array
+	 */
+	protected function build_struct( &$parts ) {
+
+		$struct = array();
+		while ( ! empty( $parts ) ) {
+			$part  = array_shift( $parts );
+			$state = $this->get_state( $part );
+			if ( 'close' === $state ) {
+				return $struct;
+			}
+			$name                 = trim( $part, '/' );
+			$part_struct          = $this->get_part( $name );
+			$part_struct['state'] = $state;
+			$part_struct['name']  = $name;
+			$struct[ $name ]      = $this->{$name}( $part_struct );
+			// Prepare struct array.
+			$this->prepare_struct_array( $struct, $parts, $name );
+		}
+
+		return $struct;
+	}
+
+	/**
+	 * Prepared struct for children and multiple element building.
+	 *
+	 * @param array  $struct The structure array.
+	 * @param array  $parts  The parts of the component.
+	 * @param string $name   The component part name.
+	 */
+	protected function prepare_struct_array( &$struct, &$parts, $name ) {
+		if ( ! isset( $struct[ $name ] ) ) {
+			return; // Bail if struct is missing.
+		}
+		if ( $this->is_struct_array( $struct[ $name ] ) ) {
+			$base_struct = $struct[ $name ];
+			unset( $struct[ $name ] );
+			foreach ( $base_struct as $index => $struct_instance ) {
+				$struct_name            = $struct_instance['name'] . '_inst_' . $index;
+				$struct[ $struct_name ] = $struct_instance;
+				$this->prepare_struct_array( $struct, $parts, $struct_name );
+			}
+
+			return;
+		}
+		// Build children.
+		if ( 'open' === $struct[ $name ]['state'] ) {
+			$struct[ $name ]['children'] += $this->build_struct( $parts );
+		}
+	}
+
+	/**
+	 * Check if the structure is an array of structures.
+	 *
+	 * @param array $struct The structure to check.
+	 *
+	 * @return bool
+	 */
+	protected function is_struct_array( $struct ) {
+		return is_array( $struct ) && ! isset( $struct['state'] ) && isset( $struct[0] );
+	}
+
+	/**
+	 * Go through the structures and compile.
+	 *
+	 * @param array $structure The components structures.
+	 */
+	protected function compile_structures( $structure ) {
+		foreach ( $structure as $struct ) {
+			$this->handle_structure( $struct['name'], $struct );
+		}
+	}
+
+	/**
+	 * Get a blueprint parts state.
+	 *
+	 * @param string $part The part name.
+	 *
+	 * @return string
+	 */
+	public function get_state( $part ) {
+		$state = 'open';
+		$pos   = strpos( $part, '/' );
+		if ( is_int( $pos ) ) {
+			switch ( $pos ) {
+				case 0:
+					$state = 'close';
+					break;
+				default:
+					$state = 'void';
+			}
+		}
+
+		return $state;
+	}
+
+	/**
+	 * Get the components value.
+	 *
+	 * @return mixed
+	 */
+	public function get_value() {
+		return $this->setting->get_value();
+	}
+
+	/**
+	 * Handles a structure part before rendering.
+	 *
+	 * @param string $name   The name of the part.
+	 * @param array  $struct The parts structure.
+	 */
+	public function handle_structure( $name, $struct ) {
+		if ( $this->has_content( $name, $struct ) ) {
+			if ( ! empty( $struct['element'] ) && $this->setting->has_param( 'condition' ) ) {
+				$struct = $this->conditional( $struct );
+				$this->setting->set_param( 'condition', null );
+			}
+			$this->compile_part( $struct );
+		}
+	}
+
+	/**
+	 * Recursively check if the current structure has content.
+	 *
+	 * @param string | null $name   The name of the part.
+	 * @param array         $struct The part structure.
+	 *
+	 * @return bool
+	 */
+	public function has_content( $name, $struct = array() ) {
+		$return = isset( $struct['content'] ) || ! empty( $this->setting->get_param( $name ) ) || ! empty( $struct['render'] );
+		if ( false === $return && ! empty( $struct['children'] ) ) {
+			foreach ( $struct['children'] as $child => $child_struct ) {
+				if ( true === $this->has_content( $child, $child_struct ) ) {
+					$return = true;
+					break;
+				}
+			}
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Build a component part.
+	 *
+	 * @param array $struct The component part structure array.
+	 */
+	public function compile_part( $struct ) {
+		$this->open_tag( $struct );
+		if ( ! self::is_void_element( $struct['element'] ) ) {
+			$this->add_content( $struct['content'] );
+			if ( ! empty( $struct['children'] ) ) {
+				foreach ( $struct['children'] as $child ) {
+					if ( ! is_null( $child ) ) {
+						$this->handle_structure( $child['name'], $child );
+					}
+				}
+			}
+			$this->close_tag( $struct );
+		}
+	}
+
+	/**
+	 * Opens a new tag.
+	 *
+	 * @param array $struct The tag structure.
+	 */
+	protected function open_tag( $struct ) {
+		if ( ! empty( $struct['element'] ) ) {
+			$this->html[] = self::build_tag( $struct['element'], $struct['attributes'] );
+		}
+	}
+
+	/**
+	 * Closes an open tag.
+	 *
+	 * @param array $struct The tag structure.
+	 */
+	protected function close_tag( $struct ) {
+		if ( ! empty( $struct['element'] ) ) {
+			$this->html[] = self::build_tag( $struct['element'], $struct['attributes'], 'close' );
+		}
+	}
+
+	/**
+	 * Adds the content to the html.
+	 *
+	 * @param string $content The content to add.
+	 */
+	protected function add_content( $content ) {
+
+		if ( ! is_string( $content ) && is_callable( $content ) ) {
+			$this->html[] = call_user_func( $content );
+		} else {
+			$this->html[] = $content;
+		}
+	}
+
+	/**
+	 * Check if an element type is a void elements.
+	 *
+	 * @param string $element The element to check.
+	 *
+	 * @return bool
+	 */
+	public static function is_void_element( $element ) {
+		$void_elements = array(
+			'area',
+			'base',
+			'br',
+			'col',
+			'embed',
+			'hr',
+			'img',
+			'input',
+			'link',
+			'meta',
+			'param',
+			'source',
+			'track',
+			'wbr',
+		);
+
+		return ! empty( $element ) && in_array( strtolower( $element ), $void_elements, true );
+	}
+
+	/**
+	 * Build an HTML tag.
+	 *
+	 * @param string $element    The element to build.
+	 * @param array  $attributes The attributes for the tags.
+	 * @param string $state      The element state.
+	 *
+	 * @return string
+	 */
+	public static function build_tag( $element, $attributes = array(), $state = 'open' ) {
+
+		$prefix_element = 'close' === $state ? '/' : '';
+		$tag            = array();
+		$tag[]          = $prefix_element . $element;
+		if ( 'close' !== $state ) {
+			$tag[] = self::build_attributes( $attributes );
+		}
+		$tag[] = self::is_void_element( $element ) ? '/' : null;
+
+		return self::compile_tag( $tag );
+	}
+
+	/**
+	 * Get a build part to construct.
+	 *
+	 * @param string $part The part name.
+	 *
+	 * @return array
+	 */
+	public function get_part( $part ) {
+		$struct = array(
+			'element'    => $part,
+			'attributes' => array(),
+			'children'   => array(),
+			'state'      => null,
+			'content'    => null,
+			'name'       => $part,
+		);
+		if ( isset( $this->build_parts[ $part ] ) ) {
+			$struct = wp_parse_args( $this->build_parts[ $part ], $struct );
+		}
+		if ( $this->setting->has_param( 'attributes' . $this->setting->separator . $part ) ) {
+			$struct['attributes'] = wp_parse_args( $this->setting->get_param( 'attributes' . $this->setting->separator . $part ), $struct['attributes'] );
+		}
+
+		return $struct;
+	}
+
+	/**
+	 * Filter the title parts structure.
+	 *
+	 * @param array $struct The array structure.
+	 *
+	 * @return array
+	 */
+	protected function title( $struct ) {
+		$struct['content'] = $this->setting->get_param( 'title', $this->setting->get_param( 'page_title' ) );
+
+		return $struct;
+	}
+
+	/**
+	 * Filter the tooltip parts structure.
+	 *
+	 * @param array $struct The array structure.
+	 *
+	 * @return array
+	 */
+	protected function tooltip( $struct ) {
+		$struct['content'] = null;
+		if ( $this->setting->has_param( 'tooltip_text' ) ) {
+			$struct['render']              = true;
+			$struct['attributes']['class'] = array(
+				'cld-tooltip',
+			);
+			$struct['content']             = $this->setting->get_param( 'tooltip_text' );
+		}
+
+		return $struct;
+	}
+
+	/**
+	 * Filter the icon parts structure.
+	 *
+	 * @param array $struct The array structure.
+	 *
+	 * @return array
+	 */
+	protected function icon( $struct ) {
+
+		$icon   = $this->setting->get_param( 'icon' );
+		$method = 'dashicon';
+		if ( ! empty( $icon ) && false === strpos( $icon, 'dashicons' ) ) {
+			$method = 'image_icon';
+		}
+
+		return $this->$method( $struct );
+	}
+
+	/**
+	 * Filter the dashicon parts structure.
+	 *
+	 * @param array  $struct The array structure.
+	 * @param string $icon   The dashicon slug.
+	 *
+	 * @return array
+	 */
+	protected function dashicon( $struct, $icon = 'dashicons-yes-alt' ) {
+		$struct['element']               = 'span';
+		$struct['attributes']['class'][] = 'dashicons';
+		$struct['attributes']['class'][] = $icon;
+
+		return $struct;
+	}
+
+	/**
+	 * Filter the image icons parts structure.
+	 *
+	 * @param array $struct The array structure.
+	 *
+	 * @return array
+	 */
+	protected function image_icon( $struct ) {
+		$struct['element']           = 'img';
+		$struct['attributes']['src'] = $this->setting->get_param( 'icon' );
+
+		return $struct;
+	}
+
+	/**
+	 * Filter the settings parts structure.
+	 *
+	 * @param array $struct The array structure.
+	 *
+	 * @return array
+	 */
+	protected function settings( $struct ) {
+		$struct['element'] = null;
+		if ( $this->setting->has_settings() ) {
+			$html = array();
+			foreach ( $this->setting->get_settings() as $setting ) {
+				$html[] = $setting->get_component()->render();
+			}
+			$struct['content'] = self::compile_html( $html );
+		}
+
+		return $struct;
+	}
+
+	/**
+	 * Builds and sanitizes attributes for an HTML tag.
+	 *
+	 * @param array $attributes Array of key value attributes to build.
+	 *
+	 * @return string
+	 */
+	public static function build_attributes( $attributes ) {
+		$return = array();
+		foreach ( $attributes as $attribute => $value ) {
+			if ( is_numeric( $attribute ) ) {
+				$return[] = esc_attr( $value );
+				continue;
+			}
+			if ( is_array( $value ) ) {
+				if ( count( $value ) !== count( $value, COUNT_RECURSIVE ) ) {
+					$value = wp_json_encode( $value );
+				} else {
+					$value = implode( ' ', $value );
+				}
+			}
+			if ( ! is_string( $value ) && is_callable( $value ) ) {
+				$value = call_user_func( $value );
+			}
+			$return[] = esc_attr( $attribute ) . '="' . esc_attr( $value ) . '"';
+		}
+
+		return implode( ' ', $return );
+	}
+
+	/**
+	 * Compiles HTML parts array into a string.
+	 *
+	 * @param array $html HTML parts array.
+	 *
+	 * @return string
+	 */
+	public static function compile_html( $html ) {
+		$html = array_filter(
+			$html,
+			function ( $item ) {
+				return ! is_null( $item );
+			}
+		);
+
+		return implode( '', $html );
+	}
+
+	/**
+	 * Compiles a tag from a parts array into a string.
+	 *
+	 * @param array $tag Tag parts array.
+	 *
+	 * @return string
+	 */
+	public static function compile_tag( $tag ) {
+		$tag = array_filter( $tag );
+
+		return '<' . implode( ' ', $tag ) . '>';
+	}
+
+	/**
+	 * Init the component.
+	 *
+	 * @param Setting $setting The setting object.
+	 *
+	 * @return self
+	 */
+	final public static function init( $setting ) {
+
+		$caller = get_called_class();
+		$type   = $setting->get_param( 'type', 'tag' );
+		// Final check if type is callable component.
+		if ( ! is_string( $type ) || ! self::is_component_type( $type ) ) {
+			// Check what type this component needs to be.
+			if ( is_callable( $type ) ) {
+				$setting->set_param( 'callback', $type );
+				$setting->set_param( 'type', 'custom' );
+				$type = 'custom';
+			} else {
+				// Set to a default HTML component if not found.
+				$type = 'html';
+			}
+			$component = "{$caller}\\{$type}";
+		} else {
+			// Set Caller.
+			$component = "{$caller}\\{$type}";
+		}
+		$component = new $component( $setting );
+		$component->setup();
+
+		return $component;
+	}
+
+	/**
+	 * Check if the type is a component.
+	 *
+	 * @param string $type The type to check.
+	 *
+	 * @return bool
+	 */
+	public static function is_component_type( $type ) {
+		$caller = get_called_class();
+
+		// Check that this type of component exists.
+		return is_callable( array( $caller . '\\' . $type, 'init' ) );
+	}
+
+	/**
+	 * Filter the conditional struct.
+	 *
+	 * @param array $struct The struct array.
+	 *
+	 * @return array
+	 */
+	protected function conditional( $struct ) {
+
+		if ( $this->setting->has_param( 'condition' ) ) {
+			$conditions     = $this->setting->get_param( 'condition' );
+			$results        = array();
+			$class          = 'open';
+			$condition_data = array();
+			foreach ( $conditions as $slug => $value ) {
+				$setting                                = $this->setting->find_setting( $slug );
+				$compare_value                          = $setting->get_value();
+				$results[]                              = $value === $compare_value;
+				$condition_data[ $setting->get_slug() ] = $value;
+			}
+			$struct['attributes']['class'][] = 'cld-ui-conditional';
+
+			if ( in_array( false, $results, true ) ) {
+				$class = 'closed';
+			}
+			$struct['attributes']['class'][]        = $class;
+			$struct['attributes']['data-condition'] = wp_json_encode( $condition_data );
+		}
+
+		return $struct;
+	}
+
+	/**
+	 * Filter the body struct.
+	 *
+	 * @param array $struct The struct array.
+	 *
+	 * @return array
+	 */
+	protected function body( $struct ) {
+		$struct['content'] = $this->setting->get_param( 'content' );
+
+		return $struct;
+	}
+
+	/**
+	 * Setup action before rendering.
+	 */
+	protected function pre_render() {
+	}
+
+	/**
+	 * Check if this is a capture component.
+	 *
+	 * @return bool
+	 */
+	final public static function is_capture() {
+		$caller = get_called_class();
+
+		// Check that this type of component exists.
+		return $caller::$capture;
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/php_ui_component_class-crops.php.html b/docs/php_ui_component_class-crops.php.html new file mode 100644 index 000000000..c34c21b1f --- /dev/null +++ b/docs/php_ui_component_class-crops.php.html @@ -0,0 +1,315 @@ + + + + + Source: php/ui/component/class-crops.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: php/ui/component/class-crops.php

+ + + + + + + +
+
+
<?php
+/**
+ * Select UI Component.
+ *
+ * @package Cloudinary
+ */
+
+namespace Cloudinary\UI\Component;
+
+use Cloudinary\Settings\Setting;
+use Cloudinary\Utils;
+use function Cloudinary\get_plugin_instance;
+
+/**
+ * Class Component
+ *
+ * @package Cloudinary\UI
+ */
+class Crops extends Select {
+
+	/**
+	 * Holds the demo file name.
+	 *
+	 * @var string
+	 */
+	protected $demo_files = array(
+		'sample.jpg',
+		'lady.jpg',
+		'horses.jpg',
+	);
+
+	/**
+	 * Holds the components build blueprint.
+	 *
+	 * @var string
+	 */
+	protected $blueprint = 'wrap|hr/|label|title/|prefix/|/label|info_box/|input/|/wrap';
+
+	/**
+	 * Enqueue scripts this component may use.
+	 */
+	public function enqueue_scripts() {
+		wp_enqueue_media();
+	}
+
+	/**
+	 * Filter the hr structure.
+	 *
+	 * @param array $struct The array structure.
+	 *
+	 * @return array
+	 */
+	protected function hr( $struct ) {
+
+		if ( 'image_settings' === $this->setting->get_parent()->get_slug() ) {
+			$struct['element'] = 'hr';
+			$struct['render']  = true;
+		}
+
+		return $struct;
+	}
+
+	/**
+	 * Filter the info box structure.
+	 *
+	 * @param array $struct The array structure.
+	 *
+	 * @return array
+	 */
+	protected function info_box( $struct ) {
+		$panel_toggle = new Setting( 'info_box_crop_gravity' );
+		$panel_toggle->set_param( 'title', __( 'What is Crop and Gravity control?', 'cloudinary' ) );
+		$panel_toggle->set_param(
+			'text',
+			sprintf(
+				// translators: %1$s: link to Crop doc, %2$s: link to Gravity doc.
+				__( 'This feature allows you to fine tune the behaviour of the %1$s and %2$s per registered crop size of the delivered images.', 'cloudinary' ),
+				'<a href="https://cloudinary.com/documentation/resizing_and_cropping#resize_and_crop_modes" target="_blank">' . __( 'Crop', 'cloudinary' ) . '</a>',
+				'<a href="https://cloudinary.com/documentation/resizing_and_cropping#control_gravity" target="_blank">' . __( 'Gravity', 'cloudinary' ) . '</a>'
+			)
+		);
+		$panel_toggle->set_param( 'icon', get_plugin_instance()->dir_url . 'css/images/crop.svg' );
+		$title_toggle = new Info_Box( $panel_toggle );
+
+		$struct['element'] = 'div';
+		$struct['content'] = $title_toggle->render();
+
+		return $struct;
+	}
+
+	/**
+	 * Filter the select input parts structure.
+	 *
+	 * @param array $struct The array structure.
+	 *
+	 * @return array
+	 */
+	protected function input( $struct ) {
+
+		$mode                             = $this->setting->get_param( 'mode', 'demos' );
+		$wrapper                          = $this->get_part( 'div' );
+		$wrapper['attributes']['class'][] = 'cld-size-items';
+		if ( 'full' === $mode ) {
+			$wrapper['attributes']['data-base'] = dirname( get_plugin_instance()->get_component( 'connect' )->api->cloudinary_url( '' ) );
+		} else {
+			$wrapper['attributes']['data-base'] = 'https://res.cloudinary.com/demo/image/upload';
+		}
+
+		$value = $this->setting->get_value();
+		if ( empty( $value ) ) {
+			$value = array();
+		}
+		$sizes = Utils::get_registered_sizes();
+
+		$selector                                = $this->make_selector();
+		$wrapper['children']['control-selector'] = $selector;
+		foreach ( $sizes as $size => $details ) {
+			if ( empty( $details['crop'] ) ) {
+				continue;
+			}
+			$row                          = $this->get_part( 'div' );
+			$row['attributes']['class'][] = 'cld-size-items-item';
+			$row['attributes']['class'][] = 'crop-preview';
+			$row['content']               = $size;
+
+			$image            = $this->get_part( 'img' );
+			$image['content'] = $size;
+			$size_array       = array();
+			if ( ! empty( $details['width'] ) ) {
+				$size_array[]                 = 'w_' . $details['width'];
+				$image['attributes']['width'] = $details['width'];
+			}
+			if ( ! empty( $details['height'] ) ) {
+				$size_array[]                  = 'h_' . $details['height'];
+				$image['attributes']['height'] = $details['height'];
+			}
+			$image['attributes']['data-size'] = implode( ',', $size_array );
+			$size_key                         = $details['width'] . 'x' . $details['height'];
+			if ( empty( $value[ $size_key ] ) ) {
+				$value[ $size_key ] = '';
+			}
+			$row['children']['size']  = $image;
+			$row['children']['input'] = $this->make_input( $this->get_name() . '[' . $size_key . ']', $value[ $size_key ] );
+			// Set the placeholder.
+			$placeholder = 'c_fill,g_auto';
+
+			if ( 'thumbnail' === $size ) {
+				$placeholder = 'c_thumb,g_auto';
+			}
+			$row['children']['input']['children']['input']['attributes']['placeholder'] = $placeholder;
+
+			$wrapper['children'][ $size ] = $row;
+
+		}
+
+		return $wrapper;
+	}
+
+	/**
+	 * Make an image selector.
+	 */
+	protected function make_selector() {
+		$selector                          = $this->get_part( 'div' );
+		$selector['attributes']['class'][] = 'cld-image-selector';
+		$mode                              = $this->setting->get_param( 'mode', 'demos' );
+
+		/**
+		 * Filter the demo files.
+		 *
+		 * @hook   cloudinary_registered_sizes
+		 * @since  3.1.3
+		 *
+		 * @param $demo_files {array} array of demo files.
+		 *
+		 * @return {array}
+		 */
+		$examples = apply_filters( 'cloudinary_demo_crop_files', $this->demo_files );
+		if ( 'full' === $mode ) {
+			$public_id = $this->setting->get_root_setting()->get_param( 'preview_id' );
+			if ( ! empty( $public_id ) ) {
+				$examples = array(
+					$public_id,
+				);
+			}
+		}
+		foreach ( $examples as $index => $file ) {
+			$name                             = pathinfo( $file, PATHINFO_FILENAME );
+			$item                             = $this->get_part( 'span' );
+			$item['attributes']['data-image'] = $file;
+			if ( 0 === $index ) {
+				$item['attributes']['data-selected'] = true;
+
+			}
+			$item['attributes']['class'][] = 'cld-image-selector-item';
+
+			$item['content']                           = $name;
+			$selector['children'][ 'image-' . $index ] = $item;
+		}
+
+		return $selector;
+	}
+
+	/**
+	 * Make an input line.
+	 *
+	 * @param string $name  The name of the input.
+	 * @param string $value The value.
+	 *
+	 * @return array
+	 */
+	protected function make_input( $name, $value ) {
+
+		$wrapper                        = $this->get_part( 'span' );
+		$wrapper['attributes']['class'] = array(
+			'crop-size-inputs',
+		);
+
+		$label                      = $this->get_part( 'label' );
+		$label['attributes']['for'] = $name;
+		$label['content']           = __( 'Disable', 'cloudinary' );
+
+		$check                          = $this->get_part( 'input' );
+		$check['attributes']['type']    = 'checkbox';
+		$check['attributes']['name']    = $name;
+		$check['attributes']['id']      = $name;
+		$check['attributes']['value']   = '--';
+		$check['attributes']['class'][] = 'disable-toggle';
+		$check['attributes']['title']   = __( 'Disable gravity and crops', 'cloudinary' );
+		if ( '--' === $value ) {
+			$check['attributes']['checked'] = 'checked';
+		}
+
+		$input                          = $this->get_part( 'input' );
+		$input['attributes']['type']    = 'text';
+		$input['attributes']['name']    = $name;
+		$input['attributes']['value']   = '--' !== $value ? $value : '';
+		$input['attributes']['class'][] = 'regular-text';
+
+		$wrapper['children']['input'] = $input;
+		$wrapper['children']['label'] = $label;
+		$wrapper['children']['check'] = $check;
+
+		return $wrapper;
+	}
+
+	/**
+	 * Sanitize the value.
+	 *
+	 * @param array $value The value to sanitize.
+	 *
+	 * @return array
+	 */
+	public function sanitize_value( $value ) {
+		return $value;
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/scripts/linenumber.js b/docs/scripts/linenumber.js new file mode 100644 index 000000000..8d52f7eaf --- /dev/null +++ b/docs/scripts/linenumber.js @@ -0,0 +1,25 @@ +/*global document */ +(function() { + var source = document.getElementsByClassName('prettyprint source linenums'); + var i = 0; + var lineNumber = 0; + var lineId; + var lines; + var totalLines; + var anchorHash; + + if (source && source[0]) { + anchorHash = document.location.hash.substring(1); + lines = source[0].getElementsByTagName('li'); + totalLines = lines.length; + + for (; i < totalLines; i++) { + lineNumber++; + lineId = 'line' + lineNumber; + lines[i].id = lineId; + if (lineId === anchorHash) { + lines[i].className += ' selected'; + } + } + } +})(); diff --git a/docs/scripts/prettify/Apache-License-2.0.txt b/docs/scripts/prettify/Apache-License-2.0.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/docs/scripts/prettify/Apache-License-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/docs/scripts/prettify/lang-css.js b/docs/scripts/prettify/lang-css.js new file mode 100644 index 000000000..041e1f590 --- /dev/null +++ b/docs/scripts/prettify/lang-css.js @@ -0,0 +1,2 @@ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", +/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); diff --git a/docs/scripts/prettify/prettify.js b/docs/scripts/prettify/prettify.js new file mode 100644 index 000000000..eef5ad7e6 --- /dev/null +++ b/docs/scripts/prettify/prettify.js @@ -0,0 +1,28 @@ +var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; +(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= +[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), +l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, +q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, +q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, +"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), +a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} +for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], +"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], +H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], +J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ +I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), +["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", +/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), +["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", +hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= +!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p th:last-child { border-right: 1px solid #ddd; } + +.ancestors { color: #999; } +.ancestors a +{ + color: #999 !important; + text-decoration: none; +} + +.clear +{ + clear: both; +} + +.important +{ + font-weight: bold; + color: #950B02; +} + +.yes-def { + text-indent: -1000px; +} + +.type-signature { + color: #aaa; +} + +.name, .signature { + font-family: Consolas, Monaco, 'Andale Mono', monospace; +} + +.details { margin-top: 14px; border-left: 2px solid #DDD; } +.details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; } +.details dd { margin-left: 70px; } +.details ul { margin: 0; } +.details ul { list-style-type: none; } +.details li { margin-left: 30px; padding-top: 6px; } +.details pre.prettyprint { margin: 0 } +.details .object-value { padding-top: 0; } + +.description { + margin-bottom: 1em; + margin-top: 1em; +} + +.code-caption +{ + font-style: italic; + font-size: 107%; + margin: 0; +} + +.prettyprint +{ + border: 1px solid #ddd; + width: 80%; + overflow: auto; +} + +.prettyprint.source { + width: inherit; +} + +.prettyprint code +{ + font-size: 100%; + line-height: 18px; + display: block; + padding: 4px 12px; + margin: 0; + background-color: #fff; + color: #4D4E53; +} + +.prettyprint code span.line +{ + display: inline-block; +} + +.prettyprint.linenums +{ + padding-left: 70px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.prettyprint.linenums ol +{ + padding-left: 0; +} + +.prettyprint.linenums li +{ + border-left: 3px #ddd solid; +} + +.prettyprint.linenums li.selected, +.prettyprint.linenums li.selected * +{ + background-color: lightyellow; +} + +.prettyprint.linenums li * +{ + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.params .name, .props .name, .name code { + color: #4D4E53; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + font-size: 100%; +} + +.params td.description > p:first-child, +.props td.description > p:first-child +{ + margin-top: 0; + padding-top: 0; +} + +.params td.description > p:last-child, +.props td.description > p:last-child +{ + margin-bottom: 0; + padding-bottom: 0; +} + +.disabled { + color: #454545; +} + +.quick-reference { + background: #f9f9f9; + padding: 15px 25px; + border-left: 3px solid #4d4e53; + list-style: none; + line-height: 1.7; +} + +h3.subsection-title { + background: #4d4e53; + color: white; + padding: 5px 10px; +} \ No newline at end of file diff --git a/docs/styles/prettify-jsdoc.css b/docs/styles/prettify-jsdoc.css new file mode 100644 index 000000000..5a2526e37 --- /dev/null +++ b/docs/styles/prettify-jsdoc.css @@ -0,0 +1,111 @@ +/* JSDoc prettify.js theme */ + +/* plain text */ +.pln { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* string content */ +.str { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a keyword */ +.kwd { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a comment */ +.com { + font-weight: normal; + font-style: italic; +} + +/* a type name */ +.typ { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* a literal value */ +.lit { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* punctuation */ +.pun { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* lisp open bracket */ +.opn { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* lisp close bracket */ +.clo { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a markup tag name */ +.tag { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a markup attribute name */ +.atn { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a markup attribute value */ +.atv { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a declaration */ +.dec { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a variable name */ +.var { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* a function name */ +.fun { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* Specify class=linenums on a pre to get line numbering */ +ol.linenums { + margin-top: 0; + margin-bottom: 0; +} diff --git a/docs/styles/prettify-tomorrow.css b/docs/styles/prettify-tomorrow.css new file mode 100644 index 000000000..b6f92a78d --- /dev/null +++ b/docs/styles/prettify-tomorrow.css @@ -0,0 +1,132 @@ +/* Tomorrow Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* Pretty printing styles. Used with prettify.js. */ +/* SPAN elements with the classes below are added by prettyprint. */ +/* plain text */ +.pln { + color: #4d4d4c; } + +@media screen { + /* string content */ + .str { + color: #718c00; } + + /* a keyword */ + .kwd { + color: #8959a8; } + + /* a comment */ + .com { + color: #8e908c; } + + /* a type name */ + .typ { + color: #4271ae; } + + /* a literal value */ + .lit { + color: #f5871f; } + + /* punctuation */ + .pun { + color: #4d4d4c; } + + /* lisp open bracket */ + .opn { + color: #4d4d4c; } + + /* lisp close bracket */ + .clo { + color: #4d4d4c; } + + /* a markup tag name */ + .tag { + color: #c82829; } + + /* a markup attribute name */ + .atn { + color: #f5871f; } + + /* a markup attribute value */ + .atv { + color: #3e999f; } + + /* a declaration */ + .dec { + color: #f5871f; } + + /* a variable name */ + .var { + color: #c82829; } + + /* a function name */ + .fun { + color: #4271ae; } } +/* Use higher contrast and text-weight for printable form. */ +@media print, projection { + .str { + color: #060; } + + .kwd { + color: #006; + font-weight: bold; } + + .com { + color: #600; + font-style: italic; } + + .typ { + color: #404; + font-weight: bold; } + + .lit { + color: #044; } + + .pun, .opn, .clo { + color: #440; } + + .tag { + color: #006; + font-weight: bold; } + + .atn { + color: #404; } + + .atv { + color: #060; } } +/* Style */ +/* +pre.prettyprint { + background: white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + font-size: 12px; + line-height: 1.5; + border: 1px solid #ccc; + padding: 10px; } +*/ + +/* Specify class=linenums on a pre to get line numbering */ +ol.linenums { + margin-top: 0; + margin-bottom: 0; } + +/* IE indents via margin-left */ +li.L0, +li.L1, +li.L2, +li.L3, +li.L4, +li.L5, +li.L6, +li.L7, +li.L8, +li.L9 { + /* */ } + +/* Alternate shading for lines */ +li.L1, +li.L3, +li.L5, +li.L7, +li.L9 { + /* */ } diff --git a/docs/ui-definitions_settings-image.php.html b/docs/ui-definitions_settings-image.php.html new file mode 100644 index 000000000..585604e11 --- /dev/null +++ b/docs/ui-definitions_settings-image.php.html @@ -0,0 +1,339 @@ + + + + + Source: ui-definitions/settings-image.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: ui-definitions/settings-image.php

+ + + + + + + +
+
+
<?php
+/**
+ * Defines the settings structure for images.
+ *
+ * @package Cloudinary
+ */
+
+use Cloudinary\Utils;
+use function Cloudinary\get_plugin_instance;
+
+$transformations_title = Utils::get_transformations_title( esc_html__( 'Image', 'cloudinary' ) );
+
+$settings = array(
+	array(
+		'type'        => 'panel',
+		'title'       => __( 'Image - Global Settings', 'cloudinary' ),
+		'anchor'      => true,
+		'option_name' => 'media_display',
+		array(
+			'type' => 'tabs',
+			'tabs' => array(
+				'image_setting' => array(
+					'text' => __( 'Settings', 'cloudinary' ),
+					'id'   => 'settings',
+				),
+				'image_preview' => array(
+					'text' => __( 'Preview', 'cloudinary' ),
+					'id'   => 'preview',
+				),
+			),
+		),
+		array(
+			'type' => 'row',
+			array(
+				'type'   => 'column',
+				'tab_id' => 'settings',
+				'class'  => array(
+					'column-min-w-50',
+				),
+				array(
+					'type'               => 'on_off',
+					'slug'               => 'image_delivery',
+					'title'              => __( 'Image delivery', 'cloudinary' ),
+					'optimisation_title' => __( 'Image delivery', 'cloudinary' ),
+					'tooltip_text'       => __(
+						'If you turn this setting off, your images will be delivered from WordPress.',
+						'cloudinary'
+					),
+					'description'        => __( 'Sync and deliver images from Cloudinary.', 'cloudinary' ),
+					'default'            => 'on',
+					'attributes'         => array(
+						'data-context' => 'image',
+					),
+					'readonly'           => static function () {
+						$plugin = get_plugin_instance();
+						return 'on' === $plugin->settings->get_value( 'image_delivery' ) && ! $plugin->get_component( 'storage' )->is_local_full();
+					},
+					'readonly_message'   => sprintf(
+						// translators: %s is a link to the storage settings page.
+						__( 'This setting currently can’t be turned off. Your images must be delivered from Cloudinary because your assets are being stored in Cloudinary only. To enable delivering images from WordPress, first select a %s in the General Settings page that will enable storing your assets also in WordPress.', 'cloudinary' ),
+						sprintf(
+							'<a href="%s">%s</a>',
+							add_query_arg( array( 'page' => 'cloudinary_connect#connect.offload' ), admin_url( 'admin.php' ) ),
+							__( 'Storage setting', 'cloudinary' )
+						)
+					),
+				),
+				array(
+					'type'      => 'group',
+					'condition' => array(
+						'image_delivery' => true,
+					),
+					array(
+						'type'    => 'tag',
+						'element' => 'hr',
+					),
+					array(
+						'type'               => 'on_off',
+						'slug'               => 'image_optimization',
+						'title'              => __( 'Image optimization', 'cloudinary' ),
+						'optimisation_title' => __( 'Image optimization', 'cloudinary' ),
+						'tooltip_text'       => __(
+							'Images will be delivered using Cloudinary’s automatic format and quality algorithms for the best tradeoff between visual quality and file size. Use Advanced Optimization options to manually tune format and quality.',
+							'cloudinary'
+						),
+						'description'        => __( 'Optimize images on my site.', 'cloudinary' ),
+						'default'            => 'on',
+						'attributes'         => array(
+							'data-context' => 'image',
+						),
+						'depends'            => array(
+							'image_delivery',
+						),
+					),
+					array(
+						'type'      => 'group',
+						'condition' => array(
+							'image_optimization' => true,
+						),
+						array(
+							'type'         => 'select',
+							'slug'         => 'image_format',
+							'title'        => __( 'Image format', 'cloudinary' ),
+							'tooltip_text' => __(
+								"The image format to use for delivery. Leave as Auto to automatically deliver the most optimal format based on the user's browser and device.",
+								'cloudinary'
+							),
+							'default'      => 'auto',
+							'options'      => array(
+								'none' => __( 'Not set', 'cloudinary' ),
+								'auto' => __( 'Auto', 'cloudinary' ),
+								'png'  => __( 'PNG', 'cloudinary' ),
+								'jpg'  => __( 'JPG', 'cloudinary' ),
+								'gif'  => __( 'GIF', 'cloudinary' ),
+								'webp' => __( 'WebP', 'cloudinary' ),
+							),
+							'suffix'       => 'f_@value',
+							'attributes'   => array(
+								'data-context' => 'image',
+								'data-meta'    => 'f',
+							),
+						),
+						array(
+							'type'         => 'select',
+							'slug'         => 'image_quality',
+							'title'        => __( 'Image quality', 'cloudinary' ),
+							'tooltip_text' => __(
+								'The compression quality to apply when delivering images. Leave as Auto to apply an algorithm that finds the best tradeoff between visual quality and file size.',
+								'cloudinary'
+							),
+							'default'      => 'auto',
+							'suffix'       => 'q_@value',
+							'options'      => array(
+								'none'      => __( 'Not set', 'cloudinary' ),
+								'auto'      => __( 'Auto', 'cloudinary' ),
+								'auto:best' => __( 'Auto best', 'cloudinary' ),
+								'auto:good' => __( 'Auto good', 'cloudinary' ),
+								'auto:eco'  => __( 'Auto eco', 'cloudinary' ),
+								'auto:low'  => __( 'Auto low', 'cloudinary' ),
+								'100'       => '100',
+								'80'        => '80',
+								'60'        => '60',
+								'40'        => '40',
+								'20'        => '20',
+							),
+							'attributes'   => array(
+								'data-context' => 'image',
+								'data-meta'    => 'q',
+							),
+						),
+					),
+					array(
+						'type'    => 'tag',
+						'element' => 'hr',
+					),
+					array(
+						'type'           => 'text',
+						'slug'           => 'image_freeform',
+						'title'          => $transformations_title,
+						'default'        => '',
+						'anchor'         => true,
+						'tooltip_text'   => sprintf(
+							// translators: The link to transformation reference.
+							__(
+								'A set of additional transformations to apply to all images. Specify your transformations using Cloudinary URL transformation syntax. See %1$sreference%2$s for all available transformations and syntax.%3$s* The Cloudinary global transformations are only applied to assets managed in the Media Library%4$s.',
+								'cloudinary'
+							),
+							'<a href="https://cloudinary.com/documentation/transformation_reference" target="_blank" rel="noopener noreferrer">',
+							'</a>',
+							'<br><br><em>',
+							'</em>'
+						),
+						'link'           => array(
+							'text' => __( 'See examples', 'cloudinary' ),
+							'href' => 'https://cloudinary.com/documentation/image_transformations',
+						),
+						'attributes'     => array(
+							'data-context' => 'image',
+							'placeholder'  => 'w_90,r_max',
+						),
+						'taxonomy_field' => array(
+							'context'  => 'image',
+							'priority' => 10,
+						),
+					),
+					array(
+						'type'  => 'info_box',
+						'icon'  => $this->dir_url . 'css/images/transformation.svg',
+						'title' => __( 'What are transformations?', 'cloudinary' ),
+						'text'  => __(
+							'A set of parameters included in a Cloudinary URL to programmatically transform the visual appearance of the assets on your website.',
+							'cloudinary'
+						),
+					),
+					array(
+						'type'    => 'tag',
+						'element' => 'hr',
+					),
+					array(
+						'type'               => 'on_off',
+						'slug'               => 'svg_support',
+						'title'              => __( 'SVG Support', 'cloudinary' ),
+						'optimisation_title' => __( 'SVG Support', 'cloudinary' ),
+						'tooltip_text'       => __(
+							'Enable Cloudinary\'s SVG Support.',
+							'cloudinary'
+						),
+						'description'        => __( 'Enable SVG support.', 'cloudinary' ),
+						'default'            => 'off',
+					),
+					array(
+						'type'    => 'crops',
+						'slug'    => 'crop_sizes',
+						'title'   => __( 'Crop and Gravity control (beta)', 'cloudinary' ),
+						'enabled' => static function () {
+							/**
+							 * Enable the Crop and Gravity control settings.
+							 *
+							 * @hook  cloudinary_enable_crop_and_gravity_control
+							 * @since 3.1.3
+							 * @default {false}
+							 *
+							 * @param $enabeld {bool} Is the Crop and Gravity control enabled?
+							 *
+							 * @retrun {bool}
+							 */
+							return apply_filters( 'cloudinary_enable_crop_and_gravity_control', false );
+						},
+					),
+				),
+			),
+			array(
+				'type'      => 'column',
+				'tab_id'    => 'preview',
+				'class'     => array(
+					'cld-ui-preview',
+					'column-min-w-50',
+				),
+				'condition' => array(
+					'image_delivery' => true,
+				),
+				array(
+					'type'           => 'image_preview',
+					'title'          => __( 'Preview', 'cloudinary' ),
+					'slug'           => 'image_preview',
+					'default'        => CLOUDINARY_ENDPOINTS_PREVIEW_IMAGE . 'w_600/sample.jpg',
+					'taxonomy_field' => array(
+						'context'  => 'image',
+						'priority' => 10,
+					),
+				),
+			),
+		),
+		array(
+			'type'  => 'info_box',
+			'icon'  => $this->dir_url . 'css/images/academy-icon.svg',
+			'title' => __( 'Need help?', 'cloudinary' ),
+			'text'  => sprintf(
+				// Translators: The HTML for opening and closing link tags.
+				__(
+					'Watch free lessons on how to use the Image Global Settings in the %1$sCloudinary Academy%2$s.',
+					'cloudinary'
+				),
+				'<a href="https://training.cloudinary.com/learn/course/introduction-to-cloudinary-for-wordpress-administrators-70-minute-course-1h85/lessons/transforming-images-and-videos-for-pages-and-posts-1545?page=1" target="_blank" rel="noopener noreferrer">',
+				'</a>'
+			),
+		),
+	),
+);
+
+/**
+ * Filter the Cloudinary global transformations tab for images.
+ *
+ * @hook cloudinary_admin_image_settings
+ *
+ * @param $settings {array} The global transformations settings.
+ *
+ * @return {array}
+ */
+return apply_filters( 'cloudinary_admin_image_settings', $settings );
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/ui-definitions_settings-metaboxes.php.html b/docs/ui-definitions_settings-metaboxes.php.html new file mode 100644 index 000000000..31d4313a3 --- /dev/null +++ b/docs/ui-definitions_settings-metaboxes.php.html @@ -0,0 +1,123 @@ + + + + + Source: ui-definitions/settings-metaboxes.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: ui-definitions/settings-metaboxes.php

+ + + + + + + +
+
+
<?php
+/**
+ * Defines the settings structure for metaboxes.
+ *
+ * @package Cloudinary
+ */
+
+/**
+ * Enable the Crop and Gravity control settings.
+ *
+ * @hook  cloudinary_enable_crop_and_gravity_control
+ * @since 3.1.3
+ * @default {false}
+ *
+ * @param $enabeld {bool} Is the Crop and Gravity control enabled?
+ *
+ * @retrun {bool}
+ */
+if ( ! apply_filters( 'cloudinary_enable_crop_and_gravity_control', false ) ) {
+	return array();
+}
+$metaboxes = array(
+	'crop_meta' => array(
+		'title'    => __( 'Cloudinary Crop and Gravity control', 'cloudinary' ),
+		'screen'   => 'attachment',
+		'settings' => array(
+			array(
+				'slug' => 'single_crop_and_gravity',
+				'type' => 'stand_alone',
+				array(
+					'type'         => 'on_off',
+					'slug'         => 'enable_crop_and_gravity',
+					'title'        => __( 'Crop and Gravity control (beta)', 'cloudinary' ),
+					'tooltip_text' => __(
+						'Enable Crop and Gravity control for registered image sizes.',
+						'cloudinary'
+					),
+					'description'  => __( 'Enable Crop and Gravity', 'cloudinary' ),
+					'default'      => 'off',
+				),
+				array(
+					'type'      => 'crops',
+					'slug'      => 'single_sizes',
+					'mode'      => 'full',
+					'condition' => array(
+						'enable_crop_and_gravity' => true,
+					),
+				),
+			),
+		),
+	),
+);
+
+/**
+ * Filter the meta boxes.
+ *
+ * @hook   cloudinary_meta_boxes
+ * @since  3.1.3
+ *
+ * @param $metaboxes {array}  Array of meta boxes to create.
+ *
+ * @return {array}
+ */
+return apply_filters( 'cloudinary_meta_boxes', $metaboxes );
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/ui-definitions_settings-pages.php.html b/docs/ui-definitions_settings-pages.php.html new file mode 100644 index 000000000..dee1e63ec --- /dev/null +++ b/docs/ui-definitions_settings-pages.php.html @@ -0,0 +1,631 @@ + + + + + Source: ui-definitions/settings-pages.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: ui-definitions/settings-pages.php

+ + + + + + + +
+
+
<?php
+/**
+ * Defines the settings structure for the main pages.
+ *
+ * @package Cloudinary
+ */
+
+use Cloudinary\Utils;
+use Cloudinary\Report;
+use function Cloudinary\get_plugin_instance;
+
+$media    = $this->get_component( 'media' );
+$settings = array(
+	'dashboard'      => array(
+		'page_title'          => __( 'Cloudinary Dashboard', 'cloudinary' ),
+		'menu_title'          => __( 'Dashboard', 'cloudinary' ),
+		'priority'            => 1,
+		'requires_connection' => true,
+		'sidebar'             => true,
+		array(
+			'type' => 'panel',
+			array(
+				'type'  => 'plan',
+				'title' => __( 'Your Current Plan', 'cloudinary' ),
+			),
+			array(
+				'type'    => 'link',
+				'url'     => 'https://cloudinary.com/console/lui/upgrade_options',
+				'content' => __( 'Upgrade Plan', 'cloudinary' ),
+			),
+		),
+		array(
+			'type' => 'panel',
+			array(
+				'type'  => 'plan_status',
+				'title' => __( 'Your Plan Status', 'cloudinary' ),
+			),
+		),
+		array(
+			'type' => 'panel_short',
+			array(
+				'type'  => 'media_status',
+				'title' => __( 'Your Media Sync Status', 'cloudinary' ),
+			),
+		),
+	),
+	'connect'        => array(
+		'page_title'         => __( 'General settings', 'cloudinary' ),
+		'menu_title'         => __( 'General settings', 'cloudinary' ),
+		'disconnected_title' => __( 'Setup', 'cloudinary' ),
+		'priority'           => 5,
+		'sidebar'            => true,
+		'settings'           => array(
+			array(
+				'title'       => __( 'Account Status', 'cloudinary' ),
+				'type'        => 'panel',
+				'collapsible' => 'open',
+				array(
+					'slug' => \Cloudinary\Connect::META_KEYS['url'],
+					'type' => 'connect',
+				),
+			),
+			array(
+				'type' => 'switch_cloud',
+			),
+		),
+	),
+	'image_settings' => array(
+		'page_title'          => __( 'Image settings', 'cloudinary' ),
+		'menu_title'          => __( 'Image settings', 'cloudinary' ),
+		'priority'            => 5,
+		'requires_connection' => true,
+		'sidebar'             => true,
+		'settings'            => include $this->dir_path . 'ui-definitions/settings-image.php', // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+	),
+	'video_settings' => array(
+		'page_title'          => __( 'Video settings', 'cloudinary' ),
+		'menu_title'          => __( 'Video settings', 'cloudinary' ),
+		'priority'            => 5,
+		'requires_connection' => true,
+		'sidebar'             => true,
+		'settings'            => include $this->dir_path . 'ui-definitions/settings-video.php', // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+	),
+	'lazy_loading'   => array(),
+	'responsive'     => array(
+		'page_title'          => __( 'Responsive images', 'cloudinary' ),
+		'menu_title'          => __( 'Responsive images', 'cloudinary' ),
+		'priority'            => 5,
+		'requires_connection' => true,
+		'sidebar'             => true,
+		'settings'            => array(
+			array(
+				'type'        => 'panel',
+				'title'       => __( 'Responsive images', 'cloudinary' ),
+				'option_name' => 'media_display',
+				array(
+					'type' => 'tabs',
+					'tabs' => array(
+						'image_setting' => array(
+							'text' => __( 'Settings', 'cloudinary' ),
+							'id'   => 'settings',
+						),
+						'image_preview' => array(
+							'text' => __( 'Preview', 'cloudinary' ),
+							'id'   => 'preview',
+						),
+					),
+				),
+				array(
+					'type' => 'row',
+					array(
+						'type'   => 'column',
+						'tab_id' => 'settings',
+						array(
+							'type'               => 'on_off',
+							'slug'               => 'enable_breakpoints',
+							'title'              => __( 'Breakpoints', 'cloudinary' ),
+							'optimisation_title' => __( 'Responsive images', 'cloudinary' ),
+							'tooltip_text'       => __(
+								'Automatically generate multiple sizes based on the configured breakpoints to enable your images to responsively adjust to different screen sizes. Note that your Cloudinary usage will increase when enabling responsive images.',
+								'cloudinary'
+							),
+							'description'        => __( 'Enable responsive images', 'cloudinary' ),
+							'default'            => 'off',
+						),
+						array(
+							'type'      => 'group',
+							'condition' => array(
+								'enable_breakpoints' => true,
+							),
+							array(
+								'type'         => 'number',
+								'slug'         => 'pixel_step',
+								'priority'     => 9,
+								'title'        => __( 'Breakpoint distance', 'cloudinary' ),
+								'tooltip_text' => __( 'The distance between each generated image. Adjusting this will adjust the number of images generated.', 'cloudinary' ),
+								'suffix'       => __( 'px', 'cloudinary' ),
+								'attributes'   => array(
+									'step' => 50,
+									'min'  => 50,
+								),
+								'default'      => 200,
+							),
+							array(
+								'type'         => 'number',
+								'slug'         => 'breakpoints',
+								'title'        => __( 'Max images', 'cloudinary' ),
+								'tooltip_text' => __(
+									'The maximum number of images to be generated. Note that generating large numbers of images will deliver a more optimal version for a wider range of screen sizes but will result in an increase in your usage.  For smaller images, the responsive algorithm may determine that the ideal number is less than the value you specify.',
+									'cloudinary'
+								),
+								'suffix'       => __( 'Recommended value: 3-40', 'cloudinary' ),
+								'attributes'   => array(
+									'min' => 3,
+									'max' => 100,
+								),
+							),
+							array(
+								'type'        => 'number',
+								'slug'        => 'max_width',
+								'title'       => __( 'Image width limit', 'cloudinary' ),
+								'extra_title' => __(
+									'The minimum and maximum width of an image created as a breakpoint. Leave “max” as empty to automatically detect based on the largest registered size in WordPress.',
+									'cloudinary'
+								),
+								'prefix'      => __( 'Max', 'cloudinary' ),
+								'suffix'      => __( 'px', 'cloudinary' ),
+								'default'     => $media->default_max_width(),
+								'attributes'  => array(
+									'step'         => 50,
+									'data-default' => $media->default_max_width(),
+								),
+							),
+							array(
+								'type'       => 'number',
+								'slug'       => 'min_width',
+								'prefix'     => __( 'Min', 'cloudinary' ),
+								'suffix'     => __( 'px', 'cloudinary' ),
+								'default'    => 200,
+								'attributes' => array(
+									'step' => 50,
+								),
+							),
+						),
+					),
+					array(
+						'type'      => 'column',
+						'tab_id'    => 'preview',
+						'class'     => array(
+							'cld-ui-preview',
+						),
+						'condition' => array(
+							'enable_breakpoints' => true,
+						),
+						array(
+							'type'    => 'breakpoints_preview',
+							'title'   => __( 'Preview', 'cloudinary' ),
+							'slug'    => 'breakpoints_preview',
+							'default' => CLOUDINARY_ENDPOINTS_PREVIEW_IMAGE . 'w_600/sample.jpg',
+						),
+					),
+				),
+				array(
+					'type'  => 'info_box',
+					'icon'  => $this->dir_url . 'css/images/academy-icon.svg',
+					'title' => __( 'Need help?', 'cloudinary' ),
+					'text'  => sprintf(
+						// Translators: The HTML for opening and closing link tags.
+						__(
+							'Watch free lessons on how to use the Responsive Images Settings in the %1$sCloudinary Academy%2$s.',
+							'cloudinary'
+						),
+						'<a href="https://training.cloudinary.com/learn/course/introduction-to-cloudinary-for-wordpress-administrators-70-minute-course-1h85/lessons/lazily-loading-and-delivering-responsive-images-1003?page=1" target="_blank" rel="noopener noreferrer">',
+						'</a>'
+					),
+				),
+			),
+		),
+	),
+	'gallery'        => array(),
+	'help'           => array(
+		'page_title' => __( 'Need help?', 'cloudinary' ),
+		'menu_title' => __( 'Need help?', 'cloudinary' ),
+		'priority'   => 50,
+		'sidebar'    => true,
+		array(
+			'type'  => 'panel',
+			'title' => __( 'Help Center', 'cloudinary' ),
+			array(
+				'type'    => 'tag',
+				'element' => 'h4',
+				'content' => __( 'How can we help', 'cloudinary' ),
+			),
+			array(
+				'type'    => 'span',
+				'content' => 'This help center is divided into segments, to make sure you will get the right answer and information as fast as possible. Know that we are here for you!',
+			),
+			array(
+				'type'       => 'row',
+				'attributes' => array(
+					'wrap' => array(
+						'class' => array(
+							'help-wrap',
+						),
+					),
+				),
+				array(
+					'type'       => 'column',
+					'attributes' => array(
+						'wrap' => array(
+							'class' => array(
+								'help-box',
+							),
+						),
+					),
+					array(
+						'type'       => 'tag',
+						'element'    => 'a',
+						'attributes' => array(
+							'href'   => 'https://cloudinary.com/documentation/wordpress_integration',
+							'target' => '_blank',
+							'rel'    => 'noopener noreferrer',
+							'class'  => array(
+								'large-button',
+							),
+						),
+						array(
+							'type'       => 'tag',
+							'element'    => 'img',
+							'attributes' => array(
+								'src' => $this->dir_url . 'css/images/documentation.jpg',
+							),
+						),
+						array(
+							'type'    => 'tag',
+							'element' => 'h4',
+							'content' => __( 'Documentation', 'cloudinary' ),
+						),
+						array(
+							'type'    => 'span',
+							'content' => __( 'Learn more about how to use the Cloudinary plugin and get the most out of the functionality.', 'cloudinary' ),
+						),
+					),
+				),
+				array(
+					'type'                => 'column',
+					'attributes'          => array(
+						'wrap' => array(
+							'class' => array(
+								'help-box',
+							),
+						),
+					),
+					array(
+						'type'       => 'tag',
+						'element'    => 'a',
+						'attributes' => array(
+							'href'   => 'https://training.cloudinary.com/courses/introduction-to-cloudinary-for-wordpress-administrators-70-minute-course-zf3x',
+							'target' => '_blank',
+							'rel'    => 'noopener noreferrer',
+							'class'  => array(
+								'large-button',
+							),
+						),
+						array(
+							'type'       => 'tag',
+							'element'    => 'img',
+							'attributes' => array(
+								'src' => $this->dir_url . 'css/images/academy.jpg',
+							),
+						),
+						array(
+							'type'    => 'tag',
+							'element' => 'h4',
+							'content' => __( 'Cloudinary Academy', 'cloudinary' ),
+						),
+						array(
+							'type'    => 'a',
+							'content' => __( "With Cloudinary's plugin, it is easy to enhance your WordPress site's images and videos! In this self-paced course, our expert instructor will show you how to configure the plugin and use its most powerful features - asset management, image and video optimization, product gallery creation and more.", 'cloudinary' ),
+						),
+					),
+				),
+				array(
+					'type'       => 'column',
+					'attributes' => array(
+						'wrap' => array(
+							'class' => array(
+								'help-box',
+							),
+						),
+					),
+					array(
+						'type'       => 'tag',
+						'element'    => 'a',
+						'attributes' => array(
+							'href'   => static function () {
+								$args = array(
+									'tf_360017815680' => '-',
+								);
+								return Utils::get_support_link( $args );
+							},
+							'target' => '_blank',
+							'rel'    => 'noopener noreferrer',
+							'class'  => array(
+								'large-button',
+							),
+						),
+						array(
+							'type'       => 'tag',
+							'element'    => 'img',
+							'attributes' => array(
+								'src' => $this->dir_url . 'css/images/request.jpg',
+							),
+						),
+						array(
+							'type'    => 'tag',
+							'element' => 'h4',
+							'content' => __( 'Submit a request', 'cloudinary' ),
+						),
+						array(
+							'type'    => 'span',
+							'content' => __( 'If you’re encountering an issue or struggling to get the plugin to work, open a ticket to contact our support team. To help us debug your queries, we recommend generating a system report.', 'cloudinary' ),
+						),
+					),
+				),
+				array(
+					'type'                => 'column',
+					'requires_connection' => true,
+					'attributes'          => array(
+						'wrap' => array(
+							'class' => array(
+								'help-box',
+							),
+						),
+					),
+					array(
+						'type'       => 'tag',
+						'element'    => 'a',
+						'attributes' => array(
+							'href'  => add_query_arg( 'section', Report::REPORT_SLUG ),
+							'class' => array(
+								'large-button',
+							),
+						),
+						array(
+							'type'       => 'tag',
+							'element'    => 'img',
+							'attributes' => array(
+								'src' => $this->dir_url . 'css/images/report.jpg',
+							),
+						),
+						array(
+							'type'    => 'tag',
+							'element' => 'h4',
+							'content' => __( 'System Report', 'cloudinary' ),
+						),
+						array(
+							'type'    => 'a',
+							'content' => __( "Generate a system report to help debug any specific issues you're having with your Cloudinary media, our support team will usually ask for this when submitting a support request.", 'cloudinary' ),
+						),
+					),
+				),
+			),
+		),
+		array(
+			'type'  => 'panel',
+			'title' => __( 'FAQ', 'cloudinary' ),
+			array(
+				array(
+					'type'        => 'panel',
+					'title'       => __( 'Do I need a Cloudinary account to use the Cloudinary plugin and can I try it out for free?', 'cloudinary' ),
+					'enabled'     => static function () {
+						return ! get_plugin_instance()->get_component( 'connect' )->is_connected();
+					},
+					'collapsible' => 'closed',
+					'content'     => sprintf(
+						// translators: The HTML markup.
+						__( 'To use the Cloudinary plugin and all the functionality that comes with it, you will need to have a Cloudinary Account. %1$sIf you don’t have an account yet, %2$ssign up%3$s now for a free Cloudinary Programmable Media account%4$s. You’ll start with generous usage limits and when your requirements grow, you can easily upgrade to a plan that best fits your needs.', 'cloudinary' ),
+						'<b>',
+						'<a href="https://cloudinary.com/signup?source=wp&utm_source=wp&utm_medium=wporgmarketplace&utm_campaign=wporgmarketplace" target="_blank" rel="noopener noreferrer">',
+						'</a>',
+						'</b>'
+					),
+				),
+				array(
+					'type'        => 'panel',
+					'title'       => __( 'I’ve installed the Cloudinary plugin, what happens now?', 'cloudinary' ),
+					'collapsible' => 'closed',
+					'content'     => __( 'If you left all the settings as default, all your current media will begin syncing with Cloudinary. Once syncing is complete, your media will be optimized and delivered using Cloudinary URLs and you should begin seeing improvements in performance across your site.', 'cloudinary' ),
+				),
+				array(
+					'type'        => 'panel',
+					'title'       => __( 'Which file types are supported?', 'cloudinary' ),
+					'collapsible' => 'closed',
+					'content'     => sprintf(
+						// translators: The HTML markup.
+						__( 'Most common media files are supported for optimization and delivery by Cloudinary. For free accounts, you will not be able to deliver PDF or ZIP files by default for security reasons. If this is a requirement, please contact our support team who can help activate this for you.%1$sTo deliver additional file types via Cloudinary, you can extend the functionality of the plugin using the %2$sactions and filters%3$s the plugin exposes for developers.', 'cloudinary' ),
+						'<br><br>',
+						'<a href="https://cloudinary.com/documentation/wordpress_integration#actions_and_filters" target="_blank" rel="noopener noreferrer">',
+						'</a>'
+					),
+				),
+				array(
+					'type'        => 'panel',
+					'title'       => __( 'Does the Cloudinary plugin require an active WordPress REST API connection?', 'cloudinary' ),
+					'collapsible' => 'closed',
+					'content'     => sprintf(
+						// translators: The HTML markup.
+						__( 'To function correctly, the Cloudinary plugin requires an active WordPress REST API connection. Ensure your WordPress setup, including multisite or headless configurations, has the REST API enabled and active for seamless plugin operation.%1$sFor more information, see %2$sWordPress’s REST API Handbook%3$s.%1$sTo manually verify your REST API connection, you can temporarily enable the %5$sCloudinary Cron%6$s feature from %4$sthe plugin’s Cron admin page%3$s. Once enabled, activate the %8$s task. When the cron runs for the first time, any connectivity issues will trigger an admin notice.%1$s%7$s%5$s Important%6$s: Enabling the Cron feature starts a recurring background process. Unless you’re actively using it for diagnostics or testing, we %5$sstrongly recommend disabling%6$s this feature to avoid generating unnecessary requests or load on your site.', 'cloudinary' ),
+						'<br><br>',
+						'<a href="https://developer.wordpress.org/rest-api/" target="_blank" rel="noopener noreferrer">',
+						'</a>',
+						'<a href="' . add_query_arg( array( 'page' => 'cloudinary&section=cron_system' ), admin_url( 'admin.php' ) ) . '">',
+						'<strong>',
+						'</strong>',
+						'<span style="color: #b0b000; font-size: 18px">&#9888;</span>',
+						'<span style="background: #e8e9e8; padding-inline: 2px;">rest_api</span>'
+					),
+				),
+				array(
+					'type'        => 'panel',
+					'title'       => __( 'Does Cloudinary work on multilingual sites?', 'cloudinary' ),
+					'collapsible' => 'closed',
+					'content'     => sprintf(
+						// translators: The HTML markup.
+						__( 'Absolutely! Cloudinary is fully compatible with WPML, the leading WordPress multilingual plugin, and is officially recommended for seamless media management across multiple languages. Cloudinary ensures that your media assets are efficiently handled on your multilingual site. For more details, visit %1$sWPML\'s official page%2$s on Cloudinary compatibility.', 'cloudinary' ),
+						'<a href="https://wpml.org/plugin/cloudinary/" target="_blank" rel="noopener noreferrer">',
+						'</a>'
+					),
+				),
+				array(
+					'type'        => 'panel',
+					'title'       => __( "I'm having an incompatibility issue with a theme, plugin, or hosting environment, what can I do?", 'cloudinary' ),
+					'collapsible' => 'closed',
+					'content'     => static function () {
+						return sprintf(
+							// translators: The HTML markup.
+							__( 'We’re compatible with most other plugins so we expect it to work absolutely fine. If you do have any issues, please %1$scontact our support team%2$s who will help resolve your issue.', 'cloudinary' ),
+							'<a href="' . Utils::get_support_link() . '" target="_blank" rel="noopener noreferrer">',
+							'</a>'
+						);
+					},
+				),
+				array(
+					'type'        => 'panel',
+					'title'       => __( 'Can I use the Cloudinary plugin for my eCommerce websites?', 'cloudinary' ),
+					'collapsible' => 'closed',
+					'content'     => sprintf(
+						// translators: The HTML markup.
+						__( 'Yes, the Cloudinary plugin has full support for WooCommerce. We also have additional functionality that allows you to add a fully optimized %1$sProduct Gallery%2$s.', 'cloudinary' ),
+						'<a href="' . esc_url( add_query_arg( 'page', 'cloudinary_gallery' ) ) . '">',
+						'</a>'
+					),
+				),
+				array(
+					'type'        => 'panel',
+					'title'       => __( 'Why are my images loading locally and not from Cloudinary?', 'cloudinary' ),
+					'collapsible' => 'closed',
+					'content'     => sprintf(
+						// translators: The HTML markup.
+						__( 'Your images may be loading locally for a number of reasons:%1$sThe asset has been selected to be delivered from WordPress. You can update this for each asset via the %5$sWordPress Media Library%4$s.%2$sYour asset is %3$sstored outside%4$s of your WordPress storage.%2$sThe asset is not properly synced with Cloudinary. You can find the sync status of your assets in the %5$sWordPress Media Library%4$s.%6$s', 'cloudinary' ),
+						'<ul><li>',
+						'</li><li>',
+						'<a href="' . add_query_arg( array( 'page' => 'cloudinary_connect#connect.cache_external.external_assets' ), admin_url( 'admin.php' ) ) . '">',
+						'</a>',
+						'<a href="' . admin_url( 'upload.php' ) . '">',
+						'</li></ul>'
+					),
+				),
+				array(
+					'type'        => 'panel',
+					'title'       => __( 'How do I handle a CLDBind error which is causing issues with lazy loading?', 'cloudinary' ),
+					'collapsible' => 'closed',
+					'content'     => __( 'The Cloudinary lazy loading scripts must be loaded in the page head. Ensure your site or any 3rd party plugins are not setup to move these scripts.', 'cloudinary' ),
+				),
+			),
+		),
+	),
+	'wizard'         => array(
+		'section' => 'wizard',
+		'slug'    => 'wizard',
+	),
+	'debug'          => array(
+		'section' => 'debug',
+		'slug'    => 'debug',
+		array(
+			'type'  => 'panel',
+			'title' => __( 'Debug log', 'cloudinary' ),
+			array(
+				'type' => 'debug',
+			),
+		),
+	),
+	'edit_asset'     => array(
+		'page_title'          => __( 'Edit asset', 'cloudinary' ),
+		'section'             => 'edit-asset',
+		'slug'                => 'edit_asset',
+		'requires_connection' => true,
+		array(
+			'type' => 'row',
+			array(
+				'type'       => 'column',
+				'width'      => '950px',
+				'attributes' => array(
+					'wrap' => array(
+						'style' => 'margin: 0 auto;max-width:1200px;',
+					),
+				),
+				array(
+					'type' => 'referrer_link',
+				),
+				array(
+					'type' => 'panel',
+					array(
+						'type' => 'asset_preview',
+					),
+				),
+			),
+		),
+	),
+);
+
+/**
+ * Filter the Cloudinary admin pages.
+ *
+ * @hook cloudinary_admin_pages
+ *
+ * @param $settings {array} The admin pages settings.
+ *
+ * @return {array}
+ */
+return apply_filters( 'cloudinary_admin_pages', $settings );
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/ui-definitions_settings-video.php.html b/docs/ui-definitions_settings-video.php.html new file mode 100644 index 000000000..d1d42b55c --- /dev/null +++ b/docs/ui-definitions_settings-video.php.html @@ -0,0 +1,388 @@ + + + + + Source: ui-definitions/settings-video.php - Cloudinary Docs + + + + + + + + + + + + +
+ + +

Source: ui-definitions/settings-video.php

+ + + + + + + +
+
+
<?php
+/**
+ * Defines the settings structure for video.
+ *
+ * @package Cloudinary
+ */
+
+use Cloudinary\Utils;
+use function Cloudinary\get_plugin_instance;
+
+$transformations_title = Utils::get_transformations_title( esc_html__( 'Video', 'cloudinary' ) );
+
+$settings = array(
+	array(
+		'type'        => 'panel',
+		'title'       => __( 'Video - Global Settings', 'cloudinary' ),
+		'anchor'      => true,
+		'option_name' => 'media_display',
+		array(
+			'type' => 'tabs',
+			'tabs' => array(
+				'image_setting' => array(
+					'text' => __( 'Settings', 'cloudinary' ),
+					'id'   => 'settings',
+				),
+				'image_preview' => array(
+					'text' => __( 'Preview', 'cloudinary' ),
+					'id'   => 'preview',
+				),
+			),
+		),
+		array(
+			'type' => 'row',
+			array(
+				'type'   => 'column',
+				'tab_id' => 'settings',
+				'class'  => array(
+					'column-min-w-50',
+				),
+				array(
+					'type'               => 'on_off',
+					'slug'               => 'video_delivery',
+					'title'              => __( 'Video delivery', 'cloudinary' ),
+					'optimisation_title' => __( 'Video delivery', 'cloudinary' ),
+					'tooltip_text'       => __(
+						'If you turn this setting off, your videos will be delivered from WordPress.',
+						'cloudinary'
+					),
+					'description'        => __( 'Sync and deliver videos from Cloudinary.', 'cloudinary' ),
+					'default'            => 'on',
+					'attributes'         => array(
+						'data-context' => 'video',
+					),
+					'readonly'           => static function () {
+						$plugin = get_plugin_instance();
+						return 'on' === $plugin->settings->get_value( 'video_delivery' ) && ! $plugin->get_component( 'storage' )->is_local_full();
+					},
+					'readonly_message'   => sprintf(
+						// translators: %s is a link to the storage settings page.
+						__( 'This setting currently can’t be turned off. Your videos must be delivered from Cloudinary because your assets are being stored in Cloudinary only. To enable delivering videos from WordPress, first select a %s in the General Settings page that will enable storing your assets also in WordPress.', 'cloudinary' ),
+						sprintf(
+							'<a href="%s">%s</a>',
+							add_query_arg( array( 'page' => 'cloudinary_connect#connect.offload' ), admin_url( 'admin.php' ) ),
+							__( 'Storage setting', 'cloudinary' )
+						)
+					),
+				),
+				array(
+					'type'      => 'group',
+					'condition' => array(
+						'video_delivery' => true,
+					),
+					array(
+						'type'    => 'tag',
+						'element' => 'hr',
+					),
+					array(
+						'type'         => 'select',
+						'slug'         => 'video_player',
+						'title'        => __( 'Video player', 'cloudinary' ),
+						'tooltip_text' => __( 'Which video player to use on all videos.', 'cloudinary' ),
+						'default'      => 'wp',
+						'options'      => array(
+							'wp'  => __( 'WordPress player', 'cloudinary' ),
+							'cld' => __( 'Cloudinary player', 'cloudinary' ),
+						),
+					),
+					array(
+						'type'      => 'group',
+						'condition' => array(
+							'video_player' => 'cld',
+						),
+						array(
+							'slug'         => 'adaptive_streaming',
+							'description'  => __( 'Adaptive bitrate streaming (beta)', 'cloudinary' ),
+							'type'         => 'on_off',
+							'default'      => 'off',
+							'tooltip_text' => sprintf(
+								// translators: Placeholders are <a> tags.
+								__(
+									'Adaptive bitrate streaming is a video delivery technique that adjusts the quality of a video stream in real time according to detected bandwidth and CPU capacity.%1$sRead more about Adaptive bitrate streaming%2$s',
+									'cloudinary'
+								),
+								'<br><a href="https://cloudinary.com/documentation/adaptive_bitrate_streaming" target="_blank">',
+								'</a>'
+							),
+						),
+						array(
+							'slug'      => 'adaptive_streaming_mode',
+							'title'     => __( 'Streaming protocol', 'cloudinary' ),
+							'type'      => 'select',
+							'default'   => 'mpd',
+							'options'   => array(
+								'mpd'  => __( 'Dynamic adaptive streaming over HTTP (MPEG-DASH)', 'cloudinary' ),
+								'm3u8' => __( 'HTTP live streaming (HLS)', 'cloudinary' ),
+							),
+							'condition' => array(
+								'adaptive_streaming' => true,
+							),
+						),
+					),
+					array(
+						'type'      => 'group',
+						'condition' => array(
+							'video_player' => 'cld',
+						),
+						array(
+							'slug'        => 'video_controls',
+							'description' => __( 'Show controls', 'cloudinary' ),
+							'type'        => 'on_off',
+							'default'     => 'on',
+						),
+						array(
+							'slug'        => 'video_loop',
+							'description' => __( 'Repeat video', 'cloudinary' ),
+							'type'        => 'on_off',
+							'default'     => 'off',
+						),
+						array(
+							'slug'         => 'video_autoplay_mode',
+							'title'        => __( 'Autoplay', 'cloudinary' ),
+							'type'         => 'radio',
+							'default'      => 'off',
+							'options'      => array(
+								'off'       => __( 'Off', 'cloudinary' ),
+								'always'    => __( 'Always', 'cloudinary' ),
+								'on-scroll' => __( 'On-scroll (autoplay when in view)', 'cloudinary' ),
+							),
+							'tooltip_text' => sprintf(
+								// translators: Placeholders are <a> tags.
+								__(
+									'Please note that when choosing "always", the video will autoplay without sound (muted). This is a built-in browser feature and applies to all major browsers.%1$sRead more about muted autoplay%2$s',
+									'cloudinary'
+								),
+								'<br><a href="https://cloudinary.com/glossary/video-autoplay" target="_blank">',
+								'</a>'
+							),
+						),
+					),
+					array(
+						'type'    => 'tag',
+						'element' => 'hr',
+					),
+					array(
+						'type' => 'group',
+						array(
+							'type'         => 'on_off',
+							'slug'         => 'video_optimization',
+							'title'        => __( 'Video optimization', 'cloudinary' ),
+							'tooltip_text' => __(
+								'Videos will be delivered using Cloudinary’s automatic format and quality algorithms for the best tradeoff between visual quality and file size. Use Advanced Optimization options to manually tune format and quality.',
+								'cloudinary'
+							),
+							'description'  => __( 'Optimize videos on my site.', 'cloudinary' ),
+							'default'      => 'on',
+							'attributes'   => array(
+								'data-context' => 'video',
+							),
+							'depends'      => array(
+								'video_delivery',
+							),
+						),
+					),
+					array(
+						'type'      => 'group',
+						'condition' => array(
+							'video_optimization' => true,
+						),
+						array(
+							'type'         => 'select',
+							'slug'         => 'video_format',
+							'title'        => __( 'Video format', 'cloudinary' ),
+							'tooltip_text' => __(
+								"The video format to use for delivery. Leave as Auto to automatically deliver the most optimal format based on the user's browser and device.",
+								'cloudinary'
+							),
+							'default'      => 'auto',
+							'options'      => array(
+								'none' => __( 'Not set', 'cloudinary' ),
+								'auto' => __( 'Auto', 'cloudinary' ),
+							),
+							'suffix'       => 'f_@value',
+							'attributes'   => array(
+								'data-context' => 'video',
+								'data-meta'    => 'f',
+							),
+						),
+						array(
+							'type'         => 'select',
+							'slug'         => 'video_quality',
+							'title'        => __( 'Video quality', 'cloudinary' ),
+							'tooltip_text' => __(
+								'The compression quality to apply when delivering videos. Leave as Auto to apply an algorithm that finds the best tradeoff between visual quality and file size.',
+								'cloudinary'
+							),
+							'default'      => 'auto',
+							'options'      => array(
+								'none'      => __( 'Not set', 'cloudinary' ),
+								'auto'      => __( 'Auto', 'cloudinary' ),
+								'auto:best' => __( 'Auto best', 'cloudinary' ),
+								'auto:good' => __( 'Auto good', 'cloudinary' ),
+								'auto:eco'  => __( 'Auto eco', 'cloudinary' ),
+								'auto:low'  => __( 'Auto low', 'cloudinary' ),
+								'100'       => '100',
+								'80'        => '80',
+								'60'        => '60',
+								'40'        => '40',
+								'20'        => '20',
+							),
+							'suffix'       => 'q_@value',
+							'attributes'   => array(
+								'data-context' => 'video',
+								'data-meta'    => 'q',
+							),
+						),
+
+					),
+					array(
+						'type'    => 'tag',
+						'element' => 'hr',
+					),
+					array(
+						'type'           => 'text',
+						'slug'           => 'video_freeform',
+						'title'          => $transformations_title,
+						'default'        => '',
+						'anchor'         => true,
+						'tooltip_text'   => sprintf(
+							// translators: The link to transformation reference.
+							__(
+								'A set of additional transformations to apply to all videos. Specify your transformations using Cloudinary URL transformation syntax. See %1$sreference%2$s for all available transformations and syntax.%3$s* The Cloudinary global transformations are only applied to assets managed in the Media Library%4$s.',
+								'cloudinary'
+							),
+							'<a href="https://cloudinary.com/documentation/transformation_reference" target="_blank" rel="noopener noreferrer">',
+							'</a>',
+							'<br><br><em>',
+							'</em>'
+						),
+						'link'           => array(
+							'text' => __( 'See examples', 'cloudinary' ),
+							'href' => 'https://cloudinary.com/documentation/transformation_reference',
+						),
+						'attributes'     => array(
+							'data-context' => 'video',
+							'placeholder'  => 'fps_15-25,ac_none',
+						),
+						'taxonomy_field' => array(
+							'context'  => 'video',
+							'priority' => 10,
+						),
+					),
+					array(
+						'type'  => 'info_box',
+						'icon'  => $this->dir_url . 'css/images/video.svg',
+						'title' => __( 'What are transformations?', 'cloudinary' ),
+						'text'  => __(
+							'A set of parameters included in a Cloudinary URL to programmatically transform the visual appearance of the assets on your website.',
+							'cloudinary'
+						),
+					),
+				),
+			),
+			array(
+				'type'      => 'column',
+				'tab_id'    => 'preview',
+				'class'     => array(
+					'column-min-w-50',
+				),
+				'condition' => array(
+					'video_delivery' => true,
+				),
+				array(
+					'type'           => 'video_preview',
+					'title'          => __( 'Video preview', 'cloudinary' ),
+					'slug'           => 'video_preview',
+					'taxonomy_field' => array(
+						'context'  => 'video',
+						'priority' => 10,
+					),
+				),
+			),
+		),
+		array(
+			'type'  => 'info_box',
+			'icon'  => $this->dir_url . 'css/images/academy-icon.svg',
+			'title' => __( 'Need help?', 'cloudinary' ),
+			'text'  => sprintf(
+				// Translators: The HTML for opening and closing link tags.
+				__(
+					'Watch free lessons on how to use the Video Global Settings in the %1$sCloudinary Academy%2$s.',
+					'cloudinary'
+				),
+				'<a href="https://training.cloudinary.com/learn/course/introduction-to-cloudinary-for-wordpress-administrators-70-minute-course-1h85/lessons/transforming-images-and-videos-for-pages-and-posts-1545?page=1" target="_blank" rel="noopener noreferrer">',
+				'</a>'
+			),
+		),
+	),
+);
+
+/**
+ * Filter the Cloudinary global transformations tab for video.
+ *
+ * @hook cloudinary_admin_video_settings
+ *
+ * @param $settings {array} The global transformations settings.
+ *
+ * @return {array}
+ */
+return apply_filters( 'cloudinary_admin_video_settings', $settings );
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/hookdoc-conf.json b/hookdoc-conf.json new file mode 100644 index 000000000..8f340862a --- /dev/null +++ b/hookdoc-conf.json @@ -0,0 +1,19 @@ +{ + "opts": { + "destination": "docs", + "template": "node_modules/wp-hookdoc/template", + "recurse": true, + "readme": "./README.md" + }, + "source": { + "includePattern": ".+\\.(php|inc)?$" + }, + "plugins": [ + "node_modules/wp-hookdoc/plugin" + ], + "templates": { + "default": { + "layoutFile": ".hookdoc/layout.tmpl" + } + } +} diff --git a/package-lock.json b/package-lock.json index ba640f0ba..aa741b1ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cloudinary", - "version": "3.2.10", + "version": "3.2.12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cloudinary", - "version": "3.2.10", + "version": "3.2.12", "hasInstallScript": true, "license": "GPL-2.0+", "devDependencies": { @@ -73,6 +73,7 @@ "istanbul-reports": "^3.0.2", "jest-puppeteer-istanbul": "^0.5.3", "jest-silent-reporter": "^0.3.0", + "jsdoc": "^3.6.6", "lint-staged": "^10.5.4", "load-grunt-tasks": "^5.1.0", "loading-attribute-polyfill": "^1.5.4", @@ -102,7 +103,8 @@ "typescript": "^4.0.5", "webpack": "^5.94.0", "webpack-cli": "^4.2.0", - "webpackbar": "^5.0.2" + "webpackbar": "^5.0.2", + "wp-hookdoc": "^0.2.0" }, "engines": { "node": ">=12", @@ -8641,6 +8643,28 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -17109,6 +17133,18 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -26968,6 +27004,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -26988,6 +27033,35 @@ "dev": true, "peer": true }, + "node_modules/jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jsdoc-type-pratt-parser": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", @@ -26997,6 +27071,15 @@ "node": ">=12.0.0" } }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/jsdoctypeparser": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz", @@ -27359,6 +27442,15 @@ "node": ">=0.10.0" } }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -29386,6 +29478,16 @@ "markdown-it": "bin/markdown-it.js" } }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, "node_modules/markdown-it/node_modules/entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", @@ -29479,6 +29581,18 @@ "integrity": "sha512-oEacRUVeTJ5D5hW1UYd2qExYI0oELdYK72k1TKGvIeYJIbqQWAz476NAc7LNixSySUhcNl++d02DvX0ccDk9/w==", "dev": true }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/marky": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", @@ -35801,6 +35915,15 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -38656,6 +38779,12 @@ "node": ">=8" } }, + "node_modules/taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", + "dev": true + }, "node_modules/tannin": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tannin/-/tannin-1.2.0.tgz", @@ -39405,6 +39534,12 @@ "node": ">=0.10.0" } }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true + }, "node_modules/underscore.string": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", @@ -40910,6 +41045,12 @@ "node": ">=0.10.0" } }, + "node_modules/wp-hookdoc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/wp-hookdoc/-/wp-hookdoc-0.2.0.tgz", + "integrity": "sha512-BDVfqSA+L7Ho99McFu0ATwJRddaSVbUH/lgoe4RFP7Ktg3kiRtp7VHGIIdn+QhQN7ZtLmCBMpqtEChDvaGDppw==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -41046,6 +41187,12 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, "node_modules/xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", @@ -47516,6 +47663,28 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, "@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -53887,6 +54056,15 @@ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, + "catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -61310,6 +61488,15 @@ "argparse": "^2.0.1" } }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.4" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -61330,6 +61517,37 @@ "dev": true, "peer": true }, + "jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, "jsdoc-type-pratt-parser": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", @@ -61607,6 +61825,15 @@ "is-buffer": "^1.1.5" } }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -63177,6 +63404,13 @@ } } }, + "markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "requires": {} + }, "markdownlint": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.25.1.tgz", @@ -63239,6 +63473,12 @@ "integrity": "sha512-oEacRUVeTJ5D5hW1UYd2qExYI0oELdYK72k1TKGvIeYJIbqQWAz476NAc7LNixSySUhcNl++d02DvX0ccDk9/w==", "dev": true }, + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true + }, "marky": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", @@ -68016,6 +68256,15 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "requires": { + "lodash": "^4.17.21" + } + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -70189,6 +70438,12 @@ } } }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==", + "dev": true + }, "tannin": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tannin/-/tannin-1.2.0.tgz", @@ -70798,6 +71053,12 @@ "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", "dev": true }, + "underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true + }, "underscore.string": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", @@ -71877,6 +72138,12 @@ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true }, + "wp-hookdoc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/wp-hookdoc/-/wp-hookdoc-0.2.0.tgz", + "integrity": "sha512-BDVfqSA+L7Ho99McFu0ATwJRddaSVbUH/lgoe4RFP7Ktg3kiRtp7VHGIIdn+QhQN7ZtLmCBMpqtEChDvaGDppw==", + "dev": true + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -71974,6 +72241,12 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, "xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", diff --git a/package.json b/package.json index 804cf078e..21e51b2e2 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "npm": ">=6.9" }, "scripts": { - "build": "wp-scripts build", + "build": "wp-scripts build && npm run build:docs", + "build:docs": "rm -rf docs/ && jsdoc -c hookdoc-conf.json php ui-definitions cloudinary.php", "deploy": "npm run build && grunt deploy && npm run release", "deploy-assets": "grunt deploy-assets", "dev": "wp-scripts start", @@ -113,6 +114,7 @@ "istanbul-reports": "^3.0.2", "jest-puppeteer-istanbul": "^0.5.3", "jest-silent-reporter": "^0.3.0", + "jsdoc": "^3.6.6", "lint-staged": "^10.5.4", "load-grunt-tasks": "^5.1.0", "loading-attribute-polyfill": "^1.5.4", @@ -142,7 +144,8 @@ "typescript": "^4.0.5", "webpack": "^5.94.0", "webpack-cli": "^4.2.0", - "webpackbar": "^5.0.2" + "webpackbar": "^5.0.2", + "wp-hookdoc": "^0.2.0" }, "version": "3.2.12" } diff --git a/php/cache/class-cache-point.php b/php/cache/class-cache-point.php index d649235b8..9f5c42168 100644 --- a/php/cache/class-cache-point.php +++ b/php/cache/class-cache-point.php @@ -110,6 +110,8 @@ public function __construct( Cache $cache ) { * @param $value {int} The default number of static assets. * * @return {int} + * + * @since 2.8.0 */ $this->sync_limit = apply_filters( 'cloudinary_on_demand_sync_limit', 100 ); $this->register_post_type(); diff --git a/php/class-admin.php b/php/class-admin.php index fe37dc294..97eca9bd7 100644 --- a/php/class-admin.php +++ b/php/class-admin.php @@ -456,7 +456,12 @@ protected function save_settings( $submission, $data ) { } else { $this->add_admin_notice( 'error_notice', __( 'No changes to save', 'cloudinary' ), 'success' ); } - // Flush cache. + /** + * Action to flush delivery caches. + * + * @hook cloudinary_flush_cache + * @since 3.0.0 + */ do_action( 'cloudinary_flush_cache' ); } diff --git a/php/class-assets.php b/php/class-assets.php index 52287e99f..a7b0c239d 100644 --- a/php/class-assets.php +++ b/php/class-assets.php @@ -211,8 +211,6 @@ public function enqueue_assets() { /** * Get the local url for an asset. * - * @hook cloudinary_local_url - * * @param string|false $url The url to filter. * @param int $asset_id The asset ID. * @@ -257,8 +255,6 @@ public function admin_bar_cache( $admin_bar ) { /** * Sets the autosync to work on cloudinary_assets even when the autosync is disabled. * - * @hook cloudinary_can_sync_asset - * * @param bool $can The can sync check value. * @param int $asset_id The asset ID. * @@ -292,8 +288,6 @@ public static function is_asset_type( $post_id ) { /** * Filter out sizes for assets. * - * @hook intermediate_image_sizes_advanced - * * @param array $new_sizes The sizes to remove. * @param array $image_meta The image meta. * @param int|null $attachment_id The asset ID. @@ -348,8 +342,6 @@ public function meta_updates() { /** * Set urls to be replaced. - * - * @hook cloudinary_string_replace */ public function add_url_replacements() { // Due to the output buffers, this can be called multiple times. @@ -778,8 +770,6 @@ public function validate_edited_asset_sync( $attachment_id ) { /** * Register our sync type. * - * @hook cloudinary_sync_base_struct - * * @param array $structs The structure of all sync types. * * @return array @@ -944,8 +934,6 @@ protected function init_asset_parents() { /** * Check if the non-local URL should be added as an asset. * - * @hook cloudinary_is_content_dir - * * @param bool $is_local The is_local flag. * @param string $url The URL to check. * @@ -1033,8 +1021,6 @@ public function get_attached_file( $file, $asset_id ) { /** * Check to see if the post is a media item. * - * @hook cloudinary_is_media - * * @param bool $is_media The is_media flag. * @param int $attachment_id The attachment ID. * @@ -1258,8 +1244,6 @@ protected function register_post_type() { /** * Setup the class. - * - * @hook cloudinary_init_settings */ public function setup() { if ( is_user_logged_in() && is_admin() && ! Utils::is_rest_api() ) { diff --git a/php/class-cache.php b/php/class-cache.php index f79ceba45..c97480995 100644 --- a/php/class-cache.php +++ b/php/class-cache.php @@ -263,7 +263,7 @@ public function bypass_cache() { * * @param $bypass {bool} True to bypass, false to not. * - * @return {bool} + * @return {bool} */ return apply_filters( 'cloudinary_bypass_cache', ! is_null( $bypass ) ); } diff --git a/php/class-connect.php b/php/class-connect.php index e5d400a68..800d48154 100644 --- a/php/class-connect.php +++ b/php/class-connect.php @@ -781,9 +781,10 @@ public function get_config() { * * @since 2.3.1 * - * @param string $new_version The version upgrading to. + * @hook cloudinary_version_upgrade * - * @param string $old_version The version upgrading from. + * @param $new_version {string} The version upgrading to. + * @param $old_version {string} The version upgrading from. */ do_action( 'cloudinary_version_upgrade', $old_version, $this->plugin->version ); } diff --git a/php/class-delivery.php b/php/class-delivery.php index 564f967a0..e43b27f8d 100644 --- a/php/class-delivery.php +++ b/php/class-delivery.php @@ -291,7 +291,7 @@ public function filter_out_cloudinary( $content ) { } $size = $this->media->get_size_from_url( $original_url ); $transformations = $this->media->get_transformations_from_string( $original_url ); - if ( 'image' === $this->media->get_resource_type( $result['post_id'] ) && ! $this->media->is_preview_only( $result['post_id'] ) ) { + if ( 'image' === $this->media->get_resource_type( $result['post_id'] ) ) { $attachment_url = wp_get_attachment_image_url( $result['post_id'], $size ); } else { $attachment_url = wp_get_attachment_url( $result['post_id'] ); @@ -509,6 +509,12 @@ public function delete_size_relationship( $attachment_id ) { $relationship = Relationship::get_relationship( $attachment_id ); $relationship->delete(); + /** + * Action to flush delivery caches. + * + * @hook cloudinary_flush_cache + * @since 3.0.0 + */ do_action( 'cloudinary_flush_cache' ); } @@ -589,6 +595,12 @@ public static function update_size_relations_state( $attachment_id, $state ) { $relationship->save(); } + /** + * Action to flush delivery caches. + * + * @hook cloudinary_flush_cache + * @since 3.0.0 + */ do_action( 'cloudinary_flush_cache' ); } @@ -617,6 +629,12 @@ public static function delete_bulk_size_relations( $ids ) { $wpdb->query( $prepared );// phpcs:ignore WordPress.DB + /** + * Action to flush delivery caches. + * + * @hook cloudinary_flush_cache + * @since 3.0.0 + */ do_action( 'cloudinary_flush_cache' ); } @@ -763,8 +781,6 @@ public function clear_cache() { * Delete cached metadata. * * @param bool $hard Whether to hard flush the cache. - * - * @hook cloudinary_flush_cache */ public function do_clear_cache( $hard = true ) { delete_post_meta_by_key( self::META_CACHE_KEY ); diff --git a/php/class-extensions.php b/php/class-extensions.php index 22099f4df..4523e0328 100644 --- a/php/class-extensions.php +++ b/php/class-extensions.php @@ -174,8 +174,10 @@ public function register_extensions() { * @hook cloudinary_register_extensions * @since 3.0.0 * - * @param $extensions {array) The list of extensions to register. + * @param $extensions {array} The list of extensions to register. * @param $plugin {Plugin} The core plugin object. + * + * @return {array} */ $extensions = apply_filters( 'cloudinary_register_extensions', $extensions, $this->plugin ); foreach ( $extensions as $slug => $extension ) { diff --git a/php/class-media.php b/php/class-media.php index 182782b3d..0b214ce0c 100644 --- a/php/class-media.php +++ b/php/class-media.php @@ -178,6 +178,8 @@ public function init_hook() { * @since 3.0.0 * * @param $filters {array} The default filters. + * + * @returns {array} */ $this->cloudinary_filters = apply_filters( 'cloudinary_media_filters', @@ -210,9 +212,12 @@ public function get_compatible_media_types() { /** * Filter the default Cloudinary Media Types. * - * @param array $types The default media types array. + * @hook cloudinary_media_types + * @default array( 'image', 'video', 'audio', 'application', 'text' ) + * + * @param $types {array} The default media types array. * - * @return array + * @return {array} */ return apply_filters( 'cloudinary_media_types', $media_types ); } @@ -230,9 +235,12 @@ public function get_syncable_delivery_types() { /** * Filter the delivery types that are able to sync. * - * @param array $types The default syncable types. + * @hook cloudinary_syncable_delivery_types + * @default array( 'upload' ) * - * @return array + * @param $types {array} The default syncable types. + * + * @return {array} */ return apply_filters( 'cloudinary_syncable_delivery_types', $types ); } @@ -269,7 +277,11 @@ public function get_convertible_extensions() { /** * Filter the base types for conversion. * - * @param array $base_types The base conversion types array. + * @hook cloudinary_convert_media_types + * + * @param $base_types {array} The base conversion types array. + * + * @return {array} */ return apply_filters( 'cloudinary_convert_media_types', $base_types ); } @@ -563,7 +575,12 @@ public function is_preview_only( $attachment_id ) { /** * Filter the file types that are preview only. * - * @param array $base_types The base preview types. + * @hook cloudinary_preview_types + * @default array( 'pdf', 'psd' ) + * + * @param $base_types {array} The base preview types. + * + * @return {array} */ $preview_types = apply_filters( 'cloudinary_preview_types', $base_types ); $mime = wp_check_filetype( get_attached_file( $attachment_id ) ); @@ -624,8 +641,12 @@ public function get_resource_type( $attachment_id ) { /** * Filter the Cloudinary resource type for the attachment. * - * @param string $type The type. - * @param int $attachment_id The attachment ID. + * @hook cloudinary_resource_type + * + * @param $type {string} The type. + * @param $attachment_id {int} The attachment ID. + * + * @return {string} */ $type = apply_filters( 'cloudinary_resource_type', $type, $attachment_id ); @@ -975,10 +996,12 @@ public function get_transformations( $attachment_id, $transformations = array(), /** * Filter the Cloudinary transformations. * - * @param array $transformations Array of transformation options. - * @param int $attachment_id The id of the asset. + * @hook cloudinary_transformations * - * @return array + * @param $transformations {array} Array of transformation options. + * @param $attachment_id {int} The id of the asset. + * + * @return {array} */ $cache[ $key ] = apply_filters( 'cloudinary_transformations', $transformations, $attachment_id ); @@ -1171,9 +1194,12 @@ public function attachment_url( $url, $attachment_id ) { * Filter doing upload. * If so, return the default attachment URL. * - * @param bool Default false. + * @hook cloudinary_doing_upload + * @default false * - * @return bool + * @param $false {bool} Default false. + * + * @return {bool} */ && ! apply_filters( 'cloudinary_doing_upload', false ) ) { @@ -1219,10 +1245,13 @@ public function apply_default_transformations( array $transformations, $attachme /** * Filter to allow bypassing defaults. Return false to not apply defaults. * - * @param bool $true True to apply defaults. - * @param int|string $attachment_id_type The current attachment ID or type. + * @hook cloudinary_apply_default_transformations + * @default true + * + * @param $true {bool} True to apply defaults. + * @param $attachment_id {int} The current attachment ID. * - * @return bool + * @return {bool} */ if ( false === apply_filters( 'cloudinary_apply_default_transformations', true, $attachment_id_type ) ) { return $transformations; @@ -1245,10 +1274,13 @@ public function apply_default_transformations( array $transformations, $attachme /** * Filter the default Quality and Format transformations for the specific media type. * - * @param array $defaults The default transformations array. - * @param array $transformations The current transformations array. + * @hook cloudinary_default_qf_transformations_{$type} + * @default array() + * + * @param $defaults {array} The default transformations array. + * @param $transformations {array} The current transformations array. * - * @return array + * @return {array} */ $default = apply_filters( "cloudinary_default_qf_transformations_{$type}", array(), $transformations ); $default = array_filter( $default ); // Clear out empty settings. @@ -1258,10 +1290,13 @@ public function apply_default_transformations( array $transformations, $attachme /** * Filter the default Freeform transformations for the specific media type. * - * @param array $defaults The default transformations array. - * @param array $transformations The current transformations array. + * @hook cloudinary_default_freeform_transformations_{$type} + * @default array() + * + * @param $defaults {array} The default transformations array. + * @param $transformations {array} The current transformations array. * - * @return array + * @return {array} */ $freeform[ $type ] = apply_filters( "cloudinary_default_freeform_transformations_{$type}", array(), $transformations ); $freeform[ $type ] = array_filter( $freeform[ $type ] ); // Clear out empty settings. @@ -1286,9 +1321,11 @@ public function apply_default_transformations( array $transformations, $attachme /** * Filter the default cloudinary transformations. * - * @param array $defaults The default transformations array. + * @hook cloudinary_default_transformations + * + * @param $defaults {array} The default transformations array. * - * @return array + * @return {array} */ $cache[ $key ] = apply_filters( 'cloudinary_default_transformations', @@ -1424,11 +1461,13 @@ public function cloudinary_url( $attachment_id, $size = array(), $transformation /** * Filter the final Cloudinary URL. * - * @param string $url The Cloudinary URL. - * @param int $attachment_id The id of the attachment. - * @param array $pre_args The arguments used to create the url. + * @hook cloudinary_converted_url + * + * @param $url {string} The Cloudinary URL. + * @param $attachment_id {int} The id of the attachment. + * @param $pre_args {array} The arguments used to create the url. * - * @return string + * @return {string} */ $url = apply_filters( 'cloudinary_converted_url', $url, $attachment_id, $pre_args ); @@ -1473,7 +1512,7 @@ public function local_url( $attachment_id ) { * @since 3.0.0 * * @param $url {string|false} The local URL - * @param $attachment_id {int} The attachment ID. + * @param $attachment_id {int} The attachment ID. * * @return {string|false} */ @@ -1581,10 +1620,12 @@ public function prepare_size( $attachment_id, $size ) { /** * Filter Cloudinary size and crops * - * @param array|string $size The size array or slug. - * @param int $attachment_id The attachment ID. + * @hook cloudinary_prepare_size + * + * @param $size {array|string} The size array or slug. + * @param $attachment_id {int} The attachment ID. * - * @return array|string + * @return {array|string} */ $size = apply_filters( 'cloudinary_prepare_size', $size, $attachment_id ); @@ -1738,20 +1779,26 @@ public function cloudinary_id( $attachment_id ) { $cloudinary_id = $this->get_cloudinary_id( $attachment_id ); /** - * Filter to validate the Cloudinary ID to allow extending it's availability. + * Filter to validate the Cloudinary ID to allow extending it's availability. * - * @param string|bool $cloudinary_id The public ID from Cloudinary, or false if not found. - * @param int $attachment_id The id of the asset. + * @hook cloudinary_validate_cloudinary_id * - * @return string|bool + * @param $cloudinary_id {string|bool} The public ID from Cloudinary, or false if not found. + * @param $attachment_id {int} The id of the asset. + * + * @return {string|bool} */ - $cloudinary_id = apply_filters( 'validate_cloudinary_id', $cloudinary_id, $attachment_id ); + $cloudinary_id = apply_filters( 'cloudinary_validate_cloudinary_id', $cloudinary_id, $attachment_id ); /** * Action the Cloudinary ID to allow extending it's availability. * - * @param string|bool $cloudinary_id The public ID from Cloudinary, or false if not found. - * @param int $attachment_id The id of the asset. + * @hook cloudinary_id + * + * @param $cloudinary_id {string|bool} The public ID from Cloudinary, or false if not found. + * @param $attachment_id {int} The id of the asset. + * + * @since 2.1.9 */ do_action( 'cloudinary_id', $cloudinary_id, $attachment_id ); @@ -2792,11 +2839,13 @@ public function get_context_options( $attachment_id ) { /** * Filter the options to allow other plugins to add requested options for uploading. * - * @param array $options The options array. - * @param \WP_Post $post The attachment post. - * @param \Cloudinary\Sync The sync object instance. + * @hook cloudinary_context_options + * + * @param $options {array} The options array. + * @param $post {WP_Post} The attachment post. + * @param $this {Media} The media object instance. * - * @return array + * @return {array} */ $context_options = apply_filters( 'cloudinary_context_options', $context_options, get_post( $attachment_id ), $this ); foreach ( $context_options as $option => &$value ) { @@ -2825,10 +2874,12 @@ public function is_folder_synced( $attachment_id ) { /** * Filter is folder synced flag. * - * @param bool $is_folder_synced Flag value for is folder sync. - * @param int $attachment_id The attachment ID. + * @hook cloudinary_is_folder_synced + * + * @param $is_folder_synced {bool} Flag value for is folder sync. + * @param $attachment_id {int} The attachment ID. * - * @return bool + * @return {bool} */ $is_folder_synced = apply_filters( 'cloudinary_is_folder_synced', $is_folder_synced, $attachment_id ); @@ -2863,11 +2914,13 @@ public function get_upload_options( $attachment_id, $context = '' ) { /** * Filter the options to allow other plugins to add requested options for uploading. * - * @param array $options The options array. - * @param \WP_Post $post The attachment post. - * @param \Cloudinary\Sync The sync object instance. + * @hook cloudinary_upload_options + * + * @param $options {array} The options array. + * @param $post {WP_Post} The attachment post. + * @param $this {Media} The media object instance. * - * @return array + * @return {array} */ $options = apply_filters( 'cloudinary_upload_options', $options, get_post( $attachment_id ), $this ); // Add folder to prevent folder contamination. @@ -3002,11 +3055,14 @@ public function can_filter_out_local() { /** * Filter to allow stopping filtering out local. * - * @param bool $can True as default. + * @hook cloudinary_filter_out_local + * @default true + * + * @param $can {bool} True as default. * - * @return bool + * @return {bool} */ - $can = apply_filters( 'cloudinary_filter_out_local', true ); + $can = apply_filters( 'cloudinary_filter_out_local', $can ); } return $can; diff --git a/php/class-rest-api.php b/php/class-rest-api.php index 0200ce98d..b2c75e459 100644 --- a/php/class-rest-api.php +++ b/php/class-rest-api.php @@ -42,6 +42,16 @@ public function rest_api_init() { 'permission_callback' => '__return_true', ); + /** + * Filter the Cloudinary REST API endpoints. + * + * @hook cloudinary_api_rest_endpoints + * @default array() + * + * @param $types {array} The registered endpoints. + * + * @return {array} + */ $this->endpoints = apply_filters( 'cloudinary_api_rest_endpoints', array() ); foreach ( $this->endpoints as $route => $endpoint ) { diff --git a/php/class-settings.php b/php/class-settings.php index ba655448e..4523eabca 100644 --- a/php/class-settings.php +++ b/php/class-settings.php @@ -368,6 +368,8 @@ public function get_value( ...$slugs ) { * * @param $value {mixed} The setting value. * @param $slug {string} The setting slug. + * + * @return {mixed} */ $return[ $slug ] = apply_filters( 'cloudinary_setting_get_value', $value, $slug ); } diff --git a/php/class-sync.php b/php/class-sync.php index 4ec29f346..b53c46e42 100644 --- a/php/class-sync.php +++ b/php/class-sync.php @@ -14,8 +14,8 @@ use Cloudinary\Sync\Download_Sync; use Cloudinary\Sync\Push_Sync; use Cloudinary\Sync\Sync_Queue; -use Cloudinary\Sync\Upload_Sync; use Cloudinary\Sync\Unsync; +use Cloudinary\Sync\Upload_Sync; use WP_Error; /** @@ -330,11 +330,14 @@ public function can_sync( $attachment_id, $type = 'file' ) { /** * Filter to allow changing if an asset is allowed to be synced. - * Return a WP Error with reason why it can't be synced. * - * @param int $attachment_id The attachment post ID. + * @hook cloudinary_can_sync_asset + * + * @param $can {bool} Can sync. + * @param $attachment_id {int} The attachment post ID. + * @param $type {string} The type of sync to attempt. * - * @return bool|\WP_Error + * @return {bool} */ return apply_filters( 'cloudinary_can_sync_asset', $can, $attachment_id, $type ); } @@ -657,9 +660,11 @@ public function setup_sync_base_struct() { /** * Filter the sync base structure to allow other plugins to sync component callbacks. * - * @param array $base_struct The base sync structure. + * @hook cloudinary_sync_base_struct * - * @return array + * @param $base_struct {array} The base sync structure. + * + * @return {array} */ $base_struct = apply_filters( 'cloudinary_sync_base_struct', $base_struct ); @@ -671,7 +676,9 @@ public function setup_sync_base_struct() { /** * Do action for setting up sync types. * - * @param \Cloudinary\Sync $this The sync object. + * @hook cloudinary_register_sync_types + * + * @param $this {Sync} The sync object. */ do_action( 'cloudinary_register_sync_types', $this ); } @@ -940,6 +947,17 @@ public function filter_status( $status, $attachment_id ) { */ public function filter_media_states( $media_states, $post ) { + /** + * Filter the Cloudinary media status. + * + * @hook cloudinary_media_status + * @default array() + * + * @param $status {array} The media status. + * @param $post_id {int} The Post ID. + * + * @return {array} + */ $status = apply_filters( 'cloudinary_media_status', array(), $post->ID ); if ( ! empty( $status ) ) { $media_states[] = $status['note']; diff --git a/php/delivery/class-bypass.php b/php/delivery/class-bypass.php index 470665106..97f7ed78f 100644 --- a/php/delivery/class-bypass.php +++ b/php/delivery/class-bypass.php @@ -122,7 +122,13 @@ public function handle_bulk_actions( $location, $action, $post_ids ) { } $this->set_attachment_delivery( $id, $delivery ); } - // This action is documented in `class-delivery.php`. + + /** + * Action to flush delivery caches. + * + * @hook cloudinary_flush_cache + * @since 3.0.0 + */ do_action( 'cloudinary_flush_cache' ); } diff --git a/php/delivery/class-lazy-load.php b/php/delivery/class-lazy-load.php index 8624279b7..560c10eda 100644 --- a/php/delivery/class-lazy-load.php +++ b/php/delivery/class-lazy-load.php @@ -257,6 +257,8 @@ public function add_features( $tag_element ) { * @since 3.0.0 * * @param $formats {array) The list of formats to exclude. + * + * @return {array} */ apply_filters( 'cloudinary_lazy_load_bypass_formats', array( 'svg' ) ), true diff --git a/php/media/class-gallery.php b/php/media/class-gallery.php index 1d91a4e75..9ee8d5dcc 100644 --- a/php/media/class-gallery.php +++ b/php/media/class-gallery.php @@ -142,7 +142,12 @@ public function get_config() { /** * Filter the gallery HTML container. * - * @param string $selector The target HTML selector. + * @hook cloudinary_gallery_html_container + * @default '' + * + * @param $selector {string} The target HTML selector. + * + * @return {string} */ $config['container'] = apply_filters( 'cloudinary_gallery_html_container', '' ); @@ -160,7 +165,11 @@ public function get_config() { /** * Filter the gallery configuration. * - * @param array $config The current gallery config. + * @hook cloudinary_gallery_config + * + * @param $config {array} The current gallery config. + * + * @return {array} */ $config = apply_filters( 'cloudinary_gallery_config', $config ); diff --git a/php/sync/class-push-sync.php b/php/sync/class-push-sync.php index 4d0d085f3..104edbde3 100644 --- a/php/sync/class-push-sync.php +++ b/php/sync/class-push-sync.php @@ -273,7 +273,14 @@ public function process_queue( \WP_REST_Request $request ) { // translators: variable is thread name and asset ID. $action_message = sprintf( __( '%1$s - cycle %3$s: Syncing asset %2$d', 'cloudinary' ), $thread, $attachment_id, $runs ); - do_action( '_cloudinary_queue_action', $action_message, $thread ); + /** + * Do action on queue action. + * + * @hook cloudinary_queue_action + * + * @param $action_message {string} The message. + */ + do_action( 'cloudinary_queue_action', $action_message ); $this->process_assets( $attachment_id ); $runs ++; $last_id = $attachment_id; @@ -283,6 +290,14 @@ public function process_queue( \WP_REST_Request $request ) { // translators: variable is thread name. $action_message = sprintf( __( 'Ending thread %s', 'cloudinary' ), $thread ); - do_action( '_cloudinary_queue_action', $action_message, $thread ); + /** + * Do action on queue action. + * + * @hook cloudinary_queue_action + * + * @param $action_message {string} The message. + * @param $thread {string} The thread. + */ + do_action( 'cloudinary_queue_action', $action_message, $thread ); } } diff --git a/php/sync/class-sync-queue.php b/php/sync/class-sync-queue.php index 220b9b7c5..33f23ad67 100644 --- a/php/sync/class-sync-queue.php +++ b/php/sync/class-sync-queue.php @@ -90,8 +90,28 @@ class Sync_Queue { * @param \Cloudinary\Plugin $plugin The plugin. */ public function __construct( \Cloudinary\Plugin $plugin ) { - $this->plugin = $plugin; - $this->cron_frequency = apply_filters( 'cloudinary_cron_frequency', 10 * MINUTE_IN_SECONDS ); + $this->plugin = $plugin; + /** + * Filter the cron job frequency. + * + * @hook cloudinary_cron_frequency + * @default 600 + * + * @param $time {int} The filtered time. + * + * @return {int} + */ + $this->cron_frequency = apply_filters( 'cloudinary_cron_frequency', 10 * MINUTE_IN_SECONDS ); + /** + * Filter the cron start offset. + * + * @hook cloudinary_cron_start_offset + * @default 60 + * + * @param $time {int} The filtered time. + * + * @return {int} + */ $this->cron_start_offset = apply_filters( 'cloudinary_cron_start_offset', MINUTE_IN_SECONDS ); $this->load_hooks(); } @@ -107,7 +127,8 @@ public function setup( $sync ) { /** * Filter the amount of background threads to process for manual syncing. * - * @hook cloudinary_queue_threads + * @hook cloudinary_queue_threads + * @default 2 * * @param $count {int} The number of manual sync threads to use. * @@ -121,7 +142,8 @@ public function setup( $sync ) { /** * Filter the amount of background threads to process for auto syncing. * - * @hook cloudinary_autosync_threads + * @hook cloudinary_autosync_threads + * @default 2 * * @param $count {int} The number of autosync threads to use. * @@ -265,7 +287,15 @@ public function get_post( $thread ) { $thread_queue = $this->get_thread_queue( $thread ); // translators: variable is thread name and queue size. $action_message = sprintf( __( '%1$s : Queue size : %2$s.', 'cloudinary' ), $thread, $thread_queue['count'] ); - do_action( '_cloudinary_queue_action', $action_message, $thread ); + /** + * Do action on queue action. + * + * @hook cloudinary_queue_action + * + * @param $action_message {string} The message. + * @param $thread {string} The thread. + */ + do_action( 'cloudinary_queue_action', $action_message, $thread ); if ( empty( $thread_queue['next'] ) ) { // Nothing left to sync. return $return; @@ -489,13 +519,27 @@ public function build_queue() { // translators: variable is page number. $action_message = __( 'Building Queue.', 'cloudinary' ); - do_action( '_cloudinary_queue_action', $action_message ); + /** + * Do action on queue action. + * + * @hook cloudinary_queue_action + * + * @param $action_message {string} The message. + */ + do_action( 'cloudinary_queue_action', $action_message ); $query = new \WP_Query( $args ); if ( ! $query->have_posts() ) { // translators: variable is page number. $action_message = __( 'No posts', 'cloudinary' ); - do_action( '_cloudinary_queue_action', $action_message ); + /** + * Do action on queue action. + * + * @hook cloudinary_queue_action + * + * @param $action_message {string} The message. + */ + do_action( 'cloudinary_queue_action', $action_message ); return; } @@ -560,7 +604,14 @@ public function stop_queue( $type = 'queue' ) { // translators: variable is queue type. $action_message = sprintf( __( 'Stopping queue: %s.', 'cloudinary' ), $type ); - do_action( '_cloudinary_queue_action', $action_message ); + /** + * Do action on queue action. + * + * @hook cloudinary_queue_action + * + * @param $action_message {string} The message. + */ + do_action( 'cloudinary_queue_action', $action_message ); if ( 'queue' === $type ) { delete_post_meta_by_key( Sync::META_KEYS['queued'] ); } else { @@ -599,7 +650,14 @@ public function start_queue( $type = 'queue' ) { } else { // translators: variable is queue type. $action_message = sprintf( __( 'Queue: %s - not running.', 'cloudinary' ), $type ); - do_action( '_cloudinary_queue_action', $action_message ); + /** + * Do action on queue action. + * + * @hook cloudinary_queue_action + * + * @param $action_message {string} The message. + */ + do_action( 'cloudinary_queue_action', $action_message ); } return $started; @@ -652,6 +710,15 @@ public function start_thread( $thread ) { // translators: variable is thread name. $action_message = sprintf( __( 'Starting thread %s.', 'cloudinary' ), $thread ); do_action( '_cloudinary_queue_action', $action_message, $thread ); + /** + * Do action on queue action. + * + * @hook cloudinary_queue_action + * + * @param $action_message {string} The message. + * @param $thread {string} The thread. + */ + do_action( 'cloudinary_queue_action', $action_message, $thread ); $this->plugin->components['api']->background_request( 'queue', array( 'thread' => $thread ) ); $sync_state = 2; // Set as started. } @@ -904,7 +971,12 @@ public function get_thread_state( $thread ) { */ public function maybe_resume_queue() { - do_action( '_cloudinary_queue_action', __( 'Resuming Maybe', 'cloudinary' ) ); + /** + * Do action on queue action. + * + * @hook cloudinary_queue_action + */ + do_action( 'cloudinary_queue_action', __( 'Resuming Maybe', 'cloudinary' ) ); $stopped = array(); if ( $this->is_running() ) { // Check each thread. @@ -914,7 +986,15 @@ public function maybe_resume_queue() { $stopped[] = $thread; // translators: variable is thread name. $action_message = sprintf( __( 'Thread %s Stopped.', 'cloudinary' ), $thread ); - do_action( '_cloudinary_queue_action', $action_message, $thread ); + /** + * Do action on queue action. + * + * @hook cloudinary_queue_action + * + * @param $action_message {string} The message. + * @param $thread {string} The thread. + */ + do_action( 'cloudinary_queue_action', $action_message, $thread ); } } diff --git a/php/sync/class-unsync.php b/php/sync/class-unsync.php index 3150b6c55..cf6ea3103 100644 --- a/php/sync/class-unsync.php +++ b/php/sync/class-unsync.php @@ -145,7 +145,12 @@ public function handle_bulk_actions( $location, $action, $post_ids ) { } - // This action is documented in `class-delivery.php`. + /** + * Action to flush delivery caches. + * + * @hook cloudinary_flush_cache + * @since 3.0.0 + */ do_action( 'cloudinary_flush_cache' ); } diff --git a/php/ui/class-component.php b/php/ui/class-component.php index 2af822f14..80625eae3 100644 --- a/php/ui/class-component.php +++ b/php/ui/class-component.php @@ -267,12 +267,14 @@ protected function setup_component_parts() { /** * Filter the components build parts. * - * @param array $build_parts The build parts. - * @param self $type The component object. + * @hook cloudinary_setup_component_parts * - * @return array + * @param $build_parts {array} The build parts. + * @param $this {self} The component object. + * + * @return {array} */ - $structs = apply_filters( 'setup_component_parts', $build_parts, $this ); + $structs = apply_filters( 'cloudinary_setup_component_parts', $build_parts, $this ); foreach ( $structs as $name => $struct ) { $struct['attributes']['class'][] = 'cld-ui-' . $name; $this->register_component_part( $name, $struct ); diff --git a/ui-definitions/settings-image.php b/ui-definitions/settings-image.php index 9caff0457..121d21479 100644 --- a/ui-definitions/settings-image.php +++ b/ui-definitions/settings-image.php @@ -268,4 +268,13 @@ ), ); +/** + * Filter the Cloudinary global transformations tab for images. + * + * @hook cloudinary_admin_image_settings + * + * @param $settings {array} The global transformations settings. + * + * @return {array} + */ return apply_filters( 'cloudinary_admin_image_settings', $settings ); diff --git a/ui-definitions/settings-pages.php b/ui-definitions/settings-pages.php index 3ca4d6245..af2d06fde 100644 --- a/ui-definitions/settings-pages.php +++ b/ui-definitions/settings-pages.php @@ -560,4 +560,13 @@ ), ); +/** + * Filter the Cloudinary admin pages. + * + * @hook cloudinary_admin_pages + * + * @param $settings {array} The admin pages settings. + * + * @return {array} + */ return apply_filters( 'cloudinary_admin_pages', $settings ); diff --git a/ui-definitions/settings-video.php b/ui-definitions/settings-video.php index 1154157e6..4ac8bef7c 100644 --- a/ui-definitions/settings-video.php +++ b/ui-definitions/settings-video.php @@ -317,4 +317,13 @@ ), ); +/** + * Filter the Cloudinary global transformations tab for video. + * + * @hook cloudinary_admin_video_settings + * + * @param $settings {array} The global transformations settings. + * + * @return {array} + */ return apply_filters( 'cloudinary_admin_video_settings', $settings );