Skip to content

Conversation

@ricardoapaes
Copy link
Contributor

@ricardoapaes ricardoapaes commented Nov 12, 2025

User description

This Pull Request introduces support for the Zoho Cliq notification service, allowing users to send messages to Cliq channels through this library.

Key Changes:

New Notifier Implementation: A new service class for Zoho Cliq has been added, following the repository's notification pattern and data structure.

JWT Token Generation: To meet Cliq's authentication requirement, a new utility class named Token (or similar) was created to encapsulate the JSON Web Token (JWT) Generation logic.

Data Structure: Mapping of necessary parameters (such as the WebHook URL and Access Token) for the new notifier's configuration.

Note on Development Dependencies:

To ensure successful composer install, it was necessary to temporarily remove the guanguans/ai-commit package from require-dev. This package was causing dependency conflicts with other project requirements, preventing a complete installation. Further investigation or updating the version of ai-commit is suggested to reintroduce it without conflicts.


PR Type

Enhancement


Description

  • Add Zoho Cliq notification service with full integration support

  • Implement OAuth token generation for Zoho Cliq authentication

  • Create message structure supporting text, cards, buttons, and bot customization

  • Remove conflicting guanguans/ai-commit dependency from require-dev

  • Update documentation and metadata to include ZohoCliq service


Diagram Walkthrough

flowchart LR
  A["Authenticator<br/>URI + Bearer Auth"] -- "generates" --> B["Token<br/>OAuth v2"]
  A -- "initializes" --> C["Client<br/>HTTP Base"]
  C -- "sends" --> D["Message<br/>Text/Card/Bot"]
  D -- "posts to" --> E["Zoho Cliq API<br/>company/channel/message"]
Loading

File Walkthrough

Relevant files
Enhancement
Authenticator.php
Zoho Cliq authentication with OAuth support                           

src/ZohoCliq/Authenticator.php

  • Implements AggregateAuthenticator combining URI template and bearer
    authentication
  • Accepts company ID, channel unique name, and optional auth token
    parameters
  • Provides static method generateToken() to create OAuth tokens via
    Token class
+48/-0   
Client.php
HTTP client for Zoho Cliq API                                                       

src/ZohoCliq/Client.php

  • Extends foundation Client class for Zoho Cliq API communication
  • Sets base URI to https://cliq.zoho.com/
  • Accepts Authenticator instance for request authentication
+27/-0   
Message.php
Message structure for Zoho Cliq notifications                       

src/ZohoCliq/Messages/Message.php

  • Extends foundation Message class with Zoho Cliq-specific fields
  • Supports text, bot customization, cards, slides, buttons, and styles
  • Defines HTTP URI endpoint for posting messages to channels
  • Includes type validation for complex message properties
+46/-0   
Token.php
OAuth token generation utility                                                     

src/ZohoCliq/Token.php

  • Implements OAuth v2 token generation for Zoho Cliq authentication
  • Exchanges client credentials for access tokens via Zoho accounts API
  • Handles JSON response parsing and token extraction
  • Uses Guzzle HTTP client for API communication
+49/-0   
Tests
ClientTest.php
Test suite for Zoho Cliq client                                                   

tests/ZohoCliq/ClientTest.php

  • Provides three test cases covering basic message sending
  • Tests message with bot customization (name and image)
  • Tests message with card, buttons, and action URLs
  • Uses mocking framework for isolated testing
+90/-0   
Documentation
README.md
Update documentation with ZohoCliq service                             

README.md

  • Adds ZohoCliq to the list of supported notification services
  • Updates service list in both header note and services section
  • Adds link to ZohoCliq service documentation
+2/-1     
README.md
Add ZohoCliq service documentation                                             

src/ZohoCliq/README.md

  • Creates service-specific documentation file
  • References usage example from test file
  • Provides links to Zoho Cliq API documentation and webhook resources
+9/-0     
Configuration changes
composer.json
Update package metadata and dependencies                                 

composer.json

  • Adds ZohoCliq to package description and keywords list
  • Removes guanguans/ai-commit from require-dev due to dependency
    conflicts
  • Maintains alphabetical ordering of service keywords
+2/-2     

@qodo-merge-pro
Copy link

qodo-merge-pro bot commented Nov 12, 2025

PR Compliance Guide 🔍

(Compliance updated until commit d9ddccf)

Below is a summary of compliance checks for this PR:

Security Compliance
Inadequate response validation

Description: The OAuth token generation blindly trusts the remote response and returns
$json['access_token'] without validating HTTP status codes or handling missing/invalid
JSON keys, enabling silent failures and potential misuse if the endpoint errors or returns
unexpected content.
Token.php [31-48]

Referred Code
    $response = (new Client)->request(
        'POST',
        'https://accounts.zoho.com/oauth/v2/token',
        [
            'form_params' => [
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret,
                'grant_type' => 'client_credentials',
                'scope' => 'ZohoCliq.Webhooks.CREATE',
            ],
        ]
    );

    $body = (string) $response->getBody();
    $json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);

    return $json['access_token'];
}
Hardcoded endpoint

Description: The base URL is hardcoded to https://cliq.zoho.com/, which may be incorrect for non-US
regions of Zoho and can lead to requests being sent to the wrong host or mixed-tenant data
exposure if reused across regions.
Client.php [25-26]

Referred Code
    $this->baseUri('https://cliq.zoho.com/');
}
Secret handling risk

Description: generateToken exposes a convenience method that accepts raw clientId and clientSecret
parameters, encouraging passing secrets directly through application code paths rather
than secure secret storage, increasing risk of accidental exposure in logs or version
control.
Authenticator.php [42-47]

Referred Code
public static function generateToken(string $clientId, string $clientSecret): string
{
    $token = new Token($clientId, $clientSecret);

    return $token->generateToken();
}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Unhandled errors: Network/request failures and JSON parsing errors are not handled, and missing
'access_token' is not validated, leading to potential uncaught exceptions or
notices.

Referred Code
    $response = (new Client)->request(
        'POST',
        'https://accounts.zoho.com/oauth/v2/token',
        [
            'form_params' => [
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret,
                'grant_type' => 'client_credentials',
                'scope' => 'ZohoCliq.Webhooks.CREATE',
            ],
        ]
    );

    $body = (string) $response->getBody();
    $json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);

    return $json['access_token'];
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Missing validation: The token flow does not validate or sanitize external responses nor verify presence/type
of 'access_token', and lacks timeouts/retry/scope validation for secure
handling.

Referred Code
    $response = (new Client)->request(
        'POST',
        'https://accounts.zoho.com/oauth/v2/token',
        [
            'form_params' => [
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret,
                'grant_type' => 'client_credentials',
                'scope' => 'ZohoCliq.Webhooks.CREATE',
            ],
        ]
    );

    $body = (string) $response->getBody();
    $json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);

    return $json['access_token'];
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing auditing: The new OAuth token generation and message send pathways do not include any audit logging
of critical actions such as token requests or message dispatch attempts.

Referred Code
public function generateToken(): string
{
    $response = (new Client)->request(
        'POST',
        'https://accounts.zoho.com/oauth/v2/token',
        [
            'form_params' => [
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret,
                'grant_type' => 'client_credentials',
                'scope' => 'ZohoCliq.Webhooks.CREATE',
            ],
        ]
    );

    $body = (string) $response->getBody();
    $json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);

    return $json['access_token'];
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Error exposure risk: Without controlled exception handling, HTTP or JSON errors may bubble up and expose
internal details to callers if not sanitized at higher layers.

Referred Code
    $response = (new Client)->request(
        'POST',
        'https://accounts.zoho.com/oauth/v2/token',
        [
            'form_params' => [
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret,
                'grant_type' => 'client_credentials',
                'scope' => 'ZohoCliq.Webhooks.CREATE',
            ],
        ]
    );

    $body = (string) $response->getBody();
    $json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);

    return $json['access_token'];
}

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Previous compliance checks

Compliance check up to commit d9ddccf
Security Compliance
Error handling and endpoint rigidity

Description: The token generation assumes JSON decoding always succeeds and that 'access_token' exists,
which can cause unhandled exceptions or notices and potentially expose secrets via
unhandled error paths; also the Zoho accounts base URL is hardcoded to 'accounts.zoho.com'
which may be incorrect for other regions, leading to misconfiguration risks.
Token.php [31-48]

Referred Code
    $response = (new Client)->request(
        'POST',
        'https://accounts.zoho.com/oauth/v2/token',
        [
            'form_params' => [
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret,
                'grant_type' => 'client_credentials',
                'scope' => 'ZohoCliq.Webhooks.CREATE',
            ],
        ]
    );

    $body = (string) $response->getBody();
    $json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);

    return $json['access_token'];
}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Fragile error handling: The code assumes a successful response and existing 'access_token' without
validating HTTP status, handling network/JSON errors, or missing keys, leading to
potential unhandled exceptions or notices.

Referred Code
$response = (new Client)->request(
    'POST',
    'https://accounts.zoho.com/oauth/v2/token',
    [
        'form_params' => [
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret,
            'grant_type' => 'client_credentials',
            'scope' => 'ZohoCliq.Webhooks.CREATE',
        ],
    ]
);

$body = (string) $response->getBody();
$json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);

return $json['access_token'];

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
No auditing: The new token generation and message sending paths add critical external calls without any
explicit audit logging of actions or outcomes in the added code.

Referred Code
public function generateToken(): string
{
    $response = (new Client)->request(
        'POST',
        'https://accounts.zoho.com/oauth/v2/token',
        [
            'form_params' => [
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret,
                'grant_type' => 'client_credentials',
                'scope' => 'ZohoCliq.Webhooks.CREATE',
            ],
        ]
    );

    $body = (string) $response->getBody();
    $json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);

    return $json['access_token'];
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Exception exposure: JSON decoding with JSON_THROW_ON_ERROR and lack of guarded HTTP handling may bubble raw
exceptions that could expose internal details if not caught by higher layers.

Referred Code
$body = (string) $response->getBody();
$json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Input validation: The new authenticators interpolate companyId and channelUniqueName into a URI template
without visible validation or normalization in this diff, relying on upstream mechanisms.

Referred Code
public function __construct(
    string $companyId,
    string $channelUniqueName,
    #[\SensitiveParameter]
    ?string $authToken = null
) {
    $authenticators = [
        new UriTemplateAuthenticator([
            'companyId' => $companyId,
            'channelUniqueName' => $channelUniqueName,
        ]),
    ];

    if (null !== $authToken) {
        $authenticators[] = new BearerAuthenticator($authToken);
    }

Learn more about managing compliance generic rules or creating your own custom rules

@qodo-merge-pro
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Decouple token generation from Guzzle

Refactor the Token class to use the library's shared HTTP client instead of
directly instantiating GuzzleHttp\Client. This change will improve testability
and ensure consistent HTTP configuration.

Examples:

src/ZohoCliq/Token.php [31-42]
        $response = (new Client)->request(
            'POST',
            'https://accounts.zoho.com/oauth/v2/token',
            [
                'form_params' => [
                    'client_id' => $this->clientId,
                    'client_secret' => $this->clientSecret,
                    'grant_type' => 'client_credentials',
                    'scope' => 'ZohoCliq.Webhooks.CREATE',
                ],

 ... (clipped 2 lines)

Solution Walkthrough:

Before:

// src/ZohoCliq/Token.php
use GuzzleHttp\Client;

class Token
{
    // ...
    public function generateToken(): string
    {
        $response = (new Client)->request(
            'POST',
            'https://accounts.zoho.com/oauth/v2/token',
            [...]
        );
        // ...
        return $json['access_token'];
    }
}

After:

// src/ZohoCliq/Token.php
use GuzzleHttp\ClientInterface;
use Guanguans\Notify\Foundation\Concerns\HasHttpClient;

class Token
{
    use HasHttpClient;
    // ...
    public function generateToken(): string
    {
        $response = $this->getHttpClient()->request(
            'POST',
            'https://accounts.zoho.com/oauth/v2/token',
            [...]
        );
        // ...
        return $json['access_token'];
    }
}
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a significant design flaw where the Token class is tightly coupled to a direct GuzzleHttp\Client instantiation, which undermines testability and consistency with the library's HTTP client abstraction.

Medium
Possible issue
Add error handling for token generation

Add a check to ensure access_token exists in the API response when generating a
Zoho Cliq token, and throw an exception if it is missing.

src/ZohoCliq/Token.php [44-47]

 $body = (string) $response->getBody();
 $json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);
 
+if (empty($json['access_token'])) {
+    throw new \InvalidArgumentException('Invalid response from Zoho Cliq API: '.(string) $body);
+}
+
 return $json['access_token'];
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential unhandled error case where the API returns a success status but no access_token, improving the robustness of the token generation logic.

Medium
  • More

@guanguans guanguans requested a review from Copilot November 12, 2025 11:44
Copilot finished reviewing on behalf of guanguans November 12, 2025 11:46
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request adds support for the Zoho Cliq notification service to the notification SDK library. The implementation follows the existing patterns established in the codebase by extending foundation classes and providing OAuth-based authentication.

  • Implements Zoho Cliq notification service with OAuth 2.0 token generation
  • Adds support for rich message features including cards, buttons, and bot customization
  • Removes conflicting guanguans/ai-commit development dependency

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/ZohoCliq/Authenticator.php Implements authentication using URI template and bearer token patterns with OAuth token generation helper
src/ZohoCliq/Client.php HTTP client for Zoho Cliq API with base URI configuration
src/ZohoCliq/Messages/Message.php Message structure supporting text, cards, buttons, slides, and bot customization
src/ZohoCliq/Token.php OAuth 2.0 token generation utility for client credentials flow
tests/ZohoCliq/ClientTest.php Test suite covering basic messages, bot customization, and card-based notifications
src/ZohoCliq/README.md Service-specific documentation with API reference links
README.md Updated main documentation to include ZohoCliq in supported services list
composer.json Added ZohoCliq to package metadata and removed conflicting ai-commit dependency

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

private string $clientId;
private string $clientSecret;

public function __construct(string $clientId, string $clientSecret)
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The $clientSecret parameter should be marked with #[\SensitiveParameter] attribute to prevent accidental exposure in stack traces and logs, similar to the pattern used in Authenticator::__construct() and other authenticators in the codebase.

Suggested change
public function __construct(string $clientId, string $clientSecret)
public function __construct(string $clientId, #[\SensitiveParameter] string $clientSecret)

Copilot uses AI. Check for mistakes.
parent::__construct(...$authenticators);
}

public static function generateToken(string $clientId, string $clientSecret): string
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The $clientSecret parameter should be marked with #[\SensitiveParameter] attribute to prevent accidental exposure in stack traces and logs, consistent with the security practices used elsewhere in the codebase.

Suggested change
public static function generateToken(string $clientId, string $clientSecret): string
public static function generateToken(string $clientId, #[\SensitiveParameter] string $clientSecret): string

Copilot uses AI. Check for mistakes.

$body = (string) $response->getBody();
$json = json_decode($body ?: '[]', true, 512, \JSON_THROW_ON_ERROR);

Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The access_token key may not exist in the API response if authentication fails or an error occurs. This will cause an undefined array key error. Consider adding error handling to check if the key exists or handle potential API error responses.

Example:

if (!isset($json['access_token'])) {
    throw new \RuntimeException('Failed to retrieve access token from Zoho API response');
}
return $json['access_token'];
Suggested change
if (!isset($json['access_token'])) {
throw new \RuntimeException('Failed to retrieve access token from Zoho API response: ' . $body);
}

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Nov 12, 2025

Codecov Report

❌ Patch coverage is 44.11765% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.69%. Comparing base (3b2e148) to head (d9ddccf).
⚠️ Report is 16 commits behind head on main.

Files with missing lines Patch % Lines
src/ZohoCliq/Token.php 0.00% 16 Missing ⚠️
src/ZohoCliq/Authenticator.php 76.92% 3 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##                main     #173      +/-   ##
=============================================
- Coverage     100.00%   98.69%   -1.31%     
- Complexity       377      385       +8     
=============================================
  Files            146      150       +4     
  Lines           1443     1455      +12     
=============================================
- Hits            1443     1436       -7     
- Misses             0       19      +19     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@guanguans
Copy link
Owner

@ricardoapaes thank you.

@guanguans guanguans merged commit 8444b30 into guanguans:main Nov 12, 2025
16 of 21 checks passed
@guanguans
Copy link
Owner

guanguans commented Nov 14, 2025

@ricardoapaes

The 3.4.0 version was re-released, and it was temporarily changed to support only webhook. If you have any good ideas, you can tell me.

Related details link:

GitHub
3.4.0 - 2025-11-14 💅 Code Refactorings

rectors: Rename HasOptionsDocCommentRector to HasOptionsRector and improve options (952d176)
zoho: Simplify Authenticator and enhance Message class (cc76977)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants