Page 1 of 1

Enabling / Disabling Hooks

Posted: Thu Jul 09, 2026 2:25 pm
by Omar_one
@azpro @beerbee @Kofod95
@ArtcoInc @14Steve14 and anyone interested in enabling/disabling Hooks.
what you think
hooks.gif
- One core-file override (hooks.php),
- sql (Adds `hooks_status`)
- 2 -3 new files

Re: No Core Changes - A different perspective

Posted: Thu Jul 09, 2026 2:39 pm
by bonbec
@Omar_one
Wow! That aligns with my view on this subject—bravo!
Sincerely, bravo!

Re: No Core Changes - A different perspective

Posted: Thu Jul 09, 2026 3:05 pm
by Omar_one
I plan to share the files today or tomorrow. If anyone would like to test them and help me get everything properly finalized, please let me know.

Re: No Core Changes - A different perspective

Posted: Fri Jul 10, 2026 7:01 am
by 14Steve14
Omar_one wrote: Thu Jul 09, 2026 2:25 pm @burt @azpro @beerbee @Kofod95
@ArtcoInc @14Steve14 and anyone interested in enabling/disabling Hooks.
what you think
hooks.gif

- One core-file override (hooks.php),
- sql (Adds `hooks_status`)
- 2 -3 new files
Looks an interesting feature

Re: No Core Changes - A different perspective

Posted: Fri Jul 10, 2026 8:06 am
by azpro
@Omar_one

Great stuff - Exactly what I had in mind.

Thank you for sharing!

Re: No Core Changes - A different perspective

Posted: Fri Jul 10, 2026 8:48 pm
by Omar_one
@azpro @beerbee @Kofod95
@ArtcoInc @14Steve14


- I added another filter for hook locations (Shop and Admin). The page will display hooks that are defined in override template also.

Code: Select all

ALTER TABLE hooks
  ADD COLUMN hooks_status ENUM('1','0') NOT NULL DEFAULT '1' AFTER hooks_method;
ALTER TABLE hooks
  ADD UNIQUE KEY hooks_unique (hooks_site, hooks_group, hooks_action, hooks_code);
Hooks_Manager.zip

⚠️ Please don't test this on the live shop, as it's still under development and testing

Re: Enabling / Disabling Hooks

Posted: Sat Jul 11, 2026 2:14 pm
by burt
Split from the thread No Core Changes - A different perspective

Re: Enabling / Disabling Hooks

Posted: Sat Jul 11, 2026 6:56 pm
by Omar_one
@azpro @beerbee @Kofod95
@ArtcoInc @14Steve14 and anyone interested in enabling/disabling Hooks.
Disabling or enabling a hook file does not automatically toggle all of its individual methods and actions.

The ZIP folder has been updated with the fix.
Hooks_Manager_v1.1.zip
Alternatively, you can manually replace the default.php file located in:
[YOUR ADMIN]/includes/actions/modules_hooks_manager/views/

Code: Select all

<?php
/*
  $Id$
  CE Phoenix, E-Commerce made Easy
  https://phoenixcart.org
  Copyright (c) 2024 Phoenix Cart
  Released under the GNU General Public License
*/

// 1. Process Toggle Logic First
global $db, $Admin;

$site        = $_GET['site'] ?? '';
$group       = $_GET['group'] ?? '';
$code        = $_GET['code'] ?? '';
$action_name = $_GET['action_name'] ?? '';

if ($site !== '' && $group !== '' && $code !== '') {

  // Get the current status from any method of this hook
  $check = $db->query(sprintf(
    "SELECT hooks_status
       FROM hooks
      WHERE hooks_site = '%s'
        AND hooks_group = '%s'
        AND hooks_code = '%s'
      LIMIT 1",
    $db->escape($site),
    $db->escape($group),
    $db->escape($code)
  ));

  $current = '1';

  if ($hook = $check->fetch_assoc()) {
    $current = $hook['hooks_status'];
  }

  $new_status = ($current === '1') ? '0' : '1';

  // Update ALL methods belonging to this hook
  $db->query(sprintf(
    "UPDATE hooks
        SET hooks_status = '%s'
      WHERE hooks_site = '%s'
        AND hooks_group = '%s'
        AND hooks_code = '%s'",
    $db->escape($new_status),
    $db->escape($site),
    $db->escape($group),
    $db->escape($code)
  ));

  // If it's an AJAX request, respond immediately and exit
  if (($_GET['action_type'] ?? '') === 'ajax') {
    exit('success');
  }

  // For non-AJAX fallback clicks, redirect back cleanly to prevent double-submits
  $redirect_params = [];

  if (!empty($_GET['hook_location'])) {
    $redirect_params[] = 'hook_location=' . urlencode($_GET['hook_location']);
  }

  if (!empty($_GET['hook_group'])) {
    $redirect_params[] = 'hook_group=' . urlencode($_GET['hook_group']);
  }

  header('Location: ' . $Admin->link('modules_hooks_manager', implode('&', $redirect_params)));
  exit;
}

// 2. Original Page Rendering & Filter Setup
$hook_locations = [];
$hook_groups = [];

foreach ($contents as $site_key => $groups) {
  $hook_locations[$site_key] = $site_key;

  foreach ($groups as $group_key => $actions) {
    $hook_groups[$group_key] = $group_key;
  }
}

ksort($hook_locations);
ksort($hook_groups);

$selected_location = trim($_GET['hook_location'] ?? '');
$selected_group    = trim($_GET['hook_group'] ?? '');

// Build hook status lookup by site/group/code
$hook_statuses = [];

foreach ($statuses as $key => $status) {
  [$s, $g, $a, $c] = explode("\0", $key);
  $hook_statuses["{$s}\0{$g}\0{$c}"] = $status;
}

// 3. Collect and Sort Row Data by File Name
$table_rows = [];

foreach ($contents as $site => $groups) {
  if ($selected_location !== '' && strtolower(trim($site)) !== strtolower(trim($selected_location))) {
    continue;
  }

  foreach ($groups as $group => $actions) {
    if ($selected_group !== '' && strtolower(trim($group)) !== strtolower(trim($selected_group))) {
      continue;
    }

    foreach ($actions as $action => $codes) {
      foreach ($codes as $code => $locations) {
        foreach ($locations as $location) {

          if (is_array($location)) {
            $file = implode('->', $location);
            $class = !empty($location[0]) ? explode('::', $location[0])[0] : null;
            $version = ($class && class_exists($class))
              ? (get_class_vars($class)['version'] ?? null)
              : null;
          } else {
            $file = "$code.php";
            $version = get_class_vars("hook_{$site}_{$group}_{$code}")['version'] ?? null;
          }

          // Use one status for the entire hook file
          $hook_key = "{$site}\0{$group}\0{$code}";
          $enabled = ($hook_statuses[$hook_key] ?? '1') === '1';

          $current_script = basename($_SERVER['PHP_SELF']);

          $query_string = 'app=hooks'
            . '&site=' . urlencode($site)
            . '&group=' . urlencode($group)
            . '&action_name=' . urlencode($action)
            . '&code=' . urlencode($code);

          if (!empty($selected_location)) {
            $query_string .= '&hook_location=' . urlencode($selected_location);
          }

          if (!empty($selected_group)) {
            $query_string .= '&hook_group=' . urlencode($selected_group);
          }

          $toggle_href = $current_script . '?' . $query_string;

          $table_rows[] = [
            'site'        => $site,
            'group'       => $group,
            'file'        => $file,
            'action'      => $action,
            'version'     => $version,
            'enabled'     => $enabled,
            'toggle_href' => $toggle_href
          ];
        }
      }
    }
  }
}

// Perform alphabetical sort on the computed filename string
usort($table_rows, function ($a, $b) {
  return strcasecmp($a['file'], $b['file']);
});
?>

<form method="get" class="mb-3">
<?php
foreach ($_GET as $k => $v) {
  if (!in_array($k, ['hook_location', 'hook_group', 'site', 'group', 'action_name', 'code', 'action_type'])) {
?>
<input type="hidden" name="<?= htmlspecialchars($k) ?>" value="<?= htmlspecialchars($v) ?>">
<?php
  }
}
?>

<div class="row mb-3">
  <div class="col-md-4 mb-2 mb-md-0">
    <select name="hook_location" class="form-select" onchange="this.form.submit()">
      <option value="">All Locations</option>
      <?php foreach ($hook_locations as $location) { ?>
        <option value="<?= htmlspecialchars($location) ?>" <?= ($selected_location === $location ? 'selected' : '') ?>>
          <?= htmlspecialchars($location) ?>
        </option>
      <?php } ?>
    </select>
  </div>

  <div class="col-md-4">
    <select name="hook_group" class="form-select" onchange="this.form.submit()">
      <option value="">All Hook Groups</option>
      <?php foreach ($hook_groups as $group_item) { ?>
        <option value="<?= htmlspecialchars($group_item) ?>" <?= ($selected_group === $group_item ? 'selected' : '') ?>>
          <?= htmlspecialchars($group_item) ?>
        </option>
      <?php } ?>
    </select>
  </div>
</div>
</form>

<div class="table-responsive">
  <table class="table table-striped table-hover">
    <thead class="table-dark">
      <tr>
        <th colspan="5"><?= defined('TABLE_HEADING_HOOKS') ? TABLE_HEADING_HOOKS : 'Registered Hooks' ?></th>
      </tr>
    </thead>
    <thead class="table-light">
      <tr>
        <th><?= TABLE_HEADING_GROUP ?></th>
        <th><?= TABLE_HEADING_FILE ?></th>
        <th><?= TABLE_HEADING_METHOD ?></th>
        <th class="text-end"><?= TABLE_HEADING_VERSION ?></th>
        <th class="text-end"><?= TABLE_HEADING_STATUS ?></th>
      </tr>
    </thead>
    <tbody>
      <?php foreach ($table_rows as $row) { ?>
       <tr
  class="<?= $row['enabled'] ? '' : 'text-muted' ?>"
  data-hook-site="<?= htmlspecialchars($row['site']) ?>"
  data-hook-group="<?= htmlspecialchars($row['group']) ?>"
  data-hook-file="<?= htmlspecialchars($row['file']) ?>"
>
          <td><?= htmlspecialchars($row['group']) ?></td>
          <td><?= htmlspecialchars($row['file']) ?></td>
          <td><?= htmlspecialchars($row['action']) ?></td>
          <td class="text-end"><?= htmlspecialchars($row['version'] ?? 'N/A') ?></td>
          <td class="text-end">
            <a href="<?= $row['toggle_href'] ?>"
               class="js-hook-toggle"
               title="<?= $row['enabled'] ? TEXT_CLICK_TO_DISABLE : TEXT_CLICK_TO_ENABLE ?>">
              <i class="fa fa-toggle-<?= $row['enabled'] ? 'on text-success' : 'off text-muted' ?> fa-lg"></i>
            </a>
          </td>
        </tr>
      <?php } ?>
    </tbody>
  </table>
</div>

<hr>

<p><?= TEXT_HOOKS_DIRECTORY . ' ' . DIR_FS_CATALOG . 'includes/hooks/' ?></p>

<script>
document.addEventListener('DOMContentLoaded', function () {

  document.querySelectorAll('.js-hook-toggle').forEach(function (link) {

    link.addEventListener('click', function (e) {
      e.preventDefault();

      const clickedRow = this.closest('tr');
      const site = clickedRow.dataset.hookSite;
      const group = clickedRow.dataset.hookGroup;
      const file = clickedRow.dataset.hookFile;

      // All rows belonging to the same hook file
      const rows = document.querySelectorAll(
        `tr[data-hook-site="${site}"][data-hook-group="${group}"][data-hook-file="${file}"]`
      );

      const turningOff =
        this.querySelector('i').classList.contains('fa-toggle-on');

      // Update all matching rows immediately
      rows.forEach(function (row) {
        const icon = row.querySelector('.js-hook-toggle i');
        const link = row.querySelector('.js-hook-toggle');

        if (turningOff) {
          icon.classList.remove('fa-toggle-on', 'text-success');
          icon.classList.add('fa-toggle-off', 'text-muted');
          row.classList.add('text-muted');
          link.title = '<?= addslashes(TEXT_CLICK_TO_ENABLE) ?>';
        } else {
          icon.classList.remove('fa-toggle-off', 'text-muted');
          icon.classList.add('fa-toggle-on', 'text-success');
          row.classList.remove('text-muted');
          link.title = '<?= addslashes(TEXT_CLICK_TO_DISABLE) ?>';
        }
      });

      const ajaxUrl = this.href + '&action_type=ajax';

      fetch(ajaxUrl)
        .then(function (response) {
          if (!response.ok) {
            throw new Error('AJAX request failed');
          }
        })
        .catch(function (error) {
          console.error(error);

          // Roll back all rows if the request fails
          rows.forEach(function (row) {
            const icon = row.querySelector('.js-hook-toggle i');
            const link = row.querySelector('.js-hook-toggle');

            if (turningOff) {
              icon.classList.remove('fa-toggle-off', 'text-muted');
              icon.classList.add('fa-toggle-on', 'text-success');
              row.classList.remove('text-muted');
              link.title = '<?= addslashes(TEXT_CLICK_TO_DISABLE) ?>';
            } else {
              icon.classList.remove('fa-toggle-on', 'text-success');
              icon.classList.add('fa-toggle-off', 'text-muted');
              row.classList.add('text-muted');
              link.title = '<?= addslashes(TEXT_CLICK_TO_ENABLE) ?>';
            }
          });
        });

    });

  });

});
</script>