' . esc_html__( 'Loading…', 'paygrab' ) . '
'; } /** * Load the app. * * @param string $hook Screen hook. * @return void */ public static function enqueue( $hook ) { if ( ! str_ends_with( (string) $hook, '_page_' . self::SLUG ) ) { return; } $dir = PAYGRAB_PATH . 'assets/admin/'; $asset = file_exists( $dir . 'settings.asset.php' ) ? require $dir . 'settings.asset.php' : array( 'dependencies' => array(), 'version' => PAYGRAB_VERSION, ); wp_enqueue_script( 'paygrab-settings', PAYGRAB_URL . 'assets/admin/settings.js', $asset['dependencies'], $asset['version'], true ); wp_set_script_translations( 'paygrab-settings', 'paygrab', PAYGRAB_PATH . 'languages' ); if ( file_exists( $dir . 'style-settings.css' ) ) { wp_enqueue_style( 'paygrab-settings', PAYGRAB_URL . 'assets/admin/style-settings.css', array( 'wp-components' ), $asset['version'] ); } wp_add_inline_script( 'paygrab-settings', 'window.paygrabSettingsBoot = ' . wp_json_encode( array( 'schema' => self::schema(), 'values' => self::values(), 'dashboard' => self::dashboard(), 'protect' => Paygrab_Files::last_check(), 'nginxRule' => Paygrab_Files::nginx_rule(), 'termsTemplate' => self::terms_template(), 'pages' => self::page_options(), 'money' => array( 'defaults' => array_combine( array_keys( Paygrab_Money::currencies() ), array_map( array( 'Paygrab_Money', 'defaults_for' ), array_keys( Paygrab_Money::currencies() ) ) ), 'symbols' => wp_list_pluck( Paygrab_Money::currencies(), 'symbol' ), 'decimals' => wp_list_pluck( Paygrab_Money::currencies(), 'decimals' ), 'hasOrders' => (bool) Paygrab_Orders::count_all(), ), 'initial' => 'payment', 'urls' => array( 'dashboard' => admin_url( 'admin.php?page=paygrab' ), 'settings' => admin_url( 'admin.php?page=' . self::SLUG ), 'preview' => add_query_arg( array( Paygrab_Preview::ARG => 1, '_wpnonce' => wp_create_nonce( Paygrab_Preview::ARG ), ), home_url( '/' ) ), ), ) ) . ';', 'before' ); } /** * Pages to choose from. * * @return array */ private static function page_options() { $options = array( '0' => __( '— Choose —', 'paygrab' ) ); foreach ( get_pages( array( 'post_status' => array( 'publish', 'draft', 'private' ) ) ) as $page ) { $options[ (string) $page->ID ] = $page->post_title . ( 'publish' !== $page->post_status ? paygrab_open() . __( 'not published', 'paygrab' ) . paygrab_close() : '' ); } return $options; } /** * The "Role of new members" field: a choice only when the site has more than one safe role. * * @return array */ private static function role_field() { $label = __( 'Role of new members', 'paygrab' ); $options = self::role_options(); if ( count( $options ) > 1 ) { return array( 'key' => 'register_role', 'type' => 'select', 'label' => $label, 'options' => $options ); } return array( 'key' => '_role', 'type' => 'fixed', 'label' => $label, 'text' => $options ? (string) reset( $options ) : translate_user_role( 'Subscriber' ), 'desc' => __( 'Members can buy and use My page; they cannot edit the site.', 'paygrab' ), ); } /** * Roles new members can get (roles that cannot write posts). * * @return array */ private static function role_options() { $out = array(); foreach ( wp_roles()->roles as $key => $role ) { if ( paygrab_role_is_safe( $key ) ) { $out[ $key ] = translate_user_role( $role['name'] ); } } return $out; } /** * URL of the PAYGRAB Pro sales page ('' = nothing about Pro is shown). * * @return string */ public static function pro_url() { // The locale of the current admin screen (the user's own language, or the site language). $locale = determine_locale(); $url = 0 === strpos( $locale, 'ja' ) ? self::PRO_URL_JA : self::PRO_URL_EN; /** * Filters the URL of the PAYGRAB Pro sales page shown in the admin screens. * * @param string $url URL ('' hides every line about Pro). * @param string $locale Locale of the current admin screen. */ return esc_url_raw( (string) apply_filters( 'paygrab_pro_url', $url, $locale ) ); } /** * Definition of the screen: sections → cards → fields. * * @return array[] */ public static function schema() { $shortcode = static function ( $code ) { /* translators: %s: shortcode */ return sprintf( __( 'The page needs the shortcode %s.', 'paygrab' ), $code ); }; $sections = array( array( 'id' => 'payment', 'name' => __( 'Payment', 'paygrab' ), 'group' => __( 'Selling', 'paygrab' ), // With other payment methods (PAYGRAB Pro: cards, PayPay and Stripe's bank transfer) the lead says so. 'lead' => count( Paygrab_Gateways::all() ) > 1 ? __( 'Orders are paid by bank transfer, cards or PayPay. The bank account is shown on the order completion screen and in the email.', 'paygrab' ) : __( 'Orders are paid by bank transfer. The account is shown on the order completion screen and in the email.', 'paygrab' ), 'cards' => array( array( 'id' => 'currency', 'title' => __( 'Currency', 'paygrab' ), 'fields' => array( array( 'key' => 'currency', 'type' => 'select', 'label' => __( 'Currency', 'paygrab' ), 'options' => wp_list_pluck( Paygrab_Money::currencies(), 'name' ), 'desc' => __( 'Changing the currency fills in the usual defaults for that currency below (tax, how a payment is matched, the legal notice page). You can change each of them afterwards. Prices are kept as they are: enter them again when you change the currency.', 'paygrab' ), ), array( 'key' => 'currency_position', 'type' => 'segment', 'label' => __( 'Symbol position', 'paygrab' ), 'options' => array( 'before' => __( 'Before', 'paygrab' ), 'after' => __( 'After', 'paygrab' ), ), // The screen shows an example in the currency chosen above, e.g. "Before ($9.99)". 'example' => array( /* translators: %s: an example amount with the currency symbol in front, e.g. "$9.99" */ 'before' => __( 'Before (%s)', 'paygrab' ), /* translators: %s: an example amount with the currency symbol after it, e.g. "9.99 $" */ 'after' => __( 'After (%s)', 'paygrab' ), ), 'desc' => __( 'The decimal point and the thousands separator follow the site’s language.', 'paygrab' ), ), ), ), array( 'id' => 'tax', 'title' => __( 'Tax', 'paygrab' ), 'fields' => array( array( 'key' => 'tax_name', 'type' => 'text', 'label' => __( 'Tax name', 'paygrab' ), 'placeholder' => __( 'Consumption tax', 'paygrab' ), 'size' => 'small', 'desc' => __( 'Shown on the tax line of the screens and emails (for example: consumption tax, VAT, GST, sales tax).', 'paygrab' ) ), array( 'key' => 'tax_rate', 'type' => 'decimal', 'label' => __( 'Tax rate', 'paygrab' ), 'unit' => '%', 'min' => 0, 'max' => 100, 'desc' => __( 'Decimals are fine (for example 8.1). With 0, no tax line is shown. A product can have its own rate on its edit screen.', 'paygrab' ) ), array( 'key' => 'tax_mode', 'type' => 'segment', 'label' => __( 'Prices you enter', 'paygrab' ), 'options' => array( 'incl' => __( 'Include the tax', 'paygrab' ), 'excl' => __( 'Do not include the tax', 'paygrab' ), ), 'desc' => __( 'Included: the price is what the customer pays, and the tax it contains is shown under the total. Not included: the tax is added on top, and the total is shown on the confirmation screen.', 'paygrab' ), ), array( 'key' => 'tax_rounding', 'type' => 'select', 'label' => __( 'Rounding', 'paygrab' ), 'options' => array( 'down' => __( 'Round down', 'paygrab' ), 'half_up' => __( 'Round half up', 'paygrab' ), 'up' => __( 'Round up', 'paygrab' ), ), 'desc' => __( 'How the part of the tax smaller than the smallest unit of the currency is treated. The tax is calculated once per line.', 'paygrab' ), ), ), ), array( 'id' => 'bank', 'title' => __( 'Bank account', 'paygrab' ), 'fields' => array( array( 'key' => 'bank_rows', 'type' => 'rows', 'label' => __( 'Account', 'paygrab' ), 'wide' => true, 'templates' => Paygrab_Money::account_templates(), 'desc' => __( 'The lines the customer sees, from top to bottom. Both the label and the value are free text. “Start from” fills in the labels only; you can change them afterwards.', 'paygrab' ), ), ), ), array( 'id' => 'reference', 'title' => __( 'Matching payments to orders', 'paygrab' ), 'fields' => array( array( 'key' => 'transfer_ref', 'type' => 'segment', 'label' => __( 'What the customer is asked to do', 'paygrab' ), 'options' => array( 'name_prefix' => __( 'Put a number in front of the name', 'paygrab' ), 'reference' => __( 'Enter the order number as the payment reference', 'paygrab' ), ), 'desc' => __( 'In Japan, only the payer’s name reaches your bank book, so the customer puts a 6-digit number in front of it (for example “362407 YAMADA HANAKO”). Elsewhere, transfers have a reference field: the customer enters the order number there.', 'paygrab' ), ), ), ), array( 'id' => 'deadline', 'title' => __( 'Deadline and reminders', 'paygrab' ), 'fields' => array( array( 'key' => 'pay_days', 'type' => 'number', 'label' => __( 'Payment deadline', 'paygrab' ), 'before' => __( 'within', 'paygrab' ), 'unit' => __( 'days of the order', 'paygrab' ), 'min' => 1, 'max' => 60 ), array( 'key' => 'remind', 'type' => 'toggle', 'label' => __( 'Reminder', 'paygrab' ), 'on' => __( 'Email the customer the day before the deadline', 'paygrab' ), 'desc' => __( 'Orders not paid by the deadline are cancelled automatically and the customer is told.', 'paygrab' ) ), ), ), ), ), array( 'id' => 'law', 'name' => __( 'Legal notice', 'paygrab' ), 'group' => __( 'Selling', 'paygrab' ), 'lead' => __( 'The “Specified Commercial Transactions Act” page is created when PAYGRAB is activated. Edit its content here.', 'paygrab' ), 'link' => array( paygrab_page_url( 'tokushoho' ), __( 'View the page', 'paygrab' ) ), 'cards' => array( array( 'id' => 'lawpage', 'title' => __( 'Legal notice page', 'paygrab' ), 'fields' => array( array( 'key' => 'law_enabled', 'type' => 'toggle', 'label' => __( 'Page', 'paygrab' ), 'on' => __( 'Use the legal notice page', 'paygrab' ), 'off' => __( 'Do not use the legal notice page', 'paygrab' ), 'desc' => __( 'Required when you sell in Japan. It is on from the start for yen and off for other currencies. When off, the buy box and the confirmation screen do not link to it.', 'paygrab' ), ), ), ), array( 'id' => 'seller', 'title' => __( 'Seller', 'paygrab' ), 'fields' => array( array( 'key' => 'law_seller', 'type' => 'text', 'label' => __( 'Seller', 'paygrab' ), 'required' => true, 'requires' => 'law_enabled' ), array( 'key' => 'law_manager', 'type' => 'text', 'label' => __( 'Person in charge', 'paygrab' ), 'required' => true, 'requires' => 'law_enabled' ), array( 'key' => 'law_address', 'type' => 'text', 'label' => __( 'Address', 'paygrab' ), 'required' => true, 'requires' => 'law_enabled', 'unless' => 'law_address_hide' ), array( 'key' => 'law_address_hide', 'type' => 'toggle', 'label' => '', 'on' => __( 'Do not show it; show “disclosed without delay upon request” instead (for individuals)', 'paygrab' ) ), array( 'key' => 'law_phone', 'type' => 'text', 'label' => __( 'Phone number', 'paygrab' ), 'required' => true, 'requires' => 'law_enabled', 'unless' => 'law_phone_hide', 'size' => 'small' ), array( 'key' => 'law_phone_hide', 'type' => 'toggle', 'label' => '', 'on' => __( 'Do not show it; show “disclosed without delay upon request” instead', 'paygrab' ) ), array( 'key' => 'law_email', 'type' => 'email', 'label' => __( 'Email address', 'paygrab' ), 'required' => true, 'requires' => 'law_enabled' ), ), ), array( 'id' => 'terms', 'title' => __( 'Terms of sale', 'paygrab' ), 'fields' => array( array( 'key' => '_law_auto', 'type' => 'note', 'text' => __( 'Payment methods, payment timing and extra fees are filled in automatically from the payment settings.', 'paygrab' ) ), array( 'key' => 'law_delivery', 'type' => 'textarea', 'label' => __( 'Delivery timing', 'paygrab' ), 'rows' => 2 ), array( 'key' => 'law_cancel', 'type' => 'textarea', 'label' => __( 'Returns and cancellations', 'paygrab' ), 'rows' => 3, 'desc' => __( 'Also shown on the final confirmation screen.', 'paygrab' ) ), array( 'key' => 'law_environment', 'type' => 'textarea', 'label' => __( 'System requirements', 'paygrab' ), 'rows' => 2, 'placeholder' => __( 'e.g. A device that can open PDF files', 'paygrab' ) ), ), ), ), ), array( 'id' => 'pages', 'name' => __( 'Pages and sign-up', 'paygrab' ), 'group' => __( 'Members', 'paygrab' ), 'lead' => __( 'These pages were created when you activated PAYGRAB. You can choose other pages.', 'paygrab' ), 'cards' => array( array( 'id' => 'pagelist', 'title' => __( 'Pages', 'paygrab' ), 'fields' => array( array( 'key' => '_pages_note', 'type' => 'note', 'text' => __( 'PAYGRAB shows its screens (checkout, My page, sign-up, log in and the legal notice) inside the pages chosen here. The pages were created when you activated PAYGRAB, so you usually do not need to change anything. To use another page, first paste the shortcode shown under the item into that page, then choose the page here. If one of these pages is deleted or set back to draft, customers cannot buy.', 'paygrab' ) ), array( 'key' => 'page_checkout', 'type' => 'page', 'label' => __( 'Checkout', 'paygrab' ), 'desc' => $shortcode( '[paygrab_checkout]' ) ), array( 'key' => 'page_mypage', 'type' => 'page', 'label' => __( 'My page', 'paygrab' ), 'desc' => $shortcode( '[paygrab_mypage]' ) ), array( 'key' => 'page_register', 'type' => 'page', 'label' => __( 'Sign up', 'paygrab' ), 'desc' => $shortcode( '[paygrab_register]' ) ), array( 'key' => 'page_login', 'type' => 'page', 'label' => __( 'Log in', 'paygrab' ), 'desc' => $shortcode( '[paygrab_login]' ) ), array( 'key' => 'page_tokushoho', 'type' => 'page', 'label' => __( 'Legal notice', 'paygrab' ), 'desc' => $shortcode( '[paygrab_tokushoho]' ) ), ), ), array( 'id' => 'signup', 'title' => __( 'Sign-up', 'paygrab' ), 'fields' => array( array( 'key' => 'email_verification', 'type' => 'toggle', 'label' => __( "Email verification\n(double opt-in)", 'paygrab' ), 'on' => __( 'On: only people who open the link in the email become members', 'paygrab' ), 'off' => __( 'Off: people become members right away, without verification', 'paygrab' ), 'desc' => __( 'People who sign up get a provisional registration email, and only those who open its link become members (the link works once, for 24 hours). Nobody is signed up with someone else’s address or a mistyped one. Recommended.', 'paygrab' ), 'desc_off' => __( 'When off: signing up makes the person a member right away and logs them in; no provisional registration email is sent. Links of sign-ups already waiting for confirmation still work.', 'paygrab' ), 'confirm' => __( 'Members will be created without checking that the email address belongs to them. Turn email verification off?', 'paygrab' ), 'warn' => array( 'title' => __( 'With verification off, members are created without checking who owns the email address.', 'paygrab' ), 'items' => array( __( 'Fake or other people’s addresses can become members, and order and payment emails reach those people.', 'paygrab' ), __( 'A person who mistypes the address gets no purchase email, no download notice and cannot reset the password. That is why the sign-up form gets an “Email address (confirm)” field.', 'paygrab' ), __( 'Large automated sign-ups are harder to stop (the hidden field, the 3-second check and the limit per IP address still work).', 'paygrab' ), ), 'note' => __( 'If emails do not arrive, do not turn this off. Send a test email on the Email screen and check your SMTP plugin first.', 'paygrab' ), ), ), array( 'key' => '_spam', 'type' => 'status', 'label' => __( 'Spam protection', 'paygrab' ), 'status' => 'ok', 'text' => __( 'On', 'paygrab' ), 'desc' => __( 'The hidden field (honeypot), refusing forms sent within 3 seconds and the limit of sign-ups per IP address are always on.', 'paygrab' ) ), self::role_field(), ), ), array( 'id' => 'terms', 'title' => __( 'Terms of use', 'paygrab' ), 'fields' => array( array( 'key' => 'terms_text', 'type' => 'richtext', 'label' => __( 'Text of the terms', 'paygrab' ), 'desc' => __( 'Shown in full in a small scrolling box under “I agree” on the sign-up form. When empty, the box is not shown.', 'paygrab' ) ), array( 'key' => 'page_terms', 'type' => 'page', 'empty' => __( '— Not used (box only) —', 'paygrab' ), 'label' => __( 'Terms of use page', 'paygrab' ), 'desc' => __( 'When chosen, “Open the terms on another page” appears under the box.', 'paygrab' ) ), array( 'key' => 'terms_scroll', 'type' => 'toggle', 'label' => __( 'Read to the end', 'paygrab' ), 'on' => __( 'Members cannot press “I agree” until they scroll to the end of the box', 'paygrab' ), 'desc' => __( 'Off by default (hard to use on phones).', 'paygrab' ) ), ), ), ), ), array( 'id' => 'design', 'name' => __( 'Design', 'paygrab' ), 'group' => __( 'Look', 'paygrab' ), 'lead' => __( 'How the screens PAYGRAB shows look: the buy box, sign-up, log in, My page and so on. The theme’s fonts and page background stay as they are.', 'paygrab' ), 'cards' => array( array( 'id' => 'look', 'title' => __( 'Choose a design', 'paygrab' ), 'fields' => array( array( 'key' => 'design_style', 'type' => 'looks', 'label' => __( 'Design', 'paygrab' ), 'wide' => true, 'options' => array( 'standard' => array( __( 'Standard', 'paygrab' ), __( 'White surfaces and thin lines. Default', 'paygrab' ), __( 'Product image: left, 4:3', 'paygrab' ) ), 'card' => array( __( 'Card', 'paygrab' ), __( 'Rounded corners, shadows, key colour', 'paygrab' ), __( 'Product image: on top, 16:9', 'paygrab' ) ), 'minimal' => array( __( 'Minimal', 'paygrab' ), __( 'Only lines and space', 'paygrab' ), __( 'Product image: right, 1:1', 'paygrab' ) ), ), ), ), ), array( 'id' => 'colors', 'title' => __( 'Colours', 'paygrab' ), 'fields' => array( array( 'key' => 'key_color_mode', 'type' => 'keycolor', 'label' => __( 'Button colour (key colour)', 'paygrab' ), 'options' => array( 'theme' => __( 'Match the theme (uses the key colour of TCD themes; black when unknown)', 'paygrab' ), 'custom' => __( 'Choose it yourself', 'paygrab' ), ), 'theme' => Paygrab_Styles::theme_color(), 'light' => Paygrab_Styles::theme_color() && Paygrab_Styles::too_light( Paygrab_Styles::theme_color() ), 'desc' => __( 'When the colour is so light that white text is hard to read, the text turns black.', 'paygrab' ), ), array( 'key' => 'key_color', 'type' => 'hidden', 'label' => __( 'Key colour', 'paygrab' ), 'with' => 'key_color_mode' ), array( 'key' => '_status_colors', 'type' => 'statuscolors', 'label' => __( 'Colours of “awaiting payment” and “purchased”', 'paygrab' ), 'text' => __( '(they carry meaning, so they do not change)', 'paygrab' ) ), ), ), array( 'id' => 'preview', 'title' => __( 'Preview', 'paygrab' ), 'fields' => array( array( 'key' => '_look_preview', 'type' => 'lookpreview', 'label' => __( 'Preview', 'paygrab' ), 'wide' => true, 'desc' => __( 'When you save, all PAYGRAB screens of the site get this design.', 'paygrab' ), 'notes' => array( __( 'The product image is set for each product (on the product’s edit screen). Products without one use the “No image” layout.', 'paygrab' ), __( 'Where the image goes depends on the design. On phones it is on top in every design.', 'paygrab' ) ), 'link' => (string) paygrab_page_url( 'mypage' ) ), ), ), ), ), array( 'id' => 'mail', 'name' => __( 'Email', 'paygrab' ), 'group' => __( 'Notifications', 'paygrab' ), 'lead' => __( 'Emails are sent with the standard WordPress function. If they do not arrive, use an SMTP plugin (such as WP Mail SMTP).', 'paygrab' ), 'cards' => array( array( 'id' => 'sender', 'title' => __( 'Sender and notifications', 'paygrab' ), 'fields' => array( array( 'key' => 'mail_from_name', 'type' => 'text', 'label' => __( 'Sender name', 'paygrab' ), 'placeholder' => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ), array( 'key' => 'mail_from', 'type' => 'email', 'label' => __( 'Sender address', 'paygrab' ), 'placeholder' => 'info@example.com', 'desc' => __( 'An address on your site’s domain is less likely to end up in spam.', 'paygrab' ) ), array( 'key' => 'mail_admin', 'type' => 'email', 'label' => __( 'Notifications to the seller', 'paygrab' ), 'placeholder' => (string) get_option( 'admin_email' ) ), array( 'key' => '_test', 'type' => 'testmail', 'label' => __( 'Test', 'paygrab' ) ), ), ), ), ), array( 'id' => 'protect', 'name' => __( 'File protection', 'paygrab' ), 'group' => __( 'Tools', 'paygrab' ), 'page' => 'protect', 'cards' => array(), ), array( 'id' => 'data', 'name' => __( 'Data', 'paygrab' ), 'group' => __( 'Tools', 'paygrab' ), 'lead' => __( 'What happens to the data when you delete PAYGRAB from the Plugins screen. Deactivating it never deletes anything.', 'paygrab' ), 'cards' => array( array( 'id' => 'removal', 'title' => __( 'When the plugin is deleted', 'paygrab' ), 'fields' => array( array( 'key' => 'delete_data', 'type' => 'toggle', 'label' => __( 'Data', 'paygrab' ), 'on' => __( 'Also delete all PAYGRAB data when the plugin is deleted', 'paygrab' ), 'desc' => __( 'Off by default: orders, members, products, settings and the files you uploaded stay, because orders are your accounting records. When on, deleting the plugin removes the order tables, the products, the uploaded sales files, the settings and the pages PAYGRAB created (pages you edited are kept). Members stay as WordPress users.', 'paygrab' ), ), ), ), ), ), ); // One short, quiet line about other ways to pay, only while bank transfer is the only one. No tracking in the link. $pro_url = self::pro_url(); if ( '' !== $pro_url && count( Paygrab_Gateways::all() ) < 2 ) { $sections[0]['cards'][] = array( 'id' => 'more', 'title' => __( 'More ways to pay', 'paygrab' ), 'fields' => array( array( 'key' => '_more_note', 'type' => 'note', 'text' => __( 'Cards, PayPay and Stripe’s bank transfer (payments confirmed automatically) come with PAYGRAB Pro, a separate product.', 'paygrab' ) ), array( 'key' => '_more_link', 'type' => 'link', 'label' => __( 'About PAYGRAB Pro', 'paygrab' ), 'url' => $pro_url, 'external' => true ), ), ); } // One quiet line under the email settings: the texts are fixed here, PAYGRAB Pro edits them. No tracking in the link. if ( '' !== $pro_url ) { foreach ( $sections as $i => $section ) { foreach ( $section['cards'] as $j => $card ) { if ( 'sender' === $card['id'] ) { $sections[ $i ]['cards'][ $j ]['fields'][] = array( 'key' => '_pro_mail', 'type' => 'note', 'foot' => true, 'text' => __( 'Emails are sent with fixed texts. To change the texts, use PAYGRAB Pro, a separate product.', 'paygrab' ), 'link' => $pro_url, 'linkText' => __( 'Learn more', 'paygrab' ) ); } } } } // A TCD theme's email verification (PANDORA etc.) wins over this switch: say so, and do not warn about "off". if ( paygrab_tcd_theme_verification_on() ) { foreach ( $sections as $i => $section ) { foreach ( $section['cards'] as $j => $card ) { if ( 'signup' !== $card['id'] ) { continue; } foreach ( $card['fields'] as $k => $field ) { if ( 'email_verification' === ( $field['key'] ?? '' ) ) { $sections[ $i ]['cards'][ $j ]['fields'][ $k ]['forced'] = true; } } array_unshift( $sections[ $i ]['cards'][ $j ]['fields'], array( 'key' => '_theme_note', 'type' => 'note', 'text' => __( 'Email verification is on in your theme’s settings, so it is also on here, whatever this switch says. Turn it off in the theme first if you want to turn it off.', 'paygrab' ) ) ); } } } // YOMERU Pro's own sign-up screen replaces this one while its email verification is on, so its setting decides. if ( paygrab_yomeru_signup() ) { foreach ( $sections as $i => $section ) { foreach ( $section['cards'] as $j => $card ) { if ( 'signup' === $card['id'] ) { array_unshift( $sections[ $i ]['cards'][ $j ]['fields'], array( 'key' => '_yomeru_note', 'type' => 'note', 'text' => __( 'While email verification and spam protection are on in YOMERU Pro, sign-up uses YOMERU’s screen and YOMERU Pro’s settings decide. The setting here applies only when PAYGRAB’s own sign-up page is used.', 'paygrab' ) ) ); } } } } /** * Filters the settings screen definition. PAYGRAB Pro adds its sections here. * * @param array $sections Sections. */ return apply_filters( 'paygrab_settings_schema', $sections ); } /** * All fields of the schema, keyed by setting key. * * @return array */ private static function fields() { $out = array(); foreach ( self::schema() as $section ) { foreach ( $section['cards'] as $card ) { foreach ( $card['fields'] as $field ) { if ( isset( $field['key'] ) && '_' !== $field['key'][0] ) { $out[ $field['key'] ] = $field; } } } } return $out; } /** * Current values of the fields on the screen. * * @return array */ public static function values() { $all = Paygrab_Settings::all(); $out = array(); foreach ( self::fields() as $key => $field ) { $value = $all[ $key ] ?? ''; if ( 'page' === $field['type'] ) { $value = (string) (int) $value; } elseif ( 'toggle' === $field['type'] ) { $value = (bool) $value; } elseif ( 'number' === $field['type'] ) { $value = (int) $value; } elseif ( 'decimal' === $field['type'] ) { $value = Paygrab_Money::rate_label( $value ); } elseif ( 'rows' === $field['type'] ) { $value = array_values( array_map( static fn( $r ) => array( 'label' => (string) ( $r['label'] ?? '' ), 'value' => (string) ( $r['value'] ?? '' ) ), is_array( $value ) ? $value : array() ) ); } elseif ( 'password' === $field['type'] ) { // Secrets (e.g. an API key) never leave the server in full: the screen gets only the end of the key. $value = self::mask_secret( (string) $value ); } $out[ $key ] = $value; } return $out; } /** * A secret as the screen sees it: dots and the last four characters (nothing for an empty one). * * @param string $secret Secret. * @return string */ public static function mask_secret( $secret ) { $secret = (string) $secret; if ( '' === $secret ) { return ''; } return str_repeat( '•', 4 ) . ( strlen( $secret ) > 8 ? substr( $secret, -4 ) : '' ); } /** * Clean one value. * * @param array $field Field. * @param mixed $value Value. * @return mixed|WP_Error */ private static function sanitize( array $field, $value ) { switch ( $field['type'] ) { case 'textarea': return sanitize_textarea_field( (string) $value ); case 'richtext': return paygrab_terms_clean( (string) $value ); case 'number': $n = (int) $value; return max( (int) ( $field['min'] ?? 0 ), min( (int) ( $field['max'] ?? PHP_INT_MAX ), $n ) ); case 'decimal': $value = trim( (string) $value ); if ( '' === $value || ! is_numeric( $value ) ) { return new WP_Error( $field['key'], __( 'Enter a number (for example 10 or 8.1).', 'paygrab' ) ); } return max( (float) ( $field['min'] ?? 0 ), min( (float) ( $field['max'] ?? 100 ), round( (float) $value, 2 ) ) ); case 'rows': // Rows of label and value; empty rows are dropped, at most 20 are kept. $rows = array(); foreach ( is_array( $value ) ? array_slice( $value, 0, 20 ) : array() as $row ) { $label = sanitize_text_field( (string) ( $row['label'] ?? '' ) ); $text = sanitize_text_field( (string) ( $row['value'] ?? '' ) ); if ( '' !== $label || '' !== $text ) { $rows[] = array( 'label' => $label, 'value' => $text ); } } return $rows; case 'toggle': return (bool) $value; case 'email': $value = trim( (string) $value ); if ( '' !== $value && ! is_email( $value ) ) { return new WP_Error( $field['key'], __( 'Enter a valid email address.', 'paygrab' ) ); } return sanitize_email( $value ); case 'looks': case 'keycolor': return array_key_exists( (string) $value, $field['options'] ) ? (string) $value : new WP_Error( $field['key'], __( 'Choose one of the options.', 'paygrab' ) ); case 'hidden': if ( 'key_color' !== $field['key'] ) { return sanitize_text_field( (string) $value ); } $color = Paygrab_Styles::hex( (string) $value ); if ( ! $color ) { return new WP_Error( $field['key'], __( 'Choose a colour.', 'paygrab' ) ); } return Paygrab_Styles::too_light( $color ) ? new WP_Error( $field['key'], __( 'This colour is too light: buttons would disappear on a white page. Choose a slightly darker colour.', 'paygrab' ) ) : $color; case 'select': case 'segment': return array_key_exists( (string) $value, $field['options'] ) ? (string) $value : new WP_Error( $field['key'], __( 'Choose one of the options.', 'paygrab' ) ); case 'page': $id = absint( $value ); return ( 0 === $id || 'page' === get_post_type( $id ) ) ? $id : new WP_Error( $field['key'], __( 'Choose a page.', 'paygrab' ) ); default: return sanitize_text_field( (string) $value ); } } /** * REST routes. * * @return void */ public static function routes() { $admin = static fn() => current_user_can( 'manage_options' ); register_rest_route( self::NS, '/settings', array( array( 'methods' => 'GET', 'permission_callback' => $admin, 'callback' => static fn() => rest_ensure_response( array( 'values' => self::values(), 'dashboard' => self::dashboard() ) ), ), array( 'methods' => 'POST', 'permission_callback' => $admin, 'callback' => array( __CLASS__, 'save' ), ), ) ); register_rest_route( self::NS, '/test-mail', array( 'methods' => 'POST', 'permission_callback' => $admin, 'callback' => static function () { $sent = Paygrab_Mailer::test(); if ( $sent ) { update_option( 'paygrab_test_mail_sent', time(), false ); } return rest_ensure_response( array( 'sent' => $sent, 'to' => Paygrab_Mailer::admin_address(), ) ); }, ) ); register_rest_route( self::NS, '/protect-check', array( 'methods' => 'POST', 'permission_callback' => $admin, 'callback' => static fn() => rest_ensure_response( Paygrab_Files::check( true ) ), ) ); } /** * Save changed settings. * * @param WP_REST_Request $request Request. * @return WP_REST_Response|WP_Error */ public static function save( WP_REST_Request $request ) { $changes = $request->get_param( 'changes' ); if ( ! is_array( $changes ) || ! $changes ) { return new WP_Error( 'paygrab_empty', __( 'There are no changes to save.', 'paygrab' ), array( 'status' => 400 ) ); } $fields = self::fields(); $clean = array(); $errors = new WP_Error(); foreach ( $changes as $key => $value ) { if ( ! isset( $fields[ $key ] ) ) { continue; } // A secret sent back as it was shown (masked) is not a change: the saved one stays. if ( 'password' === $fields[ $key ]['type'] && is_string( $value ) && '' !== $value && self::mask_secret( (string) Paygrab_Settings::get( $key ) ) === $value ) { continue; } $result = self::sanitize( $fields[ $key ], $value ); if ( ! is_wp_error( $result ) ) { /** * Filters a cleaned setting value before it is saved. Return a WP_Error to reject it. * * @param mixed $result Cleaned value. * @param array $field Field definition. * @param mixed $value Value as sent. */ $result = apply_filters( 'paygrab_sanitize_setting', $result, $fields[ $key ], $value ); } if ( is_wp_error( $result ) ) { // A colour typed for "Choose it yourself" does not block saving when "Match the theme" is chosen; the old colour stays. if ( 'key_color' === $key && 'custom' !== ( $changes['key_color_mode'] ?? Paygrab_Settings::get( 'key_color_mode' ) ) ) { continue; } $errors->add( $key, $result->get_error_message() ); continue; } $clean[ $key ] = $result; } if ( $errors->has_errors() ) { $first = $errors->get_error_code(); $out = new WP_Error( $first, $errors->get_error_message( $first ), array( 'status' => 400 ) ); foreach ( $errors->get_error_codes() as $code ) { if ( $code !== $first ) { $out->add( $code, $errors->get_error_message( $code ) ); } } return $out; } Paygrab_Settings::update( $clean ); return rest_ensure_response( array( 'values' => self::values(), 'dashboard' => self::dashboard(), ) ); } /** * What the side menu marks: sections with something left to do (the steps themselves are on the dashboard). * * @return array{protect:string,missing:array} */ public static function dashboard() { $protect = Paygrab_Files::last_check(); return array( 'protect' => is_array( $protect ) ? $protect['status'] : 'unknown', 'missing' => array( 'payment' => ! Paygrab_Gateways::ready(), 'law' => (bool) Paygrab_Settings::missing_law_items(), 'pages' => class_exists( 'Paygrab_Admin_Dashboard' ) && (bool) Paygrab_Admin_Dashboard::missing_pages(), ), ); } /** * Remind the seller to finish the setup (only on PAYGRAB screens and the plugins screen). * * @return void */ public static function setup_notice() { // Only on PAYGRAB's own list screens (products, orders): never on every admin screen or on the Plugins screen. $screen = get_current_screen(); if ( ! current_user_can( 'manage_options' ) || ! $screen || ! in_array( $screen->id, array( 'edit-' . Paygrab_Product::TYPE, 'paygrab_page_paygrab-orders' ), true ) ) { return; } $pay = ! Paygrab_Gateways::ready(); $law = (bool) Paygrab_Settings::missing_law_items(); if ( ! $pay && ! $law ) { return; } // Only what is still missing, with a link to that section. if ( $pay && $law ) { $text = Paygrab_Gateways::bank_only() ? __( 'PAYGRAB: enter the bank account and the legal notice to start selling.', 'paygrab' ) : __( 'PAYGRAB: set up a way to pay and the legal notice to start selling.', 'paygrab' ); } elseif ( $pay ) { $text = Paygrab_Gateways::bank_only() ? __( 'PAYGRAB: enter the bank account to start selling.', 'paygrab' ) : __( 'PAYGRAB: set up a way to pay to start selling.', 'paygrab' ); } else { $text = __( 'PAYGRAB: fill in the legal notice to start selling.', 'paygrab' ); } printf( '

%1$s %3$s

', esc_html( $text ), esc_url( admin_url( 'admin.php?page=' . self::SLUG ) . ( $pay && $law ? '' : ( $pay ? '#payment' : '#law' ) ) ), esc_html__( 'Open the settings', 'paygrab' ) ); } /** * A starting point for the terms of use (digital products). The seller edits it. * * @return string HTML. */ public static function terms_template() { $seller = (string) Paygrab_Settings::get( 'law_seller' ); $seller = '' !== $seller ? $seller : __( '[Seller]', 'paygrab' ); $parts = array( array( __( 'Article 1 (Scope)', 'paygrab' ), sprintf( /* translators: %s: seller name */ __( 'These terms set the conditions for using the digital product sales service provided by %s (“we”).', 'paygrab' ), $seller ) ), array( __( 'Article 2 (Membership)', 'paygrab' ), __( 'Anyone who wants to become a member agrees to these terms and applies in the way we set. Membership starts when registration is complete.', 'paygrab' ) ), array( __( 'Article 3 (Use of products)', 'paygrab' ), __( 'Products you buy are for your own use only. You may not give, sell, or share them with others, or put them where others can download them.', 'paygrab' ) ), array( __( 'Article 4 (Payment and delivery)', 'paygrab' ), __( 'Pay by the method and deadline shown on the final confirmation screen. You can download the product from My page after payment is confirmed, within the number of times and the period shown.', 'paygrab' ) ), array( __( 'Article 5 (Returns and cancellations)', 'paygrab' ), __( 'You can cancel an order before you pay. Because these are digital products, we do not accept returns or cancellations after payment.', 'paygrab' ) ), array( __( 'Article 6 (Prohibited acts)', 'paygrab' ), __( 'You may not break the law, infringe the rights of others, use another person’s account, or disturb the operation of the service.', 'paygrab' ) ), array( __( 'Article 7 (Leaving)', 'paygrab' ), __( 'You can leave at any time from My page. After you leave, you can no longer download the products you bought.', 'paygrab' ) ), array( __( 'Article 8 (Changes to these terms)', 'paygrab' ), __( 'We may change these terms when needed. The changed terms apply from the time they are shown on this site.', 'paygrab' ) ), ); $html = ''; foreach ( $parts as $part ) { $html .= '

' . esc_html( $part[0] ) . '

' . esc_html( $part[1] ) . '

'; } return $html; } }