php84compat.php000064400000007110147206606520007433 0ustar00getMessage() === 'AES-256-GCM is not available')) { throw $ex; } return false; } } } if (!is_callable('sodium_crypto_aead_aes256gcm_encrypt')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_encrypt() * @param string $message * @param string $additional_data * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_aead_aes256gcm_encrypt( #[\SensitiveParameter] $message, $additional_data, $nonce, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_encrypt($message, $additional_data, $nonce, $key); } } if (!is_callable('sodium_crypto_aead_aes256gcm_is_available')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_is_available() * @return bool */ function sodium_crypto_aead_aes256gcm_is_available() { return ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_is_available(); } } if (!is_callable('sodium_crypto_aead_chacha20poly1305_decrypt')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_decrypt() * @param string $ciphertext * @param string $additional_data * @param string $nonce * @param string $key * @return string|bool */ function sodium_crypto_aead_chacha20poly1305_decrypt( $ciphertext, $additional_data, $nonce, #[\SensitiveParameter] $key ) { try { return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_decrypt( $ciphertext, $additional_data, $nonce, $key ); } catch (Error $ex) { return false; } catch (Exception $ex) { return false; } } } if (!is_callable('sodium_crypto_aead_chacha20poly1305_encrypt')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_encrypt() * @param string $message * @param string $additional_data * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_aead_chacha20poly1305_encrypt( #[\SensitiveParameter] $message, $additional_data, $nonce, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_encrypt( $message, $additional_data, $nonce, $key ); } } if (!is_callable('sodium_crypto_aead_chacha20poly1305_keygen')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_keygen() * @return string * @throws Exception */ function sodium_crypto_aead_chacha20poly1305_keygen() { return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_keygen(); } } if (!is_callable('sodium_crypto_aead_chacha20poly1305_ietf_decrypt')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_decrypt() * @param string $message * @param string $additional_data * @param string $nonce * @param string $key * @return string|bool */ function sodium_crypto_aead_chacha20poly1305_ietf_decrypt( $message, $additional_data, $nonce, #[\SensitiveParameter] $key ) { try { return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_decrypt( $message, $additional_data, $nonce, $key ); } catch (Error $ex) { return false; } catch (Exception $ex) { return false; } } } if (!is_callable('sodium_crypto_aead_chacha20poly1305_ietf_encrypt')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_encrypt() * @param string $message * @param string $additional_data * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_aead_chacha20poly1305_ietf_encrypt( #[\SensitiveParameter] $message, $additional_data, $nonce, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_encrypt( $message, $additional_data, $nonce, $key ); } } if (!is_callable('sodium_crypto_aead_chacha20poly1305_ietf_keygen')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_keygen() * @return string * @throws Exception */ function sodium_crypto_aead_chacha20poly1305_ietf_keygen() { return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_keygen(); } } if (!is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_decrypt')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_decrypt() * @param string $ciphertext * @param string $additional_data * @param string $nonce * @param string $key * @return string|bool */ function sodium_crypto_aead_xchacha20poly1305_ietf_decrypt( $ciphertext, $additional_data, $nonce, #[\SensitiveParameter] $key ) { try { return ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_decrypt( $ciphertext, $additional_data, $nonce, $key, true ); } catch (Error $ex) { return false; } catch (Exception $ex) { return false; } } } if (!is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_encrypt() * @param string $message * @param string $additional_data * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_aead_xchacha20poly1305_ietf_encrypt( #[\SensitiveParameter] $message, $additional_data, $nonce, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_encrypt( $message, $additional_data, $nonce, $key, true ); } } if (!is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_keygen')) { /** * @see ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_keygen() * @return string * @throws Exception */ function sodium_crypto_aead_xchacha20poly1305_ietf_keygen() { return ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_keygen(); } } if (!is_callable('sodium_crypto_auth')) { /** * @see ParagonIE_Sodium_Compat::crypto_auth() * @param string $message * @param string $key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_auth( $message, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_auth($message, $key); } } if (!is_callable('sodium_crypto_auth_keygen')) { /** * @see ParagonIE_Sodium_Compat::crypto_auth_keygen() * @return string * @throws Exception */ function sodium_crypto_auth_keygen() { return ParagonIE_Sodium_Compat::crypto_auth_keygen(); } } if (!is_callable('sodium_crypto_auth_verify')) { /** * @see ParagonIE_Sodium_Compat::crypto_auth_verify() * @param string $mac * @param string $message * @param string $key * @return bool * @throws SodiumException * @throws TypeError */ function sodium_crypto_auth_verify( $mac, $message, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_auth_verify($mac, $message, $key); } } if (!is_callable('sodium_crypto_box')) { /** * @see ParagonIE_Sodium_Compat::crypto_box() * @param string $message * @param string $nonce * @param string $key_pair * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_box( #[\SensitiveParameter] $message, $nonce, #[\SensitiveParameter] $key_pair ) { return ParagonIE_Sodium_Compat::crypto_box($message, $nonce, $key_pair); } } if (!is_callable('sodium_crypto_box_keypair')) { /** * @see ParagonIE_Sodium_Compat::crypto_box_keypair() * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_box_keypair() { return ParagonIE_Sodium_Compat::crypto_box_keypair(); } } if (!is_callable('sodium_crypto_box_keypair_from_secretkey_and_publickey')) { /** * @see ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey() * @param string $secret_key * @param string $public_key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_box_keypair_from_secretkey_and_publickey( #[\SensitiveParameter] $secret_key, $public_key ) { return ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey($secret_key, $public_key); } } if (!is_callable('sodium_crypto_box_open')) { /** * @see ParagonIE_Sodium_Compat::crypto_box_open() * @param string $ciphertext * @param string $nonce * @param string $key_pair * @return string|bool */ function sodium_crypto_box_open( $ciphertext, $nonce, #[\SensitiveParameter] $key_pair ) { try { return ParagonIE_Sodium_Compat::crypto_box_open($ciphertext, $nonce, $key_pair); } catch (Error $ex) { return false; } catch (Exception $ex) { return false; } } } if (!is_callable('sodium_crypto_box_publickey')) { /** * @see ParagonIE_Sodium_Compat::crypto_box_publickey() * @param string $key_pair * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_box_publickey( #[\SensitiveParameter] $key_pair ) { return ParagonIE_Sodium_Compat::crypto_box_publickey($key_pair); } } if (!is_callable('sodium_crypto_box_publickey_from_secretkey')) { /** * @see ParagonIE_Sodium_Compat::crypto_box_publickey_from_secretkey() * @param string $secret_key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_box_publickey_from_secretkey( #[\SensitiveParameter] $secret_key ) { return ParagonIE_Sodium_Compat::crypto_box_publickey_from_secretkey($secret_key); } } if (!is_callable('sodium_crypto_box_seal')) { /** * @see ParagonIE_Sodium_Compat::crypto_box_seal() * @param string $message * @param string $public_key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_box_seal( #[\SensitiveParameter] $message, $public_key ) { return ParagonIE_Sodium_Compat::crypto_box_seal($message, $public_key); } } if (!is_callable('sodium_crypto_box_seal_open')) { /** * @see ParagonIE_Sodium_Compat::crypto_box_seal_open() * @param string $message * @param string $key_pair * @return string|bool * @throws SodiumException */ function sodium_crypto_box_seal_open( $message, #[\SensitiveParameter] $key_pair ) { try { return ParagonIE_Sodium_Compat::crypto_box_seal_open($message, $key_pair); } catch (SodiumException $ex) { if ($ex->getMessage() === 'Argument 2 must be CRYPTO_BOX_KEYPAIRBYTES long.') { throw $ex; } return false; } } } if (!is_callable('sodium_crypto_box_secretkey')) { /** * @see ParagonIE_Sodium_Compat::crypto_box_secretkey() * @param string $key_pair * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_box_secretkey( #[\SensitiveParameter] $key_pair ) { return ParagonIE_Sodium_Compat::crypto_box_secretkey($key_pair); } } if (!is_callable('sodium_crypto_box_seed_keypair')) { /** * @see ParagonIE_Sodium_Compat::crypto_box_seed_keypair() * @param string $seed * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_box_seed_keypair( #[\SensitiveParameter] $seed ) { return ParagonIE_Sodium_Compat::crypto_box_seed_keypair($seed); } } if (!is_callable('sodium_crypto_generichash')) { /** * @see ParagonIE_Sodium_Compat::crypto_generichash() * @param string $message * @param string|null $key * @param int $length * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_generichash( $message, #[\SensitiveParameter] $key = null, $length = 32 ) { return ParagonIE_Sodium_Compat::crypto_generichash($message, $key, $length); } } if (!is_callable('sodium_crypto_generichash_final')) { /** * @see ParagonIE_Sodium_Compat::crypto_generichash_final() * @param string|null $state * @param int $outputLength * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_generichash_final(&$state, $outputLength = 32) { return ParagonIE_Sodium_Compat::crypto_generichash_final($state, $outputLength); } } if (!is_callable('sodium_crypto_generichash_init')) { /** * @see ParagonIE_Sodium_Compat::crypto_generichash_init() * @param string|null $key * @param int $length * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_generichash_init( #[\SensitiveParameter] $key = null, $length = 32 ) { return ParagonIE_Sodium_Compat::crypto_generichash_init($key, $length); } } if (!is_callable('sodium_crypto_generichash_keygen')) { /** * @see ParagonIE_Sodium_Compat::crypto_generichash_keygen() * @return string * @throws Exception */ function sodium_crypto_generichash_keygen() { return ParagonIE_Sodium_Compat::crypto_generichash_keygen(); } } if (!is_callable('sodium_crypto_generichash_update')) { /** * @see ParagonIE_Sodium_Compat::crypto_generichash_update() * @param string|null $state * @param string $message * @return void * @throws SodiumException * @throws TypeError */ function sodium_crypto_generichash_update( #[\SensitiveParameter] &$state, $message = '' ) { ParagonIE_Sodium_Compat::crypto_generichash_update($state, $message); } } if (!is_callable('sodium_crypto_kdf_keygen')) { /** * @see ParagonIE_Sodium_Compat::crypto_kdf_keygen() * @return string * @throws Exception */ function sodium_crypto_kdf_keygen() { return ParagonIE_Sodium_Compat::crypto_kdf_keygen(); } } if (!is_callable('sodium_crypto_kdf_derive_from_key')) { /** * @see ParagonIE_Sodium_Compat::crypto_kdf_derive_from_key() * @param int $subkey_length * @param int $subkey_id * @param string $context * @param string $key * @return string * @throws Exception */ function sodium_crypto_kdf_derive_from_key( $subkey_length, $subkey_id, $context, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_kdf_derive_from_key( $subkey_length, $subkey_id, $context, $key ); } } if (!is_callable('sodium_crypto_kx')) { /** * @see ParagonIE_Sodium_Compat::crypto_kx() * @param string $my_secret * @param string $their_public * @param string $client_public * @param string $server_public * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_kx( #[\SensitiveParameter] $my_secret, $their_public, $client_public, $server_public ) { return ParagonIE_Sodium_Compat::crypto_kx( $my_secret, $their_public, $client_public, $server_public ); } } if (!is_callable('sodium_crypto_kx_seed_keypair')) { /** * @param string $seed * @return string * @throws Exception */ function sodium_crypto_kx_seed_keypair( #[\SensitiveParameter] $seed ) { return ParagonIE_Sodium_Compat::crypto_kx_seed_keypair($seed); } } if (!is_callable('sodium_crypto_kx_keypair')) { /** * @return string * @throws Exception */ function sodium_crypto_kx_keypair() { return ParagonIE_Sodium_Compat::crypto_kx_keypair(); } } if (!is_callable('sodium_crypto_kx_client_session_keys')) { /** * @param string $client_key_pair * @param string $server_key * @return array{0: string, 1: string} * @throws SodiumException */ function sodium_crypto_kx_client_session_keys( #[\SensitiveParameter] $client_key_pair, $server_key ) { return ParagonIE_Sodium_Compat::crypto_kx_client_session_keys($client_key_pair, $server_key); } } if (!is_callable('sodium_crypto_kx_server_session_keys')) { /** * @param string $server_key_pair * @param string $client_key * @return array{0: string, 1: string} * @throws SodiumException */ function sodium_crypto_kx_server_session_keys( #[\SensitiveParameter] $server_key_pair, $client_key ) { return ParagonIE_Sodium_Compat::crypto_kx_server_session_keys($server_key_pair, $client_key); } } if (!is_callable('sodium_crypto_kx_secretkey')) { /** * @param string $key_pair * @return string * @throws Exception */ function sodium_crypto_kx_secretkey( #[\SensitiveParameter] $key_pair ) { return ParagonIE_Sodium_Compat::crypto_kx_secretkey($key_pair); } } if (!is_callable('sodium_crypto_kx_publickey')) { /** * @param string $key_pair * @return string * @throws Exception */ function sodium_crypto_kx_publickey( #[\SensitiveParameter] $key_pair ) { return ParagonIE_Sodium_Compat::crypto_kx_publickey($key_pair); } } if (!is_callable('sodium_crypto_pwhash')) { /** * @see ParagonIE_Sodium_Compat::crypto_pwhash() * @param int $length * @param string $passwd * @param string $salt * @param int $opslimit * @param int $memlimit * @param int|null $algo * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_pwhash( $length, #[\SensitiveParameter] $passwd, $salt, $opslimit, $memlimit, $algo = null ) { return ParagonIE_Sodium_Compat::crypto_pwhash($length, $passwd, $salt, $opslimit, $memlimit, $algo); } } if (!is_callable('sodium_crypto_pwhash_str')) { /** * @see ParagonIE_Sodium_Compat::crypto_pwhash_str() * @param string $passwd * @param int $opslimit * @param int $memlimit * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_pwhash_str( #[\SensitiveParameter] $passwd, $opslimit, $memlimit ) { return ParagonIE_Sodium_Compat::crypto_pwhash_str($passwd, $opslimit, $memlimit); } } if (!is_callable('sodium_crypto_pwhash_str_needs_rehash')) { /** * @see ParagonIE_Sodium_Compat::crypto_pwhash_str_needs_rehash() * @param string $hash * @param int $opslimit * @param int $memlimit * @return bool * * @throws SodiumException */ function sodium_crypto_pwhash_str_needs_rehash( #[\SensitiveParameter] $hash, $opslimit, $memlimit ) { return ParagonIE_Sodium_Compat::crypto_pwhash_str_needs_rehash($hash, $opslimit, $memlimit); } } if (!is_callable('sodium_crypto_pwhash_str_verify')) { /** * @see ParagonIE_Sodium_Compat::crypto_pwhash_str_verify() * @param string $passwd * @param string $hash * @return bool * @throws SodiumException * @throws TypeError */ function sodium_crypto_pwhash_str_verify( #[\SensitiveParameter] $passwd, #[\SensitiveParameter] $hash ) { return ParagonIE_Sodium_Compat::crypto_pwhash_str_verify($passwd, $hash); } } if (!is_callable('sodium_crypto_pwhash_scryptsalsa208sha256')) { /** * @see ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256() * @param int $length * @param string $passwd * @param string $salt * @param int $opslimit * @param int $memlimit * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_pwhash_scryptsalsa208sha256( $length, #[\SensitiveParameter] $passwd, $salt, $opslimit, $memlimit ) { return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256( $length, $passwd, $salt, $opslimit, $memlimit ); } } if (!is_callable('sodium_crypto_pwhash_scryptsalsa208sha256_str')) { /** * @see ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str() * @param string $passwd * @param int $opslimit * @param int $memlimit * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_pwhash_scryptsalsa208sha256_str( #[\SensitiveParameter] $passwd, $opslimit, $memlimit ) { return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str($passwd, $opslimit, $memlimit); } } if (!is_callable('sodium_crypto_pwhash_scryptsalsa208sha256_str_verify')) { /** * @see ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str_verify() * @param string $passwd * @param string $hash * @return bool * @throws SodiumException * @throws TypeError */ function sodium_crypto_pwhash_scryptsalsa208sha256_str_verify( #[\SensitiveParameter] $passwd, #[\SensitiveParameter] $hash ) { return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str_verify($passwd, $hash); } } if (!is_callable('sodium_crypto_scalarmult')) { /** * @see ParagonIE_Sodium_Compat::crypto_scalarmult() * @param string $n * @param string $p * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_scalarmult( #[\SensitiveParameter] $n, $p ) { return ParagonIE_Sodium_Compat::crypto_scalarmult($n, $p); } } if (!is_callable('sodium_crypto_scalarmult_base')) { /** * @see ParagonIE_Sodium_Compat::crypto_scalarmult_base() * @param string $n * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_scalarmult_base( #[\SensitiveParameter] $n ) { return ParagonIE_Sodium_Compat::crypto_scalarmult_base($n); } } if (!is_callable('sodium_crypto_secretbox')) { /** * @see ParagonIE_Sodium_Compat::crypto_secretbox() * @param string $message * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_secretbox( #[\SensitiveParameter] $message, $nonce, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_secretbox($message, $nonce, $key); } } if (!is_callable('sodium_crypto_secretbox_keygen')) { /** * @see ParagonIE_Sodium_Compat::crypto_secretbox_keygen() * @return string * @throws Exception */ function sodium_crypto_secretbox_keygen() { return ParagonIE_Sodium_Compat::crypto_secretbox_keygen(); } } if (!is_callable('sodium_crypto_secretbox_open')) { /** * @see ParagonIE_Sodium_Compat::crypto_secretbox_open() * @param string $ciphertext * @param string $nonce * @param string $key * @return string|bool */ function sodium_crypto_secretbox_open( $ciphertext, $nonce, #[\SensitiveParameter] $key ) { try { return ParagonIE_Sodium_Compat::crypto_secretbox_open($ciphertext, $nonce, $key); } catch (Error $ex) { return false; } catch (Exception $ex) { return false; } } } if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_init_push')) { /** * @param string $key * @return array * @throws SodiumException */ function sodium_crypto_secretstream_xchacha20poly1305_init_push( #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_init_push($key); } } if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_push')) { /** * @param string $state * @param string $message * @param string $additional_data * @param int $tag * @return string * @throws SodiumException */ function sodium_crypto_secretstream_xchacha20poly1305_push( #[\SensitiveParameter] &$state, #[\SensitiveParameter] $message, $additional_data = '', $tag = 0 ) { return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_push( $state, $message, $additional_data, $tag ); } } if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_init_pull')) { /** * @param string $header * @param string $key * @return string * @throws Exception */ function sodium_crypto_secretstream_xchacha20poly1305_init_pull( $header, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_init_pull($header, $key); } } if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_pull')) { /** * @param string $state * @param string $ciphertext * @param string $additional_data * @return bool|array{0: string, 1: int} * @throws SodiumException */ function sodium_crypto_secretstream_xchacha20poly1305_pull( #[\SensitiveParameter] &$state, $ciphertext, $additional_data = '' ) { return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_pull( $state, $ciphertext, $additional_data ); } } if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_rekey')) { /** * @param string $state * @return void * @throws SodiumException */ function sodium_crypto_secretstream_xchacha20poly1305_rekey( #[\SensitiveParameter] &$state ) { ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_rekey($state); } } if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_keygen')) { /** * @return string * @throws Exception */ function sodium_crypto_secretstream_xchacha20poly1305_keygen() { return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_keygen(); } } if (!is_callable('sodium_crypto_shorthash')) { /** * @see ParagonIE_Sodium_Compat::crypto_shorthash() * @param string $message * @param string $key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_shorthash( $message, #[\SensitiveParameter] $key = '' ) { return ParagonIE_Sodium_Compat::crypto_shorthash($message, $key); } } if (!is_callable('sodium_crypto_shorthash_keygen')) { /** * @see ParagonIE_Sodium_Compat::crypto_shorthash_keygen() * @return string * @throws Exception */ function sodium_crypto_shorthash_keygen() { return ParagonIE_Sodium_Compat::crypto_shorthash_keygen(); } } if (!is_callable('sodium_crypto_sign')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign() * @param string $message * @param string $secret_key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign( $message, #[\SensitiveParameter] $secret_key ) { return ParagonIE_Sodium_Compat::crypto_sign($message, $secret_key); } } if (!is_callable('sodium_crypto_sign_detached')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_detached() * @param string $message * @param string $secret_key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign_detached( $message, #[\SensitiveParameter] $secret_key ) { return ParagonIE_Sodium_Compat::crypto_sign_detached($message, $secret_key); } } if (!is_callable('sodium_crypto_sign_keypair_from_secretkey_and_publickey')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_keypair_from_secretkey_and_publickey() * @param string $secret_key * @param string $public_key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign_keypair_from_secretkey_and_publickey( #[\SensitiveParameter] $secret_key, $public_key ) { return ParagonIE_Sodium_Compat::crypto_sign_keypair_from_secretkey_and_publickey($secret_key, $public_key); } } if (!is_callable('sodium_crypto_sign_keypair')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_keypair() * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign_keypair() { return ParagonIE_Sodium_Compat::crypto_sign_keypair(); } } if (!is_callable('sodium_crypto_sign_open')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_open() * @param string $signedMessage * @param string $public_key * @return string|bool */ function sodium_crypto_sign_open($signedMessage, $public_key) { try { return ParagonIE_Sodium_Compat::crypto_sign_open($signedMessage, $public_key); } catch (Error $ex) { return false; } catch (Exception $ex) { return false; } } } if (!is_callable('sodium_crypto_sign_publickey')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_publickey() * @param string $key_pair * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign_publickey( #[\SensitiveParameter] $key_pair ) { return ParagonIE_Sodium_Compat::crypto_sign_publickey($key_pair); } } if (!is_callable('sodium_crypto_sign_publickey_from_secretkey')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_publickey_from_secretkey() * @param string $secret_key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign_publickey_from_secretkey( #[\SensitiveParameter] $secret_key ) { return ParagonIE_Sodium_Compat::crypto_sign_publickey_from_secretkey($secret_key); } } if (!is_callable('sodium_crypto_sign_secretkey')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_secretkey() * @param string $key_pair * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign_secretkey( #[\SensitiveParameter] $key_pair ) { return ParagonIE_Sodium_Compat::crypto_sign_secretkey($key_pair); } } if (!is_callable('sodium_crypto_sign_seed_keypair')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_seed_keypair() * @param string $seed * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign_seed_keypair( #[\SensitiveParameter] $seed ) { return ParagonIE_Sodium_Compat::crypto_sign_seed_keypair($seed); } } if (!is_callable('sodium_crypto_sign_verify_detached')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_verify_detached() * @param string $signature * @param string $message * @param string $public_key * @return bool * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign_verify_detached($signature, $message, $public_key) { return ParagonIE_Sodium_Compat::crypto_sign_verify_detached($signature, $message, $public_key); } } if (!is_callable('sodium_crypto_sign_ed25519_pk_to_curve25519')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_ed25519_pk_to_curve25519() * @param string $public_key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign_ed25519_pk_to_curve25519($public_key) { return ParagonIE_Sodium_Compat::crypto_sign_ed25519_pk_to_curve25519($public_key); } } if (!is_callable('sodium_crypto_sign_ed25519_sk_to_curve25519')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_ed25519_sk_to_curve25519() * @param string $secret_key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_sign_ed25519_sk_to_curve25519( #[\SensitiveParameter] $secret_key ) { return ParagonIE_Sodium_Compat::crypto_sign_ed25519_sk_to_curve25519($secret_key); } } if (!is_callable('sodium_crypto_stream')) { /** * @see ParagonIE_Sodium_Compat::crypto_stream() * @param int $length * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_stream( $length, $nonce, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_stream($length, $nonce, $key); } } if (!is_callable('sodium_crypto_stream_keygen')) { /** * @see ParagonIE_Sodium_Compat::crypto_stream_keygen() * @return string * @throws Exception */ function sodium_crypto_stream_keygen() { return ParagonIE_Sodium_Compat::crypto_stream_keygen(); } } if (!is_callable('sodium_crypto_stream_xor')) { /** * @see ParagonIE_Sodium_Compat::crypto_stream_xor() * @param string $message * @param string $nonce * @param string $key * @return string * @throws SodiumException * @throws TypeError */ function sodium_crypto_stream_xor( #[\SensitiveParameter] $message, $nonce, #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_stream_xor($message, $nonce, $key); } } require_once dirname(__FILE__) . '/stream-xchacha20.php'; if (!is_callable('sodium_hex2bin')) { /** * @see ParagonIE_Sodium_Compat::hex2bin() * @param string $string * @param string $ignore * @return string * @throws SodiumException * @throws TypeError */ function sodium_hex2bin( #[\SensitiveParameter] $string, $ignore = '' ) { return ParagonIE_Sodium_Compat::hex2bin($string, $ignore); } } if (!is_callable('sodium_increment')) { /** * @see ParagonIE_Sodium_Compat::increment() * @param string $string * @return void * @throws SodiumException * @throws TypeError */ function sodium_increment( #[\SensitiveParameter] &$string ) { ParagonIE_Sodium_Compat::increment($string); } } if (!is_callable('sodium_library_version_major')) { /** * @see ParagonIE_Sodium_Compat::library_version_major() * @return int */ function sodium_library_version_major() { return ParagonIE_Sodium_Compat::library_version_major(); } } if (!is_callable('sodium_library_version_minor')) { /** * @see ParagonIE_Sodium_Compat::library_version_minor() * @return int */ function sodium_library_version_minor() { return ParagonIE_Sodium_Compat::library_version_minor(); } } if (!is_callable('sodium_version_string')) { /** * @see ParagonIE_Sodium_Compat::version_string() * @return string */ function sodium_version_string() { return ParagonIE_Sodium_Compat::version_string(); } } if (!is_callable('sodium_memcmp')) { /** * @see ParagonIE_Sodium_Compat::memcmp() * @param string $string1 * @param string $string2 * @return int * @throws SodiumException * @throws TypeError */ function sodium_memcmp( #[\SensitiveParameter] $string1, #[\SensitiveParameter] $string2 ) { return ParagonIE_Sodium_Compat::memcmp($string1, $string2); } } if (!is_callable('sodium_memzero')) { /** * @see ParagonIE_Sodium_Compat::memzero() * @param string $string * @return void * @throws SodiumException * @throws TypeError * * @psalm-suppress ReferenceConstraintViolation */ function sodium_memzero( #[\SensitiveParameter] &$string ) { ParagonIE_Sodium_Compat::memzero($string); } } if (!is_callable('sodium_pad')) { /** * @see ParagonIE_Sodium_Compat::pad() * @param string $unpadded * @param int $block_size * @return string * @throws SodiumException * @throws TypeError */ function sodium_pad( #[\SensitiveParameter] $unpadded, $block_size ) { return ParagonIE_Sodium_Compat::pad($unpadded, $block_size, true); } } if (!is_callable('sodium_unpad')) { /** * @see ParagonIE_Sodium_Compat::pad() * @param string $padded * @param int $block_size * @return string * @throws SodiumException * @throws TypeError */ function sodium_unpad( #[\SensitiveParameter] $padded, $block_size ) { return ParagonIE_Sodium_Compat::unpad($padded, $block_size, true); } } if (!is_callable('sodium_randombytes_buf')) { /** * @see ParagonIE_Sodium_Compat::randombytes_buf() * @param int $amount * @return string * @throws Exception */ function sodium_randombytes_buf($amount) { return ParagonIE_Sodium_Compat::randombytes_buf($amount); } } if (!is_callable('sodium_randombytes_uniform')) { /** * @see ParagonIE_Sodium_Compat::randombytes_uniform() * @param int $upperLimit * @return int * @throws Exception */ function sodium_randombytes_uniform($upperLimit) { return ParagonIE_Sodium_Compat::randombytes_uniform($upperLimit); } } if (!is_callable('sodium_randombytes_random16')) { /** * @see ParagonIE_Sodium_Compat::randombytes_random16() * @return int * @throws Exception */ function sodium_randombytes_random16() { return ParagonIE_Sodium_Compat::randombytes_random16(); } } namespaced.php000064400000002501147206606520007363 0ustar00 10') * * @param string $query The raw SQL query. * @param array $parameters Optional bound parameters. * * @return bool Success. */ public static function raw_execute( $query, $parameters = [] ) { return self::execute( $query, $parameters ); } /** * Internal helper method for executing statements. * * @param string $query The query. * @param array $parameters An array of parameters to be bound in to the query. * * @return bool|int Response of wpdb::query */ protected static function execute( $query, $parameters = [] ) { /** * The global WordPress database variable. * * @var wpdb */ global $wpdb; $show_errors = $wpdb->show_errors; if ( \YoastSEO()->classes->get( Migration_Status::class )->get_error( 'free' ) ) { $wpdb->show_errors = false; } $parameters = \array_filter( $parameters, static function ( $parameter ) { return $parameter !== null; } ); if ( ! empty( $parameters ) ) { $query = $wpdb->prepare( $query, $parameters ); } $result = $wpdb->query( $query ); $wpdb->show_errors = $show_errors; return $result; } /* * --- INSTANCE METHODS --- */ /** * "Private" constructor; shouldn't be called directly. * Use the ORM::for_table factory method instead. * * @param string $table_name Table name. * @param array $data Data to populate table. */ protected function __construct( $table_name, $data = [] ) { $this->table_name = $table_name; $this->data = $data; } /** * Sets the name of the class which the wrapped methods should return instances of. * * @param string $class_name The classname to set. * * @return void */ public function set_class_name( $class_name ) { $this->class_name = $class_name; } /** * Creates a new, empty instance of the class. Used to add a new row to your database. May optionally be passed an * associative array of data to populate the instance. If so, all fields will be flagged as dirty so all will be * saved to the database when save() is called. * * @param array|null $data Data to populate table. * * @return bool|Model|ORM */ public function create( $data = null ) { $this->is_new = true; if ( ! \is_null( $data ) ) { $this->hydrate( $data )->force_all_dirty(); } return $this->create_model_instance( $this ); } /** * Specifies the ID column to use for this instance or array of instances only. * This overrides the id_column and id_column_overrides settings. * * This is mostly useful for libraries built on top of Idiorm, and will not normally be used in manually built * queries. If you don't know why you would want to use this, you should probably just ignore it. * * @param string $id_column The ID column. * * @return ORM */ public function use_id_column( $id_column ) { $this->instance_id_column = $id_column; return $this; } /** * Creates an ORM instance from the given row (an associative array of data fetched from the database). * * @param array $row A row from the database. * * @return bool|Model */ protected function create_instance_from_row( $row ) { $instance = self::for_table( $this->table_name ); $instance->use_id_column( $this->instance_id_column ); $instance->hydrate( $row ); return $this->create_model_instance( $instance ); } /** * Tells the ORM that you are expecting a single result back from your query, and execute it. Will return a single * instance of the ORM class, or false if no rows were returned. As a shortcut, you may supply an ID as a parameter * to this method. This will perform a primary key lookup on the table. * * @param int|null $id An (optional) ID. * * @return bool|Model */ public function find_one( $id = null ) { if ( ! \is_null( $id ) ) { $this->where_id_is( $id ); } $this->limit( 1 ); $rows = $this->run(); if ( empty( $rows ) ) { return false; } return $this->create_instance_from_row( $rows[0] ); } /** * Tells the ORM that you are expecting multiple results from your query, and execute it. Will return an array of * instances of the ORM class, or an empty array if no rows were returned. * * @return array */ public function find_many() { $rows = $this->run(); if ( $rows === false ) { return []; } return \array_map( [ $this, 'create_instance_from_row' ], $rows ); } /** * Creates an instance of the model class associated with this wrapper and populate it with the supplied Idiorm * instance. * * @param ORM $orm The ORM used by model. * * @return bool|Model Instance of the model class. */ protected function create_model_instance( $orm ) { if ( $orm === false ) { return false; } /** * An instance of Model is being made. * * @var Model $model */ $model = new $this->class_name(); $model->set_orm( $orm ); return $model; } /** * Tells the ORM that you are expecting multiple results from your query, and execute it. Will return an array, or * an empty array if no rows were returned. * * @return array The query results. */ public function find_array() { return $this->run(); } /** * Tells the ORM that you wish to execute a COUNT query. * * @param string $column The table column. * * @return float|int An integer representing the number of rows returned. */ public function count( $column = '*' ) { return $this->call_aggregate_db_function( __FUNCTION__, $column ); } /** * Tells the ORM that you wish to execute a MAX query. * * @param string $column The table column. * * @return float|int The max value of the chosen column. */ public function max( $column ) { return $this->call_aggregate_db_function( __FUNCTION__, $column ); } /** * Tells the ORM that you wish to execute a MIN query. * * @param string $column The table column. * * @return float|int The min value of the chosen column. */ public function min( $column ) { return $this->call_aggregate_db_function( __FUNCTION__, $column ); } /** * Tells the ORM that you wish to execute a AVG query. * * @param string $column The table column. * * @return float|int The average value of the chosen column. */ public function avg( $column ) { return $this->call_aggregate_db_function( __FUNCTION__, $column ); } /** * Tells the ORM that you wish to execute a SUM query. * * @param string $column The table column. * * @return float|int The sum of the chosen column. */ public function sum( $column ) { return $this->call_aggregate_db_function( __FUNCTION__, $column ); } /** * Returns the select query as SQL. * * @return string The select query in SQL. */ public function get_sql() { return $this->build_select(); } /** * Returns the update query as SQL. * * @return string The update query in SQL. */ public function get_update_sql() { return $this->build_update(); } /** * Executes an aggregate query on the current connection. * * @param string $sql_function The aggregate function to call eg. MIN, COUNT, etc. * @param string $column The column to execute the aggregate query against. * * @return int */ protected function call_aggregate_db_function( $sql_function, $column ) { $alias = \strtolower( $sql_function ); $sql_function = \strtoupper( $sql_function ); if ( $column !== '*' ) { $column = $this->quote_identifier( $column ); } $result_columns = $this->result_columns; $this->result_columns = []; $this->select_expr( "{$sql_function}({$column})", $alias ); $result = $this->find_one(); $this->result_columns = $result_columns; $return_value = 0; if ( $result !== false && isset( $result->{$alias} ) ) { if ( ! \is_numeric( $result->{$alias} ) ) { $return_value = $result->{$alias}; } // phpcs:ignore Universal.Operators.StrictComparisons -- Reason: This loose comparison seems intentional. elseif ( (int) $result->{$alias} == (float) $result->{$alias} ) { $return_value = (int) $result->{$alias}; } else { $return_value = (float) $result->{$alias}; } } return $return_value; } /** * Hydrates (populate) this instance of the class from an associative array of data. This will usually be called * only from inside the class, but it's public in case you need to call it directly. * * @param array $data Data to populate table. * * @return ORM */ public function hydrate( $data = [] ) { $this->data = $data; return $this; } /** * Forces the ORM to flag all the fields in the $data array as "dirty" and therefore update them when save() is * called. * * @return ORM */ public function force_all_dirty() { $this->dirty_fields = $this->data; return $this; } /** * Performs a raw query. The query can contain placeholders in either named or question mark style. If placeholders * are used, the parameters should be an array of values which will be bound to the placeholders in the query. * If this method is called, all other query building methods will be ignored. * * @param array $query The query. * @param array $parameters The parameters. Defaults to an empty array. * * @return ORM */ public function raw_query( $query, $parameters = [] ) { $this->is_raw_query = true; $this->raw_query = $query; $this->raw_parameters = $parameters; return $this; } /** * Adds an alias for the main table to be used in SELECT queries. * * @param string $alias The alias. * * @return ORM */ public function table_alias( $alias ) { $this->table_alias = $alias; return $this; } /** * Adds an unquoted expression to the set of columns returned by the SELECT query. Internal method. * * @param string $expr The expression. * @param string|null $alias The alias to return the expression as. Defaults to null. * * @return ORM */ protected function add_result_column( $expr, $alias = null ) { if ( ! \is_null( $alias ) ) { $expr .= ' AS ' . $this->quote_identifier( $alias ); } if ( $this->using_default_result_columns ) { $this->result_columns = [ $expr ]; $this->using_default_result_columns = false; } else { $this->result_columns[] = $expr; } return $this; } /** * Counts the number of columns that belong to the primary key and their value is null. * * @return int The amount of null columns. * * @throws Exception Primary key ID contains null value(s). * @throws Exception Primary key ID missing from row or is null. */ public function count_null_id_columns() { if ( \is_array( $this->get_id_column_name() ) ) { return \count( \array_filter( $this->id(), 'is_null' ) ); } else { return \is_null( $this->id() ) ? 1 : 0; } } /** * Adds a column to the list of columns returned by the SELECT query. * * @param string $column The column. Defaults to '*'. * @param string|null $alias The alias to return the column as. Defaults to null. * * @return ORM */ public function select( $column, $alias = null ) { $column = $this->quote_identifier( $column ); return $this->add_result_column( $column, $alias ); } /** * Adds an unquoted expression to the list of columns returned by the SELECT query. * * @param string $expr The expression. * @param string|null $alias The alias to return the column as. Defaults to null. * * @return ORM */ public function select_expr( $expr, $alias = null ) { return $this->add_result_column( $expr, $alias ); } /** * Adds columns to the list of columns returned by the SELECT query. * * This defaults to '*'. * Many columns can be supplied as either an array or as a list of parameters to the method. * Note that the alias must not be numeric - if you want a numeric alias then prepend it with some alpha chars. eg. * a1. * * @example select_many(array('column', 'column2', 'column3'), 'column4', 'column5'); * @example select_many(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5'); * @example select_many('column', 'column2', 'column3'); * * @return ORM */ public function select_many() { $columns = \func_get_args(); if ( ! empty( $columns ) ) { $columns = $this->normalise_select_many_columns( $columns ); foreach ( $columns as $alias => $column ) { if ( \is_numeric( $alias ) ) { $alias = null; } $this->select( $column, $alias ); } } return $this; } /** * Adds an unquoted expression to the list of columns returned by the SELECT query. * * Many columns can be supplied as either an array or as a list of parameters to the method. * Note that the alias must not be numeric - if you want a numeric alias then prepend it with some alpha chars. eg. * a1 * * @example select_many_expr(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5') * @example select_many_expr('column', 'column2', 'column3') * @example select_many_expr(array('column', 'column2', 'column3'), 'column4', 'column5') * * @return ORM */ public function select_many_expr() { $columns = \func_get_args(); if ( ! empty( $columns ) ) { $columns = $this->normalise_select_many_columns( $columns ); foreach ( $columns as $alias => $column ) { if ( \is_numeric( $alias ) ) { $alias = null; } $this->select_expr( $column, $alias ); } } return $this; } /** * Takes a column specification for the select many methods and convert it into a normalised array of columns and * aliases. * * It is designed to turn the following styles into a normalised array: * array(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5')) * * @param array $columns The columns. * * @return array */ protected function normalise_select_many_columns( $columns ) { $return = []; foreach ( $columns as $column ) { if ( \is_array( $column ) ) { foreach ( $column as $key => $value ) { if ( ! \is_numeric( $key ) ) { $return[ $key ] = $value; } else { $return[] = $value; } } } else { $return[] = $column; } } return $return; } /** * Adds a DISTINCT keyword before the list of columns in the SELECT query. * * @return ORM */ public function distinct() { $this->distinct = true; return $this; } /** * Add a JOIN source to the query. Internal method. * * The join_operator should be one of INNER, LEFT OUTER, CROSS etc - this * will be prepended to JOIN. * * The table should be the name of the table to join to. * * The constraint may be either a string or an array with three elements. If it * is a string, it will be compiled into the query as-is, with no escaping. The * recommended way to supply the constraint is as an array with three elements: * * first_column, operator, second_column * * Example: array('user.id', '=', 'profile.user_id') * * will compile to * * ON `user`.`id` = `profile`.`user_id` * * The final (optional) argument specifies an alias for the joined table. * * @param string $join_operator The join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be * prepended to JOIN. * @param string $table The table should be the name of the table to join to. * @param string $constraint The constraint. * @param string|null $table_alias The alias for the joined table. Defaults to null. * * @return ORM */ protected function add_join_source( $join_operator, $table, $constraint, $table_alias = null ) { $join_operator = \trim( "{$join_operator} JOIN" ); $table = $this->quote_identifier( $table ); // Add table alias if present. if ( ! \is_null( $table_alias ) ) { $table_alias = $this->quote_identifier( $table_alias ); $table .= " {$table_alias}"; } // Build the constraint. if ( \is_array( $constraint ) ) { list( $first_column, $operator, $second_column ) = $constraint; $first_column = $this->quote_identifier( $first_column ); $second_column = $this->quote_identifier( $second_column ); $constraint = "{$first_column} {$operator} {$second_column}"; } $this->join_sources[] = "{$join_operator} {$table} ON {$constraint}"; return $this; } /** * Adds a RAW JOIN source to the query. * * @param string $table The table name. * @param string $constraint The constraint. * @param string $table_alias The table alias. * @param array $parameters The parameters. Defaults to an empty array. * * @return ORM */ public function raw_join( $table, $constraint, $table_alias, $parameters = [] ) { // Add table alias if present. if ( ! \is_null( $table_alias ) ) { $table_alias = $this->quote_identifier( $table_alias ); $table .= " {$table_alias}"; } $this->values = \array_merge( $this->values, $parameters ); // Build the constraint. if ( \is_array( $constraint ) ) { list( $first_column, $operator, $second_column ) = $constraint; $first_column = $this->quote_identifier( $first_column ); $second_column = $this->quote_identifier( $second_column ); $constraint = "{$first_column} {$operator} {$second_column}"; } $this->join_sources[] = "{$table} ON {$constraint}"; return $this; } /** * Adds a simple JOIN source to the query. * * @param string $table The table name. * @param string $constraint The constraint. * @param string|null $table_alias The table alias. Defaults to null. * * @return ORM */ public function join( $table, $constraint, $table_alias = null ) { return $this->add_join_source( '', $table, $constraint, $table_alias ); } /** * Adds an INNER JOIN source to the query. * * @param string $table The table name. * @param string $constraint The constraint. * @param string|null $table_alias The table alias. Defaults to null. * * @return ORM */ public function inner_join( $table, $constraint, $table_alias = null ) { return $this->add_join_source( 'INNER', $table, $constraint, $table_alias ); } /** * Adds a LEFT OUTER JOIN source to the query. * * @param string $table The table name. * @param string $constraint The constraint. * @param string|null $table_alias The table alias. Defaults to null. * * @return ORM */ public function left_outer_join( $table, $constraint, $table_alias = null ) { return $this->add_join_source( 'LEFT OUTER', $table, $constraint, $table_alias ); } /** * Adds a RIGHT OUTER JOIN source to the query. * * @param string $table The table name. * @param string $constraint The constraint. * @param string|null $table_alias The table alias. Defaults to null. * * @return ORM */ public function right_outer_join( $table, $constraint, $table_alias = null ) { return $this->add_join_source( 'RIGHT OUTER', $table, $constraint, $table_alias ); } /** * Adds a FULL OUTER JOIN source to the query. * * @param string $table The table name. * @param string $constraint The constraint. * @param string|null $table_alias The table alias. Defaults to null. * * @return ORM */ public function full_outer_join( $table, $constraint, $table_alias = null ) { return $this->add_join_source( 'FULL OUTER', $table, $constraint, $table_alias ); } /** * Adds a HAVING condition to the query. Internal method. * * @param string $fragment The fragment. * @param array $values The values. Defaults to an empty array. * * @return ORM */ protected function add_having( $fragment, $values = [] ) { return $this->add_condition( 'having', $fragment, $values ); } /** * Adds a HAVING condition to the query. Internal method. * * @param string $column_name The table column. * @param string $separator The separator. * @param mixed $value The value. * * @return ORM */ protected function add_simple_having( $column_name, $separator, $value ) { return $this->add_simple_condition( 'having', $column_name, $separator, $value ); } /** * Adds a HAVING clause with multiple values (like IN and NOT IN). Internal method. * * @param string|array $column_name The table column. * @param string $separator The separator. * @param array $values The values. * * @return ORM */ public function add_having_placeholder( $column_name, $separator, $values ) { if ( ! \is_array( $column_name ) ) { $data = [ $column_name => $values ]; } else { $data = $column_name; } $result = $this; foreach ( $data as $key => $val ) { $column = $result->quote_identifier( $key ); $placeholders = $result->create_placeholders( $val ); $result = $result->add_having( "{$column} {$separator} ({$placeholders})", $val ); } return $result; } /** * Adds a HAVING clause with no parameters(like IS NULL and IS NOT NULL). Internal method. * * @param string $column_name The column name. * @param string $operator The operator. * * @return ORM */ public function add_having_no_value( $column_name, $operator ) { $conditions = \is_array( $column_name ) ? $column_name : [ $column_name ]; $result = $this; foreach ( $conditions as $column ) { $column = $this->quote_identifier( $column ); $result = $result->add_having( "{$column} {$operator}" ); } return $result; } /** * Adds a WHERE condition to the query. Internal method. * * @param string $fragment The fragment. * @param array $values The values. Defaults to an empty array. * * @return ORM */ protected function add_where( $fragment, $values = [] ) { return $this->add_condition( 'where', $fragment, $values ); } /** * Adds a WHERE condition to the query. Internal method. * * @param string|array $column_name The table column. * @param string $separator The separator. * @param mixed $value The value. * * @return ORM */ protected function add_simple_where( $column_name, $separator, $value ) { return $this->add_simple_condition( 'where', $column_name, $separator, $value ); } /** * Adds a WHERE clause with multiple values (like IN and NOT IN). * * @param string|array $column_name The table column. * @param string $separator The separator. * @param array $values The values. * * @return ORM */ public function add_where_placeholder( $column_name, $separator, $values ) { if ( ! \is_array( $column_name ) ) { $data = [ $column_name => $values ]; } else { $data = $column_name; } $result = $this; foreach ( $data as $key => $val ) { $column = $result->quote_identifier( $key ); $placeholders = $result->create_placeholders( $val ); $result = $result->add_where( "{$column} {$separator} ({$placeholders})", $val ); } return $result; } /** * Adds a WHERE clause with no parameters(like IS NULL and IS NOT NULL). * * @param string $column_name The column name. * @param string $operator The operator. * * @return ORM */ public function add_where_no_value( $column_name, $operator ) { $conditions = \is_array( $column_name ) ? $column_name : [ $column_name ]; $result = $this; foreach ( $conditions as $column ) { $column = $this->quote_identifier( $column ); $result = $result->add_where( "{$column} {$operator}" ); } return $result; } /** * Adds a HAVING or WHERE condition to the query. Internal method. * * @param string $type The type. * @param string $fragment The fragment. * @param array $values The values. Defaults to empty array. * * @return ORM */ protected function add_condition( $type, $fragment, $values = [] ) { $conditions_class_property_name = "{$type}_conditions"; if ( ! \is_array( $values ) ) { $values = [ $values ]; } \array_push( $this->{$conditions_class_property_name}, [ self::CONDITION_FRAGMENT => $fragment, self::CONDITION_VALUES => $values, ] ); return $this; } /** * Compiles a simple COLUMN SEPARATOR VALUE style HAVING or WHERE condition into a string and value ready to be * passed to the add_condition method. * * Avoids duplication of the call to quote_identifier. * If column_name is an associative array, it will add a condition for each column. * * @param string $type The type. * @param string|array $column_name The table column. * @param string $separator The separator. * @param mixed $value The value. * * @return ORM */ protected function add_simple_condition( $type, $column_name, $separator, $value ) { $multiple = \is_array( $column_name ) ? $column_name : [ $column_name => $value ]; $result = $this; foreach ( $multiple as $key => $val ) { // Add the table name in case of ambiguous columns. if ( \count( $result->join_sources ) > 0 && \strpos( $key, '.' ) === false ) { $table = $result->table_name; if ( ! \is_null( $result->table_alias ) ) { $table = $result->table_alias; } $key = "{$table}.{$key}"; } $key = $result->quote_identifier( $key ); $placeholder = ( $val === null ) ? 'NULL' : '%s'; $result = $result->add_condition( $type, "{$key} {$separator} {$placeholder}", $val ); } return $result; } /** * Returns a string containing the given number of question marks, separated by commas. Eg "?, ?, ?". * * @param array $fields Fields to create placeholder for. * * @return string */ protected function create_placeholders( $fields ) { if ( ! empty( $fields ) ) { $db_fields = []; foreach ( $fields as $key => $value ) { // Process expression fields directly into the query. if ( \array_key_exists( $key, $this->expr_fields ) ) { $db_fields[] = $value; } else { $db_fields[] = ( $value === null ) ? 'NULL' : '%s'; } } return \implode( ', ', $db_fields ); } return ''; } /** * Filters a column/value array returning only those columns that belong to a compound primary key. * * If the key contains a column that does not exist in the given array, a null value will be returned for it. * * @param mixed $value The value. * * @return array */ protected function get_compound_id_column_values( $value ) { $filtered = []; foreach ( $this->get_id_column_name() as $key ) { $filtered[ $key ] = ( $value[ $key ] ?? null ); } return $filtered; } /** * Filters an array containing compound column/value arrays. * * @param array $values The values. * * @return array */ protected function get_compound_id_column_values_array( $values ) { $filtered = []; foreach ( $values as $value ) { $filtered[] = $this->get_compound_id_column_values( $value ); } return $filtered; } /** * Add a WHERE column = value clause to your query. Each time this is called in the chain, an additional WHERE will * be added, and these will be ANDed together when the final query is built. * * If you use an array in $column_name, a new clause will be added for each element. In this case, $value is * ignored. * * @param string|array $column_name The table column. * @param mixed|null $value The value. Defaults to null. * * @return ORM */ public function where( $column_name, $value = null ) { return $this->where_equal( $column_name, $value ); } /** * More explicitly named version of for the where() method. Can be used if preferred. * * @param string|array $column_name The table column. * @param mixed|null $value The value. Defaults to null. * * @return ORM */ public function where_equal( $column_name, $value = null ) { return $this->add_simple_where( $column_name, '=', $value ); } /** * Add a WHERE column != value clause to your query. * * @param string|array $column_name The table column. * @param mixed|null $value The value. Defaults to null. * * @return ORM */ public function where_not_equal( $column_name, $value = null ) { return $this->add_simple_where( $column_name, '!=', $value ); } /** * Queries the table by its primary key. Special method. * * If primary key is compound, only the columns that belong to they key will be used for the query. * * @param string $id The ID. * * @return ORM */ public function where_id_is( $id ) { return \is_array( $this->get_id_column_name() ) ? $this->where( $this->get_compound_id_column_values( $id ), null ) : $this->where( $this->get_id_column_name(), $id ); } /** * Allows adding a WHERE clause that matches any of the conditions specified in the array. Each element in the * associative array will be a different condition, where the key will be the column name. * * By default, an equal operator will be used against all columns, but it can be overriden for any or every column * using the second parameter. * * Each condition will be ORed together when added to the final query. * * @param array $values The values. * @param string $operator The operator. * * @return ORM */ public function where_any_is( $values, $operator = '=' ) { $data = []; $query = [ '((' ]; $first = true; foreach ( $values as $value ) { if ( $first ) { $first = false; } else { $query[] = ') OR ('; } $firstsub = true; foreach ( $value as $key => $item ) { $op = \is_string( $operator ) ? $operator : ( $operator[ $key ] ?? '=' ); if ( $op === '=' && $item === null ) { $op = 'IS'; } if ( $firstsub ) { $firstsub = false; } else { $query[] = 'AND'; } $query[] = $this->quote_identifier( $key ); $data[] = $item; $query[] = $op; $query[] = ( ( $item === null ) ? 'NULL' : '%s' ); } } $query[] = '))'; return $this->where_raw( \implode( ' ', $query ), $data ); } /** * Queries the table by its primary key. * * Similar to where_id_is() but allowing multiple primary keys. * If primary key is compound, only the columns that belong to they key will be used for the query. * * @param string[] $ids The IDs. * * @return ORM */ public function where_id_in( $ids ) { return \is_array( $this->get_id_column_name() ) ? $this->where_any_is( $this->get_compound_id_column_values_array( $ids ) ) : $this->where_in( $this->get_id_column_name(), $ids ); } /** * Adds a WHERE ... LIKE clause to your query. * * @param string|array $column_name The table column. * @param mixed|null $value The value. Defaults to null. * * @return ORM */ public function where_like( $column_name, $value = null ) { return $this->add_simple_where( $column_name, 'LIKE', $value ); } /** * Adds where WHERE ... NOT LIKE clause to your query. * * @param string|array $column_name The table column. * @param mixed|null $value The value. Defaults to null. * * @return ORM */ public function where_not_like( $column_name, $value = null ) { return $this->add_simple_where( $column_name, 'NOT LIKE', $value ); } /** * Adds a WHERE ... > clause to your query. * * @param string|array $column_name The table column. * @param mixed|null $value The value. Defaults to null. * * @return ORM */ public function where_gt( $column_name, $value = null ) { return $this->add_simple_where( $column_name, '>', $value ); } /** * Adds a WHERE ... < clause to your query. * * @param string|array $column_name The table column. * @param mixed|null $value The value. Defaults to null. * * @return ORM */ public function where_lt( $column_name, $value = null ) { return $this->add_simple_where( $column_name, '<', $value ); } /** * Adds a WHERE ... >= clause to your query. * * @param string|array $column_name The table column. * @param mixed|null $value The value. Defaults to null. * * @return ORM */ public function where_gte( $column_name, $value = null ) { return $this->add_simple_where( $column_name, '>=', $value ); } /** * Adds a WHERE ... <= clause to your query. * * @param string|array $column_name The table column. * @param mixed|null $value The value. Defaults to null. * * @return ORM */ public function where_lte( $column_name, $value = null ) { return $this->add_simple_where( $column_name, '<=', $value ); } /** * Adds a WHERE ... IN clause to your query. * * @param string|array $column_name The table column. * @param array $values The values. * * @return ORM */ public function where_in( $column_name, $values ) { return $this->add_where_placeholder( $column_name, 'IN', $values ); } /** * Adds a WHERE ... NOT IN clause to your query. * * @param string|array $column_name The table column. * @param array $values The values. * * @return ORM */ public function where_not_in( $column_name, $values ) { return $this->add_where_placeholder( $column_name, 'NOT IN', $values ); } /** * Adds a WHERE column IS NULL clause to your query. * * @param string|array $column_name The table column. * * @return ORM */ public function where_null( $column_name ) { return $this->add_where_no_value( $column_name, 'IS NULL' ); } /** * Adds a WHERE column IS NOT NULL clause to your query. * * @param string|array $column_name The table column. * * @return ORM */ public function where_not_null( $column_name ) { return $this->add_where_no_value( $column_name, 'IS NOT NULL' ); } /** * Adds a raw WHERE clause to the query. The clause should contain question mark placeholders, which will be bound * to the parameters supplied in the second argument. * * @param string $clause The clause that should contain question mark placeholders. * @param array $parameters The parameters to include in the query. * * @return ORM */ public function where_raw( $clause, $parameters = [] ) { return $this->add_where( $clause, $parameters ); } /** * Adds a LIMIT to the query. * * @param int $limit The limit. * * @return ORM */ public function limit( $limit ) { $this->limit = $limit; return $this; } /** * Adds an OFFSET to the query. * * @param int $offset The offset. * * @return ORM */ public function offset( $offset ) { $this->offset = $offset; return $this; } /** * Adds an ORDER BY clause to the query. * * @param string $column_name The column name. * @param string $ordering The ordering. DESC or ASC. * * @return ORM */ protected function add_order_by( $column_name, $ordering ) { $column_name = $this->quote_identifier( $column_name ); $this->order_by[] = "{$column_name} {$ordering}"; return $this; } /** * Adds an ORDER BY column DESC clause. * * @param string|array $column_name The table column. * * @return ORM */ public function order_by_desc( $column_name ) { return $this->add_order_by( $column_name, 'DESC' ); } /** * Adds an ORDER BY column ASC clause. * * @param string|array $column_name The table column. * * @return ORM */ public function order_by_asc( $column_name ) { return $this->add_order_by( $column_name, 'ASC' ); } /** * Adds an unquoted expression as an ORDER BY clause. * * @param string $clause The clause. * * @return ORM */ public function order_by_expr( $clause ) { $this->order_by[] = $clause; return $this; } /** * Adds a column to the list of columns to GROUP BY. * * @param string|array $column_name The table column. * * @return ORM */ public function group_by( $column_name ) { $column_name = $this->quote_identifier( $column_name ); $this->group_by[] = $column_name; return $this; } /** * Adds an unquoted expression to the list of columns to GROUP BY. * * @param string $expr The expression. * * @return ORM */ public function group_by_expr( $expr ) { $this->group_by[] = $expr; return $this; } /** * Adds a HAVING column = value clause to your query. * * Each time this is called in the chain, an additional HAVING will be added, and these will be ANDed together when * the final query is built. * * If you use an array in $column_name, a new clause will be added for each element. In this case, $value is * ignored. * * @param string|array $column_name The table column. * @param mixed|null $value The value. * * @return ORM */ public function having( $column_name, $value = null ) { return $this->having_equal( $column_name, $value ); } /** * Adds a having equal to your query. * * More explicitly named version of for the having() method. Can be used if preferred. * * @param string|array $column_name The table column. * @param mixed|null $value The value. * * @return ORM */ public function having_equal( $column_name, $value = null ) { return $this->add_simple_having( $column_name, '=', $value ); } /** * Adds a HAVING column != value clause to your query. * * @param string|array $column_name The table column. * @param mixed|null $value The value. * * @return ORM */ public function having_not_equal( $column_name, $value = null ) { return $this->add_simple_having( $column_name, '!=', $value ); } /** * Queries the table by its primary key. Special method. * * If primary key is compound, only the columns that belong to they key will be used for the query. * * @param string $id The ID. * * @return ORM */ public function having_id_is( $id ) { return \is_array( $this->get_id_column_name() ) ? $this->having( $this->get_compound_id_column_values( $id ), null ) : $this->having( $this->get_id_column_name(), $id ); } /** * Adds a HAVING ... LIKE clause to your query. * * @param string|array $column_name The table column. * @param string|null $value The value. * * @return ORM */ public function having_like( $column_name, $value = null ) { return $this->add_simple_having( $column_name, 'LIKE', $value ); } /** * Adds where HAVING ... NOT LIKE clause to your query. * * @param string|array $column_name The table column. * @param string|null $value The value. * * @return ORM */ public function having_not_like( $column_name, $value = null ) { return $this->add_simple_having( $column_name, 'NOT LIKE', $value ); } /** * Adds a HAVING ... > clause to your query. * * @param string|array $column_name The table column. * @param mixed $value The value. * * @return ORM */ public function having_gt( $column_name, $value = null ) { return $this->add_simple_having( $column_name, '>', $value ); } /** * Adds a HAVING ... < clause to your query. * * @param string|array $column_name The table column. * @param mixed $value The value. * * @return ORM */ public function having_lt( $column_name, $value = null ) { return $this->add_simple_having( $column_name, '<', $value ); } /** * Adds a HAVING ... >= clause to your query. * * @param string|array $column_name The table column. * @param mixed $value The value. Defaults to null. * * @return ORM */ public function having_gte( $column_name, $value = null ) { return $this->add_simple_having( $column_name, '>=', $value ); } /** * Adds a HAVING ... <= clause to your query. * * @param string|array $column_name The table column. * @param mixed $value The value. * * @return ORM */ public function having_lte( $column_name, $value = null ) { return $this->add_simple_having( $column_name, '<=', $value ); } /** * Adds a HAVING ... IN clause to your query. * * @param string|array $column_name The table column. * @param array|null $values The values. Defaults to null. * * @return ORM */ public function having_in( $column_name, $values = null ) { return $this->add_having_placeholder( $column_name, 'IN', $values ); } /** * Adds a HAVING ... NOT IN clause to your query. * * @param string|array $column_name The table column. * @param array|null $values The values. Defaults to null. * * @return ORM */ public function having_not_in( $column_name, $values = null ) { return $this->add_having_placeholder( $column_name, 'NOT IN', $values ); } /** * Adds a HAVING column IS NULL clause to your query. * * @param string|array $column_name The table column. * * @return ORM */ public function having_null( $column_name ) { return $this->add_having_no_value( $column_name, 'IS NULL' ); } /** * Adds a HAVING column IS NOT NULL clause to your query. * * @param string|array $column_name The table column. * * @return ORM */ public function having_not_null( $column_name ) { return $this->add_having_no_value( $column_name, 'IS NOT NULL' ); } /** * Adds a raw HAVING clause to the query. The clause should contain question mark placeholders, which will be bound * to the parameters supplied in the second argument. * * @param string $clause The clause that should contain question mark placeholders. * @param array $parameters The parameters to include in the query. * * @return ORM */ public function having_raw( $clause, $parameters = [] ) { return $this->add_having( $clause, $parameters ); } /** * Builds a SELECT statement based on the clauses that have been passed to this instance by chaining method calls. * * @return string */ protected function build_select() { // If the query is raw, just set the $this->values to be the raw query parameters and return the raw query. if ( $this->is_raw_query ) { $this->values = $this->raw_parameters; return $this->raw_query; } // Build and return the full SELECT statement by concatenating the results of calling each separate builder method. return $this->join_if_not_empty( ' ', [ $this->build_select_start(), $this->build_join(), $this->build_where(), $this->build_group_by(), $this->build_having(), $this->build_order_by(), $this->build_limit(), $this->build_offset(), ] ); } /** * Builds the start of the SELECT statement. * * @return string */ protected function build_select_start() { $fragment = 'SELECT '; $result_columns = \implode( ', ', $this->result_columns ); if ( $this->distinct ) { $result_columns = 'DISTINCT ' . $result_columns; } $fragment .= "{$result_columns} FROM " . $this->quote_identifier( $this->table_name ); if ( ! \is_null( $this->table_alias ) ) { $fragment .= ' ' . $this->quote_identifier( $this->table_alias ); } return $fragment; } /** * Builds the JOIN sources. * * @return string */ protected function build_join() { if ( \count( $this->join_sources ) === 0 ) { return ''; } return \implode( ' ', $this->join_sources ); } /** * Builds the WHERE clause(s). * * @return string */ protected function build_where() { return $this->build_conditions( 'where' ); } /** * Build the HAVING clause(s) * * @return string */ protected function build_having() { return $this->build_conditions( 'having' ); } /** * Builds GROUP BY. * * @return string */ protected function build_group_by() { if ( \count( $this->group_by ) === 0 ) { return ''; } return 'GROUP BY ' . \implode( ', ', $this->group_by ); } /** * Builds a WHERE or HAVING clause. * * @param string $type Where or having. * * @return string */ protected function build_conditions( $type ) { $conditions_class_property_name = "{$type}_conditions"; // If there are no clauses, return empty string. if ( \count( $this->{$conditions_class_property_name} ) === 0 ) { return ''; } $conditions = []; foreach ( $this->{$conditions_class_property_name} as $condition ) { $conditions[] = $condition[ self::CONDITION_FRAGMENT ]; $this->values = \array_merge( $this->values, $condition[ self::CONDITION_VALUES ] ); } return \strtoupper( $type ) . ' ' . \implode( ' AND ', $conditions ); } /** * Builds ORDER BY. * * @return string */ protected function build_order_by() { if ( \count( $this->order_by ) === 0 ) { return ''; } return 'ORDER BY ' . \implode( ', ', $this->order_by ); } /** * Builds LIMIT. * * @return string */ protected function build_limit() { if ( ! \is_null( $this->limit ) ) { return "LIMIT {$this->limit}"; } return ''; } /** * Builds OFFSET. * * @return string */ protected function build_offset() { if ( ! \is_null( $this->offset ) ) { return 'OFFSET ' . $this->offset; } return ''; } /** * Joins strings if they are not empty. * * @param string $glue Glue. * @param string[] $pieces Pieces to join. * * @return string */ protected function join_if_not_empty( $glue, $pieces ) { $filtered_pieces = []; foreach ( $pieces as $piece ) { if ( \is_string( $piece ) ) { $piece = \trim( $piece ); } if ( ! empty( $piece ) ) { $filtered_pieces[] = $piece; } } return \implode( $glue, $filtered_pieces ); } /** * Quotes a string that is used as an identifier (table names, column names etc). * This method can also deal with dot-separated identifiers eg table.column. * * @param string|string[] $identifier One or more identifiers. * * @return string */ protected function quote_one_identifier( $identifier ) { $parts = \explode( '.', $identifier ); $parts = \array_map( [ $this, 'quote_identifier_part' ], $parts ); return \implode( '.', $parts ); } /** * Quotes a string that is used as an identifier (table names, column names etc) or an array containing multiple * identifiers. This method can also deal with dot-separated identifiers eg table.column. * * @param string|string[] $identifier One or more identifiers. * * @return string */ protected function quote_identifier( $identifier ) { if ( \is_array( $identifier ) ) { $result = \array_map( [ $this, 'quote_one_identifier' ], $identifier ); return \implode( ', ', $result ); } else { return $this->quote_one_identifier( $identifier ); } } /** * Quotes a single part of an identifier, using the identifier quote character specified in the config * (or autodetected). * * @param string $part The part to quote. * * @return string */ protected function quote_identifier_part( $part ) { if ( $part === '*' ) { return $part; } $quote_character = '`'; // Double up any identifier quotes to escape them. return $quote_character . \str_replace( $quote_character, $quote_character . $quote_character, $part ) . $quote_character; } /** * Executes the SELECT query that has been built up by chaining methods on this class. * Return an array of rows as associative arrays. * * @return array|false The result rows. False if the query failed. */ protected function run() { global $wpdb; $query = $this->build_select(); $success = self::execute( $query, $this->values ); if ( $success === false ) { // If the query fails run the migrations and try again. // Action is intentionally undocumented and should not be used by third-parties. \do_action( '_yoast_run_migrations' ); $success = self::execute( $query, $this->values ); } $this->reset_idiorm_state(); if ( $success === false ) { return false; } $rows = []; foreach ( $wpdb->last_result as $row ) { $rows[] = \get_object_vars( $row ); } return $rows; } /** * Resets the Idiorm instance state. * * @return void */ private function reset_idiorm_state() { $this->values = []; $this->result_columns = [ '*' ]; $this->using_default_result_columns = true; } /** * Returns the raw data wrapped by this ORM instance as an associative array. Column names may optionally be * supplied as arguments, if so, only those keys will be returned. * * @return array Associative array of the raw data. */ public function as_array() { if ( \func_num_args() === 0 ) { return $this->data; } $args = \func_get_args(); return \array_intersect_key( $this->data, \array_flip( $args ) ); } /** * Returns the value of a property of this object (database row) or null if not present. * * If a column-names array is passed, it will return a associative array with the value of each column or null if * it is not present. * * @param string|array $key Key. * * @return array|mixed|null */ public function get( $key ) { if ( \is_array( $key ) ) { $result = []; foreach ( $key as $column ) { $result[ $column ] = ( $this->data[ $column ] ?? null ); } return $result; } else { return ( $this->data[ $key ] ?? null ); } } /** * Returns the name of the column in the database table which contains the primary key ID of the row. * * @return string The primary key ID of the row. */ protected function get_id_column_name() { if ( ! \is_null( $this->instance_id_column ) ) { return $this->instance_id_column; } return 'id'; } /** * Gets the primary key ID of this object. * * @param bool $disallow_null Whether to allow null IDs. * * @return array|mixed|null * * @throws Exception Primary key ID contains null value(s). * @throws Exception Primary key ID missing from row or is null. */ public function id( $disallow_null = false ) { $id = $this->get( $this->get_id_column_name() ); if ( $disallow_null ) { if ( \is_array( $id ) ) { foreach ( $id as $id_part ) { if ( $id_part === null ) { throw new Exception( 'Primary key ID contains null value(s)' ); } } } elseif ( $id === null ) { throw new Exception( 'Primary key ID missing from row or is null' ); } } return $id; } /** * Sets a property to a particular value on this object. * * To set multiple properties at once, pass an associative array as the first parameter and leave out the second * parameter. Flags the properties as 'dirty' so they will be saved to the database when save() is called. * * @param string|array $key Key. * @param string|null $value Value. * * @return ORM */ public function set( $key, $value = null ) { return $this->set_orm_property( $key, $value ); } /** * Set a property to a particular value on this object as expression. * * To set multiple properties at once, pass an associative array as the first parameter and leave out the second * parameter. Flags the properties as 'dirty' so they will be saved to the database when save() is called. * * @param string|array $key Key. * @param string|null $value Value. * * @return ORM */ public function set_expr( $key, $value = null ) { return $this->set_orm_property( $key, $value, true ); } /** * Sets a property on the ORM object. * * @param string|array $key Key. * @param string|null $value Value. * @param bool $expr Expression. * * @return ORM */ protected function set_orm_property( $key, $value = null, $expr = false ) { if ( ! \is_array( $key ) ) { $key = [ $key => $value ]; } foreach ( $key as $field => $value ) { $this->data[ $field ] = $value; $this->dirty_fields[ $field ] = $value; if ( $expr === false && isset( $this->expr_fields[ $field ] ) ) { unset( $this->expr_fields[ $field ] ); } elseif ( $expr === true ) { $this->expr_fields[ $field ] = true; } } return $this; } /** * Checks whether the given field has been changed since this object was saved. * * @param mixed $key Key. * * @return bool */ public function is_dirty( $key ) { return \array_key_exists( $key, $this->dirty_fields ); } /** * Checks whether the model was the result of a call to create() or not. * * @return bool */ public function is_new() { return $this->is_new; } /** * Saves any fields which have been modified on this object to the database. * * @return bool True on success. * * @throws Exception Primary key ID contains null value(s). * @throws Exception Primary key ID missing from row or is null. */ public function save() { global $wpdb; // Remove any expression fields as they are already baked into the query. $values = \array_values( \array_diff_key( $this->dirty_fields, $this->expr_fields ) ); if ( ! $this->is_new ) { // UPDATE. // If there are no dirty values, do nothing. if ( empty( $values ) && empty( $this->expr_fields ) ) { return true; } $query = \implode( ' ', [ $this->build_update(), $this->add_id_column_conditions() ] ); $id = $this->id( true ); if ( \is_array( $id ) ) { $values = \array_merge( $values, \array_values( $id ) ); } else { $values[] = $id; } } else { // INSERT. $query = $this->build_insert(); } $success = self::execute( $query, $values ); // If we've just inserted a new record, set the ID of this object. if ( $this->is_new ) { $this->is_new = false; if ( $this->count_null_id_columns() !== 0 ) { $column = $this->get_id_column_name(); // If the primary key is compound, assign the last inserted id to the first column. if ( \is_array( $column ) ) { $column = \reset( $column ); } // Explicitly cast to int to make dealing with Id's simpler. $this->data[ $column ] = (int) $wpdb->insert_id; } } $this->dirty_fields = []; $this->expr_fields = []; return $success; } /** * Extracts and gathers all dirty column names from the given model instances. * * @param array $models Array of model instances to be inserted. * * @return array The distinct set of columns that are dirty in at least one of the models. * * @throws InvalidArgumentException Instance to be inserted is not a new one. */ public function get_dirty_column_names( $models ) { $dirty_column_names = []; foreach ( $models as $model ) { if ( ! $model->orm->is_new() ) { throw new InvalidArgumentException( 'Instance to be inserted is not a new one' ); } // Remove any expression fields as they are already baked into the query. $dirty_fields = \array_diff_key( $model->orm->dirty_fields, $model->orm->expr_fields ); $dirty_column_names = \array_merge( $dirty_column_names, $dirty_fields ); } $dirty_column_names = \array_keys( $dirty_column_names ); return $dirty_column_names; } /** * Inserts multiple rows in a single query. Expects new rows as it's a strictly insert function, not an update one. * * @example From the Indexable_Link_Builder class: $this->seo_links_repository->query()->insert_many( $links ); * * @param array $models Array of model instances to be inserted. * * @return bool True for successful insert, false for failed. * * @throws InvalidArgumentException Invalid instances to be inserted. * @throws InvalidArgumentException Instance to be inserted is not a new one. */ public function insert_many( $models ) { // Validate the input first. if ( ! \is_array( $models ) ) { throw new InvalidArgumentException( 'Invalid instances to be inserted' ); } if ( empty( $models ) ) { return true; } $success = true; /** * Filter: 'wpseo_chunk_bulked_insert_queries' - Allow filtering the chunk size of each bulked INSERT query. * * @param int $chunk_size The chunk size of the bulked INSERT queries. */ $chunk = \apply_filters( 'wpseo_chunk_bulk_insert_queries', 100 ); $chunk = ! \is_int( $chunk ) ? 100 : $chunk; $chunk = ( $chunk <= 0 ) ? 100 : $chunk; $chunked_models = \array_chunk( $models, $chunk ); foreach ( $chunked_models as $models_chunk ) { $values = []; // First, we'll gather all the dirty fields throughout the models to be inserted. $dirty_column_names = $this->get_dirty_column_names( $models_chunk ); // Now, we're creating all dirty fields throughout the models and // setting them to null if they don't exist in each model. foreach ( $models_chunk as $model ) { $model_values = []; foreach ( $dirty_column_names as $dirty_column ) { // Set the value to null if it hasn't been set already. if ( ! isset( $model->orm->dirty_fields[ $dirty_column ] ) ) { $model->orm->dirty_fields[ $dirty_column ] = null; } // Only register the value if it is not null. if ( ! \is_null( $model->orm->dirty_fields[ $dirty_column ] ) ) { $model_values[] = $model->orm->dirty_fields[ $dirty_column ]; } } $values = \array_merge( $values, $model_values ); } // We now have the same set of dirty columns in all our models and also gathered all values. $query = $this->build_insert_many( $models_chunk, $dirty_column_names ); $success = $success && (bool) self::execute( $query, $values ); } return $success; } /** * Updates many records in the database. * * @return int|bool The number of rows changed if the query was succesful. False otherwise. */ public function update_many() { // Remove any expression fields as they are already baked into the query. $values = \array_values( \array_diff_key( $this->dirty_fields, $this->expr_fields ) ); // UPDATE. // If there are no dirty values, do nothing. if ( empty( $values ) && empty( $this->expr_fields ) ) { return true; } $query = $this->join_if_not_empty( ' ', [ $this->build_update(), $this->build_where() ] ); $success = self::execute( $query, \array_merge( $values, $this->values ) ); $this->dirty_fields = []; $this->expr_fields = []; return $success; } /** * Adds a WHERE clause for every column that belongs to the primary key. * * @return string The where part of the query. */ public function add_id_column_conditions() { $query = []; $query[] = 'WHERE'; $keys = \is_array( $this->get_id_column_name() ) ? $this->get_id_column_name() : [ $this->get_id_column_name() ]; $first = true; foreach ( $keys as $key ) { if ( $first ) { $first = false; } else { $query[] = 'AND'; } $query[] = $this->quote_identifier( $key ); $query[] = '= %s'; } return \implode( ' ', $query ); } /** * Builds an UPDATE query. * * @return string The update query. */ protected function build_update() { $query = []; $query[] = "UPDATE {$this->quote_identifier($this->table_name)} SET"; $field_list = []; foreach ( $this->dirty_fields as $key => $value ) { if ( ! \array_key_exists( $key, $this->expr_fields ) ) { $value = ( $value === null ) ? 'NULL' : '%s'; } $field_list[] = "{$this->quote_identifier($key)} = {$value}"; } $query[] = \implode( ', ', $field_list ); return \implode( ' ', $query ); } /** * Builds an INSERT query. * * @return string The insert query. */ protected function build_insert() { $query = []; $query[] = 'INSERT INTO'; $query[] = $this->quote_identifier( $this->table_name ); $field_list = \array_map( [ $this, 'quote_identifier' ], \array_keys( $this->dirty_fields ) ); $query[] = '(' . \implode( ', ', $field_list ) . ')'; $query[] = 'VALUES'; $placeholders = $this->create_placeholders( $this->dirty_fields ); $query[] = "({$placeholders})"; return \implode( ' ', $query ); } /** * Builds a bulk INSERT query. * * @param array $models Array of model instances to be inserted. * @param array $dirty_column_names Array of dirty fields to be used in INSERT. * * @return string The insert query. */ protected function build_insert_many( $models, $dirty_column_names ) { $example_model = $models[0]; $total_placeholders = ''; $query = []; $query[] = 'INSERT INTO'; $query[] = $this->quote_identifier( $example_model->orm->table_name ); $field_list = \array_map( [ $this, 'quote_identifier' ], $dirty_column_names ); $query[] = '(' . \implode( ', ', $field_list ) . ')'; $query[] = 'VALUES'; // We assign placeholders per model for dirty fields that have values and NULL for dirty fields that don't. foreach ( $models as $model ) { $placeholder = []; foreach ( $dirty_column_names as $dirty_field ) { $placeholder[] = ( $model->orm->dirty_fields[ $dirty_field ] === null ) ? 'NULL' : '%s'; } $placeholders = \implode( ', ', $placeholder ); $total_placeholders .= "({$placeholders}),"; } $query[] = \rtrim( $total_placeholders, ',' ); return \implode( ' ', $query ); } /** * Deletes this record from the database. * * @return string The delete query. * * @throws Exception Primary key ID contains null value(s). * @throws Exception Primary key ID missing from row or is null. */ public function delete() { $query = [ 'DELETE FROM', $this->quote_identifier( $this->table_name ), $this->add_id_column_conditions() ]; return self::execute( \implode( ' ', $query ), \is_array( $this->id( true ) ) ? \array_values( $this->id( true ) ) : [ $this->id( true ) ] ); } /** * Deletes many records from the database. * * @return bool|int Response of wpdb::query. */ public function delete_many() { // Build and return the full DELETE statement by concatenating // the results of calling each separate builder method. $query = $this->join_if_not_empty( ' ', [ 'DELETE FROM', $this->quote_identifier( $this->table_name ), $this->build_where(), ] ); return self::execute( $query, $this->values ); } /* * --- ArrayAccess --- */ /** * Checks whether the data has the key. * * @param mixed $offset Key. * * @return bool Whether the data has the key. */ #[ReturnTypeWillChange] public function offsetExists( $offset ) { return \array_key_exists( $offset, $this->data ); } /** * Retrieves the value of the key. * * @param mixed $offset Key. * * @return array|mixed|null The value. */ #[ReturnTypeWillChange] public function offsetGet( $offset ) { return $this->get( $offset ); } /** * Sets the value of the key. * * @param string|int $offset Key. * @param mixed $value Value. * * @return void */ #[ReturnTypeWillChange] public function offsetSet( $offset, $value ) { if ( \is_null( $offset ) ) { return; } $this->set( $offset, $value ); } /** * Removes the given key from the data. * * @param mixed $offset Key. * * @return void */ #[ReturnTypeWillChange] public function offsetUnset( $offset ) { unset( $this->data[ $offset ] ); unset( $this->dirty_fields[ $offset ] ); } /* * --- MAGIC METHODS --- */ /** * Handles magic get via offset. * * @param mixed $key Key. * * @return array|mixed|null The value in the offset. */ public function __get( $key ) { return $this->offsetGet( $key ); } /** * Handles magic set via offset. * * @param string|int $key Key. * @param mixed $value Value. * * @return void */ public function __set( $key, $value ) { $this->offsetSet( $key, $value ); } /** * Handles magic unset via offset. * * @param mixed $key Key. * * @return void */ public function __unset( $key ) { $this->offsetUnset( $key ); } /** * Handles magic isset via offset. * * @param mixed $key Key. * * @return bool Whether the offset has the key. */ public function __isset( $key ) { return $this->offsetExists( $key ); } } migrations/adapter.php000064400000064761147206761040011076 0ustar00dbname; } /** * Checks support for migrations. * * @return bool */ public function supports_migrations() { return true; } /** * Returns all column native types. * * @return array */ public function native_database_types() { $types = [ 'primary_key' => [ 'name' => 'integer', 'limit' => 11, 'null' => false, ], 'string' => [ 'name' => 'varchar', 'limit' => 255, ], 'text' => [ 'name' => 'text' ], 'tinytext' => [ 'name' => 'tinytext' ], 'mediumtext' => [ 'name' => 'mediumtext' ], 'integer' => [ 'name' => 'int', 'limit' => 11, ], 'tinyinteger' => [ 'name' => 'tinyint' ], 'smallinteger' => [ 'name' => 'smallint' ], 'mediuminteger' => [ 'name' => 'mediumint' ], 'biginteger' => [ 'name' => 'bigint' ], 'float' => [ 'name' => 'float' ], 'decimal' => [ 'name' => 'decimal', 'scale' => 0, 'precision' => 10, ], 'datetime' => [ 'name' => 'datetime' ], 'timestamp' => [ 'name' => 'timestamp' ], 'time' => [ 'name' => 'time' ], 'date' => [ 'name' => 'date' ], 'binary' => [ 'name' => 'blob' ], 'tinybinary' => [ 'name' => 'tinyblob' ], 'mediumbinary' => [ 'name' => 'mediumblob' ], 'longbinary' => [ 'name' => 'longblob' ], 'boolean' => [ 'name' => 'tinyint', 'limit' => 1, ], 'enum' => [ 'name' => 'enum', 'values' => [], ], 'uuid' => [ 'name' => 'char', 'limit' => 36, ], 'char' => [ 'name' => 'char' ], ]; return $types; } /** * Checks if a table exists. * * @param string $table The table name. * * @return bool */ public function has_table( $table ) { return $this->table_exists( $table ); } /** * Allows overriding the hardcoded schema table name constant in case of parallel migrations. * * @return string */ public function get_schema_version_table_name() { return Model::get_table_name( 'migrations' ); } /** * Create the schema table, if necessary. * * @return void */ public function create_schema_version_table() { if ( ! $this->has_table( $this->get_schema_version_table_name() ) ) { $t = $this->create_table( $this->get_schema_version_table_name() ); $t->column( 'version', 'string', [ 'limit' => 191 ] ); $t->finish(); $this->add_index( $this->get_schema_version_table_name(), 'version', [ 'unique' => true ] ); } } /** * Starts a transaction. * * @return void */ public function start_transaction() { if ( $this->in_transaction() === false ) { $this->begin_transaction(); } } /** * Commits a transaction. * * @return void */ public function commit_transaction() { if ( $this->in_transaction() ) { $this->commit(); } } /** * Rollbacks a transaction. * * @return void */ public function rollback_transaction() { if ( $this->in_transaction() ) { $this->rollback(); } } /** * Quotes a table name string. * * @param string $text Table name. * * @return string */ public function quote_table( $text ) { return '`' . $text . '`'; } /** * Return the SQL definition of a column. * * @param string $column_name The column name. * @param string $type The type of the column. * @param array|null $options Column options. * * @return string */ public function column_definition( $column_name, $type, $options = null ) { $col = new Column( $this, $column_name, $type, $options ); return $col->__toString(); } /** * Checks if a database exists. * * @param string $database The database name. * * @return bool */ public function database_exists( $database ) { $ddl = 'SHOW DATABASES'; $result = $this->select_all( $ddl ); if ( \count( $result ) === 0 ) { return false; } foreach ( $result as $dbrow ) { if ( $dbrow['Database'] === $database ) { return true; } } return false; } /** * Creates a database. * * @param string $db The database name. * * @return bool */ public function create_database( $db ) { if ( $this->database_exists( $db ) ) { return false; } $ddl = \sprintf( 'CREATE DATABASE %s', $this->identifier( $db ) ); $result = $this->query( $ddl ); return $result === true; } /** * Drops a database. * * @param string $db The database name. * * @return bool */ public function drop_database( $db ) { if ( ! $this->database_exists( $db ) ) { return false; } $ddl = \sprintf( 'DROP DATABASE IF EXISTS %s', $this->identifier( $db ) ); $result = $this->query( $ddl ); return $result === true; } /** * Checks if a table exists. * * @param string $table The table name. * * @return bool */ public function table_exists( $table ) { global $wpdb; // We need last error to be clear so we can check against it easily. $previous_last_error = $wpdb->last_error; $previous_suppress_errors = $wpdb->suppress_errors; $wpdb->last_error = ''; $wpdb->suppress_errors = true; $result = $wpdb->query( "SELECT * FROM $table LIMIT 1" ); // Restore the last error, as this is not truly an error and we don't want to alarm people. $wpdb->last_error = $previous_last_error; $wpdb->suppress_errors = $previous_suppress_errors; return $result !== false; } /** * Wrapper to execute a query. * * @param string $query The query to run. * * @return bool */ public function execute( $query ) { return $this->query( $query ); } /** * Executes a query. * * @param string $query The query to run. * * @return bool Whether or not the query was performed succesfully. */ public function query( $query ) { global $wpdb; $query_type = $this->determine_query_type( $query ); $data = []; if ( $query_type === Constants::SQL_SELECT || $query_type === Constants::SQL_SHOW ) { $data = $wpdb->get_results( $query, \ARRAY_A ); if ( $data === false ) { return false; } return $data; } else { // INSERT, DELETE, etc... $result = $wpdb->query( $query ); if ( $result === false ) { return false; } if ( $query_type === Constants::SQL_INSERT ) { return $wpdb->insert_id; } return true; } } /** * Returns a single result for a query. * * @param string $query The query to run. * * @return array|false An associative array of the result. */ public function select_one( $query ) { global $wpdb; $query_type = $this->determine_query_type( $query ); if ( $query_type === Constants::SQL_SELECT || $query_type === Constants::SQL_SHOW ) { $result = $wpdb->query( $query ); if ( $result === false ) { return false; } return $wpdb->last_result[0]; } return false; } /** * Returns all results for a query. * * @param string $query The query to run. * * @return array An array of associative arrays. */ public function select_all( $query ) { return $this->query( $query ); } /** * Use this method for non-SELECT queries. * Or anything where you dont necessarily expect a result string, e.g. DROPs, CREATEs, etc. * * @param string $ddl The query to run. * * @return bool */ public function execute_ddl( $ddl ) { return $this->query( $ddl ); } /** * Drops a table * * @param string $table The table name. * * @return bool Whether or not the table was succesfully dropped. */ public function drop_table( $table ) { $ddl = \sprintf( 'DROP TABLE IF EXISTS %s', $this->identifier( $table ) ); return $this->query( $ddl ); } /** * Creates a table. * * @param string $table_name The table name. * @param array $options The options. * * @return Table */ public function create_table( $table_name, $options = [] ) { return new Table( $this, $table_name, $options ); } /** * Escapes a string for usage in queries. * * @param string $text The string. * * @return string */ public function quote_string( $text ) { global $wpdb; return $wpdb->_escape( $text ); } /** * Returns a quoted string. * * @param string $text The string. * * @return string */ public function identifier( $text ) { return '`' . $text . '`'; } /** * Renames a table. * * @param string $name The current table name. * @param string $new_name The new table name. * * @return bool */ public function rename_table( $name, $new_name ) { if ( empty( $name ) || empty( $new_name ) ) { return false; } $sql = \sprintf( 'RENAME TABLE %s TO %s', $this->identifier( $name ), $this->identifier( $new_name ) ); return $this->execute_ddl( $sql ); } /** * Adds a column. * * @param string $table_name The table name. * @param string $column_name The column name. * @param string $type The column type. * @param array $options Column options. * * @return bool */ public function add_column( $table_name, $column_name, $type, $options = [] ) { if ( empty( $table_name ) || empty( $column_name ) || empty( $type ) ) { return false; } // Default types. if ( ! \array_key_exists( 'limit', $options ) ) { $options['limit'] = null; } if ( ! \array_key_exists( 'precision', $options ) ) { $options['precision'] = null; } if ( ! \array_key_exists( 'scale', $options ) ) { $options['scale'] = null; } $sql = \sprintf( 'ALTER TABLE %s ADD `%s` %s', $this->identifier( $table_name ), $column_name, $this->type_to_sql( $type, $options ) ); $sql .= $this->add_column_options( $type, $options ); return $this->execute_ddl( $sql ); } /** * Drops a column. * * @param string $table_name The table name. * @param string $column_name The column name. * * @return bool */ public function remove_column( $table_name, $column_name ) { $sql = \sprintf( 'ALTER TABLE %s DROP COLUMN %s', $this->identifier( $table_name ), $this->identifier( $column_name ) ); return $this->execute_ddl( $sql ); } /** * Renames a column. * * @param string $table_name The table name. * @param string $column_name The column name. * @param string $new_column_name The new column name. * * @return bool */ public function rename_column( $table_name, $column_name, $new_column_name ) { if ( empty( $table_name ) || empty( $column_name ) || empty( $new_column_name ) ) { return false; } $column_info = $this->column_info( $table_name, $column_name ); $current_type = $column_info['type']; $sql = \sprintf( 'ALTER TABLE %s CHANGE %s %s %s', $this->identifier( $table_name ), $this->identifier( $column_name ), $this->identifier( $new_column_name ), $current_type ); $sql .= $this->add_column_options( $current_type, $column_info ); return $this->execute_ddl( $sql ); } /** * Changes a column. * * @param string $table_name The table name. * @param string $column_name The column name. * @param string $type The column type. * @param array $options Column options. * * @return bool */ public function change_column( $table_name, $column_name, $type, $options = [] ) { if ( empty( $table_name ) || empty( $column_name ) || empty( $type ) ) { return false; } $column_info = $this->column_info( $table_name, $column_name ); // Default types. if ( ! \array_key_exists( 'limit', $options ) ) { $options['limit'] = null; } if ( ! \array_key_exists( 'precision', $options ) ) { $options['precision'] = null; } if ( ! \array_key_exists( 'scale', $options ) ) { $options['scale'] = null; } $sql = \sprintf( 'ALTER TABLE `%s` CHANGE `%s` `%s` %s', $table_name, $column_name, $column_name, $this->type_to_sql( $type, $options ) ); $sql .= $this->add_column_options( $type, $options ); return $this->execute_ddl( $sql ); } /** * Returns the database information for a column. * * @param string $table The table name. * @param string $column The column name. * * @return array|null */ public function column_info( $table, $column ) { if ( empty( $table ) || empty( $column ) ) { return null; } try { $sql = \sprintf( "SHOW FULL COLUMNS FROM %s LIKE '%s'", $this->identifier( $table ), $column ); $result = $this->select_one( $sql ); if ( \is_array( $result ) ) { $result = \array_change_key_case( $result, \CASE_LOWER ); } return $result; } catch ( Exception $e ) { return null; } } /** * Adds an index. * * @param string $table_name The table name. * @param array|string $column_name The column name(s). * @param array $options Index options. * * @return bool */ public function add_index( $table_name, $column_name, $options = [] ) { if ( empty( $table_name ) || empty( $column_name ) ) { return false; } // Unique index? if ( \is_array( $options ) && \array_key_exists( 'unique', $options ) && $options['unique'] === true ) { $unique = true; } else { $unique = false; } // Did the user specify an index name? if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) { $index_name = $options['name']; } else { $index_name = $this->get_index_name( $table_name, $column_name ); } if ( \strlen( $index_name ) > Constants::MYSQL_MAX_IDENTIFIER_LENGTH ) { return false; } if ( ! \is_array( $column_name ) ) { $column_names = [ $column_name ]; } else { $column_names = $column_name; } $cols = []; foreach ( $column_names as $name ) { $cols[] = $this->identifier( $name ); } $sql = \sprintf( 'CREATE %sINDEX %s ON %s(%s)', ( $unique === true ) ? 'UNIQUE ' : '', $this->identifier( $index_name ), $this->identifier( $table_name ), \implode( ', ', $cols ) ); return $this->execute_ddl( $sql ); } /** * Drops an index. * * @param string $table_name The table name. * @param array|string $column_name The column name(s). * @param array $options Index options. * * @return bool */ public function remove_index( $table_name, $column_name, $options = [] ) { if ( empty( $table_name ) || empty( $column_name ) ) { return false; } // Did the user specify an index name? if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) { $index_name = $options['name']; } else { $index_name = $this->get_index_name( $table_name, $column_name ); } $sql = \sprintf( 'DROP INDEX %s ON %s', $this->identifier( $index_name ), $this->identifier( $table_name ) ); return $this->execute_ddl( $sql ); } /** * Adds timestamps. * * @param string $table_name The table name. * @param string $created_column_name Created at column name. * @param string $updated_column_name Updated at column name. * * @return bool */ public function add_timestamps( $table_name, $created_column_name, $updated_column_name ) { if ( empty( $table_name ) || empty( $created_column_name ) || empty( $updated_column_name ) ) { return false; } $created_at = $this->add_column( $table_name, $created_column_name, 'datetime' ); $updated_at = $this->add_column( $table_name, $updated_column_name, 'timestamp', [ 'null' => false, 'default' => 'CURRENT_TIMESTAMP', 'extra' => 'ON UPDATE CURRENT_TIMESTAMP', ] ); return $created_at && $updated_at; } /** * Removes timestamps. * * @param string $table_name The table name. * @param string $created_column_name Created at column name. * @param string $updated_column_name Updated at column name. * * @return bool Whether or not the timestamps were removed. */ public function remove_timestamps( $table_name, $created_column_name, $updated_column_name ) { if ( empty( $table_name ) || empty( $created_column_name ) || empty( $updated_column_name ) ) { return false; } $updated_at = $this->remove_column( $table_name, $created_column_name ); $created_at = $this->remove_column( $table_name, $updated_column_name ); return $created_at && $updated_at; } /** * Checks an index. * * @param string $table_name The table name. * @param array|string $column_name The column name(s). * @param array $options Index options. * * @return bool Whether or not the index exists. */ public function has_index( $table_name, $column_name, $options = [] ) { if ( empty( $table_name ) || empty( $column_name ) ) { return false; } // Did the user specify an index name? if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) { $index_name = $options['name']; } else { $index_name = $this->get_index_name( $table_name, $column_name ); } $indexes = $this->indexes( $table_name ); foreach ( $indexes as $idx ) { if ( $idx['name'] === $index_name ) { return true; } } return false; } /** * Returns all indexes of a table. * * @param string $table_name The table name. * * @return array */ public function indexes( $table_name ) { $sql = \sprintf( 'SHOW KEYS FROM %s', $this->identifier( $table_name ) ); $result = $this->select_all( $sql ); $indexes = []; foreach ( $result as $row ) { // Skip primary. if ( $row['Key_name'] === 'PRIMARY' ) { continue; } $indexes[] = [ 'name' => $row['Key_name'], 'unique' => (int) $row['Non_unique'] === 0, ]; } return $indexes; } /** * Converts a type to sql. Default options: * $limit = null, $precision = null, $scale = null * * @param string $type The native type. * @param array $options The options. * * @return string The SQL type. * * @throws Exception If invalid arguments are supplied. */ public function type_to_sql( $type, $options = [] ) { $natives = $this->native_database_types(); if ( ! \array_key_exists( $type, $natives ) ) { $error = \sprintf( "Error:I dont know what column type of '%s' maps to for MySQL.", $type ); $error .= "\nYou provided: {$type}\n"; $error .= "Valid types are: \n"; $types = \array_keys( $natives ); foreach ( $types as $t ) { if ( $t === 'primary_key' ) { continue; } $error .= "\t{$t}\n"; } throw new Exception( $error ); } $scale = null; $precision = null; $limit = null; if ( isset( $options['precision'] ) ) { $precision = $options['precision']; } if ( isset( $options['scale'] ) ) { $scale = $options['scale']; } if ( isset( $options['limit'] ) ) { $limit = $options['limit']; } if ( isset( $options['values'] ) ) { $values = $options['values']; } $native_type = $natives[ $type ]; if ( \is_array( $native_type ) && \array_key_exists( 'name', $native_type ) ) { $column_type_sql = $native_type['name']; } else { return $native_type; } if ( $type === 'decimal' || $type === 'float' ) { // Ignore limit, use precison and scale. if ( $precision === null && \array_key_exists( 'precision', $native_type ) ) { $precision = $native_type['precision']; } if ( $scale === null && \array_key_exists( 'scale', $native_type ) ) { $scale = $native_type['scale']; } if ( $precision !== null ) { if ( \is_int( $scale ) ) { $column_type_sql .= \sprintf( '(%d, %d)', $precision, $scale ); } else { $column_type_sql .= \sprintf( '(%d)', $precision ); } } elseif ( $scale ) { throw new Exception( "Error adding $type column: precision cannot be empty if scale is specified" ); } } elseif ( $type === 'enum' ) { if ( empty( $values ) ) { throw new Exception( 'Error adding enum column: there must be at least one value defined' ); } else { $column_type_sql .= \sprintf( "('%s')", \implode( "','", \array_map( [ $this, 'quote_string' ], $values ) ) ); } } // Not a decimal column. if ( $limit === null && \array_key_exists( 'limit', $native_type ) ) { $limit = $native_type['limit']; } if ( $limit ) { $column_type_sql .= \sprintf( '(%d)', $limit ); } return $column_type_sql; } /** * Adds column options. * * @param string $type The native type. * @param array $options The options. * * @return string The SQL statement for the column options. * * @throws Exception If invalid arguments are supplied. */ public function add_column_options( $type, $options ) { $sql = ''; if ( ! \is_array( $options ) ) { return $sql; } if ( \array_key_exists( 'unsigned', $options ) && $options['unsigned'] === true ) { $sql .= ' UNSIGNED'; } if ( \array_key_exists( 'character', $options ) ) { $sql .= \sprintf( ' CHARACTER SET %s', $this->identifier( $options['character'] ) ); } if ( \array_key_exists( 'collate', $options ) ) { $sql .= \sprintf( ' COLLATE %s', $this->identifier( $options['collate'] ) ); } if ( \array_key_exists( 'auto_increment', $options ) && $options['auto_increment'] === true ) { $sql .= ' auto_increment'; } if ( \array_key_exists( 'default', $options ) && $options['default'] !== null ) { if ( $this->is_sql_method_call( $options['default'] ) ) { throw new Exception( 'MySQL does not support function calls as default values, constants only.' ); } if ( \is_int( $options['default'] ) ) { $default_format = '%d'; } elseif ( \is_bool( $options['default'] ) ) { $default_format = "'%d'"; } elseif ( $options['default'] === 'CURRENT_TIMESTAMP' ) { $default_format = '%s'; } else { $default_format = "'%s'"; } $default_value = \sprintf( $default_format, $options['default'] ); $sql .= \sprintf( ' DEFAULT %s', $default_value ); } if ( \array_key_exists( 'null', $options ) ) { if ( $options['null'] === false || $options['null'] === 'NO' ) { $sql .= ' NOT NULL'; } elseif ( $type === 'timestamp' ) { $sql .= ' NULL'; } } if ( \array_key_exists( 'comment', $options ) ) { $sql .= \sprintf( " COMMENT '%s'", $this->quote_string( $options['comment'] ) ); } if ( \array_key_exists( 'extra', $options ) ) { $sql .= \sprintf( ' %s', $this->quote_string( $options['extra'] ) ); } if ( \array_key_exists( 'after', $options ) ) { $sql .= \sprintf( ' AFTER %s', $this->identifier( $options['after'] ) ); } return $sql; } /** * Returns a list of all versions that have been migrated. * * @return string[] The version numbers that have been migrated. */ public function get_migrated_versions() { $result = $this->select_all( \sprintf( 'SELECT version FROM %s', $this->get_schema_version_table_name() ) ); return \array_column( $result, 'version' ); } /** * Adds a migrated version. * * @param string $version The version. * * @return bool Whether or not the version was succesfully set. */ public function add_version( $version ) { $sql = \sprintf( "INSERT INTO %s (version) VALUES ('%s')", $this->get_schema_version_table_name(), $version ); return $this->execute_ddl( $sql ); } /** * Removes a migrated version. * * @param string $version The version. * * @return bool Whether or not the version was succesfully removed. */ public function remove_version( $version ) { $sql = \sprintf( "DELETE FROM %s WHERE version = '%s'", $this->get_schema_version_table_name(), $version ); return $this->execute_ddl( $sql ); } /** * Returns a message displaying the current version * * @return string */ public function __toString() { return self::class . ', version ' . $this->version; } /** * Returns an index name. * * @param string $table_name The table name. * @param string $column_name The column name. * * @return string The index name. */ private function get_index_name( $table_name, $column_name ) { $name = \preg_replace( '/\\W/', '_', $table_name ); $name = \preg_replace( '/\\_{2,}/', '_', $name ); // If the column parameter is an array then the user wants to create a multi-column index. if ( \is_array( $column_name ) ) { $column_str = \implode( '_and_', $column_name ); } else { $column_str = $column_name; } $name .= \sprintf( '_%s', $column_str ); return $name; } /** * Returns the type of a query. * * @param string $query The query to run. * * @return int The query type. */ private function determine_query_type( $query ) { $query = \strtolower( \trim( $query ) ); $match = []; \preg_match( '/^(\\w)*/i', $query, $match ); $type = $match[0]; switch ( $type ) { case 'select': return Constants::SQL_SELECT; case 'update': return Constants::SQL_UPDATE; case 'delete': return Constants::SQL_DELETE; case 'insert': return Constants::SQL_INSERT; case 'alter': return Constants::SQL_ALTER; case 'drop': return Constants::SQL_DROP; case 'create': return Constants::SQL_CREATE; case 'show': return Constants::SQL_SHOW; case 'rename': return Constants::SQL_RENAME; case 'set': return Constants::SQL_SET; default: return Constants::SQL_UNKNOWN_QUERY_TYPE; } } /** * Detect whether or not the string represents a function call and if so * do not wrap it in single-quotes, otherwise do wrap in single quotes. * * @param string $text The string. * * @return bool Whether or not it's a SQL function call. */ private function is_sql_method_call( $text ) { $text = \trim( $text ); if ( \substr( $text, -2, 2 ) === '()' ) { return true; } return false; } /** * Checks if a transaction is active. * * @return bool */ private function in_transaction() { return $this->in_transaction; } /** * Starts a transaction. * * @return void * * @throws Exception If a transaction was already started. */ private function begin_transaction() { global $wpdb; if ( $this->in_transaction === true ) { throw new Exception( 'Transaction already started' ); } $wpdb->query( 'START TRANSACTION' ); $this->in_transaction = true; } /** * Commits a transaction. * * @return void * * @throws Exception If no transaction was strated. */ private function commit() { global $wpdb; if ( $this->in_transaction === false ) { throw new Exception( 'Transaction not started' ); } $wpdb->query( 'COMMIT' ); $this->in_transaction = false; } /** * Rollbacks a transaction. * * @return void * * @throws Exception If no transaction was started. */ private function rollback() { global $wpdb; if ( $this->in_transaction === false ) { throw new Exception( 'Transaction not started' ); } $wpdb->query( 'ROLLBACK' ); $this->in_transaction = false; } } migrations/constants.php000064400000001256147206761040011460 0ustar00adapter = $adapter; $this->name = $name; $this->options = $options; $this->init_sql( $name, $options ); if ( \array_key_exists( 'id', $options ) ) { if ( \is_bool( $options['id'] ) && $options['id'] === false ) { $this->auto_generate_id = false; } // If its a string then we want to auto-generate an integer-based // primary key with this name. if ( \is_string( $options['id'] ) ) { $this->auto_generate_id = true; $this->primary_keys[] = $options['id']; } } } /** * Create a column * * @param string $column_name The column name. * @param string $type The column type. * @param array $options The options. * * @return void */ public function column( $column_name, $type, $options = [] ) { // If there is already a column by the same name then silently fail and continue. foreach ( $this->columns as $column ) { if ( $column->name === $column_name ) { return; } } $column_options = []; if ( \array_key_exists( 'primary_key', $options ) ) { if ( $options['primary_key'] ) { $this->primary_keys[] = $column_name; } } if ( \array_key_exists( 'auto_increment', $options ) ) { if ( $options['auto_increment'] ) { $column_options['auto_increment'] = true; } } $column_options = \array_merge( $column_options, $options ); $column = new Column( $this->adapter, $column_name, $type, $column_options ); $this->columns[] = $column; } /** * Shortcut to create timestamps columns (default created_at, updated_at) * * @param string $created_column_name Created at column name. * @param string $updated_column_name Updated at column name. * * @return void */ public function timestamps( $created_column_name = 'created_at', $updated_column_name = 'updated_at' ) { $this->column( $created_column_name, 'datetime' ); $this->column( $updated_column_name, 'timestamp', [ 'null' => false, 'default' => 'CURRENT_TIMESTAMP', 'extra' => 'ON UPDATE CURRENT_TIMESTAMP', ] ); } /** * Get all primary keys * * @return string */ private function keys() { if ( \count( $this->primary_keys ) > 0 ) { $lead = ' PRIMARY KEY ('; $quoted = []; foreach ( $this->primary_keys as $key ) { $quoted[] = \sprintf( '%s', $this->adapter->identifier( $key ) ); } $primary_key_sql = ",\n" . $lead . \implode( ',', $quoted ) . ')'; return $primary_key_sql; } return ''; } /** * Table definition * * @param bool $wants_sql Whether or not to return SQL or execute the query. Defaults to false. * * @return bool|string * * @throws Exception If the table definition has not been intialized. */ public function finish( $wants_sql = false ) { if ( ! $this->initialized ) { throw new Exception( \sprintf( "Table Definition: '%s' has not been initialized", $this->name ) ); } $opt_str = ''; if ( \is_array( $this->options ) && \array_key_exists( 'options', $this->options ) ) { $opt_str = $this->options['options']; } elseif ( isset( $this->adapter->db_info['charset'] ) ) { $opt_str = ' DEFAULT CHARSET=' . $this->adapter->db_info['charset']; } else { $opt_str = ' DEFAULT CHARSET=utf8'; } $close_sql = \sprintf( ') %s;', $opt_str ); $create_table_sql = $this->sql; if ( $this->auto_generate_id === true ) { $this->primary_keys[] = 'id'; $primary_id = new Column( $this->adapter, 'id', 'integer', [ 'unsigned' => true, 'null' => false, 'auto_increment' => true, ] ); $create_table_sql .= $primary_id->to_sql() . ",\n"; } $create_table_sql .= $this->columns_to_str(); $create_table_sql .= $this->keys() . $close_sql; if ( $wants_sql ) { return $create_table_sql; } return $this->adapter->execute_ddl( $create_table_sql ); } /** * Get SQL for all columns. * * @return string The SQL. */ private function columns_to_str() { $fields = []; $len = \count( $this->columns ); for ( $i = 0; $i < $len; $i++ ) { $c = $this->columns[ $i ]; $fields[] = $c->__toString(); } return \implode( ",\n", $fields ); } /** * Init create sql statement. * * @param string $name The name. * @param array $options The options. * * @return void */ private function init_sql( $name, $options ) { // Are we forcing table creation? If so, drop it first. if ( \array_key_exists( 'force', $options ) && $options['force'] === true ) { $this->adapter->drop_table( $name ); } $temp = ''; if ( \array_key_exists( 'temporary', $options ) ) { $temp = ' TEMPORARY'; } $create_sql = \sprintf( 'CREATE%s TABLE ', $temp ); $create_sql .= \sprintf( "%s (\n", $this->adapter->identifier( $name ) ); $this->sql .= $create_sql; $this->initialized = true; } } migrations/column.php000064400000003465147206761040010745 0ustar00adapter = $adapter; $this->name = $name; $this->type = $type; $this->options = $options; } /** * Returns the SQL of this column. * * @return string */ public function to_sql() { $column_sql = \sprintf( '%s %s', $this->adapter->identifier( $this->name ), $this->sql_type() ); $column_sql .= $this->adapter->add_column_options( $this->type, $this->options ); return $column_sql; } /** * The SQL string version. * * @return string */ public function __toString() { return $this->to_sql(); } /** * The SQL type. * * @return string */ private function sql_type() { return $this->adapter->type_to_sql( $this->type, $this->options ); } } migrations/migration.php000064400000014354147206761040011440 0ustar00set_adapter( $adapter ); } /** * Sets an adapter. * * @param Adapter $adapter The adapter to set. * * @return $this|null */ public function set_adapter( $adapter ) { if ( ! $adapter instanceof Adapter ) { return; } $this->adapter = $adapter; return $this; } /** * Returns the current adapter. * * @return object */ public function get_adapter() { return $this->adapter; } /** * Creates a database. * * @param string $name The name of the database. * @param array|null $options The options. * * @return bool */ public function create_database( $name, $options = null ) { return $this->adapter->create_database( $name, $options ); } /** * Drops a database. * * @param string $name The name of the database. * * @return bool */ public function drop_database( $name ) { return $this->adapter->drop_database( $name ); } /** * Drops a table. * * @param string $table_name The name of the table. * * @return bool */ public function drop_table( $table_name ) { return $this->adapter->drop_table( $table_name ); } /** * Renames a table. * * @param string $name The name of the table. * @param string $new_name The new name of the table. * * @return bool */ public function rename_table( $name, $new_name ) { return $this->adapter->rename_table( $name, $new_name ); } /** * Renames a column. * * @param string $table_name The name of the table. * @param string $column_name The column name. * @param string $new_column_name The new column name. * * @return bool */ public function rename_column( $table_name, $column_name, $new_column_name ) { return $this->adapter->rename_column( $table_name, $column_name, $new_column_name ); } /** * Adds a column. * * @param string $table_name The name of the table. * @param string $column_name The column name. * @param string $type The column type. * @param array|string $options The options. * * @return bool */ public function add_column( $table_name, $column_name, $type, $options = [] ) { return $this->adapter->add_column( $table_name, $column_name, $type, $options ); } /** * Removes a column. * * @param string $table_name The name of the table. * @param string $column_name The column name. * * @return bool */ public function remove_column( $table_name, $column_name ) { return $this->adapter->remove_column( $table_name, $column_name ); } /** * Changes a column. * * @param string $table_name The name of the table. * @param string $column_name The column name. * @param string $type The column type. * @param array|string $options The options. * * @return bool */ public function change_column( $table_name, $column_name, $type, $options = [] ) { return $this->adapter->change_column( $table_name, $column_name, $type, $options ); } /** * Adds an index. * * @param string $table_name The name of the table. * @param array|string $column_name The column name. * @param array|string $options The options. * * @return bool */ public function add_index( $table_name, $column_name, $options = [] ) { return $this->adapter->add_index( $table_name, $column_name, $options ); } /** * Removes an index. * * @param string $table_name The name of the table. * @param array|string $column_name The column name. * @param array|string $options The options. * * @return bool */ public function remove_index( $table_name, $column_name, $options = [] ) { return $this->adapter->remove_index( $table_name, $column_name, $options ); } /** * Adds timestamps. * * @param string $table_name The name of the table. * @param string $created_column_name Created at column name. * @param string $updated_column_name Updated at column name. * * @return bool */ public function add_timestamps( $table_name, $created_column_name = 'created_at', $updated_column_name = 'updated_at' ) { return $this->adapter->add_timestamps( $table_name, $created_column_name, $updated_column_name ); } /** * Removes timestamps. * * @param string $table_name The name of the table. * @param string $created_column_name Created at column name. * @param string $updated_column_name Updated at column name. * * @return bool */ public function remove_timestamps( $table_name, $created_column_name = 'created_at', $updated_column_name = 'updated_at' ) { return $this->adapter->remove_timestamps( $table_name, $created_column_name, $updated_column_name ); } /** * Creates a table. * * @param string $table_name The name of the table. * @param array|string $options The options. * * @return bool|Table */ public function create_table( $table_name, $options = [] ) { return $this->adapter->create_table( $table_name, $options ); } /** * Execute a query and return the first result. * * @param string $sql The query to run. * * @return array */ public function select_one( $sql ) { return $this->adapter->select_one( $sql ); } /** * Execute a query and return all results. * * @param string $sql The query to run. * * @return array */ public function select_all( $sql ) { return $this->adapter->select_all( $sql ); } /** * Execute a query. * * @param string $sql The query to run. * * @return bool */ public function query( $sql ) { return $this->adapter->query( $sql ); } /** * Returns a quoted string. * * @param string $str The string to quote. * * @return string */ public function quote_string( $str ) { return $this->adapter->quote_string( $str ); } } model.php000064400000055500147206761040006371 0ustar00prefix . \strtolower( $table_name ); } /** * Sets the table name for the given class name. * * @param string $class_name The class to set the table name for. * * @return void */ protected function set_table_name( $class_name ) { // Prepend namespace to the class name. $class = static::$auto_prefix_models . $class_name; $class::$table = static::get_table_name( $class_name ); } /** * Retrieve the value of a static property on a class. If the * class or the property does not exist, returns the default * value supplied as the third argument (which defaults to null). * * @param string $class_name The target class name. * @param string $property The property to get the value for. * @param mixed|null $default_value Default value when property does not exist. * * @return mixed|null The value of the property. */ protected static function get_static_property( $class_name, $property, $default_value = null ) { if ( ! \class_exists( $class_name ) || ! \property_exists( $class_name, $property ) ) { return $default_value; } if ( ! isset( $class_name::${$property} ) ) { return $default_value; } return $class_name::${$property}; } /** * Static method to get a table name given a class name. * If the supplied class has a public static property * named $table, the value of this property will be * returned. * * If not, the class name will be converted using * the class_name_to_table_name() method. * * If Model::$short_table_names == true or public static * property $table_use_short_name == true then $class_name passed * to class_name_to_table_name() is stripped of namespace information. * * @param string $class_name The class name to get the table name for. * * @return string The table name. */ protected static function get_table_name_for_class( $class_name ) { $specified_table_name = static::get_static_property( $class_name, 'table' ); $use_short_class_name = static::use_short_table_name( $class_name ); if ( $use_short_class_name ) { $exploded_class_name = \explode( '\\', $class_name ); $class_name = \end( $exploded_class_name ); } if ( $specified_table_name === null ) { return static::class_name_to_table_name( $class_name ); } return $specified_table_name; } /** * Should short table names, disregarding class namespaces, be computed? * * $class_property overrides $global_option, unless $class_property is null. * * @param string $class_name The class name to get short name for. * * @return bool True when short table name should be used. */ protected static function use_short_table_name( $class_name ) { $class_property = static::get_static_property( $class_name, 'table_use_short_name' ); if ( $class_property === null ) { return static::$short_table_names; } return $class_property; } /** * Convert a namespace to the standard PEAR underscore format. * * Then convert a class name in CapWords to a table name in * lowercase_with_underscores. * * Finally strip doubled up underscores. * * For example, CarTyre would be converted to car_tyre. And * Project\Models\CarTyre would be project_models_car_tyre. * * @param string $class_name The class name to get the table name for. * * @return string The table name. */ protected static function class_name_to_table_name( $class_name ) { $find = [ '/\\\\/', '/(?<=[a-z])([A-Z])/', '/__/', ]; $replacements = [ '_', '_$1', '_', ]; $class_name = \ltrim( $class_name, '\\' ); $class_name = \preg_replace( $find, $replacements, $class_name ); return \strtolower( $class_name ); } /** * Return the ID column name to use for this class. If it is * not set on the class, returns null. * * @param string $class_name The class name to get the ID column for. * * @return string|null The ID column name. */ protected static function get_id_column_name( $class_name ) { return static::get_static_property( $class_name, 'id_column', static::DEFAULT_ID_COLUMN ); } /** * Build a foreign key based on a table name. If the first argument * (the specified foreign key column name) is null, returns the second * argument (the name of the table) with the default foreign key column * suffix appended. * * @param string $specified_foreign_key_name The keyname to build. * @param string $table_name The table name to build the key name for. * * @return string The built foreign key name. */ protected static function build_foreign_key_name( $specified_foreign_key_name, $table_name ) { if ( $specified_foreign_key_name !== null ) { return $specified_foreign_key_name; } return $table_name . static::DEFAULT_FOREIGN_KEY_SUFFIX; } /** * Factory method used to acquire instances of the given class. * The class name should be supplied as a string, and the class * should already have been loaded by PHP (or a suitable autoloader * should exist). This method actually returns a wrapped ORM object * which allows a database query to be built. The wrapped ORM object is * responsible for returning instances of the correct class when * its find_one or find_many methods are called. * * @param string $class_name The target class name. * * @return ORM Instance of the ORM wrapper. */ public static function factory( $class_name ) { $class_name = static::$auto_prefix_models . $class_name; $table_name = static::get_table_name_for_class( $class_name ); $wrapper = ORM::for_table( $table_name ); $wrapper->set_class_name( $class_name ); $wrapper->use_id_column( static::get_id_column_name( $class_name ) ); return $wrapper; } /** * Internal method to construct the queries for both the has_one and * has_many methods. These two types of association are identical; the * only difference is whether find_one or find_many is used to complete * the method chain. * * @param string $associated_class_name The associated class name. * @param string|null $foreign_key_name The foreign key name in the associated table. * @param string|null $foreign_key_name_in_current_models_table The foreign key in the current models table. * * @return ORM Instance of the ORM. * * @throws Exception When ID of current model has a null value. */ protected function has_one_or_many( $associated_class_name, $foreign_key_name = null, $foreign_key_name_in_current_models_table = null ) { $base_table_name = static::get_table_name_for_class( static::class ); $foreign_key_name = static::build_foreign_key_name( $foreign_key_name, $base_table_name ); /* * Value of foreign_table.{$foreign_key_name} we're looking for. Where foreign_table is the actual * database table in the associated model. */ if ( $foreign_key_name_in_current_models_table === null ) { // Matches foreign_table.{$foreign_key_name} with the value of "{$this->table}.{$this->id()}". $where_value = $this->id(); } else { // Matches foreign_table.{$foreign_key_name} with "{$this->table}.{$foreign_key_name_in_current_models_table}". $where_value = $this->{$foreign_key_name_in_current_models_table}; } return static::factory( $associated_class_name )->where( $foreign_key_name, $where_value ); } /** * Helper method to manage one-to-one relations where the foreign * key is on the associated table. * * @param string $associated_class_name The associated class name. * @param string|null $foreign_key_name The foreign key name in the associated table. * @param string|null $foreign_key_name_in_current_models_table The foreign key in the current models table. * * @return ORM Instance of the ORM. * * @throws Exception When ID of current model has a null value. */ protected function has_one( $associated_class_name, $foreign_key_name = null, $foreign_key_name_in_current_models_table = null ) { return $this->has_one_or_many( $associated_class_name, $foreign_key_name, $foreign_key_name_in_current_models_table ); } /** * Helper method to manage one-to-many relations where the foreign * key is on the associated table. * * @param string $associated_class_name The associated class name. * @param string|null $foreign_key_name The foreign key name in the associated table. * @param string|null $foreign_key_name_in_current_models_table The foreign key in the current models table. * * @return ORM Instance of the ORM. * * @throws Exception When ID has a null value. */ protected function has_many( $associated_class_name, $foreign_key_name = null, $foreign_key_name_in_current_models_table = null ) { $this->set_table_name( $associated_class_name ); return $this->has_one_or_many( $associated_class_name, $foreign_key_name, $foreign_key_name_in_current_models_table ); } /** * Helper method to manage one-to-one and one-to-many relations where * the foreign key is on the base table. * * @param string $associated_class_name The associated class name. * @param string|null $foreign_key_name The foreign key in the current models table. * @param string|null $foreign_key_name_in_associated_models_table The foreign key in the associated table. * * @return $this|null Instance of the foreign model. */ protected function belongs_to( $associated_class_name, $foreign_key_name = null, $foreign_key_name_in_associated_models_table = null ) { $this->set_table_name( $associated_class_name ); $associated_table_name = static::get_table_name_for_class( static::$auto_prefix_models . $associated_class_name ); $foreign_key_name = static::build_foreign_key_name( $foreign_key_name, $associated_table_name ); $associated_object_id = $this->{$foreign_key_name}; if ( $foreign_key_name_in_associated_models_table === null ) { /* * Comparison: "{$associated_table_name}.primary_key = {$associated_object_id}". * * NOTE: primary_key is a placeholder for the actual primary key column's name in $associated_table_name. */ return static::factory( $associated_class_name )->where_id_is( $associated_object_id ); } // Comparison: "{$associated_table_name}.{$foreign_key_name_in_associated_models_table} = {$associated_object_id}". return static::factory( $associated_class_name ) ->where( $foreign_key_name_in_associated_models_table, $associated_object_id ); } /** * Helper method to manage many-to-many relationships via an intermediate model. See * README for a full explanation of the parameters. * * @param string $associated_class_name The associated class name. * @param string|null $join_class_name The class name to join. * @param string|null $key_to_base_table The key to the the current models table. * @param string|null $key_to_associated_table The key to the associated table. * @param string|null $key_in_base_table The key in the current models table. * @param string|null $key_in_associated_table The key in the associated table. * * @return ORM Instance of the ORM. */ protected function has_many_through( $associated_class_name, $join_class_name = null, $key_to_base_table = null, $key_to_associated_table = null, $key_in_base_table = null, $key_in_associated_table = null ) { $base_class_name = static::class; /* * The class name of the join model, if not supplied, is formed by * concatenating the names of the base class and the associated class, * in alphabetical order. */ if ( $join_class_name === null ) { $base_model = \explode( '\\', $base_class_name ); $base_model_name = \end( $base_model ); if ( \strpos( $base_model_name, static::$auto_prefix_models ) === 0 ) { $base_model_name = \substr( $base_model_name, \strlen( static::$auto_prefix_models ), \strlen( $base_model_name ) ); } // Paris wasn't checking the name settings for the associated class. $associated_model = \explode( '\\', $associated_class_name ); $associated_model_name = \end( $associated_model ); if ( \strpos( $associated_model_name, static::$auto_prefix_models ) === 0 ) { $associated_model_name = \substr( $associated_model_name, \strlen( static::$auto_prefix_models ), \strlen( $associated_model_name ) ); } $class_names = [ $base_model_name, $associated_model_name ]; \sort( $class_names, \SORT_STRING ); $join_class_name = \implode( '', $class_names ); } // Get table names for each class. $base_table_name = static::get_table_name_for_class( $base_class_name ); $associated_table_name = static::get_table_name_for_class( static::$auto_prefix_models . $associated_class_name ); $join_table_name = static::get_table_name_for_class( static::$auto_prefix_models . $join_class_name ); // Get ID column names. $base_table_id_column = ( $key_in_base_table === null ) ? static::get_id_column_name( $base_class_name ) : $key_in_base_table; $associated_table_id_column = ( $key_in_associated_table === null ) ? static::get_id_column_name( static::$auto_prefix_models . $associated_class_name ) : $key_in_associated_table; // Get the column names for each side of the join table. $key_to_base_table = static::build_foreign_key_name( $key_to_base_table, $base_table_name ); $key_to_associated_table = static::build_foreign_key_name( $key_to_associated_table, $associated_table_name ); /* phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- Reason: This is commented out code. " SELECT {$associated_table_name}.* FROM {$associated_table_name} JOIN {$join_table_name} ON {$associated_table_name}.{$associated_table_id_column} = {$join_table_name}.{$key_to_associated_table} WHERE {$join_table_name}.{$key_to_base_table} = {$this->$base_table_id_column} ;" */ return static::factory( $associated_class_name ) ->select( "{$associated_table_name}.*" ) ->join( $join_table_name, [ "{$associated_table_name}.{$associated_table_id_column}", '=', "{$join_table_name}.{$key_to_associated_table}", ] ) ->where( "{$join_table_name}.{$key_to_base_table}", $this->{$base_table_id_column} ); } /** * Set the wrapped ORM instance associated with this Model instance. * * @param ORM $orm The ORM instance to set. * * @return void */ public function set_orm( $orm ) { $this->orm = $orm; } /** * Magic getter method, allows $model->property access to data. * * @param string $property The property to get. * * @return mixed The value of the property */ public function __get( $property ) { $value = $this->orm->get( $property ); if ( $value !== null && \in_array( $property, $this->boolean_columns, true ) ) { return (bool) $value; } if ( $value !== null && \in_array( $property, $this->int_columns, true ) ) { return (int) $value; } if ( $value !== null && \in_array( $property, $this->float_columns, true ) ) { return (float) $value; } return $value; } /** * Magic setter method, allows $model->property = 'value' access to data. * * @param string $property The property to set. * @param string $value The value to set. * * @return void */ public function __set( $property, $value ) { if ( $value !== null && \in_array( $property, $this->boolean_columns, true ) ) { $value = ( $value ) ? '1' : '0'; } if ( $value !== null && \in_array( $property, $this->int_columns, true ) ) { $value = (string) $value; } if ( $value !== null && \in_array( $property, $this->float_columns, true ) ) { $value = (string) $value; } $this->orm->set( $property, $value ); } /** * Magic unset method, allows unset($model->property) * * @param string $property The property to unset. * * @return void */ public function __unset( $property ) { $this->orm->__unset( $property ); } /** * JSON serializer. * * @return array The data of this object. */ #[ReturnTypeWillChange] public function jsonSerialize() { return $this->orm->as_array(); } /** * Strips all nested dependencies from the debug info. * * @return array */ public function __debugInfo() { if ( $this->orm ) { return $this->orm->as_array(); } return []; } /** * Magic isset method, allows isset($model->property) to work correctly. * * @param string $property The property to check. * * @return bool True when value is set. */ public function __isset( $property ) { return $this->orm->__isset( $property ); } /** * Getter method, allows $model->get('property') access to data * * @param string $property The property to get. * * @return string The value of a property. */ public function get( $property ) { return $this->orm->get( $property ); } /** * Setter method, allows $model->set('property', 'value') access to data. * * @param string|array $property The property to set. * @param string|null $value The value to give. * * @return static Current object. */ public function set( $property, $value = null ) { $this->orm->set( $property, $value ); return $this; } /** * Setter method, allows $model->set_expr('property', 'value') access to data. * * @param string|array $property The property to set. * @param string|null $value The value to give. * * @return static Current object. */ public function set_expr( $property, $value = null ) { $this->orm->set_expr( $property, $value ); return $this; } /** * Check whether the given property has changed since the object was created or saved. * * @param string $property The property to check. * * @return bool True when field is changed. */ public function is_dirty( $property ) { return $this->orm->is_dirty( $property ); } /** * Check whether the model was the result of a call to create() or not. * * @return bool True when is new. */ public function is_new() { return $this->orm->is_new(); } /** * Wrapper for Idiorm's as_array method. * * @return array The models data as array. */ public function as_array() { $args = \func_get_args(); return \call_user_func_array( [ $this->orm, 'as_array' ], $args ); } /** * Save the data associated with this model instance to the database. * * @return bool True on success. */ public function save() { if ( $this->uses_timestamps ) { if ( ! $this->created_at ) { $this->created_at = \gmdate( 'Y-m-d H:i:s' ); } $this->updated_at = \gmdate( 'Y-m-d H:i:s' ); } return $this->orm->save(); } /** * Delete the database row associated with this model instance. * * @return bool|int Response of wpdb::query. */ public function delete() { return $this->orm->delete(); } /** * Get the database ID of this model instance. * * @return int The database ID of the models instance. * * @throws Exception When the ID is a null value. */ public function id() { return $this->orm->id(); } /** * Hydrate this model instance with an associative array of data. * WARNING: The keys in the array MUST match with columns in the * corresponding database table. If any keys are supplied which * do not match up with columns, the database will throw an error. * * @param array $data The data to pass to the ORM. * * @return void */ public function hydrate( $data ) { $this->orm->hydrate( $data )->force_all_dirty(); } /** * Calls static methods directly on the ORM * * @param string $method The method to call. * @param array $arguments The arguments to use. * * @return array Result of the static call. */ public static function __callStatic( $method, $arguments ) { if ( ! \function_exists( 'get_called_class' ) ) { return []; } $model = static::factory( static::class ); return \call_user_func_array( [ $model, $method ], $arguments ); } } abstract-main.php000064400000010432147206761040010011 0ustar00container ) { return; } try { $this->container = $this->get_container(); Container_Registry::register( $this->get_name(), $this->container ); if ( ! $this->container ) { return; } if ( ! $this->container->has( Loader::class ) ) { return; } $this->container->get( Loader::class )->load(); } catch ( Exception $e ) { if ( $this->is_development() ) { throw $e; } // Don't crash the entire site, simply don't load. } } /** * Magic getter for retrieving a property from a surface. * * @param string $property The property to retrieve. * * @return mixed The value of the property. * * @throws Exception When the property doesn't exist. */ public function __get( $property ) { if ( \array_key_exists( $property, $this->cached_surfaces ) ) { return $this->cached_surfaces[ $property ]; } $surfaces = $this->get_surfaces(); if ( isset( $surfaces[ $property ] ) ) { $this->cached_surfaces[ $property ] = $this->container->get( $surfaces[ $property ] ); return $this->cached_surfaces[ $property ]; } throw new Exception( \sprintf( 'Property $%s does not exist.', $property ) ); } /** * Checks if the given property exists as a surface. * * @param string $property The property to retrieve. * * @return bool True when property is set. */ public function __isset( $property ) { if ( \array_key_exists( $property, $this->cached_surfaces ) ) { return true; } $surfaces = $this->get_surfaces(); if ( ! isset( $surfaces[ $property ] ) ) { return false; } return $this->container->has( $surfaces[ $property ] ); } /** * Prevents setting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * @param mixed $value The property value. * * @return void * * @throws Forbidden_Property_Mutation_Exception Set is never meant to be called. */ public function __set( $name, $value ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- __set must have a name and value - PHPCS #3715. throw Forbidden_Property_Mutation_Exception::cannot_set_because_property_is_immutable( $name ); } /** * Prevents unsetting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * * @return void * * @throws Forbidden_Property_Mutation_Exception Unset is never meant to be called. */ public function __unset( $name ) { throw Forbidden_Property_Mutation_Exception::cannot_unset_because_property_is_immutable( $name ); } /** * Loads the DI container. * * @return ContainerInterface|null The DI container. * * @throws Exception If something goes wrong generating the DI container. */ abstract protected function get_container(); /** * Gets the name of the plugin. * * @return string The name. */ abstract protected function get_name(); /** * Gets the surfaces of this plugin. * * @return array A mapping of surface name to the responsible class. */ abstract protected function get_surfaces(); /** * Returns whether or not we're in an environment for Yoast development. * * @return bool Whether or not to load in development mode. */ protected function is_development() { try { return WPSEO_Utils::is_development_mode(); } catch ( Exception $exception ) { // E.g. when WordPress and/or WordPress SEO are not loaded. return \defined( 'YOAST_ENVIRONMENT' ) && \YOAST_ENVIRONMENT === 'development'; } } } dependency-injection/container-registry.php000064400000004117147206761040015215 0ustar00get( $id, $invalid_behaviour ); } // phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber /** * Attempts to find a given service ID in all registered containers. * * @param string $id The service ID. * * @return string|null The name of the container if the service was found. */ public static function find( $id ) { foreach ( self::$containers as $name => $container ) { if ( $container->has( $id ) ) { return $name; } } } }