| 1 |
<?php |
| 2 |
|
| 3 |
namespace Pronamic\WordPress\PronamicClient; |
| 4 |
|
| 5 |
use WP_Admin_Bar; |
| 6 |
|
| 7 |
class AdminerModule { |
| 8 |
/** |
| 9 |
* Instance of this class. |
| 10 |
* |
| 11 |
* @var self |
| 12 |
*/ |
| 13 |
protected static $instance = null; |
| 14 |
|
| 15 |
/** |
| 16 |
* Plugin. |
| 17 |
* |
| 18 |
* @var Plugin |
| 19 |
*/ |
| 20 |
private $plugin; |
| 21 |
|
| 22 |
/** |
| 23 |
* Construct Adminer module. |
| 24 |
* |
| 25 |
* @param Plugin $plugin Plugin. |
| 26 |
*/ |
| 27 |
private function __construct( Plugin $plugin ) { |
| 28 |
$this->plugin = $plugin; |
| 29 |
|
| 30 |
\add_action( 'admin_post_pronamic_client_adminer_login', [ $this, 'adminer_login' ] ); |
| 31 |
|
| 32 |
\add_action( 'admin_bar_menu', [ $this, 'admin_bar_menu' ], 20, 1 ); |
| 33 |
} |
| 34 |
|
| 35 |
/** |
| 36 |
* Get the Adminer login URL. |
| 37 |
* |
| 38 |
* @return string |
| 39 |
*/ |
| 40 |
private function get_login_url() { |
| 41 |
return \wp_nonce_url( |
| 42 |
\add_query_arg( |
| 43 |
[ |
| 44 |
'action' => 'pronamic_client_adminer_login', |
| 45 |
], |
| 46 |
\admin_url( 'admin-post.php' ) |
| 47 |
), |
| 48 |
'pronamic_client_adminer_login' |
| 49 |
); |
| 50 |
} |
| 51 |
|
| 52 |
/** |
| 53 |
* Add Adminer to the Pronamic admin bar menu. |
| 54 |
* |
| 55 |
* @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance. |
| 56 |
*/ |
| 57 |
public function admin_bar_menu( WP_Admin_Bar $wp_admin_bar ) { |
| 58 |
if ( ! \current_user_can( 'pronamic_client' ) ) { |
| 59 |
return; |
| 60 |
} |
| 61 |
|
| 62 |
$wp_admin_bar->add_menu( |
| 63 |
[ |
| 64 |
'parent' => 'pronamic', |
| 65 |
'id' => 'pronamic_adminer', |
| 66 |
'title' => \__( 'Adminer', 'pronamic-client' ), |
| 67 |
'href' => $this->get_login_url(), |
| 68 |
'meta' => [ |
| 69 |
'target' => '_blank', |
| 70 |
'rel' => 'noopener noreferrer', |
| 71 |
], |
| 72 |
] |
| 73 |
); |
| 74 |
} |
| 75 |
|
| 76 |
/** |
| 77 |
* Login to Adminer. |
| 78 |
*/ |
| 79 |
public function adminer_login() { |
| 80 |
if ( ! \current_user_can( 'pronamic_client' ) ) { |
| 81 |
\wp_die( |
| 82 |
esc_html__( 'You are not allowed to access Adminer.', 'pronamic-client' ), |
| 83 |
esc_html__( 'Forbidden', 'pronamic-client' ), |
| 84 |
403 |
| 85 |
); |
| 86 |
} |
| 87 |
|
| 88 |
\check_admin_referer( 'pronamic_client_adminer_login' ); |
| 89 |
|
| 90 |
$adminer_url = 'https://www.adminer.org/latest.php'; |
| 91 |
|
| 92 |
$filename = 'pronamic-client-adminer-' . md5( gmdate( 'Y-m-d' ) ) . '.php'; |
| 93 |
|
| 94 |
$adminer_path = \sys_get_temp_dir() . \DIRECTORY_SEPARATOR . $filename; |
| 95 |
|
| 96 |
if ( ! \is_file( $adminer_path ) ) { |
| 97 |
$code = \file_get_contents( $adminer_url ); |
| 98 |
|
| 99 |
if ( false === $code ) { |
| 100 |
\wp_die( |
| 101 |
esc_html__( 'Adminer download failed.', 'pronamic-client' ), |
| 102 |
esc_html__( 'Error', 'pronamic-client' ), |
| 103 |
500 |
| 104 |
); |
| 105 |
} |
| 106 |
|
| 107 |
// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_file_put_contents |
| 108 |
\file_put_contents( $adminer_path, $code ); |
| 109 |
} |
| 110 |
|
| 111 |
$driver = 'server'; |
| 112 |
$server = DB_HOST; |
| 113 |
$username = DB_USER; |
| 114 |
$password = DB_PASSWORD; |
| 115 |
$db = DB_NAME; |
| 116 |
|
| 117 |
if ( \defined( 'DB_ENGINE' ) && 'sqlite' === \DB_ENGINE ) { |
| 118 |
$driver = 'sqlite'; |
| 119 |
} |
| 120 |
|
| 121 |
if ( \defined( 'FQDB' ) ) { |
| 122 |
$db = \FQDB; |
| 123 |
} |
| 124 |
|
| 125 |
$token = $this->build_adminer_token( |
| 126 |
[ |
| 127 |
'driver' => $driver, |
| 128 |
'server' => $server, |
| 129 |
'username' => $username, |
| 130 |
'password' => $password, |
| 131 |
'db' => $db, |
| 132 |
] |
| 133 |
); |
| 134 |
|
| 135 |
$adminer_url = \add_query_arg( |
| 136 |
[ 'pronamic_client_adminer_token' => $token ], |
| 137 |
\plugins_url( 'adminer/', $this->plugin->file ) |
| 138 |
); |
| 139 |
|
| 140 |
\wp_safe_redirect( $adminer_url ); |
| 141 |
|
| 142 |
exit; |
| 143 |
} |
| 144 |
|
| 145 |
/** |
| 146 |
* Build a single-use Adminer login token. |
| 147 |
* |
| 148 |
* The DB credentials are encrypted in a short-lived temp file. The token |
| 149 |
* identifies the file and derives its encryption key. |
| 150 |
* |
| 151 |
* @param array<string, string> $credentials Driver, server, username, password, db. |
| 152 |
* @return string |
| 153 |
*/ |
| 154 |
private function build_adminer_token( array $credentials ) { |
| 155 |
$token = \md5( \random_bytes( 32 ) ); |
| 156 |
$creds_file = \sys_get_temp_dir() . \DIRECTORY_SEPARATOR . '.' . $token; |
| 157 |
$key = \hash( 'sha256', $token, true ); |
| 158 |
$iv = \random_bytes( 16 ); |
| 159 |
$json = \wp_json_encode( $credentials ); |
| 160 |
$cipher = \openssl_encrypt( $json, 'aes-256-cbc', $key, \OPENSSL_RAW_DATA, $iv ); |
| 161 |
|
| 162 |
// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_file_put_contents |
| 163 |
\file_put_contents( $creds_file, $iv . $cipher ); |
| 164 |
|
| 165 |
// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.chmod_chmod |
| 166 |
\chmod( $creds_file, 0600 ); |
| 167 |
|
| 168 |
return $token; |
| 169 |
} |
| 170 |
|
| 171 |
/** |
| 172 |
* Return an instance of this class. |
| 173 |
* |
| 174 |
* @param Plugin $plugin Plugin. |
| 175 |
* @return self |
| 176 |
*/ |
| 177 |
public static function get_instance( Plugin $plugin ) { |
| 178 |
if ( null === self::$instance ) { |
| 179 |
self::$instance = new self( $plugin ); |
| 180 |
} |
| 181 |
|
| 182 |
return self::$instance; |
| 183 |
} |
| 184 |
} |
| 185 |
|