diff --git a/CHANGELOG.md b/CHANGELOG.md index 5810385..9acd58c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog ========= +## v2.0.0 (2026-01-23) + +* BREAKING: Updated `bugsnag-php` dependency to v3. More details can be found at [UPGRADING.md](https://github.com/bugsnag/bugsnag-php/blob/master/UPGRADING.md) +* Added extended notifier configuration options. + ## v1.6.5 (2025-09-30) * Update plugin metadata [#66](https://github.com/bugsnag/bugsnag-wordpress/pull/67) diff --git a/Thorfile b/Thorfile index 0a338fa..116dfe9 100755 --- a/Thorfile +++ b/Thorfile @@ -1,51 +1,50 @@ -require "fileutils" +require 'fileutils' class Wordpress < Thor - PLUGIN_NAME = "bugsnag" - PLUGIN_FILES = %W{bugsnag.php readme.txt views LICENSE.txt} - BUILD_FILES = %W{build vendor composer.lock svn} - VENDORED_BUGSNAG_PHP = "vendor/bugsnag/bugsnag/src/Bugsnag" + PLUGIN_NAME = 'bugsnag' + PLUGIN_FILES = %w[bugsnag.php readme.txt views LICENSE.txt] + BUILD_FILES = %w[build vendor composer.lock svn] VERSION_REGEX = /^\d+\.\d+\.\d+$/ - desc "build", "create a clean build of the plugin" - def build(build_dir="build") - bugsnag_php_dir = File.join(build_dir, 'bugsnag-php') + desc 'build', 'create a clean build of the plugin' + def build(build_dir = 'build') + vendor_dir = File.join(build_dir, 'vendor') # Prepare the build directory - FileUtils.mkdir_p bugsnag_php_dir + FileUtils.mkdir_p vendor_dir # Install dependencies - puts "- Installing dependencies" - `composer install` + puts '- Installing dependencies' + `composer install --no-dev --optimize-autoloader` # Copy plugin files to the build directory - puts "- Copying plugin files" + puts '- Copying plugin files' FileUtils.cp_r PLUGIN_FILES, build_dir # Copy vendored bugsnag to the build directory - puts "- Copying vendored bugsnag-php" - `cp -r #{VENDORED_BUGSNAG_PHP}/* #{bugsnag_php_dir}` + puts '- Copying vendor' + `cp -r vendor/* #{vendor_dir}` end - desc "update_version ", "update the plugin to the given version" + desc 'update_version ', 'update the plugin to the given version' def update_version(version) - return $stderr.puts "Invalid version number #{version}" unless version =~ VERSION_REGEX + return warn "Invalid version number #{version}" unless version =~ VERSION_REGEX - replace_in_file("readme.txt", /Stable tag: \d\.\d\.\d/, "Stable tag: #{version}") - replace_in_file("bugsnag.php", /Version: \d\.\d\.\d/, "Version: #{version}") - replace_in_file("bugsnag.php", /'version' => '\d\.\d\.\d'/, "'version' => '#{version}'") + replace_in_file('readme.txt', /Stable tag: \d\.\d\.\d/, "Stable tag: #{version}") + replace_in_file('bugsnag.php', /Version: \d\.\d\.\d/, "Version: #{version}") + replace_in_file('bugsnag.php', /'version' => '\d\.\d\.\d'/, "'version' => '#{version}'") end - desc "release_svn ", "perform a release to svn" + desc 'release_svn ', 'perform a release to svn' def release_svn(version, username) # Checkout a fresh copy of the svn repo checkout_svn(username) # Build a release copy into svn/trunk - build "svn/trunk" + build 'svn/trunk' # Move into the svn repo - Dir.chdir "svn" do + Dir.chdir 'svn' do # Commit changes to svn `svn add trunk/*` `svn ci -m "Release version #{version}" --username #{username}` @@ -56,10 +55,10 @@ class Wordpress < Thor end # Remove temporary files - FileUtils.rm_rf "svn" + FileUtils.rm_rf 'svn' end - desc "release_git ", "perform a release to git" + desc 'release_git ', 'perform a release to git' def release_git(version) # Commit version changes `git add readme.txt bugsnag.php` @@ -72,7 +71,7 @@ class Wordpress < Thor `git push origin master && git push --tags` end - desc "release ", "perform a release to git and svn" + desc 'release ', 'perform a release to git and svn' def release(version, wordpress_username) # Update version number update_version(version) @@ -84,8 +83,8 @@ class Wordpress < Thor release_svn(version, wordpress_username) end - desc "zip ", "create a zip of the plugin" - def zip(zip_name="bugsnag-wordpress.zip", build_dir="build") + desc 'zip ', 'create a zip of the plugin' + def zip(zip_name = 'bugsnag-wordpress.zip', build_dir = 'build') # Build a clean plugin build File.join(build_dir, PLUGIN_NAME) @@ -93,26 +92,27 @@ class Wordpress < Thor puts "- Generating #{zip_name}" Dir.chdir build_dir do `zip -r #{zip_name} #{PLUGIN_NAME}` - FileUtils.cp zip_name, "../" + FileUtils.cp zip_name, '../' end # Remove temporary files FileUtils.rm_rf build_dir end - desc "clean", "clean up any build files" - def clean() + desc 'clean', 'clean up any build files' + def clean FileUtils.rm_rf BUILD_FILES end - desc "checkout_svn ", "checkout a copy of the svn repo" + desc 'checkout_svn ', 'checkout a copy of the svn repo' def checkout_svn(username) `svn co http://plugins.svn.wordpress.org/bugsnag svn --username #{username}` end private + def replace_in_file(filename, find, replace) str = File.read(filename) - File.open(filename, 'w') {|f| f.write(str.gsub(find, replace)) } + File.open(filename, 'w') { |f| f.write(str.gsub(find, replace)) } end end diff --git a/bugsnag.php b/bugsnag.php index 2f71fe3..cc8985e 100644 --- a/bugsnag.php +++ b/bugsnag.php @@ -9,22 +9,34 @@ License: GPLv2 or later */ +use Bugsnag\Client; +use Bugsnag\Handler; +use Bugsnag\ErrorTypes; +use Bugsnag\Report; + class Bugsnag_Wordpress { private static $COMPOSER_AUTOLOADER = 'vendor/autoload.php'; - private static $PACKAGED_AUTOLOADER = 'bugsnag-php/Autoload.php'; private static $DEFAULT_NOTIFY_SEVERITIES = 'fatal,error'; + private static $DISABLED_NOTIFIER_METHODS = array( + 'setAutoCaptureSessions', + 'shouldCaptureSessions' + ); + private static $NOTIFIER = array( 'name' => 'Bugsnag Wordpress (Official)', - 'version' => '1.6.5', + 'version' => '2.0.0', 'url' => 'https://github.com/bugsnag/bugsnag-wordpress', ); - private $client; + private Client $client; private $apiKey; private $notifySeverities; - private $filterFields; + private $redactedKeys; + private $appVersion; + private $notifyEndpoint; + private $releaseStageConfig; private $pluginBase; public function __construct() @@ -35,7 +47,7 @@ public function __construct() $this->pluginBase = 'bugsnag/bugsnag.php'; // Run init actions (loading wp user) - add_action('init', array($this, 'initActions')); + add_action('init', array($this, 'registerUser')); // Load admin actions (admin links and pages) add_action('admin_menu', array($this, 'adminMenuActions')); @@ -60,12 +72,18 @@ private function activateBugsnag() // Regular $this->apiKey = get_option('bugsnag_api_key'); $this->notifySeverities = get_option('bugsnag_notify_severities'); - $this->filterFields = get_option('bugsnag_filterfields'); + $this->redactedKeys = get_option('bugsnag_redacted_keys'); + $this->appVersion = get_option('bugsnag_app_version'); + $this->notifyEndpoint = get_option('bugsnag_notify_endpoint'); + $this->releaseStageConfig = get_option('bugsnag_release_stage'); } else { // Multisite $this->apiKey = get_site_option('bugsnag_api_key'); $this->notifySeverities = get_site_option('bugsnag_notify_severities'); - $this->filterFields = get_site_option('bugsnag_filterfields'); + $this->redactedKeys = get_site_option('bugsnag_redacted_keys'); + $this->appVersion = get_site_option('bugsnag_app_version'); + $this->notifyEndpoint = get_site_option('bugsnag_notify_endpoint'); + $this->releaseStageConfig = get_site_option('bugsnag_release_stage'); } $this->constructBugsnag(); @@ -75,13 +93,24 @@ private function constructBugsnag() { // Activate the bugsnag client if (!empty($this->apiKey)) { - $this->client = new Bugsnag_Client($this->apiKey); + $this->client = Client::make($this->apiKey); $this->client->setReleaseStage($this->releaseStage()) - ->setErrorReportingLevel($this->errorReportingLevel()) - ->setFilters($this->filterFields()); + ->setErrorReportingLevel($this->errorReportingLevel()) + ->setRedactedKeys($this->redactedKeys()) + ->setAppType('wordpress'); + + // Set app version if configured + if (!empty($this->appVersion)) { + $this->client->setAppVersion($this->appVersion); + } + + // Set notify endpoint if configured + if (!empty($this->notifyEndpoint)) { + $this->client->setNotifyEndpoint($this->notifyEndpoint); + } - $this->client->mergeDeviceData(['runtimeVersions' => ['wordpress' => get_bloginfo('version')]]); + $this->client->getConfig()->mergeDeviceData(['runtimeVersions' => ['wordpress' => get_bloginfo('version')]]); $this->client->setNotifier(self::$NOTIFIER); @@ -96,8 +125,7 @@ private function constructBugsnag() if ($set_error_and_exception_handlers === true) { // Hook up automatic error handling - set_error_handler(array($this->client, 'errorHandler')); - set_exception_handler(array($this->client, 'exceptionHandler')); + Handler::register($this->client); } } } @@ -105,34 +133,22 @@ private function constructBugsnag() private function requireBugsnagPhp() { // Bugsnag-php was already loaded by some 3rd-party code, don't need to load it again. - if (class_exists('Bugsnag_Client')) { + if (class_exists('Bugsnag\Client')) { return true; } // Try loading bugsnag-php with composer autoloader. - $composer_autoloader_path = $this->relativePath(self::$COMPOSER_AUTOLOADER); - $composer_autoloader_path_filtered = apply_filters('bugsnag_composer_autoloader_path', $composer_autoloader_path); - if (file_exists($composer_autoloader_path_filtered)) { - require_once $composer_autoloader_path_filtered; - - return true; - } - - // Try loading bugsnag-php from packaged autoloader. - $packaged_autoloader_path = $this->relativePath(self::$PACKAGED_AUTOLOADER); - $packaged_autoloader_path_filtered = apply_filters('bugsnag_packaged_autoloader_path', $packaged_autoloader_path); - if (file_exists($packaged_autoloader_path_filtered)) { - require_once $packaged_autoloader_path_filtered; - + try { + require_once $this->relativePath(self::$COMPOSER_AUTOLOADER); return true; + } catch (Exception $e) { + return false; } - - return false; } private function relativePath($path) { - return dirname(__FILE__).'/'.$path; + return dirname(__FILE__) . '/' . $path; } private function errorReportingLevel() @@ -142,22 +158,22 @@ private function errorReportingLevel() $severities = explode(',', $notifySeverities); foreach ($severities as $severity) { - $level |= Bugsnag_ErrorTypes::getLevelsForSeverity($severity); + $level |= ErrorTypes::getLevelsForSeverity($severity); } return $level; } - private function filterFields() + private function redactedKeys() { - $filter_fields = apply_filters('bugsnag_filter_fields', $this->filterFields); + $redacted_keys = apply_filters('bugsnag_redacted_keys', $this->redactedKeys); // Array with empty string will break things. - if ($filter_fields === '') { + if ($redacted_keys === '') { return array(); } - return array_map('trim', explode("\n", $filter_fields)); + return array_map('trim', explode("\n", $redacted_keys)); } /** @@ -167,7 +183,10 @@ private function filterFields() */ private function releaseStage() { - if (function_exists('wp_get_environment_type')) { + // Use configured release stage if available + if (!empty($this->releaseStageConfig)) { + $release_stage = $this->releaseStageConfig; + } elseif (function_exists('wp_get_environment_type')) { $release_stage = wp_get_environment_type(); // Defaults to production when not set. } else { $release_stage = defined('WP_ENV') ? WP_ENV : 'production'; @@ -178,35 +197,31 @@ private function releaseStage() } // Action hooks - public function initActions() + public function registerUser() { - // This should be handled on stage of initializing, - // not even adding action if init failed. - // - // Leaving it here for now. - if (empty($this->client)) { + if (!$this->isStarted()) { // This might attempt to run before the client is configured. return; } + $this->client->registerCallback(function (Report $report) { + // Set the bugsnag user using the current WordPress user if available, + // set as anonymous otherwise. + $user = []; + + if (is_user_logged_in()) { + $wp_user = wp_get_current_user(); + $user['id'] = $wp_user->user_login; + $user['email'] = $wp_user->user_email; + $user['name'] = $wp_user->display_name; + } else { + $use_unsafe_spoofable_ip_address_getter = apply_filters('bugsnag_use_unsafe_spoofable_ip_address_getter', true); + $user['id'] = $use_unsafe_spoofable_ip_address_getter ? + $this->getClientIpAddressUnsafe() : + $this->getClientIpAddress(); + $user['name'] = 'anonymous'; + } - // Set the bugsnag user using the current WordPress user if available, - // set as anonymous otherwise. - $user = array(); - if (is_user_logged_in()) { - $wp_user = wp_get_current_user(); - - // Removed checks for !empty($wp_user->display_name), it should not be required. - $user['id'] = $wp_user->user_login; - $user['email'] = $wp_user->user_email; - $user['name'] = $wp_user->display_name; - } else { - $use_unsafe_spoofable_ip_address_getter = apply_filters('bugsnag_use_unsafe_spoofable_ip_address_getter', true); - $user['id'] = $use_unsafe_spoofable_ip_address_getter ? - $this->getClientIpAddressUnsafe() : - $this->getClientIpAddress(); - $user['name'] = 'anonymous'; - } - - $this->client->setUser($user); + $report->setUser($user); + }); } // Unsafe: client can spoof address. @@ -250,18 +265,24 @@ public function networkAdminMenuActions() } } - private function updateNetworkSettings($settings) + private function updateNetworkSettings() { // Update options update_site_option('bugsnag_api_key', isset($_POST['bugsnag_api_key']) ? $_POST['bugsnag_api_key'] : ''); update_site_option('bugsnag_notify_severities', isset($_POST['bugsnag_notify_severities']) ? $_POST['bugsnag_notify_severities'] : ''); - update_site_option('bugsnag_filterfields', isset($_POST['bugsnag_filterfields']) ? $_POST['bugsnag_filterfields'] : ''); + update_site_option('bugsnag_redacted_keys', isset($_POST['bugsnag_redacted_keys']) ? $_POST['bugsnag_redacted_keys'] : ''); + update_site_option('bugsnag_app_version', isset($_POST['bugsnag_app_version']) ? $_POST['bugsnag_app_version'] : ''); + update_site_option('bugsnag_notify_endpoint', isset($_POST['bugsnag_notify_endpoint']) ? $_POST['bugsnag_notify_endpoint'] : ''); + update_site_option('bugsnag_release_stage', isset($_POST['bugsnag_release_stage']) ? $_POST['bugsnag_release_stage'] : ''); update_site_option('bugsnag_network', true); // Update variables $this->apiKey = get_site_option('bugsnag_api_key'); $this->notifySeverities = get_site_option('bugsnag_notify_severities'); - $this->filterFields = get_site_option('bugsnag_filterfields'); + $this->redactedKeys = get_site_option('bugsnag_redacted_keys'); + $this->appVersion = get_site_option('bugsnag_app_version'); + $this->notifyEndpoint = get_site_option('bugsnag_notify_endpoint'); + $this->releaseStageConfig = get_site_option('bugsnag_release_stage'); echo '

Settings saved.

'; } @@ -287,16 +308,21 @@ public function testBugsnag() $this->apiKey = $_POST['bugsnag_api_key']; $this->notifySeverities = $_POST['bugsnag_notify_severities']; - $this->filterFields = $_POST['bugsnag_filterfields']; + $this->redactedKeys = $_POST['bugsnag_redacted_keys']; + $this->appVersion = $_POST['bugsnag_app_version']; + $this->notifyEndpoint = $_POST['bugsnag_notify_endpoint']; + $this->releaseStageConfig = $_POST['bugsnag_release_stage']; - $this->constructBugsnag(); $this->client->notifyError( 'BugsnagTest', 'Testing bugsnag', - array( - 'notifier' => self::$NOTIFIER, - 'docs' => array('url' => 'https://docs.bugsnag.com/platforms/php/wordpress/'), - ) + function (Report $report) { + $report->setSeverity('info'); + $report->setMetaData([ + 'notifier' => self::$NOTIFIER, + 'docs' => array('url' => 'https://docs.bugsnag.com/platforms/php/wordpress/'), + ]); + } ); die(); @@ -316,6 +342,11 @@ public function renderSettings() include $this->relativePath('views/settings.php'); } + public function isStarted() + { + return isset($this->apiKey) && isset($this->client); + } + private function renderOption($name, $value, $current) { $selected = ($value == $current) ? ' selected="selected"' : ''; @@ -342,11 +373,15 @@ public function __call($method, $arguments) ); } + if (in_array($method, self::$DISABLED_NOTIFIER_METHODS)) { + throw new BadMethodCallException(sprintf('Method %s is disabled in BugSnag for Wordpress', $method)); + } + if (method_exists($this->client, $method)) { return call_user_func_array(array($this->client, $method), $arguments); } - throw new BadMethodCallException(sprintf('Method %s does not exist on %s or Bugsnag_Client', $method, __CLASS__)); + throw new BadMethodCallException(sprintf('Method %s does not exist on %s or Bugsnag\Client', $method, __CLASS__)); } } diff --git a/composer.json b/composer.json index 057b7bd..4ba86b4 100644 --- a/composer.json +++ b/composer.json @@ -2,8 +2,7 @@ "name": "bugsnag/bugsnag-wordpress", "type": "wordpress-plugin", "require": { - "bugsnag/bugsnag": "^2.10.2", - "composer/installers": "^1.0" + "bugsnag/bugsnag": "^3.30.0" }, "license": "MIT", "authors": [ @@ -11,10 +10,5 @@ "name": "James Smith", "email": "james@loopj.com" } - ], - "config": { - "allow-plugins": { - "composer/installers": true - } - } -} + ] +} \ No newline at end of file diff --git a/readme.txt b/readme.txt index 91265f1..478b8fc 100644 --- a/readme.txt +++ b/readme.txt @@ -2,8 +2,8 @@ Contributors: loopj Tags: bugsnag, error, monitoring, exception, logging Requires at least: 2.0 -Tested up to: 6.8.2 -Stable tag: 1.6.5 +Tested up to: 6.9 +Stable tag: 2.0.0 License: GPLv2 or later Automatically detects errors & crashes on your WordPress site using BugSnag to notify you by email, chat or issues system. @@ -37,6 +37,11 @@ To manually install Bugsnag: == Changelog == += 2.0.0 = +* BREAKING: Updated `bugsnag-php` dependency to v3. More details can be found at https://github.com/bugsnag/bugsnag-php/blob/master/UPGRADING.md +* Added extended notifier configuration options. +* Update plugin metadata. + = 1.6.5 = * Update plugin metadata diff --git a/views/settings.php b/views/settings.php index 9fe7182..b27b38b 100644 --- a/views/settings.php +++ b/views/settings.php @@ -46,7 +46,7 @@ - + +

The information to remove from BugSnag reports, one per line. Use this if you want to ensure you don't send sensitive data such as passwords, and credit card numbers to our servers.

+ + + + + + + + +

+ Set the version of your application. This will be used to group errors by release. +

+ + + + + + + + + + +

+ Custom endpoint URL for error notifications. Leave blank to use the default BugSnag endpoint. +

+ + + + + + + + + + +

+ The release stage (e.g., production, staging, development). Leave blank to auto-detect from WordPress environment. +

+ +
@@ -80,7 +119,7 @@ - +
@@ -98,24 +137,27 @@ - + \ No newline at end of file