container = $container; } /** * Hooks admin screens and WooCommerce integration. * * @since 1.0.0 */ public function register() { if ( ! is_admin() ) { return; } add_action( 'admin_menu', array( $this, 'menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'assets' ) ); add_action( 'admin_init', array( $this, 'maybe_redirect_wizard' ) ); add_action( 'add_meta_boxes', array( $this, 'order_metabox' ) ); add_filter( 'plugin_action_links_' . plugin_basename( PPL_AFW_FILE ), array( $this, 'action_links' ) ); } /** * HPOS-safe order edit URL. * * @since 1.0.0 * @param int $order_id Order id. * @return string */ public static function order_edit_url( $order_id ) { if ( class_exists( \Automattic\WooCommerce\Utilities\OrderUtil::class ) && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) { return admin_url( 'admin.php?page=wc-orders&action=edit&id=' . absint( $order_id ) ); } return admin_url( 'post.php?post=' . absint( $order_id ) . '&action=edit' ); } /** * Registers the top level menu and submenus. * * @since 1.0.0 */ public function menu() { add_menu_page( __( 'Affiliates', 'affireo-affiliates-for-woocommerce' ), __( 'Affiliates', 'affireo-affiliates-for-woocommerce' ), 'ppl_afw_manage_affiliates', 'ppl-afw', array( $this, 'render_overview' ), 'dashicons-groups', 56 ); $pages = array( array( __( 'Overview', 'affireo-affiliates-for-woocommerce' ), 'ppl_afw_manage_affiliates', 'ppl-afw', 'render_overview' ), array( __( 'All Affiliates', 'affireo-affiliates-for-woocommerce' ), 'ppl_afw_manage_affiliates', 'ppl-afw-affiliates', 'render_affiliates' ), array( __( 'Referrals', 'affireo-affiliates-for-woocommerce' ), 'ppl_afw_manage_affiliates', 'ppl-afw-referrals', 'render_referrals' ), array( __( 'Visits', 'affireo-affiliates-for-woocommerce' ), 'ppl_afw_manage_affiliates', 'ppl-afw-visits', 'render_visits' ), array( __( 'Payouts', 'affireo-affiliates-for-woocommerce' ), 'ppl_afw_manage_payouts', 'ppl-afw-payouts', 'render_payouts' ), ); $pages[] = array( __( 'Reports', 'affireo-affiliates-for-woocommerce' ), 'ppl_afw_view_reports', 'ppl-afw-reports', 'render_reports' ); $pages[] = array( __( 'Tools', 'affireo-affiliates-for-woocommerce' ), 'ppl_afw_manage_settings', 'ppl-afw-tools', 'render_tools' ); $pages[] = array( __( 'Settings', 'affireo-affiliates-for-woocommerce' ), 'ppl_afw_manage_settings', 'ppl-afw-settings', 'render_settings' ); /** * Filters the Affiliates submenu pages. * * Each entry is array( label, capability, slug, render method ). * * @since 1.0.0 * @param array[] $pages Submenu definitions. */ $pages = apply_filters( 'ppl_afw_admin_pages', $pages ); foreach ( $pages as $page ) { add_submenu_page( 'ppl-afw', $page[0], $page[0], $page[1], $page[2], array( $this, $page[3] ) ); } } /** * Adds a Settings link to the plugin row on the Plugins screen. * * @since 1.0.0 * @param string[] $links Existing action links. * @return string[] */ public function action_links( $links ) { array_unshift( $links, '' . esc_html__( 'Settings', 'affireo-affiliates-for-woocommerce' ) . '' ); return $links; } /** * Loads admin CSS and JavaScript on plugin screens. * * @since 1.0.0 * @param string $hook Current screen hook. */ public function assets( $hook ) { if ( false === strpos( $hook, 'ppl-afw' ) ) { return; } $css = PPL_AFW_DIR . 'assets/css/admin.css'; wp_enqueue_style( 'ppl-afw-admin', PPL_AFW_URL . 'assets/css/admin.css', array(), file_exists( $css ) ? md5_file( $css ) : PPL_AFW_VERSION ); /** * Filters the script dependencies of the admin script. * * @since 1.2.0 * @param string[] $deps Script handles. * @param string $hook Current screen hook. */ $deps = (array) apply_filters( 'ppl_afw_admin_script_deps', array( 'jquery', 'jquery-ui-sortable' ), $hook ); $js = PPL_AFW_DIR . 'assets/js/admin.js'; wp_enqueue_script( 'ppl-afw-admin', PPL_AFW_URL . 'assets/js/admin.js', $deps, file_exists( $js ) ? md5_file( $js ) : PPL_AFW_VERSION, true ); wp_localize_script( 'ppl-afw-admin', 'pplAfwAdmin', array( 'i18n' => array( 'chooseImage' => __( 'Select image', 'affireo-affiliates-for-woocommerce' ), 'useImage' => __( 'Use image', 'affireo-affiliates-for-woocommerce' ), ), ) ); } /** * Redirects to the setup wizard once after activation. * * @since 1.0.0 */ public function maybe_redirect_wizard() { if ( get_transient( 'ppl_afw_activation_redirect' ) && current_user_can( 'ppl_afw_manage_settings' ) && ! wp_doing_ajax() ) { delete_transient( 'ppl_afw_activation_redirect' ); if ( ! get_option( 'ppl_afw_setup_complete' ) ) { wp_safe_redirect( admin_url( 'admin.php?page=ppl-afw-settings&wizard=1' ) ); exit; } } } /** * Renders a view template. * * Each variable a view may use is assigned by name, so a view only ever * sees the values listed here. * * @since 1.0.0 * @param string $name View file name without extension. * @param array $vars Variables exposed to the view. */ private function view( $name, array $vars = array() ) { $container = $this->container; $affiliate = $vars['affiliate'] ?? null; $payout = $vars['payout'] ?? null; $totals = $vars['totals'] ?? array(); $series = $vars['series'] ?? array(); $counts = $vars['counts'] ?? array(); $leaders = $vars['leaders'] ?? array(); $from = $vars['from'] ?? ''; $to = $vars['to'] ?? ''; $status = $vars['status'] ?? ''; $statuses = $vars['statuses'] ?? array(); $tab = $vars['tab'] ?? ''; $s = $vars['s'] ?? array(); include PPL_AFW_DIR . 'includes/admin/views/' . sanitize_file_name( $name ) . '.php'; } /** * Overview dashboard. * * @since 1.0.0 */ public function render_overview() { $stats = $this->container->get( 'stats' ); $to = gmdate( 'Y-m-d' ); $from = gmdate( 'Y-m-d', time() - 29 * DAY_IN_SECONDS ); $this->view( 'overview', array( 'totals' => $stats->totals( 0, $from, $to ), 'series' => $stats->series( 0, $from, $to ), 'counts' => $this->container->get( 'affiliates.repository' )->status_counts(), 'leaders' => $stats->leaderboard( 30, 5 ), ) ); } /** * Affiliates list or detail screen. * * @since 1.0.0 */ public function render_affiliates() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- routing only. $preview_id = isset( $_GET['preview'] ) ? absint( wp_unslash( $_GET['preview'] ) ) : 0; if ( $preview_id && current_user_can( 'ppl_afw_impersonate_affiliates' ) ) { $affiliate = $this->container->get( 'affiliates.repository' )->find( $preview_id ); if ( $affiliate && 'active' === $affiliate->status ) { $this->render_preview( $affiliate ); return; } } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- routing only. $view_id = isset( $_GET['view'] ) ? absint( wp_unslash( $_GET['view'] ) ) : 0; if ( $view_id ) { $affiliate = $this->container->get( 'affiliates.repository' )->find( $view_id ); if ( $affiliate ) { $this->view( 'affiliate-detail', array( 'affiliate' => $affiliate ) ); return; } } $table = new Affiliates_Table( $this->container ); $table->prepare_items(); ?>


notices(); ?> views(); ?>
search_box( __( 'Search affiliates', 'affireo-affiliates-for-woocommerce' ), 'ppl-afw-affiliate' ); ?> display(); ?>
container->get( 'frontend' ); $portal->inline_style(); /** * Fires after the dashboard assets are enqueued, here for the * admin preview. * * @since 1.3.0 */ do_action( 'ppl_afw_portal_enqueue' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only tab choice. $tab = isset( $_GET['ppl_afw_tab'] ) ? sanitize_key( wp_unslash( $_GET['ppl_afw_tab'] ) ) : 'dashboard'; $user = get_userdata( (int) $affiliate->user_id ); ?>

display_name : '#' . $affiliate->id ) ); ?>


dashboard( $affiliate, $tab, admin_url( 'admin.php?page=ppl-afw-affiliates&preview=' . (int) $affiliate->id ), true ); ?>
container ); $table->prepare_items(); ?>


notices(); ?>
display(); ?>

container ); $table->prepare_items(); // The export carries the filters being viewed, so the CSV matches the list. $export_args = array( 'action' => 'ppl_afw_export', 'what' => 'visits', ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read only list filter. $converted = isset( $_GET['converted'] ) ? sanitize_key( wp_unslash( $_GET['converted'] ) ) : ''; if ( in_array( $converted, array( '0', '1' ), true ) ) { $export_args['converted'] = $converted; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read only list filter. $affiliate_id = isset( $_GET['affiliate_id'] ) ? absint( wp_unslash( $_GET['affiliate_id'] ) ) : 0; if ( $affiliate_id ) { $export_args['affiliate_id'] = $affiliate_id; } $export_url = wp_nonce_url( add_query_arg( $export_args, admin_url( 'admin-post.php' ) ), 'ppl_afw_export' ); ?>


display(); ?>
container->get( 'payouts.repository' )->find( $invoice_id ); if ( $payout ) { $this->view( 'payout-invoice', array( 'payout' => $payout ) ); return; } } $table = new Payouts_Table( $this->container ); $table->prepare_items(); $minimum = (float) $this->container->get( 'settings' )->get( 'payout_minimum' ); $eligible = count( $this->container->get( 'referrals.repository' )->payable_totals( $minimum ) ); $awaiting = $this->container->get( 'payouts.repository' )->query( array( 'status' => 'processing', 'per_page' => 1, ) )['total']; ?>


notices(); ?>

display(); ?>
$to ) { list( $from, $to ) = array( $to, $from ); } $statuses = array( 'pending' => __( 'Pending', 'affireo-affiliates-for-woocommerce' ), 'approved' => __( 'Approved', 'affireo-affiliates-for-woocommerce' ), 'paid' => __( 'Paid', 'affireo-affiliates-for-woocommerce' ), 'rejected' => __( 'Rejected', 'affireo-affiliates-for-woocommerce' ), 'refunded' => __( 'Refunded', 'affireo-affiliates-for-woocommerce' ), ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read only export filter. $status = isset( $_GET['status'] ) ? sanitize_key( wp_unslash( $_GET['status'] ) ) : ''; if ( ! isset( $statuses[ $status ] ) ) { $status = ''; } $stats = $this->container->get( 'stats' ); $this->view( 'reports', array( 'from' => $from, 'to' => $to, 'status' => $status, 'statuses' => $statuses, 'totals' => $stats->totals( 0, $from, $to ), 'series' => $stats->series( 0, $from, $to ), 'leaders' => $stats->leaderboard_range( $from, $to, 10 ), ) ); } /** * Tools screen: announcements and data exports. * * @since 1.0.0 */ public function render_tools() { $this->view( 'tools' ); } /** * Settings screen or setup wizard. * * @since 1.0.0 */ public function render_settings() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- routing only. if ( isset( $_GET['wizard'] ) ) { $this->view( 'wizard' ); return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- routing only. $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general'; $this->view( 'settings', array( 'tab' => $tab, 's' => $this->container->get( 'settings' )->all(), ) ); } /** * Prints one-shot admin notices carried on the URL. * * @since 1.0.0 */ private function notices() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only flags. if ( isset( $_GET['ppl_msg'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only flags. echo '

' . esc_html( sanitize_text_field( wp_unslash( $_GET['ppl_msg'] ) ) ) . '

'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only flags. if ( isset( $_GET['ppl_err'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only flags. echo '

' . esc_html( sanitize_text_field( wp_unslash( $_GET['ppl_err'] ) ) ) . '

'; } } /** * Adds the attributed affiliate metabox to orders. * * @since 1.0.0 */ public function order_metabox() { foreach ( array( 'shop_order', 'woocommerce_page_wc-orders' ) as $screen ) { add_meta_box( 'ppl_afw_order_affiliate', __( 'Affiliate attribution', 'affireo-affiliates-for-woocommerce' ), array( $this, 'render_order_metabox' ), $screen, 'side' ); } } /** * Renders the order metabox. * * @since 1.0.0 * @param mixed $post_or_order Post or order object. */ public function render_order_metabox( $post_or_order ) { $order = $post_or_order instanceof \WC_Order ? $post_or_order : wc_get_order( $post_or_order->ID ?? 0 ); if ( ! $order ) { return; } $affiliate_id = (int) $order->get_meta( '_ppl_afw_affiliate_id' ); if ( ! $affiliate_id ) { echo '

' . esc_html__( 'No affiliate attributed to this order.', 'affireo-affiliates-for-woocommerce' ) . '

'; } else { $affiliate = $this->container->get( 'affiliates.repository' )->find( $affiliate_id ); $user = $affiliate ? get_userdata( (int) $affiliate->user_id ) : null; $detail = admin_url( 'admin.php?page=ppl-afw-affiliates&view=' . $affiliate_id ); $referrals = admin_url( 'admin.php?page=ppl-afw-referrals&order_id=' . $order->get_id() ); echo '

' . esc_html( $user ? $user->display_name : '#' . $affiliate_id ) . ' (' . esc_html( $affiliate->slug ?? '' ) . ')

'; echo '

' . esc_html__( 'View commissions for this order', 'affireo-affiliates-for-woocommerce' ) . '

'; } /** * Fires at the end of the order's Affiliate attribution box. * * @since 1.3.0 * @param \WC_Order $order Order object. * @param int $affiliate_id Attributed affiliate id, 0 for none. */ do_action( 'ppl_afw_order_metabox', $order, $affiliate_id ); } }