آموزش انتقال دستهبندی محصولات ووکامرس از یک سایت به سایت دیگر
چرا انتقال مرحلهای دستهبندیها اهمیت دارد؟
در فروشگاههایی که تعداد دستهبندی زیادی دارند، انجام همه عملیات در یک درخواست ممکن است باعث فشار زیاد روی سرور شود. در چنین شرایطی احتمال خطای تایماوت، قطع شدن فرایند انتقال یا کند شدن پیشخوان وردپرس افزایش پیدا میکند.
به همین دلیل، در کد اختصاصی انتقال دستهبندی محصولات، دادهها بهصورت مرحلهای پردازش میشوند. برای مثال، در هر مرحله ۲۰ دستهبندی از سایت مبدأ دریافت شده و در سایت مقصد ثبت یا بررسی میشوند. بعد از اتمام هر مرحله، درخواست بعدی بهصورت خودکار ارسال میشود تا دستههای صفحه بعد نیز منتقل شوند.
استفاده از AJAX در این فرایند باعث میشود کاربر بدون رفرش صفحه، روند انتقال را مشاهده کند. نوار پیشرفت، شماره صفحه فعلی، تعداد دستههای جدید، تعداد دستههای تکراری و تعداد کل اطلاعات پردازششده، به مدیر سایت کمک میکند تا کنترل کاملی روی عملیات داشته باشد.
حفظ ساختار دستههای والد و فرزند
یکی از مهمترین بخشهای انتقال دستهبندی محصولات ووکامرس، حفظ ساختار والد و فرزند است. برای مثال ممکن است دسته «لوازم جانبی موبایل» دارای زیرمجموعههایی مانند «قاب گوشی»، «شارژر»، «گلس» و «هندزفری» باشد. اگر هنگام انتقال، ارتباط دستههای فرزند با دسته والد حفظ نشود، همه دستهها بهصورت مستقل نمایش داده خواهند شد و ساختار فروشگاه بههم میریزد.
در این کد، شناسه دستهبندی سایت مبدأ به شناسه دستهبندی ایجادشده در سایت مقصد متصل میشود. به این ترتیب، زمانی که کد به یک زیرمجموعه میرسد، ابتدا بررسی میکند دسته والد آن قبلاً منتقل شده است یا خیر. در صورت وجود دسته والد، زیرمجموعه با شناسه درست ثبت میشود تا ساختار درختی دستهبندیها حفظ شود.
جلوگیری از ثبت دستهبندی تکراری
یکی دیگر از قابلیتهای مهم این سیستم، جلوگیری از ثبت دستههای تکراری است. قبل از ایجاد هر دسته جدید، نامک دستهبندی در فروشگاه مقصد بررسی میشود. اگر دستهای با همان نامک وجود داشته باشد، سیستم آن را بهعنوان دسته موجود شناسایی میکند و دوباره ایجاد نخواهد کرد.
نمایش وضعیت انتقال در پیشخوان وردپرس
برای تجربه کاربری بهتر، یک پنجره اختصاصی در بخش افزودن دستهبندی محصولات قرار داده میشود. مدیر سایت فقط کافی است آدرس سایت مبدأ را وارد کند و عملیات انتقال را شروع نماید. در طول انجام فرایند، پیامهای وضعیت و نوار پیشرفت نمایش داده میشوند.
همچنین در صورت وجود مشکل در ارتباط با سایت مبدأ، غیرفعال بودن API، وارد کردن آدرس نامعتبر یا پایان زمان پاسخگویی سرور، پیام خطای مناسب به کاربر نمایش داده میشود. این موضوع باعث میشود عیبیابی فرایند انتقال بسیار سادهتر باشد.
کد انتقال دستهبندی محصولات ووکامرس
در ادامه این بخش، کد اختصاصی انتقال دستهبندی محصولات ووکامرس قرار میگیرد. این کد شامل رابط کاربری در مدیریت وردپرس، درخواستهای AJAX، دریافت اطلاعات از سایت مبدأ، ایجاد دستهبندی در سایت مقصد، جلوگیری از ثبت اطلاعات تکراری و حفظ ارتباط دستههای والد و فرزند است.
پیش از اجرای کد، بهتر است از سایت مقصد بکاپ کامل تهیه کنید. همچنین باید مطمئن شوید که آدرس واردشده به دامنه صحیح فروشگاه مبدأ اشاره دارد. پیشنهاد میشود ابتدا کد را روی سایت تست یا نسخه آزمایشی اجرا کنید و بعد از بررسی نتیجه، آن را روی سایت اصلی استفاده نمایید.
کد استفاده شده در این پروژه
function wccm_user_can_import() {
return current_user_can('manage_woocommerce') || current_user_can('manage_categories');
}
/**
* نمایش دکمه در بخش افزودن دستهبندی
*/
add_action('product_cat_add_form_fields', function () {
if (!wccm_user_can_import()) {
return;
}
?>
<div class="form-field term-group wccm-import-box" style="margin-top: 16px; padding: 14px; border: 1px solid #dbeafe; background: #f8fbff; border-radius: 12px;">
<label style="display:block; margin-bottom:10px; font-weight:700; color:#1d4ed8;">
انتقال دستهبندیها
</label>
<p style="margin:0 0 12px; line-height:1.8; color:#475569;">
با این ابزار میتوانید دستهبندیهای محصولات را از یک سایت دیگر بهصورت مرحلهای وارد کنید.
</p>
<button type="button" class="button button-primary button-large" id="wccm-open-modal" style="border-radius:10px; padding:0 18px; box-shadow:0 4px 14px rgba(34,113,177,.22);">
باز کردن پنجره انتقال
</button>
</div>
<div id="wccm-modal" dir="rtl" style="display:none;">
<div class="wccm-modal-overlay"></div>
<div class="wccm-modal-box">
<button type="button" id="wccm-close-modal" class="wccm-close" aria-label="بستن">×</button>
<div class="wccm-modal-icon">⇄</div>
<h2>انتقال دستهبندی محصولات</h2>
<p class="wccm-modal-description">
آدرس سایت مبدأ را وارد کنید تا دستهبندیهای محصولات بهصورت مرحلهای منتقل شوند.
</p>
<label for="wccm-source-url">آدرس سایت مبدأ</label>
<input
type="url"
id="wccm-source-url"
placeholder="https://example.com"
autocomplete="off"
>
<small>مثال: https://example.com</small>
<div id="wccm-status" class="wccm-status" style="display:none;"></div>
<div class="wccm-modal-actions">
<button type="button" class="button" id="wccm-cancel-import">انصراف</button>
<button type="button" class="button button-primary" id="wccm-start-import">شروع انتقال</button>
</div>
</div>
</div>
<?php
});
/**
* افزودن CSS و JS فقط در صفحه دستهبندی محصولات
*/
add_action('admin_enqueue_scripts', function ($hook) {
if (
!isset($_GET['taxonomy']) ||
sanitize_key(wp_unslash($_GET['taxonomy'])) !== 'product_cat'
) {
return;
}
if (!wccm_user_can_import()) {
return;
}
wp_enqueue_script('jquery');
wp_localize_script('jquery', 'WCCM_DATA', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wccm_import_nonce'),
]);
$css = <<<'CSS'
#wccm-modal {
position: fixed;
z-index: 999999;
inset: 0;
}
.wccm-modal-overlay {
position: absolute;
inset: 0;
background: rgba(15, 23, 42, .68);
backdrop-filter: blur(3px);
}
.wccm-modal-box {
position: relative;
z-index: 2;
width: calc(100% - 32px);
max-width: 520px;
margin: 9vh auto 0;
padding: 32px;
background: #ffffff;
border-radius: 18px;
box-shadow: 0 20px 70px rgba(0, 0, 0, .25);
box-sizing: border-box;
animation: wccm-modal-show .25s ease;
}
@keyframes wccm-modal-show {
from { opacity: 0; transform: translateY(-20px) scale(.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.wccm-close {
position: absolute;
top: 12px;
left: 16px;
width: 34px;
height: 34px;
border: 0;
border-radius: 50%;
background: #f1f5f9;
color: #475569;
font-size: 25px;
line-height: 30px;
cursor: pointer;
}
.wccm-close:hover {
background: #fee2e2;
color: #b91c1c;
}
.wccm-modal-icon {
display: flex;
align-items: center;
justify-content: center;
width: 64px;
height: 64px;
margin: 0 auto 16px;
border-radius: 18px;
background: linear-gradient(135deg, #2271b1, #135e96);
color: #ffffff;
font-size: 34px;
box-shadow: 0 8px 20px rgba(34, 113, 177, .28);
}
.wccm-modal-box h2 {
margin: 0 0 10px;
text-align: center;
font-size: 23px;
color: #1e293b;
}
.wccm-modal-description {
margin: 0 auto 24px;
max-width: 410px;
color: #64748b;
text-align: center;
line-height: 1.9;
}
.wccm-modal-box label {
display: block;
margin-bottom: 8px;
color: #334155;
font-weight: 600;
}
#wccm-source-url {
width: 100%;
height: 45px;
padding: 0 14px;
border: 1px solid #cbd5e1;
border-radius: 9px;
box-sizing: border-box;
font-size: 14px;
direction: ltr;
text-align: left;
}
#wccm-source-url:focus {
border-color: #2271b1;
box-shadow: 0 0 0 3px rgba(34, 113, 177, .13);
outline: none;
}
.wccm-modal-box small {
display: block;
margin-top: 7px;
color: #94a3b8;
direction: rtl;
}
.wccm-modal-actions {
display: flex;
justify-content: flex-start;
gap: 8px;
margin-top: 24px;
}
.wccm-modal-actions .button {
min-width: 105px;
height: 40px;
border-radius: 8px;
}
.wccm-status {
margin-top: 22px;
padding: 15px;
border: 1px solid #dbeafe;
border-radius: 10px;
background: #f8fbff;
color: #334155;
line-height: 1.9;
}
.wccm-progress-wrapper {
width: 100%;
height: 25px;
margin: 12px 0;
overflow: hidden;
border-radius: 20px;
background: #e2e8f0;
}
.wccm-progress-bar {
display: flex;
align-items: center;
justify-content: center;
width: 0;
height: 100%;
border-radius: 20px;
background: linear-gradient(90deg, #2271b1, #16a34a);
color: #ffffff;
font-size: 12px;
font-weight: bold;
transition: width .45s ease;
}
.wccm-success {
margin-top: 10px;
color: #15803d;
font-weight: bold;
}
.wccm-error {
color: #b91c1c;
line-height: 1.9;
}
body.wccm-modal-open {
overflow: hidden;
}
.wccm-import-box {
border: 1px solid #dbeafe !important;
background: linear-gradient(180deg, #f8fbff, #ffffff) !important;
border-radius: 14px !important;
padding: 16px !important;
box-shadow: 0 10px 30px rgba(15, 23, 42, .06);
}
.wccm-import-box .button-primary {
background: linear-gradient(135deg, #2271b1, #135e96);
border-color: #135e96;
}
CSS;
wp_register_style('wccm-inline-style', false);
wp_enqueue_style('wccm-inline-style');
wp_add_inline_style('wccm-inline-style', $css);
$js = <<<'JS'
jQuery(function($) {
let running = false;
const $modal = $('#wccm-modal');
const $sourceUrl = $('#wccm-source-url');
const $status = $('#wccm-status');
const $startButton = $('#wccm-start-import');
function openModal() {
$modal.fadeIn(180);
$('body').addClass('wccm-modal-open');
setTimeout(function() {
$sourceUrl.trigger('focus');
}, 200);
}
function closeModal() {
if (running) return;
$modal.fadeOut(180);
$('body').removeClass('wccm-modal-open');
}
$('#wccm-open-modal').on('click', function() {
openModal();
});
$('#wccm-close-modal, #wccm-cancel-import').on('click', function() {
closeModal();
});
$('.wccm-modal-overlay').on('click', function() {
closeModal();
});
$(document).on('keydown', function(event) {
if (event.key === 'Escape' && !running) {
closeModal();
}
});
$startButton.on('click', function() {
if (running) return;
let sourceUrl = $sourceUrl.val().trim();
if (!sourceUrl) {
showError('لطفاً آدرس سایت مبدأ را وارد کنید.');
$sourceUrl.trigger('focus');
return;
}
if (!/^https?:\/\//i.test(sourceUrl)) {
sourceUrl = 'https://' + sourceUrl;
}
sourceUrl = sourceUrl.replace(/\/+$/, '');
try {
const parsedUrl = new URL(sourceUrl);
if (!parsedUrl.hostname) throw new Error();
sourceUrl = parsedUrl.origin;
} catch (error) {
showError('آدرس واردشده معتبر نیست. لطفاً آدرس کامل سایت را وارد کنید.');
$sourceUrl.trigger('focus');
return;
}
running = true;
$startButton.prop('disabled', true).text('در حال انتقال...');
$('#wccm-cancel-import, #wccm-close-modal').prop('disabled', true);
showStatus('در حال آمادهسازی انتقال...', 0, {
page: 0,
received: 0,
created: 0,
duplicated: 0,
processed: 0
});
runImport(sourceUrl, 1);
});
function runImport(sourceUrl, page) {
$.ajax({
url: WCCM_DATA.ajax_url,
type: 'POST',
dataType: 'json',
timeout: 150000,
data: {
action: 'wccm_import_categories',
nonce: WCCM_DATA.nonce,
source_url: sourceUrl,
page: page
},
success: function(response) {
if (!response.success) {
showError(
response.data && response.data.message
? response.data.message
: 'خطایی هنگام انتقال رخ داد.'
);
finishImport();
return;
}
const data = response.data;
const progress = Math.max(0, Math.min(100, parseInt(data.progress || 0)));
showStatus(data.message, progress, data);
if (!data.finished) {
setTimeout(function() {
runImport(sourceUrl, parseInt(data.next_page, 10));
}, 800);
} else {
showStatus('انتقال تمام شد.', 100, data, true);
finishImport();
}
},
error: function(xhr, textStatus) {
let message = 'ارتباط با سرور برقرار نشد.';
if (textStatus === 'timeout') {
message = 'زمان پاسخگویی سرور تمام شد.';
}
if (
xhr.responseJSON &&
xhr.responseJSON.data &&
xhr.responseJSON.data.message
) {
message = xhr.responseJSON.data.message;
}
showError(message);
finishImport();
}
});
}
function showStatus(message, progress, data, finished) {
data = data || {};
$status
.removeClass('wccm-error')
.show()
.html(
'<div><strong>' + escapeHtml(message) + '</strong></div>' +
'<div class="wccm-progress-wrapper">' +
'<div class="wccm-progress-bar" style="width:' + progress + '%;">' +
progress + '%' +
'</div>' +
'</div>' +
'<div>' +
'صفحه فعلی: ' + (data.page || 0) +
'<br>دریافتشده در این مرحله: ' + (data.received || 0) +
'<br>دستههای جدید: ' + (data.created || 0) +
'<br>دستههای تکراری: ' + (data.duplicated || 0) +
'<br>کل پردازششده: ' + (data.processed || 0) +
'</div>' +
(
finished
? '<div class="wccm-success">✅ انتقال همه دستهبندیها با موفقیت تمام شد.</div>'
: ''
)
);
}
function showError(message) {
$status
.addClass('wccm-error')
.show()
.html(
'<strong>خطا:</strong><br>' + message
);
}
function finishImport() {
running = false;
$startButton.prop('disabled', false).text('شروع دوباره انتقال');
$('#wccm-cancel-import, #wccm-close-modal').prop('disabled', false);
}
function escapeHtml(text) {
return $('<div>').text(text || '').html();
}
});
JS;
wp_add_inline_script('jquery', $js);
});
/**
* AJAX انتقال دستهبندیها
*/
add_action('wp_ajax_wccm_import_categories', function () {
if (!wccm_user_can_import()) {
wp_send_json_error([
'message' => 'شما اجازه انجام این عملیات را ندارید.'
]);
}
check_ajax_referer('wccm_import_nonce', 'nonce');
$source_url = isset($_POST['source_url'])
? esc_url_raw(wp_unslash($_POST['source_url']))
: '';
$page = isset($_POST['page'])
? absint($_POST['page'])
: 1;
if (empty($source_url)) {
wp_send_json_error([
'message' => 'آدرس سایت مبدأ وارد نشده است.'
]);
}
$source_url = untrailingslashit($source_url);
$api_url = add_query_arg([
'per_page' => 20,
'page' => max(1, $page),
'orderby' => 'id',
'order' => 'asc',
], $source_url . '/wp-json/wp/v2/product_cat');
$response = wp_remote_get($api_url, [
'timeout' => 120,
'redirection' => 5,
'sslverify' => false,
'headers' => [
'Accept' => 'application/json',
'User-Agent' => 'WordPress Category Migrator',
],
]);
if (is_wp_error($response)) {
wp_send_json_error([
'message' => 'ارتباط با سایت مبدأ برقرار نشد.<br><br>' . esc_html($response->get_error_message()) . '<br><br>آدرس درخواستشده:<br>' . esc_html($api_url)
]);
}
$status_code = wp_remote_retrieve_response_code($response);
$body = wp_remote_retrieve_body($response);
$categories = json_decode($body, true);
if ($status_code < 200 || $status_code >= 300) {
wp_send_json_error([
'message' => 'API سایت مبدأ خطا برگرداند.<br>کد پاسخ: ' . esc_html($status_code)
]);
}
if (!is_array($categories)) {
wp_send_json_error([
'message' => 'پاسخ API معتبر نیست.'
]);
}
$total_items = (int) wp_remote_retrieve_header($response, 'x-wp-total');
$total_pages = (int) wp_remote_retrieve_header($response, 'x-wp-totalpages');
if (!$total_pages) {
$total_pages = $page;
}
$mapping = get_option('wccm_category_mapping', []);
if (!is_array($mapping)) {
$mapping = [];
}
$created = 0;
$duplicated = 0;
$errors = 0;
$processed = (int) get_option('wccm_processed_total', 0);
foreach ($categories as $category) {
if (empty($category['id']) || empty($category['name'])) {
continue;
}
$source_id = absint($category['id']);
$name = sanitize_text_field($category['name']);
$slug = !empty($category['slug']) ? sanitize_title($category['slug']) : sanitize_title($name);
if (isset($mapping[$source_id])) {
$duplicated++;
$processed++;
continue;
}
$parent_id = 0;
if (!empty($category['parent'])) {
$source_parent_id = absint($category['parent']);
if (isset($mapping[$source_parent_id])) {
$parent_id = absint($mapping[$source_parent_id]);
}
}
$existing = get_term_by('slug', $slug, 'product_cat');
if ($existing && !is_wp_error($existing)) {
$target_id = (int) $existing->term_id;
$duplicated++;
} else {
$description = '';
if (
isset($category['description']['rendered']) &&
is_string($category['description']['rendered'])
) {
$description = wp_kses_post($category['description']['rendered']);
}
$inserted = wp_insert_term($name, 'product_cat', [
'slug' => $slug,
'description' => $description,
'parent' => $parent_id,
]);
if (is_wp_error($inserted)) {
$errors++;
continue;
}
$target_id = (int) $inserted['term_id'];
$created++;
}
$mapping[$source_id] = $target_id;
$processed++;
}
update_option('wccm_category_mapping', $mapping, false);
update_option('wccm_processed_total', $processed, false);
$finished = $page >= $total_pages;
if ($total_items > 0) {
$progress = round(($processed / $total_items) * 100);
$progress = min(100, max(0, $progress));
} else {
$progress = $finished ? 100 : 0;
}
if ($finished) {
$progress = 100;
delete_option('wccm_category_mapping');
delete_option('wccm_processed_total');
}
wp_send_json_success([
'page' => $page,
'next_page' => $page + 1,
'received' => count($categories),
'created' => $created,
'duplicated' => $duplicated,
'errors' => $errors,
'processed' => $processed,
'total_items' => $total_items,
'progress' => $progress,
'finished' => $finished,
'message' => $finished ? 'انتقال تمام شد.' : 'در حال انتقال دستهبندیها...',
]);
});
راهنمای گامبهگام: چگونه این کد را به سایت وردپرسی خود اضافه کنیم؟
برای اضافه کردن این کد به وردپرس، چندین روش وجود دارد. در ادامه ایمنترین و استانداردترین روشها را با هم بررسی میکنیم:
روش اول: ویرایش از طریق مدیریت فایل هاست (روش حرفهای)
گاهی اوقات ممکن است دسترسی به ویرایشگر وردپرس (Theme File Editor) به دلایل امنیتی توسط توسعهدهنده قالب بسته شده باشد، یا شاید ترجیح میدهید به جای محیط وردپرس، مستقیماً با فایلهای سرور کار کنید. در این صورت، استفاده از File Manager هاست بهترین راهکار است.
گامهای اجرای کار در هاست (cPanel یا DirectAdmin):
- ورود به هاست: ابتدا وارد پنل مدیریت هاست خود (cPanel یا DirectAdmin) شوید.
- باز کردن فایل منیجر: روی گزینه File Manager کلیک کنید.
- مسیریابی: به پوشه ریشه سایت خود (معمولاً
public_html) بروید. سپس مسیر زیر را دنبال کنید:wp-content>themes>your-theme-name(نکته: بهجایyour-theme-nameنام پوسته فعال سایت خود را انتخاب کنید. اگر از Child Theme استفاده میکنید، حتماً وارد پوشه فرزند شوید). - ویرایش فایل: فایل
functions.phpرا پیدا کنید. روی آن راستکلیک کرده و گزینه Edit را انتخاب کنید. - جایگذاری کد: کد مورد نظر خود را به انتهای فایل اضافه کنید.
- ذخیره: دکمه Save Changes را در بالای صفحه ویرایشگر بزنید.
روش دوم : اضافه کردن کد به فایل functions.php (روش سریع)
فایل functions.php قلب تپنده قالب شماست و کدهای شخصیسازی در آن قرار میگیرند.
- وارد پیشخوان وردپرس شوید: از منوی سمت راست به مسیر نمایش > ویرایشگر پرونده پوسته (Theme File Editor) بروید.
- فایل مناسب را انتخاب کنید: از ستون سمت چپ (بخش پروندههای پوسته)، فایل توابع پوسته (functions.php) را پیدا کرده و روی آن کلیک کنید.
- قرار دادن کد: به انتهای فایل بروید، یک خط جدید ایجاد کنید و کدهای بالا را دقیقاً در انتهای فایل پیست (Paste) کنید.
- ذخیره تغییرات: روی دکمه بهروزرسانی پرونده کلیک کنید.
⚠️ یک هشدار امنیتی بسیار مهم:
پیش از ویرایش فایلهای قالب، حتماً یک نسخه پشتیبان (Backup) از فایل تهیه کنید. همچنین اگر قالب خود را آپدیت کنید، تغییراتی که در فایل
functions.phpقالب اصلی اعمال کردهاید پاک خواهند شد. برای جلوگیری از این مشکل، پیشنهاد میکنیم از پوسته فرزند (Child Theme) استفاده کنید یا به سراغ روش دوم بروید.
روش سوم: استفاده از افزونههای مدیریت کدهای سفارشی (روش ایمن و بدون خطر)
اگر نمیخواهید فایلهای قالب را به صورت دستی ویرایش کنید و نگران به هم ریختن سایت یا پاک شدن کدها در آپدیتهای بعدی قالب هستید، این روش بهترین گزینه است.
- افزونه رایگان WPcode (Insert Headers and Footers) یا Code Snippets را از مخزن وردپرس نصب و فعال کنید.
- از منوی جدیدی که در پیشخوان اضافه شده، روی Add Snippet (افزودن کد جدید) کلیک کنید.
- یک عنوان دلخواه برای آن انتخاب کنید (مثلاً: نوار اعلان بالای هدر).
- نوع کد (Code Type) را روی حالت PHP Snippet قرار دهید.
- کدهای بالا را در باکس مربوطه کپی کنید.
- وضعیت را روی Active (فعال) قرار داده و دکمه Save را بزنید.
اگر به دنبال یادگیری اصولی طراحی و توسعه وردپرس هستی، پیشنهاد میکنم حتماً سری به آموزش رایگان وردپرس بزنی. همچنین اگر قصد یادگیری حرفهای و حضوری داری، دوره آموزش وردپرس در تبریز میتواند انتخاب بسیار خوبی برای شروع مسیر تخصصی تو باشد.
درباره میلاد علیزاده وکیلی
میلاد علیزاده هستم مدرس دوره های آموزش برنامه نویسی در شهر تبریز که فعالیت خودم رو از سال 95 در زمینه ی طراحی و میزبانی وبسایت شروع کردم. هدف من از راه اندازی این وبسایت ارائه ی آموزشی متفاوت همچنین اشتغال محور و پروژه محور هستش که هنرجو بعد از آموزش بتواند با دانش و تجربه ای کافی مستقیما وارد بازار کار شده و در زمینه ی برنامه نویسی کسب درامد کند.
نوشتههای بیشتر از میلاد علیزاده وکیلی

دیدگاهتان را بنویسید