├── .gitattributes ├── .gitignore ├── LICENSE ├── README.MD ├── composer.json ├── composer.lock ├── css ├── datatables.min.css └── firebase_admin.css ├── docs └── assets │ ├── firebase_config_manager_diagram.png │ └── module_screenshot.png ├── export_codebase.sh ├── firebase_config_manager.form.yml ├── firebase_config_manager.info.yml ├── firebase_config_manager.libraries.yml ├── firebase_config_manager.links.menu.yml ├── firebase_config_manager.permissions.yml ├── firebase_config_manager.routing.yml ├── firebase_config_manager.services.yml ├── js ├── datatables.min.js ├── firebase_datatables.js └── firebase_editor.js └── src ├── Controller └── FirebaseUpdateController.php ├── Form └── FirebaseAdminForm.php └── Service └── FirebaseService.php /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | /sites/default/files/config_* 3 | /sites/default/files/private/* 4 | /sites/default/files/tmp/* 5 | /vendor -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Daynis Olman 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 | # Firebase Config Manager for Drupal 2 | 3 | [![Drupal 10 & 11 Compatible](https://img.shields.io/badge/Drupal-10%20%26%2011-blue.svg)](https://www.drupal.org/) 4 | [![License: GPL v2+](https://img.shields.io/badge/License-GPL_v2+-green.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 5 | [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](/) 6 | 7 | A Drupal module that provides a user-friendly interface for managing data within a Google Firebase Firestore database directly from the Drupal admin panel. This module allows administrators to securely connect to Firestore, browse collections, and edit document fields. 8 | 9 | 10 | ## Features 11 | 12 | * **Secure Connection:** Securely connects to your Firebase project using service account credentials stored as an environment variable. Credentials are *never* stored directly in the Drupal database. 13 | * **Dynamic Collection Browsing:** Automatically lists available Firestore collections. 14 | * **Document Editing:** Provides a table-based interface for viewing and editing string and integer fields within Firestore documents. 15 | * **Real-time Updates:** Changes made in the Drupal admin are immediately reflected in your Firestore database. 16 | * **Data Integrity:** Restricts editing to string and integer fields to prevent accidental data corruption. 17 | * **AJAX-Powered Interface:** Uses AJAX for a responsive user experience without full page reloads. 18 | * **Drupal Permissions Integration:** Uses Drupal's permission system to control access to the module's features. 19 | * **Comprehensive Logging:** Logs all Firebase operations for auditing and troubleshooting. 20 | 21 | ## Module Screenshot 22 | 23 | ![Firebase Config Manager Diagram](docs/assets/module_screenshot.png) 24 | 25 | ## Requirements 26 | 27 | * Drupal 10 or 11 28 | * Composer 29 | * A Google Firebase project with Firestore enabled. 30 | * A Firebase service account with appropriate permissions to access Firestore. 31 | 32 | ## Sequence Diagram 33 | 34 | ![Firebase Config Manager Diagram](docs/assets/firebase_config_manager_diagram.png) 35 | 36 | ## Installation 37 | 38 | 1. **Install via Composer (Recommended):** 39 | 40 | ```bash 41 | composer require drupal/firebase_config_manager 42 | ``` 43 | This is the preferred method, as it automatically manages the required dependencies (`google/cloud-firestore` and `kreait/firebase-php`). 44 | 45 | 2. **Manual Installation (If Necessary):** 46 | * Place the `firebase_config_manager` directory in your Drupal `modules/contrib` directory. 47 | * Ensure the `google/cloud-firestore` and `kreait/firebase-php` libraries are installed via Composer in your Drupal root directory: 48 | ```bash 49 | cd /path/to/your/drupal/root 50 | composer require google/cloud-firestore kreait/firebase-php 51 | ``` 52 | 53 | 3. **Enable the Module:** Go to `Admin > Extend` (or `/admin/modules`) and enable the "Firebase Config Manager" module. 54 | 55 | ## Configuration 56 | 57 | 1. **Firebase Service Account:** 58 | * In the Google Cloud Console ([https://console.cloud.google.com/](https://console.cloud.google.com/)), navigate to your Firebase project. 59 | * Go to **Project settings > Service accounts**. 60 | * Click **Generate new private key**. This will download a JSON file containing your service account credentials. 61 | * **Important:** *Do not* store this JSON file directly in your codebase. 62 | 63 | 2. **Environment Variable:** 64 | * Set an environment variable on your server (e.g., `FIREBASE_CONFIG`) to hold the *contents* of the JSON key file. The method for setting environment variables depends on your server environment (Bitnami, Apache virtual host, cPanel, etc.). Consult your hosting provider's documentation. 65 | 66 | * **Example (Linux, .bashrc or .bash_profile):** 67 | ```bash 68 | export FIREBASE_CONFIG='{ ... your JSON key content ... }' 69 | ``` 70 | * **Example (Bitnami - RECOMMENDED):** Bitnami recommends using the `bnconfig` tool or modifying the `setenv.sh` file. **This is the best approach for Bitnami.** See: [https://docs.bitnami.com/general/apps/drupal/administration/configure-environment-variables/](https://docs.bitnami.com/general/apps/drupal/administration/configure-environment-variables/) 71 | A common location for `setenv.sh` is `/opt/bitnami/apache/conf/vhosts/`. You would add a line like this *inside* the virtual host configuration: 72 | 73 | ```bash 74 | SetEnv FIREBASE_CONFIG "{ ... your JSON key content ... }" 75 | ``` 76 | *Restart Apache after setting the environment variable.* 77 | 78 | * **Example (.htaccess - NOT RECOMMENDED for security reasons):** 79 | ``` 80 | SetEnv FIREBASE_CONFIG "{ ... your JSON key content ... }" 81 | ``` 82 | Using `.htaccess` is generally *discouraged* for storing sensitive credentials. It's less secure than using server-level environment variables. 83 | 84 | 3. **Drupal Configuration:** 85 | * Go to `Configuration > Web services > Firebase Config Manager` (or `/admin/config/web-services/firebase`) in your Drupal admin. 86 | * Enter the name of the environment variable you set (e.g., `FIREBASE_CONFIG`) in the "Firebase JSON Key Environment Variable" field. 87 | * Click "Save Configuration". 88 | * If the connection is successful, you'll see a dropdown to select a Firestore collection. Choose the collection you want to manage. 89 | 90 | ## Usage 91 | 92 | 1. After configuring the module and selecting a collection, the module will display a table of documents from that collection. 93 | 2. You can edit the values of string and integer fields directly in the table. 94 | 3. Changes are automatically saved to Firestore when you modify a field and move focus away from it (e.g., by clicking outside the input field or pressing Tab). 95 | 4. Visual feedback (green background for success, red for error) indicates the status of the update. 96 | 97 | ## Permissions 98 | 99 | * **`administer site configuration`:** Required to configure the Firebase environment variable (initial setup). 100 | * **`manage firebase config`:** Required to view and edit Firestore documents. Grant this permission to users who need to manage data in Firestore. 101 | 102 | ## Troubleshooting 103 | 104 | * **"Class 'Google\Cloud\Firestore\FirestoreClient' not found" error:** 105 | * Ensure the `google/cloud-firestore` and `kreait/firebase-php` libraries are installed correctly via Composer. Run `composer install` in your Drupal root directory. 106 | * Clear the Drupal cache: `drush cr` (from within your module directory or Drupal root). 107 | * Restart PHP-FPM: `sudo /opt/bitnami/ctlscript.sh restart php-fpm` (or the appropriate command for your server). 108 | * **"Missing parameters" error:** 109 | * Check your browser's developer console (Network tab) to see the data being sent in the AJAX request. Verify that `collection`, `doc`, `field`, and `value` are all present and correct. 110 | * Check Drupal's recent log messages (`/admin/reports/dblog`) for more detailed error information. 111 | * **Menu link not appearing:** 112 | * Verify that `firebase_config_manager.links.menu.yml` exists and has the correct `route_name` (matching `firebase_config_manager.routing.yml`). 113 | * Clear Drupal caches: `drush cr` 114 | * Ensure the user has the required `administer site configuration` permission. 115 | * **Data not saving to Firestore:** 116 | * Double-check your Firebase service account permissions in the Google Cloud Console. Make sure it has write access to Firestore. 117 | * Inspect the Network tab in your browser's developer tools to see the response from the AJAX request. Any errors from Firestore will be reported there. 118 | 119 | ## Security 120 | 121 | * **Never store your Firebase service account key directly in your code or Drupal's database.** Use environment variables, as described above. 122 | * Grant the `manage firebase config` permission only to trusted users. 123 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drupal/firebase_config_manager", 3 | "type": "drupal-module", 4 | "description": "Firebase Config Manager for Drupal.", 5 | "license": "GPL-2.0-or-later", 6 | "require": { 7 | "php": ">=8.1", 8 | "kreait/firebase-php": "^7.0", 9 | "drupal/core": "^10 || ^11", 10 | "google/cloud-firestore": "^1.48" 11 | }, 12 | "autoload": { 13 | "psr-4": { 14 | "Drupal\\firebase_config_manager\\": "src/" 15 | } 16 | }, 17 | "extra": { 18 | "installer-name": "firebase_config_manager" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /css/datatables.min.css: -------------------------------------------------------------------------------- 1 | :root{--dt-row-selected: 13, 110, 253;--dt-row-selected-text: 255, 255, 255;--dt-row-selected-link: 9, 10, 11;--dt-row-stripe: 0, 0, 0;--dt-row-hover: 0, 0, 0;--dt-column-ordering: 0, 0, 0;--dt-html-background: white}:root.dark{--dt-html-background: rgb(33, 37, 41)}table.dataTable td.dt-control{text-align:center;cursor:pointer}table.dataTable td.dt-control:before{display:inline-block;color:rgba(0, 0, 0, 0.5);content:"►"}table.dataTable tr.dt-hasChild td.dt-control:before{content:"▼"}html.dark table.dataTable td.dt-control:before{color:rgba(255, 255, 255, 0.5)}html.dark table.dataTable tr.dt-hasChild td.dt-control:before{color:rgba(255, 255, 255, 0.5)}table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting_asc_disabled,table.dataTable thead>tr>th.sorting_desc_disabled,table.dataTable thead>tr>td.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting_asc_disabled,table.dataTable thead>tr>td.sorting_desc_disabled{cursor:pointer;position:relative;padding-right:26px}table.dataTable thead>tr>th.sorting:before,table.dataTable thead>tr>th.sorting:after,table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_asc:after,table.dataTable thead>tr>th.sorting_desc:before,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>th.sorting_asc_disabled:after,table.dataTable thead>tr>th.sorting_desc_disabled:before,table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting:before,table.dataTable thead>tr>td.sorting:after,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_asc:after,table.dataTable thead>tr>td.sorting_desc:before,table.dataTable thead>tr>td.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_asc_disabled:after,table.dataTable thead>tr>td.sorting_desc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:after{position:absolute;display:block;opacity:.125;right:10px;line-height:9px;font-size:.8em}table.dataTable thead>tr>th.sorting:before,table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_desc:before,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>th.sorting_desc_disabled:before,table.dataTable thead>tr>td.sorting:before,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_desc:before,table.dataTable thead>tr>td.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:before{bottom:50%;content:"▲";content:"▲"/""}table.dataTable thead>tr>th.sorting:after,table.dataTable thead>tr>th.sorting_asc:after,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>th.sorting_asc_disabled:after,table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting:after,table.dataTable thead>tr>td.sorting_asc:after,table.dataTable thead>tr>td.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc_disabled:after,table.dataTable thead>tr>td.sorting_desc_disabled:after{top:50%;content:"▼";content:"▼"/""}table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_desc:after{opacity:.6}table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting_asc_disabled:before{display:none}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}div.dataTables_scrollBody>table.dataTable>thead>tr>th:before,div.dataTables_scrollBody>table.dataTable>thead>tr>th:after,div.dataTables_scrollBody>table.dataTable>thead>tr>td:before,div.dataTables_scrollBody>table.dataTable>thead>tr>td:after{display:none}div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:2px}div.dataTables_processing>div:last-child{position:relative;width:80px;height:15px;margin:1em auto}div.dataTables_processing>div:last-child>div{position:absolute;top:0;width:13px;height:13px;border-radius:50%;background:rgb(13, 110, 253);background:rgb(var(--dt-row-selected));animation-timing-function:cubic-bezier(0, 1, 1, 0)}div.dataTables_processing>div:last-child>div:nth-child(1){left:8px;animation:datatables-loader-1 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(2){left:8px;animation:datatables-loader-2 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(3){left:32px;animation:datatables-loader-2 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(4){left:56px;animation:datatables-loader-3 .6s infinite}@keyframes datatables-loader-1{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes datatables-loader-3{0%{transform:scale(1)}100%{transform:scale(0)}}@keyframes datatables-loader-2{0%{transform:translate(0, 0)}100%{transform:translate(24px, 0)}}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th,table.dataTable thead td,table.dataTable tfoot th,table.dataTable tfoot td{text-align:left}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable>thead>tr>th,table.dataTable>thead>tr>td{padding:10px;border-bottom:1px solid rgba(0, 0, 0, 0.3)}table.dataTable>thead>tr>th:active,table.dataTable>thead>tr>td:active{outline:none}table.dataTable>tfoot>tr>th,table.dataTable>tfoot>tr>td{padding:10px 10px 6px 10px;border-top:1px solid rgba(0, 0, 0, 0.3)}table.dataTable tbody tr{background-color:transparent}table.dataTable tbody tr.selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.9);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.9);color:rgb(255, 255, 255);color:rgb(var(--dt-row-selected-text))}table.dataTable tbody tr.selected a{color:rgb(9, 10, 11);color:rgb(var(--dt-row-selected-link))}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border>tbody>tr>th,table.dataTable.row-border>tbody>tr>td,table.dataTable.display>tbody>tr>th,table.dataTable.display>tbody>tr>td{border-top:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.row-border>tbody>tr:first-child>th,table.dataTable.row-border>tbody>tr:first-child>td,table.dataTable.display>tbody>tr:first-child>th,table.dataTable.display>tbody>tr:first-child>td{border-top:none}table.dataTable.row-border>tbody>tr.selected+tr.selected>td,table.dataTable.display>tbody>tr.selected+tr.selected>td{border-top-color:#0262ef}table.dataTable.cell-border>tbody>tr>th,table.dataTable.cell-border>tbody>tr>td{border-top:1px solid rgba(0, 0, 0, 0.15);border-right:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.cell-border>tbody>tr>th:first-child,table.dataTable.cell-border>tbody>tr>td:first-child{border-left:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.cell-border>tbody>tr:first-child>th,table.dataTable.cell-border>tbody>tr:first-child>td{border-top:none}table.dataTable.stripe>tbody>tr.odd>*,table.dataTable.display>tbody>tr.odd>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.023);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.023)}table.dataTable.stripe>tbody>tr.odd.selected>*,table.dataTable.display>tbody>tr.odd.selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.923);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.923)}table.dataTable.hover>tbody>tr:hover>*,table.dataTable.display>tbody>tr:hover>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.035);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.035)}table.dataTable.hover>tbody>tr.selected:hover>*,table.dataTable.display>tbody>tr.selected:hover>*{box-shadow:inset 0 0 0 9999px #0d6efd !important;box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 1) !important}table.dataTable.order-column>tbody tr>.sorting_1,table.dataTable.order-column>tbody tr>.sorting_2,table.dataTable.order-column>tbody tr>.sorting_3,table.dataTable.display>tbody tr>.sorting_1,table.dataTable.display>tbody tr>.sorting_2,table.dataTable.display>tbody tr>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.019);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019)}table.dataTable.order-column>tbody tr.selected>.sorting_1,table.dataTable.order-column>tbody tr.selected>.sorting_2,table.dataTable.order-column>tbody tr.selected>.sorting_3,table.dataTable.display>tbody tr.selected>.sorting_1,table.dataTable.display>tbody tr.selected>.sorting_2,table.dataTable.display>tbody tr.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.919);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919)}table.dataTable.display>tbody>tr.odd>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.054);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.054)}table.dataTable.display>tbody>tr.odd>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.047);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.047)}table.dataTable.display>tbody>tr.odd>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.039);box-shadow:inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.039)}table.dataTable.display>tbody>tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.954);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.954)}table.dataTable.display>tbody>tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.947);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.947)}table.dataTable.display>tbody>tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.939);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.939)}table.dataTable.display>tbody>tr.even>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.019);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.019)}table.dataTable.display>tbody>tr.even>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.011);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.011)}table.dataTable.display>tbody>tr.even>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.003);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.003)}table.dataTable.display>tbody>tr.even.selected>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.919);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919)}table.dataTable.display>tbody>tr.even.selected>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.911);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.911)}table.dataTable.display>tbody>tr.even.selected>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.903);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.903)}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.082);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.082)}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.074);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.074)}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.062);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.062)}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.982);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.982)}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.974);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.974)}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.962);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.962)}table.dataTable.no-footer{border-bottom:1px solid rgba(0, 0, 0, 0.3)}table.dataTable.compact thead th,table.dataTable.compact thead td,table.dataTable.compact tfoot th,table.dataTable.compact tfoot td,table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_length select{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;color:inherit;padding:4px}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;color:inherit;margin-left:3px}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;color:inherit !important;border:1px solid transparent;border-radius:2px;background:transparent}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:inherit !important;border:1px solid rgba(0, 0, 0, 0.3);background-color:rgba(0, 0, 0, 0.05);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.05)), color-stop(100%, rgba(0, 0, 0, 0.05)));background:-webkit-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%);background:-moz-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%);background:-ms-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%);background:-o-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%);background:linear-gradient(to bottom, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#111;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#0c0c0c;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:inherit}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid rgba(0, 0, 0, 0.3)}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}}html.dark{--dt-row-hover: 255, 255, 255;--dt-row-stripe: 255, 255, 255;--dt-column-ordering: 255, 255, 255}html.dark table.dataTable>thead>tr>th,html.dark table.dataTable>thead>tr>td{border-bottom:1px solid rgb(89, 91, 94)}html.dark table.dataTable>thead>tr>th:active,html.dark table.dataTable>thead>tr>td:active{outline:none}html.dark table.dataTable>tfoot>tr>th,html.dark table.dataTable>tfoot>tr>td{border-top:1px solid rgb(89, 91, 94)}html.dark table.dataTable.row-border>tbody>tr>th,html.dark table.dataTable.row-border>tbody>tr>td,html.dark table.dataTable.display>tbody>tr>th,html.dark table.dataTable.display>tbody>tr>td{border-top:1px solid rgb(64, 67, 70)}html.dark table.dataTable.row-border>tbody>tr.selected+tr.selected>td,html.dark table.dataTable.display>tbody>tr.selected+tr.selected>td{border-top-color:#0257d5}html.dark table.dataTable.cell-border>tbody>tr>th,html.dark table.dataTable.cell-border>tbody>tr>td{border-top:1px solid rgb(64, 67, 70);border-right:1px solid rgb(64, 67, 70)}html.dark table.dataTable.cell-border>tbody>tr>th:first-child,html.dark table.dataTable.cell-border>tbody>tr>td:first-child{border-left:1px solid rgb(64, 67, 70)}html.dark .dataTables_wrapper .dataTables_filter input,html.dark .dataTables_wrapper .dataTables_length select{border:1px solid rgba(255, 255, 255, 0.2);background-color:var(--dt-html-background)}html.dark .dataTables_wrapper .dataTables_paginate .paginate_button.current,html.dark .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{border:1px solid rgb(89, 91, 94);background:rgba(255, 255, 255, 0.15)}html.dark .dataTables_wrapper .dataTables_paginate .paginate_button.disabled,html.dark .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,html.dark .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{color:#666 !important}html.dark .dataTables_wrapper .dataTables_paginate .paginate_button:hover{border:1px solid rgb(53, 53, 53);background:rgb(53, 53, 53)}html.dark .dataTables_wrapper .dataTables_paginate .paginate_button:active{background:#3a3a3a} 2 | -------------------------------------------------------------------------------- /css/firebase_admin.css: -------------------------------------------------------------------------------- 1 | /* File: /bitnami/drupal/modules/contrib/myfolder/css/firebase_admin.css */ 2 | 3 | .firebase-admin-form { 4 | background: #f8f9fa; /* Light gray background */ 5 | padding: 20px; 6 | border-radius: 8px; 7 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); 8 | margin-bottom: 20px; 9 | } 10 | 11 | .form-item label { 12 | font-weight: bold; 13 | display: block; 14 | margin-bottom: 5px; 15 | color: #343a40; /* Dark gray for labels */ 16 | } 17 | 18 | .form-textarea-wrapper textarea, 19 | .firebase-edit { 20 | width: 100%; 21 | padding: 10px; /* Increased padding for better touch targets */ 22 | border: 1px solid #ced4da; /* Lighter border */ 23 | border-radius: 4px; 24 | box-sizing: border-box; 25 | font-family: monospace; 26 | font-size: 14px; 27 | transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; /* Smooth transition */ 28 | } 29 | 30 | .form-textarea-wrapper textarea:focus, 31 | .firebase-edit:focus { 32 | border-color: #80bdff; /* Blue focus outline */ 33 | outline: 0; 34 | box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); /* Standard focus outline */ 35 | } 36 | 37 | #firestore-documents-table { 38 | width: 100%; 39 | border-collapse: collapse; 40 | margin-top: 20px; 41 | } 42 | 43 | #firestore-documents-table th, 44 | #firestore-documents-table td { 45 | padding: 10px; 46 | text-align: left; 47 | border-bottom: 1px solid #dee2e6; /* Lighter border for rows */ 48 | } 49 | 50 | #firestore-documents-table th { 51 | background-color: #e9ecef; /* Light gray header background */ 52 | font-weight: bold; 53 | } 54 | 55 | /* Success/Error Feedback */ 56 | .firebase-edit.success { 57 | background-color: #d4edda; /* Green background for success */ 58 | border-color: #c3e6cb; 59 | } 60 | 61 | .firebase-edit.error { 62 | background-color: #f8d7da; /* Red background for error */ 63 | border-color: #f5c6cb; 64 | } 65 | 66 | /* General Form Styling */ 67 | .form-item { 68 | margin-bottom: 15px; 69 | } 70 | 71 | /* Spacing for Form Elements */ 72 | .form-textarea-wrapper, 73 | .form-select { 74 | margin-top: 5px; 75 | } 76 | 77 | /* Error Messages */ 78 | .error-message { 79 | color: #dc3545; /* Bootstrap danger color */ 80 | margin-top: 5px; 81 | font-size: 0.875rem; 82 | } -------------------------------------------------------------------------------- /docs/assets/firebase_config_manager_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daynis-olman/firebase_config_manager/7b6e8ba293d6015b4bde31dae9e12d40f09a0887/docs/assets/firebase_config_manager_diagram.png -------------------------------------------------------------------------------- /docs/assets/module_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daynis-olman/firebase_config_manager/7b6e8ba293d6015b4bde31dae9e12d40f09a0887/docs/assets/module_screenshot.png -------------------------------------------------------------------------------- /export_codebase.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Output file 4 | OUTPUT_FILE="codebase.txt" 5 | 6 | # Get the module root directory 7 | BASE_DIR="$(pwd)" 8 | 9 | # Clear the output file 10 | > "$OUTPUT_FILE" 11 | 12 | echo "Exporting only the relevant Drupal module files..." 13 | echo "Root directory: $BASE_DIR" 14 | 15 | # Strictly limit search to the current directory, max depth 3, and exclude unnecessary files 16 | find "$BASE_DIR" -mindepth 1 -maxdepth 3 -type f \ 17 | ! -path "$BASE_DIR/vendor/*" \ 18 | ! -path "$BASE_DIR/.git/*" \ 19 | ! -path "$BASE_DIR/exported_files_debug.txt" \ 20 | ! -path "$BASE_DIR/$OUTPUT_FILE" \ 21 | ! -path "$BASE_DIR/export_codebase.sh" \ 22 | ! -name "composer.lock" \ 23 | ! -name "LICENSE" \ 24 | ! -name "*.png" \ 25 | ! -name "*.jpg" \ 26 | ! -name "*.gif" \ 27 | ! -name "*.mp4" \ 28 | ! -name "*.mp3" \ 29 | ! -name "*.zip" \ 30 | ! -name "*.tar.gz" \ 31 | ! -name "*.log" \ 32 | ! -name "*.cache" \ 33 | -size -5M | while read -r file; do 34 | 35 | # Add file path as a comment at the top 36 | echo "# File: $file" >> "$OUTPUT_FILE" 37 | echo "" >> "$OUTPUT_FILE" 38 | 39 | # Append file contents 40 | cat "$file" >> "$OUTPUT_FILE" || echo "[Error reading file: $file]" >> "$OUTPUT_FILE" 41 | 42 | # Add end file comment 43 | echo "" >> "$OUTPUT_FILE" 44 | echo "# end file" >> "$OUTPUT_FILE" 45 | echo "" >> "$OUTPUT_FILE" 46 | done 47 | 48 | echo "Export complete: $(du -h "$OUTPUT_FILE" | cut -f1) saved in $OUTPUT_FILE" -------------------------------------------------------------------------------- /firebase_config_manager.form.yml: -------------------------------------------------------------------------------- 1 | firebase_config_manager.admin_settings: 2 | type: form 3 | form: 4 | - 'firebase_key' # Key input 5 | - 'project_id_display' # Firebase project ID display 6 | - 'firebase_key_update' # Button to update Firebase credentials -------------------------------------------------------------------------------- /firebase_config_manager.info.yml: -------------------------------------------------------------------------------- 1 | name: 'Firebase Config Manager' 2 | type: module 3 | description: 'A module to manage Firebase Firestore and Firebase Remote Config from Drupal.' 4 | core_version_requirement: ^10 || ^11 5 | package: Custom 6 | dependencies: 7 | - drupal:system 8 | - drupal:config 9 | - drupal:admin_toolbar 10 | - drupal:menu_ui -------------------------------------------------------------------------------- /firebase_config_manager.libraries.yml: -------------------------------------------------------------------------------- 1 | firebase_admin_styles: 2 | version: 1.x 3 | css: 4 | theme: 5 | css/firebase_admin.css: {} 6 | 7 | firebase_editor: 8 | version: 1.x 9 | js: 10 | js/firebase_editor.js: {} 11 | dependencies: 12 | - core/jquery 13 | 14 | firebase_datatables: 15 | version: 1.x 16 | js: 17 | js/datatables.min.js: {} 18 | js/firebase_datatables.js: {} 19 | css: 20 | theme: 21 | css/datatables.min.css: {} 22 | dependencies: 23 | - core/jquery 24 | - core/drupal.ajax 25 | - core/drupal.once -------------------------------------------------------------------------------- /firebase_config_manager.links.menu.yml: -------------------------------------------------------------------------------- 1 | firebase_config_manager.admin_settings: 2 | title: 'Firebase Manager' 3 | description: 'Configure Firebase Firestore and Remote Config settings.' 4 | route_name: firebase_config_manager.admin_settings 5 | parent: system.admin_config_services 6 | weight: 100 -------------------------------------------------------------------------------- /firebase_config_manager.permissions.yml: -------------------------------------------------------------------------------- 1 | manage firebase config: 2 | title: 'Manage Firebase Configuration' 3 | description: 'Allows users to manage Firebase configuration settings and Firestore documents.' 4 | restrict access: TRUE 5 | administer firebase config: 6 | title: 'Administer Firebase Configuration' 7 | description: 'Allows users to administer Firebase configuration settings' 8 | restrict access: TRUE -------------------------------------------------------------------------------- /firebase_config_manager.routing.yml: -------------------------------------------------------------------------------- 1 | firebase_config_manager.admin_settings: 2 | path: '/admin/config/web-services/firebase' 3 | defaults: 4 | _form: 'Drupal\firebase_config_manager\Form\FirebaseAdminForm' 5 | _title: 'Firebase Config Manager' 6 | methods: [GET, POST] 7 | requirements: 8 | _permission: 'administer site configuration' 9 | 10 | firebase_config_manager.update_document: 11 | path: '/admin/firebase/update-document' 12 | defaults: 13 | _controller: 'Drupal\firebase_config_manager\Controller\FirebaseUpdateController::updateDocument' 14 | _title: 'Update Firestore Document' 15 | methods: [POST] 16 | requirements: 17 | _permission: 'manage firebase config' 18 | 19 | firebase_config_manager.load_firestore_documents: 20 | path: '/admin/firebase/load-documents' 21 | defaults: 22 | _controller: 'Drupal\firebase_config_manager\Controller\FirebaseUpdateController::loadDocuments' 23 | _title: 'Load Firestore Documents' 24 | methods: [POST] 25 | requirements: 26 | _permission: 'manage firebase config' -------------------------------------------------------------------------------- /firebase_config_manager.services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | firebase_config_manager.service: 3 | class: 'Drupal\firebase_config_manager\Service\FirebaseService' 4 | arguments: ['@config.factory', '@logger.channel.default', '@file_system'] -------------------------------------------------------------------------------- /js/datatables.min.js: -------------------------------------------------------------------------------- 1 | /*! DataTables 1.13.6 2 | * ©2008-2023 SpryMedia Ltd - datatables.net/license 3 | */ 4 | !function(n){"use strict";var a;"function"==typeof define&&define.amd?define(["jquery"],function(t){return n(t,window,document)}):"object"==typeof exports?(a=require("jquery"),"undefined"==typeof window?module.exports=function(t,e){return t=t||window,e=e||a(t),n(e,t,t.document)}:n(a,window,window.document)):window.DataTable=n(jQuery,window,document)}(function(P,j,v,H){"use strict";function d(t){var e=parseInt(t,10);return!isNaN(e)&&isFinite(t)?e:null}function l(t,e,n){var a=typeof t,r="string"==a;return"number"==a||"bigint"==a||!!h(t)||(e&&r&&(t=$(t,e)),n&&r&&(t=t.replace(q,"")),!isNaN(parseFloat(t))&&isFinite(t))}function a(t,e,n){var a;return!!h(t)||(h(a=t)||"string"==typeof a)&&!!l(t.replace(V,"").replace(/