مثالهای کامل نشاندهنده نحوه ترکیب چند هوک LA24Core در یک افزونه CRM کارآمد.
مثال ۱: پلاگین کامل با منو، صفحه و دسترسی
این مثال یک پلاگین مستقل میسازد که بخش «معاملات مشتری» را به CRM اضافه میکند.
**ساختار پلاگین:**
javascript
my-deals-plugin/
├── my-deals-plugin.php
├── includes/
│ ├── class-deals-hook.php
│ ├── controllers/
│ │ └── DealsList.php
│ └── settings/
│ └── DealsSettings.php
└── templates/
└── deals-list.php
**`my-deals-plugin.php`:**
php
<?php
/**
* Plugin Name: معاملات مشتری
* Description: اضافه کردن ماژول معاملات به LA24Core CRM
* Version: 1.0.0
* Text Domain: my-deals
*/
defined( 'ABSPATH' ) || exit;
define( 'MY_DEALS_PATH', plugin_dir_path( __FILE__ ) );
define( 'MY_DEALS_URL', plugin_dir_url( __FILE__ ) );
add_action( 'plugins_loaded', function () {
if ( ! function_exists( 'la24_get_option' ) ) return;
require_once MY_DEALS_PATH . 'includes/class-deals-hook.php';
new Deals_Hook();
} );
**`includes/class-deals-hook.php`:**
php
<?php
defined( 'ABSPATH' ) || exit;
class Deals_Hook {
public function __construct() {
add_action( 'la24_dashboard_menu_items', array( $this, 'register_menu' ) );
add_filter( 'la24_endpoints', array( $this, 'register_endpoint' ), 10 );
add_filter( 'la24_capability', array( $this, 'register_capability' ), 10 );
add_filter( 'la24_groups_permission', array( $this, 'register_group' ), 10 );
add_filter( 'la24_register_setting', array( $this, 'register_setting' ), 10 );
}
public function register_menu() {
LA24()->Menu()::multi_add( array(
'DealsSection' => array(
'name' => __( 'معاملات', 'my-deals' ),
'icon' => 'fa fa-handshake-o',
'parent' => '',
'priority' => 60,
'is_toggle' => true,
),
'DealsList' => array(
'name' => __( 'همه معاملات', 'my-deals' ),
'icon' => 'fa fa-list',
'parent' => 'DealsSection',
'priority' => 10,
'is_toggle' => false,
),
) );
}
public function register_endpoint( $endpoints ) {
$endpoints['DealsList'] = array(
'name' => __( 'لیست معاملات', 'my-deals' ),
'callback' => '\MyDeals\Controllers\DealsList',
);
return $endpoints;
}
public function register_capability( $capability ) {
$capability['my_deals_view'] = array(
'name' => __( 'مجوز مشاهده معاملات', 'my-deals' ),
'gp_id' => 'deals-management',
);
$capability['my_deals_edit'] = array(
'name' => __( 'مجوز ویرایش معاملات', 'my-deals' ),
'gp_id' => 'deals-management',
);
return $capability;
}
public function register_group( $group ) {
$group['deals-management'] = __( 'مدیریت معاملات', 'my-deals' );
return $group;
}
public function register_setting( $settings ) {
$settings['my_deals'] = array(
'priority' => 45,
'name' => __( 'تنظیمات معاملات', 'my-deals' ),
'call_back' => '\MyDeals\Settings\DealsSettings',
);
return $settings;
}
}

مثال ۲: ردیابی تاریخچه پروفایل کاربر
نمایش خط زمانی فعالیتهای سفارشی در صفحه پروفایل کاربر:
php
add_filter( 'la24_history_user_profile', function ( $tabs ) {
$tabs['deals_history'] = array(
'priority' => 5,
'name' => __( 'تاریخچه معاملات', 'my-deals' ),
'callback' => '\MyDeals\Controllers\DealsHistory',
);
return $tabs;
} );
**کنترلر `DealsHistory`:**
php
<?php
namespace MyDeals\Controllers;
use LoyalAxis\abstracts\BaseViewController;
defined( 'ABSPATH' ) || exit;
class DealsHistory extends BaseViewController {
public function content() {
$profile_id = (int) ( $_GET['id'] ?? 0 );
if ( ! $profile_id ) return;
if ( ! la24_user_can( get_current_user_id(), 'my_deals_view' ) ) {
echo '<p class="text-muted">' . esc_html__( 'دسترسی ندارید.', 'my-deals' ) . '</p>';
return;
}
global $wpdb;
$deals = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}my_deals WHERE user_id = %d ORDER BY created_at DESC",
$profile_id
)
);
if ( empty( $deals ) ) {
echo '<p class="text-muted">' . esc_html__( 'هیچ معاملهای یافت نشد.', 'my-deals' ) . '</p>';
return;
}
foreach ( $deals as $deal ) {
printf(
'<div class="la24-history-item d-flex justify-content-between align-items-center border-bottom py-2">
<div>
<strong>%s</strong>
<small class="text-muted ms-2">%s</small>
</div>
<span class="badge bg-%s">%s</span>
</div>',
esc_html( $deal->title ),
esc_html( $deal->created_at ),
esc_attr( $deal->status === 'won' ? 'success' : ( $deal->status === 'lost' ? 'danger' : 'secondary' ) ),
esc_html( $deal->status )
);
}
}
}
مثال ۳: ویجت داشبورد سفارشی
کلاسهای ویجت باید از کلاس پایه انتزاعی `Widget` در LA24Core (`\LoyalAxis\classes\services\widgets\Widget`) ارثبری کنند و از یک شناسه عددی **۹۰۰ یا بالاتر** استفاده کنند — ماژولهای داخلی خود LA24Core شناسههای زیر ۹۰۰ را اشغال کردهاند، پس بازه `900+` برای افزونههای سفارشی/شخصثالث رزرو شده است. ویجت را با یک یا چند برچسب `category` (`administrative`، `woocommerce_panel`، `operational`، `reporting`، `general`) مشخص کنید تا در چیپهای جستوجو/فیلتر مودال «افزودن ویجت به داشبورد» بهدرستی نمایش داده شود — برای قرارداد کامل به [راهنمای `la24_widgets`](developer-guide-la24_widgets_fa.md) مراجعه کنید.
php
add_filter( 'la24_widgets', function ( $widgets ) {
$widgets['900'] = array(
'name' => __( 'خلاصه قیف فروش', 'my-deals' ),
'class_icon' => 'fa fa-filter',
'category' => array( 'reporting' ),
'callback' => '\MyDeals\Widgets\PipelineWidget',
);
return $widgets;
} );
**کلاس `PipelineWidget`:**
php
<?php
namespace MyDeals\Widgets;
defined( 'ABSPATH' ) || exit;
use LoyalAxis\classes\services\widgets\Widget;
use LoyalAxis\classes\utilities\Form;
class PipelineWidget extends Widget {
public function content( $settings ) {
global $wpdb;
$stats = $wpdb->get_results(
"SELECT status, COUNT(*) as total, SUM(value) as total_value
FROM {$wpdb->prefix}my_deals
GROUP BY status"
);
echo '<div class="p-3" style="background-color: ' . esc_attr( $settings['bg_color_900'] ) . '">';
echo '<h6 class="fw-bold mb-3">' . esc_html( $settings['head_text_900'] ) . '</h6>';
foreach ( $stats as $row ) {
$color = match( $row->status ) {
'won' => 'success',
'lost' => 'danger',
'negotiation' => 'warning',
default => 'secondary',
};
printf(
'<div class="d-flex justify-content-between mb-2">
<span class="badge bg-%s">%s</span>
<span>%d معامله — %s تومان</span>
</div>',
esc_attr( $color ),
esc_html( ucfirst( $row->status ) ),
(int) $row->total,
number_format( (float) $row->total_value )
);
}
echo '</div>';
}
public static function desktop_size() {
return array(
'size_x' => 3, 'size_y' => 2,
'minx' => 2, 'miny' => 1,
'maxx' => 6, 'maxy' => 6,
);
}
public static function mobile_size() {
return array(
'size_x' => 2, 'size_y' => 2,
'minx' => 1, 'miny' => 1,
'maxx' => 2, 'maxy' => 6,
);
}
public function default_settings(): array {
return array(
'head_text_900' => __( 'خلاصه قیف فروش', 'my-deals' ),
'bg_color_900' => 'rgba(255,255,255,1)',
);
}
public function widget_title() {
return $this->settings['head_text_900'] ?? '';
}
public function widget_bg_color() {
return $this->settings['bg_color_900'] ?? '';
}
public function style_settings( $settings ) {
echo Form::start_form( 0, 'la24-section-box' );
echo Form::header( __( 'تنظیمات ظاهری:', 'my-deals' ), 12, 'la24-titr' );
echo Form::color_picker( __( 'رنگ پسزمینه ویجت', 'my-deals' ), 6, 'bg_color_900', 'bg_color_900', 'center', $settings['bg_color_900'], 0, '' );
echo Form::close_form();
}
public function master_settings( $settings ) {
echo Form::start_form( 0, 'la24-section-box' );
echo Form::header( __( 'تنظیمات مدیریتی:', 'my-deals' ), 12, 'la24-titr' );
echo Form::text_input( __( 'عنوان', 'my-deals' ), 6, 'head_text_900', 'head_text_900', '', $settings['head_text_900'], '', 0 );
echo Form::close_form();
}
}

مثال ۴: دکمه عملیات پروفایل
دکمه سریع به صفحه معاملات برای کاربر مشخص:
php
add_filter( 'la24_render_profile_action_btn', function ( $actions, $is_clue ) {
$profile_id = (int) ( $_GET['id'] ?? 0 );
if ( $profile_id && la24_user_can( get_current_user_id(), 'my_deals_view' ) ) {
$url = la24_get_permalink_panel( 'DealsList/?user_id=' . $profile_id );
$actions['view_deals_btn']['content'] = sprintf(
'<a href="%s" class="btn btn-sm btn-outline-success me-1">
<i class="fa fa-handshake-o me-1"></i>%s
</a>',
esc_url( $url ),
esc_html__( 'معاملات', 'my-deals' )
);
}
return $actions;
}, 10, 2 );
مثال ۵: پنل تنظیمات چندفیلدی
کلاسی که از طریق `la24_register_setting` ثبت میشود (مثال ۱ را ببینید، جایی که `register_setting()` کلید `my_deals` را به `\MyDeals\Settings\DealsSettings` وصل میکند) باید **اینترفیس `\LoyalAxis\interfaces\Settings`** خود LA24Core را پیادهسازی کند — دو متد استاتیک، `request_process()` و `render_view()` — نه یک متد واحد `render()`. این همان قراردادی است که `LA24License_Settings.php` در داخل خودش رعایت میکند؛ برای توضیح کامل اینکه چرا این دو متد از هم جدا شدهاند و با چه ترتیبی اجرا میشوند به [راهنمای `la24_register_setting`](developer-guide-la24_register_setting_fa.md#کلاس-تنظیمات) مراجعه کنید.
مقادیر با `la24_get_option()` / `la24_update_options()` خوانده و نوشته میشوند (این توابع هم در [بخش آموزشی اختصاصی همان راهنما](developer-guide-la24_register_setting_fa.md#آموزش-la24_get_option-و-la24_update_options) توضیح داده شدهاند)، نه با `update_option()` / `get_option()`؛ در نتیجه تمام فیلدهای این پنل درون یک گروه واحد، `'my_deals_options'`، ذخیره میشوند.
php
<?php
namespace MyDeals\Settings;
use LoyalAxis\interfaces\Settings;
use LoyalAxis\traits\Notification;
defined( 'ABSPATH' ) || exit;
class DealsSettings implements Settings {
use Notification;
private const OPTION_GROUP = 'my_deals_options';
public static function render_view() {
$currency = la24_get_option( 'currency', self::OPTION_GROUP ) ?: 'IRR';
$default_stage = la24_get_option( 'default_stage', self::OPTION_GROUP ) ?: 'new';
$notify_email = la24_get_option( 'notify_email', self::OPTION_GROUP );
?>
<form method="post" class="la24-settings-form">
<div class="mb-3">
<label class="form-label"><?php esc_html_e( 'واحد ارز', 'my-deals' ); ?></label>
<input type="text" name="currency" value="<?php echo esc_attr( $currency ); ?>" class="form-control" style="max-width:120px" />
</div>
<div class="mb-3">
<label class="form-label"><?php esc_html_e( 'مرحله پیشفرض', 'my-deals' ); ?></label>
<select name="default_stage" class="form-select" style="max-width:200px">
<option value="new" <?php selected( $default_stage, 'new' ); ?>><?php esc_html_e( 'جدید', 'my-deals' ); ?></option>
<option value="negotiation" <?php selected( $default_stage, 'negotiation' ); ?>><?php esc_html_e( 'مذاکره', 'my-deals' ); ?></option>
<option value="won" <?php selected( $default_stage, 'won' ); ?>><?php esc_html_e( 'برنده شده', 'my-deals' ); ?></option>
</select>
</div>
<div class="mb-3">
<label class="form-label"><?php esc_html_e( 'ایمیل اطلاعرسانی', 'my-deals' ); ?></label>
<input type="email" name="notify_email" value="<?php echo esc_attr( $notify_email ); ?>" class="form-control" style="max-width:300px" />
</div>
<button type="submit" name="my_deals_submit" class="btn btn-primary"><?php esc_html_e( 'ذخیره تنظیمات', 'my-deals' ); ?></button>
</form>
<?php
}
public static function request_process() {
if ( isset( $_POST['my_deals_submit'] ) ) {
la24_update_options( array(
'currency' => sanitize_text_field( $_POST['currency'] ?? 'IRR' ),
'default_stage' => sanitize_text_field( $_POST['default_stage'] ?? 'new' ),
'notify_email' => sanitize_email( $_POST['notify_email'] ?? '' ),
), self::OPTION_GROUP );
self::alert( __( 'تنظیمات معاملات با موفقیت ذخیره شد.', 'my-deals' ), true, 'success' );
}
}
}
`request_process()` ابتدا اجرا میشود — برای هر پنل ثبتشده، در هر بار بارگذاری صفحهٔ تنظیمات — و فقط زمانی مینویسد که دکمهٔ ارسال خودش (`my_deals_submit`) در `$_POST` وجود داشته باشد. `render_view()` پس از آن اجرا میشود و بهسادگی مقادیر (که ممکن است همین الان بهروزرسانی شده باشند) را با `la24_get_option()` میخواند، بنابراین فرم همیشه همان چیزی را نشان میدهد که واقعاً ذخیره شده است.
