├── .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 | [](https://packagist.org/packages/devrabiul/laravel-cookie-consent)
8 | [](https://packagist.org/packages/devrabiul/laravel-cookie-consent)
9 | 
10 | 
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 |
14 |
15 |
16 |
17 |
18 | {{ $cookieConfig['cookie_title'] }}
19 |
20 |
21 |
{{ $cookieConfig['cookie_description'] }}
22 |
23 |
24 |
25 |
41 |
42 |
43 |
44 | @if (isset($cookieConfig['policy_links']) && count($cookieConfig['policy_links']) > 0)
45 |
57 | @endif
58 |
59 |
60 |
61 |
62 |
63 |
64 |
76 |
77 |
78 | {{ $cookieConfig['cookie_modal_intro'] }}
79 |
80 |
81 |
82 | @foreach ($cookieConfig['cookie_categories'] as $category => $details)
83 | @if ($details['enabled'])
84 |
85 |
95 |
{{ $details['description'] }}
96 |
97 | @endif
98 | @endforeach
99 |
100 |
101 |
116 |
117 |
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 |
--------------------------------------------------------------------------------