├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── PULL_REQUEST_TEMPLATE.md │ ├── bug_report.md │ ├── feature_request.md │ └── security-issue.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── assets ├── css │ └── style.css ├── js │ └── script.js └── laravel-cookie-consent.png ├── composer.json ├── example.blade.php ├── resources └── views │ └── cookie-consent.blade.php ├── routes └── laravel-cookie-consent.php ├── src ├── CookieConsent.php ├── CookieConsentServiceProvider.php ├── Facades │ └── CookieConsent.php ├── Http │ └── Controllers │ │ └── CookieConsentController.php └── config │ └── laravel-cookie-consent.php └── vendor ├── autoload.php └── composer ├── ClassLoader.php ├── LICENSE ├── autoload_classmap.php ├── autoload_namespaces.php ├── autoload_psr4.php ├── autoload_real.php └── autoload_static.php /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | Please provide a brief summary of the changes. 3 | 4 | ## Related Issue 5 | Link to any related issues (e.g., `Fixes #123`). 6 | 7 | ## Changes Made 8 | - [ ] Feature added 9 | - [ ] Bug fixed 10 | - [ ] Documentation updated 11 | - [ ] Tests added 12 | 13 | ## Screenshots (if applicable) 14 | Attach any relevant screenshots. 15 | 16 | ## Checklist 17 | - [ ] My code follows the project coding style. 18 | - [ ] I have tested my changes and verified they work as expected. 19 | - [ ] I have added necessary documentation (if applicable). 20 | 21 | --- 22 | 23 | Thank you for contributing! 🚀 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug to help us improve Laravel Cookie Consent 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | ## Steps to Reproduce 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '...' 17 | 3. Scroll down to '...' 18 | 4. See error 19 | 20 | ## Expected behavior 21 | A clear and concise description of what you expected to happen. 22 | 23 | ## Screenshots 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | ## Environment 27 | - Laravel Cookie Consent version: [e.g., 1.0.0] 28 | - Laravel version: [e.g., 10.x] 29 | - PHP version: [e.g., 8.2] 30 | - Operating System: [e.g., Ubuntu 22.04] 31 | 32 | ## Additional context 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea or enhancement for Laravel Cookie Consent 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Is your feature request related to a problem? Please describe. 11 | A clear and concise description of what the problem is. Ex: I'm always frustrated when [...] 12 | 13 | ## Describe the solution you'd like 14 | A clear and concise description of what you want to happen. 15 | 16 | ## Describe alternatives you've considered 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | ## Additional context 20 | Add any other context, screenshots, or mockups about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security Issue 3 | about: Report a security vulnerability confidentially 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ⚠️ **DO NOT DISCLOSE SECURITY VULNERABILITIES PUBLICLY** ⚠️ 11 | 12 | ## Issue Details 13 | - Affected version(s): 14 | - Description of the security issue: 15 | - Steps to reproduce: 16 | 17 | ## Potential Impact 18 | A clear and concise description of the potential impact this vulnerability may have. 19 | 20 | ## Suggested Fix (Optional) 21 | If you have any ideas on how to fix it, describe them here. 22 | 23 | ## Contact 24 | We will review your report and respond as soon as possible. If necessary, we may request additional details. Please provide a way to contact you (email or GitHub handle). 25 | 26 | Thank you for reporting responsibly! 🔒 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vscode 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | devrabiul@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Laravel Cookie Consent 2 | 3 | Thank you for considering contributing to Laravel Cookie Consent! Your contributions help improve the package and make it more useful for the Laravel community. 4 | 5 | ## How to Contribute 6 | 7 | ### 1. Fork and Clone the Repository 8 | First, fork the repository on GitHub and then clone it to your local machine: 9 | ```sh 10 | git clone https://github.com/your-username/laravel-cookie-consent.git 11 | cd laravel-cookie-consent 12 | ``` 13 | 14 | ### 2. Install Dependencies 15 | Ensure you have PHP and Composer installed. Then, install dependencies: 16 | ```sh 17 | composer install 18 | ``` 19 | 20 | ### 3. Create a New Branch 21 | Before making changes, create a new branch for your feature or bugfix: 22 | ```sh 23 | git checkout -b feature-or-fix-name 24 | ``` 25 | 26 | ### 4. Make Your Changes 27 | - Follow PSR-4 and PSR-12 coding standards. 28 | - Write clear, maintainable, and well-documented code. 29 | - Update or add tests if necessary. 30 | 31 | ### 5. Run Tests 32 | Ensure all tests pass before submitting your contribution: 33 | ```sh 34 | vendor/bin/pest 35 | ``` 36 | 37 | ### 6. Commit Your Changes 38 | Write meaningful commit messages following best practices: 39 | ```sh 40 | git add . 41 | git commit -m "Add feature/fix description" 42 | ``` 43 | 44 | ### 7. Push to GitHub and Create a Pull Request 45 | Push your branch to GitHub: 46 | ```sh 47 | git push origin feature-or-fix-name 48 | ``` 49 | Then, create a pull request (PR) from your branch to the `main` branch in the original repository. 50 | 51 | ### 8. Code Review and Merging 52 | - The maintainers will review your PR and may request changes. 53 | - Once approved, it will be merged into the main branch. 54 | 55 | ## Reporting Issues 56 | If you encounter any issues, please create a [GitHub issue](https://github.com/devrabiul/laravel-cookie-consent/issues) with a clear description of the problem and steps to reproduce it. 57 | 58 | ## Code of Conduct 59 | By participating in this project, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md). 60 | 61 | Thank you for contributing! 🚀 62 | 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 DevRabiul 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Cookie Consent 2 | 3 | A GDPR-compliant solution offering enterprise-grade compliance with fully customizable cookie banners for Laravel 4 | applications. Simplifies regulatory requirements while maintaining excellent user experience and complete customization 5 | capabilities. 6 | 7 | [![Latest Stable Version](https://poser.pugx.org/devrabiul/laravel-cookie-consent/v/stable)](https://packagist.org/packages/devrabiul/laravel-cookie-consent) 8 | [![Total Downloads](https://poser.pugx.org/devrabiul/laravel-cookie-consent/downloads)](https://packagist.org/packages/devrabiul/laravel-cookie-consent) 9 | ![GitHub license](https://img.shields.io/github/license/devrabiul/laravel-cookie-consent) 10 | ![GitHub Repo stars](https://img.shields.io/github/stars/devrabiul/laravel-cookie-consent?style=social) 11 | 12 | ## Features 13 | 14 | - 🔥 **One-Click Implementation** – Simple installation via Composer with auto-loaded assets 15 | - ⚡ **Zero Performance Impact** – Lightweight with lazy-loaded components 16 | - 🌍 **RTL & i18n Support** – Full right-to-left compatibility + multilingual translations 17 | - 🌙 **Dark Mode Support** – Auto dark/light mode matching system preferences 18 | - 🛡 **Granular Consent Control** – Category-level cookie management (necessary/analytics/marketing) 19 | - 📦 **Complete Customization** – Override every color, text, and layout via config 20 | - 📱 **Responsive Design** – Perfectly adapts to all devices (mobile/tablet/desktop) 21 | - 🧩 **No Frontend Dependencies** – No jQuery, Bootstrap, or Tailwind required — works everywhere effortlessly 22 | 23 | 24 | ## Installation 25 | 26 | To get started with Cookie Consent, follow these simple steps: 27 | 28 | 1. Install the package via Composer: 29 | 30 | ```bash 31 | composer require devrabiul/laravel-cookie-consent 32 | ``` 33 | 34 | 2. Publish the package resources by running: (Normal publish) 35 | 36 | ```bash 37 | php artisan vendor:publish --provider="Devrabiul\CookieConsent\CookieConsentServiceProvider" 38 | ``` 39 | 40 | ## Basic Usage 41 | 42 | Include these components in your Blade templates: 43 | 44 | 1. Add styles in the `` section: 45 | 46 | ```php 47 | {!! CookieConsent::styles() !!} 48 | ``` 49 | 50 | 2. Add scripts before closing ``: 51 | 52 | ```php 53 | {!! CookieConsent::scripts() !!} 54 | ``` 55 | 56 | ### Complete Example 57 | 58 | ```php 59 | 60 | 61 | 62 | 63 | 64 | Your Page 65 | {!! CookieConsent::styles() !!} 66 | 67 | 68 | 69 | 70 | 71 | {!! CookieConsent::scripts() !!} 72 | 73 | 74 | ``` 75 | 76 | ## Advanced Configuration 77 | 78 | ```php 79 | 80 | 81 | 82 | 83 | 84 | Your Page 85 | {!! CookieConsent::styles() !!} 86 | 87 | 88 | 89 | 90 | 91 | {!! CookieConsent::scripts(options: [ 92 | 'cookie_lifetime' => config('laravel-cookie-consent.cookie_lifetime', 7), 93 | 'reject_lifetime' => config('laravel-cookie-consent.reject_lifetime', 1), 94 | 'disable_page_interaction' => config('laravel-cookie-consent.disable_page_interaction', true), 95 | 'preferences_modal_enabled' => config('laravel-cookie-consent.preferences_modal_enabled', true), 96 | 'consent_modal_layout' => config('laravel-cookie-consent.consent_modal_layout', 'bar-inline'), 97 | 'flip_button' => config('laravel-cookie-consent.flip_button', true), 98 | 'theme' => config('laravel-cookie-consent.theme', 'default'), 99 | 'cookie_prefix' => config('laravel-cookie-consent.cookie_prefix', 'Laravel_App'), 100 | 'policy_links' => config('laravel-cookie-consent.policy_links', [ 101 | ['text' => CookieConsent::translate('Privacy Policy'), 'link' => url('privacy-policy')], 102 | ['text' => CookieConsent::translate('Terms & Conditions'), 'link' => url('terms-and-conditions')], 103 | ]), 104 | 'cookie_categories' => config('laravel-cookie-consent.cookie_categories', [ 105 | 'necessary' => [ 106 | 'enabled' => true, 107 | 'locked' => true, 108 | 'js_action' => 'loadGoogleAnalytics', 109 | 'title' => CookieConsent::translate('Essential Cookies'), 110 | 'description' => CookieConsent::translate('These cookies are essential for the website to function properly.'), 111 | ], 112 | 'analytics' => [ 113 | 'enabled' => env('COOKIE_CONSENT_ANALYTICS', false), 114 | 'locked' => false, 115 | 'title' => CookieConsent::translate('Analytics Cookies'), 116 | 'description' => CookieConsent::translate('These cookies help us understand how visitors interact with our website.'), 117 | ], 118 | 'marketing' => [ 119 | 'enabled' => env('COOKIE_CONSENT_MARKETING', false), 120 | 'locked' => false, 121 | 'js_action' => 'loadFacebookPixel', 122 | 'title' => CookieConsent::translate('Marketing Cookies'), 123 | 'description' => CookieConsent::translate('These cookies are used for advertising and tracking purposes.'), 124 | ], 125 | 'preferences' => [ 126 | 'enabled' => env('COOKIE_CONSENT_PREFERENCES', false), 127 | 'locked' => false, 128 | 'js_action' => 'loadPreferencesFunc', 129 | 'title' => CookieConsent::translate('Preferences Cookies'), 130 | 'description' => CookieConsent::translate('These cookies allow the website to remember user preferences.'), 131 | ], 132 | ]), 133 | 'cookie_title' => CookieConsent::translate('Cookie Disclaimer'), 134 | 'cookie_description' => CookieConsent::translate('This website uses cookies to enhance your browsing experience, analyze site traffic, and personalize content. By continuing to use this site, you consent to our use of cookies.'), 135 | 'cookie_modal_title' => CookieConsent::translate('Cookie Preferences'), 136 | 'cookie_modal_intro' => CookieConsent::translate('You can customize your cookie preferences below.'), 137 | 'cookie_accept_btn_text' => CookieConsent::translate('Accept All'), 138 | 'cookie_reject_btn_text' => CookieConsent::translate('Reject All'), 139 | 'cookie_preferences_btn_text' => CookieConsent::translate('Manage Preferences'), 140 | 'cookie_preferences_save_text' => CookieConsent::translate('Save Preferences'), 141 | ]) !!} 142 | 143 | 144 | 145 | ``` 146 | 147 | 148 | ### 🌙 Enable Dark Mode 149 | 150 | Add `theme="dark"` to your `` tag to automatically enable dark mode. 151 | 152 | ```html 153 | 154 | ``` 155 | 156 | --- 157 | 158 | ### 🌐 Enable RTL Mode 159 | 160 | Add `dir="rtl"` to your `` tag to enable right-to-left layout for RTL languages. 161 | 162 | ```html 163 | 164 | ``` 165 | 166 | ## Layout Options 167 | 168 | ### Config Status Control 169 | 170 | ```bash 171 | COOKIE_CONSENT_ENABLED=true 172 | COOKIE_CONSENT_PREFERENCES_ENABLED=true 173 | 174 | COOKIE_CONSENT_ANALYTICS=true 175 | COOKIE_CONSENT_MARKETING=true 176 | COOKIE_CONSENT_PREFERENCES=true 177 | ``` 178 | 179 | ### Consent Modal Styles 180 | 181 | - **`box`** - Compact floating dialog 182 | - **`box-inline`** - Inline positioned box 183 | - **`box-wide`** - Expanded floating dialog 184 | - **`cloud`** - Modern floating design 185 | - **`cloud-inline`** - Compact cloud variant 186 | - **`bar`** - Top/bottom banner 187 | - **`bar-inline`** - Compact banner 188 | 189 | *Default: `box-wide`* 190 | 191 | ### Preferences Modal Styles 192 | 193 | - **`bar`** - Full-width layout 194 | - **`box`** - Centered popup 195 | 196 | *Default: `bar`* 197 | 198 | ## Configuration 199 | 200 | Edit `config/cookie-consent.php` to modify: 201 | 202 | - Cookie lifetimes 203 | - Visual styles 204 | - Text content 205 | - Category settings 206 | 207 | ### Example service loader (replace with your actual implementation) 208 | 209 | ```javascript 210 | function loadGoogleAnalytics() { 211 | // Please put your GA script in loadGoogleAnalytics() 212 | // You can define function name from - {!! CookieConsent::scripts() !!} 213 | 214 | window.dataLayer = window.dataLayer || []; 215 | 216 | function gtag() { 217 | dataLayer.push(arguments); 218 | } 219 | 220 | gtag('js', new Date()); 221 | gtag('config', 'YOUR_GA_ID'); 222 | 223 | // Load the GA script 224 | const script = document.createElement('script'); 225 | script.src = 'https://www.googletagmanager.com/gtag/js?id=YOUR_GA_ID'; 226 | script.async = true; 227 | document.head.appendChild(script); 228 | } 229 | 230 | function loadFacebookPixel() { 231 | // Please put your marketing script in loadFacebookPixel() 232 | // You can define function name from - {!! CookieConsent::scripts() !!} 233 | 234 | !function (f, b, e, v, n, t, s) { 235 | if (f.fbq) return; 236 | n = f.fbq = function () { 237 | n.callMethod ? 238 | n.callMethod.apply(n, arguments) : n.queue.push(arguments) 239 | }; 240 | if (!f._fbq) f._fbq = n; 241 | n.push = n; 242 | n.loaded = !0; 243 | n.version = '2.0'; 244 | n.queue = []; 245 | t = b.createElement(e); 246 | t.async = !0; 247 | t.src = v; 248 | s = b.getElementsByTagName(e)[0]; 249 | s.parentNode.insertBefore(t, s) 250 | }(window, document, 'script', 251 | 'https://connect.facebook.net/en_US/fbevents.js'); 252 | fbq('init', 'YOUR_PIXEL_ID'); 253 | fbq('track', 'PageView'); 254 | } 255 | ``` 256 | 257 | ### 🎯 Get Started Today! 258 | 259 | Experience the magic of CookieConsent and enhance your Laravel applications with Cookie Consent. 260 | 261 | 🔗 **GitHub:** [Laravel Cookie Consent](https://github.com/devrabiul/laravel-cookie-consent) 262 | 🔗 **Packagist: 263 | ** [https://packagist.org/packages/devrabiul/laravel-cookie-consent](https://packagist.org/packages/devrabiul/laravel-cookie-consent) 264 | 265 | ## Contributing 266 | 267 | We welcome contributions to CookieConsent! If you would like to contribute, please fork the repository and submit a pull 268 | request. For any issues or feature requests, please open an issue on GitHub. 269 | 270 | Please: 271 | 272 | 1. Fork the repository 273 | 2. Create your feature branch 274 | 3. Submit a pull request 275 | 276 | ## License 277 | 278 | This package is open-sourced software licensed under the [MIT license](LICENSE). 279 | 280 | ## Contact 281 | 282 | For support or inquiries, please reach out to us at [Send Mail](mailto:devrabiul@gmail.com). 283 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | We actively maintain the latest major version of Laravel Cookie Consent. Security updates will only be provided for the latest release. 5 | 6 | | Version | Supported | 7 | |---------|------------------| 8 | | Latest | ✅ Active Support | 9 | | Older | ❌ No Support | 10 | 11 | ## Reporting a Vulnerability 12 | If you discover a security vulnerability in Laravel Cookie Consent, please **DO NOT** disclose it publicly. Instead, report it confidentially by following these steps: 13 | 14 | 1. **Email the Maintainer**: Send a detailed report to [devrabiul@gmail.com] with the subject **[Security Issue] Laravel Cookie Consent**. 15 | 2. **Include Details**: 16 | - Affected version(s) 17 | - Steps to reproduce the issue 18 | - Potential impact 19 | - Suggested fix (if any) 20 | 3. **Await Response**: We will investigate the issue and respond as soon as possible, typically within 72 hours. 21 | 4. **Patch and Disclosure**: Once a fix is developed and tested, we will release a security update and acknowledge your responsible disclosure. 22 | 23 | ## Best Practices for Users 24 | To keep your application secure: 25 | - Always update to the latest version. 26 | - Use Composer to manage dependencies securely. 27 | - Follow Laravel's security best practices. 28 | 29 | We appreciate your help in making Laravel Cookie Consent more secure! 🔒 30 | -------------------------------------------------------------------------------- /assets/css/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --cc-card-bg-color: #FFFFFF; 3 | --cc-card-text-color: #000000; 4 | --cc-button-bg-color: #161a1d; 5 | --cc-button-bg-color-rgb: 22, 26, 29; 6 | --cc-button-bg-opacity: .8; 7 | --cc-button-text-color: #FFFFFF; 8 | --cc-modal-transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); 9 | } 10 | 11 | [theme="dark"] { 12 | --cc-card-bg-color: #161a1c; 13 | --cc-card-text-color: #f0f0f0; 14 | --cc-button-bg-color-rgb: 194, 208, 224; 15 | --cc-button-text-color: #161a1c; 16 | } 17 | 18 | .cookie-disable-interaction body { 19 | height: auto !important; 20 | overflow: hidden !important; 21 | } 22 | 23 | .cookie-disable-interaction body::before { 24 | background: rgba(0, 0, 0, .65); 25 | content: ""; 26 | inset: 0; 27 | opacity: 1; 28 | position: fixed; 29 | z-index: 900; 30 | } 31 | 32 | .cookie-consent-root { 33 | --cookie-consent-width: 100%; 34 | --cookie-consent-bottom: 20px; 35 | position: fixed; 36 | background: var(--cc-card-bg-color); 37 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); 38 | border-radius: 10px; 39 | z-index: 1000; 40 | bottom: var(--cookie-consent-bottom); 41 | inset-inline-start: 20px; 42 | inset-inline-end: 20px; 43 | max-width: var(--cookie-consent-width); 44 | overflow: hidden; 45 | transition: 1s ease-in-out all; 46 | } 47 | 48 | .consent-layout-box, 49 | .consent-layout-box-inline { 50 | --cookie-consent-width: 24rem; 51 | } 52 | 53 | .consent-layout-box-wide { 54 | --cookie-consent-width: 36rem; 55 | } 56 | 57 | .consent-layout-cloud, 58 | .consent-layout-cloud-inline { 59 | --cookie-consent-width: 54em; 60 | } 61 | 62 | .consent-layout-bar, 63 | .consent-layout-bar-inline { 64 | --cookie-consent-width: 100%; 65 | --cookie-consent-bottom: 0; 66 | inset-inline-start: 0; 67 | inset-inline-end: 0; 68 | border-radius: 0; 69 | } 70 | 71 | .cookie-consent-hide { 72 | --cookie-consent-bottom: -150%; 73 | } 74 | 75 | @media screen and (max-width: 640px) { 76 | .consent-layout-cloud, 77 | .consent-layout-cloud-inline { 78 | --cookie-consent-width: none !important; 79 | width: calc(100% - 40px) !important; 80 | } 81 | } 82 | 83 | .cookie-consent-content-container { 84 | --cc-content-container-width: 1200px; 85 | display: flex; 86 | justify-content: space-between; 87 | flex-direction: column; 88 | align-items: center; 89 | gap: 1rem; 90 | padding: 20px; 91 | max-width: var(--cc-content-container-width); 92 | margin: auto; 93 | } 94 | 95 | .consent-layout-box .cookie-consent-content-container, 96 | .consent-layout-box-inline .cookie-consent-content-container, 97 | .consent-layout-box-wide .cookie-consent-content-container, 98 | .consent-layout-bar .cookie-consent-content-container { 99 | flex-direction: column; 100 | } 101 | 102 | .cookie-consent-content { 103 | width: 100%; 104 | } 105 | 106 | .cookie-consent-content-title { 107 | font-size: 16px; 108 | font-weight: 600; 109 | color: var(--cc-card-text-color); 110 | margin: 0; 111 | padding: 0 0 .35rem 0; 112 | } 113 | 114 | .cookie-consent-content-description { 115 | font-size: 14px; 116 | color: var(--cc-card-text-color); 117 | margin: 0; 118 | padding: 0; 119 | opacity: .8; 120 | } 121 | 122 | .cookie-consent-button-container { 123 | display: flex; 124 | justify-content: center; 125 | flex-direction: column; 126 | gap: 10px; 127 | width: 100%; 128 | } 129 | 130 | .consent-layout-box .cookie-consent-button-container, 131 | .consent-layout-box .cookie-consent-button-action, 132 | .consent-layout-box-inline .cookie-consent-button-container, 133 | .consent-layout-box-inline .cookie-consent-button-action, 134 | .consent-layout-box-wide .cookie-consent-button-container, 135 | .consent-layout-bar .cookie-consent-button-container { 136 | width: 100%; 137 | } 138 | 139 | .cookie-consent-button-action { 140 | display: flex; 141 | justify-content: center; 142 | flex-direction: column; 143 | gap: 10px; 144 | } 145 | 146 | .consent-layout-box-inline .cookie-consent-button-action, 147 | .consent-layout-box-wide .cookie-consent-button-action, 148 | .consent-layout-bar .cookie-consent-button-action, 149 | .consent-layout-bar-inline .cookie-consent-button-action { 150 | flex-direction: row; 151 | } 152 | 153 | .consent-layout-box-wide .cookie-consent-button-container, 154 | .consent-layout-cloud-inline .cookie-consent-button-action, 155 | .consent-layout-bar .cookie-consent-button-container { 156 | flex-direction: row; 157 | justify-content: space-between; 158 | align-items: center; 159 | flex-wrap: wrap; 160 | } 161 | 162 | .cookie-consent-button-container button { 163 | padding: .5rem 1.25rem; 164 | border: none; 165 | border-radius: 5px; 166 | font-size: 14px; 167 | cursor: pointer; 168 | transition: all 0.3s ease; 169 | background-color: rgba(var(--cc-button-bg-color-rgb), var(--cc-button-bg-opacity, 1)); 170 | color: var(--cc-button-text-color); 171 | min-width: max-content; 172 | } 173 | 174 | .cookie-consent-button-container button.preferences-btn { 175 | --cc-button-bg-color-rgb: 227, 232, 235; 176 | --cc-button-text-color: #000; 177 | } 178 | 179 | .cookie-consent-button-container button:hover { 180 | --cc-button-bg-opacity: 1; 181 | } 182 | 183 | .consent-layout-box-inline .cookie-consent-button-action button { 184 | width: 100%; 185 | } 186 | 187 | .cookie-consent-links-container { 188 | text-align: center; 189 | padding: .25rem 1.25rem; 190 | background: #ffffff; 191 | background: -webkit-linear-gradient(0deg, #ffffff 0%, #efefef 100%); 192 | background: linear-gradient(0deg, #ffffff 0%, #efefef 100%); 193 | } 194 | 195 | [theme="dark"] .cookie-consent-links-container { 196 | background: #1e1e1e; 197 | background: -webkit-linear-gradient(0deg, #1e1e1e 0%, #2a2a2a 100%); 198 | background: linear-gradient(0deg, #1e1e1e 0%, #2a2a2a 100%); 199 | color: #ffffff; 200 | } 201 | 202 | .cookie-consent-links-container ul { 203 | list-style: none; 204 | padding: 0; 205 | margin: 0; 206 | display: inline-flex; 207 | gap: .5rem 1rem; 208 | flex-wrap: wrap; 209 | justify-content: center; 210 | } 211 | 212 | .cookie-consent-links-container ul li { 213 | display: flex; 214 | justify-content: center; 215 | gap: 15px; 216 | } 217 | 218 | .cookie-consent-links-container a { 219 | text-decoration: none; 220 | color: rgba(var(--cc-button-bg-color-rgb), var(--cc-button-bg-opacity, 1)); 221 | font-size: 12px; 222 | } 223 | 224 | .cookie-consent-links-container a:hover { 225 | text-decoration: underline; 226 | } 227 | 228 | .cookie-consent-links-container ul { 229 | --cc-links-ul-width: calc(1200px - 40px); 230 | width: var(--cc-links-ul-width); 231 | justify-content: start; 232 | } 233 | 234 | @media (max-width: 575.98px) { 235 | 236 | .cookie-consent-button-container button, 237 | .consent-layout-box-wide .cookie-consent-button-action, 238 | .consent-layout-bar .cookie-consent-button-action, 239 | .cookie-consent-button-container button.preferences-btn { 240 | width: 100%; 241 | } 242 | } 243 | 244 | @media (min-width: 576px) { 245 | .cookie-consent-content-container { 246 | --cc-content-container-width: 480px; 247 | } 248 | 249 | .cookie-consent-links-container ul { 250 | --cc-links-ul-width: calc(480px - 40px); 251 | width: var(--cc-links-ul-width); 252 | } 253 | 254 | .consent-layout-box-inline .cookie-consent-button-container, 255 | .consent-layout-box .cookie-consent-button-container { 256 | flex-direction: column; 257 | justify-content: space-between; 258 | } 259 | 260 | .cookie-consent-button-container { 261 | flex-direction: row; 262 | justify-content: space-between; 263 | } 264 | 265 | .consent-layout-cloud .cookie-consent-button-action { 266 | flex-direction: row; 267 | } 268 | } 269 | 270 | @media (min-width: 768px) { 271 | .cookie-consent-content-container { 272 | --cc-content-container-width: 640px; 273 | } 274 | 275 | .consent-layout-box-inline .cookie-consent-button-container, 276 | .consent-layout-box .cookie-consent-button-container { 277 | flex-direction: column; 278 | justify-content: space-between; 279 | } 280 | 281 | .cookie-consent-links-container ul { 282 | --cc-links-ul-width: calc(640px - 40px); 283 | width: var(--cc-links-ul-width); 284 | } 285 | 286 | .consent-layout-cloud .cookie-consent-button-action { 287 | flex-direction: row; 288 | } 289 | } 290 | 291 | @media (min-width: 992px) { 292 | .cookie-consent-content-container { 293 | --cc-content-container-width: 860px; 294 | } 295 | 296 | .consent-layout-box-inline .cookie-consent-button-container, 297 | .consent-layout-box .cookie-consent-button-container { 298 | flex-direction: column; 299 | justify-content: space-between; 300 | } 301 | 302 | .cookie-consent-links-container ul { 303 | --cc-links-ul-width: calc(860px - 40px); 304 | width: var(--cc-links-ul-width); 305 | } 306 | 307 | .consent-layout-cloud .cookie-consent-button-action { 308 | flex-direction: row; 309 | } 310 | } 311 | 312 | @media (min-width: 1200px) { 313 | .cookie-consent-content-container { 314 | --cc-content-container-width: 1080px; 315 | } 316 | 317 | .consent-layout-box-inline .cookie-consent-button-container, 318 | .consent-layout-box .cookie-consent-button-container { 319 | flex-direction: column; 320 | justify-content: space-between; 321 | } 322 | 323 | .cookie-consent-links-container ul { 324 | --cc-links-ul-width: calc(1080px - 40px); 325 | width: var(--cc-links-ul-width); 326 | } 327 | 328 | .consent-layout-cloud .cookie-consent-button-action { 329 | flex-direction: row; 330 | } 331 | } 332 | 333 | @media (min-width: 1400px) { 334 | .cookie-consent-content-container { 335 | --cc-content-container-width: 1200px; 336 | } 337 | 338 | .consent-layout-box-inline .cookie-consent-button-container, 339 | .consent-layout-box .cookie-consent-button-container { 340 | flex-direction: column; 341 | justify-content: space-between; 342 | } 343 | 344 | .cookie-consent-links-container ul { 345 | --cc-links-ul-width: calc(1200px - 40px); 346 | width: var(--cc-links-ul-width); 347 | } 348 | 349 | .consent-layout-cloud .cookie-consent-button-action { 350 | flex-direction: row; 351 | } 352 | } 353 | 354 | /* ====================================== */ 355 | /* Modal CSS | Start */ 356 | 357 | /* Modal Base Styles */ 358 | .cookie-preferences-modal { 359 | position: fixed; 360 | top: 0; 361 | left: 0; 362 | width: 100%; 363 | height: 100%; 364 | display: flex; 365 | align-items: center; 366 | justify-content: center; 367 | z-index: 9999; 368 | opacity: 0; 369 | visibility: hidden; 370 | transition: var(--cc-modal-transition); 371 | pointer-events: none; 372 | } 373 | 374 | .cookie-preferences-modal.is-visible { 375 | opacity: 1; 376 | visibility: visible; 377 | pointer-events: auto; 378 | } 379 | 380 | .cookie-preferences-modal-overlay { 381 | position: absolute; 382 | top: 0; 383 | left: 0; 384 | width: 100%; 385 | height: 100%; 386 | background-color: rgba(0, 0, 0, 0.6); 387 | opacity: 0; 388 | transition: var(--cc-modal-transition); 389 | } 390 | 391 | .cookie-preferences-modal.is-visible .cookie-preferences-modal-overlay { 392 | opacity: 1; 393 | } 394 | 395 | .cookie-preferences-modal-content { 396 | position: relative; 397 | background-color: var(--cc-card-bg-color); 398 | border-radius: 12px; 399 | width: 90%; 400 | max-width: 43em; 401 | max-height: 90vh; 402 | overflow: hidden; 403 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); 404 | transform: translateY(20px); 405 | transition: var(--cc-modal-transition); 406 | display: flex; 407 | flex-direction: column; 408 | } 409 | 410 | .cookie-preferences-modal.is-visible .cookie-preferences-modal-content { 411 | transform: translateY(0); 412 | } 413 | 414 | /* Modal Header */ 415 | .cookie-preferences-modal-header { 416 | padding: 1em 1.4em; 417 | border-bottom: 1px solid #6c6c6c1c; 418 | display: flex; 419 | justify-content: space-between; 420 | align-items: center; 421 | flex-shrink: 0; 422 | } 423 | 424 | .cookie-preferences-modal-header h2 { 425 | margin: 0; 426 | font-size: .95rem; 427 | font-weight: 600; 428 | color: var(--cc-card-text-color); 429 | } 430 | 431 | .cookie-preferences-modal-close { 432 | background: var(--cc-button-text-color); 433 | border: none; 434 | padding: 4px; 435 | cursor: pointer; 436 | color: var(--cc-card-text-color); 437 | transition: color 0.2s ease; 438 | display: flex; 439 | align-items: center; 440 | justify-content: center; 441 | border-radius: 4px; 442 | width: 2rem; 443 | height: 2rem; 444 | } 445 | 446 | .cookie-preferences-modal-close:hover, 447 | .cookie-preferences-modal-close:focus { 448 | color: var(--cc-card-text-color); 449 | background-color: rgba(0, 0, 0, 0.05); 450 | } 451 | 452 | .cookie-preferences-modal-close svg { 453 | width: 1rem; 454 | height: 1rem; 455 | } 456 | 457 | /* Modal Body */ 458 | .cookie-preferences-modal-body { 459 | padding: 24px; 460 | overflow-y: auto; 461 | flex-grow: 1; 462 | scrollbar-width: thin; 463 | scrollbar-color: rgba(var(--cc-button-bg-color-rgb), var(--cc-button-bg-opacity, 1)) rgba(0, 0, 0, 0.1); 464 | } 465 | 466 | .cookie-preferences-modal p { 467 | margin: 0; 468 | font-size: .85rem; 469 | color: var(--cc-card-text-color); 470 | line-height: 1.5; 471 | font-weight: 400; 472 | opacity: .75; 473 | } 474 | 475 | .cookie-preferences-modal-body::-webkit-scrollbar { 476 | width: 6px; 477 | } 478 | 479 | .cookie-preferences-modal-body::-webkit-scrollbar-track { 480 | background: rgba(0, 0, 0, 0.1); 481 | border-radius: 3px; 482 | } 483 | 484 | .cookie-preferences-modal-body::-webkit-scrollbar-thumb { 485 | background-color: rgba(var(--cc-button-bg-color-rgb), var(--cc-button-bg-opacity, 1)); 486 | border-radius: 3px; 487 | } 488 | 489 | .cookie-preferences-intro { 490 | padding: 0 0 20px 0; 491 | } 492 | 493 | .cookie-categories { 494 | display: flex; 495 | flex-direction: column; 496 | gap: 20px; 497 | } 498 | 499 | .cookie-category { 500 | padding-bottom: 20px; 501 | border-bottom: 1px solid #6c6c6c1c; 502 | } 503 | 504 | .cookie-category:last-child { 505 | border-bottom: none; 506 | padding-bottom: 0; 507 | } 508 | 509 | .cookie-category-header { 510 | display: flex; 511 | justify-content: space-between; 512 | align-items: center; 513 | margin-bottom: 12px; 514 | } 515 | 516 | .cookie-category-header h3 { 517 | margin: 0; 518 | font-size: 1rem; 519 | font-weight: 500; 520 | color: var(--cc-card-text-color); 521 | } 522 | 523 | /* Modal Footer */ 524 | .cookie-preferences-modal-footer { 525 | padding: 16px 24px; 526 | border-top: 1px solid #6c6c6c1c; 527 | display: flex; 528 | justify-content: space-between; 529 | gap: 12px; 530 | flex-shrink: 0; 531 | } 532 | 533 | .cookie-preferences-modal-button-group { 534 | display: flex; 535 | justify-content: space-between; 536 | gap: 12px; 537 | } 538 | 539 | .primary-button { 540 | background-color: rgba(var(--cc-button-bg-color-rgb), var(--cc-button-bg-opacity, 1)); 541 | color: var(--cc-button-text-color); 542 | border: none; 543 | padding: .5rem 1.25rem; 544 | border-radius: 6px; 545 | cursor: pointer; 546 | font-size: 0.9rem; 547 | font-weight: 500; 548 | transition: background-color 0.2s ease; 549 | } 550 | 551 | .primary-button:hover, 552 | .primary-button:focus { 553 | background-color: rgba(var(--cc-button-bg-color-rgb), var(--cc-button-bg-opacity, 1)); 554 | } 555 | 556 | .secondary-button { 557 | background-color: rgba(var(--cc-button-bg-color-rgb), var(--cc-button-bg-opacity, 1)); 558 | color: var(--cc-button-text-color); 559 | padding: 10px 20px; 560 | border-radius: 6px; 561 | cursor: pointer; 562 | font-size: 0.9rem; 563 | font-weight: 500; 564 | transition: all 0.2s ease; 565 | } 566 | 567 | .secondary-button:hover, 568 | .secondary-button:focus { 569 | background-color: #e7e7e7; 570 | } 571 | 572 | /* Improved Toggle Switch */ 573 | .cookie-toggle { 574 | position: relative; 575 | display: inline-block; 576 | width: 44px; 577 | height: 24px; 578 | } 579 | 580 | .cookie-toggle input { 581 | opacity: 0; 582 | width: 0; 583 | height: 0; 584 | } 585 | 586 | .cookie-toggle-slider { 587 | position: absolute; 588 | cursor: pointer; 589 | top: 0; 590 | left: 0; 591 | right: 0; 592 | bottom: 0; 593 | background-color: #ccc; 594 | transition: var(--cc-modal-transition); 595 | border-radius: 24px; 596 | } 597 | 598 | .cookie-toggle-slider:before { 599 | position: absolute; 600 | content: ""; 601 | height: 20px; 602 | width: 20px; 603 | left: 2px; 604 | bottom: 2px; 605 | background-color: white; 606 | transition: var(--cc-modal-transition); 607 | border-radius: 50%; 608 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); 609 | } 610 | 611 | .cookie-toggle input:checked + .cookie-toggle-slider { 612 | background-color: rgba(var(--cc-button-bg-color-rgb), var(--cc-button-bg-opacity, 1)); 613 | } 614 | 615 | .cookie-toggle input:checked + .cookie-toggle-slider:before { 616 | transform: translateX(20px); 617 | } 618 | 619 | .cookie-toggle input:disabled + .cookie-toggle-slider { 620 | background-color: #95a5a6; 621 | cursor: not-allowed; 622 | } 623 | 624 | .cookie-toggle input:focus + .cookie-toggle-slider { 625 | box-shadow: none; 626 | } 627 | 628 | /* Responsive adjustments */ 629 | @media (max-width: 480px) { 630 | .cookie-preferences-modal-content { 631 | width: 95%; 632 | max-height: 85vh; 633 | } 634 | 635 | .cookie-preferences-modal-footer { 636 | flex-direction: column; 637 | } 638 | 639 | .primary-button { 640 | width: 100%; 641 | } 642 | } 643 | /* Modal CSS | End */ 644 | /* ====================================== */ -------------------------------------------------------------------------------- /assets/js/script.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | document.addEventListener('DOMContentLoaded', function () { 4 | // DOM Elements 5 | const consentRoot = document.querySelector('.cookie-consent-root'); 6 | const acceptButtons = document.querySelectorAll('.cookie-consent-accept'); 7 | const rejectButtons = document.querySelectorAll('.cookie-consent-reject'); 8 | const cookieConsentPrefix = consentRoot?.getAttribute('data-cookie-prefix') || 'cookie_consent'; 9 | 10 | // Modal Elements 11 | const preferencesBtn = document.querySelector('.preferences-btn'); 12 | const modal = document.querySelector('.cookie-preferences-modal'); 13 | const modalOverlay = document.querySelector('.cookie-preferences-modal-overlay'); 14 | const modalClose = document.querySelector('.cookie-preferences-modal-close'); 15 | const modalSave = document.querySelector('.cookie-preferences-save'); 16 | 17 | // Initialize banner 18 | if (consentRoot) { 19 | consentRoot.classList.remove('cookie-consent-hide'); 20 | 21 | if (consentRoot.classList.contains('cookie-disable-interaction')) { 22 | document.documentElement.classList.add('cookie-disable-interaction'); 23 | } 24 | } 25 | 26 | // Check existing consent 27 | const cookieConsent = getCookie(cookieConsentPrefix); 28 | if (cookieConsent === 'accepted' || cookieConsent === 'rejected') { 29 | hideConsentBanner(); 30 | } 31 | 32 | // Handle accept buttons (both in banner and modal) 33 | acceptButtons.forEach(button => { 34 | button.addEventListener('click', function() { 35 | setCookie(cookieConsentPrefix, 'accepted', consentRoot?.getAttribute('data-cookie-lifetime') || 7); 36 | setAllPreferences(true); // Accept all categories 37 | hideConsentBanner(); 38 | document.dispatchEvent(new CustomEvent('cookieConsentAccepted')); 39 | 40 | // Check if function exists before calling 41 | if (typeof loadCookieCategoriesEnabledServices === 'function') { 42 | try { 43 | loadCookieCategoriesEnabledServices(); 44 | } catch (e) { 45 | console.info(e); 46 | } 47 | } 48 | }); 49 | }); 50 | 51 | // Handle reject buttons (both in banner and modal) 52 | rejectButtons.forEach(button => { 53 | button.addEventListener('click', function() { 54 | setCookie(cookieConsentPrefix, 'rejected', consentRoot?.getAttribute('data-reject-lifetime') || 1); 55 | setAllPreferences(false); // Reject all non-essential categories 56 | hideConsentBanner(); 57 | document.dispatchEvent(new CustomEvent('cookieConsentRejected')); 58 | }); 59 | }); 60 | 61 | // Modal functions 62 | function showModal() { 63 | if (!modal) return; 64 | 65 | document.body.classList.add('modal-open'); 66 | modal.setAttribute('aria-hidden', 'false'); 67 | modal.classList.add('is-visible'); 68 | 69 | // Set initial toggle states 70 | const savedPreferences = getCookiePreferences(); 71 | document.querySelectorAll('.cookie-toggle input:not([disabled])').forEach(toggle => { 72 | const category = toggle.dataset.category; 73 | toggle.checked = savedPreferences ? savedPreferences[category] !== false : true; 74 | }); 75 | } 76 | 77 | function hideModal() { 78 | if (!modal) return; 79 | 80 | modal.setAttribute('aria-hidden', 'true'); 81 | modal.classList.remove('is-visible'); 82 | document.body.classList.remove('modal-open'); 83 | } 84 | 85 | // Preferences management 86 | function setAllPreferences(accept) { 87 | const preferences = {}; 88 | document.querySelectorAll('.cookie-toggle input[data-category]').forEach(toggle => { 89 | preferences[toggle.dataset.category] = toggle.disabled ? true : accept; 90 | }); 91 | setCookiePreferences(preferences); 92 | } 93 | 94 | // Event listeners for modal 95 | if (preferencesBtn) { 96 | preferencesBtn.addEventListener('click', showModal); 97 | } 98 | 99 | if (modalOverlay) modalOverlay.addEventListener('click', hideModal); 100 | if (modalClose) modalClose.addEventListener('click', hideModal); 101 | 102 | if (modalSave) { 103 | modalSave.addEventListener('click', function() { 104 | const preferences = {}; 105 | document.querySelectorAll('.cookie-toggle input[data-category]').forEach(toggle => { 106 | preferences[toggle.dataset.category] = toggle.checked; 107 | }); 108 | 109 | setCookiePreferences(preferences); 110 | setCookie(cookieConsentPrefix, 'custom', consentRoot?.getAttribute('data-cookie-lifetime') || 7); 111 | hideModal(); 112 | document.dispatchEvent(new CustomEvent('cookiePreferencesSaved', { detail: preferences })); 113 | 114 | // Action Accept 115 | setCookie(cookieConsentPrefix, 'accepted', consentRoot?.getAttribute('data-cookie-lifetime') || 7); 116 | hideConsentBanner(); 117 | document.dispatchEvent(new CustomEvent('cookieConsentAccepted')); 118 | 119 | // Check if function exists before calling 120 | if (typeof loadCookieCategoriesEnabledServices === 'function') { 121 | try { 122 | loadCookieCategoriesEnabledServices(); 123 | } catch (e) { 124 | console.info(e); 125 | } 126 | } 127 | }); 128 | } 129 | 130 | // Keyboard navigation 131 | document.addEventListener('keydown', function(e) { 132 | if (e.key === 'Escape' && modal?.classList.contains('is-visible')) { 133 | hideModal(); 134 | } 135 | }); 136 | 137 | // Cookie helper functions 138 | function setCookie(name, value, days) { 139 | const date = new Date(); 140 | date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 141 | const expires = "; expires=" + date.toUTCString(); 142 | const secureFlag = location.protocol === 'https:' ? "; Secure" : ""; 143 | document.cookie = `${name}=${value}; path=/; SameSite=Lax${expires}${secureFlag}`; 144 | } 145 | 146 | function getCookie(name) { 147 | const nameEQ = name + "="; 148 | const cookies = document.cookie.split(';'); 149 | for (let i = 0; i < cookies.length; i++) { 150 | let cookie = cookies[i].trim(); 151 | if (cookie.indexOf(nameEQ) === 0) { 152 | return cookie.substring(nameEQ.length, cookie.length); 153 | } 154 | } 155 | return null; 156 | } 157 | 158 | function setCookiePreferences(preferences) { 159 | const expires = new Date(); 160 | expires.setTime(expires.getTime() + (365 * 24 * 60 * 60 * 1000)); 161 | const secureFlag = location.protocol === 'https:' ? "; Secure" : ""; 162 | document.cookie = `cookie_preferences=${JSON.stringify(preferences)}; expires=${expires.toUTCString()}; path=/; SameSite=Lax${secureFlag}`; 163 | } 164 | 165 | function getCookiePreferences() { 166 | const name = 'cookie_preferences='; 167 | const decodedCookie = decodeURIComponent(document.cookie); 168 | const cookieArray = decodedCookie.split(';'); 169 | 170 | for(let i = 0; i < cookieArray.length; i++) { 171 | let cookie = cookieArray[i].trim(); 172 | if (cookie.indexOf(name) === 0) { 173 | return JSON.parse(cookie.substring(name.length, cookie.length)); 174 | } 175 | } 176 | return null; 177 | } 178 | 179 | // Make them available globally 180 | window.getCookie = getCookie; 181 | window.getCookiePreferences = getCookiePreferences; 182 | 183 | function hideConsentBanner() { 184 | if (consentRoot) { 185 | consentRoot.classList.add('cookie-consent-hide'); 186 | document.documentElement.classList.remove('cookie-disable-interaction'); 187 | } 188 | hideModal(); 189 | } 190 | }); 191 | 192 | // Handle scrollbar width to prevent page shift 193 | document.head.insertAdjacentHTML('beforeend', ` 194 | 200 | `); -------------------------------------------------------------------------------- /assets/laravel-cookie-consent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrabiul/laravel-cookie-consent/6a3143e9ef70031c640af51e029d8cea30b554c4/assets/laravel-cookie-consent.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devrabiul/laravel-cookie-consent", 3 | "type": "library", 4 | "description": "A GDPR-compliant cookie consent solution for Laravel applications with fully customizable cookie banners, granular consent control, and enterprise-grade compliance features.", 5 | "keywords": ["laravel", "cookie", "consent", "gdpr", "compliance", "banner", "privacy"], 6 | "homepage": "https://github.com/devrabiul/laravel-cookie-consent", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Muhammad Rabiul", 11 | "email": "devrabiul@gmail.com" 12 | } 13 | ], 14 | "require": {}, 15 | "autoload": { 16 | "psr-4": { 17 | "Devrabiul\\CookieConsent\\": "src/" 18 | } 19 | }, 20 | "extra": { 21 | "laravel": { 22 | "providers": [ 23 | "Devrabiul\\CookieConsent\\CookieConsentServiceProvider" 24 | ], 25 | "aliases": { 26 | "CookieConsent": "Devrabiul\\CookieConsent\\Facades\\CookieConsent" 27 | } 28 | } 29 | }, 30 | "minimum-stability": "dev", 31 | "prefer-stable": true 32 | } -------------------------------------------------------------------------------- /example.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Cookie Consent 8 | 9 | 16 | 17 | {!! CookieConsent::styles() !!} 18 | 19 | 20 | 21 | 22 | 23 | 24 | {!! CookieConsent::scripts(options: [ 25 | 'cookie_lifetime' => config('laravel-cookie-consent.cookie_lifetime', 7), 26 | 'reject_lifetime' => config('laravel-cookie-consent.reject_lifetime', 1), 27 | 'disable_page_interaction' => config('laravel-cookie-consent.disable_page_interaction', true), 28 | 'preferences_modal_enabled' => config('laravel-cookie-consent.preferences_modal_enabled', true), 29 | 'consent_modal_layout' => config('laravel-cookie-consent.consent_modal_layout', 'bar-inline'), 30 | 'flip_button' => config('laravel-cookie-consent.flip_button', true), 31 | 'theme' => config('laravel-cookie-consent.theme', 'default'), 32 | 'cookie_prefix' => config('laravel-cookie-consent.cookie_prefix', 'Laravel_App'), 33 | 'policy_links' => config('laravel-cookie-consent.policy_links', [ 34 | ['text' => 'Privacy Policy', 'link' => url('privacy-policy')], 35 | ['text' => 'Terms & Conditions', 'link' => url('terms-and-conditions')], 36 | ]), 37 | 'cookie_categories' => config('laravel-cookie-consent.cookie_categories', [ 38 | 'necessary' => [ 39 | 'enabled' => true, 40 | 'locked' => true, 41 | 'js_action' => 'loadGoogleAnalytics', 42 | 'title' => 'Essential Cookies', 43 | 'description' => 'These cookies are essential for the website to function properly.', 44 | ], 45 | 'analytics' => [ 46 | 'enabled' => env('COOKIE_CONSENT_ANALYTICS', false), 47 | 'locked' => false, 48 | 'title' => 'Analytics Cookies', 49 | 'description' => 'These cookies help us understand how visitors interact with our website.', 50 | ], 51 | 'marketing' => [ 52 | 'enabled' => env('COOKIE_CONSENT_MARKETING', false), 53 | 'locked' => false, 54 | 'js_action' => 'loadFacebookPixel', 55 | 'title' => 'Marketing Cookies', 56 | 'description' => 'These cookies are used for advertising and tracking purposes.', 57 | ], 58 | 'preferences' => [ 59 | 'enabled' => env('COOKIE_CONSENT_PREFERENCES', false), 60 | 'locked' => false, 61 | 'js_action' => 'loadPreferencesFunc', 62 | 'title' => 'Preferences Cookies', 63 | 'description' => 'These cookies allow the website to remember user preferences.', 64 | ], 65 | ]), 66 | 'cookie_modal_title' => 'Cookie Preferences', 67 | 'cookie_modal_intro' => 'You can customize your cookie preferences below.', 68 | 'cookie_accept_btn_text' => 'Accept All', 69 | 'cookie_reject_btn_text' => 'Reject All', 70 | 'cookie_preferences_btn_text' => 'Manage Preferences', 71 | 'cookie_preferences_save_text' => 'Save Preferences', 72 | ]) !!} 73 | 74 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /resources/views/cookie-consent.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 59 | 60 | 61 | 118 | 119 | 120 | 121 | 122 | 123 | 158 | -------------------------------------------------------------------------------- /routes/laravel-cookie-consent.php: -------------------------------------------------------------------------------- 1 | group(function () { 7 | Route::get('/laravel-cookie-consent/script-utils', 'scriptUtils')->name('laravel-cookie-consent.script-utils'); 8 | }); 9 | 10 | -------------------------------------------------------------------------------- /src/CookieConsent.php: -------------------------------------------------------------------------------- 1 | session = $session; 48 | $this->config = $config; 49 | } 50 | 51 | /** 52 | * Generate the HTML for the required stylesheet. 53 | * 54 | * @return string The HTML link tag for the stylesheet. 55 | */ 56 | public function styles(): string 57 | { 58 | $style = ''; 59 | return $style; 60 | } 61 | 62 | /** 63 | * Render the cookie consent view with the given configuration. 64 | * 65 | * @param array $cookieConfig Optional cookie configuration overrides. 66 | * @return View The cookie consent view. 67 | */ 68 | public function content(array $cookieConfig = []) 69 | { 70 | return view('laravel-cookie-consent::cookie-consent', compact('cookieConfig')); 71 | } 72 | 73 | /** 74 | * Generate the HTML for the required JavaScript with optional configuration overrides. 75 | * 76 | * @param array $options Optional configuration overrides. 77 | * @return View The cookie consent script view. 78 | */ 79 | public function scripts(array $options = []): mixed 80 | { 81 | $config = (array)$this->config->get('laravel-cookie-consent'); 82 | $config = array_merge($config, $options); 83 | if (isset($config['enabled']) && ($config['enabled'] === false || $config['enabled'] === 'false')) { 84 | return ''; 85 | } 86 | return self::content(cookieConfig: $config); 87 | } 88 | 89 | /** 90 | * Set the JavaScript type to 'module', used for Vite-based builds. 91 | * 92 | * @return void 93 | */ 94 | public function useVite(): void 95 | { 96 | $this->jsType = 'module'; 97 | } 98 | 99 | public static function getRemoveInvalidCharacters($str): array|string 100 | { 101 | return str_ireplace(['"', ';', '<', '>'], ' ', preg_replace('/\s\s+/', ' ', $str)); 102 | } 103 | 104 | /** 105 | * Translates a given key using the `messages.php` language file in the current locale. 106 | * 107 | * - If the key does not exist in the file, it creates the file and inserts the key with a default value. 108 | * - It ensures the required language directory and file exist. 109 | * - Falls back to Laravel translation helper if the key is not found after insertion. 110 | * 111 | * @param string $key The translation key, e.g. 'messages.privacy_policy'. 112 | * @param null $default The default value to use if the key doesn't exist. 113 | */ 114 | public static function translate(string $key, $default = null) 115 | { 116 | $locale = app()->getLocale(); 117 | $langDir = resource_path("lang/{$locale}"); 118 | $filePath = "{$langDir}/messages.php"; 119 | 120 | // Ensure the directory exists 121 | if (!File::exists($langDir)) { 122 | File::makeDirectory($langDir, 0755, true); 123 | } 124 | 125 | // If the file doesn't exist, create it with an empty array 126 | if (!File::exists($filePath)) { 127 | File::put($filePath, " $v) { 144 | $content .= " '{$k}' => '{$v}',\n"; 145 | } 146 | $content .= "];\n"; 147 | 148 | File::put($filePath, $content); 149 | } elseif (array_key_exists($actualKey, $translations)) { 150 | return $translations[$actualKey] ?? $default; 151 | } else { 152 | return __('messages.' . $actualKey); 153 | } 154 | } 155 | } catch (\Exception $exception) { 156 | 157 | } 158 | return $key; 159 | } 160 | } -------------------------------------------------------------------------------- /src/CookieConsentServiceProvider.php: -------------------------------------------------------------------------------- 1 | updateProcessingAssetRoutes(); 24 | $this->registerResources(); 25 | if ($this->app->runningInConsole()) { 26 | $this->registerPublishing(); 27 | } 28 | } 29 | 30 | /** 31 | * Register the publishing of configuration files. 32 | * 33 | * This method registers the configuration file for publishing to the application's config directory. 34 | * 35 | * @return void 36 | * @throws Exception If there is an error during publishing. 37 | */ 38 | private function registerPublishing(): void 39 | { 40 | // Normal publish 41 | $this->publishes([ 42 | __DIR__ . '/config/laravel-cookie-consent.php' => config_path('laravel-cookie-consent.php'), 43 | ]); 44 | } 45 | 46 | private function registerResources(): void 47 | { 48 | $this->loadRoutesFrom(__DIR__ . '/../routes/laravel-cookie-consent.php'); 49 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'laravel-cookie-consent'); 50 | // $this->commands($this->registerCommands()); 51 | } 52 | 53 | /** 54 | * Register the application services. 55 | * 56 | * This method is called to bind services into the service container. 57 | * It is used to register the CookieConsent service and load the configuration. 58 | * 59 | * @return void 60 | * @throws Exception If the configuration file cannot be loaded. 61 | */ 62 | public function register(): void 63 | { 64 | 65 | $configPath = config_path('laravel-cookie-consent.php'); 66 | 67 | if (!file_exists($configPath)) { 68 | config(['laravel-cookie-consent' => require __DIR__ . '/config/laravel-cookie-consent.php']); 69 | } 70 | 71 | $this->app->singleton('CookieConsent', function ($app) { 72 | return new CookieConsent($app['session'], $app['config']); 73 | }); 74 | } 75 | 76 | /** 77 | * Get the services provided by the provider. 78 | * 79 | * This method returns an array of services that this provider offers. 80 | * 81 | * @return array 82 | * @throws Exception If there is an error retrieving the services. 83 | */ 84 | public function provides(): array 85 | { 86 | return ['CookieConsent']; 87 | } 88 | 89 | /** 90 | * Update the routes for processing asset requests. 91 | * 92 | * This method defines a route that serves asset files from the package. 93 | * It handles the retrieval of files based on the provided path and sets the appropriate MIME type. 94 | * 95 | * @return void 96 | * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException If the file does not exist. 97 | */ 98 | private function updateProcessingAssetRoutes(): void 99 | { 100 | Route::get('/vendor/laravel-cookie-consent/assets/{path}', function ($path) { 101 | $file = __DIR__ . '/../assets/' . $path; 102 | 103 | if (file_exists($file)) { 104 | // Get file extension 105 | $extension = pathinfo($file, PATHINFO_EXTENSION); 106 | 107 | // Mime types based on file extension 108 | $mimeTypes = [ 109 | 'css' => 'text/css', 110 | 'js' => 'application/javascript', 111 | 'png' => 'image/png', 112 | 'jpg' => 'image/jpeg', 113 | 'jpeg' => 'image/jpeg', 114 | 'gif' => 'image/gif', 115 | 'svg' => 'image/svg+xml', 116 | 'woff' => 'font/woff', 117 | 'woff2' => 'font/woff2', 118 | 'ttf' => 'font/ttf', 119 | 'otf' => 'font/otf', 120 | 'eot' => 'application/vnd.ms-fontobject', 121 | 'json' => 'application/json', 122 | 'ico' => 'image/x-icon', 123 | ]; 124 | 125 | // Default to application/octet-stream if the extension is not recognized 126 | $mimeType = $mimeTypes[$extension] ?? 'application/octet-stream'; 127 | 128 | return Response::file($file, [ 129 | 'Content-Type' => $mimeType, 130 | 'Access-Control-Allow-Origin' => '*', 131 | ]); 132 | } 133 | 134 | abort(404); 135 | })->where('path', '.*'); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Facades/CookieConsent.php: -------------------------------------------------------------------------------- 1 | header('Content-Type', 'application/javascript'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/config/laravel-cookie-consent.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', 'Laravel_App'), 25 | 26 | /** 27 | * Enable or disable the cookie consent banner 28 | * 29 | * @default true 30 | * @env COOKIE_CONSENT_ENABLED 31 | */ 32 | 'enabled' => env('COOKIE_CONSENT_ENABLED', true), 33 | 34 | /** 35 | * Cookie lifetime in days 36 | * 37 | * Defines how long the consent cookie should persist in the user's browser. 38 | * 39 | * @default 365 40 | * @env COOKIE_CONSENT_LIFETIME 41 | */ 42 | 'cookie_lifetime' => env('COOKIE_CONSENT_LIFETIME', 365), 43 | 44 | /** 45 | * Rejection cookie lifetime in days 46 | * 47 | * Specifies how long the rejection cookie should persist when users decline cookies. 48 | * 49 | * @default 7 50 | * @env COOKIE_REJECT_LIFETIME 51 | */ 52 | 'reject_lifetime' => env('COOKIE_REJECT_LIFETIME', 7), 53 | 54 | /** 55 | * Consent modal layout style 56 | * 57 | * Determines the visual presentation of the consent modal. 58 | * 59 | * @default 'bar-inline' 60 | * @env COOKIE_CONSENT_MODAL_LAYOUT 61 | * @option box - Small floating box 62 | * @option box-inline - Small floating box positioned inline 63 | * @option box-wide - Larger floating box 64 | * @option cloud - Cloud-like floating consent box 65 | * @option cloud-inline - Compact cloud-style box 66 | * @option bar - Simple bar at top or bottom 67 | * @option bar-inline - Compact inline bar 68 | */ 69 | 'consent_modal_layout' => env('COOKIE_CONSENT_MODAL_LAYOUT', 'bar'), 70 | 71 | /** 72 | * Enable preferences modal 73 | * 74 | * Determines if users can access detailed cookie preferences. 75 | * 76 | * @default false 77 | * @env COOKIE_CONSENT_PREFERENCES_ENABLED 78 | */ 79 | 'preferences_modal_enabled' => env('COOKIE_CONSENT_PREFERENCES_ENABLED', true), 80 | 81 | /** 82 | * Preferences modal layout style 83 | * 84 | * Defines the visual presentation of the preferences modal. 85 | * 86 | * @default 'bar' 87 | * @env COOKIE_CONSENT_PREFERENCES_LAYOUT 88 | * @option bar - Bar-style modal 89 | * @option box - Popup-style box 90 | */ 91 | 'preferences_modal_layout' => env('COOKIE_CONSENT_PREFERENCES_LAYOUT', 'bar'), 92 | 93 | /** 94 | * Enable flip button animation 95 | * 96 | * Adds a flip animation effect to consent buttons. 97 | * 98 | * @default true 99 | * @env COOKIE_CONSENT_FLIP_BUTTON 100 | */ 101 | 'flip_button' => env('COOKIE_CONSENT_FLIP_BUTTON', true), 102 | 103 | /** 104 | * Disable page interaction until consent 105 | * 106 | * When enabled, users must interact with the cookie banner before accessing content. 107 | * 108 | * @default true 109 | * @env COOKIE_CONSENT_DISABLE_INTERACTION 110 | */ 111 | 'disable_page_interaction' => env('COOKIE_CONSENT_DISABLE_INTERACTION', true), 112 | 113 | /** 114 | * Color theme for the cookie banner 115 | * 116 | * @default 'default' 117 | * @env COOKIE_CONSENT_THEME 118 | * @option default - Standard theme 119 | * @option dark - Dark mode theme 120 | * @option light - Light mode theme 121 | * @option custom - Custom styles (requires additional CSS) 122 | */ 123 | 'theme' => env('COOKIE_CONSENT_THEME', 'default'), 124 | 125 | /** 126 | * Cookie banner title text 127 | * 128 | * @default "Cookie Disclaimer" 129 | */ 130 | 'cookie_title' => "Cookie Disclaimer", 131 | 132 | /** 133 | * Cookie banner description text 134 | * 135 | * @default "This website uses cookies to enhance your browsing experience, analyze site traffic, and personalize content. By continuing to use this site, you consent to our use of cookies." 136 | */ 137 | 'cookie_description' => "This website uses cookies to enhance your browsing experience, analyze site traffic, and personalize content. By continuing to use this site, you consent to our use of cookies.", 138 | 139 | /** 140 | * Accept all cookies button text 141 | * 142 | * @default 'Accept all' 143 | */ 144 | 'cookie_accept_btn_text' => 'Accept all', 145 | 146 | /** 147 | * Reject all cookies button text 148 | * 149 | * @default 'Reject all' 150 | */ 151 | 'cookie_reject_btn_text' => 'Reject all', 152 | 153 | /** 154 | * Manage preferences button text 155 | * 156 | * @default 'Manage preferences' 157 | */ 158 | 'cookie_preferences_btn_text' => 'Manage preferences', 159 | 160 | /** 161 | * Save preferences button text 162 | * 163 | * @default 'Save preferences' 164 | */ 165 | 'cookie_preferences_save_text' => 'Save preferences', 166 | 167 | /** 168 | * Preferences modal title text 169 | * 170 | * @default 'Cookie Preferences' 171 | */ 172 | 'cookie_modal_title' => 'Cookie Preferences', 173 | 174 | /** 175 | * Preferences modal introduction text 176 | * 177 | * @default 'You can customize your cookie preferences below.' 178 | */ 179 | 'cookie_modal_intro' => 'You can customize your cookie preferences below.', 180 | 181 | /** 182 | * Cookie categories configuration 183 | * 184 | * Defines the different types of cookies users can manage. 185 | * 186 | * @category necessary - Essential cookies that cannot be disabled 187 | * @category analytics - Cookies used for tracking and analytics 188 | * @category marketing - Cookies used for advertising 189 | * @category preferences - Cookies for user preference storage 190 | */ 191 | 'cookie_categories' => [ 192 | 'necessary' => [ 193 | 'enabled' => true, 194 | 'locked' => true, 195 | 'title' => 'Essential Cookies', 196 | 'description' => 'These cookies are essential for the website to function properly.', 197 | ], 198 | 'analytics' => [ 199 | 'enabled' => env('COOKIE_CONSENT_ANALYTICS', false), 200 | 'locked' => false, 201 | 'js_action' => 'loadGoogleAnalytics', 202 | 'title' => 'Analytics Cookies', 203 | 'description' => 'These cookies help us understand how visitors interact with our website.', 204 | ], 205 | 'marketing' => [ 206 | 'enabled' => env('COOKIE_CONSENT_MARKETING', false), 207 | 'locked' => false, 208 | 'js_action' => 'loadFacebookPixel', 209 | 'title' => 'Marketing Cookies', 210 | 'description' => 'These cookies are used for advertising and tracking purposes.', 211 | ], 212 | 'preferences' => [ 213 | 'enabled' => env('COOKIE_CONSENT_PREFERENCES', false), 214 | 'locked' => false, 215 | 'title' => 'Preferences Cookies', 216 | 'description' => 'These cookies allow the website to remember user preferences.', 217 | ], 218 | ], 219 | 220 | /** 221 | * Policy links configuration 222 | * 223 | * Links to legal documents displayed in the cookie banner. 224 | * 225 | * @item text - Display text for the link 226 | * @item link - URL to the policy document 227 | */ 228 | 'policy_links' => [ 229 | [ 230 | 'text' => 'Privacy Policy', 231 | 'link' => env('COOKIE_CONSENT_PRIVACY_POLICY_URL', '') ?? url('privacy-policy') 232 | ], 233 | [ 234 | 'text' => 'Terms and Conditions', 235 | 'link' => env('COOKIE_CONSENT_TERMS_URL', '') ?? url('terms-and-conditions') 236 | ], 237 | ], 238 | ]; 239 | 240 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see https://www.php-fig.org/psr/psr-0/ 41 | * @see https://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | /** @var \Closure(string):void */ 46 | private static $includeFile; 47 | 48 | /** @var string|null */ 49 | private $vendorDir; 50 | 51 | // PSR-4 52 | /** 53 | * @var array> 54 | */ 55 | private $prefixLengthsPsr4 = array(); 56 | /** 57 | * @var array> 58 | */ 59 | private $prefixDirsPsr4 = array(); 60 | /** 61 | * @var list 62 | */ 63 | private $fallbackDirsPsr4 = array(); 64 | 65 | // PSR-0 66 | /** 67 | * List of PSR-0 prefixes 68 | * 69 | * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) 70 | * 71 | * @var array>> 72 | */ 73 | private $prefixesPsr0 = array(); 74 | /** 75 | * @var list 76 | */ 77 | private $fallbackDirsPsr0 = array(); 78 | 79 | /** @var bool */ 80 | private $useIncludePath = false; 81 | 82 | /** 83 | * @var array 84 | */ 85 | private $classMap = array(); 86 | 87 | /** @var bool */ 88 | private $classMapAuthoritative = false; 89 | 90 | /** 91 | * @var array 92 | */ 93 | private $missingClasses = array(); 94 | 95 | /** @var string|null */ 96 | private $apcuPrefix; 97 | 98 | /** 99 | * @var array 100 | */ 101 | private static $registeredLoaders = array(); 102 | 103 | /** 104 | * @param string|null $vendorDir 105 | */ 106 | public function __construct($vendorDir = null) 107 | { 108 | $this->vendorDir = $vendorDir; 109 | self::initializeIncludeClosure(); 110 | } 111 | 112 | /** 113 | * @return array> 114 | */ 115 | public function getPrefixes() 116 | { 117 | if (!empty($this->prefixesPsr0)) { 118 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 119 | } 120 | 121 | return array(); 122 | } 123 | 124 | /** 125 | * @return array> 126 | */ 127 | public function getPrefixesPsr4() 128 | { 129 | return $this->prefixDirsPsr4; 130 | } 131 | 132 | /** 133 | * @return list 134 | */ 135 | public function getFallbackDirs() 136 | { 137 | return $this->fallbackDirsPsr0; 138 | } 139 | 140 | /** 141 | * @return list 142 | */ 143 | public function getFallbackDirsPsr4() 144 | { 145 | return $this->fallbackDirsPsr4; 146 | } 147 | 148 | /** 149 | * @return array Array of classname => path 150 | */ 151 | public function getClassMap() 152 | { 153 | return $this->classMap; 154 | } 155 | 156 | /** 157 | * @param array $classMap Class to filename map 158 | * 159 | * @return void 160 | */ 161 | public function addClassMap(array $classMap) 162 | { 163 | if ($this->classMap) { 164 | $this->classMap = array_merge($this->classMap, $classMap); 165 | } else { 166 | $this->classMap = $classMap; 167 | } 168 | } 169 | 170 | /** 171 | * Registers a set of PSR-0 directories for a given prefix, either 172 | * appending or prepending to the ones previously set for this prefix. 173 | * 174 | * @param string $prefix The prefix 175 | * @param list|string $paths The PSR-0 root directories 176 | * @param bool $prepend Whether to prepend the directories 177 | * 178 | * @return void 179 | */ 180 | public function add($prefix, $paths, $prepend = false) 181 | { 182 | $paths = (array) $paths; 183 | if (!$prefix) { 184 | if ($prepend) { 185 | $this->fallbackDirsPsr0 = array_merge( 186 | $paths, 187 | $this->fallbackDirsPsr0 188 | ); 189 | } else { 190 | $this->fallbackDirsPsr0 = array_merge( 191 | $this->fallbackDirsPsr0, 192 | $paths 193 | ); 194 | } 195 | 196 | return; 197 | } 198 | 199 | $first = $prefix[0]; 200 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 201 | $this->prefixesPsr0[$first][$prefix] = $paths; 202 | 203 | return; 204 | } 205 | if ($prepend) { 206 | $this->prefixesPsr0[$first][$prefix] = array_merge( 207 | $paths, 208 | $this->prefixesPsr0[$first][$prefix] 209 | ); 210 | } else { 211 | $this->prefixesPsr0[$first][$prefix] = array_merge( 212 | $this->prefixesPsr0[$first][$prefix], 213 | $paths 214 | ); 215 | } 216 | } 217 | 218 | /** 219 | * Registers a set of PSR-4 directories for a given namespace, either 220 | * appending or prepending to the ones previously set for this namespace. 221 | * 222 | * @param string $prefix The prefix/namespace, with trailing '\\' 223 | * @param list|string $paths The PSR-4 base directories 224 | * @param bool $prepend Whether to prepend the directories 225 | * 226 | * @throws \InvalidArgumentException 227 | * 228 | * @return void 229 | */ 230 | public function addPsr4($prefix, $paths, $prepend = false) 231 | { 232 | $paths = (array) $paths; 233 | if (!$prefix) { 234 | // Register directories for the root namespace. 235 | if ($prepend) { 236 | $this->fallbackDirsPsr4 = array_merge( 237 | $paths, 238 | $this->fallbackDirsPsr4 239 | ); 240 | } else { 241 | $this->fallbackDirsPsr4 = array_merge( 242 | $this->fallbackDirsPsr4, 243 | $paths 244 | ); 245 | } 246 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 247 | // Register directories for a new namespace. 248 | $length = strlen($prefix); 249 | if ('\\' !== $prefix[$length - 1]) { 250 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 251 | } 252 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 253 | $this->prefixDirsPsr4[$prefix] = $paths; 254 | } elseif ($prepend) { 255 | // Prepend directories for an already registered namespace. 256 | $this->prefixDirsPsr4[$prefix] = array_merge( 257 | $paths, 258 | $this->prefixDirsPsr4[$prefix] 259 | ); 260 | } else { 261 | // Append directories for an already registered namespace. 262 | $this->prefixDirsPsr4[$prefix] = array_merge( 263 | $this->prefixDirsPsr4[$prefix], 264 | $paths 265 | ); 266 | } 267 | } 268 | 269 | /** 270 | * Registers a set of PSR-0 directories for a given prefix, 271 | * replacing any others previously set for this prefix. 272 | * 273 | * @param string $prefix The prefix 274 | * @param list|string $paths The PSR-0 base directories 275 | * 276 | * @return void 277 | */ 278 | public function set($prefix, $paths) 279 | { 280 | if (!$prefix) { 281 | $this->fallbackDirsPsr0 = (array) $paths; 282 | } else { 283 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 284 | } 285 | } 286 | 287 | /** 288 | * Registers a set of PSR-4 directories for a given namespace, 289 | * replacing any others previously set for this namespace. 290 | * 291 | * @param string $prefix The prefix/namespace, with trailing '\\' 292 | * @param list|string $paths The PSR-4 base directories 293 | * 294 | * @throws \InvalidArgumentException 295 | * 296 | * @return void 297 | */ 298 | public function setPsr4($prefix, $paths) 299 | { 300 | if (!$prefix) { 301 | $this->fallbackDirsPsr4 = (array) $paths; 302 | } else { 303 | $length = strlen($prefix); 304 | if ('\\' !== $prefix[$length - 1]) { 305 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 306 | } 307 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 308 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 309 | } 310 | } 311 | 312 | /** 313 | * Turns on searching the include path for class files. 314 | * 315 | * @param bool $useIncludePath 316 | * 317 | * @return void 318 | */ 319 | public function setUseIncludePath($useIncludePath) 320 | { 321 | $this->useIncludePath = $useIncludePath; 322 | } 323 | 324 | /** 325 | * Can be used to check if the autoloader uses the include path to check 326 | * for classes. 327 | * 328 | * @return bool 329 | */ 330 | public function getUseIncludePath() 331 | { 332 | return $this->useIncludePath; 333 | } 334 | 335 | /** 336 | * Turns off searching the prefix and fallback directories for classes 337 | * that have not been registered with the class map. 338 | * 339 | * @param bool $classMapAuthoritative 340 | * 341 | * @return void 342 | */ 343 | public function setClassMapAuthoritative($classMapAuthoritative) 344 | { 345 | $this->classMapAuthoritative = $classMapAuthoritative; 346 | } 347 | 348 | /** 349 | * Should class lookup fail if not found in the current class map? 350 | * 351 | * @return bool 352 | */ 353 | public function isClassMapAuthoritative() 354 | { 355 | return $this->classMapAuthoritative; 356 | } 357 | 358 | /** 359 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 360 | * 361 | * @param string|null $apcuPrefix 362 | * 363 | * @return void 364 | */ 365 | public function setApcuPrefix($apcuPrefix) 366 | { 367 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 368 | } 369 | 370 | /** 371 | * The APCu prefix in use, or null if APCu caching is not enabled. 372 | * 373 | * @return string|null 374 | */ 375 | public function getApcuPrefix() 376 | { 377 | return $this->apcuPrefix; 378 | } 379 | 380 | /** 381 | * Registers this instance as an autoloader. 382 | * 383 | * @param bool $prepend Whether to prepend the autoloader or not 384 | * 385 | * @return void 386 | */ 387 | public function register($prepend = false) 388 | { 389 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 390 | 391 | if (null === $this->vendorDir) { 392 | return; 393 | } 394 | 395 | if ($prepend) { 396 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 397 | } else { 398 | unset(self::$registeredLoaders[$this->vendorDir]); 399 | self::$registeredLoaders[$this->vendorDir] = $this; 400 | } 401 | } 402 | 403 | /** 404 | * Unregisters this instance as an autoloader. 405 | * 406 | * @return void 407 | */ 408 | public function unregister() 409 | { 410 | spl_autoload_unregister(array($this, 'loadClass')); 411 | 412 | if (null !== $this->vendorDir) { 413 | unset(self::$registeredLoaders[$this->vendorDir]); 414 | } 415 | } 416 | 417 | /** 418 | * Loads the given class or interface. 419 | * 420 | * @param string $class The name of the class 421 | * @return true|null True if loaded, null otherwise 422 | */ 423 | public function loadClass($class) 424 | { 425 | if ($file = $this->findFile($class)) { 426 | $includeFile = self::$includeFile; 427 | $includeFile($file); 428 | 429 | return true; 430 | } 431 | 432 | return null; 433 | } 434 | 435 | /** 436 | * Finds the path to the file where the class is defined. 437 | * 438 | * @param string $class The name of the class 439 | * 440 | * @return string|false The path if found, false otherwise 441 | */ 442 | public function findFile($class) 443 | { 444 | // class map lookup 445 | if (isset($this->classMap[$class])) { 446 | return $this->classMap[$class]; 447 | } 448 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 449 | return false; 450 | } 451 | if (null !== $this->apcuPrefix) { 452 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 453 | if ($hit) { 454 | return $file; 455 | } 456 | } 457 | 458 | $file = $this->findFileWithExtension($class, '.php'); 459 | 460 | // Search for Hack files if we are running on HHVM 461 | if (false === $file && defined('HHVM_VERSION')) { 462 | $file = $this->findFileWithExtension($class, '.hh'); 463 | } 464 | 465 | if (null !== $this->apcuPrefix) { 466 | apcu_add($this->apcuPrefix.$class, $file); 467 | } 468 | 469 | if (false === $file) { 470 | // Remember that this class does not exist. 471 | $this->missingClasses[$class] = true; 472 | } 473 | 474 | return $file; 475 | } 476 | 477 | /** 478 | * Returns the currently registered loaders keyed by their corresponding vendor directories. 479 | * 480 | * @return array 481 | */ 482 | public static function getRegisteredLoaders() 483 | { 484 | return self::$registeredLoaders; 485 | } 486 | 487 | /** 488 | * @param string $class 489 | * @param string $ext 490 | * @return string|false 491 | */ 492 | private function findFileWithExtension($class, $ext) 493 | { 494 | // PSR-4 lookup 495 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 496 | 497 | $first = $class[0]; 498 | if (isset($this->prefixLengthsPsr4[$first])) { 499 | $subPath = $class; 500 | while (false !== $lastPos = strrpos($subPath, '\\')) { 501 | $subPath = substr($subPath, 0, $lastPos); 502 | $search = $subPath . '\\'; 503 | if (isset($this->prefixDirsPsr4[$search])) { 504 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 505 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 506 | if (file_exists($file = $dir . $pathEnd)) { 507 | return $file; 508 | } 509 | } 510 | } 511 | } 512 | } 513 | 514 | // PSR-4 fallback dirs 515 | foreach ($this->fallbackDirsPsr4 as $dir) { 516 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 517 | return $file; 518 | } 519 | } 520 | 521 | // PSR-0 lookup 522 | if (false !== $pos = strrpos($class, '\\')) { 523 | // namespaced class name 524 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 525 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 526 | } else { 527 | // PEAR-like class name 528 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 529 | } 530 | 531 | if (isset($this->prefixesPsr0[$first])) { 532 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 533 | if (0 === strpos($class, $prefix)) { 534 | foreach ($dirs as $dir) { 535 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 536 | return $file; 537 | } 538 | } 539 | } 540 | } 541 | } 542 | 543 | // PSR-0 fallback dirs 544 | foreach ($this->fallbackDirsPsr0 as $dir) { 545 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 546 | return $file; 547 | } 548 | } 549 | 550 | // PSR-0 include paths. 551 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 552 | return $file; 553 | } 554 | 555 | return false; 556 | } 557 | 558 | /** 559 | * @return void 560 | */ 561 | private static function initializeIncludeClosure() 562 | { 563 | if (self::$includeFile !== null) { 564 | return; 565 | } 566 | 567 | /** 568 | * Scope isolated include. 569 | * 570 | * Prevents access to $this/self from included files. 571 | * 572 | * @param string $file 573 | * @return void 574 | */ 575 | self::$includeFile = \Closure::bind(static function($file) { 576 | include $file; 577 | }, null, null); 578 | } 579 | } 580 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Nils Adermann, Jordi Boggiano 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/src'), 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 33 | 34 | return $loader; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'Devrabiul\\LaravelCookieConsent\\' => 31, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'Devrabiul\\LaravelCookieConsent\\' => 18 | array ( 19 | 0 => __DIR__ . '/../..' . '/src', 20 | ), 21 | ); 22 | 23 | public static $classMap = array ( 24 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 25 | ); 26 | 27 | public static function getInitializer(ClassLoader $loader) 28 | { 29 | return \Closure::bind(function () use ($loader) { 30 | $loader->prefixLengthsPsr4 = ComposerStaticInit097b259ba67d03f0111b724291f0acdf::$prefixLengthsPsr4; 31 | $loader->prefixDirsPsr4 = ComposerStaticInit097b259ba67d03f0111b724291f0acdf::$prefixDirsPsr4; 32 | $loader->classMap = ComposerStaticInit097b259ba67d03f0111b724291f0acdf::$classMap; 33 | 34 | }, null, ClassLoader::class); 35 | } 36 | } 37 | --------------------------------------------------------------------------------