v1.7.1
This commit is contained in:
parent
4402f3570d
commit
7b7fff5246
36 changed files with 610 additions and 31 deletions
|
|
@ -153,7 +153,7 @@
|
|||
var targetId = $( this ).data( 'target' );
|
||||
var $row = $( '#' + targetId );
|
||||
$row.prop( 'hidden', function ( i, v ) { return !v; } );
|
||||
$( this ).text( $row.prop( 'hidden' ) ? 'Dismiss' : 'Cancel' );
|
||||
$( this ).text( $row.prop( 'hidden' ) ? mraAlerts.i18n.dismiss : mraAlerts.i18n.cancel );
|
||||
} );
|
||||
|
||||
// Cancel button inside dismiss form row.
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
var $row = $( this ).closest( 'tr.mra-dismiss-row' );
|
||||
var rowId = $row.attr( 'id' );
|
||||
$row.prop( 'hidden', true );
|
||||
$( '[data-target="' + rowId + '"]' ).text( 'Dismiss' );
|
||||
$( '[data-target="' + rowId + '"]' ).text( mraAlerts.i18n.dismiss );
|
||||
} );
|
||||
} );
|
||||
} ( jQuery ) );
|
||||
|
|
|
|||
|
|
@ -1,5 +1,17 @@
|
|||
== Changelog ==
|
||||
|
||||
= 1.7.1 =
|
||||
|
||||
_Release date: 2026-06-02_
|
||||
|
||||
**Fixed**
|
||||
|
||||
* Dismissal timestamps are now stored in UTC, consistent with all other plugin timestamps (`dismissed_at`).
|
||||
* Settings option (`robotstxt_mediaaudit_settings`) no longer autoloads on every page request, reducing the autoloaded options payload.
|
||||
* Alert count on the Media Audit dashboard is now cached with a transient (1 hour TTL) and invalidated automatically on dismiss, reactivate, scan, purge, and full reset operations.
|
||||
* "Dismiss" and "Cancel" button labels on the Alerts page are now translatable via `wp_localize_script`.
|
||||
* Added direct file access guard (`if ( ! defined( 'ABSPATH' ) ) { exit; }`) to all PHP class files in `includes/`.
|
||||
|
||||
= 1.7.0 =
|
||||
|
||||
_Release date: 2026-05-11_
|
||||
|
|
|
|||
|
|
@ -130,11 +130,12 @@ class AlertsPage {
|
|||
notes = VALUES(notes)",
|
||||
$attachment_id,
|
||||
$user_id,
|
||||
current_time( 'mysql' ),
|
||||
current_time( 'mysql', true ),
|
||||
$notes
|
||||
)
|
||||
);
|
||||
|
||||
delete_transient( 'mra_alert_count' );
|
||||
self::invalidate_cache();
|
||||
}
|
||||
|
||||
|
|
@ -155,6 +156,7 @@ class AlertsPage {
|
|||
array( '%d' )
|
||||
);
|
||||
|
||||
delete_transient( 'mra_alert_count' );
|
||||
self::invalidate_cache();
|
||||
}
|
||||
|
||||
|
|
@ -192,6 +194,17 @@ class AlertsPage {
|
|||
|
||||
wp_enqueue_style( 'mra-admin', $base . 'css/media-audit-admin.css', array(), $ver );
|
||||
wp_enqueue_script( 'mra-admin', $base . 'js/media-audit-admin.js', array( 'jquery' ), $ver, true );
|
||||
|
||||
wp_localize_script(
|
||||
'mra-admin',
|
||||
'mraAlerts',
|
||||
array(
|
||||
'i18n' => array(
|
||||
'dismiss' => __( 'Dismiss', 'robotstxt-mediaaudit' ),
|
||||
'cancel' => __( 'Cancel', 'robotstxt-mediaaudit' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Admin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\External\HostnameFilter;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Admin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Admin\AlertsPage;
|
||||
use MediaRightsAudit\Admin\AttachmentDetailPage;
|
||||
use MediaRightsAudit\External\ExternalScanner;
|
||||
|
|
@ -928,6 +932,11 @@ class AuditPage {
|
|||
* @return int
|
||||
*/
|
||||
private static function get_alert_attachment_count(): int {
|
||||
$cached = get_transient( 'mra_alert_count' );
|
||||
if ( is_int( $cached ) ) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
|
|
@ -937,6 +946,7 @@ class AuditPage {
|
|||
);
|
||||
|
||||
if ( ! is_array( $rows ) ) {
|
||||
set_transient( 'mra_alert_count', 0, HOUR_IN_SECONDS );
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -983,6 +993,7 @@ class AuditPage {
|
|||
}
|
||||
}
|
||||
|
||||
set_transient( 'mra_alert_count', $alert_count, HOUR_IN_SECONDS );
|
||||
return $alert_count;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Admin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Admin\AlertsPage;
|
||||
use MediaRightsAudit\Admin\AttachmentDetailPage;
|
||||
use MediaRightsAudit\External\HostnameFilter;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Admin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers and renders the plugin Settings page with tab navigation.
|
||||
*
|
||||
|
|
@ -233,6 +237,7 @@ class Settings {
|
|||
'type' => 'array',
|
||||
'sanitize_callback' => array( $this, 'sanitize' ),
|
||||
'default' => array(),
|
||||
'autoload' => false,
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Admin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Core\Database;
|
||||
use MediaRightsAudit\Core\Queue\Scheduler;
|
||||
use MediaRightsAudit\External\ExternalScanner;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\CLI;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Core\Database;
|
||||
use MediaRightsAudit\External\ExternalScanner;
|
||||
use MediaRightsAudit\Internal\AttachmentIndexer;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Core;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Admin\Settings;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Core;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages all custom table creation and schema migrations.
|
||||
*
|
||||
|
|
@ -86,6 +90,8 @@ class Database {
|
|||
$wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_usage`" );
|
||||
$wpdb->query( "TRUNCATE TABLE `{$wpdb->prefix}mra_media_index`" );
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery
|
||||
|
||||
delete_transient( 'mra_alert_count' );
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Core;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Core\Queue\Scheduler;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Core;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Admin\AlertsPage;
|
||||
use MediaRightsAudit\Admin\AttachmentDetailPage;
|
||||
use MediaRightsAudit\Admin\AuditPage;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Core\Queue;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin wrapper around Action Scheduler that namespaces all jobs under a single group.
|
||||
*
|
||||
|
|
|
|||
4
includes/External/AbstractProvider.php
vendored
4
includes/External/AbstractProvider.php
vendored
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\External;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides rate-limited scanning via a transient-based per-minute counter.
|
||||
*
|
||||
|
|
|
|||
10
includes/External/ExternalScanner.php
vendored
10
includes/External/ExternalScanner.php
vendored
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\External;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Core\Queue\Scheduler;
|
||||
|
||||
/**
|
||||
|
|
@ -145,6 +149,8 @@ class ExternalScanner {
|
|||
|
||||
self::scan_batch( $batch_size );
|
||||
|
||||
delete_transient( 'mra_alert_count' );
|
||||
|
||||
if ( self::get_pending_count() > 0 ) {
|
||||
Scheduler::schedule_single( self::AS_HOOK );
|
||||
}
|
||||
|
|
@ -361,6 +367,8 @@ class ExternalScanner {
|
|||
)
|
||||
);
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
||||
|
||||
delete_transient( 'mra_alert_count' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -381,6 +389,8 @@ class ExternalScanner {
|
|||
);
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery
|
||||
|
||||
delete_transient( 'mra_alert_count' );
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
|
|
|
|||
4
includes/External/GoogleVisionProvider.php
vendored
4
includes/External/GoogleVisionProvider.php
vendored
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\External;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Admin\Settings;
|
||||
|
||||
/**
|
||||
|
|
|
|||
4
includes/External/HostnameFilter.php
vendored
4
includes/External/HostnameFilter.php
vendored
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\External;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies hostnames against the site's configured alert (include) and
|
||||
* ignored (exclude) lists stored in the robotstxt_mediaaudit_settings option.
|
||||
|
|
|
|||
4
includes/External/PicDefenseProvider.php
vendored
4
includes/External/PicDefenseProvider.php
vendored
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\External;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Admin\Settings;
|
||||
|
||||
/**
|
||||
|
|
|
|||
4
includes/External/ResultsConsolidator.php
vendored
4
includes/External/ResultsConsolidator.php
vendored
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\External;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates external scan results across multiple providers to surface
|
||||
* domains that appear in more than one provider's findings (consensus signals).
|
||||
|
|
|
|||
4
includes/External/ScanResult.php
vendored
4
includes/External/ScanResult.php
vendored
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\External;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable result returned by AbstractProvider::scan().
|
||||
*/
|
||||
|
|
|
|||
4
includes/External/TinEyeProvider.php
vendored
4
includes/External/TinEyeProvider.php
vendored
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\External;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Admin\Settings;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Internal;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Core\Queue\Scheduler;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Internal;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use MediaRightsAudit\Core\Queue\Scheduler;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Privacy;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erases Media Audit data for a given user via the WordPress privacy tools.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
namespace MediaRightsAudit\Privacy;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports Media Audit data for a given user via the WordPress privacy tools.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
=== Media Audit (by ROBOTSTXT) ===
|
||||
Contributors: javiercasares, robotstxt
|
||||
Tags: media, copyright, images, reverse image search, media library
|
||||
Requires at least: 6.8
|
||||
Requires at least: 5.3
|
||||
Tested up to: 7.0
|
||||
Requires PHP: 8.2
|
||||
Requires PHP: 8.0
|
||||
Requires Plugins: action-scheduler
|
||||
Stable tag: 1.7.0
|
||||
Stable tag: 1.7.1
|
||||
License: GPL-3.0-or-later
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
* Plugin Name: Media Audit (by ROBOTSTXT)
|
||||
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-mediaaudit
|
||||
* Description: Internal media library usage auditing and external reverse image search to detect potential copyright issues.
|
||||
* Version: 1.7.0
|
||||
* Requires at least: 6.8
|
||||
* Version: 1.7.1
|
||||
* Requires at least: 5.3
|
||||
* Tested up to: 7.0
|
||||
* Requires PHP: 8.2
|
||||
* Requires PHP: 8.0
|
||||
* Requires Plugins: action-scheduler
|
||||
* Author: ROBOTSTXT
|
||||
* Author URI: https://www.robotstxt.es/
|
||||
|
|
@ -23,7 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
exit;
|
||||
}
|
||||
|
||||
define( 'ROBOTSTXT_MEDIAAUDIT_VERSION', '1.7.0' );
|
||||
define( 'ROBOTSTXT_MEDIAAUDIT_VERSION', '1.7.1' );
|
||||
define( 'ROBOTSTXT_MEDIAAUDIT_DB_VERSION', '1.2.0' );
|
||||
define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_FILE', __FILE__ );
|
||||
define( 'ROBOTSTXT_MEDIAAUDIT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
|
|
|
|||
383
robotstxt-updater.php
Normal file
383
robotstxt-updater.php
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
<?php
|
||||
/**
|
||||
* Generic JSON-based updater for ROBOTSTXT plugins.
|
||||
*
|
||||
* This file is designed to be copied to any ROBOTSTXT plugin.
|
||||
* It auto-configures itself by reading the plugin headers.
|
||||
*
|
||||
* @package ROBOTSTXT
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'Robotstxt_Updater' ) ) {
|
||||
/**
|
||||
* Class Robotstxt_Updater
|
||||
*
|
||||
* Generic updater that works with any plugin.
|
||||
* Reads plugin headers and constructs update URL automatically.
|
||||
*/
|
||||
class Robotstxt_Updater {
|
||||
|
||||
/**
|
||||
* Plugin file path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $plugin_file_path;
|
||||
|
||||
/**
|
||||
* Plugin basename (e.g., 'my-plugin/my-plugin.php').
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $plugin_basename;
|
||||
|
||||
/**
|
||||
* Plugin slug (directory name).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $plugin_slug;
|
||||
|
||||
/**
|
||||
* Remote JSON URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $json_url;
|
||||
|
||||
/**
|
||||
* Cache key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $cache_key;
|
||||
|
||||
/**
|
||||
* Plugin headers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $plugin_data;
|
||||
|
||||
/**
|
||||
* Initialize the updater.
|
||||
*
|
||||
* Usage in your main plugin file:
|
||||
* require_once __DIR__ . '/robotstxt-updater.php';
|
||||
* Robotstxt_Updater::init( __FILE__ );
|
||||
*
|
||||
* @param string $plugin_file_path Absolute path to the main plugin file.
|
||||
*/
|
||||
public static function init( string $plugin_file_path ): void {
|
||||
$instance = new self( $plugin_file_path );
|
||||
$instance->register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $plugin_file_path Absolute path to the main plugin file.
|
||||
*/
|
||||
private function __construct( string $plugin_file_path ) {
|
||||
$this->plugin_file_path = $plugin_file_path;
|
||||
$this->plugin_basename = plugin_basename( $plugin_file_path );
|
||||
$this->plugin_slug = dirname( $this->plugin_basename );
|
||||
$this->plugin_data = $this->get_plugin_data();
|
||||
$this->json_url = $this->build_json_url();
|
||||
$this->cache_key = 'robotstxt_updater_' . md5( $this->plugin_basename );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register WordPress hooks.
|
||||
*/
|
||||
private function register(): void {
|
||||
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'inject_update_info' ) );
|
||||
add_filter( 'plugins_api', array( $this, 'provide_plugin_details' ), 10, 3 );
|
||||
add_action( 'admin_init', array( $this, 'handle_cache_clear' ) );
|
||||
add_action( 'robotstxt_updater_clear_cache', array( $this, 'clear_cache' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin headers.
|
||||
*
|
||||
* @return array Plugin data.
|
||||
*/
|
||||
private function get_plugin_data(): array {
|
||||
if ( ! function_exists( 'get_plugin_data' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
return get_plugin_data( $this->plugin_file_path, false, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build JSON URL from plugin headers.
|
||||
*
|
||||
* Tries to use "Gitea Plugin URI" header to construct the URL.
|
||||
* Falls back to Plugin URI if Gitea URI is not available.
|
||||
*
|
||||
* @return string JSON URL.
|
||||
*/
|
||||
private function build_json_url(): string {
|
||||
// Try Gitea Plugin URI (format: "OWNER/REPO" or full URL).
|
||||
if ( ! empty( $this->plugin_data['Gitea Plugin URI'] ) ) {
|
||||
$gitea_uri = $this->plugin_data['Gitea Plugin URI'];
|
||||
|
||||
// If it's already a full URL, use it.
|
||||
if ( str_starts_with( $gitea_uri, 'http' ) ) {
|
||||
// Extract base URL and construct JSON path.
|
||||
return rtrim( $gitea_uri, '/' ) . '/raw/branch/main/update.json';
|
||||
}
|
||||
|
||||
// If it's in format "OWNER/REPO", construct full URL.
|
||||
if ( preg_match( '#^[^/]+/[^/]+$#', $gitea_uri ) ) {
|
||||
return "https://git.robotstxt.es/{$gitea_uri}/raw/branch/main/update.json";
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try to extract from Plugin URI.
|
||||
if ( ! empty( $this->plugin_data['PluginURI'] ) ) {
|
||||
$plugin_uri = $this->plugin_data['PluginURI'];
|
||||
if ( str_contains( $plugin_uri, 'git.robotstxt.es' ) ) {
|
||||
return rtrim( $plugin_uri, '/' ) . '/raw/branch/main/update.json';
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: construct from plugin slug.
|
||||
return "https://git.robotstxt.es/ROBOTSTXT/{$this->plugin_slug}/raw/branch/main/update.json";
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject update info into WP's plugin update transient.
|
||||
*
|
||||
* @param object|mixed $transient The update_plugins transient.
|
||||
*
|
||||
* @return object The modified transient.
|
||||
*/
|
||||
public function inject_update_info( $transient ) {
|
||||
if ( ! is_object( $transient ) ) {
|
||||
$transient = new stdClass();
|
||||
}
|
||||
|
||||
if ( empty( $transient->checked ) || ! is_array( $transient->checked ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( empty( $transient->checked[ $this->plugin_basename ] ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
$current_version = $transient->checked[ $this->plugin_basename ];
|
||||
$remote = $this->get_remote_data();
|
||||
|
||||
if ( empty( $remote['version'] ) || empty( $remote['download_url'] ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( ! $this->is_compatible( $remote ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( version_compare( $remote['version'], $current_version, '>' ) ) {
|
||||
$update = (object) array(
|
||||
'slug' => $remote['slug'] ?? $this->plugin_slug,
|
||||
'plugin' => $this->plugin_basename,
|
||||
'new_version' => $remote['version'],
|
||||
'url' => $remote['homepage'] ?? $this->plugin_data['PluginURI'] ?? '',
|
||||
'package' => $remote['download_url'],
|
||||
'tested' => $remote['tested'] ?? '',
|
||||
'requires' => $remote['requires'] ?? '',
|
||||
'requires_php' => $remote['requires_php'] ?? '',
|
||||
);
|
||||
|
||||
$transient->response[ $this->plugin_basename ] = $update;
|
||||
}
|
||||
|
||||
return $transient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide "View details" modal content.
|
||||
*
|
||||
* @param false|object|array $result The result object or array.
|
||||
* @param string $action The type of information being requested.
|
||||
* @param object $args Plugin API arguments.
|
||||
*
|
||||
* @return false|object The plugin information object or false.
|
||||
*/
|
||||
public function provide_plugin_details( $result, string $action, object $args ) {
|
||||
if ( 'plugin_information' !== $action ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( empty( $args->slug ) || $args->slug !== $this->plugin_slug ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$remote = $this->get_remote_data();
|
||||
|
||||
if ( empty( $remote['version'] ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return (object) array(
|
||||
'name' => $remote['name'] ?? $this->plugin_data['Name'] ?? $this->plugin_slug,
|
||||
'slug' => $remote['slug'] ?? $this->plugin_slug,
|
||||
'version' => $remote['version'],
|
||||
'author' => $remote['author'] ?? $this->plugin_data['Author'] ?? '',
|
||||
'homepage' => $remote['homepage'] ?? $this->plugin_data['PluginURI'] ?? '',
|
||||
'requires' => $remote['requires'] ?? '',
|
||||
'tested' => $remote['tested'] ?? '',
|
||||
'requires_php' => $remote['requires_php'] ?? '',
|
||||
'sections' => array(
|
||||
'description' => $remote['description'] ?? $this->plugin_data['Description'] ?? '',
|
||||
'changelog' => $remote['changelog'] ?? '',
|
||||
),
|
||||
'download_link' => $remote['download_url'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote data with caching and HMAC signature verification.
|
||||
*
|
||||
* @return array Remote data.
|
||||
*/
|
||||
private function get_remote_data(): array {
|
||||
$cached = get_site_transient( $this->cache_key );
|
||||
|
||||
// Verify HMAC signature if AUTH_SALT is defined and cache has signature.
|
||||
if ( false !== $cached && defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) {
|
||||
if ( is_array( $cached ) && isset( $cached['signature'], $cached['data'] ) ) {
|
||||
$expected_sig = hash_hmac( 'sha256', $this->cache_key . serialize( $cached['data'] ), AUTH_SALT );
|
||||
|
||||
if ( hash_equals( $expected_sig, $cached['signature'] ) ) {
|
||||
// Signature valid, return data.
|
||||
return is_array( $cached['data'] ) ? $cached['data'] : array();
|
||||
}
|
||||
|
||||
// Signature invalid, delete corrupted cache.
|
||||
delete_site_transient( $this->cache_key );
|
||||
$cached = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid cache, fetch fresh data.
|
||||
if ( false === $cached ) {
|
||||
$remote = $this->fetch_json();
|
||||
|
||||
// Store with HMAC signature if AUTH_SALT is available.
|
||||
if ( defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) {
|
||||
$payload = array(
|
||||
'data' => $remote ?: array(),
|
||||
'timestamp' => time(),
|
||||
'signature' => hash_hmac( 'sha256', $this->cache_key . serialize( $remote ?: array() ), AUTH_SALT ),
|
||||
);
|
||||
set_site_transient( $this->cache_key, $payload, 6 * HOUR_IN_SECONDS );
|
||||
} else {
|
||||
// Fallback to standard caching.
|
||||
set_site_transient( $this->cache_key, $remote ?: array(), 6 * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return is_array( $remote ) ? $remote : array();
|
||||
}
|
||||
|
||||
// Legacy cache format without signature (backward compatibility).
|
||||
return is_array( $cached ) ? $cached : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch JSON from remote URL.
|
||||
*
|
||||
* @return array Decoded JSON data.
|
||||
*/
|
||||
private function fetch_json(): array {
|
||||
$response = wp_remote_get(
|
||||
$this->json_url,
|
||||
array(
|
||||
'timeout' => 10,
|
||||
'headers' => array(
|
||||
'Accept' => 'application/json',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$code = (int) wp_remote_retrieve_response_code( $response );
|
||||
if ( $code < 200 || $code >= 300 ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$data = json_decode( $body, true );
|
||||
|
||||
return is_array( $data ) ? $data : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check compatibility.
|
||||
*
|
||||
* @param array $remote Remote data.
|
||||
*
|
||||
* @return bool True if compatible.
|
||||
*/
|
||||
private function is_compatible( array $remote ): bool {
|
||||
if ( ! empty( $remote['requires_php'] ) ) {
|
||||
if ( version_compare( PHP_VERSION, $remote['requires_php'], '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $remote['requires'] ) ) {
|
||||
if ( version_compare( get_bloginfo( 'version' ), $remote['requires'], '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle manual cache clear via URL parameter.
|
||||
*/
|
||||
public function handle_cache_clear(): void {
|
||||
// Check if this is a cache clear request first.
|
||||
$clear_cache = filter_input( INPUT_GET, 'robotstxt_clear_update_cache', FILTER_UNSAFE_RAW );
|
||||
if ( null === $clear_cache ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a cache clear request - now verify nonce.
|
||||
$nonce_raw = filter_input( INPUT_GET, '_wpnonce', FILTER_UNSAFE_RAW );
|
||||
$nonce = $nonce_raw ? sanitize_text_field( wp_unslash( $nonce_raw ) ) : '';
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, 'robotstxt_clear_update_cache' ) ) {
|
||||
wp_die( esc_html__( 'Security check failed', 'robotstxt-smtp' ) );
|
||||
}
|
||||
|
||||
// Check permissions.
|
||||
if ( ! current_user_can( 'update_plugins' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-smtp' ) );
|
||||
}
|
||||
|
||||
$this->clear_cache();
|
||||
wp_safe_redirect( remove_query_arg( array( 'robotstxt_clear_update_cache', '_wpnonce' ) ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear update cache.
|
||||
*/
|
||||
public function clear_cache(): void {
|
||||
delete_site_transient( $this->cache_key );
|
||||
delete_site_transient( 'update_plugins' );
|
||||
}
|
||||
}
|
||||
}
|
||||
7
vendor/autoload.php
vendored
7
vendor/autoload.php
vendored
|
|
@ -14,12 +14,9 @@ if (PHP_VERSION_ID < 50600) {
|
|||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
throw new RuntimeException($err);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit957728ab3efa005f456e5f9df13a19d2::getLoader();
|
||||
return ComposerAutoloaderInit149e6f8eeaef8d46113f09e1e8d4e757::getLoader();
|
||||
|
|
|
|||
45
vendor/composer/InstalledVersions.php
vendored
45
vendor/composer/InstalledVersions.php
vendored
|
|
@ -26,12 +26,23 @@ use Composer\Semver\VersionParser;
|
|||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||
* @internal
|
||||
*/
|
||||
private static $selfDir = null;
|
||||
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $installedIsLocalDir;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
|
|
@ -309,6 +320,24 @@ class InstalledVersions
|
|||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
|
||||
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||
// all installed packages for example
|
||||
self::$installedIsLocalDir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getSelfDir()
|
||||
{
|
||||
if (self::$selfDir === null) {
|
||||
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||
}
|
||||
|
||||
return self::$selfDir;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -322,19 +351,27 @@ class InstalledVersions
|
|||
}
|
||||
|
||||
$installed = array();
|
||||
$copiedLocalDir = false;
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
$selfDir = self::getSelfDir();
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = $required;
|
||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $installed[count($installed) - 1];
|
||||
self::$installedByVendor[$vendorDir] = $required;
|
||||
$installed[] = $required;
|
||||
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||
self::$installed = $required;
|
||||
self::$installedIsLocalDir = true;
|
||||
}
|
||||
}
|
||||
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -350,7 +387,7 @@ class InstalledVersions
|
|||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array()) {
|
||||
if (self::$installed !== array() && !$copiedLocalDir) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
|
|
|
|||
2
vendor/composer/LICENSE
vendored
2
vendor/composer/LICENSE
vendored
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
|
@ -17,3 +18,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
|
|
|||
10
vendor/composer/autoload_real.php
vendored
10
vendor/composer/autoload_real.php
vendored
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit957728ab3efa005f456e5f9df13a19d2
|
||||
class ComposerAutoloaderInit149e6f8eeaef8d46113f09e1e8d4e757
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
|
|
@ -22,12 +22,14 @@ class ComposerAutoloaderInit957728ab3efa005f456e5f9df13a19d2
|
|||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit957728ab3efa005f456e5f9df13a19d2', 'loadClassLoader'), true, true);
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit149e6f8eeaef8d46113f09e1e8d4e757', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit957728ab3efa005f456e5f9df13a19d2', 'loadClassLoader'));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit149e6f8eeaef8d46113f09e1e8d4e757', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit957728ab3efa005f456e5f9df13a19d2::getInitializer($loader));
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit149e6f8eeaef8d46113f09e1e8d4e757::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
|
|
|
|||
12
vendor/composer/autoload_static.php
vendored
12
vendor/composer/autoload_static.php
vendored
|
|
@ -4,17 +4,17 @@
|
|||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit957728ab3efa005f456e5f9df13a19d2
|
||||
class ComposerStaticInit149e6f8eeaef8d46113f09e1e8d4e757
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'M' =>
|
||||
'M' =>
|
||||
array (
|
||||
'MediaRightsAudit\\' => 17,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'MediaRightsAudit\\' =>
|
||||
'MediaRightsAudit\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/includes',
|
||||
),
|
||||
|
|
@ -51,9 +51,9 @@ class ComposerStaticInit957728ab3efa005f456e5f9df13a19d2
|
|||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit957728ab3efa005f456e5f9df13a19d2::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit957728ab3efa005f456e5f9df13a19d2::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit957728ab3efa005f456e5f9df13a19d2::$classMap;
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit149e6f8eeaef8d46113f09e1e8d4e757::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit149e6f8eeaef8d46113f09e1e8d4e757::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit149e6f8eeaef8d46113f09e1e8d4e757::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
|
|
|
|||
4
vendor/composer/installed.php
vendored
4
vendor/composer/installed.php
vendored
|
|
@ -1,6 +1,6 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => 'robotstxt/mediaaudit',
|
||||
'name' => 'robotstxt/robotstxt-mediaaudit',
|
||||
'pretty_version' => '1.0.0+no-version-set',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => null,
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
'dev' => false,
|
||||
),
|
||||
'versions' => array(
|
||||
'robotstxt/mediaaudit' => array(
|
||||
'robotstxt/robotstxt-mediaaudit' => array(
|
||||
'pretty_version' => '1.0.0+no-version-set',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => null,
|
||||
|
|
|
|||
25
vendor/composer/platform_check.php
vendored
Normal file
25
vendor/composer/platform_check.php
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
// platform_check.php @generated by Composer
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 80200)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||
} elseif (!headers_sent()) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue