PluginProbe
BetterLinks – Link Shortener, Link Cloaking, Redirects, Affiliate Link Manager & MCP / 3.1.0
BetterLinks – Link Shortener, Link Cloaking, Redirects, Affiliate Link Manager & MCP v3.1.0
3.1.2 3.1.1 3.1.0 3.0.1 3.0.0 2.4.13 2.4.12 2.4.11 2.4.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 All 109 releases
betterlinks / betterlinks.php

betterlinks.php in BetterLinks – Link Shortener, Link Cloaking, Redirects, Affiliate Link Manager & MCP 3.1.0, at betterlinks.php

280 lines 12.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * Plugin Name: BetterLinks
4 * Plugin URI: https://betterlinks.io/
5 * Description: Create, shorten, cloak, track and manage any URL. Gather click analytics, run marketing campaigns, and connect AI assistants over MCP.
6 * Version: 3.1.0
7 * Author: WPDeveloper
8 * Author URI: https://wpdeveloper.com
9 * License: GPL-3.0+
10 * License URI: http://www.gnu.org/licenses/gpl-3.0.txt
11 * Author URI: https://wpdeveloper.com
12 * Text Domain: betterlinks
13 * Domain Path: /languages
14 */
15
16 use BetterLinks\Admin\Cache;
17
18 if (!defined('ABSPATH')) {
19 exit();
20 }
21
22 if (file_exists(dirname(__FILE__) . '/vendor/autoload.php')) {
23 require_once dirname(__FILE__) . '/vendor/autoload.php';
24 }
25
26 /**
27 * Bundled MCP runtime (WordPress Abilities API).
28 *
29 * Loaded through the Jetpack Autoloader so that if the same library is also
30 * shipped by another plugin — or lands in WordPress core — the newest copy
31 * wins and loads once, with no fatal class collisions. This lets BetterLinks
32 * serve its MCP connector out of the box, without the standalone Abilities API
33 * plugin. See docs/mcp-server.md for the update procedure.
34 */
35 $betterlinks_mcp_runtime = dirname(__FILE__) . '/dependencies/vendor/autoload_packages.php';
36 if (is_readable($betterlinks_mcp_runtime)) {
37 require_once $betterlinks_mcp_runtime;
38 }
39 unset($betterlinks_mcp_runtime);
40
41 if (!class_exists('BetterLinks')) {
42 final class BetterLinks
43 {
44 private $Installer;
45 private $upload_dir;
46 private function __construct()
47 {
48 $this->upload_dir_path();
49 $this->define_constants();
50 $this->set_global_settings();
51 $this->Installer = new BetterLinks\Installer();
52 register_activation_hook(__FILE__, [$this, 'activate']);
53 register_deactivation_hook(__FILE__, [$this, 'deactivate']);
54 add_action('plugins_loaded', [$this, 'on_plugins_loaded']);
55 add_action('betterlinks_loaded', [$this, 'init_plugin']);
56 add_action('admin_init', [$this, 'run_migrator']);
57 add_action('admin_init', [$this, 'do_the_works_if_failed_during_activation'], 100);
58 add_action('admin_init', [$this, 'maybe_complete_legacy_quick_setup'], 9);
59 add_action('admin_init', [$this, 'quick_setup']);
60 $this->dispatch_hook();
61 add_action( 'wp_enqueue_scripts', [$this, 'frontend_scripts'] );
62 }
63
64 public function do_the_works_if_failed_during_activation()
65 {
66 $betterlinks_activation_flag = BetterLinks\Helper::btl_get_option("betterlinks_activation_flag");
67 if(isset($betterlinks_activation_flag["last_activation_background_processes_firing_timestamp"]) && isset($betterlinks_activation_flag["last_activation_timestamp"])) {
68 if($betterlinks_activation_flag["last_activation_background_processes_firing_timestamp"]){
69 return false;
70 }
71 $waiting_time_in_seconds = 5;
72 if((absInt($betterlinks_activation_flag["last_activation_timestamp"]) + $waiting_time_in_seconds) > time()){
73 // don't go any further and return false here if,
74 // $waiting_time_in_seconds (in this case 5 seconds) haven't passed yet since the activation flag was setted
75 return false;
76 }
77 $all_tasks = array_merge(
78 $this->Installer->activation,
79 $this->Installer->migration
80 );
81 foreach ($all_tasks as $task) {
82 $this->Installer->$task();
83 }
84 }
85 }
86
87 public static function init()
88 {
89 static $instance = false;
90
91 if (!$instance) {
92 $instance = new self();
93 }
94
95 return $instance;
96 }
97 public function define_constants()
98 {
99 /**
100 * Defines CONSTANTS for Whole plugins.
101 */
102 define('BETTERLINKS_VERSION', '3.1.0');
103 define('BETTERLINKS_DB_VERSION', '1.6.11');
104 define('BETTERLINKS_MENU_NOTICE', '10');
105 define('BETTERLINKS_SETTINGS_NAME', 'betterlinks_settings');
106 define('BETTERLINKS_PLUGIN_FILE', __FILE__);
107 define('BETTERLINKS_PLUGIN_BASENAME', plugin_basename(__FILE__));
108 define('BETTERLINKS_PLUGIN_SLUG', 'betterlinks');
109 define('BETTERLINKS_PLUGIN_ROOT_URI', plugins_url('/', __FILE__));
110 define('BETTERLINKS_ROOT_DIR_PATH', plugin_dir_path(__FILE__));
111 define('BETTERLINKS_ASSETS_DIR_PATH', BETTERLINKS_ROOT_DIR_PATH . 'assets/');
112 define('BETTERLINKS_ASSETS_URI', BETTERLINKS_PLUGIN_ROOT_URI . 'assets/');
113 define('BETTERLINKS_UPLOAD_DIR_PATH', $this->upload_dir['basedir'] . '/betterlinks_uploads');
114 define('BETTERLINKS_EXISTS_LINKS_JSON', defined('BETTERLINKS_ALLOW_JSON_REDIRECT') ? file_exists(BETTERLINKS_UPLOAD_DIR_PATH . '/links.json') && BETTERLINKS_ALLOW_JSON_REDIRECT : file_exists(BETTERLINKS_UPLOAD_DIR_PATH . '/links.json'));
115 define('BETTERLINKS_EXISTS_CLICKS_JSON', file_exists(BETTERLINKS_UPLOAD_DIR_PATH . '/clicks.json'));
116 define('BETTERLINKS_EXISTS_SETTINGS_JSON', defined('BETTERLINKS_ALLOW_JSON_REDIRECT') ? file_exists(BETTERLINKS_UPLOAD_DIR_PATH . '/settings.json') && BETTERLINKS_ALLOW_JSON_REDIRECT : file_exists(BETTERLINKS_UPLOAD_DIR_PATH . '/settings.json'));
117 define('BETTERLINKS_LINKS_OPTION_NAME', 'betterlinks_links');
118 define('BETTERLINKS_CACHE_LINKS_NAME', 'betterlinks_cache_links_data');
119 define('BETTERLINKS_DB_ALTER_OPTIONS', 'betterlinks_db_alter_options');
120 define('BETTERLINKS_CUSTOM_DOMAIN_MENU', 'betterlinks_custom_domain_menu');
121 define('BETTERLINKS_AI_API_KEYS_OPTION_NAME', 'betterlinks_ai_api_keys');
122 }
123
124 public function upload_dir_path()
125 {
126 $this->upload_dir = wp_get_upload_dir();
127 }
128
129
130 public function on_plugins_loaded()
131 {
132 do_action('betterlinks_loaded');
133 }
134
135 /**
136 * Initialize the plugin
137 *
138 * @return void
139 */
140 public function init_plugin()
141 {
142 BetterLinks\API::init();
143 if (is_admin()) {
144 new BetterLinks\Admin();
145 }
146 BetterLinks\Integration::init();
147 new BetterLinks\Link();
148 new BetterLinks\Tools();
149 new BetterLinks\Frontend;
150 new BetterLinks\Elementor();
151
152 // MCP connector: register abilities (always, so generic Abilities
153 // clients can discover BetterLinks) and the MCP server surface (which
154 // gates serving on the enable_mcp setting). Guarded so a build without
155 // the bundled Abilities runtime still boots.
156 ( new BetterLinks\Abilities\Abilities_Registrar() )->init();
157 ( new BetterLinks\Mcp\Mcp_Manager() )->init();
158 }
159
160 public function dispatch_hook()
161 {
162 BetterLinks\API::dispatch_hook();
163 BetterLinks\Cron::init();
164 }
165
166 public function set_global_settings()
167 {
168 $GLOBALS['betterlinks'] = BetterLinks\Helper::get_links();
169 $settings = Cache::get_json_settings();
170 $auto_create_link_settings = defined('BETTERLINKS_PRO_AUTO_LINK_CREATE_OPTION_NAME') ? get_option( BETTERLINKS_PRO_AUTO_LINK_CREATE_OPTION_NAME, array() ) : array();
171 if ( is_string( $auto_create_link_settings ) ) {
172 $auto_create_link_settings = json_decode( $auto_create_link_settings, true );
173 }
174 $settings = is_array($settings) ? $settings : array();
175 $auto_create_link_settings = is_array( $auto_create_link_settings ) ? $auto_create_link_settings : array();
176 $GLOBALS['betterlinks_settings'] = array_merge( $settings, $auto_create_link_settings );
177 }
178
179 public function run_migrator()
180 {
181 $btl_version = BetterLinks\Helper::btl_get_option('betterlinks_version');
182 $should_insert = $btl_version===false;
183 if ($btl_version != BETTERLINKS_VERSION && BetterLinks\Helper::btl_update_option('betterlinks_version', BETTERLINKS_VERSION, $should_insert, !$should_insert)) {
184 // The admin links payload is cached in a transient with no expiry, and
185 // which categories it contains depends on the running code (e.g. the
186 // Link in Bio category exclusion). A payload written by the previous
187 // version can therefore describe categories wrongly forever — drop it
188 // once per upgrade so the first dashboard load rebuilds it fresh.
189 BetterLinks\Helper::clear_query_cache();
190 $this->Installer->data($this->Installer->migration)->save()->dispatch();
191 BetterLinks\Helper::btl_update_option('betterlinks_activation_flag', [
192 "last_activation_timestamp" => time(),
193 "last_activation_background_processes_firing_timestamp" => false,
194 ]);
195 }
196 }
197
198 public function activate()
199 {
200 $this->Installer->data($this->Installer->activation)->save()->dispatch();
201 BetterLinks\Helper::btl_update_option('betterlinks_activation_flag', [
202 "last_activation_timestamp" => time(),
203 "last_activation_background_processes_firing_timestamp" => false,
204 ]);
205 add_option('betterlinks_quick_setup', true);
206 }
207
208 /**
209 * Retire the Quick Setup entry on sites that were already set up.
210 *
211 * Only the wizard's final step ever writes 'complete', so any site configured
212 * before the wizard existed — or where an admin skipped it — kept showing
213 * "Quick Setup" forever no matter how configured it was. Runs once per install:
214 * if links already exist, the site is demonstrably past onboarding.
215 */
216 public function maybe_complete_legacy_quick_setup() {
217 if ( get_option( 'betterlinks_quick_setup_backfilled' ) ) return;
218 update_option( 'betterlinks_quick_setup_backfilled', 1, false );
219
220 if ( 'complete' === get_option( 'betterlinks_quick_setup_step' ) ) return;
221
222 global $wpdb;
223 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
224 $has_links = (int) $wpdb->get_var( "SELECT COUNT(ID) FROM {$wpdb->prefix}betterlinks" );
225 if ( $has_links > 0 ) {
226 update_option( 'betterlinks_quick_setup_step', 'complete' );
227 delete_option( 'betterlinks_quick_setup' );
228 }
229 }
230
231 public function quick_setup() {
232 if( 'complete' === get_option('betterlinks_quick_setup_step') ) return;
233 if( get_option( 'betterlinks_quick_setup' ) && is_admin() ) {
234 delete_option( 'betterlinks_quick_setup' );
235
236 $redirect_url = admin_url('admin.php?page=betterlinks-quick-setup');
237 wp_safe_redirect($redirect_url);
238 exit;
239 }
240 }
241
242 public function deactivate()
243 {
244 new BetterLinks\Uninstall();
245 }
246
247 public function frontend_scripts() {
248 $dependencies = include_once BETTERLINKS_ASSETS_DIR_PATH . 'js/betterlinks.app.core.min.asset.php';
249
250 // Enqueue main app script (geolocation logic is bundled inside)
251 wp_enqueue_script( 'betterlinks-app', BETTERLINKS_ASSETS_URI . 'js/betterlinks.app.core.min.js', [ 'jquery' ], $dependencies['version'], true );
252
253 wp_localize_script('betterlinks-app', 'betterLinksApp', [
254 'betterlinks_nonce' => wp_create_nonce('betterlinks_admin_nonce'),
255 'ajaxurl' => admin_url('admin-ajax.php'),
256 'site_url' => apply_filters('betterlinks/site_url', site_url()),
257 'rest_url' => rest_url(),
258 'nonce' => wp_create_nonce('wp_rest'),
259 'betterlinkspro_version' => defined('BETTERLINKS_PRO_VERSION') ? BETTERLINKS_PRO_VERSION : null,
260 ]);
261 }
262 }
263 }
264
265 /**
266 * Initializes the main plugin
267 *
268 * @return \BetterLinks
269 */
270 if (!function_exists('BetterLinks_Start')) {
271 function BetterLinks_Start()
272 {
273 return BetterLinks::init();
274
275 }
276 }
277
278 // Plugin Start
279 BetterLinks_Start();
280