( 'log' === $tab && $auto ) ? 10000 : 0 ) );
}
public function action_links( $links ) {
array_unshift( $links, 'Настройки' );
return $links;
}
public function register_settings() {
register_setting( 'rushop_modules', RuShop::MODULES_OPTION, array( 'sanitize_callback' => array( $this, 'sanitize_modules' ) ) );
if ( class_exists( 'RuShop_OneC' ) ) {
register_setting( 'rushop_onec', RuShop_OneC::OPTION, array( 'sanitize_callback' => array( $this, 'sanitize_onec' ) ) );
}
}
/* ---------- Сохранение ---------- */
public function sanitize_modules( $value ) {
$available = array();
foreach ( RuShop::modules() as $slug => $module ) {
if ( ! empty( $module['available'] ) ) {
$available[] = $slug;
}
}
return array_values( array_intersect( $available, array_map( 'sanitize_key', (array) $value ) ) );
}
public function sanitize_onec( $in ) {
$in = (array) $in;
$old = RuShop_OneC::settings();
$statuses = array_keys( wc_get_order_statuses() );
$flag = function ( $key ) use ( $in ) {
return empty( $in[ $key ] ) ? 0 : 1;
};
$meta_key = function ( $key ) use ( $in ) {
return (string) preg_replace( '/[^A-Za-z0-9_\-]/', '', (string) ( $in[ $key ] ?? '' ) );
};
// Пустое поле пароля — оставляем прежний.
if ( ! empty( $in['password'] ) ) {
$hash = wp_hash_password( (string) $in['password'] );
} else {
$hash = $in['password_hash'] ?? $old['password_hash'];
}
$events = array();
foreach ( array( 'cancelled', 'paid', 'shipped' ) as $event ) {
$value = (string) ( $in['status_events'][ $event ] ?? '' );
$events[ $event ] = in_array( $value, $statuses, true ) ? $value : '';
}
$map = array();
foreach ( (array) ( $in['status_map'] ?? array() ) as $row ) {
$from = sanitize_text_field( $row['from'] ?? '' );
$to = (string) ( $row['to'] ?? '' );
if ( '' !== $from && in_array( $to, $statuses, true ) ) {
$map[] = array( 'from' => $from, 'to' => $to );
}
}
$warehouses = array_map( 'strval', array_keys( (array) get_option( RuShop_OneC_Catalog::WAREHOUSES, array() ) ) );
return array(
'login' => sanitize_text_field( $in['login'] ?? '' ),
'password_hash' => (string) $hash,
'new_status' => 'draft' === ( $in['new_status'] ?? '' ) ? 'draft' : 'publish',
'update_name' => $flag( 'update_name' ),
'update_description' => $flag( 'update_description' ),
'import_images' => $flag( 'import_images' ),
'import_properties' => $flag( 'import_properties' ),
'name_from_full' => $flag( 'name_from_full' ),
'missing_action' => in_array( $in['missing_action'] ?? '', array( 'none', 'draft', 'outofstock', 'trash' ), true ) ? $in['missing_action'] : 'draft',
'batch_size' => min( 1000, max( 1, absint( $in['batch_size'] ?? 50 ) ) ),
'price_type' => sanitize_text_field( $in['price_type'] ?? '' ),
'old_price_type' => sanitize_text_field( $in['old_price_type'] ?? '' ),
'warehouses' => array_values( array_intersect( $warehouses, array_map( 'strval', (array) ( $in['warehouses'] ?? array() ) ) ) ),
'export_statuses' => array_values( array_intersect( $statuses, (array) ( $in['export_statuses'] ?? array() ) ) ),
'export_limit' => min( 1000, max( 1, absint( $in['export_limit'] ?? 200 ) ) ),
'orders_schema' => in_array( $in['orders_schema'] ?? '', array( 'auto', '2.08', '3.1' ), true ) ? $in['orders_schema'] : 'auto',
'sale_init_version' => in_array( $in['sale_init_version'] ?? '', array( '2.05', '2.08', '2.09', '2.10', '3.1', 'request' ), true ) ? $in['sale_init_version'] : '2.10',
'inn_meta' => $meta_key( 'inn_meta' ),
'kpp_meta' => $meta_key( 'kpp_meta' ),
'update_order_items' => $flag( 'update_order_items' ),
'status_events' => $events,
'status_map' => $map,
'record_payments' => $flag( 'record_payments' ),
'log_mode' => in_array( $in['log_mode'] ?? '', array( 'basic', 'products', 'debug' ), true ) ? $in['log_mode'] : 'products',
'log_days' => min( 90, max( 1, absint( $in['log_days'] ?? 14 ) ) ),
);
}
/* ---------- Страница ---------- */
public function render() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( 'Нет доступа.' );
}
$tabs = array( 'modules' => 'Модули' );
if ( class_exists( 'RuShop_OneC' ) ) {
$tabs['onec'] = 'Обмен с 1С';
$tabs['log'] = 'Журнал обмена';
}
$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'modules'; // phpcs:ignore
if ( ! isset( $tabs[ $tab ] ) ) {
$tab = 'modules';
}
echo '
RuShop для WooCommerce
';
settings_errors();
if ( 'onec' === $tab ) {
$this->render_onec();
} elseif ( 'log' === $tab ) {
$this->render_log();
} else {
$this->render_modules();
}
echo '
';
}
private function render_modules() {
$enabled = RuShop::enabled_modules();
echo '';
}
private function render_onec() {
$s = RuShop_OneC::settings();
$last = get_option( 'rushop_1c_last_exchange' );
$types = (array) get_option( RuShop_OneC_Catalog::PRICE_TYPES, array() );
$warehouses = (array) get_option( RuShop_OneC_Catalog::WAREHOUSES, array() );
$statuses = wc_get_order_statuses();
?>
Подключение в 1С
Адрес сайта для узла обмена:
Логин и пароль — те, что вы зададите ниже.
Последний обмен:
Открыть журнал обмена
run_id : 'all';
if ( isset( $_GET['run'] ) ) { // phpcs:ignore
$run = (string) preg_replace( '/[^a-z0-9]/i', '', wp_unslash( $_GET['run'] ) ); // phpcs:ignore
$run = '' === $run ? 'all' : $run;
}
$level = isset( $_GET['level'] ) ? sanitize_key( wp_unslash( $_GET['level'] ) ) : ''; // phpcs:ignore
if ( ! in_array( $level, array( '', 'problems', 'error', 'warning', 'info', 'debug' ), true ) ) {
$level = '';
}
$kind = isset( $_GET['kind'] ) ? sanitize_key( wp_unslash( $_GET['kind'] ) ) : ''; // phpcs:ignore
if ( ! isset( RuShop_OneC_Log::kinds()[ $kind ] ) ) {
$kind = '';
}
return array(
'run' => $run,
'level' => $level,
'kind' => $kind,
's' => isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '', // phpcs:ignore
'dir' => ( isset( $_GET['dir'] ) && 'asc' === $_GET['dir'] ) ? 'asc' : 'desc', // phpcs:ignore
);
}
private function log_object_link( $row ) {
$id = (int) $row->object_id;
if ( ! $id ) {
return '';
}
$url = '';
$label = '';
if ( in_array( $row->object_type, array( 'product', 'variation' ), true ) ) {
$post_id = 'variation' === $row->object_type ? (int) wp_get_post_parent_id( $id ) : $id;
$url = $post_id ? get_edit_post_link( $post_id, '' ) : '';
$label = ( 'variation' === $row->object_type ? 'Вариация #' : 'Товар #' ) . $id;
} elseif ( 'order' === $row->object_type ) {
$order = wc_get_order( $id );
$url = $order ? $order->get_edit_order_url() : '';
$label = 'Заказ #' . $id;
} elseif ( 'category' === $row->object_type ) {
$url = get_edit_term_link( $id, 'product_cat' );
$label = 'Категория #' . $id;
}
if ( '' === $label ) {
return '';
}
return $url ? '' . esc_html( $label ) . '' : esc_html( $label );
}
private function render_log() {
$runs = RuShop_OneC_Log::runs( 30 );
$f = $this->log_filters( $runs );
$page = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1; // phpcs:ignore
$per = 100;
$auto = ! empty( $_GET['auto'] ); // phpcs:ignore
$levels = RuShop_OneC_Log::level_labels();
$kinds = RuShop_OneC_Log::kinds();
list( $rows, $total ) = RuShop_OneC_Log::query( $f, $page, $per, $f['dir'] );
list( $summary ) = RuShop_OneC_Log::query( array( 'run' => $f['run'], 'kind' => 'summary' ), 1, 12, 'DESC' );
$args = array(
'page' => 'rushop',
'tab' => 'log',
'run' => $f['run'],
'level' => $f['level'],
'kind' => $f['kind'],
's' => $f['s'],
'dir' => $f['dir'],
);
$base = add_query_arg( $args, admin_url( 'admin.php' ) );
// Итоги выбранного обмена.
if ( $summary ) {
echo 'Итоги
';
foreach ( array_reverse( $summary ) as $row ) {
printf(
'- %s %s
',
esc_html( $row->message ),
esc_html( get_date_from_gmt( $row->logged_at, 'd.m H:i:s' ) )
);
}
echo '
';
}
// Фильтры.
echo '';
$export = wp_nonce_url( add_query_arg( array_merge( $args, array( 'action' => 'rushop_1c_log_export' ) ), admin_url( 'admin-post.php' ) ), 'rushop_1c_log' );
printf(
'Записей: %d. %s · Скачать CSV
',
(int) $total,
esc_url( $auto ? remove_query_arg( 'auto', $base ) : add_query_arg( 'auto', 1, $base ) ),
$auto ? 'Выключить автообновление' : 'Включить автообновление (каждые 10 с)',
esc_url( $export )
);
// Таблица.
if ( ! $rows ) {
echo 'Записей пока нет. Они появятся, когда 1С начнёт обмен с сайтом.
';
} else {
echo '| Время | Уровень | Событие | Что произошло | Объект |
';
foreach ( $rows as $row ) {
$details = '';
if ( '' !== (string) $row->data ) {
$decoded = json_decode( $row->data, true );
$pretty = is_array( $decoded ) ? wp_json_encode( $decoded, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ) : $row->data;
$details = 'подробности
' . esc_html( $pretty ) . '
';
}
printf(
'| %s | %s | %s | %s%s%s | %s |
',
esc_html( get_date_from_gmt( $row->logged_at, 'd.m H:i:s' ) ),
esc_attr( $row->level ),
esc_html( $levels[ $row->level ] ?? $row->level ),
esc_html( $kinds[ $row->kind ] ?? $row->kind ),
esc_html( $row->message ),
'' !== (string) $row->ref && ! in_array( $row->kind, array( 'request', 'summary' ), true ) ? '
Ид 1С: ' . esc_html( $row->ref ) . '' : '',
$details, // phpcs:ignore
$this->log_object_link( $row ) // phpcs:ignore
);
}
echo '
';
$pages = (int) ceil( $total / $per );
if ( $pages > 1 ) {
echo '' . wp_kses_post( (string) paginate_links( array(
'base' => add_query_arg( 'paged', '%#%', $base ),
'format' => '',
'current' => $page,
'total' => $pages,
) ) ) . '
';
}
}
// Очистка.
echo '';
}
public function log_export() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( 'Нет доступа.' );
}
check_admin_referer( 'rushop_1c_log' );
if ( ! class_exists( 'RuShop_OneC_Log' ) ) {
wp_die( 'Модуль «Обмен с 1С» не включён.' );
}
$f = $this->log_filters( array() );
nocache_headers();
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename="rushop-1c-log-' . gmdate( 'Ymd-His' ) . '.csv"' );
$out = fopen( 'php://output', 'w' );
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fwrite, WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- потоковый вывод CSV в php://output, не файл на диске.
fwrite( $out, "\xEF\xBB\xBF" ); // BOM, чтобы Excel не ломал кириллицу
fputcsv( $out, array( 'Время', 'Обмен', 'Уровень', 'Событие', 'Тип объекта', 'ID объекта', 'Ид 1С', 'Сообщение', 'Данные' ), ';' );
$cell = function ( $value ) {
$value = (string) $value;
return preg_match( '/^[=+\-@\t\r]/', $value ) ? "'" . $value : $value; // защита от формул в Excel
};
$offset = 0;
do {
$rows = RuShop_OneC_Log::rows( $f, 2000, $offset, 'ASC' );
foreach ( $rows as $r ) {
fputcsv( $out, array(
get_date_from_gmt( $r->logged_at, 'Y-m-d H:i:s' ),
$r->run_id,
$r->level,
$r->kind,
$r->object_type,
$r->object_id,
$cell( $r->ref ),
$cell( $r->message ),
$cell( $r->data ),
), ';' );
}
$offset += 2000;
} while ( 2000 === count( $rows ) && $offset < 200000 );
fclose( $out );
// phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fwrite, WordPress.WP.AlternativeFunctions.file_system_operations_fclose
exit;
}
public function log_clear() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( 'Нет доступа.' );
}
check_admin_referer( 'rushop_1c_log_clear' );
if ( class_exists( 'RuShop_OneC_Log' ) ) {
RuShop_OneC_Log::clear();
}
wp_safe_redirect( admin_url( 'admin.php?page=rushop&tab=log' ) );
exit;
}
/* ---------- Поля формы ---------- */
private function name( $key ) {
return esc_attr( RuShop_OneC::OPTION . '[' . $key . ']' );
}
private function row( $label, $html, $description = '' ) {
printf(
'| %s | %s%s |
',
esc_html( $label ),
$html, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- собрано из экранированных частей
'' !== $description ? '' . $description . '
' : '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- подсказки заданы в коде
);
}
private function text( array $s, $key, $placeholder = '', $autocomplete = '' ) {
return sprintf(
'',
$this->name( $key ),
esc_attr( (string) $s[ $key ] ),
esc_attr( $placeholder ),
$autocomplete ? ' autocomplete="' . esc_attr( $autocomplete ) . '"' : ''
);
}
private function checkbox( array $s, $key, $label ) {
return sprintf(
'',
$this->name( $key ),
checked( ! empty( $s[ $key ] ), true, false ),
esc_html( $label )
);
}
private function select( array $s, $key, array $options ) {
$html = sprintf( '';
}
private function status_select( $name, $current, array $statuses ) {
$html = sprintf( '';
}
}