Plugin Directory

Changeset 3752647


Ignore:
Timestamp:
10/18/2026 07:01:52 AM (6 days ago)
Author:
noplanman
Message:

Version 0.7.0

Location:
wp-otp/trunk
Files:
94 added
39 deleted
66 edited

Legend:

Unmodified
Added
Removed
  • wp-otp/trunk

    • Property svn:ignore
      •  

        old new  
        44.gitignore
        55.gitlab-ci.yml
        6 .scrutinizer.yml
        76composer.json
        87composer.lock
        98deploy.sh
         9phpcs.xml
        1010phpcs.xml.dist
  • wp-otp/trunk/admin/class-wp-otp-admin.php

    r2466054 r3752647  
    1212use chillerlan\QRCode\QRCode;
    1313use chillerlan\QRCode\QROptions;
     14use OTPHP\InternalClock;
    1415use OTPHP\TOTP;
    1516use ParagonIE\ConstantTime\Base32;
    … …  
    7071                }
    7172
    72                 check_admin_referer( 'wp_otp_nonce', 'wp_otp_nonce' );
     73                check_admin_referer( 'wp_otp_setup', 'wp_otp_nonce' );
    7374
    7475                $user = get_userdata( $user_id );
    … …  
    7677                $user_meta_data = Wp_Otp_User_Meta::get_instance();
    7778
    78                 // Get the secret.
    7979                $secret = $user_meta_data->get( 'secret', $this->get_random_secret() );
    8080
    81                 $otp = TOTP::create( $secret );
    82                 $otp->setLabel( $user->user_login );
     81                $otp = TOTP::createFromSecret( $secret, new InternalClock() )
     82                        ->withLabel( $user->user_login );
    8383
    8484                $otp_code = sanitize_key( $_POST['wp-otp-code'] ?? '' );
    … …  
    125125         */
    126126        public function admin_init(): void {
    127                 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     127                if ( ! isset( $_GET['wp_otp_nonce'] ) ) {
     128                        return;
     129                }
     130
    128131                if ( 'yes' === ( sanitize_key( $_GET['wp-otp-reconfigure'] ?? '' ) ) ) {
     132                        check_admin_referer( 'wp_otp_reconfigure', 'wp_otp_nonce' );
     133
    129134                        Wp_Otp_User_Meta::clear();
    130135                        wp_safe_redirect( get_edit_profile_url() . '#wp-otp' );
    … …  
    132137                }
    133138
    134                 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    135139                if ( 'yes' === ( sanitize_key( $_GET['wp-otp-new-recovery-codes'] ?? '' ) ) ) {
     140                        check_admin_referer( 'wp_otp_new_recovery_codes', 'wp_otp_nonce' );
     141
    136142                        $otp_recovery_codes = $this->get_random_recovery_codes();
    137143                        Wp_Otp_User_Meta::get_instance()->set_all( [
    … …  
    230236         * @return string
    231237         */
    232         public function get_random_hash( $length = 0 ): string {
     238        public function get_random_hash( int $length = 0 ): string {
    233239                try {
    234240                        $random_hash = Base32::encode( random_bytes( 64 ) );
    235                 } catch ( Throwable $e ) {
     241                } catch ( Throwable ) {
    236242                        $random_hash = Base32::encode( md5( microtime( true ) ) . md5( microtime( true ) ) );
    237243                }
    … …  
    258264                $user_meta_data->set( 'secret', $secret, true );
    259265
    260                 $otp = TOTP::create( $secret );
    261                 $otp->setLabel( $user->user_login );
     266                $otp = TOTP::createFromSecret( $secret, new InternalClock() )
     267                        ->withLabel( $user->user_login );
    262268
    263269                // Issuer isn't allowed to have any colon.
    264                 $otp->setIssuer( str_replace( [ ':', '%3a', '%3A' ], '', get_bloginfo( 'name' ) ) );
     270                $otp = $otp->withIssuer( str_replace( [ ':', '%3a', '%3A' ], '', get_bloginfo( 'name' ) ) );
    265271
    266272                $qr_code_provisioning_uri_default = 'https://api.qrserver.com/v1/create-qr-code/?data={PROVISIONING_URI}&qzone=2';
    … …  
    303309                        ],
    304310                        [
    305                                 'name'           => 'andOTP',
    306                                 'uri'            => 'https://github.com/andOTP/andOTP',
    307                                 'uri_play_store' => 'https://play.google.com/store/apps/details?id=org.shadowice.flocke.andotp',
    308                                 'uri_f_droid'    => 'https://f-droid.org/packages/org.shadowice.flocke.andotp',
    309                                 'uri_logo'       => plugins_url( 'images/andotp.png', __FILE__ ),
    310                         ],
    311                         [
    312                                 'name'        => 'OneTimePass',
    313                                 'uri'         => 'https://github.com/OneTimePass/OneTimePass',
    314                                 'uri_f_droid' => 'https://f-droid.org/en/packages/com.github.onetimepass',
    315                                 'uri_logo'    => plugins_url( 'images/onetimepass.png', __FILE__ ),
     311                                'name'           => 'Authenticator.cc',
     312                                'uri'            => 'https://github.com/Authenticator-Extension/Authenticator',
     313                                'uri_play_store' => 'https://chrome.google.com/webstore/detail/authenticator/bhghoamapcdpbohphigoooaddinpkbai',
     314                                'uri_mozilla'    => 'https://addons.mozilla.org/en-US/firefox/addon/auth-helper?src=external-github',
     315                                'uri_microsoft'  => 'https://microsoftedge.microsoft.com/addons/detail/ocglkepbibnalbgmbachknglpdipeoio',
     316                                'uri_logo'       => plugins_url( 'images/authenticator.png', __FILE__ ),
    316317                        ],
    317318                        [
    … …  
    343344                                'name'     => 'App Store',
    344345                                'uri_logo' => plugins_url( 'images/app-store.png', __FILE__ ),
     346                        ],
     347                        'mozilla'    => [
     348                                'name'     => 'Firefox AddOns',
     349                                'uri_logo' => plugins_url( 'images/mozilla-firefox.png', __FILE__ ),
     350                        ],
     351                        'microsoft'  => [
     352                                'name'     => 'Microsoft Edge AddOns',
     353                                'uri_logo' => plugins_url( 'images/ms-edge.png', __FILE__ ),
    345354                        ],
    346355                ];
    … …  
    388397
    389398                if ( $user_meta_data->get( 'enabled' ) ) {
    390                         $recovery_codes       = array_filter( $user_meta_data->get( 'recovery_codes' ) );
    391                         $recovery_codes_count = count( $recovery_codes );
     399                        $recovery_codes_count = count( array_filter( $user_meta_data->get( 'recovery_codes' ) ?? [] ) );
    392400                        if ( $recovery_codes_count < 3 ) {
    393401                                $this->show_user_notification( [
    394402                                        '<strong>' . esc_html__( 'Important', 'wp-otp' ) . '</strong>',
    395                                         sprintf(
    396                                                 _n( // phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
    397                                                         'You have %d WP-OTP recovery code left. You should generate new ones.',
    398                                                         'You have %d WP-OTP recovery codes left. You should generate new ones.',
    399                                                         $recovery_codes_count,
    400                                                         'wp-otp'
    401                                                 ),
    402                                                 $recovery_codes_count
    403                                         ),
     403                                        // translators: %d is the number of WP-OTP recovery codes left.
     404                                        sprintf( _n(
     405                                                'You have %d WP-OTP recovery code left. You should generate new ones.',
     406                                                'You have %d WP-OTP recovery codes left. You should generate new ones.',
     407                                                $recovery_codes_count,
     408                                                'wp-otp'
     409                                        ), $recovery_codes_count ),
    404410                                        sprintf(
    405411                                                '<a href="%1$s" class="button">%2$s</a>',
    406                                                 esc_url( add_query_arg( 'wp-otp-new-recovery-codes', 'yes', get_edit_profile_url() ) ),
     412                                                esc_url( wp_nonce_url( add_query_arg( 'wp-otp-new-recovery-codes', 'yes', get_edit_profile_url() ), 'wp_otp_new_recovery_codes', 'wp_otp_nonce' ) ),
    407413                                                esc_html_x( 'Regenerate', 'Link to regenerate the WP-OTP recovery codes', 'wp-otp' )
    408414                                        ),
  • wp-otp/trunk/admin/css/wp-otp-admin.css

    r2466054 r3752647  
    22        background-color: #fff;
    33        display: inline-block;
    4         margin: 8px 16px;
     4        margin: 8px;
    55        padding: 4px 8px;
     6        border-radius: 3px;
     7}
     8
     9.wp-otp-app-box > span {
     10        display: block;
     11        font-weight: bold;
     12        margin-bottom: .5em;
     13}
     14
     15.wp-otp-app-box a {
     16        display: inline-block;
    617}
    718
    … …  
    1223}
    1324
    14 .wp-otp-recovery-codes-box del,
    15 .wp-otp-recovery-codes-box span {
    16         display: block;
    17         font-weight: normal;
    18         margin: 4px;
    19 }
    20 
    21 .wp-otp-recovery-codes-box del {
    22         color: #800;
    23 }
    24 
    2525#wp-otp-qr-code {
    2626        background-color: #fff;
    2727        width: 222px;
    2828}
     29
     30code.wp-otp-secret {
     31        display: inline-block;
     32        user-select: all;
     33        white-space: nowrap;
     34}
     35
     36th.wp-otp-info-column {
     37        display: flex;
     38        flex-direction: column;
     39        gap: 1rem;
     40}
  • wp-otp/trunk/admin/partials/wp-otp-profile-display.php

    r2477335 r3752647  
    88 */
    99
     10defined( 'WPINC' ) || exit;
    1011?>
    1112
    … …  
    1415<table class="form-table">
    1516        <tr>
    16                 <th scope="row">
    17                         <?php esc_html_e( 'OTP Secret', 'wp-otp' ); ?>:<br>
    18                         <?php echo esc_html( chunk_split( $secret, 4, ' ' ) ); ?><br><br>
     17                <th scope="row" class="wp-otp-info-column">
     18                        <div>
     19                                <span><?php esc_html_e( 'OTP Secret', 'wp-otp' ); ?>:</span>
     20                                <code class="wp-otp-secret"><?php echo esc_html( chunk_split( $secret, 4, ' ' ) ); ?></code>
     21                        </div>
    1922                        <?php if ( $otp_enabled ) : ?>
    20                                 <?php
    21                                 printf(
    22                                         '<em>%1$s</em><br><a href="%2$s" class="button button-small wp-otp-link-reconfigure">%3$s</a>',
    23                                         esc_html__( 'WP-OTP has been configured successfully.', 'wp-otp' ),
    24                                         esc_url( add_query_arg( 'wp-otp-reconfigure', 'yes' ) ),
    25                                         esc_html_x( 'Reconfigure', 'Link to reset and reconfigure WP-OTP secret', 'wp-otp' )
    26                                 );
    27                                 ?>
    28                                 <br><br>
    29                                 <div class="wp-otp-recovery-codes-box">
    30                                         <?php esc_html_e( 'Recovery codes', 'wp-otp' ); ?>:<br>
     23                                <div>
     24                                        <div>
     25                                                <?php
     26                                                $recovery_codes_count = count( array_filter( $user_meta_data->get( 'recovery_codes' ) ?? [] ) );
     27
     28                                                echo esc_html(
     29                                                        sprintf(
     30                                                                // translators: %d is the number of WP-OTP recovery codes left.
     31                                                                _n( '%s recovery code left', '%s recovery codes left', $recovery_codes_count, 'wp-otp' ),
     32                                                                $recovery_codes_count
     33                                                        )
     34                                                );
     35                                                ?>
     36                                        </div>
    3137                                        <?php
    32                                         foreach ( $user_meta_data->get( 'recovery_codes' ) as $code => $unused ) {
    33                                                 printf( '<%1$s>%2$s</%1$s>', $unused ? 'span' : 'del', $code ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    34                                         }
    3538                                        printf(
    3639                                                '<a href="%1$s" class="button button-small wp-otp-link-new-recovery-codes">%2$s</a>',
    37                                                 esc_url( add_query_arg( 'wp-otp-new-recovery-codes', 'yes' ) ),
     40                                                esc_url( wp_nonce_url( add_query_arg( 'wp-otp-new-recovery-codes', 'yes' ), 'wp_otp_new_recovery_codes', 'wp_otp_nonce' ) ),
    3841                                                esc_html_x( 'Regenerate', 'Link to regenerate the WP-OTP recovery codes', 'wp-otp' )
    3942                                        );
    4043                                        ?>
    4144                                </div>
     45                                <div>
     46                                        <div><?php esc_html_e( 'WP-OTP has been configured successfully.', 'wp-otp' ); ?></div>
     47                                        <?php
     48                                        printf(
     49                                                '<a href="%1$s" class="button button-small wp-otp-link-reconfigure">%2$s</a>',
     50                                                esc_url( wp_nonce_url( add_query_arg( 'wp-otp-reconfigure', 'yes' ), 'wp_otp_reconfigure', 'wp_otp_nonce' ) ),
     51                                                esc_html_x( 'Reconfigure', 'Link to reset and reconfigure WP-OTP secret', 'wp-otp' )
     52                                        );
     53                                        ?>
     54                                </div>
    4255                        <?php else : ?>
    43                                 <em><?php esc_html_e( 'To activate WP-OTP, enter the One Time Password from your authenticator app and save your profile.', 'wp-otp' ); ?></em><br><br>
    44                                 <label for="wp-otp-code"><?php esc_html_e( 'One Time Password', 'wp-otp' ); ?></label><br>
    45                                 <input type="text" class="input" name="wp-otp-code" id="wp-otp-code"/>
     56                                <div>
     57                                        <em><?php esc_html_e( 'To activate WP-OTP, enter the One Time Password from your authenticator app and save your profile.', 'wp-otp' ); ?></em><br><br>
     58                                        <label for="wp-otp-code"><?php esc_html_e( 'One Time Password', 'wp-otp' ); ?></label><br>
     59                                        <input type="text" class="input" name="wp-otp-code" id="wp-otp-code"/>
     60                                </div>
    4661                        <?php endif; ?>
    47                         <?php wp_nonce_field( 'wp_otp_nonce', 'wp_otp_nonce', false ); ?>
     62                        <?php wp_nonce_field( 'wp_otp_setup', 'wp_otp_nonce', false ); ?>
    4863                </th>
    4964                <td>
    … …  
    5772                        <?php foreach ( $otp_apps as $otp_app ) : ?>
    5873                                <span class="wp-otp-app-box">
    59                                         <strong><?php echo esc_html( $otp_app['name'] ); ?></strong><br>
     74                                        <span><?php echo esc_html( $otp_app['name'] ); ?></span>
    6075                                        <a href="<?php echo esc_html( $otp_app['uri'] ); ?>" target="_blank">
    6176                                                <img src="<?php echo esc_url( $otp_app['uri_logo'] ); ?>"
  • wp-otp/trunk/includes/class-wp-otp-user-meta.php

    r2466054 r3752647  
    122122         * @return mixed Requested option value.
    123123         */
    124         public function get( $key = null, $default = null ) {
     124        public function get( $key = null, $default = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.defaultFound
    125125                if ( null !== $key ) {
    126126                        if ( isset( self::$user_meta[ $key ] ) ) {
    … …  
    165165         * @return Wp_Otp_User_Meta Instance of this class.
    166166         */
    167         public function set( string $key, $value, bool $save = false ): Wp_Otp_User_Meta {
    168                 if ( null !== $key ) {
    169                         if ( null !== $value ) {
    170                                 self::$user_meta[ $key ] = $value;
    171                         } else {
    172                                 unset( self::$user_meta[ $key ] );
    173                         }
     167        public function set( string $key, mixed $value, bool $save = false ): Wp_Otp_User_Meta {
     168                if ( null !== $value ) {
     169                        self::$user_meta[ $key ] = $value;
     170                } else {
     171                        unset( self::$user_meta[ $key ] );
    174172                }
    175173
  • wp-otp/trunk/public/class-wp-otp-public.php

    r2466054 r3752647  
    1212namespace Wp_Otp;
    1313
     14use OTPHP\InternalClock;
    1415use OTPHP\TOTP;
    1516use OTPHP\TOTPInterface;
    … …  
    5455                ?>
    5556                <p>
    56                         <label for="wp-otp-code"><?php echo wp_kses_data( $otp_text ); ?></label><br/>
     57                        <label for="wp-otp-code"><?php echo wp_kses_data( $otp_text ); ?></label><br>
    5758                        <?php '' !== $otp_text_sub && print wp_kses_data( sprintf( '<em>%s</em>', $otp_text_sub ) ); ?>
    5859                        <input type="text" class="input" name="wp-otp-code" id="wp-otp-code"/>
    … …  
    8586
    8687                // If this is a valid OTP code, all good!
    87                 if ( $this->verify_otp( $otp, $otp_code ) ) {
     88                if ( $this->verify_otp( $otp, $otp_code, $user->ID ) ) {
    8889                        return $user;
    8990                }
    … …  
    142143
    143144                // First let's check for a valid OTP code input.
    144                 $otp_code = substr( $password, -6 );
    145                 $tmp_pass = substr( $password, 0, -6 );
    146                 if ( wp_check_password( $tmp_pass, $user->user_pass, $user->ID ) && $this->verify_otp( $otp, $otp_code ) ) {
    147                         $password = (string) $tmp_pass;
     145                $otp_code  = substr( $password, -6 );
     146                $real_pass = substr( $password, 0, -6 );
     147                if ( wp_check_password( $real_pass, $user->user_pass, $user->ID ) && $this->verify_otp( $otp, $otp_code, $user->ID ) ) {
     148                        $password = $real_pass;
    148149                        return;
    149150                }
    … …  
    157158                        }
    158159
    159                         $tmp_pass = substr( $password, 0, -strlen( $recovery_code ) );
    160                         if ( wp_check_password( $tmp_pass, $user->user_pass, $user->ID ) ) {
     160                        $real_pass = substr( $password, 0, -strlen( $recovery_code ) );
     161                        if ( wp_check_password( $real_pass, $user->user_pass, $user->ID ) ) {
    161162                                // Unset the recovery code that has just been used.
    162163                                $recovery_codes[ $otp_code ] = false;
    163164                                $user_meta_data->set( 'recovery_codes', $recovery_codes, true );
    164                                 $password = (string) $tmp_pass;
     165                                $password = $real_pass;
    165166                                return;
    166167                        }
    … …  
    179180        private function get_otp_if_enabled( Wp_Otp_User_Meta $user_meta_data ): ?TOTPInterface {
    180181                if ( $user_meta_data->get( 'enabled' ) && null !== $user_meta_data->get( 'secret' ) ) {
    181                         return TOTP::create( $user_meta_data->get( 'secret' ) );
     182                        return TOTP::createFromSecret( $user_meta_data->get( 'secret' ), new InternalClock() );
    182183                }
    183184
    … …  
    186187
    187188        /**
    188          * Verify the OTP code using the passed TOTP object.
     189         * Verify the OTP code using the passed user and TOTP object.
    189190         *
    190191         * @since 0.3.0
    191192         *
    192          * @param TOTPInterface $otp      OTP object.
     193         * @param TOTPInterface $otp      TOTP object.
    193194         * @param string        $otp_code OTP code to be verified.
     195         * @param int           $user_id  User ID.
    194196         *
    195197         * @return bool
    196198         */
    197         private function verify_otp( TOTPInterface $otp, string $otp_code ): bool {
     199        private function verify_otp( TOTPInterface $otp, string $otp_code, int $user_id ): bool {
     200                $otp_codes_used = get_transient( 'wp-otp-codes-used-' . $user_id ) ?: [];
     201
     202                if ( in_array( $otp_code, $otp_codes_used, true ) ) {
     203                        return false;
     204                }
     205
    198206                /**
    199207                 * Filter for the OTP code expiration window.
    … …  
    201209                 * @since 0.1.0
    202210                 *
    203                  * @param string $otp_window
     211                 * @param int $otp_window
    204212                 */
    205213                $otp_window = (int) apply_filters( 'wp_otp_code_expiration_window', 2 );
    206214
    207                 return $otp->verify( $otp_code, null, $otp_window );
     215                $verified = $otp->verify( $otp_code, null, $otp_window );
     216
     217                if ( $verified ) {
     218                        $otp_codes_used[] = $otp_code;
     219                        set_transient( 'wp-otp-codes-used-' . $user_id, $otp_codes_used, $otp->getPeriod() * $otp_window );
     220                }
     221
     222                return $verified;
    208223        }
    209224}
  • wp-otp/trunk/readme.txt

    r2477335 r3752647  
    22Contributors: noplanman
    33Donate link: https://noplanman.ch/donate
    4 Tags: login, 2fa, two factor, otp, totp, one time password, security, recovery, google authenticator
    5 Requires at least: 4.6
    6 Tested up to: 5.6
    7 Stable tag: 0.6.1
    8 Requires PHP: 7.4
     4Tags: 2fa, two factor, otp, one time password, login
     5Requires at least: 6.4
     6Tested up to: 7.1
     7Stable tag: 0.7.0
     8Requires PHP: 8.2
    99Author URI: https://noplanman.ch
    10 Plugin URI: https://git.feneas.org/noplanman/wp-otp
     10Plugin URI: https://gitlab.com/noplanman/wp-otp
    1111License: GPLv2 or later
    1212License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    1616== Description ==
    1717
    18 With WP-OTP you can easily set up 2 Factor Authentication with One Time Passwords for your WordPress login.
    19 This extra layer makes your WordPress site a lot more secure.
     18With WP-OTP you can set up 2 Factor Authentication with One Time Passwords for your WordPress login.
     19This extra layer makes your WordPress site more secure.
    2020
    21 The new stealth mode allows for invisible OTP code entry, making your login screen look like any other, no extra OTP code input field.
     21Stealth mode allows for invisible OTP code entry, making your login screen look like any other, no extra OTP code input field.
     22
     23For a more comprehensive solution, use [Two Factor](https://wordpress.org/plugins/two-factor/) by WordPress instead!
    2224
    2325= Getting started =
    … …  
    2527
    2628It's as easy as scanning the provided QR Code or entering the OTP secret to any OTP generator app.
    27 Then just activate it by entering the generated OTP and voilà, all set up.
     29Then activate it by entering the generated OTP and voilà, all set up.
    2830Now, the login requires an OTP code to succeed.
    2931
    … …  
    3234= Development =
    3335This plugin is completely open source and a work of passion.
    34 If you would like to be part of it and join in, make your way over to the [project page](https://git.feneas.org/noplanman/wp-otp) now.
    35 Also, if you have an idea you would like to see in this plugin or if you've found a bug, please [let me know](https://git.feneas.org/noplanman/wp-otp/issues/new).
     36If you would like to be part of it and join in, make your way over to the [project page](https://gitlab.com/noplanman/wp-otp) now.
    3637
    3738= Configuration =
    … …  
    5152
    5253= Minimum requirements =
    53 WordPress 4.6, PHP 7.4.
     54WordPress 6.4, PHP 8.2.
    5455
    5556= Donate / Support =
    … …  
    5758All [donations](https://noplanman.ch/donate) are much appreciated, thank you 🙏
    5859
    59 [Get professional support for this plugin with a Tidelift subscription](https://tidelift.com/subscription/pkg/wordpress-wp-otp?utm_source=wordpress-wp-otp&utm_medium=referral&utm_campaign=readme)
    60 *Tidelift helps make open source sustainable for maintainers while giving companies assurances about security, maintenance, and licensing for their dependencies.*
    61 
    6260= Security =
    6361
    64 To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
     62To report a security vulnerability, please email me at `security@noplanman.ch`.
    6563
    6664== Installation ==
    6765
    68 You can either use the built in WordPress installer or install the plugin manually.
     66You can either use the built-in WordPress installer or install the plugin manually.
    6967
    7068For an automated installation:
    … …  
    8785
    8886= Can I reset my OTP secret key? =
    89 Yes, just click the `Reconfigure` button on the profile page.
     87Yes, click the `Reconfigure` button on the profile page.
    9088
    9189= Why is there no OTP input field on the login form? =
    … …  
    9492
    9593== Changelog ==
     94
     95= 0.7.0 =
     96* Require at least WP 6.4 and PHP 8.2.
     97* Update OTPHP to 11.5 and use immutable API and explicit PSR clock.
     98* Update chillerlan/php-qrcode integration for v6.
     99* Secured sensitive actions with nonces.
    96100
    97101= 0.6.1 =
    … …  
    154158== Upgrade Notice ==
    155159
     160= 0.7.0 =
     161Minimum requirements are now WP 6.4 and PHP 8.2.
     162
    156163= 0.6.0 =
    157 Minimum requirements are now WP 4.6 and PHP 7.4, supporting PHP 8.0!
     164Minimum requirements are now WP 4.6 and PHP 7.4, supporting PHP 8.0.
    158165
    159166= 0.4.0 =
    160 Minimum requirements are now WP 4.6 and PHP 7.1!
     167Minimum requirements are now WP 4.6 and PHP 7.1.
  • wp-otp/trunk/vendor/autoload.php

    r2477335 r3752647  
    33// autoload.php @generated by Composer
    44
     5if (PHP_VERSION_ID < 50600) {
     6    if (!headers_sent()) {
     7        header('HTTP/1.1 500 Internal Server Error');
     8    }
     9    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
     10    if (!ini_get('display_errors')) {
     11        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
     12            fwrite(STDERR, $err);
     13        } elseif (!headers_sent()) {
     14            echo $err;
     15        }
     16    }
     17    throw new RuntimeException($err);
     18}
     19
    520require_once __DIR__ . '/composer/autoload_real.php';
    621
    7 return ComposerAutoloaderInitc7ff053f14f929092d06e37035c6064c::getLoader();
     22return ComposerAutoloaderInit0cde52f04200d0b097061008ff484a30::getLoader();
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/README.md

    r2466054 r3752647  
    11# chillerlan/php-qrcode
    22
    3 A PHP 7.4+ QR Code library based on the [implementation](https://github.com/kazuhikoarase/qrcode-generator) by [Kazuhiko Arase](https://github.com/kazuhikoarase),
    4 namespaced, cleaned up, improved and other stuff.
     3A PHP QR Code generator based on the [implementation by Kazuhiko Arase](https://github.com/kazuhikoarase/qrcode-generator), namespaced, cleaned up, improved and other stuff. <br>
     4It also features a QR Code reader based on a [PHP port](https://github.com/khanamiryan/php-qrcode-detector-decoder) of the [ZXing library](https://github.com/zxing/zxing).
    55
     6**Attention:** there is now also a javascript port on NPM: [@chillerlan/qrcode](https://www.npmjs.com/package/@chillerlan/qrcode).
     7
     8[![PHP Version Support][php-badge]][php]
    69[![Packagist version][packagist-badge]][packagist]
    7 [![License][license-badge]][license]
    8 [![Travis CI][travis-badge]][travis]
     10[![Continuous Integration][gh-action-badge]][gh-action]
    911[![CodeCov][coverage-badge]][coverage]
    10 [![Scrunitizer CI][scrutinizer-badge]][scrutinizer]
     12[![Codacy][codacy-badge]][codacy]
    1113[![Packagist downloads][downloads-badge]][downloads]
    12 [![PayPal donate][donate-badge]][donate]
     14[![Documentation][readthedocs-badge]][readthedocs]
    1315
    14 [![Continuous Integration][gh-action-badge]][gh-action] [![phpDocs][gh-docs-badge]][gh-docs]
     16[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-qrcode?logo=php&color=8892BF&logoColor=fff
     17[php]: https://www.php.net/supported-versions.php
     18[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?logo=packagist&logoColor=fff
     19[packagist]: https://packagist.org/packages/chillerlan/php-qrcode
     20[gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-qrcode/ci.yml?branch=v6.0.x&logo=github&logoColor=fff
     21[gh-action]: https://github.com/chillerlan/php-qrcode/actions/workflows/ci.yml?query=branch%3Amain
     22[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-qrcode/v6.0.x?logo=codecov&logoColor=fff
     23[coverage]: https://app.codecov.io/gh/chillerlan/php-qrcode/tree/v6.0.x
     24[codacy-badge]: https://img.shields.io/codacy/grade/edccfc4fe5a34b74b1c53ee03f097b8d/v6.0.x?logo=codacy&logoColor=fff
     25[codacy]: https://app.codacy.com/gh/chillerlan/php-qrcode/dashboard?branch=v6.0.x
     26[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode?logo=packagist&logoColor=fff
     27[downloads]: https://packagist.org/packages/chillerlan/php-qrcode/stats
     28[readthedocs-badge]: https://img.shields.io/readthedocs/php-qrcode/v6.0.x?logo=readthedocs&logoColor=fff
     29[readthedocs]: https://php-qrcode.readthedocs.io/en/v6.0.x/
    1530
    16 [packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?style=flat-square
    17 [packagist]: https://packagist.org/packages/chillerlan/php-qrcode
    18 [license-badge]: https://img.shields.io/github/license/chillerlan/php-qrcode.svg?style=flat-square
    19 [license]: https://github.com/chillerlan/php-qrcode/blob/main/LICENSE
    20 [travis-badge]: https://img.shields.io/travis/chillerlan/php-qrcode.svg?style=flat-square
    21 [travis]: https://travis-ci.org/chillerlan/php-qrcode
    22 [coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-qrcode.svg?style=flat-square
    23 [coverage]: https://codecov.io/github/chillerlan/php-qrcode
    24 [scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-qrcode.svg?style=flat-square
    25 [scrutinizer]: https://scrutinizer-ci.com/g/chillerlan/php-qrcode
    26 [downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode.svg?style=flat-square
    27 [downloads]: https://packagist.org/packages/chillerlan/php-qrcode/stats
    28 [donate-badge]: https://img.shields.io/badge/donate-paypal-ff33aa.svg?style=flat-square
    29 [donate]: https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4
    30 [gh-action-badge]: https://github.com/chillerlan/php-qrcode/workflows/Continuous%20Integration/badge.svg
    31 [gh-action]: https://github.com/chillerlan/php-qrcode/actions?query=workflow%3A%22Continuous+Integration%22
    32 [gh-docs-badge]: https://github.com/chillerlan/php-qrcode/workflows/Docs/badge.svg
    33 [gh-docs]: https://github.com/chillerlan/php-qrcode/actions?query=workflow%3ADocs
     31# Overview
    3432
    35 ## Documentation
     33## Features
    3634
    37 See [the wiki](https://github.com/chillerlan/php-qrcode/wiki) for advanced documentation.
    38 An API documentation created with [phpDocumentor](https://www.phpdoc.org/) can be found at https://chillerlan.github.io/php-qrcode/ (WIP).
     35- Creation of [Model 2 QR Codes](https://www.qrcode.com/en/codes/model12.html), [Version 1 to 40](https://www.qrcode.com/en/about/version.html)
     36- [ECC Levels](https://www.qrcode.com/en/about/error_correction.html) L/M/Q/H supported
     37- Mixed mode support (encoding modes can be combined within a QR symbol). Supported modes:
     38  - numeric
     39  - alphanumeric
     40  - 8-bit binary
     41    - [ECI support](https://en.wikipedia.org/wiki/Extended_Channel_Interpretation)
     42  - 13-bit double-byte:
     43    - kanji (Japanese, Shift-JIS)
     44    - hanzi (simplified Chinese, GB2312/GB18030) as [defined in GBT18284-2000](https://www.chinesestandard.net/PDF/English.aspx/GBT18284-2000)
     45- Flexible, easily extensible output modules, built-in support for the following output formats:
     46  - [GdImage](https://www.php.net/manual/book.image) (raster graphics: avif, bmp, gif, jpeg, png, webp)
     47  - [ImageMagick](https://www.php.net/manual/book.imagick) ([multiple supported image formats](https://imagemagick.org/script/formats.php))
     48  - Markup types: SVG, HTML, etc.
     49  - String types: JSON, plain text, etc.
     50  - Encapsulated Postscript (EPS)
     51  - PDF via [FPDF](https://github.com/setasign/fpdf)
     52- QR Code reader (via GD and ImageMagick)
    3953
    40 ### Requirements
    41 - PHP 7.4+
    42   - `ext-mbstring`
    43   - optional:
    44     - `ext-json`, `ext-gd`
    45     - `ext-imagick` with [ImageMagick](https://imagemagick.org) installed
     54
     55## Requirements
     56
     57- PHP 8.2+
     58  - [`ext-mbstring`](https://www.php.net/manual/book.mbstring.php)
     59  - optional:
     60    - [`ext-gd`](https://www.php.net/manual/book.image) for `QRGdImage` based output
     61    - [`ext-imagick`](https://github.com/Imagick/imagick) with [ImageMagick](https://imagemagick.org) installed
     62    - [`ext-fileinfo`](https://www.php.net/manual/book.fileinfo.php) required by `QRImagick` output
    4663    - [`setasign/fpdf`](https://github.com/setasign/fpdf) for the PDF output module
     64    - [`intervention/image`](https://github.com/Intervention/image) for alternative GD/ImageMagick output
    4765
    48 ### Installation
    49 **requires [composer](https://getcomposer.org)**
     66For the QR Code reader, either `ext-gd` or `ext-imagick` is required!
    5067
    51 via terminal: `composer require chillerlan/php-qrcode`
    5268
    53 *composer.json*
     69# Documentation
     70
     71- The user manual is at https://php-qrcode.readthedocs.io/ ([sources](https://github.com/chillerlan/php-qrcode/tree/v6.0.x/docs))
     72- An API documentation created with [phpDocumentor](https://www.phpdoc.org/) can be found at https://chillerlan.github.io/php-qrcode/
     73- The documentation for the `QROptions` container is here: [chillerlan/php-settings-container](https://github.com/chillerlan/php-settings-container#readme)
     74- Benchmark results can be found in the [`benchmark` branch](https://github.com/chillerlan/php-qrcode/tree/benchmark/markdown)
     75
     76**Important: Please use the examples from the branch that matches your installed php-qrcode version (
     77[v4.x](https://github.com/chillerlan/php-qrcode/tree/v4.3.x/examples),
     78[v5.x](https://github.com/chillerlan/php-qrcode/tree/v5.0.x/examples),
     79[v6.x](https://github.com/chillerlan/php-qrcode/tree/v6.0.x/examples),
     80[dev-main](https://github.com/chillerlan/php-qrcode/tree/main/examples)
     81)!**<br/>
     82The `main` (default) branch is the active development for future major versions, and it is most likely incompatible with the latest release versions.
     83
     84## Installation with [composer](https://getcomposer.org)
     85
     86See [the installation guide](https://php-qrcode.readthedocs.io/en/v6.0.x/Usage/Installation.html) for more info!
     87
     88
     89### Terminal
     90
     91```
     92composer require chillerlan/php-qrcode
     93```
     94
     95
     96### composer.json
     97
    5498```json
    5599{
    56100        "require": {
    57                 "php": "^7.4",
    58                 "chillerlan/php-qrcode": "dev-main"
     101                "php": "^8.2",
     102                "chillerlan/php-qrcode": "v6.0.x-dev#<commit_hash>"
    59103        }
    60104}
    61105```
    62106
    63 Note: replace `dev-main` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^3.2` - see [releases](https://github.com/chillerlan/php-qrcode/releases) for valid versions.
    64 For PHP version ...
    65   - 7.4+ use `^4.2`
    66   - 7.2+ use `^3.3`
    67   - 7.0+ use `^2.0` (PHP 7.0 and 7.1 are EOL!)
    68   - 5.6+ use `^1.0` (please let PHP 5 die!)
     107Note: replace `v6.0.x-dev` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^5.0` - see [releases](https://github.com/chillerlan/php-qrcode/releases) for valid versions.
    69108
    70 ### Quickstart
     109
     110## Quickstart
     111
    71112We want to encode this URI for a mobile authenticator into a QRcode image:
     113
    72114```php
    73115$data = 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net';
    … …  
    77119```
    78120
     121Wait, what was that? Please again, slower! See [Advanced usage](https://php-qrcode.readthedocs.io/en/v6.0.x/Usage/Advanced-usage.html) in the manual.
     122Also, have a look [in the examples folder](https://github.com/chillerlan/php-qrcode/tree/v6.0.x/examples) for some more usage examples.
     123
    79124<p align="center">
    80         <img alt="QR codes are awesome!" src="https://raw.githubusercontent.com/chillerlan/php-qrcode/main/examples/example_image.png">
    81         <img alt="QR codes are awesome!" src="https://raw.githubusercontent.com/chillerlan/php-qrcode/main/examples/example_svg.png">
     125        <img alt="QR codes are awesome!" style="width: auto; height: 530px;" src="https://raw.githubusercontent.com/chillerlan/php-qrcode/v6.0.x/.github/images/example.svg">
    82126</p>
    83127
    84 Wait, what was that? Please again, slower! See [Advanced usage](https://github.com/chillerlan/php-qrcode/wiki/Advanced-usage) on the wiki.
    85128
    86 ### Framework Integration
    87 - Drupal [Google Authenticator Login `ga_login`](https://www.drupal.org/project/ga_login)
    88 - WordPress [`wp-two-factor-auth`](https://github.com/sjinks/wp-two-factor-auth)
    89 - WordPress [Simple 2FA `simple-2fa`](https://wordpress.org/plugins/simple-2fa/)
    90 - WoltLab Suite [two-step-verification](http://pluginstore.woltlab.com/file/3007-two-step-verification/) 
    91 - [Cachet](https://github.com/CachetHQ/Cachet)
    92 - [Appwrite](https://github.com/appwrite/appwrite) 
    93 - other uses: [dependents](https://github.com/chillerlan/php-qrcode/network/dependents) / [packages](https://github.com/chillerlan/php-qrcode/network/dependents?dependent_type=PACKAGE)
     129# Disclaimer!
    94130
    95 ### Shameless advertising
    96 Hi, please check out my other projects that are way cooler than qrcodes!
    97 
    98 - [php-oauth-core](https://github.com/chillerlan/php-oauth-core) - an OAuth 1/2 client library along with a bunch of [providers](https://github.com/chillerlan/php-oauth-providers)
    99 - [php-httpinterface](https://github.com/chillerlan/php-httpinterface) - a PSR-7/15/17/18 implemetation
    100 - [php-database](https://github.com/chillerlan/php-database) - a database client & querybuilder for MySQL, Postgres, SQLite, MSSQL, Firebird
    101 
    102 ### Disclaimer!
    103131I don't take responsibility for molten CPUs, misled applications, failed log-ins etc.. Use at your own risk!
    104132
    105 #### Trademark Notice
    106133
    107 The word "QR Code" is registered trademark of *DENSO WAVE INCORPORATED*<br>
    108 http://www.denso-wave.com/qrcode/faqpatent-e.html
     134## License notice
     135
     136- Parts of this code are [ported to PHP](https://github.com/codemasher/php-qrcode-decoder) from the [ZXing project](https://github.com/zxing/zxing) and licensed under the [Apache License, Version 2.0](./NOTICE).
     137- [The documentation](https://github.com/chillerlan/php-qrcode/tree/v6.0.x/docs) is licensed under the [Creative Commons Attribution 4.0 International (CC BY 4.0) License](https://creativecommons.org/licenses/by/4.0/).
     138
     139
     140## Trademark Notice
     141
     142The word "QR Code" is a registered trademark of *DENSO WAVE INCORPORATED*<br>
     143https://www.qrcode.com/en/faq.html#patentH2Title
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/composer.json

    r2466054 r3752647  
    11{
     2        "$schema": "https://getcomposer.org/schema.json",
    23        "name": "chillerlan/php-qrcode",
    3         "description": "A QR code generator. PHP 7.4+",
     4        "description": "A QR Code generator and reader with a user-friendly API. PHP 8.2+",
    45        "homepage": "https://github.com/chillerlan/php-qrcode",
    5         "license": "MIT",
    6         "minimum-stability": "stable",
     6        "license": [
     7                "MIT", "Apache-2.0"
     8        ],
    79        "type": "library",
    810        "keywords": [
    9                 "QR code", "qrcode", "qr", "qrcode-generator", "phpqrcode"
     11                "QR code", "qrcode", "qr", "qrcode-generator", "phpqrcode", "qrcode-reader", "qr-reader"
    1012        ],
    1113        "authors": [
    1214                {
    1315                        "name": "Kazuhiko Arase",
    14                         "homepage": "https://github.com/kazuhikoarase"
     16                        "homepage": "https://github.com/kazuhikoarase/qrcode-generator"
     17                },
     18                {
     19                        "name":"ZXing Authors",
     20                        "homepage": "https://github.com/zxing/zxing"
     21                },
     22                {
     23                        "name": "Ashot Khanamiryan",
     24                        "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder"
    1525                },
    1626                {
    … …  
    2434                }
    2535        ],
     36        "funding": [
     37                {
     38                        "type": "Ko-Fi",
     39                        "url": "https://ko-fi.com/codemasher"
     40                }
     41        ],
     42        "support": {
     43                "docs": "https://php-qrcode.readthedocs.io",
     44                "issues": "https://github.com/chillerlan/php-qrcode/issues",
     45                "source": "https://github.com/chillerlan/php-qrcode"
     46        },
     47        "minimum-stability": "stable",
     48        "prefer-stable": true,
    2649        "require": {
    27                 "php": "^7.4 || ^8.0",
     50                "php": "^8.2",
    2851                "ext-mbstring": "*",
    29                 "chillerlan/php-settings-container": "^2.1"
     52                "chillerlan/php-settings-container": "^3.2.1"
    3053        },
    3154        "require-dev": {
    32                 "phpunit/phpunit": "^9.4",
    33                 "phan/phan": "^3.2.2",
    34                 "setasign/fpdf": "^1.8.2"
     55                "ext-fileinfo": "*",
     56                "chillerlan/php-authenticator": "^5.3",
     57                "intervention/image": "^3.11",
     58                "phan/phan": "^6.0.1",
     59                "phpbench/phpbench": "^1.4",
     60                "phpunit/phpunit": "^11.5",
     61                "phpmd/phpmd": "^2.15",
     62                "phpstan/phpstan": "^2.1.40",
     63                "phpstan/phpstan-deprecation-rules": "^2.0.4",
     64                "setasign/fpdf": "^1.8.6",
     65                "slevomat/coding-standard": "^8.28",
     66                "squizlabs/php_codesniffer": "^4.0"
    3567        },
    3668        "suggest": {
    3769                "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
    38                 "setasign/fpdf": "Required to use the QR FPDF output."
     70                "intervention/image": "More advanced GD and ImageMagick output.",
     71                "setasign/fpdf": "Required to use the QR FPDF output.",
     72                "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code"
    3973        },
    4074        "autoload": {
    4175                "psr-4": {
    42                         "chillerlan\\QRCode\\": "src/"
     76                        "chillerlan\\QRCode\\": "src"
    4377                }
    4478        },
    4579        "autoload-dev": {
    4680                "psr-4": {
    47                         "chillerlan\\QRCodePublic\\": "public/",
    48                         "chillerlan\\QRCodeTest\\": "tests/",
    49                         "chillerlan\\QRCodeExamples\\": "examples/"
     81                        "chillerlan\\QRCodeBenchmark\\": "benchmark",
     82                        "chillerlan\\QRCodeTest\\": "tests"
     83                }
     84        },
     85        "scripts": {
     86                "phan": "@php vendor/bin/phan",
     87                "phpbench":[
     88                        "Composer\\Config::disableProcessTimeout",
     89                        "@php vendor/bin/phpbench run"
     90                ],
     91                "phpcs": "@php vendor/bin/phpcs",
     92                "phpmd": "@php vendor/bin/phpmd src text ./phpmd.xml.dist",
     93                "phpstan": "@php vendor/bin/phpstan",
     94                "phpstan-baseline": "@php vendor/bin/phpstan --generate-baseline",
     95                "phpunit": "@php vendor/bin/phpunit"
     96        },
     97        "config": {
     98                "lock": false,
     99                "sort-packages": true,
     100                "platform-check": true,
     101                "allow-plugins": {
     102                        "dealerdirect/phpcodesniffer-composer-installer": true
    50103                }
    51104        }
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Data/AlphaNum.php

    r2466054 r3752647  
    33 * Class AlphaNum
    44 *
    5  * @filesource   AlphaNum.php
    65 * @created      25.11.2015
    7  * @package      chillerlan\QRCode\Data
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode\Data;
    1413
    15 use chillerlan\QRCode\QRCode;
    16 
    17 use function ord, sprintf;
     14use chillerlan\QRCode\Common\{BitBuffer, Mode};
     15use function ceil, intdiv, preg_match, strpos;
    1816
    1917/**
    … …  
    2321 * ISO/IEC 18004:2000 Section 8.4.3
    2422 */
    25 final class AlphaNum extends QRDataAbstract{
    26 
    27         protected int $datamode = QRCode::DATA_ALPHANUM;
    28 
    29         protected array $lengthBits = [9, 11, 13];
     23final class AlphaNum extends QRDataModeAbstract{
    3024
    3125        /**
    32          * @inheritdoc
     26         * ISO/IEC 18004:2000 Table 5
     27         *
     28         * @var string
    3329         */
    34         protected function write(string $data):void{
     30        private const CHAR_MAP = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
    3531
    36                 for($i = 0; $i + 1 < $this->strlen; $i += 2){
    37                         $this->bitBuffer->put($this->getCharCode($data[$i]) * 45 + $this->getCharCode($data[$i + 1]), 11);
     32        public const DATAMODE = Mode::ALPHANUM;
     33
     34        public function getLengthInBits():int{
     35                return (int)ceil($this->getCharCount() * (11 / 2));
     36        }
     37
     38        public static function validateString(string $string):bool{
     39                return (bool)preg_match('/^[A-Z\d %$*+\-.:\/]+$/', $string);
     40        }
     41
     42        public function write(BitBuffer $bitBuffer, int $versionNumber):static{
     43                $len = $this->getCharCount();
     44
     45                $bitBuffer
     46                        ->put(self::DATAMODE, 4)
     47                        ->put($len, $this->getLengthBits($versionNumber))
     48                ;
     49
     50                // encode 2 characters in 11 bits
     51                for($i = 0; ($i + 1) < $len; $i += 2){
     52                        $bitBuffer->put(
     53                                ($this->ord($this->data[$i]) * 45 + $this->ord($this->data[($i + 1)])),
     54                                11,
     55                        );
    3856                }
    3957
    40                 if($i < $this->strlen){
    41                         $this->bitBuffer->put($this->getCharCode($data[$i]), 6);
     58                // encode a remaining character in 6 bits
     59                if($i < $len){
     60                        $bitBuffer->put($this->ord($this->data[$i]), 6);
    4261                }
    4362
     63                return $this;
    4464        }
    4565
    4666        /**
    47          * get the code for the given character
     67         * @inheritDoc
    4868         *
    49          * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence
     69         * @throws \chillerlan\QRCode\Data\QRCodeDataException
    5070         */
    51         protected function getCharCode(string $chr):int{
     71        public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
     72                $length = $bitBuffer->read($this->getLengthBits($versionNumber));
     73                $result = '';
     74                // Read two characters at a time
     75                while($length > 1){
    5276
    53                 if(!isset($this::CHAR_MAP_ALPHANUM[$chr])){
    54                         throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, ord($chr)));
     77                        if($bitBuffer->available() < 11){
     78                                throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
     79                        }
     80
     81                        $nextTwoCharsBits  = $bitBuffer->read(11);
     82                        $result           .= $this->chr(intdiv($nextTwoCharsBits, 45));
     83                        $result           .= $this->chr($nextTwoCharsBits % 45);
     84                        $length           -= 2;
    5585                }
    5686
    57                 return $this::CHAR_MAP_ALPHANUM[$chr];
     87                if($length === 1){
     88                        // special case: one character left
     89                        if($bitBuffer->available() < 6){
     90                                throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
     91                        }
     92
     93                        $result .= $this->chr($bitBuffer->read(6));
     94                }
     95
     96                return $result;
     97        }
     98
     99        /**
     100         * @throws \chillerlan\QRCode\Data\QRCodeDataException
     101         */
     102        private function ord(string $chr):int{
     103                /** @phan-suppress-next-line PhanParamSuspiciousOrder */
     104                $ord = strpos(self::CHAR_MAP, $chr);
     105
     106                if($ord === false){
     107                        throw new QRCodeDataException('invalid character'); // @codeCoverageIgnore
     108                }
     109
     110                return $ord;
     111        }
     112
     113        /**
     114         * @throws \chillerlan\QRCode\Data\QRCodeDataException
     115         */
     116        private function chr(int $ord):string{
     117
     118                if($ord < 0 || $ord > 44){
     119                        throw new QRCodeDataException('invalid character code'); // @codeCoverageIgnore
     120                }
     121
     122                return self::CHAR_MAP[$ord];
    58123        }
    59124
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Data/Byte.php

    r2466054 r3752647  
    33 * Class Byte
    44 *
    5  * @filesource   Byte.php
    65 * @created      25.11.2015
    7  * @package      chillerlan\QRCode\Data
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode\Data;
    1413
    15 use chillerlan\QRCode\QRCode;
    16 
    17 use function ord;
     14use chillerlan\QRCode\Common\{BitBuffer, Mode};
     15use function chr, ord;
    1816
    1917/**
    20  * Byte mode, ISO-8859-1 or UTF-8
     18 * 8-bit Byte mode, ISO-8859-1 or UTF-8
    2119 *
    2220 * ISO/IEC 18004:2000 Section 8.3.4
    2321 * ISO/IEC 18004:2000 Section 8.4.4
    2422 */
    25 final class Byte extends QRDataAbstract{
     23final class Byte extends QRDataModeAbstract{
    2624
    27         protected int $datamode = QRCode::DATA_BYTE;
     25        public const DATAMODE = Mode::BYTE;
    2826
    29         protected array $lengthBits = [8, 16, 16];
     27        public function getLengthInBits():int{
     28                return ($this->getCharCount() * 8);
     29        }
    3030
    31         /**
    32          * @inheritdoc
    33          */
    34         protected function write(string $data):void{
     31        public static function validateString(string $string):bool{
     32                return $string !== '';
     33        }
     34
     35        public function write(BitBuffer $bitBuffer, int $versionNumber):static{
     36                $len = $this->getCharCount();
     37
     38                $bitBuffer
     39                        ->put(self::DATAMODE, 4)
     40                        ->put($len, $this->getLengthBits($versionNumber))
     41                ;
     42
    3543                $i = 0;
    3644
    37                 while($i < $this->strlen){
    38                         $this->bitBuffer->put(ord($data[$i]), 8);
     45                while($i < $len){
     46                        $bitBuffer->put(ord($this->data[$i]), 8);
    3947                        $i++;
    4048                }
    4149
     50                return $this;
     51        }
     52
     53        /**
     54         * @inheritDoc
     55         *
     56         * @throws \chillerlan\QRCode\Data\QRCodeDataException
     57         */
     58        public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
     59                $length = $bitBuffer->read($this->getLengthBits($versionNumber));
     60
     61                if($bitBuffer->available() < (8 * $length)){
     62                        throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
     63                }
     64
     65                $readBytes = '';
     66
     67                for($i = 0; $i < $length; $i++){
     68                        $readBytes .= chr($bitBuffer->read(8));
     69                }
     70
     71                return $readBytes;
    4272        }
    4373
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Data/Kanji.php

    r2466054 r3752647  
    33 * Class Kanji
    44 *
    5  * @filesource   Kanji.php
    65 * @created      25.11.2015
    7  * @package      chillerlan\QRCode\Data
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode\Data;
    1413
    15 use chillerlan\QRCode\QRCode;
    16 
    17 use function mb_strlen, ord, sprintf, strlen;
     14use chillerlan\QRCode\Common\{BitBuffer, Mode};
     15use Throwable;
     16use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding,
     17        mb_internal_encoding, mb_strlen, ord, sprintf, strlen;
    1818
    1919/**
    20  * Kanji mode: double-byte characters from the Shift JIS character set
     20 * Kanji mode: 13-bit double-byte characters from the Shift-JIS character set
    2121 *
    2222 * ISO/IEC 18004:2000 Section 8.3.5
    2323 * ISO/IEC 18004:2000 Section 8.4.5
     24 *
     25 * @see https://en.wikipedia.org/wiki/Shift_JIS#As_defined_in_JIS_X_0208:1997
     26 * @see http://www.rikai.com/library/kanjitables/kanji_codes.sjis.shtml
     27 * @see https://gist.github.com/codemasher/d07d3e6e9346c08e7a41b8b978784952
    2428 */
    25 final class Kanji extends QRDataAbstract{
    26 
    27         protected int $datamode = QRCode::DATA_KANJI;
    28 
    29         protected array $lengthBits = [8, 10, 12];
     29final class Kanji extends QRDataModeAbstract{
    3030
    3131        /**
    32          * @inheritdoc
     32         * possible values: SJIS, SJIS-2004
     33         *
     34         * SJIS-2004 may produce errors in PHP < 8
     35         *
     36         * @var string
    3337         */
    34         protected function getLength(string $data):int{
    35                 return mb_strlen($data, 'SJIS');
     38        public const ENCODING = 'SJIS';
     39
     40        public const DATAMODE = Mode::KANJI;
     41
     42        protected function getCharCount():int{
     43                return mb_strlen($this->data, self::ENCODING);
     44        }
     45
     46        public function getLengthInBits():int{
     47                return ($this->getCharCount() * 13);
     48        }
     49
     50        public static function convertEncoding(string $string):string{
     51                $detected = mb_detect_encoding($string, [mb_internal_encoding(), 'UTF-8', 'SJIS', 'SJIS-2004'], true);
     52
     53                if($detected === false){
     54                        throw new QRCodeDataException('mb_detect_encoding error');
     55                }
     56
     57                if($detected === self::ENCODING){
     58                        return $string;
     59                }
     60
     61                $string = mb_convert_encoding($string, self::ENCODING, $detected);
     62
     63                if(!is_string($string)){
     64                        throw new QRCodeDataException(sprintf('invalid encoding: %s', $detected));
     65                }
     66
     67                return $string;
    3668        }
    3769
    3870        /**
    39          * @inheritdoc
     71         * checks if a string qualifies as SJIS Kanji
     72         */
     73        public static function validateString(string $string):bool{
     74
     75                try{
     76                        $string = self::convertEncoding($string);
     77                }
     78                catch(Throwable){
     79                        return false;
     80                }
     81
     82                $len = strlen($string);
     83
     84                if($len < 2 || ($len % 2) !== 0){
     85                        return false;
     86                }
     87
     88                for($i = 0; $i < $len; $i += 2){
     89                        $byte1 = ord($string[$i]);
     90                        $byte2 = ord($string[($i + 1)]);
     91
     92                        // byte 1 unused and vendor ranges
     93                        if($byte1 < 0x81 || ($byte1 > 0x84 && $byte1 < 0x88) || ($byte1 > 0x9f && $byte1 < 0xe0) ||  $byte1 > 0xea){
     94                                return false;
     95                        }
     96
     97                        // byte 2 unused ranges
     98                        if($byte2 < 0x40 || $byte2 === 0x7f || $byte2 > 0xfc){
     99                                return false;
     100                        }
     101
     102                }
     103
     104                return true;
     105        }
     106
     107        /**
     108         * @inheritDoc
    40109         *
    41110         * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence
    42111         */
    43         protected function write(string $data):void{
    44                 $len = strlen($data);
     112        public function write(BitBuffer $bitBuffer, int $versionNumber):static{
    45113
    46                 for($i = 0; $i + 1 < $len; $i += 2){
    47                         $c = ((0xff & ord($data[$i])) << 8) | (0xff & ord($data[$i + 1]));
     114                $bitBuffer
     115                        ->put(self::DATAMODE, 4)
     116                        ->put($this->getCharCount(), $this->getLengthBits($versionNumber))
     117                ;
    48118
    49                         if($c >= 0x8140 && $c <= 0x9FFC){
     119                $len = strlen($this->data);
     120
     121                for($i = 0; ($i + 1) < $len; $i += 2){
     122                        $c = (((0xff & ord($this->data[$i])) << 8) | (0xff & ord($this->data[($i + 1)])));
     123
     124                        if($c >= 0x8140 && $c <= 0x9ffc){
    50125                                $c -= 0x8140;
    51126                        }
    52                         elseif($c >= 0xE040 && $c <= 0xEBBF){
    53                                 $c -= 0xC140;
     127                        elseif($c >= 0xe040 && $c <= 0xebbf){
     128                                $c -= 0xc140;
    54129                        }
    55130                        else{
    56                                 throw new QRCodeDataException(sprintf('illegal char at %d [%d]', $i + 1, $c));
     131                                throw new QRCodeDataException(sprintf('illegal char at %d [%d]', ($i + 1), $c));
    57132                        }
    58133
    59                         $this->bitBuffer->put(((($c >> 8) & 0xff) * 0xC0) + ($c & 0xff), 13);
    60 
     134                        $bitBuffer->put((((($c >> 8) & 0xff) * 0xc0) + ($c & 0xff)), 13);
    61135                }
    62136
    63137                if($i < $len){
    64                         throw new QRCodeDataException(sprintf('illegal char at %d', $i + 1));
     138                        throw new QRCodeDataException(sprintf('illegal char at %d', ($i + 1)));
    65139                }
    66140
     141                return $this;
     142        }
     143
     144        /**
     145         * @inheritDoc
     146         *
     147         * @throws \chillerlan\QRCode\Data\QRCodeDataException
     148         */
     149        public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
     150                $length = $bitBuffer->read($this->getLengthBits($versionNumber));
     151
     152                if($bitBuffer->available() < ($length * 13)){
     153                        throw new QRCodeDataException('not enough bits available');  // @codeCoverageIgnore
     154                }
     155
     156                // Each character will require 2 bytes. Read the characters as 2-byte pairs and decode as SJIS afterwards
     157                $buffer = [];
     158                $offset = 0;
     159
     160                while($length > 0){
     161                        // Each 13 bits encodes a 2-byte character
     162                        $twoBytes          = $bitBuffer->read(13);
     163                        $assembledTwoBytes = ((intdiv($twoBytes, 0x0c0) << 8) | ($twoBytes % 0x0c0));
     164
     165                        $assembledTwoBytes += ($assembledTwoBytes < 0x01f00)
     166                                ? 0x08140  // In the 0x8140 to 0x9FFC range
     167                                : 0x0c140; // In the 0xE040 to 0xEBBF range
     168
     169                        $buffer[$offset]       = chr(0xff & ($assembledTwoBytes >> 8));
     170                        $buffer[($offset + 1)] = chr(0xff & $assembledTwoBytes);
     171                        $offset                += 2;
     172                        $length--;
     173                }
     174
     175                $encoded = mb_convert_encoding(implode('', $buffer), mb_internal_encoding(), self::ENCODING);
     176
     177                if($encoded === false){
     178                        throw new QRCodeDataException('mb_convert_encoding() error'); // @codeCoverageIgnore
     179                }
     180
     181                return $encoded;
    67182        }
    68183
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Data/Number.php

    r2466054 r3752647  
    33 * Class Number
    44 *
    5  * @filesource   Number.php
    65 * @created      26.11.2015
    7  * @package      chillerlan\QRCode\Data
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode\Data;
    1413
    15 use chillerlan\QRCode\QRCode;
    16 
    17 use function ord, sprintf, str_split, substr;
     14use chillerlan\QRCode\Common\{BitBuffer, Mode};
     15use function ceil, intdiv, preg_match, substr, unpack;
    1816
    1917/**
    … …  
    2321 * ISO/IEC 18004:2000 Section 8.4.2
    2422 */
    25 final class Number extends QRDataAbstract{
     23final class Number extends QRDataModeAbstract{
    2624
    27         protected int $datamode = QRCode::DATA_NUMBER;
     25        public const DATAMODE = Mode::NUMBER;
    2826
    29         protected array $lengthBits = [10, 12, 14];
     27        public function getLengthInBits():int{
     28                return (int)ceil($this->getCharCount() * (10 / 3));
     29        }
    3030
    31         /**
    32          * @inheritdoc
    33          */
    34         protected function write(string $data):void{
     31        public static function validateString(string $string):bool{
     32                return (bool)preg_match('/^\d+$/', $string);
     33        }
     34
     35        public function write(BitBuffer $bitBuffer, int $versionNumber):static{
     36                $len = $this->getCharCount();
     37
     38                $bitBuffer
     39                        ->put(self::DATAMODE, 4)
     40                        ->put($len, $this->getLengthBits($versionNumber))
     41                ;
     42
    3543                $i = 0;
    3644
    37                 while($i + 2 < $this->strlen){
    38                         $this->bitBuffer->put($this->parseInt(substr($data, $i, 3)), 10);
     45                // encode numeric triplets in 10 bits
     46                while(($i + 2) < $len){
     47                        $bitBuffer->put($this->parseInt(substr($this->data, $i, 3)), 10);
    3948                        $i += 3;
    4049                }
    4150
    42                 if($i < $this->strlen){
     51                if($i < $len){
    4352
    44                         if($this->strlen - $i === 1){
    45                                 $this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 1)), 4);
     53                        // encode 2 remaining numbers in 7 bits
     54                        if(($len - $i) === 2){
     55                                $bitBuffer->put($this->parseInt(substr($this->data, $i, 2)), 7);
    4656                        }
    47                         elseif($this->strlen - $i === 2){
    48                                 $this->bitBuffer->put($this->parseInt(substr($data, $i, $i + 2)), 7);
     57                        // encode one remaining number in 4 bits
     58                        elseif(($len - $i) === 1){
     59                                $bitBuffer->put($this->parseInt(substr($this->data, $i, 1)), 4);
    4960                        }
    5061
    5162                }
    5263
     64                return $this;
    5365        }
    5466
    … …  
    5668         * get the code for the given numeric string
    5769         *
    58          * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence
     70         * @throws \chillerlan\QRCode\Data\QRCodeDataException
    5971         */
    60         protected function parseInt(string $string):int{
     72        private function parseInt(string $string):int{
    6173                $num = 0;
    6274
    63                 foreach(str_split($string) as $chr){
    64                         $c = ord($chr);
     75                $ords = unpack('C*', $string);
    6576
    66                         if(!isset($this::CHAR_MAP_NUMBER[$chr])){
    67                                 throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, $c));
    68                         }
     77                if($ords === false){
     78                        throw new QRCodeDataException('unpack() error');
     79                }
    6980
    70                         $c   = $c - 48; // ord('0')
    71                         $num = $num * 10 + $c;
     81                foreach($ords as $ord){
     82                        $num = ($num * 10 + $ord - 48);
    7283                }
    7384
    … …  
    7586        }
    7687
     88        /**
     89         * @inheritDoc
     90         *
     91         * @throws \chillerlan\QRCode\Data\QRCodeDataException
     92         */
     93        public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
     94                $length = $bitBuffer->read($this->getLengthBits($versionNumber));
     95                $result = '';
     96                // Read three digits at a time
     97                while($length >= 3){
     98                        // Each 10 bits encodes three digits
     99                        if($bitBuffer->available() < 10){
     100                                throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
     101                        }
     102
     103                        $threeDigitsBits = $bitBuffer->read(10);
     104
     105                        if($threeDigitsBits >= 1000){
     106                                throw new QRCodeDataException('error decoding numeric value');
     107                        }
     108
     109                        $result .= intdiv($threeDigitsBits, 100);
     110                        $result .= (intdiv($threeDigitsBits, 10) % 10);
     111                        $result .= ($threeDigitsBits % 10);
     112
     113                        $length -= 3;
     114                }
     115
     116                if($length === 2){
     117                        // Two digits left over to read, encoded in 7 bits
     118                        if($bitBuffer->available() < 7){
     119                                throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
     120                        }
     121
     122                        $twoDigitsBits = $bitBuffer->read(7);
     123
     124                        if($twoDigitsBits >= 100){
     125                                throw new QRCodeDataException('error decoding numeric value');
     126                        }
     127
     128                        $result .= intdiv($twoDigitsBits, 10);
     129                        $result .= ($twoDigitsBits % 10);
     130                }
     131                elseif($length === 1){
     132                        // One digit left over to read
     133                        if($bitBuffer->available() < 4){
     134                                throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore
     135                        }
     136
     137                        $digitBits = $bitBuffer->read(4);
     138
     139                        if($digitBits >= 10){
     140                                throw new QRCodeDataException('error decoding numeric value');
     141                        }
     142
     143                        $result .= $digitBits;
     144                }
     145
     146                return $result;
     147        }
     148
    77149}
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Data/QRCodeDataException.php

    r2331970 r3752647  
    33 * Class QRCodeDataException
    44 *
    5  * @filesource   QRCodeDataException.php
    65 * @created      09.12.2015
    7  * @package      chillerlan\QRCode\Data
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode\Data;
    … …  
    1514use chillerlan\QRCode\QRCodeException;
    1615
    17 class QRCodeDataException extends QRCodeException{}
     16/**
     17 * An exception container
     18 */
     19final class QRCodeDataException extends QRCodeException{
     20
     21}
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Data/QRMatrix.php

    r2466054 r3752647  
    33 * Class QRMatrix
    44 *
    5  * @filesource   QRMatrix.php
    65 * @created      15.11.2017
    7  * @package      chillerlan\QRCode\Data
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2017 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode\Data;
    1413
    15 use chillerlan\QRCode\QRCode;
    16 use Closure;
    17 
    18 use function array_fill, array_key_exists, array_push, array_unshift, count, floor, in_array, max, min, range;
     14use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Version};
     15use chillerlan\QRCode\QRCodeException;
     16use function array_fill, array_map, array_reverse, count, intdiv, in_array;
    1917
    2018/**
    21  * Holds a numerical representation of the final QR Code;
     19 * Holds an array representation of the final QR Code that contains numerical values for later output modifications;
    2220 * maps the ECC coded binary data and applies the mask pattern
    2321 *
    2422 * @see http://www.thonky.com/qr-code-tutorial/format-version-information
     23 *
     24 * @property Version|null     $version
     25 * @property EccLevel|null    $eccLevel
     26 * @property MaskPattern|null $maskPattern
     27 * @property int              $moduleCount
     28 * @property int[][]          $matrix
    2529 */
    26 final class QRMatrix{
    27 
    28         /** @var int */
    29         public const M_NULL       = 0x00;
    30         /** @var int */
    31         public const M_DARKMODULE = 0x02;
    32         /** @var int */
    33         public const M_DATA       = 0x04;
    34         /** @var int */
    35         public const M_FINDER     = 0x06;
    36         /** @var int */
    37         public const M_SEPARATOR  = 0x08;
    38         /** @var int */
    39         public const M_ALIGNMENT  = 0x0a;
    40         /** @var int */
    41         public const M_TIMING     = 0x0c;
    42         /** @var int */
    43         public const M_FORMAT     = 0x0e;
    44         /** @var int */
    45         public const M_VERSION    = 0x10;
    46         /** @var int */
    47         public const M_QUIETZONE  = 0x12;
    48         /** @var int */
    49         public const M_LOGO       = 0x14;
    50         /** @var int */
    51         public const M_FINDER_DOT = 0x16;
    52         /** @var int */
    53         public const M_TEST       = 0xff;
    54 
    55         /**
    56          * ISO/IEC 18004:2000 Annex E, Table E.1 - Row/column coordinates of center module of Alignment Patterns
    57          *
    58          * version -> pattern
     30class QRMatrix{
     31
     32        /*
     33         * special values
     34         */
     35
     36        /** @var int */
     37        public const IS_DARK            = 0b100000000000;
     38        /** @var int */
     39        public const M_NULL             = 0b000000000000;
     40        /** @var int */
     41        public const M_LOGO             = 0b001000000000;
     42        /** @var int */
     43        public const M_LOGO_DARK        = 0b101000000000;
     44
     45        /*
     46         * light values
     47         */
     48
     49        /** @var int */
     50        public const M_DATA             = 0b000000000010;
     51        /** @var int */
     52        public const M_FINDER           = 0b000000000100;
     53        /** @var int */
     54        public const M_SEPARATOR        = 0b000000001000;
     55        /** @var int */
     56        public const M_ALIGNMENT        = 0b000000010000;
     57        /** @var int */
     58        public const M_TIMING           = 0b000000100000;
     59        /** @var int */
     60        public const M_FORMAT           = 0b000001000000;
     61        /** @var int */
     62        public const M_VERSION          = 0b000010000000;
     63        /** @var int */
     64        public const M_QUIETZONE        = 0b000100000000;
     65
     66        /*
     67         * dark values
     68         */
     69
     70        /** @var int */
     71        public const M_DARKMODULE       = 0b100000000001;
     72        /** @var int */
     73        public const M_DATA_DARK        = 0b100000000010;
     74        /** @var int */
     75        public const M_FINDER_DARK      = 0b100000000100;
     76        /** @var int */
     77        public const M_ALIGNMENT_DARK   = 0b100000010000;
     78        /** @var int */
     79        public const M_TIMING_DARK      = 0b100000100000;
     80        /** @var int */
     81        public const M_FORMAT_DARK      = 0b100001000000;
     82        /** @var int */
     83        public const M_VERSION_DARK     = 0b100010000000;
     84        /** @var int */
     85        public const M_FINDER_DOT       = 0b110000000000;
     86
     87        /*
     88         * values used for reversed reflectance
     89         */
     90
     91        /** @var int */
     92        public const M_DARKMODULE_LIGHT = 0b000000000001;
     93        /** @var int */
     94        public const M_FINDER_DOT_LIGHT = 0b010000000000;
     95        /** @var int */
     96        public const M_SEPARATOR_DARK   = 0b100000001000;
     97        /** @var int */
     98        public const M_QUIETZONE_DARK   = 0b100100000000;
     99
     100        /**
     101         * Map of flag => coord
     102         *
     103         * @see \chillerlan\QRCode\Data\QRMatrix::checkNeighbours()
    59104         *
    60105         * @var int[][]
    61106         */
    62         protected const alignmentPattern = [
    63                 1  => [],
    64                 2  => [6, 18],
    65                 3  => [6, 22],
    66                 4  => [6, 26],
    67                 5  => [6, 30],
    68                 6  => [6, 34],
    69                 7  => [6, 22, 38],
    70                 8  => [6, 24, 42],
    71                 9  => [6, 26, 46],
    72                 10 => [6, 28, 50],
    73                 11 => [6, 30, 54],
    74                 12 => [6, 32, 58],
    75                 13 => [6, 34, 62],
    76                 14 => [6, 26, 46, 66],
    77                 15 => [6, 26, 48, 70],
    78                 16 => [6, 26, 50, 74],
    79                 17 => [6, 30, 54, 78],
    80                 18 => [6, 30, 56, 82],
    81                 19 => [6, 30, 58, 86],
    82                 20 => [6, 34, 62, 90],
    83                 21 => [6, 28, 50, 72,  94],
    84                 22 => [6, 26, 50, 74,  98],
    85                 23 => [6, 30, 54, 78, 102],
    86                 24 => [6, 28, 54, 80, 106],
    87                 25 => [6, 32, 58, 84, 110],
    88                 26 => [6, 30, 58, 86, 114],
    89                 27 => [6, 34, 62, 90, 118],
    90                 28 => [6, 26, 50, 74,  98, 122],
    91                 29 => [6, 30, 54, 78, 102, 126],
    92                 30 => [6, 26, 52, 78, 104, 130],
    93                 31 => [6, 30, 56, 82, 108, 134],
    94                 32 => [6, 34, 60, 86, 112, 138],
    95                 33 => [6, 30, 58, 86, 114, 142],
    96                 34 => [6, 34, 62, 90, 118, 146],
    97                 35 => [6, 30, 54, 78, 102, 126, 150],
    98                 36 => [6, 24, 50, 76, 102, 128, 154],
    99                 37 => [6, 28, 54, 80, 106, 132, 158],
    100                 38 => [6, 32, 58, 84, 110, 136, 162],
    101                 39 => [6, 26, 54, 82, 110, 138, 166],
    102                 40 => [6, 30, 58, 86, 114, 142, 170],
     107        protected const neighbours = [
     108                0b00000001 => [-1, -1],
     109                0b00000010 => [ 0, -1],
     110                0b00000100 => [ 1, -1],
     111                0b00001000 => [ 1,  0],
     112                0b00010000 => [ 1,  1],
     113                0b00100000 => [ 0,  1],
     114                0b01000000 => [-1,  1],
     115                0b10000000 => [-1,  0],
    103116        ];
    104117
    105118        /**
    106          * ISO/IEC 18004:2000 Annex D, Table D.1 - Version information bit stream for each version
    107          *
    108          * no version pattern for QR Codes < 7
    109          *
    110          * @var int[]
    111          */
    112         protected const versionPattern = [
    113                 7  => 0b000111110010010100,
    114                 8  => 0b001000010110111100,
    115                 9  => 0b001001101010011001,
    116                 10 => 0b001010010011010011,
    117                 11 => 0b001011101111110110,
    118                 12 => 0b001100011101100010,
    119                 13 => 0b001101100001000111,
    120                 14 => 0b001110011000001101,
    121                 15 => 0b001111100100101000,
    122                 16 => 0b010000101101111000,
    123                 17 => 0b010001010001011101,
    124                 18 => 0b010010101000010111,
    125                 19 => 0b010011010100110010,
    126                 20 => 0b010100100110100110,
    127                 21 => 0b010101011010000011,
    128                 22 => 0b010110100011001001,
    129                 23 => 0b010111011111101100,
    130                 24 => 0b011000111011000100,
    131                 25 => 0b011001000111100001,
    132                 26 => 0b011010111110101011,
    133                 27 => 0b011011000010001110,
    134                 28 => 0b011100110000011010,
    135                 29 => 0b011101001100111111,
    136                 30 => 0b011110110101110101,
    137                 31 => 0b011111001001010000,
    138                 32 => 0b100000100111010101,
    139                 33 => 0b100001011011110000,
    140                 34 => 0b100010100010111010,
    141                 35 => 0b100011011110011111,
    142                 36 => 0b100100101100001011,
    143                 37 => 0b100101010000101110,
    144                 38 => 0b100110101001100100,
    145                 39 => 0b100111010101000001,
    146                 40 => 0b101000110001101001,
    147         ];
    148 
    149         /**
    150          * ISO/IEC 18004:2000 Section 8.9 - Format Information
    151          *
    152          * ECC level -> mask pattern
     119         * the matrix version - always set in QRMatrix, may be null in BitMatrix
     120         */
     121        protected Version|null $version = null;
     122
     123        /**
     124         * the current ECC level - always set in QRMatrix, may be null in BitMatrix
     125         */
     126        protected EccLevel|null $eccLevel = null;
     127
     128        /**
     129         * the mask pattern that was used in the most recent operation, set via:
     130         *
     131         * - QRMatrix::setFormatInfo()
     132         * - QRMatrix::mask()
     133         * - BitMatrix::readFormatInformation()
     134         */
     135        protected MaskPattern|null $maskPattern = null;
     136
     137        /**
     138         * the size (side length) of the matrix, including quiet zone (if created)
     139         */
     140        protected int $moduleCount;
     141
     142        /**
     143         * the actual matrix data array
    153144         *
    154145         * @var int[][]
    155146         */
    156         protected const formatPattern = [
    157                 [ // L
    158                         0b111011111000100,
    159                         0b111001011110011,
    160                         0b111110110101010,
    161                         0b111100010011101,
    162                         0b110011000101111,
    163                         0b110001100011000,
    164                         0b110110001000001,
    165                         0b110100101110110,
    166                 ],
    167                 [ // M
    168                         0b101010000010010,
    169                         0b101000100100101,
    170                         0b101111001111100,
    171                         0b101101101001011,
    172                         0b100010111111001,
    173                         0b100000011001110,
    174                         0b100111110010111,
    175                         0b100101010100000,
    176                 ],
    177                 [ // Q
    178                         0b011010101011111,
    179                         0b011000001101000,
    180                         0b011111100110001,
    181                         0b011101000000110,
    182                         0b010010010110100,
    183                         0b010000110000011,
    184                         0b010111011011010,
    185                         0b010101111101101,
    186                 ],
    187                 [ // H
    188                         0b001011010001001,
    189                         0b001001110111110,
    190                         0b001110011100111,
    191                         0b001100111010000,
    192                         0b000011101100010,
    193                         0b000001001010101,
    194                         0b000110100001100,
    195                         0b000100000111011,
    196                 ],
    197         ];
    198 
    199         /**
    200          * the current QR Code version number
    201          */
    202         protected int $version;
    203 
    204         /**
    205          * the current ECC level
    206          */
    207         protected int $eclevel;
    208 
    209         /**
    210          * the used mask pattern, set via QRMatrix::mapData()
    211          */
    212         protected int $maskPattern = QRCode::MASK_PATTERN_AUTO;
    213 
    214         /**
    215          * the size (side length) of the matrix
    216          */
    217         protected int $moduleCount;
    218 
    219         /**
    220          * the actual matrix data array
    221          *
    222          * @var int[][]
    223          */
    224147        protected array $matrix;
    225148
    226149        /**
    227150         * QRMatrix constructor.
    228          *
    229          * @throws \chillerlan\QRCode\Data\QRCodeDataException
    230          */
    231         public function __construct(int $version, int $eclevel){
    232 
    233                 if(!in_array($version, range(1, 40), true)){
    234                         throw new QRCodeDataException('invalid QR Code version');
    235                 }
    236 
    237                 if(!array_key_exists($eclevel, QRCode::ECC_MODES)){
    238                         throw new QRCodeDataException('invalid ecc level');
    239                 }
    240 
     151         */
     152        public function __construct(Version $version, EccLevel $eccLevel){
    241153                $this->version     = $version;
    242                 $this->eclevel     = $eclevel;
    243                 $this->moduleCount = $this->version * 4 + 17;
    244                 $this->matrix      = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
    245         }
    246 
    247         /**
    248          * shortcut to initialize the matrix
    249          */
    250         public function init(int $maskPattern, bool $test = null):QRMatrix{
     154                $this->eccLevel    = $eccLevel;
     155                $this->moduleCount = $this->version->getDimension();
     156                $this->matrix      = $this->createMatrix($this->moduleCount, $this::M_NULL);
     157        }
     158
     159        /**
     160         * This magic getter serves as an upgrade path for the deprecated property getter methods,
     161         * which will be removed in v7 in favor of asymmetric visibility (PHP 8.4+).
     162         *
     163         * @throws \chillerlan\QRCode\QRCodeException
     164         * @codeCoverageIgnore
     165         */
     166        public function __get(string $name):mixed{
     167
     168                if(in_array($name, ['matrix', 'version', 'eccLevel', 'maskPattern', 'moduleCount'], true)){
     169                        return $this->{$name};
     170                }
     171
     172                throw new QRCodeException('invalid property access');
     173        }
     174
     175        /**
     176         * Creates a 2-dimensional array (square) of the given $size
     177         *
     178         * @return int[][]
     179         */
     180        protected function createMatrix(int $size, int $value):array{
     181                return array_fill(0, $size, array_fill(0, $size, $value));
     182        }
     183
     184        /**
     185         * shortcut to initialize the functional patterns
     186         */
     187        public function initFunctionalPatterns():static{
    251188                return $this
    252189                        ->setFinderPattern()
    … …  
    254191                        ->setAlignmentPattern()
    255192                        ->setTimingPattern()
    256                         ->setVersionNumber($test)
    257                         ->setFormatInfo($maskPattern, $test)
    258193                        ->setDarkModule()
     194                        ->setVersionNumber()
     195                        ->setFormatInfo()
    259196                ;
    260197        }
    261198
    262199        /**
    263          * Returns the data matrix, returns a pure boolean representation if $boolean is set to true
    264          *
    265          * @return int[][]|bool[][]
    266          */
    267         public function matrix(bool $boolean = false):array{
    268 
    269                 if(!$boolean){
    270                         return $this->matrix;
    271                 }
    272 
    273                 $matrix = [];
    274 
    275                 foreach($this->matrix as $y => $row){
    276                         $matrix[$y] = [];
    277 
    278                         foreach($row as $x => $val){
    279                                 $matrix[$y][$x] = ($val >> 8) > 0;
    280                         }
    281                 }
    282 
     200         * Returns the data matrix
     201         *
     202         * @return int[][]
     203         *
     204         * @deprecated 6.0.1 This method will be removed in v7, use the property "QRMatrix::$matrix" instead.
     205         */
     206        public function getMatrix():array{
     207                return $this->matrix;
     208        }
     209
     210        /**
     211         * Returns a boolean representation of the data matrix
     212         *
     213         * @return bool[][]
     214         * @phan-suppress PhanTypeMismatchReturn
     215         */
     216        public function getBooleanMatrix():array{
     217                $matrix = $this->matrix;
     218
     219                foreach($matrix as &$row){
     220                        $row = array_map($this->isDark(...), $row);
     221                }
     222                /** @var bool[][] $matrix (phpstan hates this otherwise) */
    283223                return $matrix;
    284224        }
    … …  
    286226        /**
    287227         * Returns the current version number
    288          */
    289         public function version():int{
     228         *
     229         * @deprecated 6.0.1 This method will be removed in v7, use the property "QRMatrix::$version" instead.
     230         */
     231        public function getVersion():Version|null{
    290232                return $this->version;
    291233        }
    … …  
    293235        /**
    294236         * Returns the current ECC level
    295          */
    296         public function eccLevel():int{
    297                 return $this->eclevel;
     237         *
     238         * @deprecated 6.0.1 This method will be removed in v7, use the property "QRMatrix::$eccLevel" instead.
     239         */
     240        public function getEccLevel():EccLevel|null{
     241                return $this->eccLevel;
    298242        }
    299243
    300244        /**
    301245         * Returns the current mask pattern
    302          */
    303         public function maskPattern():int{
     246         *
     247         * @deprecated 6.0.1 This method will be removed in v7, use the property "QRMatrix::$maskPattern" instead.
     248         */
     249        public function getMaskPattern():MaskPattern|null{
    304250                return $this->maskPattern;
    305251        }
    … …  
    309255         *
    310256         * size = version * 4 + 17 [ + 2 * quietzone size]
    311          */
    312         public function size():int{
     257         *
     258         * @deprecated 6.0.1 This method will be removed in v7, use the property "QRMatrix::$moduleCount" instead.
     259         */
     260        public function getSize():int{
    313261                return $this->moduleCount;
    314262        }
    315263
    316264        /**
    317          * Returns the value of the module at position [$x, $y]
     265         * Returns the value of the module at position [$x, $y] or -1 if the coordinate is outside the matrix
    318266         */
    319267        public function get(int $x, int $y):int{
     268
     269                if(!isset($this->matrix[$y][$x])){
     270                        return -1;
     271                }
     272
    320273                return $this->matrix[$y][$x];
    321274        }
    … …  
    324277         * Sets the $M_TYPE value for the module at position [$x, $y]
    325278         *
    326          *   true  => $M_TYPE << 8
     279         *   true  => $M_TYPE | 0x800
    327280         *   false => $M_TYPE
    328281         */
    329         public function set(int $x, int $y, bool $value, int $M_TYPE):QRMatrix{
    330                 $this->matrix[$y][$x] = $M_TYPE << ($value ? 8 : 0);
    331 
    332                 return $this;
    333         }
    334 
    335         /**
    336          * Checks whether a module is true (dark) or false (light)
    337          *
    338          *   true  => $value >> 8 === $M_TYPE
    339          *            $value >> 8 > 0
    340          *
    341          *   false => $value === $M_TYPE
    342          *            $value >> 8 === 0
     282        public function set(int $x, int $y, bool $value, int $M_TYPE):static{
     283
     284                if(isset($this->matrix[$y][$x])){
     285                        // we don't know whether the input is dark, so we remove the dark bit
     286                        $M_TYPE &= ~$this::IS_DARK;
     287
     288                        if($value === true){
     289                                $M_TYPE |= $this::IS_DARK;
     290                        }
     291
     292                        $this->matrix[$y][$x] = $M_TYPE;
     293                }
     294
     295                return $this;
     296        }
     297
     298        /**
     299         * Fills an area of $width * $height, from the given starting point [$startX, $startY] (top left) with $value for $M_TYPE.
     300         */
     301        public function setArea(int $startX, int $startY, int $width, int $height, bool $value, int $M_TYPE):static{
     302
     303                for($y = $startY; $y < ($startY + $height); $y++){
     304                        for($x = $startX; $x < ($startX + $width); $x++){
     305                                $this->set($x, $y, $value, $M_TYPE);
     306                        }
     307                }
     308
     309                return $this;
     310        }
     311
     312        /**
     313         * Flips the value of the module at ($x, $y)
     314         */
     315        public function flip(int $x, int $y):static{
     316
     317                if(isset($this->matrix[$y][$x])){
     318                        $this->matrix[$y][$x] ^= $this::IS_DARK;
     319                }
     320
     321                return $this;
     322        }
     323
     324        /**
     325         * Checks whether the module at ($x, $y) is of the given $M_TYPE
     326         *
     327         *   true => $value & $M_TYPE === $M_TYPE
     328         *
     329         * Also, returns false if the given coordinates are out of range.
     330         */
     331        public function checkType(int $x, int $y, int $M_TYPE):bool{
     332
     333                if(isset($this->matrix[$y][$x])){
     334                        return ($this->matrix[$y][$x] & $M_TYPE) === $M_TYPE;
     335                }
     336
     337                return false;
     338        }
     339
     340        /**
     341         * Checks whether the module at ($x, $y) is in the given array of $M_TYPES,
     342         * returns true if a match is found, otherwise false.
     343         *
     344         * @param int[] $M_TYPES
     345         */
     346        public function checkTypeIn(int $x, int $y, array $M_TYPES):bool{
     347
     348                foreach($M_TYPES as $type){
     349                        if($this->checkType($x, $y, $type)){
     350                                return true;
     351                        }
     352                }
     353
     354                return false;
     355        }
     356
     357        /**
     358         * Checks whether the module at ($x, $y) is true (dark) or false (light)
     359         *
     360         * Also, returns false if the given coordinates are out of range.
    343361         */
    344362        public function check(int $x, int $y):bool{
    345                 return ($this->matrix[$y][$x] >> 8) > 0;
    346         }
    347 
     363
     364                if(isset($this->matrix[$y][$x])){
     365                        return $this->isDark($this->matrix[$y][$x]);
     366                }
     367
     368                return false;
     369        }
     370
     371        /**
     372         * Checks whether the given $M_TYPE is a dark value
     373         */
     374        public function isDark(int $M_TYPE):bool{
     375                return ($M_TYPE & $this::IS_DARK) === $this::IS_DARK;
     376        }
     377
     378        /**
     379         * Checks the status of the neighbouring modules for the module at ($x, $y) and returns a bitmask with the results.
     380         *
     381         * The 8 flags of the bitmask represent the status of each of the neighbouring fields,
     382         * starting with the lowest bit for top left, going clockwise:
     383         *
     384         *   0 1 2
     385         *   7 # 3
     386         *   6 5 4
     387         */
     388        public function checkNeighbours(int $x, int $y, int|null $M_TYPE = null):int{
     389                $bits = 0;
     390
     391                foreach($this::neighbours as $bit => [$ix, $iy]){
     392                        $ix += $x;
     393                        $iy += $y;
     394
     395                        // check if the field is the same type
     396                        if($M_TYPE !== null && !$this->checkType($ix, $iy, $M_TYPE)){
     397                                continue;
     398                        }
     399
     400                        if($this->checkType($ix, $iy, $this::IS_DARK)){
     401                                $bits |= $bit;
     402                        }
     403                }
     404
     405                return $bits;
     406        }
    348407
    349408        /**
    350409         * Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder
    351          */
    352         public function setDarkModule():QRMatrix{
    353                 $this->set(8, 4 * $this->version + 9, true, $this::M_DARKMODULE);
     410         *
     411         * 4 * version + 9 or moduleCount - 8
     412         */
     413        public function setDarkModule():static{
     414                $this->set(8, ($this->moduleCount - 8), true, $this::M_DARKMODULE);
    354415
    355416                return $this;
    … …  
    361422         * ISO/IEC 18004:2000 Section 7.3.2
    362423         */
    363         public function setFinderPattern():QRMatrix{
     424        public function setFinderPattern():static{
    364425
    365426                $pos = [
    366427                        [0, 0], // top left
    367                         [$this->moduleCount - 7, 0], // bottom left
    368                         [0, $this->moduleCount - 7], // top right
     428                        [($this->moduleCount - 7), 0], // top right
     429                        [0, ($this->moduleCount - 7)], // bottom left
    369430                ];
    370431
    371432                foreach($pos as $c){
    372                         for($y = 0; $y < 7; $y++){
    373                                 for($x = 0; $x < 7; $x++){
    374                                         // outer (dark) 7*7 square
    375                                         if($x === 0 || $x === 6 || $y === 0 || $y === 6){
    376                                                 $this->set($c[0] + $y, $c[1] + $x, true, $this::M_FINDER);
    377                                         }
    378                                         // inner (light) 5*5 square
    379                                         elseif($x === 1 || $x === 5 || $y === 1 || $y === 5){
    380                                                 $this->set($c[0] + $y, $c[1] + $x, false, $this::M_FINDER);
    381                                         }
    382                                         // 3*3 dot
    383                                         else{
    384                                                 $this->set($c[0] + $y, $c[1] + $x, true, $this::M_FINDER_DOT);
    385                                         }
    386                                 }
    387                         }
     433                        $this
     434                                // phpcs:ignore
     435                                ->setArea( $c[0]     ,  $c[1]     , 7, 7, true, $this::M_FINDER)
     436                                ->setArea(($c[0] + 1), ($c[1] + 1), 5, 5, false, $this::M_FINDER)
     437                                ->setArea(($c[0] + 2), ($c[1] + 2), 3, 3, true, $this::M_FINDER_DOT)
     438                        ;
    388439                }
    389440
    … …  
    396447         * ISO/IEC 18004:2000 Section 7.3.3
    397448         */
    398         public function setSeparators():QRMatrix{
     449        public function setSeparators():static{
    399450
    400451                $h = [
    401452                        [7, 0],
    402                         [$this->moduleCount - 8, 0],
    403                         [7, $this->moduleCount - 8],
     453                        [($this->moduleCount - 8), 0],
     454                        [7, ($this->moduleCount - 8)],
    404455                ];
    405456
    406457                $v = [
    407458                        [7, 7],
    408                         [$this->moduleCount - 1, 7],
    409                         [7, $this->moduleCount - 8],
     459                        [($this->moduleCount - 1), 7],
     460                        [7, ($this->moduleCount - 8)],
    410461                ];
    411462
    412463                for($c = 0; $c < 3; $c++){
    413464                        for($i = 0; $i < 8; $i++){
    414                                 $this->set($h[$c][0]     , $h[$c][1] + $i, false, $this::M_SEPARATOR);
    415                                 $this->set($v[$c][0] - $i, $v[$c][1]     , false, $this::M_SEPARATOR);
     465                                // phpcs:ignore
     466                                $this->set( $h[$c][0]      , ($h[$c][1] + $i), false, $this::M_SEPARATOR);
     467                                $this->set(($v[$c][0] - $i),  $v[$c][1]      , false, $this::M_SEPARATOR);
    416468                        }
    417469                }
    … …  
    426478         * ISO/IEC 18004:2000 Section 7.3.5
    427479         */
    428         public function setAlignmentPattern():QRMatrix{
    429 
    430                 foreach($this::alignmentPattern[$this->version] as $y){
    431                         foreach($this::alignmentPattern[$this->version] as $x){
     480        public function setAlignmentPattern():static{
     481                $alignmentPattern = $this->version->getAlignmentPattern();
     482
     483                foreach($alignmentPattern as $y){
     484                        foreach($alignmentPattern as $x){
    432485
    433486                                // skip existing patterns
    … …  
    436489                                }
    437490
    438                                 for($ry = -2; $ry <= 2; $ry++){
    439                                         for($rx = -2; $rx <= 2; $rx++){
    440                                                 $v = ($ry === 0 && $rx === 0) || $ry === 2 || $ry === -2 || $rx === 2 || $rx === -2;
    441 
    442                                                 $this->set($x + $rx, $y + $ry, $v, $this::M_ALIGNMENT);
    443                                         }
    444                                 }
     491                                $this
     492                                        ->setArea(($x - 2), ($y - 2), 5, 5, true, $this::M_ALIGNMENT)
     493                                        ->setArea(($x - 1), ($y - 1), 3, 3, false, $this::M_ALIGNMENT)
     494                                        ->set($x, $y, true, $this::M_ALIGNMENT)
     495                                ;
    445496
    446497                        }
    … …  
    456507         * ISO/IEC 18004:2000 Section 7.3.4
    457508         */
    458         public function setTimingPattern():QRMatrix{
    459 
    460                 foreach(range(8, $this->moduleCount - 8 - 1) as $i){
     509        public function setTimingPattern():static{
     510
     511                for($i = 8; $i < ($this->moduleCount - 8); $i++){
    461512
    462513                        if($this->matrix[6][$i] !== $this::M_NULL || $this->matrix[$i][6] !== $this::M_NULL){
    … …  
    464515                        }
    465516
    466                         $v = $i % 2 === 0;
     517                        $v = ($i % 2) === 0;
    467518
    468519                        $this->set($i, 6, $v, $this::M_TIMING); // h
    … …  
    478529         * ISO/IEC 18004:2000 Section 8.10
    479530         */
    480         public function setVersionNumber(bool $test = null):QRMatrix{
    481                 $bits = $this::versionPattern[$this->version] ?? false;
    482 
    483                 if($bits !== false){
     531        public function setVersionNumber():static{
     532                $bits = $this->version->getVersionPattern();
     533
     534                if($bits !== null){
    484535
    485536                        for($i = 0; $i < 18; $i++){
    486                                 $a = (int)floor($i / 3);
    487                                 $b = $i % 3 + $this->moduleCount - 8 - 3;
    488                                 $v = !$test && (($bits >> $i) & 1) === 1;
     537                                $a = intdiv($i, 3);
     538                                $b = (($i % 3) + ($this->moduleCount - 8 - 3));
     539                                $v = (($bits >> $i) & 1) === 1;
    489540
    490541                                $this->set($b, $a, $v, $this::M_VERSION); // ne
    … …  
    498549
    499550        /**
    500          * Draws the format info along the finder patterns
     551         * Draws the format info along the finder patterns. If no $maskPattern, all format info modules will be set to false.
    501552         *
    502553         * ISO/IEC 18004:2000 Section 8.9
    503554         */
    504         public function setFormatInfo(int $maskPattern, bool $test = null):QRMatrix{
    505                 $bits = $this::formatPattern[QRCode::ECC_MODES[$this->eclevel]][$maskPattern] ?? 0;
     555        public function setFormatInfo(MaskPattern|null $maskPattern = null):static{
     556                $this->maskPattern = $maskPattern;
     557                $bits              = 0; // sets all format fields to false (test mode)
     558
     559                if($this->maskPattern instanceof MaskPattern){
     560                        $bits = $this->eccLevel->getformatPattern($this->maskPattern);
     561                }
    506562
    507563                for($i = 0; $i < 15; $i++){
    508                         $v = !$test && (($bits >> $i) & 1) === 1;
     564                        $v = (($bits >> $i) & 1) === 1;
    509565
    510566                        if($i < 6){
    … …  
    512568                        }
    513569                        elseif($i < 8){
    514                                 $this->set(8, $i + 1, $v, $this::M_FORMAT);
     570                                $this->set(8, ($i + 1), $v, $this::M_FORMAT);
    515571                        }
    516572                        else{
    517                                 $this->set(8, $this->moduleCount - 15 + $i, $v, $this::M_FORMAT);
     573                                $this->set(8, ($this->moduleCount - 15 + $i), $v, $this::M_FORMAT);
    518574                        }
    519575
    520576                        if($i < 8){
    521                                 $this->set($this->moduleCount - $i - 1, 8, $v, $this::M_FORMAT);
     577                                $this->set(($this->moduleCount - $i - 1), 8, $v, $this::M_FORMAT);
    522578                        }
    523579                        elseif($i < 9){
    524                                 $this->set(15 - $i, 8, $v, $this::M_FORMAT);
     580                                $this->set(((15 - $i)), 8, $v, $this::M_FORMAT);
    525581                        }
    526582                        else{
    527                                 $this->set(15 - $i - 1, 8, $v, $this::M_FORMAT);
    528                         }
    529 
    530                 }
    531 
    532                 $this->set(8, $this->moduleCount - 8, !$test, $this::M_FORMAT);
     583                                $this->set((15 - $i - 1), 8, $v, $this::M_FORMAT);
     584                        }
     585
     586                }
    533587
    534588                return $this;
    … …  
    542596         * @throws \chillerlan\QRCode\Data\QRCodeDataException
    543597         */
    544         public function setQuietZone(int $size = null):QRMatrix{
    545 
    546                 if($this->matrix[$this->moduleCount - 1][$this->moduleCount - 1] === $this::M_NULL){
     598        public function setQuietZone(int $quietZoneSize):static{
     599
     600                // early exit if there's nothing to add
     601                if($quietZoneSize < 1){
     602                        return $this;
     603                }
     604
     605                if($this->matrix[($this->moduleCount - 1)][($this->moduleCount - 1)] === $this::M_NULL){
    547606                        throw new QRCodeDataException('use only after writing data');
    548607                }
    549608
    550                 $size = $size !== null
    551                         ? max(0, min($size, floor($this->moduleCount / 2)))
    552                         : 4;
    553 
    554                 for($y = 0; $y < $this->moduleCount; $y++){
    555                         for($i = 0; $i < $size; $i++){
    556                                 array_unshift($this->matrix[$y], $this::M_QUIETZONE);
    557                                 array_push($this->matrix[$y], $this::M_QUIETZONE);
    558                         }
    559                 }
    560 
    561                 $this->moduleCount += ($size * 2);
    562 
    563                 $r = array_fill(0, $this->moduleCount, $this::M_QUIETZONE);
    564 
    565                 for($i = 0; $i < $size; $i++){
    566                         array_unshift($this->matrix, $r);
    567                         array_push($this->matrix, $r);
     609                // create a matrix with the new size
     610                $newSize   = ($this->moduleCount + ($quietZoneSize * 2));
     611                $newMatrix = $this->createMatrix($newSize, $this::M_QUIETZONE);
     612
     613                // copy over the current matrix
     614                foreach($this->matrix as $y => $row){
     615                        foreach($row as $x => $val){
     616                                $newMatrix[($y + $quietZoneSize)][($x + $quietZoneSize)] = $val;
     617                        }
     618                }
     619
     620                // set the new values
     621                $this->moduleCount = $newSize;
     622                $this->matrix      = $newMatrix;
     623
     624                return $this;
     625        }
     626
     627        /**
     628         * Rotates the matrix by 90 degrees clock wise
     629         */
     630        public function rotate90():static{
     631                /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
     632                $this->matrix = array_map((fn(int ...$a):array => array_reverse($a)), ...$this->matrix);
     633
     634                return $this;
     635        }
     636
     637        /**
     638         * Inverts the values of the whole matrix
     639         *
     640         * ISO/IEC 18004:2015 Section 6.2 - Reflectance reversal
     641         */
     642        public function invert():static{
     643
     644                foreach($this->matrix as $y => $row){
     645                        foreach($row as $x => $val){
     646
     647                                // skip null fields
     648                                if($val === $this::M_NULL){
     649                                        continue;
     650                                }
     651
     652                                $this->flip($x, $y);
     653                        }
    568654                }
    569655
    … …  
    573659        /**
    574660         * Clears a space of $width * $height in order to add a logo or text.
    575          *
    576          * Additionally, the logo space can be positioned within the QR Code - respecting the main functional patterns -
    577          * using $startX and $startY. If either of these are null, the logo space will be centered in that direction.
     661         * If no $height is given, the space will be assumed a square of $width.
     662         *
     663         * Additionally, the logo space can be positioned within the QR Code using $startX and $startY.
     664         * If either of these are null, the logo space will be centered in that direction.
    578665         * ECC level "H" (30%) is required.
     666         *
     667         * The coordinates of $startX and $startY do not include the quiet zone:
     668         * [0, 0] is always the top left module of the top left finder pattern, negative values go into the quiet zone top and left.
    579669         *
    580670         * Please note that adding a logo space minimizes the error correction capacity of the QR Code and
    … …  
    589679         * @throws \chillerlan\QRCode\Data\QRCodeDataException
    590680         */
    591         public function setLogoSpace(int $width, int $height, int $startX = null, int $startY = null):QRMatrix{
    592 
    593                 // for logos we operate in ECC H (30%) only
    594                 if($this->eclevel !== QRCode::ECC_H){
     681        public function setLogoSpace(int $width, int|null $height = null, int|null $startX = null, int|null $startY = null):static{
     682                $height ??= $width;
     683
     684                // if width and height happen to be negative or 0 (default value), just return - nothing to do
     685                if($width <= 0 || $height <= 0){
     686                        return $this; // @codeCoverageIgnore
     687                }
     688
     689                // for logos, we operate in ECC H (30%) only
     690                if($this->eccLevel->getLevel() !== EccLevel::H){
    595691                        throw new QRCodeDataException('ECC level "H" required to add logo space');
    596692                }
    597693
    598                 // we need uneven sizes, adjust if needed
    599                 if(($width % 2) === 0){
     694                // $this->moduleCount includes the quiet zone (if created), we need the QR size here
     695                $dimension = $this->version->getDimension();
     696
     697                // throw if the size exceeds the qrcode size
     698                if($width > $dimension || $height > $dimension){
     699                        throw new QRCodeDataException('logo dimensions exceed matrix size');
     700                }
     701
     702                // we need uneven sizes to center the logo space, adjust if needed
     703                if($startX === null && ($width % 2) === 0){
    600704                        $width++;
    601705                }
    602706
    603                 if(($height % 2) === 0){
     707                if($startY === null && ($height % 2) === 0){
    604708                        $height++;
    605709                }
    606710
    607                 // $this->moduleCount includes the quiet zone (if created), we need the QR size here
    608                 $length = $this->version * 4 + 17;
    609 
    610711                // throw if the logo space exceeds the maximum error correction capacity
    611                 if($width * $height > floor($length * $length * 0.2)){
     712                if(($width * $height) > (int)($dimension * $dimension * 0.25)){
    612713                        throw new QRCodeDataException('logo space exceeds the maximum error correction capacity');
    613714                }
    614715
    615                 // quiet zone size
    616                 $qz    = ($this->moduleCount - $length) / 2;
    617                 // skip quiet zone and the first 9 rows/columns (finder-, mode-, version- and timing patterns)
    618                 $start = $qz + 9;
    619                 // skip quiet zone
    620                 $end   = $this->moduleCount - $qz;
     716                $quietzone = (($this->moduleCount - $dimension) / 2);
     717                $end       = ($this->moduleCount - $quietzone);
    621718
    622719                // determine start coordinates
    623                 $startX = ($startX !== null ? $startX : ($length - $width) / 2) + $qz;
    624                 $startY = ($startY !== null ? $startY : ($length - $height) / 2) + $qz;
     720                $startX ??= (($dimension - $width) / 2);
     721                $startY ??= (($dimension - $height) / 2);
     722                $endX     = ($quietzone + $startX + $width);
     723                $endY     = ($quietzone + $startY + $height);
    625724
    626725                // clear the space
     726                for($y = ($quietzone + $startY); $y < $endY; $y++){
     727                        for($x = ($quietzone + $startX); $x < $endX; $x++){
     728                                // out of bounds, skip
     729                                if($x < $quietzone || $y < $quietzone ||$x >= $end || $y >= $end){
     730                                        continue;
     731                                }
     732
     733                                $this->set($x, $y, false, $this::M_LOGO);
     734                        }
     735                }
     736
     737                return $this;
     738        }
     739
     740        /**
     741         * Maps the interleaved binary $data on the matrix
     742         */
     743        public function writeCodewords(BitBuffer $bitBuffer):static{
     744                $data      = (new ReedSolomonEncoder($this->version, $this->eccLevel))->interleaveEcBytes($bitBuffer);
     745                $byteCount = count($data);
     746                $iByte     = 0;
     747                $iBit      = 7;
     748                $direction = true;
     749
     750                for($i = ($this->moduleCount - 1); $i > 0; $i -= 2){
     751
     752                        // skip vertical alignment pattern
     753                        if($i === 6){
     754                                $i--;
     755                        }
     756
     757                        for($count = 0; $count < $this->moduleCount; $count++){
     758                                $y = $count;
     759
     760                                if($direction){
     761                                        $y = ($this->moduleCount - 1 - $count);
     762                                }
     763
     764                                for($col = 0; $col < 2; $col++){
     765                                        $x = ($i - $col);
     766
     767                                        // skip functional patterns
     768                                        if($this->matrix[$y][$x] !== $this::M_NULL){
     769                                                continue;
     770                                        }
     771
     772                                        $this->matrix[$y][$x] = $this::M_DATA;
     773
     774                                        if($iByte < $byteCount && (($data[$iByte] >> $iBit--) & 1) === 1){
     775                                                $this->matrix[$y][$x] |= $this::IS_DARK;
     776                                        }
     777
     778                                        if($iBit === -1){
     779                                                $iByte++;
     780                                                $iBit = 7;
     781                                        }
     782                                }
     783                        }
     784
     785                        $direction = !$direction; // switch directions
     786                }
     787
     788                return $this;
     789        }
     790
     791        /**
     792         * Applies/reverses the mask pattern
     793         *
     794         * ISO/IEC 18004:2000 Section 8.8.1
     795         */
     796        public function mask(MaskPattern $maskPattern):static{
     797                $this->maskPattern = $maskPattern;
     798                $mask              = $this->maskPattern->getMask();
     799
    627800                foreach($this->matrix as $y => $row){
    628801                        foreach($row as $x => $val){
    629                                 // out of bounds, skip
    630                                 if($x < $start || $y < $start ||$x >= $end || $y >= $end){
    631                                         continue;
     802                                // skip non-data modules
     803                                if(($val & $this::M_DATA) === $this::M_DATA && $mask($x, $y)){
     804                                        $this->flip($x, $y);
    632805                                }
    633                                 // a match
    634                                 if($x >= $startX && $x < ($startX + $width) && $y >= $startY && $y < ($startY + $height)){
    635                                         $this->set($x, $y, false, $this::M_LOGO);
    636                                 }
    637                         }
    638                 }
    639 
    640                 return $this;
    641         }
    642 
    643         /**
    644          * Maps the binary $data array from QRDataInterface::maskECC() on the matrix,
    645          * masking the data using $maskPattern (ISO/IEC 18004:2000 Section 8.8)
    646          *
    647          * @see \chillerlan\QRCode\Data\QRDataAbstract::maskECC()
    648          *
    649          * @param int[] $data
    650          * @param int   $maskPattern
    651          *
    652          * @return \chillerlan\QRCode\Data\QRMatrix
    653          */
    654         public function mapData(array $data, int $maskPattern):QRMatrix{
    655                 $this->maskPattern = $maskPattern;
    656                 $byteCount         = count($data);
    657                 $y                 = $this->moduleCount - 1;
    658                 $inc               = -1;
    659                 $byteIndex         = 0;
    660                 $bitIndex          = 7;
    661                 $mask              = $this->getMask($this->maskPattern);
    662 
    663                 for($i = $y; $i > 0; $i -= 2){
    664 
    665                         if($i === 6){
    666                                 $i--;
    667                         }
    668 
    669                         while(true){
    670                                 for($c = 0; $c < 2; $c++){
    671                                         $x = $i - $c;
    672 
    673                                         if($this->matrix[$y][$x] === $this::M_NULL){
    674                                                 $v = false;
    675 
    676                                                 if($byteIndex < $byteCount){
    677                                                         $v = (($data[$byteIndex] >> $bitIndex) & 1) === 1;
    678                                                 }
    679 
    680                                                 if($mask($x, $y) === 0){
    681                                                         $v = !$v;
    682                                                 }
    683 
    684                                                 $this->matrix[$y][$x] = $this::M_DATA << ($v ? 8 : 0);
    685                                                 $bitIndex--;
    686 
    687                                                 if($bitIndex === -1){
    688                                                         $byteIndex++;
    689                                                         $bitIndex = 7;
    690                                                 }
    691 
    692                                         }
    693                                 }
    694 
    695                                 $y += $inc;
    696 
    697                                 if($y < 0 || $this->moduleCount <= $y){
    698                                         $y   -=  $inc;
    699                                         $inc  = -$inc;
    700 
    701                                         break;
    702                                 }
    703 
    704                         }
    705                 }
    706 
    707                 return $this;
    708         }
    709 
    710         /**
    711          * ISO/IEC 18004:2000 Section 8.8.1
    712          *
    713          * Note that some versions of the QR code standard have had errors in the section about mask patterns.
    714          * The information below has been corrected. (https://www.thonky.com/qr-code-tutorial/mask-patterns)
    715          *
    716          * @see \chillerlan\QRCode\QRMatrix::mapData()
    717          *
    718          * @internal
    719          *
    720          * @throws \chillerlan\QRCode\Data\QRCodeDataException
    721          */
    722         protected function getMask(int $maskPattern):Closure{
    723 
    724                 if((0b111 & $maskPattern) !== $maskPattern){
    725                         throw new QRCodeDataException('invalid mask pattern'); // @codeCoverageIgnore
    726                 }
    727 
    728                 return [
    729                         0b000 => fn($x, $y):int => ($x + $y) % 2,
    730                         0b001 => fn($x, $y):int => $y % 2,
    731                         0b010 => fn($x, $y):int => $x % 3,
    732                         0b011 => fn($x, $y):int => ($x + $y) % 3,
    733                         0b100 => fn($x, $y):int => ((int)($y / 2) + (int)($x / 3)) % 2,
    734                         0b101 => fn($x, $y):int => (($x * $y) % 2) + (($x * $y) % 3),
    735                         0b110 => fn($x, $y):int => ((($x * $y) % 2) + (($x * $y) % 3)) % 2,
    736                         0b111 => fn($x, $y):int => ((($x * $y) % 3) + (($x + $y) % 2)) % 2,
    737                 ][$maskPattern];
     806                        }
     807                }
     808
     809                return $this;
    738810        }
    739811
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Output/QRCodeOutputException.php

    r2331970 r3752647  
    33 * Class QRCodeOutputException
    44 *
    5  * @filesource   QRCodeOutputException.php
    65 * @created      09.12.2015
    7  * @package      chillerlan\QRCode\Output
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode\Output;
    … …  
    1514use chillerlan\QRCode\QRCodeException;
    1615
    17 class QRCodeOutputException extends QRCodeException{}
     16/**
     17 * An exception container
     18 */
     19final class QRCodeOutputException extends QRCodeException{
     20
     21}
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Output/QRFpdf.php

    r2466054 r3752647  
    33 * Class QRFpdf
    44 *
    5  * https://github.com/chillerlan/php-qrcode/pull/49
     5 * @created      03.06.2020
     6 * @author       Maximilian Kresse
     7 * @license      MIT
    68 *
    7  * @filesource   QRFpdf.php
    8  * @created      03.06.2020
    9  * @package      chillerlan\QRCode\Output
    10  * @author       Maximilian Kresse
    11  *
    12  * @license      MIT
     9 * @see https://github.com/chillerlan/php-qrcode/pull/49
    1310 */
     11declare(strict_types=1);
    1412
    1513namespace chillerlan\QRCode\Output;
    1614
     15use chillerlan\QRCode\QROptions;
    1716use chillerlan\QRCode\Data\QRMatrix;
    18 use chillerlan\QRCode\QRCodeException;
    1917use chillerlan\Settings\SettingsContainerInterface;
    2018use FPDF;
    2119
    22 use function array_values, class_exists, count, is_array;
     20use function class_exists;
    2321
    2422/**
    … …  
    2927 */
    3028class QRFpdf extends QROutputAbstract{
     29        use RGBArrayModuleValueTrait;
    3130
    32         public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
     31        final public const MIME_TYPE = 'application/pdf';
     32
     33        /** @var int[]|null  */
     34        protected array|null $prevColor = null;
     35        protected FPDF       $fpdf;
     36
     37        /**
     38         * QRFpdf constructor.
     39         *
     40         * @throws \chillerlan\QRCode\Output\QRCodeOutputException
     41         */
     42        public function __construct(SettingsContainerInterface|QROptions|iterable $options, QRMatrix $matrix){
    3343
    3444                if(!class_exists(FPDF::class)){
    3545                        // @codeCoverageIgnoreStart
    36                         throw new QRCodeException(
    37                                 'The QRFpdf output requires FPDF as dependency but the class "\FPDF" couldn\'t be found.'
     46                        throw new QRCodeOutputException(
     47                                'The QRFpdf output requires FPDF (https://github.com/Setasign/FPDF)'.
     48                                // phpcs:ignore
     49                                ' as dependency but the class "\\FPDF" could not be found.'
    3850                        );
    3951                        // @codeCoverageIgnoreEnd
    … …  
    4456
    4557        /**
    46          * @inheritDoc
     58         * Initializes an FPDF instance
    4759         */
    48         protected function setModuleValues():void{
     60        protected function initFPDF():FPDF{
     61                $fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, $this->getOutputDimensions());
     62                $fpdf->AddPage();
    4963
    50                 foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
    51                         $v = $this->options->moduleValues[$M_TYPE] ?? null;
     64                return $fpdf;
     65        }
    5266
    53                         if(!is_array($v) || count($v) < 3){
    54                                 $this->moduleValues[$M_TYPE] = $defaultValue
    55                                         ? [0, 0, 0]
    56                                         : [255, 255, 255];
    57                         }
    58                         else{
    59                                 $this->moduleValues[$M_TYPE] = array_values($v);
    60                         }
     67        public function dump(string|null $file = null, FPDF|null $fpdf = null):string|FPDF{
     68                $this->fpdf = ($fpdf ?? $this->initFPDF());
    6169
     70                if($this::moduleValueIsValid($this->options->bgColor)){
     71                        $bgColor          = $this->prepareModuleValue($this->options->bgColor);
     72                        [$width, $height] = $this->getOutputDimensions();
     73                        /** @phan-suppress-next-line PhanParamTooFewUnpack */
     74                        $this->fpdf->SetFillColor(...$bgColor);
     75                        $this->fpdf->Rect(0, 0, $width, $height, 'F');
    6276                }
    6377
    64         }
     78                $this->prevColor = null;
    6579
    66         /**
    67          * @inheritDoc
    68          *
    69          * @return string|\FPDF
    70          */
    71         public function dump(string $file = null){
    72                 $file ??= $this->options->cachefile;
    73 
    74                 $fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, [$this->length, $this->length]);
    75                 $fpdf->AddPage();
    76 
    77                 $prevColor = null;
    78 
    79                 foreach($this->matrix->matrix() as $y => $row){
    80 
     80                foreach($this->matrix->getMatrix() as $y => $row){
    8181                        foreach($row as $x => $M_TYPE){
    82                                 /** @var int $M_TYPE */
    83                                 $color = $this->moduleValues[$M_TYPE];
    84 
    85                                 if($prevColor === null || $prevColor !== $color){
    86                                         $fpdf->SetFillColor(...$color);
    87                                         $prevColor = $color;
    88                                 }
    89 
    90                                 $fpdf->Rect($x * $this->scale, $y * $this->scale, 1 * $this->scale, 1 * $this->scale, 'F');
     82                                $this->module($x, $y, $M_TYPE);
    9183                        }
    92 
    9384                }
    9485
    9586                if($this->options->returnResource){
    96                         return $fpdf;
     87                        return $this->fpdf;
    9788                }
    9889
    99                 $pdfData = $fpdf->Output('S');
     90                $pdfData = $this->fpdf->Output('S');
    10091
    101                 if($file !== null){
    102                         $this->saveToFile($pdfData, $file);
    103                 }
     92                $this->saveToFile($pdfData, $file);
    10493
    105                 if($this->options->imageBase64){
    106                         $pdfData = sprintf('data:application/pdf;base64,%s', base64_encode($pdfData));
     94                if($this->options->outputBase64){
     95                        $pdfData = $this->toBase64DataURI($pdfData);
    10796                }
    10897
    … …  
    11099        }
    111100
     101        /**
     102         * Renders a single module
     103         */
     104        protected function module(int $x, int $y, int $M_TYPE):void{
     105
     106                if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
     107                        return;
     108                }
     109
     110                $color = $this->getModuleValue($M_TYPE);
     111
     112                if($color !== null && $color !== $this->prevColor){
     113                        /** @phan-suppress-next-line PhanParamTooFewUnpack */
     114                        $this->fpdf->SetFillColor(...$color);
     115                        $this->prevColor = $color;
     116                }
     117
     118                $this->fpdf->Rect(($x * $this->scale), ($y * $this->scale), $this->scale, $this->scale, 'F');
     119        }
     120
    112121}
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Output/QRImagick.php

    r2466054 r3752647  
    33 * Class QRImagick
    44 *
    5  * @filesource   QRImagick.php
    65 * @created      04.07.2018
    7  * @package      chillerlan\QRCode\Output
    86 * @author       smiley <smiley@chillerlan.net>
    97 * @copyright    2018 smiley
    … …  
    1210 * @noinspection PhpComposerExtensionStubsInspection
    1311 */
     12declare(strict_types=1);
    1413
    1514namespace chillerlan\QRCode\Output;
    1615
     16use chillerlan\QRCode\QROptions;
    1717use chillerlan\QRCode\Data\QRMatrix;
    18 use chillerlan\QRCode\QRCodeException;
    1918use chillerlan\Settings\SettingsContainerInterface;
    2019use Imagick, ImagickDraw, ImagickPixel;
    21 
    22 use function extension_loaded, is_string;
     20use function extension_loaded, in_array, is_string, max, min, preg_match, sprintf, strlen;
    2321
    2422/**
    2523 * ImageMagick output module (requires ext-imagick)
    2624 *
    27  * @see http://php.net/manual/book.imagick.php
    28  * @see http://phpimagick.com
     25 * @see https://php.net/manual/book.imagick.php
     26 * @see https://phpimagick.com
    2927 */
    3028class QRImagick extends QROutputAbstract{
    3129
     30        /**
     31         * The main image instance
     32         */
    3233        protected Imagick $imagick;
    3334
    3435        /**
    35          * @inheritDoc
    36          */
    37         public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
    38 
    39                 if(!extension_loaded('imagick')){
    40                         throw new QRCodeException('ext-imagick not loaded'); // @codeCoverageIgnore
    41                 }
    42 
    43                 parent::__construct($options, $matrix);
    44         }
    45 
    46         /**
    47          * @inheritDoc
    48          */
    49         protected function setModuleValues():void{
    50 
    51                 foreach($this::DEFAULT_MODULE_VALUES as $type => $defaultValue){
    52                         $v = $this->options->moduleValues[$type] ?? null;
    53 
    54                         if(!is_string($v)){
    55                                 $this->moduleValues[$type] = $defaultValue
    56                                         ? new ImagickPixel($this->options->markupDark)
    57                                         : new ImagickPixel($this->options->markupLight);
    58                         }
    59                         else{
    60                                 $this->moduleValues[$type] = new ImagickPixel($v);
    61                         }
    62                 }
    63         }
     36         * The main draw instance
     37         */
     38        protected ImagickDraw $imagickDraw;
     39
     40        /**
     41         * The allocated background color
     42         */
     43        protected ImagickPixel $backgroundColor;
    6444
    6545        /**
    6646         * @inheritDoc
    6747         *
    68          * @return string|\Imagick
    69          */
    70         public function dump(string $file = null){
    71                 $file ??= $this->options->cachefile;
    72                 $this->imagick = new Imagick;
    73 
    74                 $this->imagick->newImage(
    75                         $this->length,
    76                         $this->length,
    77                         new ImagickPixel($this->options->imagickBG ?? 'transparent'),
    78                         $this->options->imagickFormat
    79                 );
     48         * @throws \chillerlan\QRCode\Output\QRCodeOutputException
     49         */
     50        public function __construct(SettingsContainerInterface|QROptions|iterable $options, QRMatrix $matrix){
     51
     52                foreach(['fileinfo', 'imagick'] as $ext){
     53                        if(!extension_loaded($ext)){
     54                                throw new QRCodeOutputException(sprintf('ext-%s not loaded', $ext)); // @codeCoverageIgnore
     55                        }
     56                }
     57
     58                parent::__construct($options, $matrix);
     59        }
     60
     61        /**
     62         * note: we're not necessarily validating the several values, just checking the general syntax
     63         *
     64         * @see https://www.php.net/manual/imagickpixel.construct.php
     65         * @inheritDoc
     66         */
     67        public static function moduleValueIsValid(mixed $value):bool{
     68
     69                if(!is_string($value)){
     70                        return false;
     71                }
     72
     73                $value = trim($value);
     74
     75                // hex notation
     76                // #rgb(a)
     77                // #rrggbb(aa)
     78                // #rrrrggggbbbb(aaaa)
     79                // ...
     80                if(preg_match('/^#[a-f\d]+$/i', $value) && in_array((strlen($value) - 1), [3, 4, 6, 8, 9, 12, 16, 24, 32], true)){
     81                        return true;
     82                }
     83
     84                // css (-like) func(...values)
     85                if(preg_match('#^(graya?|hs(b|la?)|rgba?)\([\d .,%]+\)$#i', $value)){
     86                        return true;
     87                }
     88
     89                // predefined css color
     90                if(preg_match('/^[a-z]+$/i', $value)){
     91                        return true;
     92                }
     93
     94                return false;
     95        }
     96
     97        protected function prepareModuleValue(mixed $value):ImagickPixel{
     98                return new ImagickPixel($value);
     99        }
     100
     101        protected function getDefaultModuleValue(bool $isDark):ImagickPixel{
     102                return $this->prepareModuleValue(($isDark) ? '#000' : '#fff');
     103        }
     104
     105        public function dump(string|null $file = null):string|Imagick{
     106                $this->setBgColor();
     107
     108                $this->imagick = $this->createImage();
    80109
    81110                $this->drawImage();
     111                // set transparency color after all operations
     112                $this->setTransparencyColor();
    82113
    83114                if($this->options->returnResource){
    … …  
    87118                $imageData = $this->imagick->getImageBlob();
    88119
    89                 if($file !== null){
    90                         $this->saveToFile($imageData, $file);
     120                $this->imagick->destroy();
     121
     122                $this->saveToFile($imageData, $file);
     123
     124                if($this->options->outputBase64){
     125                        $imageData = $this->toBase64DataURI($imageData);
    91126                }
    92127
    … …  
    95130
    96131        /**
     132         * Sets the background color
     133         */
     134        protected function setBgColor():void{
     135
     136                if($this::moduleValueIsValid($this->options->bgColor)){
     137                        $this->backgroundColor = $this->prepareModuleValue($this->options->bgColor);
     138
     139                        return;
     140                }
     141
     142                $this->backgroundColor = $this->prepareModuleValue('white');
     143        }
     144
     145        /**
     146         * Creates a new Imagick instance
     147         */
     148        protected function createImage():Imagick{
     149                $imagick          = new Imagick;
     150                [$width, $height] = $this->getOutputDimensions();
     151
     152                $imagick->newImage($width, $height, $this->backgroundColor, $this->options->imagickFormat);
     153
     154                if($this->options->quality > -1){
     155                        $imagick->setImageCompressionQuality(max(0, min(100, $this->options->quality)));
     156                }
     157
     158                return $imagick;
     159        }
     160
     161        /**
     162         * Sets the transparency color
     163         */
     164        protected function setTransparencyColor():void{
     165
     166                if(!$this->options->imageTransparent){
     167                        return;
     168                }
     169
     170                $transparencyColor = $this->backgroundColor;
     171
     172                if($this::moduleValueIsValid($this->options->transparencyColor)){
     173                        $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor);
     174                }
     175
     176                $this->imagick->transparentPaintImage($transparencyColor, 0.0, 10, false);
     177        }
     178
     179        /**
    97180         * Creates the QR image via ImagickDraw
    98181         */
    99182        protected function drawImage():void{
    100                 $draw = new ImagickDraw;
    101 
    102                 foreach($this->matrix->matrix() as $y => $row){
     183                $this->imagickDraw = new ImagickDraw;
     184                $this->imagickDraw->setStrokeWidth(0);
     185
     186                foreach($this->matrix->getMatrix() as $y => $row){
    103187                        foreach($row as $x => $M_TYPE){
    104                                 $draw->setStrokeColor($this->moduleValues[$M_TYPE]);
    105                                 $draw->setFillColor($this->moduleValues[$M_TYPE]);
    106                                 $draw->rectangle(
    107                                         $x * $this->scale,
    108                                         $y * $this->scale,
    109                                         ($x + 1) * $this->scale,
    110                                         ($y + 1) * $this->scale
    111                                 );
    112 
     188                                $this->module($x, $y, $M_TYPE);
    113189                        }
    114190                }
    115191
    116                 $this->imagick->drawImage($draw);
     192                $this->imagick->drawImage($this->imagickDraw);
     193        }
     194
     195        /**
     196         * draws a single pixel at the given position
     197         */
     198        protected function module(int $x, int $y, int $M_TYPE):void{
     199
     200                if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
     201                        return;
     202                }
     203
     204                $this->imagickDraw->setFillColor($this->getModuleValue($M_TYPE));
     205
     206                if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){
     207                        $this->imagickDraw->circle(
     208                                (($x + 0.5) * $this->scale),
     209                                (($y + 0.5) * $this->scale),
     210                                (($x + 0.5 + $this->circleRadius) * $this->scale),
     211                                (($y + 0.5) * $this->scale),
     212                        );
     213
     214                        return;
     215                }
     216
     217                $this->imagickDraw->rectangle(
     218                        ($x * $this->scale),
     219                        ($y * $this->scale),
     220                        ((($x + 1) * $this->scale) - 1),
     221                        ((($y + 1) * $this->scale) - 1),
     222                );
    117223        }
    118224
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Output/QRMarkup.php

    r2466054 r3752647  
    33 * Class QRMarkup
    44 *
    5  * @filesource   QRMarkup.php
    65 * @created      17.12.2016
    7  * @package      chillerlan\QRCode\Output
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2016 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode\Output;
    1413
    15 use chillerlan\QRCode\QRCode;
     14/**
     15 * Abstract for markup types: HTML, SVG, ... XML anyone?
     16 */
     17abstract class QRMarkup extends QROutputAbstract{
     18        use CssColorModuleValueTrait;
    1619
    17 use function is_string, sprintf, strip_tags, trim;
     20        public function dump(string|null $file = null):string{
     21                $saveToFile = $file !== null;
     22                $data       = $this->createMarkup($saveToFile);
    1823
    19 /**
    20  * Converts the matrix into markup types: HTML, SVG, ...
    21  */
    22 class QRMarkup extends QROutputAbstract{
     24                $this->saveToFile($data, $file);
    2325
    24         protected string $defaultMode = QRCode::OUTPUT_MARKUP_SVG;
    25 
    26         /**
    27          * @see \sprintf()
    28          */
    29         protected string $svgHeader = '<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" '.
    30                                       'style="width: 100%%; height: auto;" viewBox="0 0 %2$d %2$d">';
    31 
    32         /**
    33          * @inheritDoc
    34          */
    35         protected function setModuleValues():void{
    36 
    37                 foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
    38                         $v = $this->options->moduleValues[$M_TYPE] ?? null;
    39 
    40                         if(!is_string($v)){
    41                                 $this->moduleValues[$M_TYPE] = $defaultValue
    42                                         ? $this->options->markupDark
    43                                         : $this->options->markupLight;
    44                         }
    45                         else{
    46                                 $this->moduleValues[$M_TYPE] = trim(strip_tags($v), '\'"');
    47                         }
    48 
     26                // transform to data URI only when not saving to file
     27                if(!$saveToFile && $this->options->outputBase64){
     28                        return $this->toBase64DataURI($data);
    4929                }
    5030
     31                return $data;
    5132        }
    5233
    5334        /**
    54          * HTML output
     35         * returns a string with all css classes for the current element
    5536         */
    56         protected function html(string $file = null):string{
    57 
    58                 $html = empty($this->options->cssClass)
    59                         ? '<div>'
    60                         : '<div class="'.$this->options->cssClass.'">';
    61 
    62                 $html .= $this->options->eol;
    63 
    64                 foreach($this->matrix->matrix() as $row){
    65                         $html .= '<div>';
    66 
    67                         foreach($row as $M_TYPE){
    68                                 $html .= '<span style="background: '.$this->moduleValues[$M_TYPE].';"></span>';
    69                         }
    70 
    71                         $html .= '</div>'.$this->options->eol;
    72                 }
    73 
    74                 $html .= '</div>'.$this->options->eol;
    75 
    76                 if($file !== null){
    77                         return '<!DOCTYPE html>'.
    78                                '<head><meta charset="UTF-8"><title>QR Code</title></head>'.
    79                                '<body>'.$this->options->eol.$html.'</body>';
    80                 }
    81 
    82                 return $html;
     37        protected function getCssClass(int $M_TYPE = 0):string{
     38                return $this->options->cssClass;
    8339        }
    8440
    8541        /**
    86          * SVG output
    87          *
    88          * @see https://github.com/codemasher/php-qrcode/pull/5
     42         * returns the fully parsed and rendered markup string for the given input
    8943         */
    90         protected function svg(string $file = null):string{
    91                 $matrix = $this->matrix->matrix();
    92 
    93                 $svg = sprintf($this->svgHeader, $this->options->cssClass, $this->options->svgViewBoxSize ?? $this->moduleCount)
    94                        .$this->options->eol
    95                        .'<defs>'.$this->options->svgDefs.'</defs>'
    96                        .$this->options->eol;
    97 
    98                 foreach($this->moduleValues as $M_TYPE => $value){
    99                         $path = '';
    100 
    101                         foreach($matrix as $y => $row){
    102                                 //we'll combine active blocks within a single row as a lightweight compression technique
    103                                 $start = null;
    104                                 $count = 0;
    105 
    106                                 foreach($row as $x => $module){
    107 
    108                                         if($module === $M_TYPE){
    109                                                 $count++;
    110 
    111                                                 if($start === null){
    112                                                         $start = $x;
    113                                                 }
    114 
    115                                                 if(isset($row[$x + 1])){
    116                                                         continue;
    117                                                 }
    118                                         }
    119 
    120                                         if($count > 0){
    121                                                 $len   = $count;
    122                                                 $start ??= 0; // avoid type coercion in sprintf() - phan happy
    123 
    124                                                 $path .= sprintf('M%s %s h%s v1 h-%sZ ', $start, $y, $len, $len);
    125 
    126                                                 // reset count
    127                                                 $count = 0;
    128                                                 $start = null;
    129                                         }
    130 
    131                                 }
    132 
    133                         }
    134 
    135                         if(!empty($path)){
    136                                 $svg .= sprintf(
    137                                         '<path class="qr-%s %s" stroke="transparent" fill="%s" fill-opacity="%s" d="%s" />',
    138                                         $M_TYPE, $this->options->cssClass, $value, $this->options->svgOpacity, $path
    139                                 );
    140                         }
    141 
    142                 }
    143 
    144                 // close svg
    145                 $svg .= '</svg>'.$this->options->eol;
    146 
    147                 // if saving to file, append the correct headers
    148                 if($file !== null){
    149                         return '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'.
    150                                $this->options->eol.$svg;
    151                 }
    152 
    153                 if($this->options->imageBase64){
    154                         $svg = sprintf('data:image/svg+xml;base64,%s', base64_encode($svg));
    155                 }
    156 
    157                 return $svg;
    158         }
     44        abstract protected function createMarkup(bool $saveToFile):string;
    15945
    16046}
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php

    r2466054 r3752647  
    33 * Class QROutputAbstract
    44 *
    5  * @filesource   QROutputAbstract.php
    65 * @created      09.12.2015
    7  * @package      chillerlan\QRCode\Output
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
     9 *
     10 * @noinspection PhpComposerExtensionStubsInspection
    1111 */
     12declare(strict_types=1);
    1213
    1314namespace chillerlan\QRCode\Output;
    1415
    15 use chillerlan\QRCode\{Data\QRMatrix, QRCode};
     16use chillerlan\QRCode\QROptions;
     17use chillerlan\QRCode\Data\QRMatrix;
    1618use chillerlan\Settings\SettingsContainerInterface;
    17 
    18 use function call_user_func_array, dirname, file_put_contents, get_called_class, in_array, is_writable, sprintf;
     19use finfo;
     20use function base64_encode, dirname, extension_loaded, file_put_contents, is_iterable, is_writable, ksort, sprintf;
     21use const FILEINFO_MIME_TYPE;
    1922
    2023/**
    … …  
    2629         * the current size of the QR matrix
    2730         *
    28          * @see \chillerlan\QRCode\Data\QRMatrix::size()
     31         * @see \chillerlan\QRCode\Data\QRMatrix::getSize()
    2932         */
    3033        protected int $moduleCount;
    3134
    3235        /**
    33          * the current output mode
    34          *
    35          * @see \chillerlan\QRCode\QROptions::$outputType
    36          */
    37         protected string $outputMode;
    38 
    39         /**
    40          * the default output mode of the current output module
    41          */
    42         protected string $defaultMode;
    43 
    44         /**
    45          * the current scaling for a QR pixel
     36         * the side length of the QR image (modules * scale)
     37         */
     38        protected int $length;
     39
     40        /**
     41         * The current module scale value (might have been modified)
    4642         *
    4743         * @see \chillerlan\QRCode\QROptions::$scale
    … …  
    5046
    5147        /**
    52          * the side length of the QR image (modules * scale)
    53          */
    54         protected int $length;
    55 
    56         /**
    5748         * an (optional) array of color values for the several QR matrix parts
     49         *
     50         * @phpstan-var array<int, mixed>
    5851         */
    5952        protected array $moduleValues;
    … …  
    6558
    6659        /**
    67          * @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions
    68          */
    69         protected SettingsContainerInterface $options;
     60         * the options instance
     61         */
     62        protected SettingsContainerInterface|QROptions $options;
     63
     64        /**
     65         * @see \chillerlan\QRCode\QROptions::$excludeFromConnect
     66         * @var int[]
     67         * @deprecated 6.0.1 This property will be removed in v7, use `$this->options->excludeFromConnect` instead.
     68         */
     69        protected array $excludeFromConnect;
     70
     71        /**
     72         * @see \chillerlan\QRCode\QROptions::$keepAsSquare
     73         * @var int[]
     74         * @deprecated 6.0.1 This property will be removed in v7, use `$this->options->keepAsSquare` instead.
     75         */
     76        protected array $keepAsSquare;
     77
     78        /**
     79         * @see \chillerlan\QRCode\QROptions::$connectPaths
     80         * @deprecated 6.0.1 This property will be removed in v7, use `$this->options->connectPaths` instead.
     81         */
     82        protected bool $connectPaths;
     83
     84        /**
     85         * @see \chillerlan\QRCode\QROptions::$eol
     86         * @deprecated 6.0.1 This property will be removed in v7, use `$this->options->eol` instead.
     87         */
     88        protected string $eol;
     89
     90        /**
     91         * @see \chillerlan\QRCode\QROptions::$drawLightModules
     92         * @deprecated 6.0.1 This property will be removed in v7, use `$this->options->drawLightModules` instead.
     93         */
     94        protected bool $drawLightModules;
     95
     96        /**
     97         * @see \chillerlan\QRCode\QROptions::$drawCircularModules
     98         * @deprecated 6.0.1 This property will be removed in v7, use `$this->options->drawCircularModules` instead.
     99         */
     100        protected bool $drawCircularModules;
     101
     102        /**
     103         * @see \chillerlan\QRCode\QROptions::$circleRadius
     104         * @deprecated 6.0.1 This property will be removed in v7, use `$this->options->circleRadius` instead.
     105         */
     106        protected float $circleRadius;
     107
     108        /**
     109         * @deprecated 6.0.1 This property will be removed in v7, use `$this->options->circleRadius * 2` instead.
     110         */
     111        protected float $circleDiameter;
    70112
    71113        /**
    72114         * QROutputAbstract constructor.
    73115         */
    74         public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
    75                 $this->options     = $options;
    76                 $this->matrix      = $matrix;
    77                 $this->moduleCount = $this->matrix->size();
     116        public function __construct(SettingsContainerInterface|QROptions|iterable $options, QRMatrix $matrix){
     117
     118                if(is_iterable($options)){
     119                        $options = new QROptions($options);
     120                }
     121
     122                $this->options = $options;
     123                $this->matrix  = $matrix;
     124
     125                if($this->options->invertMatrix){
     126                        $this->matrix->invert();
     127                }
     128
     129                $this->copyVars();
     130                $this->setMatrixDimensions();
     131                $this->setModuleValues();
     132        }
     133
     134        /**
     135         * Creates copies of several QROptions values to avoid calling the magic getters/property hooks
     136         * in long loops for a significant performance increase.
     137         *
     138         * These variables are usually used in the "module" methods and are called up to 31329 times (at version 40).
     139         *
     140         * @deprecated 6.0.1 This method will be removed in v7 without replacement (unnecessary).
     141         */
     142        protected function copyVars():void{
     143
     144                $vars = [
     145                        'connectPaths',
     146                        'excludeFromConnect',
     147                        'eol',
     148                        'drawLightModules',
     149                        'drawCircularModules',
     150                        'keepAsSquare',
     151                        'circleRadius',
     152                ];
     153
     154                foreach($vars as $property){
     155                        $this->{$property} = $this->options->{$property};
     156                }
     157
     158                $this->circleDiameter = ($this->circleRadius * 2);
     159        }
     160
     161        /**
     162         * Sets/updates the matrix dimensions
     163         *
     164         * Call this method if you modify the matrix from within your custom output class in case the dimensions have been changed
     165         */
     166        protected function setMatrixDimensions():void{
     167                $this->moduleCount = $this->matrix->getSize();
    78168                $this->scale       = $this->options->scale;
    79                 $this->length      = $this->moduleCount * $this->scale;
    80 
    81                 $class = get_called_class();
    82 
    83                 if(isset(QRCode::OUTPUT_MODES[$class]) && in_array($this->options->outputType, QRCode::OUTPUT_MODES[$class])){
    84                         $this->outputMode = $this->options->outputType;
    85                 }
    86 
    87                 $this->setModuleValues();
    88         }
    89 
    90         /**
    91          * Sets the initial module values (clean-up & defaults)
    92          */
    93         abstract protected function setModuleValues():void;
    94 
    95         /**
    96          * saves the qr data to a file
     169                $this->length      = ($this->moduleCount * $this->scale);
     170        }
     171
     172        /**
     173         * Returns a 2 element array with the current output width and height
     174         *
     175         * The type and units of the values depend on the output class. The default value is the current module count * scale.
     176         *
     177         * @return int[]
     178         */
     179        protected function getOutputDimensions():array{
     180                return [$this->length, $this->length];
     181        }
     182
     183        /**
     184         * Sets the initial module values
     185         */
     186        protected function setModuleValues():void{
     187
     188                // first fill the map with the default values
     189                foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
     190                        $this->moduleValues[$M_TYPE] = $this->getDefaultModuleValue($defaultValue);
     191                }
     192
     193                // now loop over the options values to replace defaults and add extra values
     194                /** @var int $M_TYPE */
     195                foreach($this->options->moduleValues as $M_TYPE => $value){
     196                        if($this::moduleValueIsValid($value)){
     197                                $this->moduleValues[$M_TYPE] = $this->prepareModuleValue($value);
     198                        }
     199                }
     200
     201        }
     202
     203        /**
     204         * Prepares the value for the given input (return value depends on the output class)
     205         */
     206        abstract protected function prepareModuleValue(mixed $value):mixed;
     207
     208        /**
     209         * Returns a default value for either dark or light modules (return value depends on the output class)
     210         */
     211        abstract protected function getDefaultModuleValue(bool $isDark):mixed;
     212
     213        /**
     214         * Returns the prepared value for the given $M_TYPE
     215         *
     216         * @throws \chillerlan\QRCode\Output\QRCodeOutputException if $moduleValues[$M_TYPE] doesn't exist
     217         */
     218        protected function getModuleValue(int $M_TYPE):mixed{
     219
     220                if(!isset($this->moduleValues[$M_TYPE])){
     221                        throw new QRCodeOutputException(sprintf('$M_TYPE %012b not found in module values map', $M_TYPE));
     222                }
     223
     224                return $this->moduleValues[$M_TYPE];
     225        }
     226
     227        /**
     228         * Returns the prepared module value at the given coordinate [$x, $y] (convenience)
     229         */
     230        protected function getModuleValueAt(int $x, int $y):mixed{
     231                return $this->getModuleValue($this->matrix->get($x, $y));
     232        }
     233
     234        /**
     235         * Returns a base64 data URI for the given string and mime type
     236         *
     237         * The mime type can be set via class constant MIME_TYPE in child classes,
     238         * or given via $mime, otherwise it is guessed from the image $data.
     239         */
     240        protected function toBase64DataURI(string $data, string|null $mime = null):string{
     241                $mime ??= static::MIME_TYPE;
     242
     243                if($mime === ''){
     244                        $mime = $this->guessMimeType($data);
     245                }
     246
     247                return sprintf('data:%s;base64,%s', $mime, base64_encode($data));
     248        }
     249
     250        /**
     251         * Guesses the mime type from the given $imageData
     252         *
     253         * @throws \chillerlan\QRCode\Output\QRCodeOutputException
     254         */
     255        protected function guessMimeType(string $imageData):string{
     256
     257                if(!extension_loaded('fileinfo')){
     258                        throw new QRCodeOutputException('ext-fileinfo not loaded, cannot guess mime type');
     259                }
     260
     261                $mime = (new finfo(FILEINFO_MIME_TYPE))->buffer($imageData);
     262
     263                if($mime === false){
     264                        throw new QRCodeOutputException('unable to detect mime type');
     265                }
     266
     267                return $mime;
     268        }
     269
     270        /**
     271         * Saves the qr $data to a $file. If $file is null, nothing happens.
    97272         *
    98273         * @see file_put_contents()
    99          * @see \chillerlan\QRCode\QROptions::cachefile
     274         * @see \chillerlan\QRCode\QROptions::$cachefile
    100275         *
    101276         * @throws \chillerlan\QRCode\Output\QRCodeOutputException
    102277         */
    103         protected function saveToFile(string $data, string $file):bool{
     278        protected function saveToFile(string $data, string|null $file = null):void{
     279
     280                if($file === null){
     281                        return;
     282                }
    104283
    105284                if(!is_writable(dirname($file))){
    106                         throw new QRCodeOutputException(sprintf('Could not write data to cache file: %s', $file));
    107                 }
    108 
    109                 return (bool)file_put_contents($file, $data);
    110         }
    111 
    112         /**
    113          * @inheritDoc
    114          */
    115         public function dump(string $file = null){
    116                 $file ??= $this->options->cachefile;
    117 
    118                 // call the built-in output method with the optional file path as parameter
    119                 // to make the called method aware if a cache file was given
    120                 $data = call_user_func_array([$this, $this->outputMode ?? $this->defaultMode], [$file]);
    121 
    122                 if($file !== null){
    123                         $this->saveToFile($data, $file);
    124                 }
    125 
    126                 return $data;
     285                        throw new QRCodeOutputException(sprintf('Cannot write data to cache file: %s', $file));
     286                }
     287
     288                if(file_put_contents($file, $data) === false){
     289                        throw new QRCodeOutputException(sprintf('Cannot write data to cache file: %s (file_put_contents error)', $file));
     290                }
     291        }
     292
     293        /**
     294         * collects the modules per QRMatrix::M_* type, runs a transform method on each module and
     295         * returns an array with the transformed modules.
     296         *
     297         * @see \chillerlan\QRCode\Output\QROutputAbstract::moduleTransform()
     298         *
     299         * @return array<int, mixed>
     300         */
     301        protected function collectModules():array{
     302                $paths = [];
     303
     304                // collect the modules for each type
     305                foreach($this->matrix->getMatrix() as $y => $row){
     306                        foreach($row as $x => $M_TYPE){
     307                                $M_TYPE_LAYER = $M_TYPE;
     308
     309                                if($this->connectPaths && !$this->matrix->checkTypeIn($x, $y, $this->excludeFromConnect)){
     310                                        // to connect paths we'll redeclare the $M_TYPE_LAYER to data only
     311                                        $M_TYPE_LAYER = QRMatrix::M_DATA;
     312
     313                                        if($this->matrix->isDark($M_TYPE)){
     314                                                $M_TYPE_LAYER = QRMatrix::M_DATA_DARK;
     315                                        }
     316                                }
     317
     318                                // collect the modules per $M_TYPE
     319                                $module = $this->moduleTransform($x, $y, $M_TYPE, $M_TYPE_LAYER);
     320
     321                                if($module !== null){
     322                                        $paths[$M_TYPE_LAYER][] = $module;
     323                                }
     324                        }
     325                }
     326
     327                // beautify output
     328                ksort($paths);
     329
     330                return $paths;
     331        }
     332
     333        /**
     334         * The transform callback for the module collector
     335         *
     336         *   $x            - current column
     337         *   $y            - current row
     338         *   $M_TYPE       - field value
     339         *   $M_TYPE_LAYER - (possibly modified) field value that acts as layer id ($paths array key)
     340         *
     341         * This method should return a value suitable for the current output class.
     342         * It must return `null` for an empty value.
     343         *
     344         * @see \chillerlan\QRCode\Output\QROutputAbstract::collectModules()
     345         */
     346        protected function moduleTransform(int $x, int $y, int $M_TYPE, int $M_TYPE_LAYER):mixed{
     347                return null;
    127348        }
    128349
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/Output/QROutputInterface.php

    r2466054 r3752647  
    33 * Interface QROutputInterface,
    44 *
    5  * @filesource   QROutputInterface.php
    65 * @created      02.12.2015
    7  * @package      chillerlan\QRCode\Output
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode\Output;
    … …  
    2019interface QROutputInterface{
    2120
    22         const DEFAULT_MODULE_VALUES = [
    23                 // light
    24                 QRMatrix::M_NULL            => false, // 0
    25                 QRMatrix::M_DATA            => false, // 4
    26                 QRMatrix::M_FINDER          => false, // 6
    27                 QRMatrix::M_SEPARATOR       => false, // 8
    28                 QRMatrix::M_ALIGNMENT       => false, // 10
    29                 QRMatrix::M_TIMING          => false, // 12
    30                 QRMatrix::M_FORMAT          => false, // 14
    31                 QRMatrix::M_VERSION         => false, // 16
    32                 QRMatrix::M_QUIETZONE       => false, // 18
    33                 QRMatrix::M_LOGO            => false, // 20
    34                 QRMatrix::M_TEST            => false, // 255
    35                 // dark
    36                 QRMatrix::M_DARKMODULE << 8 => true,  // 512
    37                 QRMatrix::M_DATA << 8       => true,  // 1024
    38                 QRMatrix::M_FINDER << 8     => true,  // 1536
    39                 QRMatrix::M_ALIGNMENT << 8  => true,  // 2560
    40                 QRMatrix::M_TIMING << 8     => true,  // 3072
    41                 QRMatrix::M_FORMAT << 8     => true,  // 3584
    42                 QRMatrix::M_VERSION << 8    => true,  // 4096
    43                 QRMatrix::M_FINDER_DOT << 8 => true,  // 5632
    44                 QRMatrix::M_TEST << 8       => true,  // 65280
     21        /**
     22         * Map of built-in output class FQN
     23         *
     24         * @var string[]
     25         * @see https://github.com/chillerlan/php-qrcode/issues/223
     26         */
     27        public const MODES = [
     28                QREps::class,
     29                QRFpdf::class,
     30                QRGdImageAVIF::class,
     31                QRGdImageBMP::class,
     32                QRGdImageGIF::class,
     33                QRGdImageJPEG::class,
     34                QRGdImagePNG::class,
     35                QRGdImageWEBP::class,
     36                QRImagick::class,
     37                QRInterventionImage::class,
     38                QRMarkupHTML::class,
     39                QRMarkupSVG::class,
     40                QRMarkupXML::class,
     41                QRStringJSON::class,
     42                QRStringText::class,
    4543        ];
    4644
    4745        /**
    48          * generates the output, optionally dumps it to a file, and returns it
     46         * Map of module type => default value
    4947         *
    50          * @return mixed
     48         * @var bool[]
    5149         */
    52         public function dump(string $file = null);
     50        public const DEFAULT_MODULE_VALUES = [
     51                // light
     52                QRMatrix::M_NULL             => false,
     53                QRMatrix::M_DARKMODULE_LIGHT => false,
     54                QRMatrix::M_DATA             => false,
     55                QRMatrix::M_FINDER           => false,
     56                QRMatrix::M_SEPARATOR        => false,
     57                QRMatrix::M_ALIGNMENT        => false,
     58                QRMatrix::M_TIMING           => false,
     59                QRMatrix::M_FORMAT           => false,
     60                QRMatrix::M_VERSION          => false,
     61                QRMatrix::M_QUIETZONE        => false,
     62                QRMatrix::M_LOGO             => false,
     63                QRMatrix::M_FINDER_DOT_LIGHT => false,
     64                // dark
     65                QRMatrix::M_DARKMODULE       => true,
     66                QRMatrix::M_DATA_DARK        => true,
     67                QRMatrix::M_FINDER_DARK      => true,
     68                QRMatrix::M_SEPARATOR_DARK   => true,
     69                QRMatrix::M_ALIGNMENT_DARK   => true,
     70                QRMatrix::M_TIMING_DARK      => true,
     71                QRMatrix::M_FORMAT_DARK      => true,
     72                QRMatrix::M_VERSION_DARK     => true,
     73                QRMatrix::M_QUIETZONE_DARK   => true,
     74                QRMatrix::M_LOGO_DARK        => true,
     75                QRMatrix::M_FINDER_DOT       => true,
     76        ];
     77
     78        /**
     79         * Map of module type => readable name (for CSS etc.)
     80         *
     81         * @var string[]
     82         */
     83        public const LAYERNAMES = [
     84                // light
     85                QRMatrix::M_NULL             => 'null',
     86                QRMatrix::M_DARKMODULE_LIGHT => 'darkmodule-light',
     87                QRMatrix::M_DATA             => 'data',
     88                QRMatrix::M_FINDER           => 'finder',
     89                QRMatrix::M_SEPARATOR        => 'separator',
     90                QRMatrix::M_ALIGNMENT        => 'alignment',
     91                QRMatrix::M_TIMING           => 'timing',
     92                QRMatrix::M_FORMAT           => 'format',
     93                QRMatrix::M_VERSION          => 'version',
     94                QRMatrix::M_QUIETZONE        => 'quietzone',
     95                QRMatrix::M_LOGO             => 'logo',
     96                QRMatrix::M_FINDER_DOT_LIGHT => 'finder-dot-light',
     97                // dark
     98                QRMatrix::M_DARKMODULE       => 'darkmodule',
     99                QRMatrix::M_DATA_DARK        => 'data-dark',
     100                QRMatrix::M_FINDER_DARK      => 'finder-dark',
     101                QRMatrix::M_SEPARATOR_DARK   => 'separator-dark',
     102                QRMatrix::M_ALIGNMENT_DARK   => 'alignment-dark',
     103                QRMatrix::M_TIMING_DARK      => 'timing-dark',
     104                QRMatrix::M_FORMAT_DARK      => 'format-dark',
     105                QRMatrix::M_VERSION_DARK     => 'version-dark',
     106                QRMatrix::M_QUIETZONE_DARK   => 'quietzone-dark',
     107                QRMatrix::M_LOGO_DARK        => 'logo-dark',
     108                QRMatrix::M_FINDER_DOT       => 'finder-dot',
     109        ];
     110
     111        /**
     112         * Note: do not call this constant from the interface, but rather from one of the child classes
     113         *
     114         * @var string
     115         * @see \chillerlan\QRCode\Output\QROutputAbstract::toBase64DataURI()
     116         */
     117        public const MIME_TYPE = '';
     118
     119        /**
     120         * Determines whether the given value is valid
     121         */
     122        public static function moduleValueIsValid(mixed $value):bool;
     123
     124        /**
     125         * Generates the output, optionally dumps it to a file, and returns it
     126         *
     127         * please note that the value of QROptions::$cachefile is already evaluated at this point.
     128         * if the output module is invoked manually, it has no effect at all.
     129         * you need to supply the $file parameter here in that case (or handle the option value in your custom output module).
     130         *
     131         * @see \chillerlan\QRCode\QRCode::renderMatrix()
     132         */
     133        public function dump(string|null $file = null):mixed;
    53134
    54135}
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/QRCode.php

    r2466054 r3752647  
    33 * Class QRCode
    44 *
    5  * @filesource   QRCode.php
    65 * @created      26.11.2015
    7  * @package      chillerlan\QRCode
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode;
    1413
    15 use chillerlan\QRCode\Data\{
    16         AlphaNum, Byte, Kanji, MaskPatternTester, Number, QRCodeDataException, QRDataInterface, QRMatrix
     14use chillerlan\QRCode\Common\{
     15        ECICharset, GDLuminanceSource, IMagickLuminanceSource, LuminanceSourceInterface, MaskPattern, Mode
    1716};
    18 use chillerlan\QRCode\Output\{
    19         QRCodeOutputException, QRFpdf, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString
    20 };
     17use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Hanzi, Kanji, Number, QRData, QRDataModeInterface, QRMatrix};
     18use chillerlan\QRCode\Decoder\{Decoder, DecoderResult};
     19use chillerlan\QRCode\Output\{QRCodeOutputException, QROutputInterface};
    2120use chillerlan\Settings\SettingsContainerInterface;
    22 
    23 use function call_user_func_array, class_exists, in_array, ord, strlen, strtolower, str_split;
     21use function class_exists, class_implements, in_array, is_iterable, mb_convert_encoding, mb_internal_encoding;
    2422
    2523/**
    … …  
    2725 *
    2826 * @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
    29  * @see http://www.qrcode.com/en/codes/model12.html
     27 * @see https://www.qrcode.com/en/codes/model12.html
    3028 * @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf
    3129 * @see https://en.wikipedia.org/wiki/QR_code
    32  * @see http://www.thonky.com/qr-code-tutorial/
     30 * @see https://www.thonky.com/qr-code-tutorial/
    3331 */
    3432class QRCode{
    3533
    36         /** @var int */
    37         public const VERSION_AUTO       = -1;
    38         /** @var int */
    39         public const MASK_PATTERN_AUTO  = -1;
    40 
    41         // ISO/IEC 18004:2000 Table 2
    42 
    43         /** @var int */
    44         public const DATA_NUMBER   = 0b0001;
    45         /** @var int */
    46         public const DATA_ALPHANUM = 0b0010;
    47         /** @var int */
    48         public const DATA_BYTE     = 0b0100;
    49         /** @var int */
    50         public const DATA_KANJI    = 0b1000;
    51 
    52         /**
    53          * References to the keys of the following tables:
    54          *
    55          * @see \chillerlan\QRCode\Data\QRDataInterface::MAX_LENGTH
    56          *
    57          * @var int[]
    58          */
    59         public const DATA_MODES = [
    60                 self::DATA_NUMBER   => 0,
    61                 self::DATA_ALPHANUM => 1,
    62                 self::DATA_BYTE     => 2,
    63                 self::DATA_KANJI    => 3,
    64         ];
    65 
    66         // ISO/IEC 18004:2000 Tables 12, 25
    67 
    68         /** @var int */
    69         public const ECC_L = 0b01; // 7%.
    70         /** @var int */
    71         public const ECC_M = 0b00; // 15%.
    72         /** @var int */
    73         public const ECC_Q = 0b11; // 25%.
    74         /** @var int */
    75         public const ECC_H = 0b10; // 30%.
    76 
    77         /**
    78          * References to the keys of the following tables:
    79          *
    80          * @see \chillerlan\QRCode\Data\QRDataInterface::MAX_BITS
    81          * @see \chillerlan\QRCode\Data\QRDataInterface::RSBLOCKS
    82          * @see \chillerlan\QRCode\Data\QRMatrix::formatPattern
    83          *
    84          * @var int[]
    85          */
    86         public const ECC_MODES = [
    87                 self::ECC_L => 0,
    88                 self::ECC_M => 1,
    89                 self::ECC_Q => 2,
    90                 self::ECC_H => 3,
    91         ];
    92 
    93         /** @var string */
    94         public const OUTPUT_MARKUP_HTML = 'html';
    95         /** @var string */
    96         public const OUTPUT_MARKUP_SVG  = 'svg';
    97         /** @var string */
    98         public const OUTPUT_IMAGE_PNG   = 'png';
    99         /** @var string */
    100         public const OUTPUT_IMAGE_JPG   = 'jpg';
    101         /** @var string */
    102         public const OUTPUT_IMAGE_GIF   = 'gif';
    103         /** @var string */
    104         public const OUTPUT_STRING_JSON = 'json';
    105         /** @var string */
    106         public const OUTPUT_STRING_TEXT = 'text';
    107         /** @var string */
    108         public const OUTPUT_IMAGICK     = 'imagick';
    109         /** @var string */
    110         public const OUTPUT_FPDF        = 'fpdf';
    111         /** @var string */
    112         public const OUTPUT_CUSTOM      = 'custom';
    113 
    114         /**
    115          * Map of built-in output modules => capabilities
    116          *
    117          * @var string[][]
    118          */
    119         public const OUTPUT_MODES = [
    120                 QRMarkup::class => [
    121                         self::OUTPUT_MARKUP_SVG,
    122                         self::OUTPUT_MARKUP_HTML,
    123                 ],
    124                 QRImage::class => [
    125                         self::OUTPUT_IMAGE_PNG,
    126                         self::OUTPUT_IMAGE_GIF,
    127                         self::OUTPUT_IMAGE_JPG,
    128                 ],
    129                 QRString::class => [
    130                         self::OUTPUT_STRING_JSON,
    131                         self::OUTPUT_STRING_TEXT,
    132                 ],
    133                 QRImagick::class => [
    134                         self::OUTPUT_IMAGICK,
    135                 ],
    136                 QRFpdf::class => [
    137                         self::OUTPUT_FPDF
    138                 ]
    139         ];
    140 
    141         /**
    142          * Map of data mode => interface
    143          *
    144          * @var string[]
    145          */
    146         protected const DATA_INTERFACES = [
    147                 'number'   => Number::class,
    148                 'alphanum' => AlphaNum::class,
    149                 'kanji'    => Kanji::class,
    150                 'byte'     => Byte::class,
    151         ];
    152 
    15334        /**
    15435         * The settings container
    155          *
    156          * @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface
    157          */
    158         protected SettingsContainerInterface $options;
    159 
    160         /**
    161          * The selected data interface (Number, AlphaNum, Kanji, Byte)
    162          */
    163         protected QRDataInterface $dataInterface;
     36         */
     37        protected SettingsContainerInterface|QROptions $options;
     38
     39        /**
     40         * A collection of one or more data segments of QRDataModeInterface instances to write
     41         *
     42         * @var \chillerlan\QRCode\Data\QRDataModeInterface[]
     43         */
     44        protected array $dataSegments = [];
     45
     46        /**
     47         * The luminance source for the reader
     48         */
     49        protected string $luminanceSourceFQN = GDLuminanceSource::class;
    16450
    16551        /**
    16652         * QRCode constructor.
    16753         *
    168          * Sets the options instance, determines the current mb-encoding and sets it to UTF-8
    169          */
    170         public function __construct(SettingsContainerInterface $options = null){
    171                 $this->options = $options ?? new QROptions;
    172         }
    173 
    174         /**
    175          * Renders a QR Code for the given $data and QROptions
    176          *
    177          * @return mixed
    178          */
    179         public function render(string $data, string $file = null){
    180                 return $this->initOutputInterface($data)->dump($file);
     54         * @phpstan-param array<string, mixed> $options
     55         */
     56        public function __construct(SettingsContainerInterface|QROptions|iterable $options = new QROptions){
     57                $this->setOptions($options);
     58        }
     59
     60        /**
     61         * Sets an options instance
     62         *
     63         * @phpstan-param array<string, mixed> $options
     64         */
     65        public function setOptions(SettingsContainerInterface|QROptions|iterable $options):static{
     66
     67                if(is_iterable($options)){
     68                        $options = new QROptions($options);
     69                }
     70
     71                $this->options = $options;
     72
     73                if($this->options->readerUseImagickIfAvailable){
     74                        $this->luminanceSourceFQN = IMagickLuminanceSource::class;
     75                }
     76
     77                return $this;
     78        }
     79
     80        /**
     81         * Renders a QR Code for the given $data and QROptions, saves $file optionally
     82         */
     83        public function render(string|null $data = null, string|null $file = null):mixed{
     84
     85                if($data !== null){
     86                        /** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */
     87                        foreach(Mode::INTERFACES as $dataInterface){
     88
     89                                if($dataInterface::validateString($data)){
     90                                        $this->addSegment(new $dataInterface($data));
     91
     92                                        break;
     93                                }
     94                        }
     95                }
     96
     97                return $this->renderMatrix($this->getQRMatrix(), $file);
     98        }
     99
     100        /**
     101         * Renders a QR Code for the given QRMatrix and QROptions, saves $file optionally
     102         */
     103        public function renderMatrix(QRMatrix $matrix, string|null $file = null):mixed{
     104                return $this->initOutputInterface($matrix)->dump($file ?? $this->options->cachefile);
    181105        }
    182106
    183107        /**
    184108         * Returns a QRMatrix object for the given $data and current QROptions
    185          *
    186          * @throws \chillerlan\QRCode\Data\QRCodeDataException
    187          */
    188         public function getMatrix(string $data):QRMatrix{
    189 
    190                 if(empty($data)){
    191                         throw new QRCodeDataException('QRCode::getMatrix() No data given.');
    192                 }
    193 
    194                 $this->dataInterface = $this->initDataInterface($data);
    195 
    196                 $maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
    197                         ? (new MaskPatternTester($this->dataInterface))->getBestMaskPattern()
    198                         : $this->options->maskPattern;
    199 
    200                 $matrix = $this->dataInterface->initMatrix($maskPattern);
    201 
    202                 if((bool)$this->options->addQuietzone){
     109         */
     110        public function getQRMatrix():QRMatrix{
     111                $matrix = (new QRData($this->options, $this->dataSegments))->writeMatrix();
     112
     113                $maskPattern = $this->options->maskPattern === MaskPattern::AUTO
     114                        ? MaskPattern::getBestPattern($matrix)
     115                        : new MaskPattern($this->options->maskPattern);
     116
     117                $matrix->setFormatInfo($maskPattern)->mask($maskPattern);
     118
     119                return $this->addMatrixModifications($matrix);
     120        }
     121
     122        /**
     123         * add matrix modifications after mask pattern evaluation and before handing over to output
     124         */
     125        protected function addMatrixModifications(QRMatrix $matrix):QRMatrix{
     126
     127                if($this->options->addLogoSpace){
     128                        // check whether one of the dimensions was omitted
     129                        $logoSpaceWidth  = ($this->options->logoSpaceWidth ?? $this->options->logoSpaceHeight ?? 0);
     130                        $logoSpaceHeight = ($this->options->logoSpaceHeight ?? $logoSpaceWidth);
     131
     132                        $matrix->setLogoSpace(
     133                                $logoSpaceWidth,
     134                                $logoSpaceHeight,
     135                                $this->options->logoSpaceStartX,
     136                                $this->options->logoSpaceStartY,
     137                        );
     138                }
     139
     140                if($this->options->addQuietzone){
    203141                        $matrix->setQuietZone($this->options->quietzoneSize);
    204142                }
    … …  
    208146
    209147        /**
    210          * returns a fresh QRDataInterface for the given $data
    211          *
    212          * @throws \chillerlan\QRCode\Data\QRCodeDataException
    213          */
    214         public function initDataInterface(string $data):QRDataInterface{
    215 
    216                 // allow forcing the data mode
    217                 // see https://github.com/chillerlan/php-qrcode/issues/39
    218                 $interface = $this::DATA_INTERFACES[strtolower($this->options->dataModeOverride)] ?? null;
    219 
    220                 if($interface !== null){
    221                         return new $interface($this->options, $data);
    222                 }
    223 
    224                 foreach($this::DATA_INTERFACES as $mode => $dataInterface){
    225 
    226                         if(call_user_func_array([$this, 'is'.$mode], [$data])){
    227                                 return new $dataInterface($this->options, $data);
    228                         }
    229 
    230                 }
    231 
    232                 throw new QRCodeDataException('invalid data type'); // @codeCoverageIgnore
    233         }
    234 
    235         /**
    236          * returns a fresh (built-in) QROutputInterface
     148         * initializes a fresh built-in or custom QROutputInterface
    237149         *
    238150         * @throws \chillerlan\QRCode\Output\QRCodeOutputException
    239151         */
    240         protected function initOutputInterface(string $data):QROutputInterface{
    241 
    242                 if($this->options->outputType === $this::OUTPUT_CUSTOM && class_exists($this->options->outputInterface)){
    243                         return new $this->options->outputInterface($this->options, $this->getMatrix($data));
    244                 }
    245 
    246                 foreach($this::OUTPUT_MODES as $outputInterface => $modes){
    247 
    248                         if(in_array($this->options->outputType, $modes, true) && class_exists($outputInterface)){
    249                                 return new $outputInterface($this->options, $this->getMatrix($data));
    250                         }
    251 
    252                 }
    253 
    254                 throw new QRCodeOutputException('invalid output type');
    255         }
    256 
    257         /**
    258          * checks if a string qualifies as numeric
    259          */
    260         public function isNumber(string $string):bool{
    261                 return $this->checkString($string, QRDataInterface::CHAR_MAP_NUMBER);
    262         }
    263 
    264         /**
    265          * checks if a string qualifies as alphanumeric
    266          */
    267         public function isAlphaNum(string $string):bool{
    268                 return $this->checkString($string, QRDataInterface::CHAR_MAP_ALPHANUM);
    269         }
    270 
    271         /**
    272          * checks is a given $string matches the characters of a given $charmap, returns false on the first invalid occurence.
    273          */
    274         protected function checkString(string $string, array $charmap):bool{
    275 
    276                 foreach(str_split($string) as $chr){
    277                         if(!isset($charmap[$chr])){
    278                                 return false;
    279                         }
    280                 }
    281 
    282                 return true;
    283         }
    284 
    285         /**
    286          * checks if a string qualifies as Kanji
    287          */
    288         public function isKanji(string $string):bool{
    289                 $i   = 0;
    290                 $len = strlen($string);
    291 
    292                 while($i + 1 < $len){
    293                         $c = ((0xff & ord($string[$i])) << 8) | (0xff & ord($string[$i + 1]));
    294 
    295                         if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){
    296                                 return false;
    297                         }
    298 
    299                         $i += 2;
    300                 }
    301 
    302                 return $i >= $len;
    303         }
    304 
    305         /**
    306          * a dummy
    307          */
    308         public function isByte(string $data):bool{
    309                 return !empty($data);
     152        protected function initOutputInterface(QRMatrix $matrix):QROutputInterface{
     153                $outputInterface = $this->options->outputInterface;
     154
     155                if(!class_exists($outputInterface)){
     156                        throw new QRCodeOutputException('invalid output class');
     157                }
     158
     159                if(!in_array(QROutputInterface::class, class_implements($outputInterface), true)){
     160                        throw new QRCodeOutputException('output class does not implement QROutputInterface');
     161                }
     162
     163                /** @var \chillerlan\QRCode\Output\QROutputInterface $instance */
     164                $instance = new $outputInterface($this->options, $matrix);
     165
     166                return $instance;
     167        }
     168
     169        /**
     170         * Adds a data segment
     171         *
     172         * ISO/IEC 18004:2000 8.3.6 - Mixing modes
     173         * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length
     174         */
     175        public function addSegment(QRDataModeInterface $segment):static{
     176                $this->dataSegments[] = $segment;
     177
     178                return $this;
     179        }
     180
     181        /**
     182         * Clears the data segments array
     183         *
     184         * @codeCoverageIgnore
     185         */
     186        public function clearSegments():static{
     187                $this->dataSegments = [];
     188
     189                return $this;
     190        }
     191
     192        /**
     193         * Adds a numeric data segment
     194         *
     195         * ISO/IEC 18004:2000 8.3.2 - Numeric Mode
     196         */
     197        public function addNumericSegment(string $data):static{
     198                return $this->addSegment(new Number($data));
     199        }
     200
     201        /**
     202         * Adds an alphanumeric data segment
     203         *
     204         * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode
     205         */
     206        public function addAlphaNumSegment(string $data):static{
     207                return $this->addSegment(new AlphaNum($data));
     208        }
     209
     210        /**
     211         * Adds a Kanji data segment (Japanese 13-bit double-byte characters, Shift-JIS)
     212         *
     213         * ISO/IEC 18004:2000 8.3.5 - Kanji Mode
     214         */
     215        public function addKanjiSegment(string $data):static{
     216                return $this->addSegment(new Kanji($data));
     217        }
     218
     219        /**
     220         * Adds a Hanzi data segment (simplified Chinese 13-bit double-byte characters, GB2312/GB18030)
     221         *
     222         * GBT18284-2000 Hanzi Mode
     223         */
     224        public function addHanziSegment(string $data):static{
     225                return $this->addSegment(new Hanzi($data));
     226        }
     227
     228        /**
     229         * Adds an 8-bit byte data segment
     230         *
     231         * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode
     232         */
     233        public function addByteSegment(string $data):static{
     234                return $this->addSegment(new Byte($data));
     235        }
     236
     237        /**
     238         * Adds a standalone ECI designator
     239         *
     240         * The ECI designator must be followed by a Byte segment that contains the string encoded according to the given ECI charset
     241         *
     242         * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode
     243         */
     244        public function addEciDesignator(int $encoding):static{
     245                return $this->addSegment(new ECI($encoding));
     246        }
     247
     248        /**
     249         * Adds an ECI data segment (including designator)
     250         *
     251         * The given string will be encoded from mb_internal_encoding() to the given ECI character set
     252         *
     253         * I hate this somehow, but I'll leave it for now
     254         *
     255         * @throws \chillerlan\QRCode\QRCodeException
     256         */
     257        public function addEciSegment(int $encoding, string $data):static{
     258                // validate the encoding id
     259                $eciCharset = new ECICharset($encoding);
     260                // get charset name
     261                $eciCharsetName = $eciCharset->getName();
     262
     263                if($eciCharsetName === null){
     264                        throw new QRCodeException('unable to add ECI segment');
     265                }
     266
     267                // convert the string to the given charset
     268                $data = mb_convert_encoding($data, $eciCharsetName, mb_internal_encoding());
     269
     270                if($data === false){
     271                        throw new QRCodeException('mb_convert_encoding() error'); // @codeCoverageIgnore
     272                }
     273
     274                return $this
     275                        ->addEciDesignator($eciCharset->getID())
     276                        ->addByteSegment($data)
     277                ;
     278        }
     279
     280        /**
     281         * Reads a QR Code from a given file
     282         *
     283         * @codeCoverageIgnore
     284         */
     285        public function readFromFile(string $path):DecoderResult{
     286                return $this->readFromSource($this->luminanceSourceFQN::fromFile($path, $this->options));
     287        }
     288
     289        /**
     290         * Reads a QR Code from the given data blob
     291         */
     292        public function readFromBlob(string $blob):DecoderResult{
     293                return $this->readFromSource($this->luminanceSourceFQN::fromBlob($blob, $this->options));
     294        }
     295
     296        /**
     297         * Reads a QR Code from the given luminance source
     298         */
     299        public function readFromSource(LuminanceSourceInterface $source):DecoderResult{
     300                return (new Decoder($this->options))->decode($source);
    310301        }
    311302
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/QRCodeException.php

    r2466054 r3752647  
    33 * Class QRCodeException
    44 *
    5  * @filesource   QRCodeException.php
    65 * @created      27.11.2015
    7  * @package      chillerlan\QRCode
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode;
    … …  
    1817 * An exception container
    1918 */
    20 class QRCodeException extends Exception{}
     19class QRCodeException extends Exception{
     20
     21}
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/QROptions.php

    r2466054 r3752647  
    33 * Class QROptions
    44 *
    5  * @filesource   QROptions.php
    65 * @created      08.12.2015
    7  * @package      chillerlan\QRCode
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2015 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\QRCode;
    … …  
    1716/**
    1817 * The QRCode settings container
    19  *
    20  * @property int         $version
    21  * @property int         $versionMin
    22  * @property int         $versionMax
    23  * @property int         $eccLevel
    24  * @property int         $maskPattern
    25  * @property bool        $addQuietzone
    26  * @property int         $quietzoneSize
    27  * @property string|null $dataModeOverride
    28  * @property string      $outputType
    29  * @property string|null $outputInterface
    30  * @property string|null $cachefile
    31  * @property string      $eol
    32  * @property int         $scale
    33  * @property string      $cssClass
    34  * @property float       $svgOpacity
    35  * @property string      $svgDefs
    36  * @property int         $svgViewBoxSize
    37  * @property string      $textDark
    38  * @property string      $textLight
    39  * @property string      $markupDark
    40  * @property string      $markupLight
    41  * @property bool        $returnResource
    42  * @property bool        $imageBase64
    43  * @property bool        $imageTransparent
    44  * @property array       $imageTransparencyBG
    45  * @property int         $pngCompression
    46  * @property int         $jpegQuality
    47  * @property string      $imagickFormat
    48  * @property string|null $imagickBG
    49  * @property string      $fpdfMeasureUnit
    50  * @property array|null  $moduleValues
    5118 */
    5219class QROptions extends SettingsContainerAbstract{
    53         use QROptionsTrait;
     20        use QROptionsTrait, QRCodeReaderOptionsTrait;
    5421}
  • wp-otp/trunk/vendor/chillerlan/php-qrcode/src/QROptionsTrait.php

    r2466054 r3752647  
    33 * Trait QROptionsTrait
    44 *
    5  * @filesource   QROptionsTrait.php
     5 * Note: the docblocks in this file are optimized for readability in PhpStorm ond on readthedocs.io
     6 *
    67 * @created      10.03.2018
    7  * @package      chillerlan\QRCode
    88 * @author       smiley <smiley@chillerlan.net>
    99 * @copyright    2018 smiley
    1010 * @license      MIT
    1111 *
    12  * @noinspection PhpUnused
     12 * @noinspection PhpUnused, PhpComposerExtensionStubsInspection
    1313 */
     14declare(strict_types=1);
    1415
    1516namespace chillerlan\QRCode;
    1617
    17 use function array_values, count, in_array, is_numeric, max, min, sprintf, strtolower;
     18use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
     19use chillerlan\QRCode\Output\QRMarkupSVG;
     20use function constant, in_array, is_string, max, min, sprintf, strtolower, strtoupper, trim;
     21use const JSON_THROW_ON_ERROR, JSON_UNESCAPED_SLASHES, PHP_EOL;
    1822
    1923/**
    2024 * The QRCode plug-in settings & setter functionality
     25 *
     26 * @property int               $version
     27 * @property int               $versionMin
     28 * @property int               $versionMax
     29 * @property int               $eccLevel
     30 * @property int               $maskPattern
     31 * @property bool              $addQuietzone
     32 * @property int               $quietzoneSize
     33 * @property string            $outputInterface
     34 * @property bool              $returnResource
     35 * @property string|null       $cachefile
     36 * @property bool              $outputBase64
     37 * @property string            $eol
     38 * @property mixed             $bgColor
     39 * @property bool              $invertMatrix
     40 * @property bool              $drawLightModules
     41 * @property bool              $drawCircularModules
     42 * @property float             $circleRadius
     43 * @property int[]             $keepAsSquare
     44 * @property bool              $connectPaths
     45 * @property int[]             $excludeFromConnect
     46 * @property array<int, mixed> $moduleValues
     47 * @property bool              $addLogoSpace
     48 * @property int|null          $logoSpaceWidth
     49 * @property int|null          $logoSpaceHeight
     50 * @property int|null          $logoSpaceStartX
     51 * @property int|null          $logoSpaceStartY
     52 * @property int               $scale
     53 * @property bool              $imageTransparent
     54 * @property mixed             $transparencyColor
     55 * @property int               $quality
     56 * @property bool              $gdImageUseUpscale
     57 * @property string            $imagickFormat
     58 * @property string            $cssClass
     59 * @property bool              $svgAddXmlHeader
     60 * @property string            $svgDefs
     61 * @property string            $svgPreserveAspectRatio
     62 * @property bool              $svgUseFillAttributes
     63 * @property string            $textLineStart
     64 * @property int               $jsonFlags
     65 * @property string            $fpdfMeasureUnit
     66 * @property string|null       $xmlStylesheet
    2167 */
    2268trait QROptionsTrait{
    2369
     70        /*
     71         * QR Code specific settings
     72         */
     73
    2474        /**
    2575         * QR Code version number
    2676         *
    27          * [1 ... 40] or QRCode::VERSION_AUTO
    28          */
    29         protected int $version = QRCode::VERSION_AUTO;
     77         * `1 ... 40` or `Version::AUTO` (default)
     78         *
     79         * @see \chillerlan\QRCode\Common\Version
     80         */
     81        protected int $version = Version::AUTO;
    3082
    3183        /**
    3284         * Minimum QR version
    3385         *
    34          * if $version = QRCode::VERSION_AUTO
     86         * if `QROptions::$version` is set to `Version::AUTO` (default: 1)
    3587         */
    3688        protected int $versionMin = 1;
    … …  
    3890        /**
    3991         * Maximum QR version
     92         *
     93         * if `QROptions::$version` is set to `Version::AUTO` (default: 40)
    4094         */
    4195        protected int $versionMax = 40;
    … …  
    4498         * Error correct level
    4599         *
    46          * QRCode::ECC_X where X is:
    47          *
    48          *   - L =>  7%
    49          *   - M => 15%
    50          *   - Q => 25%
    51          *   - H => 30%
    52          */
    53         protected int $eccLevel = QRCode::ECC_L;
    54 
    55         /**
    56          * Mask Pattern to use
    57          *
    58          * [0...7] or QRCode::MASK_PATTERN_AUTO
    59          */
    60         protected int $maskPattern = QRCode::MASK_PATTERN_AUTO;
     100         * the constant `EccLevel::X` where `X` is:
     101         *
     102         * - `L` =>  7% (default)
     103         * - `M` => 15%
     104         * - `Q` => 25%
     105         * - `H` => 30%
     106         *
     107         * alternatively you can just pass the letters L/M/Q/H (case-insensitive) to the magic setter
     108         *
     109         * @see \chillerlan\QRCode\Common\EccLevel
     110         * @see https://github.com/chillerlan/php-qrcode/discussions/160
     111         */
     112        protected int $eccLevel = EccLevel::L;
     113
     114        /**
     115         * Mask Pattern to use (no value in using, mostly for unit testing purposes)
     116         *
     117         * `0 ... 7` or `MaskPattern::PATTERN_AUTO` (default)
     118         *
     119         * @see \chillerlan\QRCode\Common\MaskPattern
     120         */
     121        protected int $maskPattern = MaskPattern::AUTO;
    61122
    62123        /**
    63124         * Add a "quiet zone" (margin) according to the QR code spec
     125         *
     126         * @see https://www.qrcode.com/en/howto/code.html
    64127         */
    65128        protected bool $addQuietzone = true;
    … …  
    68131         * Size of the quiet zone
    69132         *
    70          * internally clamped to [0 ... $moduleCount / 2], defaults to 4 modules
     133         * internally clamped to `0 ... $moduleCount / 2` (default: 4)
    71134         */
    72135        protected int $quietzoneSize = 4;
    73136
    74         /**
    75          * Use this to circumvent the data mode detection and force the usage of the given mode.
    76          *
    77          * valid modes are: Number, AlphaNum, Kanji, Byte (case insensitive)
    78          *
    79          * @see https://github.com/chillerlan/php-qrcode/issues/39
    80          */
    81         protected ?string $dataModeOverride = null;
    82 
    83         /**
    84          * The output type
    85          *
    86          *   - QRCode::OUTPUT_MARKUP_XXXX where XXXX = HTML, SVG
    87          *   - QRCode::OUTPUT_IMAGE_XXX where XXX = PNG, GIF, JPG
    88          *   - QRCode::OUTPUT_STRING_XXXX where XXXX = TEXT, JSON
    89          *   - QRCode::OUTPUT_CUSTOM
    90          */
    91         protected string $outputType = QRCode::OUTPUT_IMAGE_PNG;
    92 
    93         /**
    94          * the FQCN of the custom QROutputInterface if $outputType is set to QRCode::OUTPUT_CUSTOM
    95          */
    96         protected ?string $outputInterface = null;
    97 
    98         /**
    99          * /path/to/cache.file
    100          */
    101         protected ?string $cachefile = null;
    102 
    103         /**
    104          * newline string [HTML, SVG, TEXT]
     137
     138        /*
     139         * General output settings
     140         */
     141
     142        /**
     143         * The FQCN of the `QROutputInterface` to use
     144         */
     145        protected string $outputInterface = QRMarkupSVG::class;
     146
     147        /**
     148         * Return the image resource instead of a render if applicable.
     149         *
     150         * - `QRGdImage`: `resource` (PHP < 8), `GdImage`
     151         * - `QRImagick`: `Imagick`
     152         * - `QRFpdf`:    `FPDF`
     153         *
     154         * This option overrides/ignores other output settings, such as `QROptions::$cachefile`
     155         * and `QROptions::$outputBase64`. (default: `false`)
     156         *
     157         * @see \chillerlan\QRCode\Output\QROutputInterface::dump()
     158         */
     159        protected bool $returnResource = false;
     160
     161        /**
     162         * Optional cache file path `/path/to/cache.file`
     163         *
     164         * Please note that the `$file` parameter in `QRCode::render()` and `QRCode::renderMatrix()`
     165         * takes precedence over the `QROptions::$cachefile` value. (default: `null`)
     166         *
     167         * @see \chillerlan\QRCode\QRCode::render()
     168         * @see \chillerlan\QRCode\QRCode::renderMatrix()
     169         */
     170        protected string|null $cachefile = null;
     171
     172        /**
     173         * Toggle base64 data URI or raw data output (if applicable)
     174         *
     175         * (default: `true`)
     176         *
     177         * @see \chillerlan\QRCode\Output\QROutputAbstract::toBase64DataURI()
     178         */
     179        protected bool $outputBase64 = true;
     180
     181        /**
     182         * Newline string
     183         *
     184         * (default: `PHP_EOL`)
    105185         */
    106186        protected string $eol = PHP_EOL;
    107187
    108         /**
    109          * size of a QR code pixel [SVG, IMAGE_*], HTML via CSS
     188
     189        /*
     190         * Common visual modifications
     191         */
     192
     193        /**
     194         * Sets the image background color (if applicable)
     195         *
     196         * - `QRImagick`: defaults to `"white"`
     197         * - `QRGdImage`: defaults to `[255, 255, 255]`
     198         * - `QRFpdf`: defaults to blank internally (white page)
     199         */
     200        protected mixed $bgColor = null;
     201
     202        /**
     203         * Whether to invert the matrix (reflectance reversal)
     204         *
     205         * (default: `false`)
     206         *
     207         * @see \chillerlan\QRCode\Data\QRMatrix::invert()
     208         */
     209        protected bool $invertMatrix = false;
     210
     211        /**
     212         * Whether to draw the light (false) modules
     213         *
     214         * (default: `true`)
     215         */
     216        protected bool $drawLightModules = true;
     217
     218        /**
     219         * Specify whether to draw the modules as filled circles
     220         *
     221         * a note for `GdImage` output:
     222         *
     223         * if `QROptions::$scale` is less than 20, the image will be upscaled internally, then the modules will be drawn
     224         * using `imagefilledellipse()` and then scaled back to the expected size
     225         *
     226         * No effect in: `QREps`, `QRFpdf`, `QRMarkupHTML`
     227         *
     228         * @see \imagefilledellipse()
     229         * @see https://github.com/chillerlan/php-qrcode/issues/23
     230         * @see https://github.com/chillerlan/php-qrcode/discussions/122
     231         */
     232        protected bool $drawCircularModules = false;
     233
     234        /**
     235         * Specifies the radius of the modules when `QROptions::$drawCircularModules` is set to `true`
     236         *
     237         * (default: 0.45)
     238         */
     239        protected float $circleRadius = 0.45;
     240
     241        /**
     242         * Specifies which module types to exclude when `QROptions::$drawCircularModules` is set to `true`
     243         *
     244         * (default: `[]`)
     245         *
     246         * @var int[]
     247         */
     248        protected array $keepAsSquare = [];
     249
     250        /**
     251         * Whether to connect the paths for the several module types to avoid weird glitches when using gradients etc.
     252         *
     253         * This option is exclusive to output classes that use the module collector `QROutputAbstract::collectModules()`,
     254         * which converts the `$M_TYPE` of all modules to `QRMatrix::M_DATA` and `QRMatrix::M_DATA_DARK` respectively.
     255         *
     256         * Module types that should not be added to the connected path can be excluded via `QROptions::$excludeFromConnect`.
     257         *
     258         * Currentty used in `QREps` and `QRMarkupSVG`.
     259         *
     260         * @see \chillerlan\QRCode\Output\QROutputAbstract::collectModules()
     261         * @see \chillerlan\QRCode\QROptionsTrait::$excludeFromConnect
     262         * @see https://github.com/chillerlan/php-qrcode/issues/57
     263         */
     264        protected bool $connectPaths = false;
     265
     266        /**
     267         * Specify which paths/patterns to exclude from connecting if `QROptions::$connectPaths` is set to `true`
     268         *
     269         * @see \chillerlan\QRCode\QROptionsTrait::$connectPaths
     270         *
     271         * @var int[]
     272         */
     273        protected array $excludeFromConnect = [];
     274
     275        /**
     276         * Module values map
     277         *
     278         * - `QRImagick`, `QRMarkupHTML`, `QRMarkupSVG`: #ABCDEF, cssname, rgb(), rgba()...
     279         * - `QREps`, `QRFpdf`, `QRGdImage`: `[R, G, B]` // 0-255
     280         * - `QREps`: `[C, M, Y, K]` // 0-255
     281         *
     282         * @see \chillerlan\QRCode\Output\QROutputAbstract::setModuleValues()
     283         *
     284         * @var array<int, mixed>
     285         */
     286        protected array $moduleValues = [];
     287
     288        /**
     289         * Toggles logo space creation
     290         *
     291         * @see \chillerlan\QRCode\QRCode::addMatrixModifications()
     292         * @see \chillerlan\QRCode\Data\QRMatrix::setLogoSpace()
     293         */
     294        protected bool $addLogoSpace = false;
     295
     296        /**
     297         * Width of the logo space
     298         *
     299         * if only `QROptions::$logoSpaceWidth` is given, the logo space is assumed a square of that size
     300         */
     301        protected int|null $logoSpaceWidth = null;
     302
     303        /**
     304         * Height of the logo space
     305         *
     306         * if only `QROptions::$logoSpaceHeight` is given, the logo space is assumed a square of that size
     307         */
     308        protected int|null $logoSpaceHeight = null;
     309
     310        /**
     311         * Optional horizontal start position of the logo space (top left corner)
     312         */
     313        protected int|null $logoSpaceStartX = null;
     314
     315        /**
     316         * Optional vertical start position of the logo space (top left corner)
     317         */
     318        protected int|null $logoSpaceStartY = null;
     319
     320
     321        /*
     322         * Common raster image settings (QRGdImage, QRImagick)
     323         */
     324
     325        /**
     326         * Pixel size of a QR code module
    110327         */
    111328        protected int $scale = 5;
    112329
    113330        /**
    114          * a common css class
    115          */
    116         protected string $cssClass = '';
    117 
    118         /**
    119          * SVG opacity
    120          */
    121         protected float $svgOpacity = 1.0;
    122 
    123         /**
    124          * anything between <defs>
    125          *
    126          * @see https://developer.mozilla.org/docs/Web/SVG/Element/defs
    127          */
    128         protected string $svgDefs = '<style>rect{shape-rendering:crispEdges}</style>';
    129 
    130         /**
    131          * SVG viewBox size. a single integer number which defines width/height of the viewBox attribute.
    132          *
    133          * viewBox="0 0 x x"
    134          *
    135          * @see https://css-tricks.com/scale-svg/#article-header-id-3
    136          */
    137         protected ?int $svgViewBoxSize = null;
    138 
    139         /**
    140          * string substitute for dark
    141          */
    142         protected string $textDark = '🔴';
    143 
    144         /**
    145          * string substitute for light
    146          */
    147         protected string $textLight = '⭕';
    148 
    149         /**
    150          * markup substitute for dark (CSS value)
    151          */
    152         protected string $markupDark = '#000';
    153 
    154         /**
    155          * markup substitute for light (CSS value)
    156          */
    157         protected string $markupLight = '#fff';
    158 
    159         /**
    160          * Return the image resource instead of a render if applicable.
    161          * This option overrides other output options, such as $cachefile and $imageBase64.
    162          *
    163          * Supported by the following modules:
    164          *
    165          * - QRImage:   resource (PHP < 8), GdImage
    166          * - QRImagick: Imagick
    167          * - QRFpdf:    FPDF
    168          *
    169          * @see \chillerlan\QRCode\Output\QROutputInterface::dump()
    170          *
    171          * @var bool
    172          */
    173         protected bool $returnResource = false;
    174 
    175         /**
    176          * toggle base64 or raw image data
    177          */
    178         protected bool $imageBase64 = true;
    179 
    180         /**
    181          * toggle transparency, not supported by jpg
    182          */
    183         protected bool $imageTransparent = true;
    184 
    185         /**
    186          * @see imagecolortransparent()
    187          *
    188          * [R, G, B]
    189          */
    190         protected array $imageTransparencyBG = [255, 255, 255];
    191 
    192         /**
    193          * @see imagepng()
    194          */
    195         protected int $pngCompression = -1;
    196 
    197         /**
    198          * @see imagejpeg()
    199          */
    200         protected int $jpegQuality = 85;
     331         * Toggle transparency
     332         *
     333         * - `QRGdImage` and `QRImagick`: the given `QROptions::$transparencyColor` is set as transparent
     334         *
     335         * @see https://github.com/chillerlan/php-qrcode/discussions/121
     336         */
     337        protected bool $imageTransparent = false;
     338
     339        /**
     340         * Sets a transparency color for when `QROptions::$imageTransparent` is set to `true`.
     341         *
     342         * Defaults to `QROptions::$bgColor`.
     343         *
     344         * - `QRGdImage`: `[R, G, B]`, this color is set as transparent in `imagecolortransparent()`
     345         * - `QRImagick`: `"color_str"`, this color is set in `Imagick::transparentPaintImage()`
     346         *
     347         * @see \imagecolortransparent()
     348         * @see \Imagick::transparentPaintImage()
     349         */
     350        protected mixed $transparencyColor = null;
     351
     352        /**
     353         * Compression quality
     354         *
     355         * The given value depends on the used output type:
     356         *
     357         * - `QRGdImageBMP`:  `[0...1]`
     358         * - `QRGdImageJPEG`: `[0...100]`
     359         * - `QRGdImageWEBP`: `[0...9]`
     360         * - `QRGdImagePNG`:  `[0...100]`
     361         * - `QRImagick`:     `[0...100]`
     362         *
     363         * @see \imagebmp()
     364         * @see \imagejpeg()
     365         * @see \imagepng()
     366         * @see \imagewebp()
     367         * @see \Imagick::setImageCompressionQuality()
     368         */
     369        protected int $quality = -1;
     370
     371
     372        /*
     373         * QRGdImage settings
     374         */
     375
     376        /**
     377         * Toggles the usage of internal upscaling when `QROptions::$drawCircularModules` is set to `true` and
     378         * `QROptions::$scale` is less than 20
     379         *
     380         * @see \chillerlan\QRCode\Output\QRGdImage::createImage()
     381         * @see https://github.com/chillerlan/php-qrcode/issues/23
     382         */
     383        protected bool $gdImageUseUpscale = true;
     384
     385
     386        /*
     387         * QRImagick settings
     388         */
    201389
    202390        /**
    203391         * Imagick output format
    204392         *
    205          * @see \Imagick::setType()
    206          */
    207         protected string $imagickFormat = 'png';
    208 
    209         /**
    210          * Imagick background color (defaults to "transparent")
    211          *
    212          * @see \ImagickPixel::__construct()
    213          */
    214         protected ?string $imagickBG = null;
    215 
    216         /**
    217          * Measurement unit for FPDF output: pt, mm, cm, in (defaults to "pt")
    218          *
    219          * @see \FPDF::__construct()
     393         * @see \Imagick::setImageFormat()
     394         * @see https://www.imagemagick.org/script/formats.php
     395         */
     396        protected string $imagickFormat = 'png32';
     397
     398
     399        /*
     400         * Common markup output settings (QRMarkupSVG, QRMarkupHTML)
     401         */
     402
     403        /**
     404         * A common css class
     405         */
     406        protected string $cssClass = 'qrcode';
     407
     408
     409        /*
     410         * QRMarkupSVG settings
     411         */
     412
     413        /**
     414         * Whether to add an XML header line or not, e.g. to embed the SVG directly in HTML
     415         *
     416         * `<?xml version="1.0" encoding="UTF-8"?>`
     417         */
     418        protected bool $svgAddXmlHeader = true;
     419
     420        /**
     421         * Anything in the SVG `<defs>` tag
     422         *
     423         * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
     424         */
     425        protected string $svgDefs = '';
     426
     427        /**
     428         * Sets the value for the "preserveAspectRatio" on the `<svg>` element
     429         *
     430         * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio
     431         */
     432        protected string $svgPreserveAspectRatio = 'xMidYMid';
     433
     434        /**
     435         * Whether to use the SVG `fill` attributes
     436         *
     437         * If set to `true` (default), the `fill` attribute will be set with the module value for the `<path>` element's `$M_TYPE`.
     438         * When set to `false`, the module values map will be ignored and the QR Code may be styled via CSS.
     439         *
     440         * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill
     441         */
     442        protected bool $svgUseFillAttributes = true;
     443
     444
     445        /*
     446         * QRStringText settings
     447         */
     448
     449        /**
     450         * An optional line prefix, e.g. empty space to align the QR Code in a console
     451         */
     452        protected string $textLineStart = '';
     453
     454
     455        /*
     456         * QRStringJSON settings
     457         */
     458
     459        /**
     460         * Sets the flags to use for the `json_encode()` call
     461         *
     462         * @see https://www.php.net/manual/json.constants.php
     463         */
     464        protected int $jsonFlags = (JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
     465
     466
     467        /*
     468         * QRFpdf settings
     469         */
     470
     471        /**
     472         * Measurement unit for `FPDF` output: `pt`, `mm`, `cm`, `in` (default: `pt`)
     473         *
     474         * @see FPDF::__construct()
    220475         */
    221476        protected string $fpdfMeasureUnit = 'pt';
    222477
    223         /**
    224          * Module values map
    225          *
    226          *   - HTML, IMAGICK: #ABCDEF, cssname, rgb(), rgba()...
    227          *   - IMAGE: [63, 127, 255] // R, G, B
    228          */
    229         protected ?array $moduleValues = null;
     478
     479        /*
     480         * QRMarkupXML settings
     481         */
     482
     483        /**
     484         * Sets an optional XSLT stylesheet in the XML output
     485         *
     486         * @see https://developer.mozilla.org/en-US/docs/Web/XSLT
     487         */
     488        protected string|null $xmlStylesheet = null;
     489
    230490
    231491        /**
    … …  
    255515
    256516        /**
    257          * sets the error correction level
     517         * sets/clamps the version number
     518         */
     519        protected function set_version(int $version):void{
     520                $this->version = ($version !== Version::AUTO) ? max(1, min(40, $version)) : Version::AUTO;
     521        }
     522
     523        /**
     524         * sets the ECC level
    258525         *
    259526         * @throws \chillerlan\QRCode\QRCodeException
    260527         */
    261         protected function set_eccLevel(int $eccLevel):void{
    262 
    263                 if(!isset(QRCode::ECC_MODES[$eccLevel])){
    264                         throw new QRCodeException(sprintf('Invalid error correct level: %s', $eccLevel));
     528        protected function set_eccLevel(int|string $eccLevel):void{
     529
     530                if(is_string($eccLevel)){
     531                        $ecc = strtoupper(trim($eccLevel));
     532
     533                        if(!in_array($ecc, ['L', 'M', 'Q', 'H'], true)){
     534                                throw new QRCodeException(sprintf('Invalid ECC level: "%s"', $ecc));
     535                        }
     536
     537                        // @todo: PHP 8.3+
     538                        // $eccLevel = EccLevel::{$ecc};
     539                        $eccLevel = constant(EccLevel::class.'::'.$ecc);
    265540                }
    266541
     542                /** @var int $eccLevel */
     543                if((0b11 & $eccLevel) !== $eccLevel){
     544                        throw new QRCodeException(sprintf('Invalid ECC level: "%s"', $eccLevel));
     545                }
     546
    267547                $this->eccLevel = $eccLevel;
    268548        }
    269549
    270550        /**
    271          * sets/clamps the mask pattern
    272          */
    273         protected function set_maskPattern(int $maskPattern):void{
    274 
    275                 if($maskPattern !== QRCode::MASK_PATTERN_AUTO){
    276                         $this->maskPattern = max(0, min(7, $maskPattern));
    277                 }
    278 
    279         }
    280 
    281         /**
    282          * sets the transparency background color
    283          *
    284          * @throws \chillerlan\QRCode\QRCodeException
    285          */
    286         protected function set_imageTransparencyBG(array $imageTransparencyBG):void{
    287 
    288                 // invalid value - set to white as default
    289                 if(count($imageTransparencyBG) < 3){
    290                         $this->imageTransparencyBG = [255, 255, 255];
    291 
    292                         return;
    293                 }
    294 
    295                 foreach($imageTransparencyBG as $k => $v){
    296 
    297                         // cut off exceeding items
    298                         if($k > 2){
    299                                 break;
    300                         }
    301 
    302                         if(!is_numeric($v)){
    303                                 throw new QRCodeException('Invalid RGB value.');
    304                         }
    305 
    306                         // clamp the values
    307                         $this->imageTransparencyBG[$k] = max(0, min(255, (int)$v));
    308                 }
    309 
    310                 // use the array values to not run into errors with the spread operator (...$arr)
    311                 $this->imageTransparencyBG = array_values($this->imageTransparencyBG);
    312         }
    313 
    314         /**
    315          * sets/clamps the version number
    316          */
    317         protected function set_version(int $version):void{
    318 
    319                 if($version !== QRCode::VERSION_AUTO){
    320                         $this->version = max(1, min(40, $version));
    321                 }
    322 
     551         * clamps the module scale at a minimum of 1 and a maximum of 50
     552         */
     553        protected function set_scale(int $scale):void{
     554                $this->scale = max(1, min($scale, 50));
     555        }
     556
     557        /**
     558         * sets/clamps the quiet zone size
     559         */
     560        protected function set_quietzoneSize(int $quietzoneSize):void{
     561                $this->quietzoneSize = max(0, min($quietzoneSize, 75));
    323562        }
    324563
    … …  
    338577        }
    339578
     579        /**
     580         * clamp the logo space values between 0 and maximum length (177 modules at version 40)
     581         */
     582        protected function clampLogoSpaceValue(int|null $value):int|null{
     583
     584                if($value === null){
     585                        return null;
     586                }
     587
     588                return (int)max(0, min(177, $value));
     589        }
     590
     591        /**
     592         * clamp/set logo space width
     593         */
     594        protected function set_logoSpaceWidth(int|null $value):void{
     595                $this->logoSpaceWidth = $this->clampLogoSpaceValue($value);
     596        }
     597
     598        /**
     599         * clamp/set logo space height
     600         */
     601        protected function set_logoSpaceHeight(int|null $value):void{
     602                $this->logoSpaceHeight = $this->clampLogoSpaceValue($value);
     603        }
     604
     605        /**
     606         * clamp/set horizontal logo space start
     607         */
     608        protected function set_logoSpaceStartX(int|null $value):void{
     609                $this->logoSpaceStartX = $this->clampLogoSpaceValue($value);
     610        }
     611
     612        /**
     613         * clamp/set vertical logo space start
     614         */
     615        protected function set_logoSpaceStartY(int|null $value):void{
     616                $this->logoSpaceStartY = $this->clampLogoSpaceValue($value);
     617        }
     618
     619        /**
     620         * clamp/set SVG circle radius
     621         */
     622        protected function set_circleRadius(float $circleRadius):void{
     623                $this->circleRadius = max(0.1, min(0.75, $circleRadius));
     624        }
     625
    340626}
  • wp-otp/trunk/vendor/chillerlan/php-settings-container/README.md

    r2466054 r3752647  
    11# chillerlan/php-settings-container
    22
    3 A container class for immutable settings objects. Not a DI container. PHP 7.4+
    4 - [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerInterface.php) provides immutable properties with magic getter & setter and some fancy
     3A container class for settings objects - decouple configuration logic from your application! Not a DI container.
     4- [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerInterface.php) provides fancy [property hooks](https://wiki.php.net/rfc/property-hooks) for PHP < 8.4.
    55
    66[![PHP Version Support][php-badge]][php]
    77[![version][packagist-badge]][packagist]
    88[![license][license-badge]][license]
    9 [![Travis][travis-badge]][travis]
     9[![Continuous Integration][gh-action-badge]][gh-action]
    1010[![Coverage][coverage-badge]][coverage]
    11 [![Scrunitizer][scrutinizer-badge]][scrutinizer]
    12 [![Packagist downloads][downloads-badge]][downloads]<br/>
    13 [![Continuous Integration][gh-action-badge]][gh-action]
     11[![Codacy][codacy-badge]][codacy]
     12[![Packagist downloads][downloads-badge]][downloads]
    1413
    1514[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-settings-container?logo=php&color=8892BF
    1615[php]: https://www.php.net/supported-versions.php
    17 [packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-settings-container.svg
     16[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-settings-container.svg?logo=packagist
    1817[packagist]: https://packagist.org/packages/chillerlan/php-settings-container
    1918[license-badge]: https://img.shields.io/github/license/chillerlan/php-settings-container.svg
    2019[license]: https://github.com/chillerlan/php-settings-container/blob/main/LICENSE
    21 [travis-badge]: https://img.shields.io/travis/com/chillerlan/php-settings-container/main?logo=travis
    22 [travis]: https://travis-ci.org/chillerlan/php-settings-container
    2320[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-settings-container.svg?logo=codecov
    2421[coverage]: https://codecov.io/github/chillerlan/php-settings-container
    25 [scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-settings-container.svg?logo=scrutinizer
    26 [scrutinizer]: https://scrutinizer-ci.com/g/chillerlan/php-settings-container
    27 [downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-settings-container.svg
     22[codacy-badge]: https://img.shields.io/codacy/grade/bd2467799e2943d2853ce3ebad5af490/main?logo=codacy
     23[codacy]: https://app.codacy.com/gh/chillerlan/php-settings-container/dashboard?branch=main
     24[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-settings-container.svg?logo=packagist
    2825[downloads]: https://packagist.org/packages/chillerlan/php-settings-container/stats
    29 [gh-action-badge]: https://github.com/chillerlan/php-settings-container/workflows/Continuous%20Integration/badge.svg
    30 [gh-action]: https://github.com/chillerlan/php-settings-container/actions?query=workflow%3A%22Continuous+Integration%22
     26[gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-settings-container/ci.yml?branch=main&logo=github
     27[gh-action]: https://github.com/chillerlan/php-settings-container/actions/workflows/ci.yml?query=branch%3Amain
    3128
    3229## Documentation
    … …  
    3532**requires [composer](https://getcomposer.org)**
    3633
    37 *composer.json* (note: replace `dev-main` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^2.0` - see [releases](https://github.com/chillerlan/php-settings-container/releases) for valid versions)
     34*composer.json* (note: replace `dev-main` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^3.0` - see [releases](https://github.com/chillerlan/php-settings-container/releases) for valid versions)
    3835```json
    3936{
    4037        "require": {
    41                 "php": "^7.4",
     38                "php": "^8.1",
    4239                "chillerlan/php-settings-container": "dev-main"
    4340        }
    … …  
    4946## Usage
    5047
    51 The `SettingsContainerInterface` (wrapped in`SettingsContainerAbstract` ) provides plug-in functionality for immutable object properties and adds some fancy, like loading/saving JSON, arrays etc.
     48The `SettingsContainerInterface` (wrapped in`SettingsContainerAbstract`) provides plug-in functionality for immutable object properties and adds some fancy, like loading/saving JSON, arrays etc.
    5249It takes an `iterable` as the only constructor argument and calls a method with the trait's name on invocation (`MyTrait::MyTrait()`) for each used trait.
    5350
     51A PHPStan ruleset to exclude errors generated by accessing magic properties on `SettingsContainerInterface` can be found in `rules-magic-access.neon`.
     52
     53
    5454### Simple usage
    55 ```php
    56 class MyContainer extends SettingsContainerAbstract{
    57         protected $foo;
    58         protected $bar;
    59 }
    60 ```
    61 Typed properties in PHP 7.4+:
    6255```php
    6356class MyContainer extends SettingsContainerAbstract{
    … …  
    6861
    6962```php
    70 // use it just like a \stdClass
     63// use it just like a \stdClass (except the properties are fixed)
    7164$container = new MyContainer;
    7265$container->foo = 'what';
    7366$container->bar = 'foo';
    7467
    75 // which is equivalent to 
     68// which is equivalent to
    7669$container = new MyContainer(['bar' => 'foo', 'foo' => 'what']);
    7770// ...or try
    … …  
    8578// JSON via JsonSerializable
    8679$json = json_encode($container); // -> {"foo": "what", "bar": "foo"}
    87 
    88 //non-existing properties will be ignored:
     80```
     81
     82By default, non-existing properties will be ignored and return `null`:
     83
     84```php
    8985$container->nope = 'what';
    9086
    … …  
    9288```
    9389
     90You can change this behaviour by adding the attribute `ThrowOnInvalidProperty` to your container class:
     91
     92```php
     93#[ThrowOnInvalidProperty(true)]
     94class MyContainer extends SettingsContainerAbstract{
     95        // ...
     96}
     97
     98$container->nope = 'what'; // -> throws: attempt to write invalid property: "$nope"
     99```
     100
     101
    94102### Advanced usage
     103
     104Suppose the following trait from library 1:
     105
    95106```php
    96107trait SomeOptions{
    97         protected $foo;
    98         protected $what;
    99        
     108        protected string $foo;
     109        protected string $what;
     110
    100111        // this method will be called in SettingsContainerAbstract::construct()
    101112        // after the properties have been set
    102         protected function SomeOptions(){
     113        protected function SomeOptions():void{
    103114                // just some constructor stuff...
    104115                $this->foo = strtoupper($this->foo);
    105116        }
    106        
     117
     118        /*
     119         * special prefixed magic setters & getters ("set_"/"get_" + property name)
     120         */
     121
    107122        // this method will be called from __set() when property $what is set
    108         protected function set_what(string $value){
     123        protected function set_what(string $value):void{
    109124                $this->what = md5($value);
    110125        }
    111 }
    112 
     126
     127        // this method is called on __get() for the property $what
     128        protected function get_what():string{
     129                return 'hash: '.$this->what;
     130        }
     131}
     132```
     133
     134And another trait from library 2:
     135
     136```php
    113137trait MoreOptions{
    114         protected $bar = 'whatever'; // provide default values
    115 }
    116 ```
     138        protected string $bar = 'whatever'; // provide default values
     139}
     140```
     141
     142We can now plug the several library options together to a single class/object:
    117143
    118144```php
    119145$commonOptions = [
    120146        // SomeOptions
    121         'foo' => 'whatever', 
     147        'foo' => 'whatever',
    122148        // MoreOptions
    123149        'bar' => 'nothing',
    124150];
    125151
    126 // now plug the several library options together to a single object
    127152$container = new class ($commonOptions) extends SettingsContainerAbstract{
    128153        use SomeOptions, MoreOptions;
    … …  
    133158
    134159$container->what = 'some value';
    135 var_dump($container->what); // -> md5 hash of "some value"
    136 ```
     160var_dump($container->what); // -> hash: 5946210c9e93ae37891dfe96c3e39614 (custom getter added "hash: ")
     161```
     162
     163### A note on property hooks (PHP 8.4+)
     164
     165Property hooks are called whenever a property is accessed (except from within the hook itself of course), which means that the custom get/set methods this library allows would conflict when a custom method is defined for a property that also has a hook defined.
     166To prevent double method calls, the internal methods `hasSetHook()` and `hasGetHook()` have been introduced, and are called whenever the magic get/set methods are called: when both, a custom method and a property hook exist, only the property hook will be called.
     167<br/>Public properties will never call the magic get/set, however, their hooks *will* be called. (un)serializing a `SettingsContainerInterface` instance will bypass magic get/set and existing property hooks, while JSON de/encode as will call magic get/set or existing hooks explicitly via the `toArray()` and `fromIterable()` methods.
     168
     169```php
     170class PropertyHooksContainer extends SettingsContainerAbstract{
     171
     172        protected string $someValue{
     173                set => doStuff($value);
     174        }
     175
     176        // this method will be ignored in magic calls as a "set" hook on the property exists
     177        protected function set_someValue(string $value):void{
     178                $this->someValue = doOtherStuff($value);
     179        }
     180
     181        // this custom method will be called as the property has no "get" hook
     182        protected function get_someValue():string{
     183                return doWhatever($this->someValue);
     184        }
     185
     186        // this property will never trigger the magic get/set and associated methods
     187        public string $otherValue{
     188                set => doStuff($value);
     189                get => $this->otherValue;
     190        }
     191
     192}
     193```
     194
    137195
    138196### API
    … …  
    140198#### [`SettingsContainerAbstract`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerAbstract.php)
    141199
    142 method | return  | info
    143 -------- | ----  | -----------
    144 `__construct(iterable $properties = null)` | - | calls `construct()` internally after the properties have been set
    145 (protected) `construct()` | void | calls a method with trait name as replacement constructor for each used trait
    146 `__get(string $property)` | mixed | calls `$this->{'get_'.$property}()` if such a method exists
    147 `__set(string $property, $value)` | void | calls `$this->{'set_'.$property}($value)` if such a method exists
    148 `__isset(string $property)` | bool |
    149 `__unset(string $property)` | void |
    150 `__toString()` | string | a JSON string
    151 `toArray()` | array |
    152 `fromIterable(iterable $properties)` | `SettingsContainerInterface` |
    153 `toJSON(int $jsonOptions = null)` | string | accepts [JSON options constants](http://php.net/manual/json.constants.php)
    154 `fromJSON(string $json)` | `SettingsContainerInterface` |
    155 `jsonSerialize()` | mixed | implements the [`JsonSerializable`](https://www.php.net/manual/en/jsonserializable.jsonserialize.php) interface
     200| method                                     | return                       | info                                                                                                                |
     201|--------------------------------------------|------------------------------|---------------------------------------------------------------------------------------------------------------------|
     202| `__construct(iterable $properties = null)` | -                            | calls `construct()` internally after the properties have been set                                                   |
     203| `__get(string $property)`                  | mixed                        | calls `$this->{'get_'.$property}()` if such a method exists                                                         |
     204| `__set(string $property, $value)`          | void                         | calls `$this->{'set_'.$property}($value)` if such a method exists                                                   |
     205| `__isset(string $property)`                | bool                         |                                                                                                                     |
     206| `__unset(string $property)`                | void                         |                                                                                                                     |
     207| `__toString()`                             | string                       | a JSON string                                                                                                       |
     208| `toArray()`                                | array                        |                                                                                                                     |
     209| `fromIterable(iterable $properties)`       | `SettingsContainerInterface` |                                                                                                                     |
     210| `toJSON(int $jsonOptions = null)`          | string                       | accepts [JSON options constants](http://php.net/manual/json.constants.php)                                          |
     211| `fromJSON(string $json)`                   | `SettingsContainerInterface` |                                                                                                                     |
     212| `jsonSerialize()`                          | mixed                        | implements the [`JsonSerializable`](https://www.php.net/manual/en/jsonserializable.jsonserialize.php) interface     |
     213| `serialize()`                              | string                       | implements the [`Serializable`](https://www.php.net/manual/en/serializable.serialize.php) interface                 |
     214| `unserialize(string $data)`                | void                         | implements the [`Serializable`](https://www.php.net/manual/en/serializable.unserialize.php) interface               |
     215| `__serialize()`                            | array                        | implements the [`Serializable`](https://www.php.net/manual/en/language.oop5.magic.php#object.serialize) interface   |
     216| `__unserialize(array $data)`               | void                         | implements the [`Serializable`](https://www.php.net/manual/en/language.oop5.magic.php#object.unserialize) interface |
     217
     218
     219#### Internal (protected) methods
     220
     221| method                               | return | info                                                                          |
     222|--------------------------------------|--------|-------------------------------------------------------------------------------|
     223| `construct()`                        | void   | calls a method with trait name as replacement constructor for each used trait |
     224| `isPrivate(string $property)`        | bool   | private properties are excluded from magic calls                              |
     225| `hasSetHook(string $property)`       | bool   |                                                                               |
     226| `hasGetHook(string $property)`       | bool   |                                                                               |
     227
    156228
    157229## Disclaimer
    158 This might be either an utterly genius or completely stupid idea - you decide. However, i like it and it works.
    159 Also, this is not a dependency injection container. Stop using DI containers FFS.
     230This might be either an absolutely brilliant or completely stupid idea - you decide (in hindsight it was a great idea I guess - property hooks made their way into PHP 8.4).
     231<br/>Also, this is not a dependency injection container. Stop using DI containers FFS.
  • wp-otp/trunk/vendor/chillerlan/php-settings-container/composer.json

    r2466054 r3752647  
    11{
    22        "name": "chillerlan/php-settings-container",
    3         "description": "A container class for immutable settings objects. Not a DI container. PHP 7.4+",
     3        "description": "A container class for immutable settings objects. Not a DI container.",
    44        "homepage": "https://github.com/chillerlan/php-settings-container",
    55        "license": "MIT",
    … …  
    77        "minimum-stability": "stable",
    88        "keywords": [
    9                 "php7", "helper", "container", "settings"
     9                "helper", "container", "settings", "configuration", "property hook"
    1010        ],
    1111        "authors": [
    … …  
    2121        },
    2222        "require": {
    23                 "php": "^7.4 || ^8.0",
     23                "php": "^8.1",
    2424                "ext-json": "*"
    2525        },
    2626        "require-dev": {
    27                 "phan/phan": "^4.0",
    28                 "phpunit/phpunit": "^9.5"
     27                "phan/phan": "^5.5.2",
     28                "phpmd/phpmd": "^2.15",
     29                "phpstan/phpstan": "^2.1.31",
     30                "phpstan/phpstan-deprecation-rules": "^2.0.3",
     31                "phpunit/phpunit": "^10.5",
     32                "slevomat/coding-standard": "^8.22",
     33                "squizlabs/php_codesniffer": "^4.0"
    2934        },
    3035        "autoload": {
    3136                "psr-4": {
    32                         "chillerlan\\Settings\\": "src/"
     37                        "chillerlan\\Settings\\": "src"
    3338                }
    3439        },
    3540        "autoload-dev": {
    3641                "psr-4": {
    37                         "chillerlan\\SettingsTest\\": "tests/",
    38                         "chillerlan\\SettingsExamples\\": "examples/"
     42                        "chillerlan\\SettingsTest\\": "tests"
     43                }
     44        },
     45        "scripts": {
     46                "phan": "@php vendor/bin/phan",
     47                "phpcs": "@php vendor/bin/phpcs -v",
     48                "phpmd": "@php vendor/bin/phpmd src text ./phpmd.xml.dist",
     49                "phpstan": "@php vendor/bin/phpstan",
     50                "phpstan-baseline": "@php vendor/bin/phpstan --generate-baseline",
     51                "phpunit": "@php vendor/bin/phpunit"
     52        },
     53        "config": {
     54                "lock": false,
     55                "sort-packages": true,
     56                "platform-check": true,
     57                "allow-plugins": {
     58                        "dealerdirect/phpcodesniffer-composer-installer": true
    3959                }
    4060        }
  • wp-otp/trunk/vendor/chillerlan/php-settings-container/src/SettingsContainerAbstract.php

    r2466054 r3752647  
    33 * Class SettingsContainerAbstract
    44 *
    5  * @filesource   SettingsContainerAbstract.php
    65 * @created      28.08.2018
    7  * @package      chillerlan\Settings
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2018 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\Settings;
    1413
    15 use ReflectionClass, ReflectionProperty;
    16 
    17 use function call_user_func, call_user_func_array, get_object_vars, json_decode, json_encode, method_exists, property_exists;
     14use chillerlan\Settings\Attributes\ThrowOnInvalidProperty;
     15use InvalidArgumentException, JsonException, ReflectionException, ReflectionObject,
     16        ReflectionProperty, ReflectionAttribute, RuntimeException;
     17use function is_object, json_decode, json_encode, json_last_error_msg,
     18        method_exists, property_exists, serialize, sprintf, unserialize;
     19use const JSON_THROW_ON_ERROR, PHP_VERSION_ID;
    1820
    1921abstract class SettingsContainerAbstract implements SettingsContainerInterface{
    2022
     23        protected const SET_PREFIX = 'set_';
     24        protected const GET_PREFIX = 'get_';
     25
    2126        /**
    2227         * SettingsContainerAbstract constructor.
    2328         *
    24          * @param iterable|null $properties
    25          */
    26         public function __construct(iterable $properties = null){
     29         * @phpstan-param array<string, mixed> $properties
     30         */
     31        public function __construct(iterable|null $properties = null){
    2732
    2833                if(!empty($properties)){
    … …  
    3641         * calls a method with trait name as replacement constructor for each used trait
    3742         * (remember pre-php5 classname constructors? yeah, basically this.)
    38          *
    39          * @return void
    4043         */
    4144        protected function construct():void{
    42                 $traits = (new ReflectionClass($this))->getTraits();
     45                $traits = (new ReflectionObject($this))->getTraits();
    4346
    4447                foreach($traits as $trait){
    … …  
    4649
    4750                        if(method_exists($this, $method)){
    48                                 call_user_func([$this, $method]);
    49                         }
    50                 }
    51 
    52         }
    53 
    54         /**
    55          * @inheritdoc
    56          *
    57          * @return mixed|null
    58          */
    59         public function __get(string $property){
    60 
     51                                $this->{$method}();
     52                        }
     53                }
     54
     55        }
     56
     57        public function __get(string $property):mixed{
     58                // back out if the property is inaccessible
    6159                if(!property_exists($this, $property) || $this->isPrivate($property)){
     60
     61                        if($this->throwOnInvalidProperty()){
     62                                throw new RuntimeException(sprintf('attempt to read invalid property: "$%s"', $property));
     63                        }
     64
    6265                        return null;
    6366                }
    64 
    65                 $method = 'get_'.$property;
    66 
    67                 if(method_exists($this, $method)){
    68                         return call_user_func([$this, $method]);
    69                 }
    70 
     67                // call an existing custom method, skip if the property has a hook
     68                if(method_exists($this, static::GET_PREFIX.$property) && !$this->hasGetHook($property)){
     69                        return $this->{static::GET_PREFIX.$property}();
     70                }
     71                // retrieve the value (triggers an existing property hook)
    7172                return $this->{$property};
    7273        }
    7374
    74         /**
    75          * @inheritdoc
    76          */
    77         public function __set(string $property, $value):void{
     75        public function __set(string $property, mixed $value):void{
    7876
    7977                if(!property_exists($this, $property) || $this->isPrivate($property)){
     78
     79                        if($this->throwOnInvalidProperty()){
     80                                throw new RuntimeException(sprintf('attempt to write invalid property: "$%s"', $property));
     81                        }
     82
    8083                        return;
    8184                }
    8285
    83                 $method = 'set_'.$property;
    84 
    85                 if(method_exists($this, $method)){
    86                         call_user_func_array([$this, $method], [$value]);
     86                if(method_exists($this, static::SET_PREFIX.$property) && !$this->hasSetHook($property)){
     87                        $this->{static::SET_PREFIX.$property}($value);
    8788
    8889                        return;
    … …  
    9293        }
    9394
    94         /**
    95          * @inheritdoc
    96          */
    9795        public function __isset(string $property):bool{
    9896                return isset($this->{$property}) && !$this->isPrivate($property);
    9997        }
    10098
    101         /**
    102          * @internal Checks if a property is private
    103          *
    104          * @param string $property
    105          *
    106          * @return bool
    107          */
    108         protected function isPrivate(string $property):bool{
    109                 return (new ReflectionProperty($this, $property))->isPrivate();
    110         }
    111 
    112         /**
    113          * @inheritdoc
    114          */
    11599        public function __unset(string $property):void{
    116100
    … …  
    121105        }
    122106
    123         /**
    124          * @inheritdoc
    125          */
    126107        public function __toString():string{
    127108                return $this->toJSON();
    … …  
    129110
    130111        /**
    131          * @inheritdoc
    132          */
     112         * @internal Checks if a property is private
     113         */
     114        final protected function isPrivate(string $property):bool{
     115                return (new ReflectionProperty($this, $property))->isPrivate();
     116        }
     117
     118        /**
     119         * @internal Checks if a property has a "set" hook
     120         */
     121        final protected function hasSetHook(string $property):bool{
     122
     123                if(PHP_VERSION_ID < 80400){
     124                        return false;
     125                }
     126                /** @phan-suppress-next-line PhanUndeclaredMethod, PhanUndeclaredClassConstant */
     127                return (new ReflectionProperty($this, $property))->hasHook(\PropertyHookType::Set);
     128        }
     129
     130        /**
     131         * @internal Checks if a property has a "get" hook
     132         */
     133        final protected function hasGetHook(string $property):bool{
     134
     135                if(PHP_VERSION_ID < 80400){
     136                        return false;
     137                }
     138                /** @phan-suppress-next-line PhanUndeclaredMethod, PhanUndeclaredClassConstant */
     139                return (new ReflectionProperty($this, $property))->hasHook(\PropertyHookType::Get);
     140        }
     141
     142        /**
     143         * @internal Checks for the attribute "ThrowOnInvalidProperty", used in the magic get/set
     144         *
     145         * @see \chillerlan\Settings\Attributes\ThrowOnInvalidProperty
     146         */
     147        final protected function throwOnInvalidProperty():bool{
     148
     149                $attributes = (new ReflectionObject($this))
     150                        ->getAttributes(ThrowOnInvalidProperty::class, ReflectionAttribute::IS_INSTANCEOF)
     151                ;
     152
     153                if($attributes === []){
     154                        return false;
     155                }
     156                /** @var \chillerlan\Settings\Attributes\ThrowOnInvalidProperty $attr */
     157                $attr = $attributes[0]->newInstance();
     158
     159                return $attr->throwOnInvalid;
     160        }
     161
    133162        public function toArray():array{
    134                 return get_object_vars($this);
    135         }
    136 
    137         /**
    138          * @inheritdoc
    139          */
    140         public function fromIterable(iterable $properties):SettingsContainerInterface{
     163
     164                $properties = (new ReflectionObject($this))
     165                        ->getProperties(~(ReflectionProperty::IS_STATIC | ReflectionProperty::IS_READONLY | ReflectionProperty::IS_PRIVATE))
     166                ;
     167
     168                $data = [];
     169
     170                foreach($properties as $reflectionProperty){
     171                        // the magic getter is called intentionally here, so that any existing hook methods are called on export
     172                        $data[$reflectionProperty->name] = $this->__get($reflectionProperty->name);
     173                }
     174
     175                return $data;
     176        }
     177
     178        /**
     179         * @param iterable<string, mixed> $properties
     180         */
     181        public function fromIterable(iterable $properties):static{
    141182
    142183                foreach($properties as $key => $value){
    … …  
    147188        }
    148189
    149         /**
    150          * @inheritdoc
    151          */
    152         public function toJSON(int $jsonOptions = null):string{
    153                 return json_encode($this, $jsonOptions ?? 0);
    154         }
    155 
    156         /**
    157          * @inheritdoc
    158          */
    159         public function fromJSON(string $json):SettingsContainerInterface{
     190        public function toJSON(int|null $jsonOptions = null):string{
     191                $json = json_encode($this, ($jsonOptions ?? 0));
     192
     193                if($json === false){
     194                        throw new JsonException(json_last_error_msg()); // @codeCoverageIgnore
     195                }
     196
     197                return $json;
     198        }
     199
     200        public function fromJSON(string $json):static{
     201                /** @phpstan-var array<string, mixed> $data */
    160202                $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
    161203
    … …  
    164206
    165207        /**
    166          * @inheritdoc
     208         * @return array<string, mixed>
    167209         */
    168210        public function jsonSerialize():array{
    … …  
    170212        }
    171213
     214        /**
     215         * Returns a serialized string representation of the object in its current state (except static/readonly properties)
     216         */
     217        public function serialize():string{
     218                return serialize($this);
     219        }
     220
     221        /**
     222         * Restores the data (except static/readonly properties) from the given serialized object to the current instance
     223         *
     224         * @throws \InvalidArgumentException
     225         */
     226        public function unserialize(string $data):void{
     227                $obj = unserialize($data);
     228
     229                if(!is_object($obj)){
     230                        throw new InvalidArgumentException('The given serialized string is invalid');
     231                }
     232
     233                $reflection = new ReflectionObject($obj);
     234
     235                if(!$reflection->isInstance($this)){
     236                        throw new InvalidArgumentException('The unserialized object does not match the class of this container');
     237                }
     238
     239                $properties = $reflection->getProperties(~(ReflectionProperty::IS_STATIC | ReflectionProperty::IS_READONLY));
     240                $data       = [];
     241
     242                foreach($properties as $reflectionProperty){
     243                        $data[$reflectionProperty->name] = (PHP_VERSION_ID < 80400)
     244                                ? $reflectionProperty->getValue($obj)
     245                                /** @phan-suppress-next-line PhanUndeclaredMethod */
     246                                : $reflectionProperty->getRawValue($obj);
     247                }
     248
     249                $this->__unserialize($data);
     250        }
     251
     252        /**
     253         * Returns a serialized array representation of the object in its current state (except static/readonly properties),
     254         * bypassing custom getters and property hooks
     255         *
     256         * @return array<string, mixed>
     257         */
     258        public function __serialize():array{
     259
     260                $properties = (new ReflectionObject($this))
     261                        ->getProperties(~(ReflectionProperty::IS_STATIC | ReflectionProperty::IS_READONLY))
     262                ;
     263
     264                $data = [];
     265
     266                foreach($properties as $reflectionProperty){
     267                        // bypass existing property hooks for PHP >= 8.4
     268                        $data[$reflectionProperty->name] = (PHP_VERSION_ID < 80400)
     269                                ? $reflectionProperty->getValue($this)
     270                                /** @phan-suppress-next-line PhanUndeclaredMethod */
     271                                : $reflectionProperty->getRawValue($this);
     272                }
     273
     274                return $data;
     275        }
     276
     277        /**
     278         * Restores the data from the given array to the current instance,
     279         * bypassing custom setters and property hooks
     280         *
     281         * @param array<string, mixed> $data
     282         */
     283        public function __unserialize(array $data):void{
     284                $reflection = new ReflectionObject($this);
     285
     286                foreach($data as $key => $value){
     287                        try{
     288                                $reflectionProperty = $reflection->getProperty($key);
     289
     290                                if($reflectionProperty->isStatic() || $reflectionProperty->isReadOnly()){
     291                                        continue; // @codeCoverageIgnore
     292                                }
     293
     294                                (PHP_VERSION_ID < 80400)
     295                                        ? $reflectionProperty->setValue($this, $value)
     296                                        /** @phan-suppress-next-line PhanUndeclaredMethod */
     297                                        : $reflectionProperty->setRawValue($this, $value);
     298
     299                        }
     300                        // @codeCoverageIgnoreStart
     301                        catch(ReflectionException){
     302                                // attempt to assign a non-existent property, skip
     303                                continue;
     304                        }
     305                        // @codeCoverageIgnoreEnd
     306                }
     307
     308        }
     309
    172310}
  • wp-otp/trunk/vendor/chillerlan/php-settings-container/src/SettingsContainerInterface.php

    r2331970 r3752647  
    33 * Interface SettingsContainerInterface
    44 *
    5  * @filesource   SettingsContainerInterface.php
    65 * @created      28.08.2018
    7  * @package      chillerlan\Settings
    86 * @author       Smiley <smiley@chillerlan.net>
    97 * @copyright    2018 Smiley
    108 * @license      MIT
    119 */
     10declare(strict_types=1);
    1211
    1312namespace chillerlan\Settings;
    1413
    15 use JsonSerializable;
     14use JsonSerializable, Serializable;
    1615
    1716/**
    1817 * a generic container with magic getter and setter
    1918 */
    20 interface SettingsContainerInterface extends JsonSerializable{
     19interface SettingsContainerInterface extends JsonSerializable, Serializable{
    2120
    2221        /**
    2322         * Retrieve the value of $property
    2423         *
    25          * @param string $property
    26          *
    27          * @return mixed
     24         * @return mixed|null
    2825         */
    29         public function __get(string $property);
     26        public function __get(string $property):mixed;
    3027
    3128        /**
    3229         * Set $property to $value while avoiding private and non-existing properties
    33          *
    34          * @param string $property
    35          * @param mixed  $value
    36          *
    37          * @return void
    3830         */
    39         public function __set(string $property, $value):void;
     31        public function __set(string $property, mixed $value):void;
    4032
    4133        /**
    4234         * Checks if $property is set (aka. not null), excluding private properties
    43          *
    44          * @param string $property
    45          *
    46          * @return bool
    4735         */
    4836        public function __isset(string $property):bool;
    … …  
    5038        /**
    5139         * Unsets $property while avoiding private and non-existing properties
    52          *
    53          * @param string $property
    54          *
    55          * @return void
    5640         */
    5741        public function __unset(string $property):void;
    5842
    5943        /**
    60          * @see SettingsContainerInterface::toJSON()
    61          *
    62          * @return string
     44         * @see \chillerlan\Settings\SettingsContainerInterface::toJSON()
    6345         */
    6446        public function __toString():string;
    … …  
    6749         * Returns an array representation of the settings object
    6850         *
    69          * @return array
     51         * The values will be run through the magic __get(), which may also call custom getters.
     52         *
     53         * @return array<string, mixed>
    7054         */
    7155        public function toArray():array;
    … …  
    7458         * Sets properties from a given iterable
    7559         *
    76          * @param iterable $properties
     60         * The values will be run through the magic __set(), which may also call custom setters.
    7761         *
    78          * @return \chillerlan\Settings\SettingsContainerInterface
     62         * @param iterable<string, mixed> $properties
    7963         */
    80         public function fromIterable(iterable $properties):SettingsContainerInterface;
     64        public function fromIterable(iterable $properties):static;
    8165
    8266        /**
    8367         * Returns a JSON representation of the settings object
     68         *
    8469         * @see \json_encode()
     70         * @see \chillerlan\Settings\SettingsContainerInterface::toArray()
    8571         *
    86          * @param int|null $jsonOptions
    87          *
    88          * @return string
     72         * @throws \JsonException
    8973         */
    90         public function toJSON(int $jsonOptions = null):string;
     74        public function toJSON(int|null $jsonOptions = null):string;
    9175
    9276        /**
    9377         * Sets properties from a given JSON string
    9478         *
    95          * @param string $json
    96          *
    97          * @return \chillerlan\Settings\SettingsContainerInterface
     79         * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable()
    9880         *
    9981         * @throws \Exception
    10082         * @throws \JsonException
    10183         */
    102         public function fromJSON(string $json):SettingsContainerInterface;
     84        public function fromJSON(string $json):static;
    10385
    10486}
  • wp-otp/trunk/vendor/composer/ClassLoader.php

    r2477335 r3752647  
    4343class ClassLoader
    4444{
     45    /** @var \Closure(string):void */
     46    private static $includeFile;
     47
     48    /** @var string|null */
    4549    private $vendorDir;
    4650
    4751    // PSR-4
     52    /**
     53     * @var array<string, array<string, int>>
     54     */
    4855    private $prefixLengthsPsr4 = array();
     56    /**
     57     * @var array<string, list<string>>
     58     */
    4959    private $prefixDirsPsr4 = array();
     60    /**
     61     * @var list<string>
     62     */
    5063    private $fallbackDirsPsr4 = array();
    5164
    5265    // PSR-0
     66    /**
     67     * List of PSR-0 prefixes
     68     *
     69     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
     70     *
     71     * @var array<string, array<string, list<string>>>
     72     */
    5373    private $prefixesPsr0 = array();
     74    /**
     75     * @var list<string>
     76     */
    5477    private $fallbackDirsPsr0 = array();
    5578
     79    /** @var bool */
    5680    private $useIncludePath = false;
     81
     82    /**
     83     * @var array<string, string>
     84     */
    5785    private $classMap = array();
     86
     87    /** @var bool */
    5888    private $classMapAuthoritative = false;
     89
     90    /**
     91     * @var array<string, bool>
     92     */
    5993    private $missingClasses = array();
     94
     95    /** @var string|null */
    6096    private $apcuPrefix;
    6197
     98    /**
     99     * @var array<string, self>
     100     */
    62101    private static $registeredLoaders = array();
    63102
     103    /**
     104     * @param string|null $vendorDir
     105     */
    64106    public function __construct($vendorDir = null)
    65107    {
    66108        $this->vendorDir = $vendorDir;
    67     }
    68 
     109        self::initializeIncludeClosure();
     110    }
     111
     112    /**
     113     * @return array<string, list<string>>
     114     */
    69115    public function getPrefixes()
    70116    {
    … …  
    76122    }
    77123
     124    /**
     125     * @return array<string, list<string>>
     126     */
    78127    public function getPrefixesPsr4()
    79128    {
    … …  
    81130    }
    82131
     132    /**
     133     * @return list<string>
     134     */
    83135    public function getFallbackDirs()
    84136    {
    … …  
    86138    }
    87139
     140    /**
     141     * @return list<string>
     142     */
    88143    public function getFallbackDirsPsr4()
    89144    {
    … …  
    91146    }
    92147
     148    /**
     149     * @return array<string, string> Array of classname => path
     150     */
    93151    public function getClassMap()
    94152    {
    … …  
    97155
    98156    /**
    99      * @param array $classMap Class to filename map
     157     * @param array<string, string> $classMap Class to filename map
     158     *
     159     * @return void
    100160     */
    101161    public function addClassMap(array $classMap)
    … …  
    112172     * appending or prepending to the ones previously set for this prefix.
    113173     *
    114      * @param string       $prefix  The prefix
    115      * @param array|string $paths   The PSR-0 root directories
    116      * @param bool         $prepend Whether to prepend the directories
     174     * @param string              $prefix  The prefix
     175     * @param list<string>|string $paths   The PSR-0 root directories
     176     * @param bool                $prepend Whether to prepend the directories
     177     *
     178     * @return void
    117179     */
    118180    public function add($prefix, $paths, $prepend = false)
    119181    {
     182        $paths = (array) $paths;
    120183        if (!$prefix) {
    121184            if ($prepend) {
    122185                $this->fallbackDirsPsr0 = array_merge(
    123                     (array) $paths,
     186                    $paths,
    124187                    $this->fallbackDirsPsr0
    125188                );
    … …  
    127190                $this->fallbackDirsPsr0 = array_merge(
    128191                    $this->fallbackDirsPsr0,
    129                     (array) $paths
     192                    $paths
    130193                );
    131194            }
    … …  
    136199        $first = $prefix[0];
    137200        if (!isset($this->prefixesPsr0[$first][$prefix])) {
    138             $this->prefixesPsr0[$first][$prefix] = (array) $paths;
     201            $this->prefixesPsr0[$first][$prefix] = $paths;
    139202
    140203            return;
    … …  
    142205        if ($prepend) {
    143206            $this->prefixesPsr0[$first][$prefix] = array_merge(
    144                 (array) $paths,
     207                $paths,
    145208                $this->prefixesPsr0[$first][$prefix]
    146209            );
    … …  
    148211            $this->prefixesPsr0[$first][$prefix] = array_merge(
    149212                $this->prefixesPsr0[$first][$prefix],
    150                 (array) $paths
     213                $paths
    151214            );
    152215        }
    … …  
    157220     * appending or prepending to the ones previously set for this namespace.
    158221     *
    159      * @param string       $prefix  The prefix/namespace, with trailing '\\'
    160      * @param array|string $paths   The PSR-4 base directories
    161      * @param bool         $prepend Whether to prepend the directories
     222     * @param string              $prefix  The prefix/namespace, with trailing '\\'
     223     * @param list<string>|string $paths   The PSR-4 base directories
     224     * @param bool                $prepend Whether to prepend the directories
    162225     *
    163226     * @throws \InvalidArgumentException
     227     *
     228     * @return void
    164229     */
    165230    public function addPsr4($prefix, $paths, $prepend = false)
    166231    {
     232        $paths = (array) $paths;
    167233        if (!$prefix) {
    168234            // Register directories for the root namespace.
    169235            if ($prepend) {
    170236                $this->fallbackDirsPsr4 = array_merge(
    171                     (array) $paths,
     237                    $paths,
    172238                    $this->fallbackDirsPsr4
    173239                );
    … …  
    175241                $this->fallbackDirsPsr4 = array_merge(
    176242                    $this->fallbackDirsPsr4,
    177                     (array) $paths
     243                    $paths
    178244                );
    179245            }
    … …  
    185251            }
    186252            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
    187             $this->prefixDirsPsr4[$prefix] = (array) $paths;
     253            $this->prefixDirsPsr4[$prefix] = $paths;
    188254        } elseif ($prepend) {
    189255            // Prepend directories for an already registered namespace.
    190256            $this->prefixDirsPsr4[$prefix] = array_merge(
    191                 (array) $paths,
     257                $paths,
    192258                $this->prefixDirsPsr4[$prefix]
    193259            );
    … …  
    196262            $this->prefixDirsPsr4[$prefix] = array_merge(
    197263                $this->prefixDirsPsr4[$prefix],
    198                 (array) $paths
     264                $paths
    199265            );
    200266        }
    … …  
    205271     * replacing any others previously set for this prefix.
    206272     *
    207      * @param string       $prefix The prefix
    208      * @param array|string $paths  The PSR-0 base directories
     273     * @param string              $prefix The prefix
     274     * @param list<string>|string $paths  The PSR-0 base directories
     275     *
     276     * @return void
    209277     */
    210278    public function set($prefix, $paths)
    … …  
    221289     * replacing any others previously set for this namespace.
    222290     *
    223      * @param string       $prefix The prefix/namespace, with trailing '\\'
    224      * @param array|string $paths  The PSR-4 base directories
     291     * @param string              $prefix The prefix/namespace, with trailing '\\'
     292     * @param list<string>|string $paths  The PSR-4 base directories
    225293     *
    226294     * @throws \InvalidArgumentException
     295     *
     296     * @return void
    227297     */
    228298    public function setPsr4($prefix, $paths)
    … …  
    244314     *
    245315     * @param bool $useIncludePath
     316     *
     317     * @return void
    246318     */
    247319    public function setUseIncludePath($useIncludePath)
    … …  
    266338     *
    267339     * @param bool $classMapAuthoritative
     340     *
     341     * @return void
    268342     */
    269343    public function setClassMapAuthoritative($classMapAuthoritative)
    … …  
    286360     *
    287361     * @param string|null $apcuPrefix
     362     *
     363     * @return void
    288364     */
    289365    public function setApcuPrefix($apcuPrefix)
    … …  
    306382     *
    307383     * @param bool $prepend Whether to prepend the autoloader or not
     384     *
     385     * @return void
    308386     */
    309387    public function register($prepend = false)
    … …  
    312390
    313391        if (null === $this->vendorDir) {
    314             //no-op
    315         } elseif ($prepend) {
     392            return;
     393        }
     394
     395        if ($prepend) {
    316396            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
    317397        } else {
    … …  
    323403    /**
    324404     * Unregisters this instance as an autoloader.
     405     *
     406     * @return void
    325407     */
    326408    public function unregister()
    … …  
    337419     *
    338420     * @param  string    $class The name of the class
    339      * @return bool|null True if loaded, null otherwise
     421     * @return true|null True if loaded, null otherwise
    340422     */
    341423    public function loadClass($class)
    342424    {
    343425        if ($file = $this->findFile($class)) {
    344             includeFile($file);
     426            $includeFile = self::$includeFile;
     427            $includeFile($file);
    345428
    346429            return true;
    347430        }
     431
     432        return null;
    348433    }
    349434
    … …  
    391476
    392477    /**
    393      * Returns the currently registered loaders indexed by their corresponding vendor directories.
    394      *
    395      * @return self[]
     478     * Returns the currently registered loaders keyed by their corresponding vendor directories.
     479     *
     480     * @return array<string, self>
    396481     */
    397482    public static function getRegisteredLoaders()
    … …  
    400485    }
    401486
     487    /**
     488     * @param  string       $class
     489     * @param  string       $ext
     490     * @return string|false
     491     */
    402492    private function findFileWithExtension($class, $ext)
    403493    {
    … …  
    465555        return false;
    466556    }
     557
     558    /**
     559     * @return void
     560     */
     561    private static function initializeIncludeClosure()
     562    {
     563        if (self::$includeFile !== null) {
     564            return;
     565        }
     566
     567        /**
     568         * Scope isolated include.
     569         *
     570         * Prevents access to $this/self from included files.
     571         *
     572         * @param  string $file
     573         * @return void
     574         */
     575        self::$includeFile = \Closure::bind(static function($file) {
     576            include $file;
     577        }, null, null);
     578    }
    467579}
    468 
    469 /**
    470  * Scope isolated include.
    471  *
    472  * Prevents access to $this/self from included files.
    473  */
    474 function includeFile($file)
    475 {
    476     include $file;
    477 }
  • wp-otp/trunk/vendor/composer/InstalledVersions.php

    r2477335 r3752647  
    11<?php
    22
    3 
    4 
    5 
    6 
    7 
    8 
    9 
    10 
    11 
     3/*
     4 * This file is part of Composer.
     5 *
     6 * (c) Nils Adermann <naderman@naderman.de>
     7 *     Jordi Boggiano <j.boggiano@seld.be>
     8 *
     9 * For the full copyright and license information, please view the LICENSE
     10 * file that was distributed with this source code.
     11 */
    1212
    1313namespace Composer;
    … …  
    1616use Composer\Semver\VersionParser;
    1717
    18 
    19 
    20 
    21 
    22 
     18/**
     19 * This class is copied in every Composer installed project and available to all
     20 *
     21 * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
     22 *
     23 * To require its presence, you can require `composer-runtime-api ^2.0`
     24 *
     25 * @final
     26 */
    2327class InstalledVersions
    2428{
    25 private static $installed = array (
    26   'root' =>
    27   array (
    28     'pretty_version' => 'trunk',
    29     'version' => 'dev-trunk',
    30     'aliases' =>
    31     array (
    32     ),
    33     'reference' => NULL,
    34     'name' => 'noplanman/wp-otp',
    35   ),
    36   'versions' =>
    37   array (
    38     'beberlei/assert' =>
    39     array (
    40       'pretty_version' => 'v3.3.0',
    41       'version' => '3.3.0.0',
    42       'aliases' =>
    43       array (
    44       ),
    45       'reference' => '5367e3895976b49704ae671f75bc5f0ba1b986ab',
    46     ),
    47     'chillerlan/php-qrcode' =>
    48     array (
    49       'pretty_version' => '4.3.0',
    50       'version' => '4.3.0.0',
    51       'aliases' =>
    52       array (
    53       ),
    54       'reference' => '4968063fb3baeedb658293f89f9673fbf2499a3e',
    55     ),
    56     'chillerlan/php-settings-container' =>
    57     array (
    58       'pretty_version' => '2.1.1',
    59       'version' => '2.1.1.0',
    60       'aliases' =>
    61       array (
    62       ),
    63       'reference' => '98ccc1b31b31a53bcb563465c4961879b2b93096',
    64     ),
    65     'noplanman/wp-otp' =>
    66     array (
    67       'pretty_version' => 'trunk',
    68       'version' => 'dev-trunk',
    69       'aliases' =>
    70       array (
    71       ),
    72       'reference' => NULL,
    73     ),
    74     'paragonie/constant_time_encoding' =>
    75     array (
    76       'pretty_version' => 'v2.4.0',
    77       'version' => '2.4.0.0',
    78       'aliases' =>
    79       array (
    80       ),
    81       'reference' => 'f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c',
    82     ),
    83     'spomky-labs/otphp' =>
    84     array (
    85       'pretty_version' => 'v10.0.1',
    86       'version' => '10.0.1.0',
    87       'aliases' =>
    88       array (
    89       ),
    90       'reference' => 'f44cce5a9db4b8da410215d992110482c931232f',
    91     ),
    92     'thecodingmachine/safe' =>
    93     array (
    94       'pretty_version' => 'v1.3.3',
    95       'version' => '1.3.3.0',
    96       'aliases' =>
    97       array (
    98       ),
    99       'reference' => 'a8ab0876305a4cdaef31b2350fcb9811b5608dbc',
    100     ),
    101   ),
    102 );
    103 private static $canGetVendors;
    104 private static $installedByVendor = array();
    105 
    106 
    107 
    108 
    109 
    110 
    111 
    112 public static function getInstalledPackages()
    113 {
    114 $packages = array();
    115 foreach (self::getInstalled() as $installed) {
    116 $packages[] = array_keys($installed['versions']);
     29    /**
     30     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
     31     * @internal
     32     */
     33    private static $selfDir = null;
     34
     35    /**
     36     * @var mixed[]|null
     37     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
     38     */
     39    private static $installed;
     40
     41    /**
     42     * @var bool
     43     */
     44    private static $installedIsLocalDir;
     45
     46    /**
     47     * @var bool|null
     48     */
     49    private static $canGetVendors;
     50
     51    /**
     52     * @var array[]
     53     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     54     */
     55    private static $installedByVendor = array();
     56
     57    /**
     58     * Returns a list of all package names which are present, either by being installed, replaced or provided
     59     *
     60     * @return string[]
     61     * @psalm-return list<string>
     62     */
     63    public static function getInstalledPackages()
     64    {
     65        $packages = array();
     66        foreach (self::getInstalled() as $installed) {
     67            $packages[] = array_keys($installed['versions']);
     68        }
     69
     70        if (1 === \count($packages)) {
     71            return $packages[0];
     72        }
     73
     74        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
     75    }
     76
     77    /**
     78     * Returns a list of all package names with a specific type e.g. 'library'
     79     *
     80     * @param  string   $type
     81     * @return string[]
     82     * @psalm-return list<string>
     83     */
     84    public static function getInstalledPackagesByType($type)
     85    {
     86        $packagesByType = array();
     87
     88        foreach (self::getInstalled() as $installed) {
     89            foreach ($installed['versions'] as $name => $package) {
     90                if (isset($package['type']) && $package['type'] === $type) {
     91                    $packagesByType[] = $name;
     92                }
     93            }
     94        }
     95
     96        return $packagesByType;
     97    }
     98
     99    /**
     100     * Checks whether the given package is installed
     101     *
     102     * This also returns true if the package name is provided or replaced by another package
     103     *
     104     * @param  string $packageName
     105     * @param  bool   $includeDevRequirements
     106     * @return bool
     107     */
     108    public static function isInstalled($packageName, $includeDevRequirements = true)
     109    {
     110        foreach (self::getInstalled() as $installed) {
     111            if (isset($installed['versions'][$packageName])) {
     112                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
     113            }
     114        }
     115
     116        return false;
     117    }
     118
     119    /**
     120     * Checks whether the given package satisfies a version constraint
     121     *
     122     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
     123     *
     124     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
     125     *
     126     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
     127     * @param  string        $packageName
     128     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
     129     * @return bool
     130     */
     131    public static function satisfies(VersionParser $parser, $packageName, $constraint)
     132    {
     133        $constraint = $parser->parseConstraints((string) $constraint);
     134        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
     135
     136        return $provided->matches($constraint);
     137    }
     138
     139    /**
     140     * Returns a version constraint representing all the range(s) which are installed for a given package
     141     *
     142     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
     143     * whether a given version of a package is installed, and not just whether it exists
     144     *
     145     * @param  string $packageName
     146     * @return string Version constraint usable with composer/semver
     147     */
     148    public static function getVersionRanges($packageName)
     149    {
     150        foreach (self::getInstalled() as $installed) {
     151            if (!isset($installed['versions'][$packageName])) {
     152                continue;
     153            }
     154
     155            $ranges = array();
     156            if (isset($installed['versions'][$packageName]['pretty_version'])) {
     157                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
     158            }
     159            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
     160                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
     161            }
     162            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
     163                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
     164            }
     165            if (array_key_exists('provided', $installed['versions'][$packageName])) {
     166                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
     167            }
     168
     169            return implode(' || ', $ranges);
     170        }
     171
     172        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
     173    }
     174
     175    /**
     176     * @param  string      $packageName
     177     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     178     */
     179    public static function getVersion($packageName)
     180    {
     181        foreach (self::getInstalled() as $installed) {
     182            if (!isset($installed['versions'][$packageName])) {
     183                continue;
     184            }
     185
     186            if (!isset($installed['versions'][$packageName]['version'])) {
     187                return null;
     188            }
     189
     190            return $installed['versions'][$packageName]['version'];
     191        }
     192
     193        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
     194    }
     195
     196    /**
     197     * @param  string      $packageName
     198     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     199     */
     200    public static function getPrettyVersion($packageName)
     201    {
     202        foreach (self::getInstalled() as $installed) {
     203            if (!isset($installed['versions'][$packageName])) {
     204                continue;
     205            }
     206
     207            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
     208                return null;
     209            }
     210
     211            return $installed['versions'][$packageName]['pretty_version'];
     212        }
     213
     214        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
     215    }
     216
     217    /**
     218     * @param  string      $packageName
     219     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
     220     */
     221    public static function getReference($packageName)
     222    {
     223        foreach (self::getInstalled() as $installed) {
     224            if (!isset($installed['versions'][$packageName])) {
     225                continue;
     226            }
     227
     228            if (!isset($installed['versions'][$packageName]['reference'])) {
     229                return null;
     230            }
     231
     232            return $installed['versions'][$packageName]['reference'];
     233        }
     234
     235        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
     236    }
     237
     238    /**
     239     * @param  string      $packageName
     240     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
     241     */
     242    public static function getInstallPath($packageName)
     243    {
     244        foreach (self::getInstalled() as $installed) {
     245            if (!isset($installed['versions'][$packageName])) {
     246                continue;
     247            }
     248
     249            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
     250        }
     251
     252        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
     253    }
     254
     255    /**
     256     * @return array
     257     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
     258     */
     259    public static function getRootPackage()
     260    {
     261        $installed = self::getInstalled();
     262
     263        return $installed[0]['root'];
     264    }
     265
     266    /**
     267     * Returns the raw installed.php data for custom implementations
     268     *
     269     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
     270     * @return array[]
     271     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
     272     */
     273    public static function getRawData()
     274    {
     275        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
     276
     277        if (null === self::$installed) {
     278            // only require the installed.php file if this file is loaded from its dumped location,
     279            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
     280            if (substr(__DIR__, -8, 1) !== 'C') {
     281                self::$installed = include __DIR__ . '/installed.php';
     282            } else {
     283                self::$installed = array();
     284            }
     285        }
     286
     287        return self::$installed;
     288    }
     289
     290    /**
     291     * Returns the raw data of all installed.php which are currently loaded for custom implementations
     292     *
     293     * @return array[]
     294     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     295     */
     296    public static function getAllRawData()
     297    {
     298        return self::getInstalled();
     299    }
     300
     301    /**
     302     * Lets you reload the static array from another file
     303     *
     304     * This is only useful for complex integrations in which a project needs to use
     305     * this class but then also needs to execute another project's autoloader in process,
     306     * and wants to ensure both projects have access to their version of installed.php.
     307     *
     308     * A typical case would be PHPUnit, where it would need to make sure it reads all
     309     * the data it needs from this class, then call reload() with
     310     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
     311     * the project in which it runs can then also use this class safely, without
     312     * interference between PHPUnit's dependencies and the project's dependencies.
     313     *
     314     * @param  array[] $data A vendor/composer/installed.php data set
     315     * @return void
     316     *
     317     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
     318     */
     319    public static function reload($data)
     320    {
     321        self::$installed = $data;
     322        self::$installedByVendor = array();
     323
     324        // when using reload, we disable the duplicate protection to ensure that self::$installed data is
     325        // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
     326        // so we have to assume it does not, and that may result in duplicate data being returned when listing
     327        // all installed packages for example
     328        self::$installedIsLocalDir = false;
     329    }
     330
     331    /**
     332     * @return string
     333     */
     334    private static function getSelfDir()
     335    {
     336        if (self::$selfDir === null) {
     337            self::$selfDir = strtr(__DIR__, '\\', '/');
     338        }
     339
     340        return self::$selfDir;
     341    }
     342
     343    /**
     344     * @return array[]
     345     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     346     */
     347    private static function getInstalled()
     348    {
     349        if (null === self::$canGetVendors) {
     350            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
     351        }
     352
     353        $installed = array();
     354        $copiedLocalDir = false;
     355
     356        if (self::$canGetVendors) {
     357            $selfDir = self::getSelfDir();
     358            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
     359                $vendorDir = strtr($vendorDir, '\\', '/');
     360                if (isset(self::$installedByVendor[$vendorDir])) {
     361                    $installed[] = self::$installedByVendor[$vendorDir];
     362                } elseif (is_file($vendorDir.'/composer/installed.php')) {
     363                    /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
     364                    $required = require $vendorDir.'/composer/installed.php';
     365                    self::$installedByVendor[$vendorDir] = $required;
     366                    $installed[] = $required;
     367                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
     368                        self::$installed = $required;
     369                        self::$installedIsLocalDir = true;
     370                    }
     371                }
     372                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
     373                    $copiedLocalDir = true;
     374                }
     375            }
     376        }
     377
     378        if (null === self::$installed) {
     379            // only require the installed.php file if this file is loaded from its dumped location,
     380            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
     381            if (substr(__DIR__, -8, 1) !== 'C') {
     382                /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
     383                $required = require __DIR__ . '/installed.php';
     384                self::$installed = $required;
     385            } else {
     386                self::$installed = array();
     387            }
     388        }
     389
     390        if (self::$installed !== array() && !$copiedLocalDir) {
     391            $installed[] = self::$installed;
     392        }
     393
     394        return $installed;
     395    }
    117396}
    118 
    119 
    120 if (1 === \count($packages)) {
    121 return $packages[0];
    122 }
    123 
    124 return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
    125 }
    126 
    127 
    128 
    129 
    130 
    131 
    132 
    133 
    134 
    135 public static function isInstalled($packageName)
    136 {
    137 foreach (self::getInstalled() as $installed) {
    138 if (isset($installed['versions'][$packageName])) {
    139 return true;
    140 }
    141 }
    142 
    143 return false;
    144 }
    145 
    146 
    147 
    148 
    149 
    150 
    151 
    152 
    153 
    154 
    155 
    156 
    157 
    158 
    159 public static function satisfies(VersionParser $parser, $packageName, $constraint)
    160 {
    161 $constraint = $parser->parseConstraints($constraint);
    162 $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
    163 
    164 return $provided->matches($constraint);
    165 }
    166 
    167 
    168 
    169 
    170 
    171 
    172 
    173 
    174 
    175 
    176 public static function getVersionRanges($packageName)
    177 {
    178 foreach (self::getInstalled() as $installed) {
    179 if (!isset($installed['versions'][$packageName])) {
    180 continue;
    181 }
    182 
    183 $ranges = array();
    184 if (isset($installed['versions'][$packageName]['pretty_version'])) {
    185 $ranges[] = $installed['versions'][$packageName]['pretty_version'];
    186 }
    187 if (array_key_exists('aliases', $installed['versions'][$packageName])) {
    188 $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
    189 }
    190 if (array_key_exists('replaced', $installed['versions'][$packageName])) {
    191 $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
    192 }
    193 if (array_key_exists('provided', $installed['versions'][$packageName])) {
    194 $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
    195 }
    196 
    197 return implode(' || ', $ranges);
    198 }
    199 
    200 throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    201 }
    202 
    203 
    204 
    205 
    206 
    207 public static function getVersion($packageName)
    208 {
    209 foreach (self::getInstalled() as $installed) {
    210 if (!isset($installed['versions'][$packageName])) {
    211 continue;
    212 }
    213 
    214 if (!isset($installed['versions'][$packageName]['version'])) {
    215 return null;
    216 }
    217 
    218 return $installed['versions'][$packageName]['version'];
    219 }
    220 
    221 throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    222 }
    223 
    224 
    225 
    226 
    227 
    228 public static function getPrettyVersion($packageName)
    229 {
    230 foreach (self::getInstalled() as $installed) {
    231 if (!isset($installed['versions'][$packageName])) {
    232 continue;
    233 }
    234 
    235 if (!isset($installed['versions'][$packageName]['pretty_version'])) {
    236 return null;
    237 }
    238 
    239 return $installed['versions'][$packageName]['pretty_version'];
    240 }
    241 
    242 throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    243 }
    244 
    245 
    246 
    247 
    248 
    249 public static function getReference($packageName)
    250 {
    251 foreach (self::getInstalled() as $installed) {
    252 if (!isset($installed['versions'][$packageName])) {
    253 continue;
    254 }
    255 
    256 if (!isset($installed['versions'][$packageName]['reference'])) {
    257 return null;
    258 }
    259 
    260 return $installed['versions'][$packageName]['reference'];
    261 }
    262 
    263 throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    264 }
    265 
    266 
    267 
    268 
    269 
    270 public static function getRootPackage()
    271 {
    272 $installed = self::getInstalled();
    273 
    274 return $installed[0]['root'];
    275 }
    276 
    277 
    278 
    279 
    280 
    281 
    282 
    283 public static function getRawData()
    284 {
    285 return self::$installed;
    286 }
    287 
    288 
    289 
    290 
    291 
    292 
    293 
    294 
    295 
    296 
    297 
    298 
    299 
    300 
    301 
    302 
    303 
    304 
    305 
    306 public static function reload($data)
    307 {
    308 self::$installed = $data;
    309 self::$installedByVendor = array();
    310 }
    311 
    312 
    313 
    314 
    315 private static function getInstalled()
    316 {
    317 if (null === self::$canGetVendors) {
    318 self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
    319 }
    320 
    321 $installed = array();
    322 
    323 if (self::$canGetVendors) {
    324 
    325 foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
    326 if (isset(self::$installedByVendor[$vendorDir])) {
    327 $installed[] = self::$installedByVendor[$vendorDir];
    328 } elseif (is_file($vendorDir.'/composer/installed.php')) {
    329 $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
    330 }
    331 }
    332 }
    333 
    334 $installed[] = self::$installed;
    335 
    336 return $installed;
    337 }
    338 }
  • wp-otp/trunk/vendor/composer/autoload_classmap.php

    r2466054 r3752647  
    33// autoload_classmap.php @generated by Composer
    44
    5 $vendorDir = dirname(dirname(__FILE__));
     5$vendorDir = dirname(__DIR__);
    66$baseDir = dirname($vendorDir);
    77
  • wp-otp/trunk/vendor/composer/autoload_files.php

    r2466054 r3752647  
    33// autoload_files.php @generated by Composer
    44
    5 $vendorDir = dirname(dirname(__FILE__));
     5$vendorDir = dirname(__DIR__);
    66$baseDir = dirname($vendorDir);
    77
    88return array(
    9     'a4ecaeafb8cfb009ad0e052c90355e98' => $vendorDir . '/beberlei/assert/lib/Assert/functions.php',
    10     '51fcf4e06c07cc00c920b44bcd900e7a' => $vendorDir . '/thecodingmachine/safe/deprecated/apc.php',
    11     '47f619d9197b36cf5ab70738d7743fe2' => $vendorDir . '/thecodingmachine/safe/deprecated/libevent.php',
    12     'ea6bb8a12ef9b68f6ada99058e530760' => $vendorDir . '/thecodingmachine/safe/deprecated/mssql.php',
    13     '9a29089eb3ce41a446744c68a00f118c' => $vendorDir . '/thecodingmachine/safe/deprecated/stats.php',
    14     '72243e5536b63e298acb6476f01f1aff' => $vendorDir . '/thecodingmachine/safe/lib/special_cases.php',
    15     '3f648889e687f31c52f949ba8a9d0873' => $vendorDir . '/thecodingmachine/safe/generated/apache.php',
    16     'eeb4581d958421a4244aaa4167c6a575' => $vendorDir . '/thecodingmachine/safe/generated/apcu.php',
    17     '04cb0b3c1dac5b5ddb23c14e3d66dbe9' => $vendorDir . '/thecodingmachine/safe/generated/array.php',
    18     '450b332a74a9a21e043c5e953485a791' => $vendorDir . '/thecodingmachine/safe/generated/bzip2.php',
    19     '6e9b7954ecfd7cbb9ca239319d1acdb6' => $vendorDir . '/thecodingmachine/safe/generated/calendar.php',
    20     '2c6d7e8bd2de9a272a9d4d43b0a4304a' => $vendorDir . '/thecodingmachine/safe/generated/classobj.php',
    21     '0b8231c1ad0865447c988a4c16b4001f' => $vendorDir . '/thecodingmachine/safe/generated/com.php',
    22     '7643a71fe1c3256058c8fee234cb86e5' => $vendorDir . '/thecodingmachine/safe/generated/cubrid.php',
    23     '68e1365710575942efc1d55000032cee' => $vendorDir . '/thecodingmachine/safe/generated/curl.php',
    24     '02fd26bca803106c5b942a7197c3ad8b' => $vendorDir . '/thecodingmachine/safe/generated/datetime.php',
    25     'f4817dcbd956cd221b1c31f6fbd5749c' => $vendorDir . '/thecodingmachine/safe/generated/dir.php',
    26     '51c3f2d10ca61a70dbcea0e38d8e902d' => $vendorDir . '/thecodingmachine/safe/generated/eio.php',
    27     '1d34f34327ca3e81535963016e3be2c3' => $vendorDir . '/thecodingmachine/safe/generated/errorfunc.php',
    28     '4fd0ba2d3717b0424d474bebfdafa2b4' => $vendorDir . '/thecodingmachine/safe/generated/exec.php',
    29     '98f4dae054bc7fb19c13be14935cbdd3' => $vendorDir . '/thecodingmachine/safe/generated/fileinfo.php',
    30     '5530ae063ba88323eaf0a07904efdf85' => $vendorDir . '/thecodingmachine/safe/generated/filesystem.php',
    31     '633f4f134975d70e97bddad83348e91a' => $vendorDir . '/thecodingmachine/safe/generated/filter.php',
    32     'fbd163fc68c5faf73d5ed4002ffd836d' => $vendorDir . '/thecodingmachine/safe/generated/fpm.php',
    33     '21b511999d61411fab0692ff8795bbed' => $vendorDir . '/thecodingmachine/safe/generated/ftp.php',
    34     '85fbd73fc92365cd90526b0ea03cae3a' => $vendorDir . '/thecodingmachine/safe/generated/funchand.php',
    35     '51df9c146e0b7dcbdf358d8abd24dbdc' => $vendorDir . '/thecodingmachine/safe/generated/gmp.php',
    36     '93bb7fe678d7dcfb1322f8e3475a48b0' => $vendorDir . '/thecodingmachine/safe/generated/gnupg.php',
    37     'c171ba99cf316379ff66468392bf4950' => $vendorDir . '/thecodingmachine/safe/generated/hash.php',
    38     '5ab4aad4c28e468209fbfcceb2e5e6a5' => $vendorDir . '/thecodingmachine/safe/generated/ibase.php',
    39     '4d57409c5e8e576b0c64c08d9d731cfb' => $vendorDir . '/thecodingmachine/safe/generated/ibmDb2.php',
    40     'eeb246d5403972a9d62106e4a4883496' => $vendorDir . '/thecodingmachine/safe/generated/iconv.php',
    41     'c28a05f498c01b810a714f7214b7a8da' => $vendorDir . '/thecodingmachine/safe/generated/image.php',
    42     '8063cd92acdf00fd978b5599eb7cc142' => $vendorDir . '/thecodingmachine/safe/generated/imap.php',
    43     '8bd26dbe768e9c9599edad7b198e5446' => $vendorDir . '/thecodingmachine/safe/generated/info.php',
    44     '0c577fe603b029d4b65c84376b15dbd5' => $vendorDir . '/thecodingmachine/safe/generated/ingres-ii.php',
    45     'd4362910bde43c0f956b52527effd7d4' => $vendorDir . '/thecodingmachine/safe/generated/inotify.php',
    46     '696ba49197d9b55f0428a12bb5a818e1' => $vendorDir . '/thecodingmachine/safe/generated/json.php',
    47     '9818aaa99c8647c63f8ef62b7a368160' => $vendorDir . '/thecodingmachine/safe/generated/ldap.php',
    48     'bcf523ff2a195eb08e0fbb668ed784d0' => $vendorDir . '/thecodingmachine/safe/generated/libxml.php',
    49     '68be68a9a8b95bb56cab6109ff03bc88' => $vendorDir . '/thecodingmachine/safe/generated/lzf.php',
    50     'bdca804bb0904ea9f53f328dfc0bb8a5' => $vendorDir . '/thecodingmachine/safe/generated/mailparse.php',
    51     'b0a3fcac3eaf55445796d6af26b89366' => $vendorDir . '/thecodingmachine/safe/generated/mbstring.php',
    52     '98de16b8db03eb0cb4d318b4402215a6' => $vendorDir . '/thecodingmachine/safe/generated/misc.php',
    53     'c112440003b56e243b192c11fa9d836e' => $vendorDir . '/thecodingmachine/safe/generated/msql.php',
    54     '7cefd81607cd21b8b3a15656eb6465f5' => $vendorDir . '/thecodingmachine/safe/generated/mysql.php',
    55     'aaf438b080089c6d0686679cd34aa72e' => $vendorDir . '/thecodingmachine/safe/generated/mysqli.php',
    56     'df0ef890e9afbf95f3924feb1c7a89f3' => $vendorDir . '/thecodingmachine/safe/generated/mysqlndMs.php',
    57     'db595fee5972867e45c5327010d78735' => $vendorDir . '/thecodingmachine/safe/generated/mysqlndQc.php',
    58     'cbac956836b72483dcff1ac39d5c0a0f' => $vendorDir . '/thecodingmachine/safe/generated/network.php',
    59     '6c8f89dfbdc117d7871f572269363f25' => $vendorDir . '/thecodingmachine/safe/generated/oci8.php',
    60     '169a669966a45c06bf55ed029122729b' => $vendorDir . '/thecodingmachine/safe/generated/opcache.php',
    61     'def61bf4fecd4d4bca7354919cd69302' => $vendorDir . '/thecodingmachine/safe/generated/openssl.php',
    62     '26bb010649a6d32d4120181458aa6ef2' => $vendorDir . '/thecodingmachine/safe/generated/outcontrol.php',
    63     '1212c201fe43c7492a085b2c71505e0f' => $vendorDir . '/thecodingmachine/safe/generated/password.php',
    64     '002ebcb842e2c0d5b7f67fe64cc93158' => $vendorDir . '/thecodingmachine/safe/generated/pcntl.php',
    65     '86df38612982dade72c7085ce7eca81f' => $vendorDir . '/thecodingmachine/safe/generated/pcre.php',
    66     '1cacc3e65f82a473fbd5507c7ce4385d' => $vendorDir . '/thecodingmachine/safe/generated/pdf.php',
    67     '1fc22f445c69ea8706e82fce301c0831' => $vendorDir . '/thecodingmachine/safe/generated/pgsql.php',
    68     'c70b42561584f7144bff38cd63c4eef3' => $vendorDir . '/thecodingmachine/safe/generated/posix.php',
    69     '9923214639c32ca5173db03a177d3b63' => $vendorDir . '/thecodingmachine/safe/generated/ps.php',
    70     '7e9c3f8eae2b5bf42205c4f1295cb7a7' => $vendorDir . '/thecodingmachine/safe/generated/pspell.php',
    71     '91aa91f6245c349c2e2e88bd0025f199' => $vendorDir . '/thecodingmachine/safe/generated/readline.php',
    72     'd43773cacb9e5e8e897aa255e32007d1' => $vendorDir . '/thecodingmachine/safe/generated/rpminfo.php',
    73     'f053a3849e9e8383762b34b91db0320b' => $vendorDir . '/thecodingmachine/safe/generated/rrd.php',
    74     '775b964f72f827a1bf87c65ab5b10800' => $vendorDir . '/thecodingmachine/safe/generated/sem.php',
    75     '816428bd69c29ab5e1ed622af5dca0cd' => $vendorDir . '/thecodingmachine/safe/generated/session.php',
    76     '5093e233bedbefaef0df262bfbab0a5c' => $vendorDir . '/thecodingmachine/safe/generated/shmop.php',
    77     '01352920b0151f17e671266e44b52536' => $vendorDir . '/thecodingmachine/safe/generated/simplexml.php',
    78     'b080617b1d949683c2e37f8f01dc0e15' => $vendorDir . '/thecodingmachine/safe/generated/sockets.php',
    79     '2708aa182ddcfe6ce27c96acaaa40f69' => $vendorDir . '/thecodingmachine/safe/generated/sodium.php',
    80     'f1b96cb260a5baeea9a7285cda82a1ec' => $vendorDir . '/thecodingmachine/safe/generated/solr.php',
    81     '3fd8853757d0fe3557c179efb807afeb' => $vendorDir . '/thecodingmachine/safe/generated/spl.php',
    82     '9312ce96a51c846913fcda5f186d58dd' => $vendorDir . '/thecodingmachine/safe/generated/sqlsrv.php',
    83     'd3eb383ad0b8b962b29dc4afd29d6715' => $vendorDir . '/thecodingmachine/safe/generated/ssdeep.php',
    84     '42a09bc448f441a0b9f9367ea975c0bf' => $vendorDir . '/thecodingmachine/safe/generated/ssh2.php',
    85     'ef711077d356d1b33ca0b10b67b0be8f' => $vendorDir . '/thecodingmachine/safe/generated/stream.php',
    86     '764b09f6df081cbb2807b97c6ace3866' => $vendorDir . '/thecodingmachine/safe/generated/strings.php',
    87     'ef241678769fee4a44aaa288f3b78aa1' => $vendorDir . '/thecodingmachine/safe/generated/swoole.php',
    88     '0efc8f6778cba932b9e2a89e28de2452' => $vendorDir . '/thecodingmachine/safe/generated/uodbc.php',
    89     'd383d32907b98af53ee9208c62204fd0' => $vendorDir . '/thecodingmachine/safe/generated/uopz.php',
    90     '2fd2e4060f7fe772660f002ce38f0b71' => $vendorDir . '/thecodingmachine/safe/generated/url.php',
    91     '782249e03deebeaf57b9991ff5493aa0' => $vendorDir . '/thecodingmachine/safe/generated/var.php',
    92     '344440cd1cd7200fdb4f12af0d3c587f' => $vendorDir . '/thecodingmachine/safe/generated/xdiff.php',
    93     '3599f369219c658a5fb6c4fe66832f62' => $vendorDir . '/thecodingmachine/safe/generated/xml.php',
    94     '7fcd313da9fae337051b091b3492c21b' => $vendorDir . '/thecodingmachine/safe/generated/xmlrpc.php',
    95     'd668c74cfa92d893b582356733d9a80e' => $vendorDir . '/thecodingmachine/safe/generated/yaml.php',
    96     '4af1dca6db8c527c6eed27bff85ff0e5' => $vendorDir . '/thecodingmachine/safe/generated/yaz.php',
    97     'fe43ca06499ac37bc2dedd823af71eb5' => $vendorDir . '/thecodingmachine/safe/generated/zip.php',
    98     '356736db98a6834f0a886b8d509b0ecd' => $vendorDir . '/thecodingmachine/safe/generated/zlib.php',
     9    '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
    9910);
  • wp-otp/trunk/vendor/composer/autoload_namespaces.php

    r1685086 r3752647  
    33// autoload_namespaces.php @generated by Composer
    44
    5 $vendorDir = dirname(dirname(__FILE__));
     5$vendorDir = dirname(__DIR__);
    66$baseDir = dirname($vendorDir);
    77
  • wp-otp/trunk/vendor/composer/autoload_psr4.php

    r2466054 r3752647  
    33// autoload_psr4.php @generated by Composer
    44
    5 $vendorDir = dirname(dirname(__FILE__));
     5$vendorDir = dirname(__DIR__);
    66$baseDir = dirname($vendorDir);
    77
    … …  
    99    'chillerlan\\Settings\\' => array($vendorDir . '/chillerlan/php-settings-container/src'),
    1010    'chillerlan\\QRCode\\' => array($vendorDir . '/chillerlan/php-qrcode/src'),
    11     'Safe\\' => array($vendorDir . '/thecodingmachine/safe/lib', $vendorDir . '/thecodingmachine/safe/deprecated', $vendorDir . '/thecodingmachine/safe/generated'),
     11    'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
    1212    'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'),
    1313    'OTPHP\\' => array($vendorDir . '/spomky-labs/otphp/src'),
    14     'Assert\\' => array($vendorDir . '/beberlei/assert/lib/Assert'),
    1514);
  • wp-otp/trunk/vendor/composer/autoload_real.php

    r2477335 r3752647  
    33// autoload_real.php @generated by Composer
    44
    5 class ComposerAutoloaderInitc7ff053f14f929092d06e37035c6064c
     5class ComposerAutoloaderInit0cde52f04200d0b097061008ff484a30
    66{
    77    private static $loader;
    … …  
    2525        require __DIR__ . '/platform_check.php';
    2626
    27         spl_autoload_register(array('ComposerAutoloaderInitc7ff053f14f929092d06e37035c6064c', 'loadClassLoader'), true, true);
    28         self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
    29         spl_autoload_unregister(array('ComposerAutoloaderInitc7ff053f14f929092d06e37035c6064c', 'loadClassLoader'));
     27        spl_autoload_register(array('ComposerAutoloaderInit0cde52f04200d0b097061008ff484a30', 'loadClassLoader'), true, true);
     28        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
     29        spl_autoload_unregister(array('ComposerAutoloaderInit0cde52f04200d0b097061008ff484a30', 'loadClassLoader'));
    3030
    31         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
    32         if ($useStaticLoader) {
    33             require __DIR__ . '/autoload_static.php';
    34 
    35             call_user_func(\Composer\Autoload\ComposerStaticInitc7ff053f14f929092d06e37035c6064c::getInitializer($loader));
    36         } else {
    37             $map = require __DIR__ . '/autoload_namespaces.php';
    38             foreach ($map as $namespace => $path) {
    39                 $loader->set($namespace, $path);
    40             }
    41 
    42             $map = require __DIR__ . '/autoload_psr4.php';
    43             foreach ($map as $namespace => $path) {
    44                 $loader->setPsr4($namespace, $path);
    45             }
    46 
    47             $classMap = require __DIR__ . '/autoload_classmap.php';
    48             if ($classMap) {
    49                 $loader->addClassMap($classMap);
    50             }
    51         }
     31        require __DIR__ . '/autoload_static.php';
     32        call_user_func(\Composer\Autoload\ComposerStaticInit0cde52f04200d0b097061008ff484a30::getInitializer($loader));
    5233
    5334        $loader->register(true);
    5435
    55         if ($useStaticLoader) {
    56             $includeFiles = Composer\Autoload\ComposerStaticInitc7ff053f14f929092d06e37035c6064c::$files;
    57         } else {
    58             $includeFiles = require __DIR__ . '/autoload_files.php';
    59         }
    60         foreach ($includeFiles as $fileIdentifier => $file) {
    61             composerRequirec7ff053f14f929092d06e37035c6064c($fileIdentifier, $file);
     36        $filesToLoad = \Composer\Autoload\ComposerStaticInit0cde52f04200d0b097061008ff484a30::$files;
     37        $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
     38            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
     39                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
     40
     41                require $file;
     42            }
     43        }, null, null);
     44        foreach ($filesToLoad as $fileIdentifier => $file) {
     45            $requireFile($fileIdentifier, $file);
    6246        }
    6347
    … …  
    6549    }
    6650}
    67 
    68 function composerRequirec7ff053f14f929092d06e37035c6064c($fileIdentifier, $file)
    69 {
    70     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
    71         require $file;
    72 
    73         $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    74     }
    75 }
  • wp-otp/trunk/vendor/composer/autoload_static.php

    r2477335 r3752647  
    55namespace Composer\Autoload;
    66
    7 class ComposerStaticInitc7ff053f14f929092d06e37035c6064c
     7class ComposerStaticInit0cde52f04200d0b097061008ff484a30
    88{
    99    public static $files = array (
    10         'a4ecaeafb8cfb009ad0e052c90355e98' => __DIR__ . '/..' . '/beberlei/assert/lib/Assert/functions.php',
    11         '51fcf4e06c07cc00c920b44bcd900e7a' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/apc.php',
    12         '47f619d9197b36cf5ab70738d7743fe2' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/libevent.php',
    13         'ea6bb8a12ef9b68f6ada99058e530760' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/mssql.php',
    14         '9a29089eb3ce41a446744c68a00f118c' => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated/stats.php',
    15         '72243e5536b63e298acb6476f01f1aff' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/special_cases.php',
    16         '3f648889e687f31c52f949ba8a9d0873' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/apache.php',
    17         'eeb4581d958421a4244aaa4167c6a575' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/apcu.php',
    18         '04cb0b3c1dac5b5ddb23c14e3d66dbe9' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/array.php',
    19         '450b332a74a9a21e043c5e953485a791' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/bzip2.php',
    20         '6e9b7954ecfd7cbb9ca239319d1acdb6' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/calendar.php',
    21         '2c6d7e8bd2de9a272a9d4d43b0a4304a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/classobj.php',
    22         '0b8231c1ad0865447c988a4c16b4001f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/com.php',
    23         '7643a71fe1c3256058c8fee234cb86e5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/cubrid.php',
    24         '68e1365710575942efc1d55000032cee' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/curl.php',
    25         '02fd26bca803106c5b942a7197c3ad8b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/datetime.php',
    26         'f4817dcbd956cd221b1c31f6fbd5749c' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/dir.php',
    27         '51c3f2d10ca61a70dbcea0e38d8e902d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/eio.php',
    28         '1d34f34327ca3e81535963016e3be2c3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/errorfunc.php',
    29         '4fd0ba2d3717b0424d474bebfdafa2b4' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/exec.php',
    30         '98f4dae054bc7fb19c13be14935cbdd3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/fileinfo.php',
    31         '5530ae063ba88323eaf0a07904efdf85' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/filesystem.php',
    32         '633f4f134975d70e97bddad83348e91a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/filter.php',
    33         'fbd163fc68c5faf73d5ed4002ffd836d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/fpm.php',
    34         '21b511999d61411fab0692ff8795bbed' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ftp.php',
    35         '85fbd73fc92365cd90526b0ea03cae3a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/funchand.php',
    36         '51df9c146e0b7dcbdf358d8abd24dbdc' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/gmp.php',
    37         '93bb7fe678d7dcfb1322f8e3475a48b0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/gnupg.php',
    38         'c171ba99cf316379ff66468392bf4950' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/hash.php',
    39         '5ab4aad4c28e468209fbfcceb2e5e6a5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ibase.php',
    40         '4d57409c5e8e576b0c64c08d9d731cfb' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ibmDb2.php',
    41         'eeb246d5403972a9d62106e4a4883496' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/iconv.php',
    42         'c28a05f498c01b810a714f7214b7a8da' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/image.php',
    43         '8063cd92acdf00fd978b5599eb7cc142' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/imap.php',
    44         '8bd26dbe768e9c9599edad7b198e5446' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/info.php',
    45         '0c577fe603b029d4b65c84376b15dbd5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ingres-ii.php',
    46         'd4362910bde43c0f956b52527effd7d4' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/inotify.php',
    47         '696ba49197d9b55f0428a12bb5a818e1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/json.php',
    48         '9818aaa99c8647c63f8ef62b7a368160' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ldap.php',
    49         'bcf523ff2a195eb08e0fbb668ed784d0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/libxml.php',
    50         '68be68a9a8b95bb56cab6109ff03bc88' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/lzf.php',
    51         'bdca804bb0904ea9f53f328dfc0bb8a5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mailparse.php',
    52         'b0a3fcac3eaf55445796d6af26b89366' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mbstring.php',
    53         '98de16b8db03eb0cb4d318b4402215a6' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/misc.php',
    54         'c112440003b56e243b192c11fa9d836e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/msql.php',
    55         '7cefd81607cd21b8b3a15656eb6465f5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysql.php',
    56         'aaf438b080089c6d0686679cd34aa72e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysqli.php',
    57         'df0ef890e9afbf95f3924feb1c7a89f3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysqlndMs.php',
    58         'db595fee5972867e45c5327010d78735' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysqlndQc.php',
    59         'cbac956836b72483dcff1ac39d5c0a0f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/network.php',
    60         '6c8f89dfbdc117d7871f572269363f25' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/oci8.php',
    61         '169a669966a45c06bf55ed029122729b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/opcache.php',
    62         'def61bf4fecd4d4bca7354919cd69302' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/openssl.php',
    63         '26bb010649a6d32d4120181458aa6ef2' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/outcontrol.php',
    64         '1212c201fe43c7492a085b2c71505e0f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/password.php',
    65         '002ebcb842e2c0d5b7f67fe64cc93158' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pcntl.php',
    66         '86df38612982dade72c7085ce7eca81f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pcre.php',
    67         '1cacc3e65f82a473fbd5507c7ce4385d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pdf.php',
    68         '1fc22f445c69ea8706e82fce301c0831' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pgsql.php',
    69         'c70b42561584f7144bff38cd63c4eef3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/posix.php',
    70         '9923214639c32ca5173db03a177d3b63' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ps.php',
    71         '7e9c3f8eae2b5bf42205c4f1295cb7a7' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pspell.php',
    72         '91aa91f6245c349c2e2e88bd0025f199' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/readline.php',
    73         'd43773cacb9e5e8e897aa255e32007d1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/rpminfo.php',
    74         'f053a3849e9e8383762b34b91db0320b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/rrd.php',
    75         '775b964f72f827a1bf87c65ab5b10800' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sem.php',
    76         '816428bd69c29ab5e1ed622af5dca0cd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/session.php',
    77         '5093e233bedbefaef0df262bfbab0a5c' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/shmop.php',
    78         '01352920b0151f17e671266e44b52536' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/simplexml.php',
    79         'b080617b1d949683c2e37f8f01dc0e15' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sockets.php',
    80         '2708aa182ddcfe6ce27c96acaaa40f69' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sodium.php',
    81         'f1b96cb260a5baeea9a7285cda82a1ec' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/solr.php',
    82         '3fd8853757d0fe3557c179efb807afeb' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/spl.php',
    83         '9312ce96a51c846913fcda5f186d58dd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sqlsrv.php',
    84         'd3eb383ad0b8b962b29dc4afd29d6715' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ssdeep.php',
    85         '42a09bc448f441a0b9f9367ea975c0bf' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ssh2.php',
    86         'ef711077d356d1b33ca0b10b67b0be8f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/stream.php',
    87         '764b09f6df081cbb2807b97c6ace3866' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/strings.php',
    88         'ef241678769fee4a44aaa288f3b78aa1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/swoole.php',
    89         '0efc8f6778cba932b9e2a89e28de2452' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/uodbc.php',
    90         'd383d32907b98af53ee9208c62204fd0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/uopz.php',
    91         '2fd2e4060f7fe772660f002ce38f0b71' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/url.php',
    92         '782249e03deebeaf57b9991ff5493aa0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/var.php',
    93         '344440cd1cd7200fdb4f12af0d3c587f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xdiff.php',
    94         '3599f369219c658a5fb6c4fe66832f62' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xml.php',
    95         '7fcd313da9fae337051b091b3492c21b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xmlrpc.php',
    96         'd668c74cfa92d893b582356733d9a80e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/yaml.php',
    97         '4af1dca6db8c527c6eed27bff85ff0e5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/yaz.php',
    98         'fe43ca06499ac37bc2dedd823af71eb5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zip.php',
    99         '356736db98a6834f0a886b8d509b0ecd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zlib.php',
     10        '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
    10011    );
    10112
    10213    public static $prefixLengthsPsr4 = array (
    103         'c' => 
     14        'c' =>
    10415        array (
    10516            'chillerlan\\Settings\\' => 20,
    10617            'chillerlan\\QRCode\\' => 18,
    10718        ),
    108         'S' =>
     19        'P' =>
    10920        array (
    110             'Safe\\' => 5,
    111         ),
    112         'P' =>
    113         array (
     21            'Psr\\Clock\\' => 10,
    11422            'ParagonIE\\ConstantTime\\' => 23,
    11523        ),
    116         'O' => 
     24        'O' =>
    11725        array (
    11826            'OTPHP\\' => 6,
    119         ),
    120         'A' =>
    121         array (
    122             'Assert\\' => 7,
    12327        ),
    12428    );
    12529
    12630    public static $prefixDirsPsr4 = array (
    127         'chillerlan\\Settings\\' => 
     31        'chillerlan\\Settings\\' =>
    12832        array (
    12933            0 => __DIR__ . '/..' . '/chillerlan/php-settings-container/src',
    13034        ),
    131         'chillerlan\\QRCode\\' => 
     35        'chillerlan\\QRCode\\' =>
    13236        array (
    13337            0 => __DIR__ . '/..' . '/chillerlan/php-qrcode/src',
    13438        ),
    135         'Safe\\' =>
     39        'Psr\\Clock\\' =>
    13640        array (
    137             0 => __DIR__ . '/..' . '/thecodingmachine/safe/lib',
    138             1 => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated',
    139             2 => __DIR__ . '/..' . '/thecodingmachine/safe/generated',
     41            0 => __DIR__ . '/..' . '/psr/clock/src',
    14042        ),
    141         'ParagonIE\\ConstantTime\\' => 
     43        'ParagonIE\\ConstantTime\\' =>
    14244        array (
    14345            0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src',
    14446        ),
    145         'OTPHP\\' => 
     47        'OTPHP\\' =>
    14648        array (
    14749            0 => __DIR__ . '/..' . '/spomky-labs/otphp/src',
    148         ),
    149         'Assert\\' =>
    150         array (
    151             0 => __DIR__ . '/..' . '/beberlei/assert/lib/Assert',
    15250        ),
    15351    );
    … …  
    16058    {
    16159        return \Closure::bind(function () use ($loader) {
    162             $loader->prefixLengthsPsr4 = ComposerStaticInitc7ff053f14f929092d06e37035c6064c::$prefixLengthsPsr4;
    163             $loader->prefixDirsPsr4 = ComposerStaticInitc7ff053f14f929092d06e37035c6064c::$prefixDirsPsr4;
    164             $loader->classMap = ComposerStaticInitc7ff053f14f929092d06e37035c6064c::$classMap;
     60            $loader->prefixLengthsPsr4 = ComposerStaticInit0cde52f04200d0b097061008ff484a30::$prefixLengthsPsr4;
     61            $loader->prefixDirsPsr4 = ComposerStaticInit0cde52f04200d0b097061008ff484a30::$prefixDirsPsr4;
     62            $loader->classMap = ComposerStaticInit0cde52f04200d0b097061008ff484a30::$classMap;
    16563
    16664        }, null, ClassLoader::class);
  • wp-otp/trunk/vendor/composer/installed.json

    r2466054 r3752647  
    22    "packages": [
    33        {
    4             "name": "beberlei/assert",
    5             "version": "v3.3.0",
    6             "version_normalized": "3.3.0.0",
    7             "source": {
    8                 "type": "git",
    9                 "url": "https://github.com/beberlei/assert.git",
    10                 "reference": "5367e3895976b49704ae671f75bc5f0ba1b986ab"
    11             },
    12             "dist": {
    13                 "type": "zip",
    14                 "url": "https://api.github.com/repos/beberlei/assert/zipball/5367e3895976b49704ae671f75bc5f0ba1b986ab",
    15                 "reference": "5367e3895976b49704ae671f75bc5f0ba1b986ab",
    16                 "shasum": ""
    17             },
    18             "require": {
    19                 "ext-ctype": "*",
    20                 "ext-intl": "*",
    21                 "ext-json": "*",
     4            "name": "chillerlan/php-qrcode",
     5            "version": "6.0.1",
     6            "version_normalized": "6.0.1.0",
     7            "source": {
     8                "type": "git",
     9                "url": "https://github.com/chillerlan/php-qrcode.git",
     10                "reference": "49006e34bd5328f163e80329e7312f34dceea59b"
     11            },
     12            "dist": {
     13                "type": "zip",
     14                "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/49006e34bd5328f163e80329e7312f34dceea59b",
     15                "reference": "49006e34bd5328f163e80329e7312f34dceea59b",
     16                "shasum": ""
     17            },
     18            "require": {
     19                "chillerlan/php-settings-container": "^3.2.1",
    2220                "ext-mbstring": "*",
    23                 "ext-simplexml": "*",
    24                 "php": "^7.0 || ^8.0"
     21                "php": "^8.2"
    2522            },
    2623            "require-dev": {
    27                 "friendsofphp/php-cs-fixer": "*",
    28                 "phpstan/phpstan": "*",
    29                 "phpunit/phpunit": ">=6.0.0",
    30                 "yoast/phpunit-polyfills": "^0.1.0"
    31             },
    32             "time": "2020-11-13T20:02:54+00:00",
    33             "type": "library",
    34             "installation-source": "dist",
    35             "autoload": {
    36                 "psr-4": {
    37                     "Assert\\": "lib/Assert"
    38                 },
    39                 "files": [
    40                     "lib/Assert/functions.php"
    41                 ]
    42             },
    43             "notification-url": "https://packagist.org/downloads/",
    44             "license": [
    45                 "BSD-2-Clause"
    46             ],
    47             "authors": [
    48                 {
    49                     "name": "Benjamin Eberlei",
    50                     "email": "kontakt@beberlei.de",
    51                     "role": "Lead Developer"
    52                 },
    53                 {
    54                     "name": "Richard Quadling",
    55                     "email": "rquadling@gmail.com",
    56                     "role": "Collaborator"
    57                 }
    58             ],
    59             "description": "Thin assertion library for input validation in business models.",
    60             "keywords": [
    61                 "assert",
    62                 "assertion",
    63                 "validation"
    64             ],
    65             "support": {
    66                 "issues": "https://github.com/beberlei/assert/issues",
    67                 "source": "https://github.com/beberlei/assert/tree/v3.3.0"
    68             },
    69             "install-path": "../beberlei/assert"
    70         },
    71         {
    72             "name": "chillerlan/php-qrcode",
    73             "version": "4.3.0",
    74             "version_normalized": "4.3.0.0",
    75             "source": {
    76                 "type": "git",
    77                 "url": "https://github.com/chillerlan/php-qrcode.git",
    78                 "reference": "4968063fb3baeedb658293f89f9673fbf2499a3e"
    79             },
    80             "dist": {
    81                 "type": "zip",
    82                 "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/4968063fb3baeedb658293f89f9673fbf2499a3e",
    83                 "reference": "4968063fb3baeedb658293f89f9673fbf2499a3e",
    84                 "shasum": ""
    85             },
    86             "require": {
    87                 "chillerlan/php-settings-container": "^2.1",
    88                 "ext-mbstring": "*",
    89                 "php": "^7.4 || ^8.0"
    90             },
    91             "require-dev": {
    92                 "phan/phan": "^3.2.2",
    93                 "phpunit/phpunit": "^9.4",
    94                 "setasign/fpdf": "^1.8.2"
     24                "chillerlan/php-authenticator": "^5.3",
     25                "ext-fileinfo": "*",
     26                "intervention/image": "^3.11",
     27                "phan/phan": "^6.0.1",
     28                "phpbench/phpbench": "^1.4",
     29                "phpmd/phpmd": "^2.15",
     30                "phpstan/phpstan": "^2.1.40",
     31                "phpstan/phpstan-deprecation-rules": "^2.0.4",
     32                "phpunit/phpunit": "^11.5",
     33                "setasign/fpdf": "^1.8.6",
     34                "slevomat/coding-standard": "^8.28",
     35                "squizlabs/php_codesniffer": "^4.0"
    9536            },
    9637            "suggest": {
    9738                "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
    98                 "setasign/fpdf": "Required to use the QR FPDF output."
    99             },
    100             "time": "2020-11-18T20:49:20+00:00",
     39                "intervention/image": "More advanced GD and ImageMagick output.",
     40                "setasign/fpdf": "Required to use the QR FPDF output.",
     41                "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code"
     42            },
     43            "time": "2026-03-18T21:21:07+00:00",
    10144            "type": "library",
    10245            "installation-source": "dist",
    10346            "autoload": {
    10447                "psr-4": {
    105                     "chillerlan\\QRCode\\": "src/"
    106                 }
    107             },
    108             "notification-url": "https://packagist.org/downloads/",
    109             "license": [
    110                 "MIT"
     48                    "chillerlan\\QRCode\\": "src"
     49                }
     50            },
     51            "notification-url": "https://packagist.org/downloads/",
     52            "license": [
     53                "MIT",
     54                "Apache-2.0"
    11155            ],
    11256            "authors": [
    11357                {
    11458                    "name": "Kazuhiko Arase",
    115                     "homepage": "https://github.com/kazuhikoarase"
     59                    "homepage": "https://github.com/kazuhikoarase/qrcode-generator"
     60                },
     61                {
     62                    "name": "ZXing Authors",
     63                    "homepage": "https://github.com/zxing/zxing"
     64                },
     65                {
     66                    "name": "Ashot Khanamiryan",
     67                    "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder"
    11668                },
    11769                {
    … …  
    12577                }
    12678            ],
    127             "description": "A QR code generator. PHP 7.4+",
     79            "description": "A QR Code generator and reader with a user-friendly API. PHP 8.2+",
    12880            "homepage": "https://github.com/chillerlan/php-qrcode",
    12981            "keywords": [
    … …  
    13183                "qr",
    13284                "qr code",
     85                "qr-reader",
    13386                "qrcode",
    134                 "qrcode-generator"
    135             ],
    136             "support": {
     87                "qrcode-generator",
     88                "qrcode-reader"
     89            ],
     90            "support": {
     91                "docs": "https://php-qrcode.readthedocs.io",
    13792                "issues": "https://github.com/chillerlan/php-qrcode/issues",
    138                 "source": "https://github.com/chillerlan/php-qrcode/tree/4.3.0"
     93                "source": "https://github.com/chillerlan/php-qrcode"
    13994            },
    14095            "funding": [
    14196                {
    142                     "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
    143                     "type": "custom"
    144                 },
    145                 {
    14697                    "url": "https://ko-fi.com/codemasher",
    147                     "type": "ko_fi"
     98                    "type": "Ko-Fi"
    14899                }
    149100            ],
    … …  
    152103        {
    153104            "name": "chillerlan/php-settings-container",
    154             "version": "2.1.1",
    155             "version_normalized": "2.1.1.0",
     105            "version": "3.3.0",
     106            "version_normalized": "3.3.0.0",
    156107            "source": {
    157108                "type": "git",
    158109                "url": "https://github.com/chillerlan/php-settings-container.git",
    159                 "reference": "98ccc1b31b31a53bcb563465c4961879b2b93096"
    160             },
    161             "dist": {
    162                 "type": "zip",
    163                 "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/98ccc1b31b31a53bcb563465c4961879b2b93096",
    164                 "reference": "98ccc1b31b31a53bcb563465c4961879b2b93096",
     110                "reference": "a0a487cbf5344f721eb504bf0f59bada40c381b7"
     111            },
     112            "dist": {
     113                "type": "zip",
     114                "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/a0a487cbf5344f721eb504bf0f59bada40c381b7",
     115                "reference": "a0a487cbf5344f721eb504bf0f59bada40c381b7",
    165116                "shasum": ""
    166117            },
    167118            "require": {
    168119                "ext-json": "*",
    169                 "php": "^7.4 || ^8.0"
     120                "php": "^8.1"
    170121            },
    171122            "require-dev": {
    172                 "phan/phan": "^4.0",
    173                 "phpunit/phpunit": "^9.5"
    174             },
    175             "time": "2021-01-06T15:57:03+00:00",
     123                "phan/phan": "^5.5.2",
     124                "phpmd/phpmd": "^2.15",
     125                "phpstan/phpstan": "^2.1.31",
     126                "phpstan/phpstan-deprecation-rules": "^2.0.3",
     127                "phpunit/phpunit": "^10.5",
     128                "slevomat/coding-standard": "^8.22",
     129                "squizlabs/php_codesniffer": "^4.0"
     130            },
     131            "time": "2026-03-20T21:10:52+00:00",
    176132            "type": "library",
    177133            "installation-source": "dist",
    178134            "autoload": {
    179135                "psr-4": {
    180                     "chillerlan\\Settings\\": "src/"
     136                    "chillerlan\\Settings\\": "src"
    181137                }
    182138            },
    … …  
    192148                }
    193149            ],
    194             "description": "A container class for immutable settings objects. Not a DI container. PHP 7.4+",
     150            "description": "A container class for immutable settings objects. Not a DI container.",
    195151            "homepage": "https://github.com/chillerlan/php-settings-container",
    196152            "keywords": [
    197                 "PHP7",
    198153                "Settings",
     154                "configuration",
    199155                "container",
    200                 "helper"
     156                "helper",
     157                "property hook"
    201158            ],
    202159            "support": {
    … …  
    218175        {
    219176            "name": "paragonie/constant_time_encoding",
    220             "version": "v2.4.0",
    221             "version_normalized": "2.4.0.0",
     177            "version": "v3.1.3",
     178            "version_normalized": "3.1.3.0",
    222179            "source": {
    223180                "type": "git",
    224181                "url": "https://github.com/paragonie/constant_time_encoding.git",
    225                 "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c"
    226             },
    227             "dist": {
    228                 "type": "zip",
    229                 "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c",
    230                 "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c",
    231                 "shasum": ""
    232             },
    233             "require": {
    234                 "php": "^7|^8"
     182                "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77"
     183            },
     184            "dist": {
     185                "type": "zip",
     186                "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
     187                "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
     188                "shasum": ""
     189            },
     190            "require": {
     191                "php": "^8"
    235192            },
    236193            "require-dev": {
    237                 "phpunit/phpunit": "^6|^7|^8|^9",
    238                 "vimeo/psalm": "^1|^2|^3|^4"
    239             },
    240             "time": "2020-12-06T15:14:20+00:00",
     194                "infection/infection": "^0",
     195                "nikic/php-fuzzer": "^0",
     196                "phpunit/phpunit": "^9|^10|^11",
     197                "vimeo/psalm": "^4|^5|^6"
     198            },
     199            "time": "2025-09-24T15:06:41+00:00",
    241200            "type": "library",
    242201            "installation-source": "dist",
    … …  
    287246        },
    288247        {
     248            "name": "psr/clock",
     249            "version": "1.0.0",
     250            "version_normalized": "1.0.0.0",
     251            "source": {
     252                "type": "git",
     253                "url": "https://github.com/php-fig/clock.git",
     254                "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
     255            },
     256            "dist": {
     257                "type": "zip",
     258                "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
     259                "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
     260                "shasum": ""
     261            },
     262            "require": {
     263                "php": "^7.0 || ^8.0"
     264            },
     265            "time": "2022-11-25T14:36:26+00:00",
     266            "type": "library",
     267            "installation-source": "dist",
     268            "autoload": {
     269                "psr-4": {
     270                    "Psr\\Clock\\": "src/"
     271                }
     272            },
     273            "notification-url": "https://packagist.org/downloads/",
     274            "license": [
     275                "MIT"
     276            ],
     277            "authors": [
     278                {
     279                    "name": "PHP-FIG",
     280                    "homepage": "https://www.php-fig.org/"
     281                }
     282            ],
     283            "description": "Common interface for reading the clock.",
     284            "homepage": "https://github.com/php-fig/clock",
     285            "keywords": [
     286                "clock",
     287                "now",
     288                "psr",
     289                "psr-20",
     290                "time"
     291            ],
     292            "support": {
     293                "issues": "https://github.com/php-fig/clock/issues",
     294                "source": "https://github.com/php-fig/clock/tree/1.0.0"
     295            },
     296            "install-path": "../psr/clock"
     297        },
     298        {
    289299            "name": "spomky-labs/otphp",
    290             "version": "v10.0.1",
    291             "version_normalized": "10.0.1.0",
     300            "version": "11.5.0",
     301            "version_normalized": "11.5.0.0",
    292302            "source": {
    293303                "type": "git",
    294304                "url": "https://github.com/Spomky-Labs/otphp.git",
    295                 "reference": "f44cce5a9db4b8da410215d992110482c931232f"
    296             },
    297             "dist": {
    298                 "type": "zip",
    299                 "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/f44cce5a9db4b8da410215d992110482c931232f",
    300                 "reference": "f44cce5a9db4b8da410215d992110482c931232f",
    301                 "shasum": ""
    302             },
    303             "require": {
    304                 "beberlei/assert": "^3.0",
    305                 "ext-mbstring": "*",
    306                 "paragonie/constant_time_encoding": "^2.0",
    307                 "php": "^7.2|^8.0",
    308                 "thecodingmachine/safe": "^0.1.14|^1.0"
     305                "reference": "877683d6352b80cdc7020fd43a725629c2524435"
     306            },
     307            "dist": {
     308                "type": "zip",
     309                "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/877683d6352b80cdc7020fd43a725629c2524435",
     310                "reference": "877683d6352b80cdc7020fd43a725629c2524435",
     311                "shasum": ""
     312            },
     313            "require": {
     314                "paragonie/constant_time_encoding": "^2.0 || ^3.0",
     315                "php": ">=8.1",
     316                "psr/clock": "^1.0",
     317                "symfony/deprecation-contracts": "^3.2"
    309318            },
    310319            "require-dev": {
    311                 "php-coveralls/php-coveralls": "^2.0",
    312                 "phpstan/phpstan": "^0.12",
    313                 "phpstan/phpstan-beberlei-assert": "^0.12",
    314                 "phpstan/phpstan-deprecation-rules": "^0.12",
    315                 "phpstan/phpstan-phpunit": "^0.12",
    316                 "phpstan/phpstan-strict-rules": "^0.12",
    317                 "phpunit/phpunit": "^8.0",
    318                 "thecodingmachine/phpstan-safe-rule": "^1.0"
    319             },
    320             "time": "2020-01-28T09:24:19+00:00",
    321             "type": "library",
    322             "extra": {
    323                 "branch-alias": {
    324                     "v10.0": "10.0.x-dev",
    325                     "v9.0": "9.0.x-dev",
    326                     "v8.3": "8.3.x-dev"
    327                 }
    328             },
     320                "symfony/error-handler": "^6.4|^7.0|^8.0"
     321            },
     322            "time": "2026-06-06T23:41:24+00:00",
     323            "type": "library",
    329324            "installation-source": "dist",
    330325            "autoload": {
    … …  
    360355            "support": {
    361356                "issues": "https://github.com/Spomky-Labs/otphp/issues",
    362                 "source": "https://github.com/Spomky-Labs/otphp/tree/v10.0.1"
    363             },
     357                "source": "https://github.com/Spomky-Labs/otphp/tree/11.5.0"
     358            },
     359            "funding": [
     360                {
     361                    "url": "https://github.com/Spomky",
     362                    "type": "github"
     363                },
     364                {
     365                    "url": "https://www.patreon.com/FlorentMorselli",
     366                    "type": "patreon"
     367                }
     368            ],
    364369            "install-path": "../spomky-labs/otphp"
    365370        },
    366371        {
    367             "name": "thecodingmachine/safe",
    368             "version": "v1.3.3",
    369             "version_normalized": "1.3.3.0",
    370             "source": {
    371                 "type": "git",
    372                 "url": "https://github.com/thecodingmachine/safe.git",
    373                 "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc"
    374             },
    375             "dist": {
    376                 "type": "zip",
    377                 "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
    378                 "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
    379                 "shasum": ""
    380             },
    381             "require": {
    382                 "php": ">=7.2"
    383             },
    384             "require-dev": {
    385                 "phpstan/phpstan": "^0.12",
    386                 "squizlabs/php_codesniffer": "^3.2",
    387                 "thecodingmachine/phpstan-strict-rules": "^0.12"
    388             },
    389             "time": "2020-10-28T17:51:34+00:00",
     372            "name": "symfony/deprecation-contracts",
     373            "version": "v3.7.1",
     374            "version_normalized": "3.7.1.0",
     375            "source": {
     376                "type": "git",
     377                "url": "https://github.com/symfony/deprecation-contracts.git",
     378                "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
     379            },
     380            "dist": {
     381                "type": "zip",
     382                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
     383                "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
     384                "shasum": ""
     385            },
     386            "require": {
     387                "php": ">=8.1"
     388            },
     389            "time": "2026-06-05T06:23:12+00:00",
    390390            "type": "library",
    391391            "extra": {
     392                "thanks": {
     393                    "url": "https://github.com/symfony/contracts",
     394                    "name": "symfony/contracts"
     395                },
    392396                "branch-alias": {
    393                     "dev-master": "0.1-dev"
    394                 }
    395             },
    396             "installation-source": "dist",
    397             "autoload": {
    398                 "psr-4": {
    399                     "Safe\\": [
    400                         "lib/",
    401                         "deprecated/",
    402                         "generated/"
    403                     ]
    404                 },
     397                    "dev-main": "3.7-dev"
     398                }
     399            },
     400            "installation-source": "dist",
     401            "autoload": {
    405402                "files": [
    406                     "deprecated/apc.php",
    407                     "deprecated/libevent.php",
    408                     "deprecated/mssql.php",
    409                     "deprecated/stats.php",
    410                     "lib/special_cases.php",
    411                     "generated/apache.php",
    412                     "generated/apcu.php",
    413                     "generated/array.php",
    414                     "generated/bzip2.php",
    415                     "generated/calendar.php",
    416                     "generated/classobj.php",
    417                     "generated/com.php",
    418                     "generated/cubrid.php",
    419                     "generated/curl.php",
    420                     "generated/datetime.php",
    421                     "generated/dir.php",
    422                     "generated/eio.php",
    423                     "generated/errorfunc.php",
    424                     "generated/exec.php",
    425                     "generated/fileinfo.php",
    426                     "generated/filesystem.php",
    427                     "generated/filter.php",
    428                     "generated/fpm.php",
    429                     "generated/ftp.php",
    430                     "generated/funchand.php",
    431                     "generated/gmp.php",
    432                     "generated/gnupg.php",
    433                     "generated/hash.php",
    434                     "generated/ibase.php",
    435                     "generated/ibmDb2.php",
    436                     "generated/iconv.php",
    437                     "generated/image.php",
    438                     "generated/imap.php",
    439                     "generated/info.php",
    440                     "generated/ingres-ii.php",
    441                     "generated/inotify.php",
    442                     "generated/json.php",
    443                     "generated/ldap.php",
    444                     "generated/libxml.php",
    445                     "generated/lzf.php",
    446                     "generated/mailparse.php",
    447                     "generated/mbstring.php",
    448                     "generated/misc.php",
    449                     "generated/msql.php",
    450                     "generated/mysql.php",
    451                     "generated/mysqli.php",
    452                     "generated/mysqlndMs.php",
    453                     "generated/mysqlndQc.php",
    454                     "generated/network.php",
    455                     "generated/oci8.php",
    456                     "generated/opcache.php",
    457                     "generated/openssl.php",
    458                     "generated/outcontrol.php",
    459                     "generated/password.php",
    460                     "generated/pcntl.php",
    461                     "generated/pcre.php",
    462                     "generated/pdf.php",
    463                     "generated/pgsql.php",
    464                     "generated/posix.php",
    465                     "generated/ps.php",
    466                     "generated/pspell.php",
    467                     "generated/readline.php",
    468                     "generated/rpminfo.php",
    469                     "generated/rrd.php",
    470                     "generated/sem.php",
    471                     "generated/session.php",
    472                     "generated/shmop.php",
    473                     "generated/simplexml.php",
    474                     "generated/sockets.php",
    475                     "generated/sodium.php",
    476                     "generated/solr.php",
    477                     "generated/spl.php",
    478                     "generated/sqlsrv.php",
    479                     "generated/ssdeep.php",
    480                     "generated/ssh2.php",
    481                     "generated/stream.php",
    482                     "generated/strings.php",
    483                     "generated/swoole.php",
    484                     "generated/uodbc.php",
    485                     "generated/uopz.php",
    486                     "generated/url.php",
    487                     "generated/var.php",
    488                     "generated/xdiff.php",
    489                     "generated/xml.php",
    490                     "generated/xmlrpc.php",
    491                     "generated/yaml.php",
    492                     "generated/yaz.php",
    493                     "generated/zip.php",
    494                     "generated/zlib.php"
     403                    "function.php"
    495404                ]
    496405            },
    … …  
    499408                "MIT"
    500409            ],
    501             "description": "PHP core functions that throw exceptions instead of returning FALSE on error",
    502             "support": {
    503                 "issues": "https://github.com/thecodingmachine/safe/issues",
    504                 "source": "https://github.com/thecodingmachine/safe/tree/v1.3.3"
    505             },
    506             "install-path": "../thecodingmachine/safe"
     410            "authors": [
     411                {
     412                    "name": "Nicolas Grekas",
     413                    "email": "p@tchwork.com"
     414                },
     415                {
     416                    "name": "Symfony Community",
     417                    "homepage": "https://symfony.com/contributors"
     418                }
     419            ],
     420            "description": "A generic function and convention to trigger deprecation notices",
     421            "homepage": "https://symfony.com",
     422            "support": {
     423                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1"
     424            },
     425            "funding": [
     426                {
     427                    "url": "https://symfony.com/sponsor",
     428                    "type": "custom"
     429                },
     430                {
     431                    "url": "https://github.com/fabpot",
     432                    "type": "github"
     433                },
     434                {
     435                    "url": "https://github.com/nicolas-grekas",
     436                    "type": "github"
     437                },
     438                {
     439                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
     440                    "type": "tidelift"
     441                }
     442            ],
     443            "install-path": "../symfony/deprecation-contracts"
    507444        }
    508445    ],
  • wp-otp/trunk/vendor/composer/installed.php

    r2466054 r3752647  
    1 <?php return array (
    2   'root' =>
    3   array (
    4     'pretty_version' => 'trunk',
    5     'version' => 'dev-trunk',
    6     'aliases' =>
    7     array (
     1<?php return array(
     2    'root' => array(
     3        'name' => 'noplanman/wp-otp',
     4        'pretty_version' => 'trunk',
     5        'version' => 'dev-trunk',
     6        'reference' => null,
     7        'type' => 'wordpress-plugin',
     8        'install_path' => __DIR__ . '/../../',
     9        'aliases' => array(),
     10        'dev' => false,
    811    ),
    9     'reference' => NULL,
    10     'name' => 'noplanman/wp-otp',
    11   ),
    12   'versions' =>
    13   array (
    14     'beberlei/assert' =>
    15     array (
    16       'pretty_version' => 'v3.3.0',
    17       'version' => '3.3.0.0',
    18       'aliases' =>
    19       array (
    20       ),
    21       'reference' => '5367e3895976b49704ae671f75bc5f0ba1b986ab',
     12    'versions' => array(
     13        'chillerlan/php-qrcode' => array(
     14            'pretty_version' => '6.0.1',
     15            'version' => '6.0.1.0',
     16            'reference' => '49006e34bd5328f163e80329e7312f34dceea59b',
     17            'type' => 'library',
     18            'install_path' => __DIR__ . '/../chillerlan/php-qrcode',
     19            'aliases' => array(),
     20            'dev_requirement' => false,
     21        ),
     22        'chillerlan/php-settings-container' => array(
     23            'pretty_version' => '3.3.0',
     24            'version' => '3.3.0.0',
     25            'reference' => 'a0a487cbf5344f721eb504bf0f59bada40c381b7',
     26            'type' => 'library',
     27            'install_path' => __DIR__ . '/../chillerlan/php-settings-container',
     28            'aliases' => array(),
     29            'dev_requirement' => false,
     30        ),
     31        'noplanman/wp-otp' => array(
     32            'pretty_version' => 'trunk',
     33            'version' => 'dev-trunk',
     34            'reference' => null,
     35            'type' => 'wordpress-plugin',
     36            'install_path' => __DIR__ . '/../../',
     37            'aliases' => array(),
     38            'dev_requirement' => false,
     39        ),
     40        'paragonie/constant_time_encoding' => array(
     41            'pretty_version' => 'v3.1.3',
     42            'version' => '3.1.3.0',
     43            'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77',
     44            'type' => 'library',
     45            'install_path' => __DIR__ . '/../paragonie/constant_time_encoding',
     46            'aliases' => array(),
     47            'dev_requirement' => false,
     48        ),
     49        'psr/clock' => array(
     50            'pretty_version' => '1.0.0',
     51            'version' => '1.0.0.0',
     52            'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d',
     53            'type' => 'library',
     54            'install_path' => __DIR__ . '/../psr/clock',
     55            'aliases' => array(),
     56            'dev_requirement' => false,
     57        ),
     58        'spomky-labs/otphp' => array(
     59            'pretty_version' => '11.5.0',
     60            'version' => '11.5.0.0',
     61            'reference' => '877683d6352b80cdc7020fd43a725629c2524435',
     62            'type' => 'library',
     63            'install_path' => __DIR__ . '/../spomky-labs/otphp',
     64            'aliases' => array(),
     65            'dev_requirement' => false,
     66        ),
     67        'symfony/deprecation-contracts' => array(
     68            'pretty_version' => 'v3.7.1',
     69            'version' => '3.7.1.0',
     70            'reference' => 'f3202fa1b5097b0af062dc978b32ecf63404e31d',
     71            'type' => 'library',
     72            'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
     73            'aliases' => array(),
     74            'dev_requirement' => false,
     75        ),
    2276    ),
    23     'chillerlan/php-qrcode' =>
    24     array (
    25       'pretty_version' => '4.3.0',
    26       'version' => '4.3.0.0',
    27       'aliases' =>
    28       array (
    29       ),
    30       'reference' => '4968063fb3baeedb658293f89f9673fbf2499a3e',
    31     ),
    32     'chillerlan/php-settings-container' =>
    33     array (
    34       'pretty_version' => '2.1.1',
    35       'version' => '2.1.1.0',
    36       'aliases' =>
    37       array (
    38       ),
    39       'reference' => '98ccc1b31b31a53bcb563465c4961879b2b93096',
    40     ),
    41     'noplanman/wp-otp' =>
    42     array (
    43       'pretty_version' => 'trunk',
    44       'version' => 'dev-trunk',
    45       'aliases' =>
    46       array (
    47       ),
    48       'reference' => NULL,
    49     ),
    50     'paragonie/constant_time_encoding' =>
    51     array (
    52       'pretty_version' => 'v2.4.0',
    53       'version' => '2.4.0.0',
    54       'aliases' =>
    55       array (
    56       ),
    57       'reference' => 'f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c',
    58     ),
    59     'spomky-labs/otphp' =>
    60     array (
    61       'pretty_version' => 'v10.0.1',
    62       'version' => '10.0.1.0',
    63       'aliases' =>
    64       array (
    65       ),
    66       'reference' => 'f44cce5a9db4b8da410215d992110482c931232f',
    67     ),
    68     'thecodingmachine/safe' =>
    69     array (
    70       'pretty_version' => 'v1.3.3',
    71       'version' => '1.3.3.0',
    72       'aliases' =>
    73       array (
    74       ),
    75       'reference' => 'a8ab0876305a4cdaef31b2350fcb9811b5608dbc',
    76     ),
    77   ),
    7877);
  • wp-otp/trunk/vendor/composer/platform_check.php

    r2466054 r3752647  
    55$issues = array();
    66
    7 if (!(PHP_VERSION_ID >= 70400)) {
    8     $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
     7if (!(PHP_VERSION_ID >= 80200)) {
     8    $issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.';
    99}
    1010
    … …  
    2020        }
    2121    }
    22     trigger_error(
    23         'Composer detected issues in your platform: ' . implode(' ', $issues),
    24         E_USER_ERROR
     22    throw new \RuntimeException(
     23        'Composer detected issues in your platform: ' . implode(' ', $issues)
    2524    );
    2625}
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/LICENSE.txt

    r2466054 r3752647  
    11The MIT License (MIT)
    22
    3 Copyright (c) 2016 - 2020 Paragon Initiative Enterprises
     3Copyright (c) 2016 - 2022 Paragon Initiative Enterprises
    44
    55Permission is hereby granted, free of charge, to any person obtaining a copy
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/README.md

    r2192307 r3752647  
    11# Constant-Time Encoding
    22
    3 [![Build Status](https://travis-ci.org/paragonie/constant_time_encoding.svg?branch=master)](https://travis-ci.org/paragonie/constant_time_encoding)
     3[![Build Status](https://github.com/paragonie/constant_time_encoding/actions/workflows/ci.yml/badge.svg)](https://github.com/paragonie/constant_time_encoding/actions)
     4[![Static Analysis](https://github.com/paragonie/constant_time_encoding/actions/workflows/psalm.yml/badge.svg)](https://github.com/paragonie/constant_time_encoding/actions)
    45[![Latest Stable Version](https://poser.pugx.org/paragonie/constant_time_encoding/v/stable)](https://packagist.org/packages/paragonie/constant_time_encoding)
    56[![Latest Unstable Version](https://poser.pugx.org/paragonie/constant_time_encoding/v/unstable)](https://packagist.org/packages/paragonie/constant_time_encoding)
    … …  
    1213misses. Further reading on [cache-timing attacks](http://blog.ircmaxell.com/2014/11/its-all-about-time.html).
    1314
    14 Our fork offers the following enchancements:
     15Our fork offers the following enhancements:
    1516
    1617* `mbstring.func_overload` resistance
    … …  
    2324## PHP Version Requirements
    2425
    25 Version 2 of this library should work on **PHP 7** or newer. For PHP 5
    26 support, see [the v1.x branch](https://github.com/paragonie/constant_time_encoding/tree/v1.x).
     26Version 3 of this library should work on **PHP 8** or newer.
    2727
    28 If you are adding this as a dependency to a project intended to work on both PHP 5 and PHP 7, please set the required version to `^1|^2` instead of just `^1` or `^2`.
     28Version 2 of this library should work on **PHP 7** or newer. See [the v2.x branch](https://github.com/paragonie/constant_time_encoding/tree/v2.x).
     29
     30For PHP 5 support, see [the v1.x branch](https://github.com/paragonie/constant_time_encoding/tree/v1.x).
     31
     32If you are adding this as a dependency to a project intended to work on PHP 5 through 8.4, please set the required version to `^1|^2|^3`.
    2933
    3034## How to Install
    … …  
    3741
    3842```php
    39 use \ParagonIE\ConstantTime\Encoding;
     43use ParagonIE\ConstantTime\Encoding;
    4044
    4145// possibly (if applicable):
    … …  
    6468
    6569```php
    66 use \ParagonIE\ConstantTime\Base64;
    67 use \ParagonIE\ConstantTime\Base32;
     70use ParagonIE\ConstantTime\Base64;
     71use ParagonIE\ConstantTime\Base32;
    6872
    6973$data = random_bytes(32);
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/composer.json

    r2466054 r3752647  
    3838  },
    3939  "require": {
    40     "php": "^7|^8"
     40    "php": "^8"
    4141  },
    4242  "require-dev": {
    43     "phpunit/phpunit": "^6|^7|^8|^9",
    44     "vimeo/psalm": "^1|^2|^3|^4"
     43    "infection/infection": "^0",
     44    "nikic/php-fuzzer": "^0",
     45    "phpunit/phpunit": "^9|^10|^11",
     46    "vimeo/psalm": "^4|^5|^6"
    4547  },
    4648  "autoload": {
    … …  
    4850      "ParagonIE\\ConstantTime\\": "src/"
    4951    }
     52  },
     53  "autoload-dev": {
     54    "psr-4": {
     55      "ParagonIE\\ConstantTime\\Tests\\": "tests/"
     56    }
     57  },
     58  "scripts": {
     59    "mutation-test": "infection"
     60  },
     61  "config": {
     62    "process-timeout": 0,
     63    "allow-plugins": {
     64      "infection/extension-installer": true
     65    }
    5066  }
    5167}
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/Base32.php

    r2466054 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use InvalidArgumentException;
     6use Override;
     7use RangeException;
     8use SensitiveParameter;
     9use TypeError;
     10use function pack;
     11use function rtrim;
     12use function strlen;
     13use function substr;
     14use function unpack;
     15
    516/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     17 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
    718 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    819 *
    … …  
    4152     * @return string
    4253     */
    43     public static function decode(string $encodedString, bool $strictPadding = false): string
    44     {
     54    #[Override]
     55    public static function decode(
     56        #[SensitiveParameter]
     57        string $encodedString,
     58        bool $strictPadding = false
     59    ): string {
    4560        return static::doDecode($encodedString, false, $strictPadding);
    4661    }
    … …  
    5368     * @return string
    5469     */
    55     public static function decodeUpper(string $src, bool $strictPadding = false): string
    56     {
     70    public static function decodeUpper(
     71        #[SensitiveParameter]
     72        string $src,
     73        bool $strictPadding = false
     74    ): string {
    5775        return static::doDecode($src, true, $strictPadding);
    5876    }
    … …  
    6179     * Encode into Base32 (RFC 4648)
    6280     *
     81     * @param string $binString
     82     * @return string
     83     * @throws TypeError
     84     */
     85    #[Override]
     86    public static function encode(
     87        #[SensitiveParameter]
     88        string $binString
     89    ): string {
     90        return static::doEncode($binString, false, true);
     91    }
     92
     93    /**
     94     * Encode into Base32 (RFC 4648)
     95     *
    6396     * @param string $src
    6497     * @return string
    65      * @throws \TypeError
    66      */
    67     public static function encode(string $src): string
    68     {
    69         return static::doEncode($src, false, true);
    70     }
    71     /**
    72      * Encode into Base32 (RFC 4648)
     98     * @throws TypeError
     99     * @api
     100     */
     101    public static function encodeUnpadded(
     102        #[SensitiveParameter]
     103        string $src
     104    ): string {
     105        return static::doEncode($src, false, false);
     106    }
     107
     108    /**
     109     * Encode into uppercase Base32 (RFC 4648)
    73110     *
    74111     * @param string $src
    75112     * @return string
    76      * @throws \TypeError
    77      */
    78     public static function encodeUnpadded(string $src): string
    79     {
    80         return static::doEncode($src, false, false);
     113     * @throws TypeError
     114     * @api
     115     */
     116    public static function encodeUpper(
     117        #[SensitiveParameter]
     118        string $src
     119    ): string {
     120        return static::doEncode($src, true, true);
    81121    }
    82122
    … …  
    86126     * @param string $src
    87127     * @return string
    88      * @throws \TypeError
    89      */
    90     public static function encodeUpper(string $src): string
    91     {
    92         return static::doEncode($src, true, true);
    93     }
    94 
    95     /**
    96      * Encode into uppercase Base32 (RFC 4648)
    97      *
    98      * @param string $src
    99      * @return string
    100      * @throws \TypeError
    101      */
    102     public static function encodeUpperUnpadded(string $src): string
    103     {
     128     * @throws TypeError
     129     * @api
     130     */
     131    public static function encodeUpperUnpadded(
     132        #[SensitiveParameter]
     133        string $src
     134    ): string {
    104135        return static::doEncode($src, true, false);
    105136    }
    … …  
    111142     * @param int $src
    112143     * @return int
     144     * @api
    113145     */
    114146    protected static function decode5Bits(int $src): int
    … …  
    133165     * @param int $src
    134166     * @return int
     167     * @api
    135168     */
    136169    protected static function decode5BitsUpper(int $src): int
    … …  
    153186     * @param int $src
    154187     * @return string
     188     * @api
    155189     */
    156190    protected static function encode5Bits(int $src): string
    … …  
    161195        $diff -= ((25 - $src) >> 8) & 73;
    162196
    163         return \pack('C', $src + $diff);
     197        return pack('C', $src + $diff);
    164198    }
    165199
    … …  
    172206     * @param int $src
    173207     * @return string
     208     * @api
    174209     */
    175210    protected static function encode5BitsUpper(int $src): string
    … …  
    180215        $diff -= ((25 - $src) >> 8) & 41;
    181216
    182         return \pack('C', $src + $diff);
    183     }
    184 
     217        return pack('C', $src + $diff);
     218    }
     219
     220    /**
     221     * @param string $encodedString
     222     * @param bool $upper
     223     * @return string
     224     * @api
     225     */
     226    public static function decodeNoPadding(
     227        #[SensitiveParameter]
     228        string $encodedString,
     229        bool $upper = false
     230    ): string {
     231        $srcLen = strlen($encodedString);
     232        if ($srcLen === 0) {
     233            return '';
     234        }
     235        if (($srcLen & 7) === 0) {
     236            for ($j = 0; $j < 7 && $j < $srcLen; ++$j) {
     237                if ($encodedString[$srcLen - $j - 1] === '=') {
     238                    throw new InvalidArgumentException(
     239                        "decodeNoPadding() doesn't tolerate padding"
     240                    );
     241                }
     242            }
     243        }
     244        return static::doDecode(
     245            $encodedString,
     246            $upper,
     247            true
     248        );
     249    }
    185250
    186251    /**
    … …  
    191256     * @param bool $strictPadding
    192257     * @return string
    193      * @throws \TypeError
    194      * @psalm-suppress RedundantCondition
    195      */
    196     protected static function doDecode(string $src, bool $upper = false, bool $strictPadding = false): string
    197     {
     258     *
     259     * @throws TypeError
     260     */
     261    protected static function doDecode(
     262        #[SensitiveParameter]
     263        string $src,
     264        bool $upper = false,
     265        bool $strictPadding = false
     266    ): string {
    198267        // We do this to reduce code duplication:
    199268        $method = $upper
    … …  
    202271
    203272        // Remove padding
    204         $srcLen = Binary::safeStrlen($src);
     273        $srcLen = strlen($src);
    205274        if ($srcLen === 0) {
    206275            return '';
    … …  
    217286            }
    218287            if (($srcLen & 7) === 1) {
    219                 throw new \RangeException(
     288                throw new RangeException(
    220289                    'Incorrect padding'
    221290                );
    222291            }
    223292        } else {
    224             $src = \rtrim($src, '=');
    225             $srcLen = Binary::safeStrlen($src);
     293            $src = rtrim($src, '=');
     294            $srcLen = strlen($src);
    226295        }
    227296
    … …  
    231300        for ($i = 0; $i + 8 <= $srcLen; $i += 8) {
    232301            /** @var array<int, int> $chunk */
    233             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 8));
     302            $chunk = unpack('C*', substr($src, $i, 8));
    234303            /** @var int $c0 */
    235304            $c0 = static::$method($chunk[1]);
    … …  
    249318            $c7 = static::$method($chunk[8]);
    250319
    251             $dest .= \pack(
     320            $dest .= pack(
    252321                'CCCCC',
    253322                (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
    … …  
    262331        if ($i < $srcLen) {
    263332            /** @var array<int, int> $chunk */
    264             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
     333            $chunk = unpack('C*', substr($src, $i, $srcLen - $i));
    265334            /** @var int $c0 */
    266335            $c0 = static::$method($chunk[1]);
    … …  
    280349                $c6 = static::$method($chunk[7]);
    281350
    282                 $dest .= \pack(
     351                $dest .= pack(
    283352                    'CCCC',
    284353                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
    … …  
    288357                );
    289358                $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6) >> 8;
     359                if ($strictPadding) {
     360                    $err |= ($c6 << 5) & 0xff;
     361                }
    290362            } elseif ($i + 5 < $srcLen) {
    291363                /** @var int $c1 */
    … …  
    300372                $c5 = static::$method($chunk[6]);
    301373
    302                 $dest .= \pack(
     374                $dest .= pack(
    303375                    'CCCC',
    304376                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
    … …  
    318390                $c4 = static::$method($chunk[5]);
    319391
    320                 $dest .= \pack(
     392                $dest .= pack(
    321393                    'CCC',
    322394                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
    … …  
    325397                );
    326398                $err |= ($c0 | $c1 | $c2 | $c3 | $c4) >> 8;
     399                if ($strictPadding) {
     400                    $err |= ($c4 << 7) & 0xff;
     401                }
    327402            } elseif ($i + 3 < $srcLen) {
    328403                /** @var int $c1 */
    … …  
    333408                $c3 = static::$method($chunk[4]);
    334409
    335                 $dest .= \pack(
     410                $dest .= pack(
    336411                    'CC',
    337412                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
    … …  
    339414                );
    340415                $err |= ($c0 | $c1 | $c2 | $c3) >> 8;
     416                if ($strictPadding) {
     417                    $err |= ($c3 << 4) & 0xff;
     418                }
    341419            } elseif ($i + 2 < $srcLen) {
    342420                /** @var int $c1 */
    … …  
    345423                $c2 = static::$method($chunk[3]);
    346424
    347                 $dest .= \pack(
     425                $dest .= pack(
    348426                    'CC',
    349427                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff,
    … …  
    351429                );
    352430                $err |= ($c0 | $c1 | $c2) >> 8;
     431                if ($strictPadding) {
     432                    $err |= ($c2 << 6) & 0xff;
     433                }
    353434            } elseif ($i + 1 < $srcLen) {
    354435                /** @var int $c1 */
    355436                $c1 = static::$method($chunk[2]);
    356437
    357                 $dest .= \pack(
     438                $dest .= pack(
    358439                    'C',
    359440                    (($c0 << 3) | ($c1 >> 2)             ) & 0xff
    360441                );
    361442                $err |= ($c0 | $c1) >> 8;
     443                if ($strictPadding) {
     444                    $err |= ($c1 << 6) & 0xff;
     445                }
    362446            } else {
    363                 $dest .= \pack(
     447                $dest .= pack(
    364448                    'C',
    365449                    (($c0 << 3)                          ) & 0xff
    … …  
    368452            }
    369453        }
    370         /** @var bool $check */
    371454        $check = ($err === 0);
    372455        if (!$check) {
    373             throw new \RangeException(
     456            throw new RangeException(
    374457                'Base32::doDecode() only expects characters in the correct base32 alphabet'
    375458            );
    … …  
    385468     * @param bool $pad
    386469     * @return string
    387      * @throws \TypeError
    388      */
    389     protected static function doEncode(string $src, bool $upper = false, $pad = true): string
    390     {
     470     * @throws TypeError
     471     */
     472    protected static function doEncode(
     473        #[SensitiveParameter]
     474        string $src,
     475        bool $upper = false,
     476        bool $pad = true
     477    ): string {
    391478        // We do this to reduce code duplication:
    392479        $method = $upper
    … …  
    395482       
    396483        $dest = '';
    397         $srcLen = Binary::safeStrlen($src);
     484        $srcLen = strlen($src);
    398485
    399486        // Main loop (no padding):
    400487        for ($i = 0; $i + 5 <= $srcLen; $i += 5) {
    401488            /** @var array<int, int> $chunk */
    402             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 5));
     489            $chunk = unpack('C*', substr($src, $i, 5));
    403490            $b0 = $chunk[1];
    404491            $b1 = $chunk[2];
    … …  
    419506        if ($i < $srcLen) {
    420507            /** @var array<int, int> $chunk */
    421             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
     508            $chunk = unpack('C*', substr($src, $i, $srcLen - $i));
    422509            $b0 = $chunk[1];
    423510            if ($i + 3 < $srcLen) {
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/Base32Hex.php

    r2192307 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use Override;
     6use function pack;
     7
    58/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     9 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
    710 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    811 *
    … …  
    4144     * @return int
    4245     */
     46    #[Override]
    4347    protected static function decode5Bits(int $src): int
    4448    {
    … …  
    6165     * @return int
    6266     */
     67    #[Override]
    6368    protected static function decode5BitsUpper(int $src): int
    6469    {
    … …  
    8186     * @return string
    8287     */
     88    #[Override]
    8389    protected static function encode5Bits(int $src): string
    8490    {
    … …  
    8894        $src += ((0x39 - $src) >> 8) & 39;
    8995
    90         return \pack('C', $src);
     96        return pack('C', $src);
    9197    }
    9298
    … …  
    100106     * @return string
    101107     */
     108    #[Override]
    102109    protected static function encode5BitsUpper(int $src): string
    103110    {
    … …  
    107114        $src += ((0x39 - $src) >> 8) & 7;
    108115
    109         return \pack('C', $src);
     116        return pack('C', $src);
    110117    }
    111118}
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/Base64.php

    r2466054 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use InvalidArgumentException;
     6use Override;
     7use RangeException;
     8use SensitiveParameter;
     9use SodiumException;
     10use TypeError;
     11use function extension_loaded;
     12use function pack;
     13use function rtrim;
     14use function sodium_base642bin;
     15use function sodium_bin2base64;
     16use function strlen;
     17use function substr;
     18use function unpack;
     19use const SODIUM_BASE64_VARIANT_ORIGINAL;
     20use const SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING;
     21use const SODIUM_BASE64_VARIANT_URLSAFE;
     22use const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING;
     23
    524/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     25 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
    726 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    827 *
    … …  
    3958     * Base64 character set "[A-Z][a-z][0-9]+/"
    4059     *
     60     * @param string $binString
     61     * @return string
     62     *
     63     * @throws TypeError
     64     */
     65    #[Override]
     66    public static function encode(
     67        #[SensitiveParameter]
     68        string $binString
     69    ): string {
     70        if (extension_loaded('sodium')) {
     71            $variant = match(static::class) {
     72                Base64::class => SODIUM_BASE64_VARIANT_ORIGINAL,
     73                Base64UrlSafe::class => SODIUM_BASE64_VARIANT_URLSAFE,
     74                default => 0,
     75            };
     76            if ($variant > 0) {
     77                try {
     78                    return sodium_bin2base64($binString, $variant);
     79                } catch (SodiumException $ex) {
     80                    throw new RangeException($ex->getMessage(), $ex->getCode(), $ex);
     81                }
     82            }
     83        }
     84        return static::doEncode($binString, true);
     85    }
     86
     87    /**
     88     * Encode into Base64, no = padding
     89     *
     90     * Base64 character set "[A-Z][a-z][0-9]+/"
     91     *
    4192     * @param string $src
    4293     * @return string
    43      * @throws \TypeError
    44      */
    45     public static function encode(string $src): string
    46     {
    47         return static::doEncode($src, true);
    48     }
    49 
    50     /**
    51      * Encode into Base64, no = padding
    52      *
    53      * Base64 character set "[A-Z][a-z][0-9]+/"
    54      *
    55      * @param string $src
    56      * @return string
    57      * @throws \TypeError
    58      */
    59     public static function encodeUnpadded(string $src): string
    60     {
     94     *
     95     * @throws TypeError
     96     * @api
     97     */
     98    public static function encodeUnpadded(
     99        #[SensitiveParameter]
     100        string $src
     101    ): string {
     102        if (extension_loaded('sodium')) {
     103            $variant = match(static::class) {
     104                Base64::class => SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING,
     105                Base64UrlSafe::class => SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,
     106                default => 0,
     107            };
     108            if ($variant > 0) {
     109                try {
     110                    return sodium_bin2base64($src, $variant);
     111                } catch (SodiumException $ex) {
     112                    throw new RangeException($ex->getMessage(), $ex->getCode(), $ex);
     113                }
     114            }
     115        }
    61116        return static::doEncode($src, false);
    62117    }
    … …  
    66121     * @param bool $pad   Include = padding?
    67122     * @return string
    68      * @throws \TypeError
    69      */
    70     protected static function doEncode(string $src, bool $pad = true): string
    71     {
     123     *
     124     * @throws TypeError
     125     */
     126    protected static function doEncode(
     127        #[SensitiveParameter]
     128        string $src,
     129        bool $pad = true
     130    ): string {
    72131        $dest = '';
    73         $srcLen = Binary::safeStrlen($src);
     132        $srcLen = strlen($src);
    74133        // Main loop (no padding):
    75134        for ($i = 0; $i + 3 <= $srcLen; $i += 3) {
    76135            /** @var array<int, int> $chunk */
    77             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, 3));
     136            $chunk = unpack('C*', substr($src, $i, 3));
    78137            $b0 = $chunk[1];
    79138            $b1 = $chunk[2];
    … …  
    89148        if ($i < $srcLen) {
    90149            /** @var array<int, int> $chunk */
    91             $chunk = \unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i));
     150            $chunk = unpack('C*', substr($src, $i, $srcLen - $i));
    92151            $b0 = $chunk[1];
    93152            if ($i + 1 < $srcLen) {
    … …  
    120179     * @param bool $strictPadding
    121180     * @return string
    122      * @throws \RangeException
    123      * @throws \TypeError
    124      * @psalm-suppress RedundantCondition
    125      */
    126     public static function decode(string $encodedString, bool $strictPadding = false): string
    127     {
     181     *
     182     * @throws RangeException
     183     * @throws TypeError
     184     */
     185    #[Override]
     186    public static function decode(
     187        #[SensitiveParameter]
     188        string $encodedString,
     189        bool $strictPadding = false
     190    ): string {
    128191        // Remove padding
    129         $srcLen = Binary::safeStrlen($encodedString);
     192        $srcLen = strlen($encodedString);
    130193        if ($srcLen === 0) {
    131194            return '';
    … …  
    142205            }
    143206            if (($srcLen & 3) === 1) {
    144                 throw new \RangeException(
     207                throw new RangeException(
    145208                    'Incorrect padding'
    146209                );
    147210            }
    148211            if ($encodedString[$srcLen - 1] === '=') {
    149                 throw new \RangeException(
     212                throw new RangeException(
    150213                    'Incorrect padding'
    151214                );
    152215            }
     216            if (extension_loaded('sodium')) {
     217                $variant = match(static::class) {
     218                    Base64::class => SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING,
     219                    Base64UrlSafe::class => SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,
     220                    default => 0,
     221                };
     222                if ($variant > 0) {
     223                    try {
     224                        return sodium_base642bin(substr($encodedString, 0, $srcLen), $variant);
     225                    } catch (SodiumException $ex) {
     226                        throw new RangeException($ex->getMessage(), $ex->getCode(), $ex);
     227                    }
     228                }
     229            }
    153230        } else {
    154             $encodedString = \rtrim($encodedString, '=');
    155             $srcLen = Binary::safeStrlen($encodedString);
     231            // Just remove all padding.
     232            $encodedString = rtrim($encodedString, '=');
     233            $srcLen = strlen($encodedString);
    156234        }
    157235
    … …  
    161239        for ($i = 0; $i + 4 <= $srcLen; $i += 4) {
    162240            /** @var array<int, int> $chunk */
    163             $chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, 4));
     241            $chunk = unpack('C*', substr($encodedString, $i, 4));
    164242            $c0 = static::decode6Bits($chunk[1]);
    165243            $c1 = static::decode6Bits($chunk[2]);
    … …  
    167245            $c3 = static::decode6Bits($chunk[4]);
    168246
    169             $dest .= \pack(
     247            $dest .= pack(
    170248                'CCC',
    171249                ((($c0 << 2) | ($c1 >> 4)) & 0xff),
    … …  
    178256        if ($i < $srcLen) {
    179257            /** @var array<int, int> $chunk */
    180             $chunk = \unpack('C*', Binary::safeSubstr($encodedString, $i, $srcLen - $i));
     258            $chunk = unpack('C*', substr($encodedString, $i, $srcLen - $i));
    181259            $c0 = static::decode6Bits($chunk[1]);
    182260
    … …  
    184262                $c1 = static::decode6Bits($chunk[2]);
    185263                $c2 = static::decode6Bits($chunk[3]);
    186                 $dest .= \pack(
     264                $dest .= pack(
    187265                    'CC',
    188266                    ((($c0 << 2) | ($c1 >> 4)) & 0xff),
    … …  
    190268                );
    191269                $err |= ($c0 | $c1 | $c2) >> 8;
     270                if ($strictPadding) {
     271                    $err |= ($c2 << 6) & 0xff;
     272                }
    192273            } elseif ($i + 1 < $srcLen) {
    193274                $c1 = static::decode6Bits($chunk[2]);
    194                 $dest .= \pack(
     275                $dest .= pack(
    195276                    'C',
    196277                    ((($c0 << 2) | ($c1 >> 4)) & 0xff)
    197278                );
    198279                $err |= ($c0 | $c1) >> 8;
    199             } elseif ($i < $srcLen && $strictPadding) {
     280                if ($strictPadding) {
     281                    $err |= ($c1 << 4) & 0xff;
     282                }
     283            } elseif ($strictPadding) {
    200284                $err |= 1;
    201285            }
    202286        }
    203         /** @var bool $check */
    204287        $check = ($err === 0);
    205288        if (!$check) {
    206             throw new \RangeException(
     289            throw new RangeException(
    207290                'Base64::decode() only expects characters in the correct base64 alphabet'
    208291            );
    209292        }
    210293        return $dest;
     294    }
     295
     296    /**
     297     * @param string $encodedString
     298     * @return string
     299     * @api
     300     */
     301    public static function decodeNoPadding(
     302        #[SensitiveParameter]
     303        string $encodedString
     304    ): string {
     305        $srcLen = strlen($encodedString);
     306        if ($srcLen === 0) {
     307            return '';
     308        }
     309        if (($srcLen & 3) === 0) {
     310            // If $strLen is not zero, and it is divisible by 4, then it's at least 4.
     311            if ($encodedString[$srcLen - 1] === '=' || $encodedString[$srcLen - 2] === '=') {
     312                throw new InvalidArgumentException(
     313                    "decodeNoPadding() doesn't tolerate padding"
     314                );
     315            }
     316        }
     317        return static::decode(
     318            $encodedString,
     319            true
     320        );
    211321    }
    212322
    … …  
    267377        $diff += ((62 - $src) >> 8) & 3;
    268378
    269         return \pack('C', $src + $diff);
     379        return pack('C', $src + $diff);
    270380    }
    271381}
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/Base64DotSlash.php

    r2192307 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use Override;
     6
    57/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     8 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
    79 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    810 *
    … …  
    4547     * @return int
    4648     */
     49    #[Override]
    4750    protected static function decode6Bits(int $src): int
    4851    {
    … …  
    7174     * @return string
    7275     */
     76    #[Override]
    7377    protected static function encode6Bits(int $src): string
    7478    {
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php

    r2192307 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use Override;
     6
    57/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     8 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
    79 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    810 *
    … …  
    4547     * @return int
    4648     */
     49    #[Override]
    4750    protected static function decode6Bits(int $src): int
    4851    {
    … …  
    6871     * @return string
    6972     */
     73    #[Override]
    7074    protected static function encode6Bits(int $src): string
    7175    {
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/Base64UrlSafe.php

    r2192307 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use Override;
     6
    57/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     8 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
    79 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    810 *
    … …  
    4648     * @return int
    4749     */
     50    #[Override]
    4851    protected static function decode6Bits(int $src): int
    4952    {
    … …  
    7578     * @return string
    7679     */
     80    #[Override]
    7781    protected static function encode6Bits(int $src): string
    7882    {
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/Binary.php

    r2466054 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use SensitiveParameter;
     6use TypeError;
     7use function strlen;
     8use function substr;
     9
    510/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     11 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
    712 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    813 *
    … …  
    4449     * @return int
    4550     */
    46     public static function safeStrlen(string $str): int
    47     {
    48         if (\function_exists('mb_strlen')) {
    49             return (int) \mb_strlen($str, '8bit');
    50         } else {
    51             return \strlen($str);
    52         }
     51    public static function safeStrlen(
     52        #[SensitiveParameter]
     53        string $str
     54    ): int {
     55        return strlen($str);
    5356    }
    5457
    … …  
    6164     * @param string $str
    6265     * @param int $start
    63      * @param int $length
     66     * @param ?int $length
    6467     * @return string
    65      * @throws \TypeError
     68     *
     69     * @throws TypeError
    6670     */
    6771    public static function safeSubstr(
     72        #[SensitiveParameter]
    6873        string $str,
    6974        int $start = 0,
    70         $length = null
     75        ?int $length = null
    7176    ): string {
    7277        if ($length === 0) {
    7378            return '';
    7479        }
    75         if (\function_exists('mb_substr')) {
    76             return \mb_substr($str, $start, $length, '8bit');
    77         }
    7880        // Unlike mb_substr(), substr() doesn't accept NULL for length
    7981        if ($length !== null) {
    80             return \substr($str, $start, $length);
     82            return substr($str, $start, $length);
    8183        } else {
    82             return \substr($str, $start);
     84            return substr($str, $start);
    8385        }
    8486    }
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/EncoderInterface.php

    r2192307 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use SensitiveParameter;
     6
    57/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     8 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
    79 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    810 *
    … …  
    3941     * @return string
    4042     */
    41     public static function encode(string $binString): string;
     43    public static function encode(
     44        #[SensitiveParameter]
     45        string $binString
     46    ): string;
    4247
    4348    /**
    … …  
    4954     * @return string (raw binary)
    5055     */
    51     public static function decode(string $encodedString, bool $strictPadding = false): string;
     56    public static function decode(
     57        #[SensitiveParameter]
     58        string $encodedString,
     59        bool $strictPadding = false
     60    ): string;
    5261}
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/Encoding.php

    r2192307 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use RangeException;
     6use SensitiveParameter;
     7use TypeError;
     8
    59/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     10 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
    711 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    812 *
    … …  
    2933 * Class Encoding
    3034 * @package ParagonIE\ConstantTime
     35 * @api
    3136 */
    3237abstract class Encoding
    … …  
    3742     * @param string $str
    3843     * @return string
    39      * @throws \TypeError
    40      */
    41     public static function base32Encode(string $str): string
    42     {
     44     * @throws TypeError
     45     */
     46    public static function base32Encode(
     47        #[SensitiveParameter]
     48        string $str
     49    ): string {
    4350        return Base32::encode($str);
    4451    }
    … …  
    4956     * @param string $str
    5057     * @return string
    51      * @throws \TypeError
    52      */
    53     public static function base32EncodeUpper(string $str): string
    54     {
     58     * @throws TypeError
     59     */
     60    public static function base32EncodeUpper(
     61        #[SensitiveParameter]
     62        string $str
     63    ): string {
    5564        return Base32::encodeUpper($str);
    5665    }
    … …  
    6170     * @param string $str
    6271     * @return string
    63      * @throws \TypeError
    64      */
    65     public static function base32Decode(string $str): string
    66     {
     72     * @throws TypeError
     73     */
     74    public static function base32Decode(
     75        #[SensitiveParameter]
     76        string $str
     77    ): string {
    6778        return Base32::decode($str);
    6879    }
    … …  
    7384     * @param string $str
    7485     * @return string
    75      * @throws \TypeError
    76      */
    77     public static function base32DecodeUpper(string $str): string
    78     {
     86     * @throws TypeError
     87     */
     88    public static function base32DecodeUpper(
     89        #[SensitiveParameter]
     90        string $str
     91    ): string {
    7992        return Base32::decodeUpper($str);
    8093    }
    … …  
    8598     * @param string $str
    8699     * @return string
    87      * @throws \TypeError
    88      */
    89     public static function base32HexEncode(string $str): string
    90     {
     100     * @throws TypeError
     101     */
     102    public static function base32HexEncode(
     103        #[SensitiveParameter]
     104        string $str
     105    ): string {
    91106        return Base32Hex::encode($str);
    92107    }
    … …  
    97112     * @param string $str
    98113     * @return string
    99      * @throws \TypeError
    100      */
    101     public static function base32HexEncodeUpper(string $str): string
    102     {
     114     * @throws TypeError
     115     */
     116    public static function base32HexEncodeUpper(
     117        #[SensitiveParameter]
     118        string $str
     119    ): string {
    103120        return Base32Hex::encodeUpper($str);
    104121    }
    … …  
    109126     * @param string $str
    110127     * @return string
    111      * @throws \TypeError
    112      */
    113     public static function base32HexDecode(string $str): string
    114     {
     128     * @throws TypeError
     129     */
     130    public static function base32HexDecode(
     131        #[SensitiveParameter]
     132        string $str
     133    ): string {
    115134        return Base32Hex::decode($str);
    116135    }
    … …  
    121140     * @param string $str
    122141     * @return string
    123      * @throws \TypeError
    124      */
    125     public static function base32HexDecodeUpper(string $str): string
    126     {
     142     * @throws TypeError
     143     */
     144    public static function base32HexDecodeUpper(
     145        #[SensitiveParameter]
     146        string $str
     147    ): string {
    127148        return Base32Hex::decodeUpper($str);
    128149    }
    … …  
    133154     * @param string $str
    134155     * @return string
    135      * @throws \TypeError
    136      */
    137     public static function base64Encode(string $str): string
    138     {
     156     * @throws TypeError
     157     */
     158    public static function base64Encode(
     159        #[SensitiveParameter]
     160        string $str
     161    ): string {
    139162        return Base64::encode($str);
    140163    }
    … …  
    145168     * @param string $str
    146169     * @return string
    147      * @throws \TypeError
    148      */
    149     public static function base64Decode(string $str): string
    150     {
     170     * @throws TypeError
     171     */
     172    public static function base64Decode(
     173        #[SensitiveParameter]
     174        string $str
     175    ): string {
    151176        return Base64::decode($str);
    152177    }
    … …  
    158183     * @param string $str
    159184     * @return string
    160      * @throws \TypeError
    161      */
    162     public static function base64EncodeDotSlash(string $str): string
    163     {
     185     * @throws TypeError
     186     */
     187    public static function base64EncodeDotSlash(
     188        #[SensitiveParameter]
     189        string $str
     190    ): string {
    164191        return Base64DotSlash::encode($str);
    165192    }
    … …  
    172199     * @param string $str
    173200     * @return string
    174      * @throws \RangeException
    175      * @throws \TypeError
    176      */
    177     public static function base64DecodeDotSlash(string $str): string
    178     {
     201     * @throws RangeException
     202     * @throws TypeError
     203     */
     204    public static function base64DecodeDotSlash(
     205        #[SensitiveParameter]
     206        string $str
     207    ): string {
    179208        return Base64DotSlash::decode($str);
    180209    }
    … …  
    186215     * @param string $str
    187216     * @return string
    188      * @throws \TypeError
    189      */
    190     public static function base64EncodeDotSlashOrdered(string $str): string
    191     {
     217     * @throws TypeError
     218     */
     219    public static function base64EncodeDotSlashOrdered(
     220        #[SensitiveParameter]
     221        string $str
     222    ): string {
    192223        return Base64DotSlashOrdered::encode($str);
    193224    }
    … …  
    200231     * @param string $str
    201232     * @return string
    202      * @throws \RangeException
    203      * @throws \TypeError
    204      */
    205     public static function base64DecodeDotSlashOrdered(string $str): string
    206     {
     233     * @throws RangeException
     234     * @throws TypeError
     235     */
     236    public static function base64DecodeDotSlashOrdered(
     237        #[SensitiveParameter]
     238        string $str
     239    ): string {
    207240        return Base64DotSlashOrdered::decode($str);
    208241    }
    … …  
    214247     * @param string $bin_string (raw binary)
    215248     * @return string
    216      * @throws \TypeError
    217      */
    218     public static function hexEncode(string $bin_string): string
    219     {
     249     * @throws TypeError
     250     */
     251    public static function hexEncode(
     252        #[SensitiveParameter]
     253        string $bin_string
     254    ): string {
    220255        return Hex::encode($bin_string);
    221256    }
    … …  
    227262     * @param string $hex_string
    228263     * @return string (raw binary)
    229      * @throws \RangeException
    230      */
    231     public static function hexDecode(string $hex_string): string
    232     {
     264     * @throws RangeException
     265     */
     266    public static function hexDecode(
     267        #[SensitiveParameter]
     268        string $hex_string
     269    ): string {
    233270        return Hex::decode($hex_string);
    234271    }
    … …  
    240277     * @param string $bin_string (raw binary)
    241278     * @return string
    242      * @throws \TypeError
    243      */
    244     public static function hexEncodeUpper(string $bin_string): string
    245     {
     279     * @throws TypeError
     280     */
     281    public static function hexEncodeUpper(
     282        #[SensitiveParameter]
     283        string $bin_string
     284    ): string {
    246285        return Hex::encodeUpper($bin_string);
    247286    }
    … …  
    254293     * @return string
    255294     */
    256     public static function hexDecodeUpper(string $bin_string): string
    257     {
     295    public static function hexDecodeUpper(
     296        #[SensitiveParameter]
     297        string $bin_string
     298    ): string {
    258299        return Hex::decode($bin_string);
    259300    }
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/Hex.php

    r2466054 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use Override;
     6use RangeException;
     7use SensitiveParameter;
     8use SodiumException;
     9use TypeError;
     10use function extension_loaded;
     11use function pack;
     12use function sodium_bin2hex;
     13use function sodium_hex2bin;
     14use function strlen;
     15use function unpack;
     16
    517/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     18 *  Copyright (c) 2016 - 2025 Paragon Initiative Enterprises.
    719 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    820 *
    … …  
    3850     * @param string $binString (raw binary)
    3951     * @return string
    40      * @throws \TypeError
     52     * @throws TypeError
    4153     */
    42     public static function encode(string $binString): string
    43     {
    44         /** @var string $hex */
     54    #[Override]
     55    public static function encode(
     56        #[SensitiveParameter]
     57        string $binString
     58    ): string {
     59        if (extension_loaded('sodium')) {
     60            try {
     61                return sodium_bin2hex($binString);
     62            } catch (SodiumException $ex) {
     63                throw new RangeException($ex->getMessage(), $ex->getCode(), $ex);
     64            }
     65        }
    4566        $hex = '';
    46         $len = Binary::safeStrlen($binString);
     67        $len = strlen($binString);
    4768        for ($i = 0; $i < $len; ++$i) {
    4869            /** @var array<int, int> $chunk */
    49             $chunk = \unpack('C', Binary::safeSubstr($binString, $i, 1));
    50             /** @var int $c */
     70            $chunk = unpack('C', $binString[$i]);
    5171            $c = $chunk[1] & 0xf;
    52             /** @var int $b */
    5372            $b = $chunk[1] >> 4;
    5473
    … …  
    6887     * @param string $binString (raw binary)
    6988     * @return string
    70      * @throws \TypeError
     89     * @throws TypeError
    7190     */
    72     public static function encodeUpper(string $binString): string
    73     {
    74         /** @var string $hex */
     91    public static function encodeUpper(
     92        #[SensitiveParameter]
     93        string $binString
     94    ): string {
    7595        $hex = '';
    76         /** @var int $len */
    77         $len = Binary::safeStrlen($binString);
     96        $len = strlen($binString);
    7897
    7998        for ($i = 0; $i < $len; ++$i) {
    8099            /** @var array<int, int> $chunk */
    81             $chunk = \unpack('C', Binary::safeSubstr($binString, $i, 2));
    82             /** @var int $c */
     100            $chunk = unpack('C', $binString[$i]);
    83101            $c = $chunk[1] & 0xf;
    84             /** @var int $b */
    85102            $b = $chunk[1] >> 4;
    86103
    … …  
    101118     * @param bool $strictPadding
    102119     * @return string (raw binary)
    103      * @throws \RangeException
     120     * @throws RangeException
    104121     */
    105     public static function decode(string $encodedString, bool $strictPadding = false): string
    106     {
    107         /** @var int $hex_pos */
     122    #[Override]
     123    public static function decode(
     124        #[SensitiveParameter]
     125        string $encodedString,
     126        bool $strictPadding = false
     127    ): string {
     128        if (extension_loaded('sodium') && $strictPadding) {
     129            try {
     130                return sodium_hex2bin($encodedString);
     131            } catch (SodiumException $ex) {
     132                throw new RangeException($ex->getMessage(), $ex->getCode(), $ex);
     133            }
     134        }
    108135        $hex_pos = 0;
    109         /** @var string $bin */
    110136        $bin = '';
    111         /** @var int $c_acc */
    112137        $c_acc = 0;
    113         /** @var int $hex_len */
    114         $hex_len = Binary::safeStrlen($encodedString);
    115         /** @var int $state */
     138        $hex_len = strlen($encodedString);
    116139        $state = 0;
    117140        if (($hex_len & 1) !== 0) {
    118141            if ($strictPadding) {
    119                 throw new \RangeException(
     142                throw new RangeException(
    120143                    'Expected an even number of hexadecimal characters'
    121144                );
    … …  
    127150
    128151        /** @var array<int, int> $chunk */
    129         $chunk = \unpack('C*', $encodedString);
     152        $chunk = unpack('C*', $encodedString);
    130153        while ($hex_pos < $hex_len) {
    131154            ++$hex_pos;
    132             /** @var int $c */
    133155            $c = $chunk[$hex_pos];
    134             /** @var int $c_num */
    135156            $c_num = $c ^ 48;
    136             /** @var int $c_num0 */
    137157            $c_num0 = ($c_num - 10) >> 8;
    138             /** @var int $c_alpha */
    139158            $c_alpha = ($c & ~32) - 55;
    140             /** @var int $c_alpha0 */
    141159            $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8;
    142160
    143161            if (($c_num0 | $c_alpha0) === 0) {
    144                 throw new \RangeException(
     162                throw new RangeException(
    145163                    'Expected hexadecimal character'
    146164                );
    147165            }
    148             /** @var int $c_val */
    149166            $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
    150167            if ($state === 0) {
    151168                $c_acc = $c_val * 16;
    152169            } else {
    153                 $bin .= \pack('C', $c_acc | $c_val);
     170                $bin .= pack('C', $c_acc | $c_val);
    154171            }
    155172            $state ^= 1;
  • wp-otp/trunk/vendor/paragonie/constant_time_encoding/src/RFC4648.php

    r2192307 r3752647  
    33namespace ParagonIE\ConstantTime;
    44
     5use SensitiveParameter;
     6use TypeError;
     7
    58/**
    6  *  Copyright (c) 2016 - 2018 Paragon Initiative Enterprises.
     9 *  Copyright (c) 2016 - 2022 Paragon Initiative Enterprises.
    710 *  Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com)
    811 *
    … …  
    3235 *
    3336 * @package ParagonIE\ConstantTime
     37 * @api
    3438 */
    3539abstract class RFC4648
    … …  
    4246     * @param string $str
    4347     * @return string
    44      * @throws \TypeError
    45      */
    46     public static function base64Encode(string $str): string
    47     {
     48     *
     49     * @throws TypeError
     50     */
     51    public static function base64Encode(
     52        #[SensitiveParameter]
     53        string $str
     54    ): string {
    4855        return Base64::encode($str);
    4956    }
    … …  
    5663     * @param string $str
    5764     * @return string
    58      * @throws \TypeError
    59      */
    60     public static function base64Decode(string $str): string
    61     {
     65     *
     66     * @throws TypeError
     67     */
     68    public static function base64Decode(
     69        #[SensitiveParameter]
     70        string $str
     71    ): string {
    6272        return Base64::decode($str, true);
    6373    }
    … …  
    7080     * @param string $str
    7181     * @return string
    72      * @throws \TypeError
    73      */
    74     public static function base64UrlSafeEncode(string $str): string
    75     {
     82     *
     83     * @throws TypeError
     84     */
     85    public static function base64UrlSafeEncode(
     86        #[SensitiveParameter]
     87        string $str
     88    ): string {
    7689        return Base64UrlSafe::encode($str);
    7790    }
    … …  
    8497     * @param string $str
    8598     * @return string
    86      * @throws \TypeError
    87      */
    88     public static function base64UrlSafeDecode(string $str): string
    89     {
     99     *
     100     * @throws TypeError
     101     */
     102    public static function base64UrlSafeDecode(
     103        #[SensitiveParameter]
     104        string $str
     105    ): string {
    90106        return Base64UrlSafe::decode($str, true);
    91107    }
    … …  
    98114     * @param string $str
    99115     * @return string
    100      * @throws \TypeError
    101      */
    102     public static function base32Encode(string $str): string
    103     {
     116     *
     117     * @throws TypeError
     118     */
     119    public static function base32Encode(
     120        #[SensitiveParameter]
     121        string $str
     122    ): string {
    104123        return Base32::encodeUpper($str);
    105124    }
    … …  
    112131     * @param string $str
    113132     * @return string
    114      * @throws \TypeError
    115      */
    116     public static function base32Decode(string $str): string
    117     {
     133     *
     134     * @throws TypeError
     135     */
     136    public static function base32Decode(
     137        #[SensitiveParameter]
     138        string $str
     139    ): string {
    118140        return Base32::decodeUpper($str, true);
    119141    }
    … …  
    126148     * @param string $str
    127149     * @return string
    128      * @throws \TypeError
    129      */
    130     public static function base32HexEncode(string $str): string
    131     {
     150     *
     151     * @throws TypeError
     152     */
     153    public static function base32HexEncode(
     154        #[SensitiveParameter]
     155        string $str
     156    ): string {
    132157        return Base32::encodeUpper($str);
    133158    }
    … …  
    140165     * @param string $str
    141166     * @return string
    142      * @throws \TypeError
    143      */
    144     public static function base32HexDecode(string $str): string
    145     {
     167     *
     168     * @throws TypeError
     169     */
     170    public static function base32HexDecode(
     171        #[SensitiveParameter]
     172        string $str
     173    ): string {
    146174        return Base32::decodeUpper($str, true);
    147175    }
    … …  
    154182     * @param string $str
    155183     * @return string
    156      * @throws \TypeError
    157      */
    158     public static function base16Encode(string $str): string
    159     {
     184     *
     185     * @throws TypeError
     186     */
     187    public static function base16Encode(
     188        #[SensitiveParameter]
     189        string $str
     190    ): string {
    160191        return Hex::encodeUpper($str);
    161192    }
    … …  
    169200     * @return string
    170201     */
    171     public static function base16Decode(string $str): string
    172     {
     202    public static function base16Decode(
     203        #[SensitiveParameter]
     204        string $str
     205    ): string {
    173206        return Hex::decode($str, true);
    174207    }
  • wp-otp/trunk/vendor/spomky-labs/otphp/composer.json

    r2331970 r3752647  
    1717    ],
    1818    "require": {
    19         "php": "^7.2|^8.0",
    20         "ext-mbstring": "*",
    21         "paragonie/constant_time_encoding": "^2.0",
    22         "beberlei/assert": "^3.0",
    23         "thecodingmachine/safe": "^0.1.14|^1.0"
     19        "php": ">=8.1",
     20        "paragonie/constant_time_encoding": "^2.0 || ^3.0",
     21        "psr/clock": "^1.0",
     22        "symfony/deprecation-contracts": "^3.2"
    2423    },
    2524    "require-dev": {
    26         "phpunit/phpunit": "^8.0",
    27         "php-coveralls/php-coveralls": "^2.0",
    28         "phpstan/phpstan": "^0.12",
    29         "phpstan/phpstan-beberlei-assert": "^0.12",
    30         "phpstan/phpstan-deprecation-rules": "^0.12",
    31         "phpstan/phpstan-phpunit": "^0.12",
    32         "phpstan/phpstan-strict-rules": "^0.12",
    33         "thecodingmachine/phpstan-safe-rule": "^1.0"
    34     },
    35     "suggest": {
     25        "symfony/error-handler": "^6.4|^7.0|^8.0"
    3626    },
    3727    "autoload": {
    … …  
    4131        "psr-4": { "OTPHP\\Test\\": "tests/" }
    4232    },
    43     "extra": {
    44         "branch-alias": {
    45             "v10.0": "10.0.x-dev",
    46             "v9.0": "9.0.x-dev",
    47             "v8.3": "8.3.x-dev"
    48         }
     33    "config": {
     34        "allow-plugins": {
     35            "phpstan/extension-installer": true,
     36            "infection/extension-installer": true,
     37            "composer/package-versions-deprecated": true,
     38            "symfony/flex": true,
     39            "symfony/runtime": true
     40        },
     41        "optimize-autoloader": true,
     42        "preferred-install": {
     43            "*": "dist"
     44        },
     45        "sort-packages": true
    4946    }
    5047}
  • wp-otp/trunk/vendor/spomky-labs/otphp/src/Factory.php

    r2331970 r3752647  
    33declare(strict_types=1);
    44
    5 /*
    6  * The MIT License (MIT)
    7  *
    8  * Copyright (c) 2014-2019 Spomky-Labs
    9  *
    10  * This software may be modified and distributed under the terms
    11  * of the MIT license.  See the LICENSE file for details.
    12  */
    13 
    145namespace OTPHP;
    156
    16 use Assert\Assertion;
    17 use InvalidArgumentException;
    18 use function Safe\parse_url;
    19 use function Safe\sprintf;
     7use function count;
     8use OTPHP\Exception\InvalidProvisioningUriException;
     9use Psr\Clock\ClockInterface;
     10use function sprintf;
    2011use Throwable;
    2112
    2213/**
    2314 * This class is used to load OTP object from a provisioning Uri.
     15 *
     16 * @readonly
     17 *
     18 * @see \OTPHP\Test\FactoryTest
    2419 */
    2520final class Factory implements FactoryInterface
    2621{
    27     public static function loadFromProvisioningUri(string $uri): OTPInterface
     22    public static function loadFromProvisioningUri(string $uri, ?ClockInterface $clock = null): OTPInterface
    2823    {
    2924        try {
    30             $parsed_url = parse_url($uri);
     25            $parsed_url = Url::fromString($uri);
     26            $parsed_url->getScheme() === 'otpauth' || throw new InvalidProvisioningUriException('Invalid scheme.');
    3127        } catch (Throwable $throwable) {
    32             throw new InvalidArgumentException('Not a valid OTP provisioning URI', $throwable->getCode(), $throwable);
     28            throw new InvalidProvisioningUriException(
     29                'Not a valid OTP provisioning URI',
     30                $throwable->getCode(),
     31                $throwable
     32            );
    3333        }
    34         Assertion::isArray($parsed_url, 'Not a valid OTP provisioning URI');
    35         self::checkData($parsed_url);
     34        if ($clock === null) {
     35            trigger_deprecation(
     36                'spomky-labs/otphp',
     37                '11.3.0',
     38                'The parameter "$clock" will become mandatory in 12.0.0. Please set a valid PSR Clock implementation instead of "null".'
     39            );
     40            $clock = new InternalClock();
     41        }
    3642
    37         $otp = self::createOTP($parsed_url);
     43        try {
     44            $otp = self::createOTP($parsed_url, $clock);
    3845
    39         self::populateOTP($otp, $parsed_url);
     46            self::populateOTP($otp, $parsed_url);
     47        } catch (InvalidProvisioningUriException $exception) {
     48            throw $exception;
     49        } catch (Throwable $throwable) {
     50            throw new InvalidProvisioningUriException(
     51                'Not a valid OTP provisioning URI',
     52                $throwable->getCode(),
     53                $throwable
     54            );
     55        }
    4056
    4157        return $otp;
    4258    }
    4359
    44     /**
    45      * @param array<string, mixed> $data
    46      */
    47     private static function populateParameters(OTPInterface &$otp, array $data): void
     60    private static function populateParameters(OTPInterface $otp, Url $data): void
    4861    {
    49         foreach ($data['query'] as $key => $value) {
     62        foreach ($data->getQuery() as $key => $value) {
    5063            $otp->setParameter($key, $value);
    5164        }
    5265    }
    5366
    54     /**
    55      * @param array<string, mixed> $data
    56      */
    57     private static function populateOTP(OTPInterface &$otp, array $data): void
     67    private static function populateOTP(OTPInterface $otp, Url $data): void
    5868    {
    5969        self::populateParameters($otp, $data);
    60         $result = explode(':', rawurldecode(mb_substr($data['path'], 1)));
     70        $result = explode(':', rawurldecode(substr($data->getPath(), 1)));
    6171
    62         if (2 > \count($result)) {
     72        if (count($result) < 2) {
    6373            $otp->setIssuerIncludedAsParameter(false);
    6474
    … …  
    6676        }
    6777
    68         if (null !== $otp->getIssuer()) {
    69             Assertion::eq($result[0], $otp->getIssuer(), 'Invalid OTP: invalid issuer in parameter');
     78        $issuerFromLabel = $result[0];
     79        $issuerFromParameter = $otp->getIssuer();
     80
     81        if ($issuerFromParameter !== null) {
     82            // Issuer parameter takes precedence over issuer in label
     83            // According to Google Authenticator spec: "they should be equal" but not required to be
    7084            $otp->setIssuerIncludedAsParameter(true);
     85        } else {
     86            // No issuer parameter, use the issuer from label
     87            $issuerFromLabel !== '' || throw new InvalidProvisioningUriException(
     88                'Issuer from label must not be empty.'
     89            );
     90            $otp->setIssuer($issuerFromLabel);
    7191        }
    72         $otp->setIssuer($result[0]);
     92    }
     93
     94    private static function createOTP(Url $parsed_url, ClockInterface $clock): OTPInterface
     95    {
     96        switch ($parsed_url->getHost()) {
     97            case 'totp':
     98                $totp = TOTP::createFromSecret($parsed_url->getSecret(), $clock);
     99                $totp->setLabel(self::getLabel($parsed_url->getPath()));
     100
     101                return $totp;
     102            case 'hotp':
     103                $hotp = HOTP::createFromSecret($parsed_url->getSecret());
     104                $hotp->setLabel(self::getLabel($parsed_url->getPath()));
     105
     106                return $hotp;
     107            default:
     108                throw new InvalidProvisioningUriException(sprintf('Unsupported "%s" OTP type', $parsed_url->getHost()));
     109        }
    73110    }
    74111
    75112    /**
    76      * @param array<string, mixed> $data
     113     * @param non-empty-string $data
     114     * @return non-empty-string
    77115     */
    78     private static function checkData(array &$data): void
    79     {
    80         foreach (['scheme', 'host', 'path', 'query'] as $key) {
    81             Assertion::keyExists($data, $key, 'Not a valid OTP provisioning URI');
    82         }
    83         Assertion::eq('otpauth', $data['scheme'], 'Not a valid OTP provisioning URI');
    84         parse_str($data['query'], $data['query']);
    85         Assertion::keyExists($data['query'], 'secret', 'Not a valid OTP provisioning URI');
    86     }
    87 
    88     /**
    89      * @param array<string, mixed> $parsed_url
    90      */
    91     private static function createOTP(array $parsed_url): OTPInterface
    92     {
    93         switch ($parsed_url['host']) {
    94             case 'totp':
    95                 $totp = TOTP::create($parsed_url['query']['secret']);
    96                 $totp->setLabel(self::getLabel($parsed_url['path']));
    97 
    98                 return $totp;
    99             case 'hotp':
    100                 $hotp = HOTP::create($parsed_url['query']['secret']);
    101                 $hotp->setLabel(self::getLabel($parsed_url['path']));
    102 
    103                 return $hotp;
    104             default:
    105                 throw new InvalidArgumentException(sprintf('Unsupported "%s" OTP type', $parsed_url['host']));
    106         }
    107     }
    108 
    109116    private static function getLabel(string $data): string
    110117    {
    111         $result = explode(':', rawurldecode(mb_substr($data, 1)));
     118        $result = explode(':', rawurldecode(substr($data, 1)));
     119        $label = count($result) === 2 ? $result[1] : $result[0];
     120        $label !== '' || throw new InvalidProvisioningUriException('Label must not be empty.');
    112121
    113         return 2 === \count($result) ? $result[1] : $result[0];
     122        return $label;
    114123    }
    115124}
  • wp-otp/trunk/vendor/spomky-labs/otphp/src/FactoryInterface.php

    r2331970 r3752647  
    33declare(strict_types=1);
    44
    5 /*
    6  * The MIT License (MIT)
    7  *
    8  * Copyright (c) 2014-2019 Spomky-Labs
    9  *
    10  * This software may be modified and distributed under the terms
    11  * of the MIT license.  See the LICENSE file for details.
    12  */
     5namespace OTPHP;
    136
    14 namespace OTPHP;
     7use Psr\Clock\ClockInterface;
    158
    169interface FactoryInterface
    1710{
    1811    /**
    19      * This method is the unique public method of the class.
    20      * It can load a provisioning Uri and convert it into an OTP object.
     12     * This method is the unique public method of the class. It can load a provisioning Uri and convert it into an OTP
     13     * object.
     14     *
     15     * @param non-empty-string $uri
    2116     */
    22     public static function loadFromProvisioningUri(string $uri): OTPInterface;
     17    public static function loadFromProvisioningUri(string $uri, ?ClockInterface $clock = null): OTPInterface;
    2318}
  • wp-otp/trunk/vendor/spomky-labs/otphp/src/HOTP.php

    r2331970 r3752647  
    33declare(strict_types=1);
    44
    5 /*
    6  * The MIT License (MIT)
    7  *
    8  * Copyright (c) 2014-2019 Spomky-Labs
    9  *
    10  * This software may be modified and distributed under the terms
    11  * of the MIT license.  See the LICENSE file for details.
    12  */
    13 
    145namespace OTPHP;
    156
    16 use Assert\Assertion;
     7use function is_int;
     8use OTPHP\Exception\InvalidParameterException;
    179
     10/**
     11 * Note: This class is not marked as readonly because the verify() method
     12 * updates the counter state on successful verification, which is an intentional
     13 * side-effect to prevent OTP reuse.
     14 *
     15 * @see \OTPHP\Test\HOTPTest
     16 */
    1817final class HOTP extends OTP implements HOTPInterface
    1918{
    20     protected function __construct(?string $secret, int $counter, string $digest, int $digits)
    21     {
    22         parent::__construct($secret, $digest, $digits);
    23         $this->setCounter($counter);
     19    private const DEFAULT_WINDOW = 0;
     20
     21    public static function create(
     22        null|string $secret = null,
     23        int $counter = self::DEFAULT_COUNTER,
     24        string $digest = self::DEFAULT_DIGEST,
     25        int $digits = self::DEFAULT_DIGITS,
     26        ?int $secretSize = null
     27    ): self {
     28        $htop = $secret !== null
     29            ? self::createFromSecret($secret)
     30            : self::generate($secretSize)
     31        ;
     32        $htop->setCounter($counter);
     33        $htop->setDigest($digest);
     34        $htop->setDigits($digits);
     35
     36        return $htop;
    2437    }
    2538
    26     public static function create(?string $secret = null, int $counter = 0, string $digest = 'sha1', int $digits = 6): HOTPInterface
     39    public static function createFromSecret(string $secret): self
    2740    {
    28         return new self($secret, $counter, $digest, $digits);
     41        $htop = new self($secret);
     42        $htop->setCounter(self::DEFAULT_COUNTER);
     43        $htop->setDigest(self::DEFAULT_DIGEST);
     44        $htop->setDigits(self::DEFAULT_DIGITS);
     45
     46        return $htop;
    2947    }
    3048
    31     protected function setCounter(int $counter): void
     49    /**
     50     * @param positive-int|null $secretSize
     51     */
     52    public static function generate(?int $secretSize = null): self
    3253    {
    33         $this->setParameter('counter', $counter);
     54        return self::createFromSecret(self::generateSecret($secretSize));
    3455    }
    3556
     57    /**
     58     * @return 0|positive-int
     59     */
    3660    public function getCounter(): int
    3761    {
    38         return $this->getParameter('counter');
    39     }
     62        $value = $this->getParameter('counter');
     63        (is_int($value) && $value >= 0) || throw new InvalidParameterException(
     64            'Invalid "counter" parameter.',
     65            'counter',
     66            $value
     67        );
    4068
    41     private function updateCounter(int $counter): void
    42     {
    43         $this->setCounter($counter);
     69        return $value;
    4470    }
    4571
    4672    public function getProvisioningUri(): string
    4773    {
    48         return $this->generateURI('hotp', ['counter' => $this->getCounter()]);
     74        return $this->generateURI('hotp', [
     75            'counter' => $this->getCounter(),
     76        ]);
    4977    }
    5078
    5179    /**
    5280     * If the counter is not provided, the OTP is verified at the actual counter.
     81     *
     82     * @param null|0|positive-int $counter
    5383     */
    54     public function verify(string $otp, ?int $counter = null, ?int $window = null): bool
     84    public function verify(string $otp, null|int $counter = null, null|int $window = null): bool
    5585    {
    56         Assertion::greaterOrEqualThan($counter, 0, 'The counter must be at least 0.');
     86        $counter >= 0 || throw new InvalidParameterException(
     87            'The counter must be at least 0.',
     88            'counter',
     89            $counter
     90        );
    5791
    58         if (null === $counter) {
     92        if ($counter === null) {
    5993            $counter = $this->getCounter();
    6094        } elseif ($counter < $this->getCounter()) {
    … …  
    6599    }
    66100
    67     private function getWindow(?int $window): int
     101    public function setCounter(int $counter): void
    68102    {
    69         return abs($window ?? 0);
     103        $this->setParameter('counter', $counter);
    70104    }
    71105
    72     private function verifyOtpWithWindow(string $otp, int $counter, ?int $window): bool
     106    public function withCounter(int $counter): self
     107    {
     108        $otp = clone $this;
     109        $otp->setParameter('counter', $counter);
     110
     111        return $otp;
     112    }
     113
     114    /**
     115     * @return array<non-empty-string, callable>
     116     */
     117    protected function getParameterMap(): array
     118    {
     119        return [...parent::getParameterMap(), ...[
     120            'counter' => static function (mixed $value): int {
     121                $value = (int) $value;
     122                $value >= 0 || throw new InvalidParameterException(
     123                    'Counter must be at least 0.',
     124                    'counter',
     125                    $value
     126                );
     127
     128                return $value;
     129            },
     130        ]];
     131    }
     132
     133    private function updateCounter(int $counter): void
     134    {
     135        $this->setCounter($counter);
     136    }
     137
     138    /**
     139     * @param null|0|positive-int $window
     140     */
     141    private function getWindow(null|int $window): int
     142    {
     143        return abs($window ?? self::DEFAULT_WINDOW);
     144    }
     145
     146    /**
     147     * @param non-empty-string $otp
     148     * @param 0|positive-int $counter
     149     * @param null|0|positive-int $window
     150     */
     151    private function verifyOtpWithWindow(string $otp, int $counter, null|int $window): bool
    73152    {
    74153        $window = $this->getWindow($window);
    … …  
    84163        return false;
    85164    }
    86 
    87     /**
    88      * @return array<string, mixed>
    89      */
    90     protected function getParameterMap(): array
    91     {
    92         $v = array_merge(
    93             parent::getParameterMap(),
    94             ['counter' => function ($value): int {
    95                 Assertion::greaterOrEqualThan((int) $value, 0, 'Counter must be at least 0.');
    96 
    97                 return (int) $value;
    98             }]
    99         );
    100 
    101         return $v;
    102     }
    103165}
  • wp-otp/trunk/vendor/spomky-labs/otphp/src/HOTPInterface.php

    r2331970 r3752647  
    22
    33declare(strict_types=1);
    4 
    5 /*
    6  * The MIT License (MIT)
    7  *
    8  * Copyright (c) 2014-2019 Spomky-Labs
    9  *
    10  * This software may be modified and distributed under the terms
    11  * of the MIT license.  See the LICENSE file for details.
    12  */
    134
    145namespace OTPHP;
    … …  
    167interface HOTPInterface extends OTPInterface
    178{
     9    public const DEFAULT_COUNTER = 0;
     10
    1811    /**
    1912     * The initial counter (a positive integer).
    … …  
    2215
    2316    /**
    24      * Create a new TOTP object.
     17     * Create a new HOTP object.
    2518     *
    2619     * If the secret is null, a random 64 bytes secret will be generated.
     20     *
     21     * @param null|non-empty-string $secret
     22     * @param 0|positive-int $counter
     23     * @param non-empty-string $digest
     24     * @param positive-int $digits
     25     *
     26     * @deprecated Deprecated since v11.1, use ::createFromSecret or ::generate instead
    2727     */
    28     public static function create(?string $secret = null, int $counter = 0, string $digest = 'sha1', int $digits = 6): self;
     28    public static function create(
     29        null|string $secret = null,
     30        int $counter = 0,
     31        string $digest = 'sha1',
     32        int $digits = 6
     33    ): self;
     34
     35    /**
     36     * @deprecated Deprecated since v11.4, use {@see self::withCounter()} instead
     37     */
     38    public function setCounter(int $counter): void;
     39
     40    public function withCounter(int $counter): self;
    2941}
  • wp-otp/trunk/vendor/spomky-labs/otphp/src/OTP.php

    r2331970 r3752647  
    33declare(strict_types=1);
    44
    5 /*
    6  * The MIT License (MIT)
    7  *
    8  * Copyright (c) 2014-2019 Spomky-Labs
    9  *
    10  * This software may be modified and distributed under the terms
    11  * of the MIT license.  See the LICENSE file for details.
     5namespace OTPHP;
     6
     7use function array_key_exists;
     8use function chr;
     9use function count;
     10use Exception;
     11use function in_array;
     12use function is_int;
     13use function is_string;
     14use OTPHP\Exception\InvalidLabelException;
     15use OTPHP\Exception\InvalidParameterException;
     16use OTPHP\Exception\ParameterNotFoundException;
     17use OTPHP\Exception\SecretDecodingException;
     18use ParagonIE\ConstantTime\Base32;
     19use function sprintf;
     20use const STR_PAD_LEFT;
     21use function strlen;
     22
     23/**
     24 * @readonly
    1225 */
    13 
    14 namespace OTPHP;
    15 
    16 use Assert\Assertion;
    17 use ParagonIE\ConstantTime\Base32;
    18 use RuntimeException;
    19 use function Safe\ksort;
    20 use function Safe\sprintf;
    21 
    2226abstract class OTP implements OTPInterface
    2327{
    24     use ParameterTrait;
    25 
    26     protected function __construct(?string $secret, string $digest, int $digits)
     28    private const DEFAULT_SECRET_SIZE = 64;
     29
     30    /**
     31     * Minimum digest size, in bytes, required by the RFC 4226 dynamic truncation.
     32     *
     33     * The truncation reads four bytes starting at an offset taken from the low
     34     * nibble of the last digest byte, i.e. an offset in the range [0, 15]. It
     35     * therefore reads up to index "offset + 3" = 18 and needs at least 19 bytes.
     36     * A shorter digest (e.g. MD5, 16 bytes) makes the truncation read past the
     37     * end of the hash, collapsing the output to a small, secret-independent set
     38     * of values. Such algorithms are also outside RFC 4226/6238 and are not
     39     * interoperable with authenticator apps. See {@see self::generateOTP()}.
     40     */
     41    private const MINIMUM_DIGEST_SIZE = 19;
     42
     43    /**
     44     * @var array<non-empty-string, mixed>
     45     */
     46    private array $parameters = [];
     47
     48    /**
     49     * @var non-empty-string|null
     50     */
     51    private null|string $issuer = null;
     52
     53    /**
     54     * @var non-empty-string|null
     55     */
     56    private null|string $label = null;
     57
     58    private bool $issuer_included_as_parameter = true;
     59
     60    /**
     61     * @param non-empty-string $secret
     62     */
     63    protected function __construct(string $secret)
    2764    {
    2865        $this->setSecret($secret);
    29         $this->setDigest($digest);
    30         $this->setDigits($digits);
    3166    }
    3267
    … …  
    3974
    4075    /**
     76     * @param 0|positive-int $input
     77     */
     78    public function at(int $input): string
     79    {
     80        return $this->generateOTP($input);
     81    }
     82
     83    /**
     84     * @return array<non-empty-string, mixed>
     85     */
     86    public function getParameters(): array
     87    {
     88        $parameters = $this->parameters;
     89
     90        if ($this->getIssuer() !== null && $this->isIssuerIncludedAsParameter() === true) {
     91            $parameters['issuer'] = $this->getIssuer();
     92        }
     93
     94        return $parameters;
     95    }
     96
     97    public function getSecret(): string
     98    {
     99        $value = $this->getParameter('secret');
     100        (is_string($value) && $value !== '') || throw new InvalidParameterException(
     101            'Invalid "secret" parameter.',
     102            'secret',
     103            $value
     104        );
     105
     106        return $value;
     107    }
     108
     109    public function getLabel(): null|string
     110    {
     111        return $this->label;
     112    }
     113
     114    public function setLabel(string $label): void
     115    {
     116        $this->setParameter('label', $label);
     117    }
     118
     119    public function withLabel(string $label): static
     120    {
     121        $otp = clone $this;
     122        $otp->setParameter('label', $label);
     123
     124        return $otp;
     125    }
     126
     127    public function getIssuer(): null|string
     128    {
     129        return $this->issuer;
     130    }
     131
     132    public function setIssuer(string $issuer): void
     133    {
     134        $this->setParameter('issuer', $issuer);
     135    }
     136
     137    public function withIssuer(string $issuer): static
     138    {
     139        $otp = clone $this;
     140        $otp->setParameter('issuer', $issuer);
     141
     142        return $otp;
     143    }
     144
     145    public function isIssuerIncludedAsParameter(): bool
     146    {
     147        return $this->issuer_included_as_parameter;
     148    }
     149
     150    public function setIssuerIncludedAsParameter(bool $issuer_included_as_parameter): void
     151    {
     152        $this->issuer_included_as_parameter = $issuer_included_as_parameter;
     153    }
     154
     155    public function withIssuerIncludedAsParameter(bool $issuer_included_as_parameter): static
     156    {
     157        $otp = clone $this;
     158        $otp->issuer_included_as_parameter = $issuer_included_as_parameter;
     159
     160        return $otp;
     161    }
     162
     163    public function getDigits(): int
     164    {
     165        $value = $this->getParameter('digits');
     166        (is_int($value) && $value >= 1 && $value <= self::MAX_DIGITS) || throw new InvalidParameterException(
     167            'Invalid "digits" parameter.',
     168            'digits',
     169            $value
     170        );
     171
     172        return $value;
     173    }
     174
     175    public function getDigest(): string
     176    {
     177        $value = $this->getParameter('algorithm');
     178        (is_string($value) && $value !== '') || throw new InvalidParameterException(
     179            'Invalid "algorithm" parameter.',
     180            'algorithm',
     181            $value
     182        );
     183
     184        return $value;
     185    }
     186
     187    public function hasParameter(string $parameter): bool
     188    {
     189        return array_key_exists($parameter, $this->parameters);
     190    }
     191
     192    public function getParameter(string $parameter): mixed
     193    {
     194        if ($this->hasParameter($parameter)) {
     195            return $this->getParameters()[$parameter];
     196        }
     197
     198        throw new ParameterNotFoundException(sprintf('Parameter "%s" does not exist', $parameter), $parameter);
     199    }
     200
     201    public function setParameter(string $parameter, mixed $value): void
     202    {
     203        $map = $this->getParameterMap();
     204
     205        if (array_key_exists($parameter, $map) === true) {
     206            $callback = $map[$parameter];
     207            $value = $callback($value);
     208        }
     209
     210        if (in_array($parameter, ['label', 'issuer'], true)) {
     211            $this->{$parameter} = $value;
     212        } else {
     213            $this->parameters[$parameter] = $value;
     214        }
     215    }
     216
     217    public function withParameter(string $parameter, mixed $value): static
     218    {
     219        $otp = clone $this;
     220        $otp->setParameter($parameter, $value);
     221
     222        return $otp;
     223    }
     224
     225    public function setSecret(string $secret): void
     226    {
     227        $this->setParameter('secret', $secret);
     228    }
     229
     230    public function withSecret(string $secret): static
     231    {
     232        $otp = clone $this;
     233        $otp->setParameter('secret', $secret);
     234
     235        return $otp;
     236    }
     237
     238    public function setDigits(int $digits): void
     239    {
     240        $this->setParameter('digits', $digits);
     241    }
     242
     243    public function withDigits(int $digits): static
     244    {
     245        $otp = clone $this;
     246        $otp->setParameter('digits', $digits);
     247
     248        return $otp;
     249    }
     250
     251    public function setDigest(string $digest): void
     252    {
     253        $this->setParameter('algorithm', $digest);
     254    }
     255
     256    public function withDigest(string $digest): static
     257    {
     258        $otp = clone $this;
     259        $otp->setParameter('algorithm', $digest);
     260
     261        return $otp;
     262    }
     263
     264    /**
     265     * @param positive-int|null $secretSize
     266     *
     267     * @return non-empty-string
     268     */
     269    final protected static function generateSecret(?int $secretSize = null): string
     270    {
     271        $secretSize ??= self::DEFAULT_SECRET_SIZE;
     272        $secretSize > 0 || throw new InvalidParameterException(
     273            'Secret size must be at least 1.',
     274            'secretSize',
     275            $secretSize
     276        );
     277
     278        return Base32::encodeUpper(random_bytes($secretSize));
     279    }
     280
     281    /**
    41282     * The OTP at the specified input.
     283     *
     284     * @param 0|positive-int $input
     285     *
     286     * @return non-empty-string
    42287     */
    43288    protected function generateOTP(int $input): string
    44289    {
    45290        $hash = hash_hmac($this->getDigest(), $this->intToByteString($input), $this->getDecodedSecret(), true);
    46 
    47         $hmac = array_values(unpack('C*', $hash));
    48 
    49         $offset = ($hmac[\count($hmac) - 1] & 0xF);
    50         $code = ($hmac[$offset + 0] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF);
     291        $unpacked = unpack('C*', $hash);
     292        $unpacked !== false || throw new InvalidParameterException('Invalid data.', 'hash', $hash);
     293        /** @var list<int> $hmac */
     294        $hmac = array_values($unpacked);
     295
     296        $offset = ($hmac[count($hmac) - 1] & 0xF);
     297        $code = ($hmac[$offset] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF);
    51298        $otp = $code % (10 ** $this->getDigits());
    52299
    … …  
    54301    }
    55302
    56     public function at(int $timestamp): string
    57     {
    58         return $this->generateOTP($timestamp);
    59     }
    60 
    61     /**
    62      * @param array<string, mixed> $options
     303    /**
     304     * @param array<non-empty-string, mixed> $options
    63305     */
    64306    protected function filterOptions(array &$options): void
    65307    {
    66         foreach (['algorithm' => 'sha1', 'period' => 30, 'digits' => 6] as $key => $default) {
     308        foreach ([
     309            'algorithm' => 'sha1',
     310            'period' => 30,
     311            'digits' => 6,
     312        ] as $key => $default) {
    67313            if (isset($options[$key]) && $default === $options[$key]) {
    68314                unset($options[$key]);
    … …  
    74320
    75321    /**
    76      * @param array<string, mixed> $options
     322     * @param non-empty-string $type
     323     * @param array<non-empty-string, mixed> $options
     324     *
     325     * @return non-empty-string
    77326     */
    78327    protected function generateURI(string $type, array $options): string
    79328    {
     329        $options = [...$options, ...$this->getParameters()];
     330        $this->filterOptions($options);
     331        $params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options, '', '&'));
     332
     333        return sprintf('otpauth://%s/%s?%s', $type, rawurlencode($this->buildProvisioningUriLabel()), $params);
     334    }
     335
     336    /**
     337     * @param non-empty-string $safe
     338     * @param non-empty-string $user
     339     */
     340    protected function compareOTP(string $safe, string $user): bool
     341    {
     342        return hash_equals($safe, $user);
     343    }
     344
     345    /**
     346     * @return array<non-empty-string, callable>
     347     */
     348    protected function getParameterMap(): array
     349    {
     350        return [
     351            'label' => function (string $value): string {
     352                $value !== '' || throw new InvalidLabelException('Label must not be empty.', 'label', $value);
     353                $this->validateLabel($value);
     354
     355                return $value;
     356            },
     357            'secret' => static fn (string $value): string => strtoupper(trim($value, '=')),
     358            'algorithm' => static function (string $value): string {
     359                $value = strtolower($value);
     360                in_array($value, hash_hmac_algos(), true) || throw new InvalidParameterException(
     361                    sprintf('The "%s" digest is not supported.', $value),
     362                    'algorithm',
     363                    $value
     364                );
     365                $size = strlen(hash($value, '', true));
     366                $size >= self::MINIMUM_DIGEST_SIZE || throw new InvalidParameterException(
     367                    sprintf(
     368                        'The "%s" digest produces a %d-byte hash which is too short for the RFC 4226 dynamic truncation; at least %d bytes are required.',
     369                        $value,
     370                        $size,
     371                        self::MINIMUM_DIGEST_SIZE
     372                    ),
     373                    'algorithm',
     374                    $value
     375                );
     376
     377                return $value;
     378            },
     379            'digits' => static function ($value): int {
     380                $value = (int) $value;
     381                ($value >= 1 && $value <= self::MAX_DIGITS) || throw new InvalidParameterException(
     382                    sprintf('Digits must be between 1 and %d.', self::MAX_DIGITS),
     383                    'digits',
     384                    $value
     385                );
     386
     387                return $value;
     388            },
     389            'issuer' => function (string $value): string {
     390                $value !== '' || throw new InvalidLabelException('Issuer must not be empty.', 'issuer', $value);
     391                $this->hasColon($value) === false || throw new InvalidLabelException(
     392                    'Issuer must not contain a colon.',
     393                    'issuer',
     394                    $value
     395                );
     396
     397                return $value;
     398            },
     399        ];
     400    }
     401
     402    /**
     403     * @return non-empty-string
     404     */
     405    private function getDecodedSecret(): string
     406    {
     407        try {
     408            $decoded = Base32::decodeUpper($this->getSecret());
     409        } catch (Exception) {
     410            throw new SecretDecodingException('Unable to decode the secret. Is it correctly base32 encoded?');
     411        }
     412        $decoded !== '' || throw new SecretDecodingException('The decoded secret must not be empty.');
     413
     414        return $decoded;
     415    }
     416
     417    private function intToByteString(int $int): string
     418    {
     419        $result = [];
     420        while ($int !== 0) {
     421            $result[] = chr($int & 0xFF);
     422            $int >>= 8;
     423        }
     424
     425        return str_pad(implode('', array_reverse($result)), 8, "\000", STR_PAD_LEFT);
     426    }
     427
     428    /**
     429     * @return non-empty-string
     430     */
     431    private function buildProvisioningUriLabel(): string
     432    {
     433        $issuer = $this->getIssuer();
    80434        $label = $this->getLabel();
    81         Assertion::string($label, 'The label is not set.');
    82         Assertion::false($this->hasColon($label), 'Label must not contain a colon.');
    83         $options = array_merge($options, $this->getParameters());
    84         $this->filterOptions($options);
    85         $params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options));
    86 
    87         return sprintf('otpauth://%s/%s?%s', $type, rawurlencode((null !== $this->getIssuer() ? $this->getIssuer().':' : '').$label), $params);
    88     }
    89 
    90     private function getDecodedSecret(): string
    91     {
    92         try {
    93             return Base32::decodeUpper($this->getSecret());
    94         } catch (\Exception $e) {
    95             throw new RuntimeException('Unable to decode the secret. Is it correctly base32 encoded?');
    96         }
    97     }
    98 
    99     private function intToByteString(int $int): string
    100     {
    101         $result = [];
    102         while (0 !== $int) {
    103             $result[] = \chr($int & 0xFF);
    104             $int >>= 8;
    105         }
    106 
    107         return str_pad(implode(array_reverse($result)), 8, "\000", STR_PAD_LEFT);
    108     }
    109 
    110     protected function compareOTP(string $safe, string $user): bool
    111     {
    112         return hash_equals($safe, $user);
     435
     436        return match (true) {
     437            $issuer === null && $label === null => throw new InvalidLabelException(
     438                'The label is not set. Either label or issuer must be set.',
     439                'label'
     440            ),
     441            $label !== null && $this->hasColon($label) => throw new InvalidLabelException(
     442                'Label must not contain a colon.',
     443                'label',
     444                $label
     445            ),
     446            $issuer !== null && $label !== null => $issuer . ':' . $label,
     447            $issuer !== null => $issuer,
     448            default => $label,
     449        };
     450    }
     451
     452    /**
     453     * Validates a label according to Google Authenticator spec:
     454     * label = accountname / issuer (":" / "%3A") *"%20" accountname
     455     * Neither issuer nor account name may themselves contain a colon.
     456     *
     457     * Valid examples:
     458     * - alice@gmail.com
     459     * - Provider1:Alice%20Smith
     460     * - Big%20Corporation%3A%20alice%40bigco.com
     461     *
     462     * @param non-empty-string $value
     463     */
     464    private function validateLabel(string $value): void
     465    {
     466        // Check for colon separators (literal or URL-encoded)
     467        $hasLiteralColon = str_contains($value, ':');
     468        $hasEncodedColon = str_contains($value, '%3A') || str_contains($value, '%3a');
     469
     470        if (! $hasLiteralColon && ! $hasEncodedColon) {
     471            // Simple label (account name only) - no colons allowed anywhere
     472            return;
     473        }
     474
     475        // Label contains a separator - validate issuer:account format
     476        // Split by literal or encoded colon
     477        $parts = match (true) {
     478            $hasLiteralColon => explode(':', $value, 2),
     479            default => preg_split('/%3[Aa]/', $value, 2),
     480        };
     481
     482        if ($parts === false || count($parts) !== 2) {
     483            throw new InvalidLabelException('Label must not contain a colon.', 'label', $value);
     484        }
     485
     486        [$issuerPart, $accountPart] = $parts;
     487
     488        // Remove leading %20 (spaces) from account part per spec: *"%20" accountname
     489        $accountPart = ltrim($accountPart, '%20');
     490
     491        // Validate that neither part contains additional colons
     492        if ($this->hasColon($issuerPart) || $this->hasColon($accountPart)) {
     493            throw new InvalidLabelException(
     494                'Neither issuer nor account name in label may contain a colon.',
     495                'label',
     496                $value
     497            );
     498        }
     499    }
     500
     501    /**
     502     * @param non-empty-string $value
     503     */
     504    private function hasColon(string $value): bool
     505    {
     506        $colons = [':', '%3A', '%3a'];
     507        foreach ($colons as $colon) {
     508            if (str_contains($value, $colon)) {
     509                return true;
     510            }
     511        }
     512
     513        return false;
    113514    }
    114515}
  • wp-otp/trunk/vendor/spomky-labs/otphp/src/OTPInterface.php

    r2331970 r3752647  
    22
    33declare(strict_types=1);
    4 
    5 /*
    6  * The MIT License (MIT)
    7  *
    8  * Copyright (c) 2014-2019 Spomky-Labs
    9  *
    10  * This software may be modified and distributed under the terms
    11  * of the MIT license.  See the LICENSE file for details.
    12  */
    134
    145namespace OTPHP;
    … …  
    167interface OTPInterface
    178{
    18     /**
    19      * @return string Return the OTP at the specified timestamp
    20      */
    21     public function at(int $timestamp): string;
     9    public const DEFAULT_DIGITS = 6;
    2210
    2311    /**
    24      * Verify that the OTP is valid with the specified input.
    25      * If no input is provided, the input is set to a default value or false is returned.
     12     * Upper bound for the "digits" parameter. The RFC 4226 dynamic truncation
     13     * yields a 31-bit value (max 2 147 483 647, i.e. 10 digits); above this
     14     * bound the extra digits carry no entropy and "10 ** digits" overflows
     15     * PHP's integer range, leading to a DivisionByZeroError during generation.
    2616     */
    27     public function verify(string $otp, ?int $input = null, ?int $window = null): bool;
     17    public const MAX_DIGITS = 10;
     18
     19    public const DEFAULT_DIGEST = 'sha1';
    2820
    2921    /**
    30      * @return string The secret of the OTP
     22     * Create a OTP object from an existing secret.
     23     *
     24     * @param non-empty-string $secret
     25     */
     26    public static function createFromSecret(string $secret): self;
     27
     28    /**
     29     * Create a new OTP object. A random 64 bytes secret will be generated.
     30     */
     31    public static function generate(): self;
     32
     33    /**
     34     * @param non-empty-string $secret
     35     *
     36     * @deprecated Deprecated since v11.4, use {@see self::withSecret()} instead
     37     */
     38    public function setSecret(string $secret): void;
     39
     40    /**
     41     * @param non-empty-string $secret
     42     */
     43    public function withSecret(string $secret): self;
     44
     45    /**
     46     * @deprecated Deprecated since v11.4, use {@see self::withDigits()} instead
     47     */
     48    public function setDigits(int $digits): void;
     49
     50    public function withDigits(int $digits): self;
     51
     52    /**
     53     * @param non-empty-string $digest
     54     *
     55     * @deprecated Deprecated since v11.4, use {@see self::withDigest()} instead
     56     */
     57    public function setDigest(string $digest): void;
     58
     59    /**
     60     * @param non-empty-string $digest
     61     */
     62    public function withDigest(string $digest): self;
     63
     64    /**
     65     * Generate the OTP at the specified input.
     66     *
     67     * @param 0|positive-int $input
     68     *
     69     * @return non-empty-string Return the OTP at the specified timestamp
     70     */
     71    public function at(int $input): string;
     72
     73    /**
     74     * Verify that the OTP is valid with the specified input. If no input is provided, the input is set to a default
     75     * value or false is returned.
     76     *
     77     * @param non-empty-string $otp
     78     * @param null|0|positive-int $input
     79     * @param null|0|positive-int $window
     80     */
     81    public function verify(string $otp, null|int $input = null, null|int $window = null): bool;
     82
     83    /**
     84     * @return non-empty-string The secret of the OTP
    3185     */
    3286    public function getSecret(): string;
    3387
    3488    /**
    35      * @param string $label The label of the OTP
     89     * @param non-empty-string $label The label of the OTP
     90     *
     91     * @deprecated Deprecated since v11.4, use {@see self::withLabel()} instead
    3692     */
    3793    public function setLabel(string $label): void;
    3894
    3995    /**
    40      * @return string|null The label of the OTP
     96     * @param non-empty-string $label The label of the OTP
    4197     */
    42     public function getLabel(): ?string;
     98    public function withLabel(string $label): self;
    4399
    44100    /**
    45      * @return string|null The issuer
     101     * @return non-empty-string|null The label of the OTP
     102     */
     103    public function getLabel(): null|string;
     104
     105    /**
     106     * @return non-empty-string|null The issuer
    46107     */
    47108    public function getIssuer(): ?string;
    48109
     110    /**
     111     * @param non-empty-string $issuer
     112     *
     113     * @deprecated Deprecated since v11.4, use {@see self::withIssuer()} instead
     114     */
    49115    public function setIssuer(string $issuer): void;
     116
     117    /**
     118     * @param non-empty-string $issuer
     119     */
     120    public function withIssuer(string $issuer): self;
    50121
    51122    /**
    … …  
    54125    public function isIssuerIncludedAsParameter(): bool;
    55126
     127    /**
     128     * @deprecated Deprecated since v11.4, use {@see self::withIssuerIncludedAsParameter()} instead
     129     */
    56130    public function setIssuerIncludedAsParameter(bool $issuer_included_as_parameter): void;
    57131
     132    public function withIssuerIncludedAsParameter(bool $issuer_included_as_parameter): self;
     133
    58134    /**
    59      * @return int Number of digits in the OTP
     135     * @return positive-int Number of digits in the OTP
    60136     */
    61137    public function getDigits(): int;
    62138
    63139    /**
    64      * @return string Digest algorithm used to calculate the OTP. Possible values are 'md5', 'sha1', 'sha256' and 'sha512'
     140     * @return non-empty-string Digest algorithm used to calculate the OTP. Spec-compliant, interoperable values are 'sha1', 'sha256' and 'sha512'. Any digest shorter than 19 bytes (e.g. 'md5') is rejected as it cannot satisfy the RFC 4226 dynamic truncation.
    65141     */
    66142    public function getDigest(): string;
    67143
    68144    /**
    69      * @return mixed|null
     145     * @param non-empty-string $parameter
    70146     */
    71     public function getParameter(string $parameter);
     147    public function getParameter(string $parameter): mixed;
    72148
     149    /**
     150     * @param non-empty-string $parameter
     151     */
    73152    public function hasParameter(string $parameter): bool;
    74153
    75154    /**
    76      * @return array<string, mixed>
     155     * @return array<non-empty-string, mixed>
    77156     */
    78157    public function getParameters(): array;
    79158
    80159    /**
    81      * @param mixed|null $value
     160     * @param non-empty-string $parameter
     161     *
     162     * @deprecated Deprecated since v11.4, use {@see self::withParameter()} instead
    82163     */
    83     public function setParameter(string $parameter, $value): void;
     164    public function setParameter(string $parameter, mixed $value): void;
     165
     166    /**
     167     * @param non-empty-string $parameter
     168     */
     169    public function withParameter(string $parameter, mixed $value): self;
    84170
    85171    /**
    86172     * Get the provisioning URI.
     173     *
     174     * @return non-empty-string
    87175     */
    88176    public function getProvisioningUri(): string;
    … …  
    91179     * Get the provisioning URI.
    92180     *
    93      * @param string $uri         The Uri of the QRCode generator with all parameters. This Uri MUST contain a placeholder that will be replaced by the method.
    94      * @param string $placeholder the placeholder to be replaced in the QR Code generator URI
     181     * @param non-empty-string $uri         The Uri of the QRCode generator with all parameters. This Uri MUST contain a placeholder that will be replaced by the method.
     182     * @param non-empty-string $placeholder the placeholder to be replaced in the QR Code generator URI
    95183     */
    96184    public function getQrCodeUri(string $uri, string $placeholder): string;
  • wp-otp/trunk/vendor/spomky-labs/otphp/src/TOTP.php

    r2331970 r3752647  
    33declare(strict_types=1);
    44
    5 /*
    6  * The MIT License (MIT)
     5namespace OTPHP;
     6
     7use function is_int;
     8use OTPHP\Exception\InvalidParameterException;
     9use Psr\Clock\ClockInterface;
     10
     11/**
     12 * @readonly
    713 *
    8  * Copyright (c) 2014-2019 Spomky-Labs
    9  *
    10  * This software may be modified and distributed under the terms
    11  * of the MIT license.  See the LICENSE file for details.
     14 * @see \OTPHP\Test\TOTPTest
    1215 */
    13 
    14 namespace OTPHP;
    15 
    16 use Assert\Assertion;
    17 use function Safe\ksort;
    18 
    1916final class TOTP extends OTP implements TOTPInterface
    2017{
    21     protected function __construct(?string $secret, int $period, string $digest, int $digits, int $epoch = 0)
    22     {
    23         parent::__construct($secret, $digest, $digits);
    24         $this->setPeriod($period);
    25         $this->setEpoch($epoch);
    26     }
    27 
    28     public static function create(?string $secret = null, int $period = 30, string $digest = 'sha1', int $digits = 6, int $epoch = 0): TOTPInterface
    29     {
    30         return new self($secret, $period, $digest, $digits, $epoch);
    31     }
    32 
    33     protected function setPeriod(int $period): void
     18    private readonly ClockInterface $clock;
     19
     20    public function __construct(string $secret, ?ClockInterface $clock = null)
     21    {
     22        parent::__construct($secret);
     23        if ($clock === null) {
     24            trigger_deprecation(
     25                'spomky-labs/otphp',
     26                '11.3.0',
     27                'The parameter "$clock" will become mandatory in 12.0.0. Please set a valid PSR Clock implementation instead of "null".'
     28            );
     29            $clock = new InternalClock();
     30        }
     31
     32        $this->clock = $clock;
     33    }
     34
     35    public static function create(
     36        null|string $secret = null,
     37        int $period = self::DEFAULT_PERIOD,
     38        string $digest = self::DEFAULT_DIGEST,
     39        int $digits = self::DEFAULT_DIGITS,
     40        int $epoch = self::DEFAULT_EPOCH,
     41        ?ClockInterface $clock = null,
     42        ?int $secretSize = null
     43    ): self {
     44        $totp = $secret !== null
     45            ? self::createFromSecret($secret, $clock)
     46            : self::generate($clock, $secretSize)
     47        ;
     48        $totp->setPeriod($period);
     49        $totp->setDigest($digest);
     50        $totp->setDigits($digits);
     51        $totp->setEpoch($epoch);
     52
     53        return $totp;
     54    }
     55
     56    public static function createFromSecret(string $secret, ?ClockInterface $clock = null): self
     57    {
     58        $totp = new self($secret, $clock);
     59        $totp->setPeriod(self::DEFAULT_PERIOD);
     60        $totp->setDigest(self::DEFAULT_DIGEST);
     61        $totp->setDigits(self::DEFAULT_DIGITS);
     62        $totp->setEpoch(self::DEFAULT_EPOCH);
     63
     64        return $totp;
     65    }
     66
     67    /**
     68     * @param positive-int|null $secretSize
     69     */
     70    public static function generate(?ClockInterface $clock = null, ?int $secretSize = null): self
     71    {
     72        return self::createFromSecret(self::generateSecret($secretSize), $clock);
     73    }
     74
     75    public function getPeriod(): int
     76    {
     77        $value = $this->getParameter('period');
     78        (is_int($value) && $value > 0) || throw new InvalidParameterException(
     79            'Invalid "period" parameter.',
     80            'period',
     81            $value
     82        );
     83
     84        return $value;
     85    }
     86
     87    public function getEpoch(): int
     88    {
     89        $value = $this->getParameter('epoch');
     90        (is_int($value) && $value >= 0) || throw new InvalidParameterException(
     91            'Invalid "epoch" parameter.',
     92            'epoch',
     93            $value
     94        );
     95
     96        return $value;
     97    }
     98
     99    public function expiresIn(): int
     100    {
     101        $period = $this->getPeriod();
     102
     103        return $period - (($this->clock->now()->getTimestamp() - $this->getEpoch()) % $period);
     104    }
     105
     106    /**
     107     * The OTP at the specified input.
     108     *
     109     * @param 0|positive-int $input
     110     */
     111    public function at(int $input): string
     112    {
     113        return $this->generateOTP($this->timecode($input));
     114    }
     115
     116    public function now(): string
     117    {
     118        $timestamp = $this->clock->now()
     119            ->getTimestamp();
     120        $timestamp >= 0 || throw new InvalidParameterException(
     121            'The timestamp must return a positive integer.',
     122            'timestamp',
     123            $timestamp
     124        );
     125
     126        return $this->at($timestamp);
     127    }
     128
     129    /**
     130     * If no timestamp is provided, the OTP is verified at the actual timestamp. When used, the leeway parameter will
     131     * allow time drift. The passed value is in seconds.
     132     *
     133     * @param 0|positive-int $timestamp
     134     * @param null|0|positive-int $leeway
     135     */
     136    public function verify(string $otp, null|int $timestamp = null, null|int $leeway = null): bool
     137    {
     138        $timestamp ??= $this->clock->now()
     139            ->getTimestamp();
     140        $timestamp >= 0 || throw new InvalidParameterException(
     141            'Timestamp must be at least 0.',
     142            'timestamp',
     143            $timestamp
     144        );
     145
     146        if ($leeway === null) {
     147            return $this->compareOTP($this->at($timestamp), $otp);
     148        }
     149
     150        $leeway = abs($leeway);
     151        $leeway < $this->getPeriod() || throw new InvalidParameterException(
     152            'The leeway must be lower than the TOTP period',
     153            'leeway',
     154            $leeway
     155        );
     156        $timestampMinusLeeway = $timestamp - $leeway;
     157        $timestampMinusLeeway >= 0 || throw new InvalidParameterException(
     158            'The timestamp must be greater than or equal to the leeway.',
     159            'timestamp',
     160            $timestamp
     161        );
     162
     163        return $this->compareOTP($this->at($timestampMinusLeeway), $otp)
     164            || $this->compareOTP($this->at($timestamp), $otp)
     165            || $this->compareOTP($this->at($timestamp + $leeway), $otp);
     166    }
     167
     168    public function getProvisioningUri(): string
     169    {
     170        $params = [];
     171        if ($this->getPeriod() !== 30) {
     172            $params['period'] = $this->getPeriod();
     173        }
     174
     175        if ($this->getEpoch() !== 0) {
     176            $params['epoch'] = $this->getEpoch();
     177        }
     178
     179        return $this->generateURI('totp', $params);
     180    }
     181
     182    public function setPeriod(int $period): void
    34183    {
    35184        $this->setParameter('period', $period);
    36185    }
    37186
    38     public function getPeriod(): int
    39     {
    40         return $this->getParameter('period');
    41     }
    42 
    43     private function setEpoch(int $epoch): void
     187    public function withPeriod(int $period): self
     188    {
     189        $otp = clone $this;
     190        $otp->setParameter('period', $period);
     191
     192        return $otp;
     193    }
     194
     195    public function setEpoch(int $epoch): void
    44196    {
    45197        $this->setParameter('epoch', $epoch);
    46198    }
    47199
    48     public function getEpoch(): int
    49     {
    50         return $this->getParameter('epoch');
    51     }
    52 
    53     public function at(int $timestamp): string
    54     {
    55         return $this->generateOTP($this->timecode($timestamp));
    56     }
    57 
    58     public function now(): string
    59     {
    60         return $this->at(time());
    61     }
    62 
    63     /**
    64      * If no timestamp is provided, the OTP is verified at the actual timestamp.
    65      */
    66     public function verify(string $otp, ?int $timestamp = null, ?int $window = null): bool
    67     {
    68         $timestamp = $this->getTimestamp($timestamp);
    69 
    70         if (null === $window) {
    71             return $this->compareOTP($this->at($timestamp), $otp);
    72         }
    73 
    74         return $this->verifyOtpWithWindow($otp, $timestamp, $window);
    75     }
    76 
    77     private function verifyOtpWithWindow(string $otp, int $timestamp, int $window): bool
    78     {
    79         $window = abs($window);
    80 
    81         for ($i = 0; $i <= $window; ++$i) {
    82             $next = $i * $this->getPeriod() + $timestamp;
    83             $previous = -$i * $this->getPeriod() + $timestamp;
    84             $valid = $this->compareOTP($this->at($next), $otp) ||
    85                 $this->compareOTP($this->at($previous), $otp);
    86 
    87             if ($valid) {
    88                 return true;
    89             }
    90         }
    91 
    92         return false;
    93     }
    94 
    95     private function getTimestamp(?int $timestamp): int
    96     {
    97         $timestamp = $timestamp ?? time();
    98         Assertion::greaterOrEqualThan($timestamp, 0, 'Timestamp must be at least 0.');
    99 
    100         return $timestamp;
    101     }
    102 
    103     public function getProvisioningUri(): string
    104     {
    105         $params = [];
    106         if (30 !== $this->getPeriod()) {
    107             $params['period'] = $this->getPeriod();
    108         }
    109 
    110         if (0 !== $this->getEpoch()) {
    111             $params['epoch'] = $this->getEpoch();
    112         }
    113 
    114         return $this->generateURI('totp', $params);
    115     }
    116 
     200    public function withEpoch(int $epoch): self
     201    {
     202        $otp = clone $this;
     203        $otp->setParameter('epoch', $epoch);
     204
     205        return $otp;
     206    }
     207
     208    /**
     209     * @return array<non-empty-string, callable>
     210     */
     211    protected function getParameterMap(): array
     212    {
     213        return [
     214            ...parent::getParameterMap(),
     215            'period' => static function ($value): int {
     216                (int) $value > 0 || throw new InvalidParameterException('Period must be at least 1.', 'period', $value);
     217
     218                return (int) $value;
     219            },
     220            'epoch' => static function ($value): int {
     221                (int) $value >= 0 || throw new InvalidParameterException(
     222                    'Epoch must be greater than or equal to 0.',
     223                    'epoch',
     224                    $value
     225                );
     226
     227                return (int) $value;
     228            },
     229        ];
     230    }
     231
     232    /**
     233     * @param array<non-empty-string, mixed> $options
     234     */
     235    protected function filterOptions(array &$options): void
     236    {
     237        parent::filterOptions($options);
     238
     239        if (isset($options['epoch']) && $options['epoch'] === 0) {
     240            unset($options['epoch']);
     241        }
     242
     243        ksort($options);
     244    }
     245
     246    /**
     247     * @param 0|positive-int $timestamp
     248     *
     249     * @return 0|positive-int
     250     */
    117251    private function timecode(int $timestamp): int
    118252    {
    119         return (int) floor(($timestamp - $this->getEpoch()) / $this->getPeriod());
    120     }
    121 
    122     /**
    123      * @return array<string, mixed>
    124      */
    125     protected function getParameterMap(): array
    126     {
    127         $v = array_merge(
    128             parent::getParameterMap(),
    129             [
    130                 'period' => function ($value): int {
    131                     Assertion::greaterThan((int) $value, 0, 'Period must be at least 1.');
    132 
    133                     return (int) $value;
    134                 },
    135                 'epoch' => function ($value): int {
    136                     Assertion::greaterOrEqualThan((int) $value, 0, 'Epoch must be greater than or equal to 0.');
    137 
    138                     return (int) $value;
    139                 },
    140             ]
    141         );
    142 
    143         return $v;
    144     }
    145 
    146     /**
    147      * @param array<string, mixed> $options
    148      */
    149     protected function filterOptions(array &$options): void
    150     {
    151         parent::filterOptions($options);
    152 
    153         if (isset($options['epoch']) && 0 === $options['epoch']) {
    154             unset($options['epoch']);
    155         }
    156 
    157         ksort($options);
     253        $timecode = (int) floor(($timestamp - $this->getEpoch()) / $this->getPeriod());
     254        $timecode >= 0 || throw new InvalidParameterException(
     255            'Timecode must be at least 0. The timestamp must be greater than or equal to the epoch.',
     256            'timecode',
     257            $timecode
     258        );
     259
     260        return $timecode;
    158261    }
    159262}
  • wp-otp/trunk/vendor/spomky-labs/otphp/src/TOTPInterface.php

    r2331970 r3752647  
    22
    33declare(strict_types=1);
    4 
    5 /*
    6  * The MIT License (MIT)
    7  *
    8  * Copyright (c) 2014-2019 Spomky-Labs
    9  *
    10  * This software may be modified and distributed under the terms
    11  * of the MIT license.  See the LICENSE file for details.
    12  */
    134
    145namespace OTPHP;
    … …  
    167interface TOTPInterface extends OTPInterface
    178{
     9    public const DEFAULT_PERIOD = 30;
     10
     11    public const DEFAULT_EPOCH = 0;
     12
    1813    /**
    1914     * Create a new TOTP object.
    2015     *
    2116     * If the secret is null, a random 64 bytes secret will be generated.
     17     *
     18     * @param null|non-empty-string $secret
     19     * @param positive-int $period
     20     * @param non-empty-string $digest
     21     * @param positive-int $digits
     22     *
     23     * @deprecated Deprecated since v11.1, use ::createFromSecret or ::generate instead
    2224     */
    23     public static function create(?string $secret = null, int $period = 30, string $digest = 'sha1', int $digits = 6): self;
     25    public static function create(
     26        null|string $secret = null,
     27        int $period = self::DEFAULT_PERIOD,
     28        string $digest = self::DEFAULT_DIGEST,
     29        int $digits = self::DEFAULT_DIGITS
     30    ): self;
     31
     32    /**
     33     * @deprecated Deprecated since v11.4, use {@see self::withPeriod()} instead
     34     */
     35    public function setPeriod(int $period): void;
     36
     37    public function withPeriod(int $period): self;
     38
     39    /**
     40     * @deprecated Deprecated since v11.4, use {@see self::withEpoch()} instead
     41     */
     42    public function setEpoch(int $epoch): void;
     43
     44    public function withEpoch(int $epoch): self;
    2445
    2546    /**
    2647     * Return the TOTP at the current time.
     48     *
     49     * @return non-empty-string
    2750     */
    2851    public function now(): string;
    … …  
    3356    public function getPeriod(): int;
    3457
     58    public function expiresIn(): int;
     59
    3560    public function getEpoch(): int;
    3661}
  • wp-otp/trunk/wp-otp.php

    r2477335 r3752647  
    33 * Plugin Name:       WP-OTP
    44 * Plugin URI:        https://wordpress.org/plugins/wp-otp/
    5  * Description:       WP-OTP adds 2 Factor Authentication using TOTP. (Based on "WP Secure Login" by Brijesh Kothari)
    6  * Version:           0.6.1
     5 * Description:       WP-OTP adds 2 Factor Authentication using TOTP.
     6 * Version:           0.7.0
     7 * Requires PHP:      8.2
    78 * Author:            Armando Lüscher
    89 * Author URI:        https://noplanman.ch
    910 * License:           GPLv2 or later
    1011 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
    11  * GitLab Plugin URI: https://git.feneas.org/noplanman/wp-otp
    12  * GitLab Branch:     master
    1312 *
    1413 * @package Wp_Otp
    … …  
    2221// Define constants.
    2322define( 'WP_OTP_SLUG', 'wp-otp' );
    24 define( 'WP_OTP_VERSION', '0.6.1' );
     23define( 'WP_OTP_VERSION', '0.7.0' );
    2524
    2625require_once __DIR__ . '/vendor/autoload.php';
Note: See TracChangeset for help on using the changeset viewer.