30 β Yakuniy kapston: to'liq professional plugin¶
β¬ οΈ Oldingi: 29 β Xavfsizlik auditi (chuqur) Β· π README
Bu bobda: butun kitob bo'ylab bo'lak-bo'lak qurgan barcha narsani bitta professional, distribution-ready plugin'ga birlashtiramiz β
kitoblar-katalogi: PSR-4 autoload va DI'siz singleton orkestratorPluginsinfi (03/05),kitobCPT +janrtaxonomy (07/08), muallif/ISBN meta box +register_post_meta(09), Settings API sozlamalari (06),kitkat/v1/kitoblarREST endpoint (16), dynamic Gutenberg blok "Kitoblar ro'yxati" (19-21), ixtiyoriy WooCommerce/HPOS hook (27), hamma joyda nonce/sanitize/escape/capability/prepare (12/29), i18n (24), PHPUnit testlar (25) vareadme.txt+ SVN reliz (28) β to'liq fayl daraxti, RUNBOOK va o'sish yo'li bilan; kitob yakuni.
Muammo: 29 bob bilim, lekin bitta plugin emas¶
Siz 29 bob davomida har bir mavzuni alohida o'rgandingiz: bir bobda CPT, boshqasida REST, uchinchisida blok. Har biri alohida ishladi. Lekin real plugin β bu alohida parchalar to'plami emas, balki bir butun tizim: komponentlar bir-biriga toza ulanadi, bitta kirish nuqtasi hammasini ishga tushiradi, xavfsizlik har qatlamda, va u yangilanishga, testlashga, tarqatishga tayyor.
Bu bob β kapston. Biz kitoblar-katalogi plugin'ini boshidan oxirigacha professional tarzda yig'amiz. Har bir qadam yonida qaysi bobdan kelganini ko'rsatamiz β bu bob butun kitobning xaritasi.
βΉοΈ Halol eslatma. Quyidagi PHP fayllar php -l (PHP 8.4) bilan sintaktik tekshirilgan, dynamic blok esa @wordpress/create-block bilan yaratilib npm run build (@wordpress/scripts, Node 24) bilan haqiqatan qurilgan. Ammo plugin'ni aktivatsiya qilish, wp-admin'da ko'rish, jonli REST javoblari, blok muharririda ishlatish va WooCommerce β bularning hammasi ishlab turgan WordPress saytini talab qiladi. Shuning uchun "ishga tushirish" qadamlari "o'z saytingizda sinab ko'ring" ohangida. Lokal WP'ni wp-env bilan oson ko'tarasiz (02-bob).
Umumiy ko'rinish: arxitektura¶
Avval butun plugin'ning xaritasini ko'raylik. Bitta Plugin sinfi β orkestrator: u barcha komponentlarni yig'adi va har biriga "o'zingni ro'yxatdan o'tkaz" deydi. Har komponent o'z hook'larini WordPress'ga ulaydi. Yadro hech narsani o'zgartirmaymiz β faqat hook (01-bobning falsafasi).
Asosiy g'oya: har komponent mustaqil. CPT komponentini o'chirsangiz, REST komponenti baribir kompilyatsiya bo'ladi. Bu β toza, kengaytiriladigan dizayn (05-bob).
To'liq fayl daraxti¶
Mana professional plugin'ning to'liq tuzilishi. Har papka aniq mas'uliyatga ega:
kitoblar-katalogi/
βββ kitoblar-katalogi.php # Kirish nuqtasi: header, autoload, boot (03/05)
βββ uninstall.php # O'chirilganda tozalash (03)
βββ composer.json # PSR-4 autoload + dev (phpcs, phpunit)
βββ readme.txt # wordpress.org formati (28)
βββ package.json # @wordpress/scripts (blok build) (19)
βββ phpcs.xml.dist # WordPress Coding Standards (25)
βββ phpunit.xml.dist # Test konfiguratsiyasi (25)
βββ .gitignore # vendor/, node_modules/, build/
βββ src/ # Sof PHP, PSR-4 (Oqil\KitobKatalog\)
β βββ Plugin.php # Orkestrator (singleton)
β βββ Autoloader.php # Composer'siz zaxira autoloader
β βββ PostTypes/
β β βββ KitobPostType.php # CPT (07)
β β βββ JanrTaxonomy.php # Taxonomy (08)
β β βββ KitobMeta.php # Meta box + register_post_meta (09)
β βββ Admin/
β β βββ Settings.php # Settings API (06)
β βββ Rest/
β β βββ KitoblarController.php # REST endpoint (16)
β βββ Blocks/
β β βββ KitobRoyxatBlock.php # Dynamic blok PHP qismi (21)
β βββ Integrations/
β βββ WooCommerce.php # Ixtiyoriy WC/HPOS hook (27)
βββ blok-src/ # Blok JSX manbasi (build oldidan)
β βββ kitob-royxat/
β βββ block.json # apiVersion 3, dynamic (19/21)
β βββ edit.js # Muharrir UI (20)
β βββ render.php # Server-render (21)
βββ build/ # npm run build chiqishi (commit qilinadi yoki .distignore)
β βββ kitob-royxat/ ...
βββ languages/ # .pot / .po / .mo (24)
β βββ kitoblar-katalogi.pot
βββ tests/ # PHPUnit (25)
βββ KitobMetaTest.php
π Nima uchun src/ va blok-src/ alohida? src/ β sof PHP, Composer PSR-4 bilan yuklanadi. Blok JSX'i esa @wordpress/scripts bilan kompilyatsiya qilinadi va build/ ga chiqadi. PHP yuklovchi blok manbasini ko'rmasligi kerak β shuning uchun ular ajratilgan.
1-qadam: kirish nuqtasi va header (03 / 05)¶
Plugin header'i β WordPress'ga "men plugin'man" deb aytadi. So'ng autoload va bitta boot() chaqiruvi:
<?php
/**
* Plugin Name: Kitoblar katalogi
* Plugin URI: https://ioqil.uz/plugins/kitoblar-katalogi
* Description: Kitoblarni CPT, janr, REST va Gutenberg blok bilan boshqaradi.
* Version: 1.0.0
* Requires at least: 7.0
* Requires PHP: 8.3
* Author: Oqil Imomnazarov
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: kitoblar-katalogi
* Domain Path: /languages
*
* @package Oqil\KitobKatalog
*/
namespace Oqil\KitobKatalog;
defined( 'ABSPATH' ) || exit; // To'g'ridan-to'g'ri kirishni to'sish.
const VERSIYA = '1.0.0';
define( __NAMESPACE__ . '\\FAYL', __FILE__ );
define( __NAMESPACE__ . '\\YOL', plugin_dir_path( __FILE__ ) );
define( __NAMESPACE__ . '\\URL', plugin_dir_url( __FILE__ ) );
// Composer autoload (yoki o'z zaxira yuklovchimiz).
$autoload = __DIR__ . '/vendor/autoload.php';
if ( is_readable( $autoload ) ) {
require $autoload;
} else {
require __DIR__ . '/src/Autoloader.php';
( new Autoloader( __NAMESPACE__, __DIR__ . '/src' ) )->register();
}
register_activation_hook( __FILE__, [ Plugin::class, 'aktivatsiya' ] );
register_deactivation_hook( __FILE__, [ Plugin::class, 'deaktivatsiya' ] );
add_action(
'plugins_loaded',
static function (): void {
Plugin::instance()->boot();
}
);
π‘ Nega plugins_loaded? Bu β barcha plugin'lar yuklangach ishlaydigan eng erta xavfsiz hook. CPT/taxonomy esa ichkarida init hook'iga ulanadi (WordPress shuni talab qiladi).
Zaxira autoloader (Composer bo'lmaganda β PSR-4 mantiq, 05-bob):
<?php
namespace Oqil\KitobKatalog;
defined( 'ABSPATH' ) || exit;
final class Autoloader {
public function __construct(
private readonly string $prefiks,
private readonly string $asos_yol,
) {}
public function register(): void {
spl_autoload_register( [ $this, 'yukla' ] );
}
public function yukla( string $sinf ): void {
if ( ! str_starts_with( $sinf, $this->prefiks . '\\' ) ) {
return; // Faqat o'z namespace'imiz.
}
$nisbiy = substr( $sinf, strlen( $this->prefiks ) + 1 );
$fayl = $this->asos_yol . '/' . str_replace( '\\', '/', $nisbiy ) . '.php';
if ( is_readable( $fayl ) ) {
require $fayl;
}
}
}
βΉοΈ Bu readonly xossali konstruktor promotion va str_starts_with() β PHP 8.3+ idiomi (05-bob). Ishlab chiqarishda Composer autoload'ni afzal ko'ring; bu zaxira β faqat vendor/ bo'lmaganda.
2-qadam: orkestrator Plugin sinfi (05)¶
Bu β plugin'ning yuragi. Singleton (bitta namuna), komponentlarni yig'adi, har biriga register() deydi:
<?php
namespace Oqil\KitobKatalog;
use Oqil\KitobKatalog\PostTypes\KitobPostType;
use Oqil\KitobKatalog\PostTypes\JanrTaxonomy;
use Oqil\KitobKatalog\PostTypes\KitobMeta;
use Oqil\KitobKatalog\Admin\Settings;
use Oqil\KitobKatalog\Rest\KitoblarController;
use Oqil\KitobKatalog\Blocks\KitobRoyxatBlock;
use Oqil\KitobKatalog\Integrations\WooCommerce;
defined( 'ABSPATH' ) || exit;
final class Plugin {
private static ?Plugin $instance = null;
/** @var array<int, object> */
private array $komponentlar = [];
private function __construct() {}
public static function instance(): Plugin {
return self::$instance ??= new self();
}
public function boot(): void {
$this->komponentlar = [
new KitobPostType(),
new JanrTaxonomy(),
new KitobMeta(),
new Settings(),
new KitoblarController(),
new KitobRoyxatBlock(),
];
// WooCommerce faqat aktiv bo'lsa (ixtiyoriy integratsiya).
if ( class_exists( \WooCommerce::class ) ) {
$this->komponentlar[] = new WooCommerce();
}
foreach ( $this->komponentlar as $komponent ) {
if ( method_exists( $komponent, 'register' ) ) {
$komponent->register();
}
}
add_action( 'init', [ $this, 'tarjimani_yukla' ] );
}
public function tarjimani_yukla(): void {
load_plugin_textdomain(
'kitoblar-katalogi',
false,
dirname( plugin_basename( FAYL ) ) . '/languages'
);
}
public static function aktivatsiya(): void {
( new KitobPostType() )->royxat();
( new JanrTaxonomy() )->royxat();
flush_rewrite_rules(); // Slug'lar darhol ishlashi uchun.
}
public static function deaktivatsiya(): void {
flush_rewrite_rules();
}
}
π Aktivatsiyada CPT/taxonomy'ni qo'lda ro'yxatdan o'tkazib, keyin flush_rewrite_rules(). Aks holda yangi kitoblar/... URL'lari 404 beradi, foydalanuvchi sozlamalarni qayta saqlamaguncha. flush_rewrite_rules() qimmat β uni faqat aktivatsiya/deaktivatsiyada chaqiring, har so'rovda emas.
π‘ Singleton vs DI konteyner. Bu yerda sodda singleton ishlatdik. Yirik plugin'da to'liq DI konteyner (masalan league/container) afzal β komponentlarni test paytida almashtirish osonroq. Boshlash uchun singleton yetarli.
3-qadam: CPT kitob va taxonomy janr (07 / 08)¶
<?php
namespace Oqil\KitobKatalog\PostTypes;
defined( 'ABSPATH' ) || exit;
final class KitobPostType {
public const SLUG = 'kitob';
public function register(): void {
add_action( 'init', [ $this, 'royxat' ] );
}
public function royxat(): void {
register_post_type(
self::SLUG,
[
'labels' => [
'name' => __( 'Kitoblar', 'kitoblar-katalogi' ),
'singular_name' => __( 'Kitob', 'kitoblar-katalogi' ),
'add_new_item' => __( 'Yangi kitob qo\'shish', 'kitoblar-katalogi' ),
],
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-book-alt',
'rewrite' => [ 'slug' => 'kitoblar' ],
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ],
'show_in_rest' => true, // Blok muharriri + REST uchun SHART.
'rest_base' => 'kitoblar',
]
);
}
}
Taxonomy janr β ierarxik (kategoriyaga o'xshash):
<?php
namespace Oqil\KitobKatalog\PostTypes;
defined( 'ABSPATH' ) || exit;
final class JanrTaxonomy {
public const SLUG = 'janr';
public function register(): void {
add_action( 'init', [ $this, 'royxat' ] );
}
public function royxat(): void {
register_taxonomy(
self::SLUG,
KitobPostType::SLUG,
[
'labels' => [
'name' => __( 'Janrlar', 'kitoblar-katalogi' ),
'singular_name' => __( 'Janr', 'kitoblar-katalogi' ),
],
'public' => true,
'hierarchical' => true,
'show_admin_column' => true,
'show_in_rest' => true,
'rewrite' => [ 'slug' => 'janr' ],
]
);
}
}
β οΈ show_in_rest => true ni unutmang. Usiz CPT/taxonomy blok muharririda ko'rinmaydi va REST API'da yo'q. Zamonaviy WordPress'da bu deyarli har doim kerak.
4-qadam: meta box va register_post_meta (09)¶
Muallif va ISBN maydonlari. Ikki marta ro'yxatdan o'tamiz: register_post_meta (REST/blok uchun) va meta box (klassik tahrirlovchi uchun). Saqlashda xavfsizlik uch darvozasi: nonce + capability + sanitize.
<?php
namespace Oqil\KitobKatalog\PostTypes;
defined( 'ABSPATH' ) || exit;
final class KitobMeta {
public const MUALLIF = '_kitkat_muallif';
public const ISBN = '_kitkat_isbn';
public function register(): void {
add_action( 'init', [ $this, 'meta_royxat' ] );
add_action( 'add_meta_boxes', [ $this, 'meta_box' ] );
add_action( 'save_post_' . KitobPostType::SLUG, [ $this, 'saqla' ], 10, 2 );
}
public function meta_royxat(): void {
register_post_meta(
KitobPostType::SLUG,
self::MUALLIF,
[
'type' => 'string',
'single' => true,
'show_in_rest' => true,
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => static fn(): bool => current_user_can( 'edit_posts' ),
]
);
register_post_meta(
KitobPostType::SLUG,
self::ISBN,
[
'type' => 'string',
'single' => true,
'show_in_rest' => true,
'sanitize_callback' => [ self::class, 'isbn_tozala' ],
'auth_callback' => static fn(): bool => current_user_can( 'edit_posts' ),
]
);
}
/** ISBN faqat raqam, X va defis. Sof-PHP mantiq β WP'siz ham ishlaydi. */
public static function isbn_tozala( string $xom ): string {
return preg_replace( '/[^0-9X-]/i', '', $xom ) ?? '';
}
public function meta_box(): void {
add_meta_box(
'kitkat_kitob_maydonlari',
__( 'Kitob ma\'lumotlari', 'kitoblar-katalogi' ),
[ $this, 'meta_box_chiqar' ],
KitobPostType::SLUG,
'side'
);
}
public function meta_box_chiqar( \WP_Post $post ): void {
wp_nonce_field( 'kitkat_meta_saqla', 'kitkat_meta_nonce' );
$muallif = (string) get_post_meta( $post->ID, self::MUALLIF, true );
$isbn = (string) get_post_meta( $post->ID, self::ISBN, true );
?>
<p>
<label for="kitkat_muallif"><?php esc_html_e( 'Muallif', 'kitoblar-katalogi' ); ?></label>
<input type="text" id="kitkat_muallif" name="kitkat_muallif"
value="<?php echo esc_attr( $muallif ); ?>" class="widefat" />
</p>
<p>
<label for="kitkat_isbn"><?php esc_html_e( 'ISBN', 'kitoblar-katalogi' ); ?></label>
<input type="text" id="kitkat_isbn" name="kitkat_isbn"
value="<?php echo esc_attr( $isbn ); ?>" class="widefat" />
</p>
<?php
}
public function saqla( int $post_id, \WP_Post $post ): void {
// 1) Nonce (CSRF).
if ( ! isset( $_POST['kitkat_meta_nonce'] )
|| ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['kitkat_meta_nonce'] ) ), 'kitkat_meta_saqla' ) ) {
return;
}
// 2) Avtosaqlashni o'tkazib yuborish.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// 3) Capability.
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// 4) Sanitize + saqlash.
$muallif = sanitize_text_field( wp_unslash( $_POST['kitkat_muallif'] ?? '' ) );
$isbn = self::isbn_tozala( (string) wp_unslash( $_POST['kitkat_isbn'] ?? '' ) );
update_post_meta( $post_id, self::MUALLIF, $muallif );
update_post_meta( $post_id, self::ISBN, $isbn );
}
}
β οΈ auth_callback REST orqali yozishni nazorat qiladi. register_post_meta show_in_rest => true bo'lganda, har kim REST orqali meta'ni o'zgartira olmasligi uchun auth_callback bilan capability tekshiring. Meta box esa o'z nonce + current_user_can darvozasiga ega β ikkalasi mustaqil himoya (defense-in-depth, 29-bob).
5-qadam: Settings API sozlamalari (06)¶
Plugin'ning sozlamalar sahifasi β Sozlamalar > Kitoblar katalogi:
<?php
namespace Oqil\KitobKatalog\Admin;
defined( 'ABSPATH' ) || exit;
final class Settings {
public const OPTION_GROUP = 'kitkat_settings';
public const OPTION_NAME = 'kitkat_options';
public function register(): void {
add_action( 'admin_menu', [ $this, 'menu' ] );
add_action( 'admin_init', [ $this, 'sozlamalar' ] );
}
public function menu(): void {
add_options_page(
__( 'Kitoblar katalogi sozlamalari', 'kitoblar-katalogi' ),
__( 'Kitoblar katalogi', 'kitoblar-katalogi' ),
'manage_options',
'kitkat-settings',
[ $this, 'sahifa' ]
);
}
public function sozlamalar(): void {
register_setting(
self::OPTION_GROUP,
self::OPTION_NAME,
[
'type' => 'array',
'sanitize_callback' => [ $this, 'tozala' ],
'default' => [ 'son' => 10 ],
]
);
add_settings_section( 'kitkat_asosiy', __( 'Asosiy', 'kitoblar-katalogi' ), '__return_false', 'kitkat-settings' );
add_settings_field(
'kitkat_son',
__( 'Har sahifadagi kitoblar soni', 'kitoblar-katalogi' ),
[ $this, 'son_maydoni' ],
'kitkat-settings',
'kitkat_asosiy'
);
}
public function son_maydoni(): void {
$opt = (array) get_option( self::OPTION_NAME, [] );
$son = isset( $opt['son'] ) ? absint( $opt['son'] ) : 10;
printf(
'<input type="number" min="1" max="100" name="%s[son]" value="%d" />',
esc_attr( self::OPTION_NAME ),
(int) $son
);
}
/** Sof-PHP sanitize: 1..100 oralig'iga qisadi. */
public function tozala( mixed $xom ): array {
$xom = is_array( $xom ) ? $xom : [];
$son = isset( $xom['son'] ) ? absint( $xom['son'] ) : 10;
return [ 'son' => max( 1, min( 100, $son ) ) ];
}
public function sahifa(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<form action="options.php" method="post">
<?php
settings_fields( self::OPTION_GROUP ); // nonce + group.
do_settings_sections( 'kitkat-settings' );
submit_button();
?>
</form>
</div>
<?php
}
}
π‘ settings_fields() o'zi nonce'ni qo'shadi, va WordPress sanitizatsiyani sanitize_callback orqali bajaradi β Settings API'ning kuchi shu. Lekin sahifani ko'rsatishdan oldin current_user_can('manage_options') ni baribir tekshiring.
6-qadam: REST endpoint kitkat/v1/kitoblar (16)¶
Janr bo'yicha filtrlanadigan, public (faqat o'qish) endpoint:
<?php
namespace Oqil\KitobKatalog\Rest;
use Oqil\KitobKatalog\PostTypes\KitobPostType;
use Oqil\KitobKatalog\PostTypes\JanrTaxonomy;
use Oqil\KitobKatalog\PostTypes\KitobMeta;
defined( 'ABSPATH' ) || exit;
final class KitoblarController {
public const NS = 'kitkat/v1';
public const ROUTE = '/kitoblar';
public function register(): void {
add_action( 'rest_api_init', [ $this, 'route_royxat' ] );
}
public function route_royxat(): void {
register_rest_route(
self::NS,
self::ROUTE,
[
'methods' => \WP_REST_Server::READABLE, // GET
'callback' => [ $this, 'kitoblar' ],
'permission_callback' => '__return_true', // Ataylab ochiq: faqat nashr etilgan public ma'lumot.
'args' => [
'janr' => [
'type' => 'string',
'sanitize_callback' => 'sanitize_title',
],
'son' => [
'type' => 'integer',
'default' => 10,
'sanitize_callback' => 'absint',
'validate_callback' => static fn( $v ): bool => is_numeric( $v ) && (int) $v > 0 && (int) $v <= 100,
],
],
]
);
}
public function kitoblar( \WP_REST_Request $request ): \WP_REST_Response {
$args = [
'post_type' => KitobPostType::SLUG,
'post_status' => 'publish',
'posts_per_page' => absint( $request['son'] ),
'no_found_rows' => true, // Sahifalash kerak emas -> tezroq (26-bob).
];
if ( ! empty( $request['janr'] ) ) {
$args['tax_query'] = [
[
'taxonomy' => JanrTaxonomy::SLUG,
'field' => 'slug',
'terms' => $request['janr'],
],
];
}
$query = new \WP_Query( $args );
$natija = [];
foreach ( $query->posts as $post ) {
$natija[] = [
'id' => $post->ID,
'nom' => get_the_title( $post ),
'havola' => get_permalink( $post ),
'muallif' => (string) get_post_meta( $post->ID, KitobMeta::MUALLIF, true ),
'isbn' => (string) get_post_meta( $post->ID, KitobMeta::ISBN, true ),
];
}
return new \WP_REST_Response( $natija, 200 );
}
}
π permission_callback => '__return_true' bu yerda ATAYLAB. Endpoint faqat nashr etilgan (publish) kitoblarni qaytaradi β bu allaqachon ommaviy ma'lumot. Lekin agar endpoint ma'lumot o'zgartirsa (POST/DELETE) yoki maxfiy maydon qaytarsa β '__return_true' halokat bo'lardi (29-bob). Ochiq endpoint ataylab ochiq bo'lsagina, izoh bilan.
β οΈ son ni cheklang. validate_callback 1..100 oralig'ini majburlaydi. Usiz hujumchi ?son=999999 bilan saytni DoS qila oladi. Cheklov β performance va xavfsizlik (26/29).
Jonli sinov (o'z saytingizda): brauzerda
https://sayt.uz/wp-json/kitkat/v1/kitoblar?janr=ilmiy&son=5ni oching β JSON massiv qaytadi. Jonli javob WordPress ishlab turishini talab qiladi.
7-qadam: dynamic Gutenberg blok "Kitoblar ro'yxati" (19-21)¶
Blok ikki qismdan iborat: JS muharrir qismi (edit.js, block.json) va PHP server-render (render.php). block.json β manifest:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "oqil/kitob-royxat",
"version": "1.0.0",
"title": "Kitoblar ro'yxati",
"category": "widgets",
"icon": "book-alt",
"description": "Tanlangan janr bo'yicha kitoblar ro'yxatini ko'rsatadi.",
"textdomain": "kitoblar-katalogi",
"attributes": {
"son": { "type": "number", "default": 5 },
"janr": { "type": "string", "default": "" }
},
"supports": { "html": false },
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"render": "file:./render.php"
}
Muharrir UI (edit.js) β InspectorControls bilan son/janr sozlamasi (20-bob):
import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl, TextControl } from '@wordpress/components';
import './editor.scss';
export default function Edit( { attributes, setAttributes } ) {
const { son, janr } = attributes;
const blockProps = useBlockProps();
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Sozlamalar', 'kitoblar-katalogi' ) }>
<RangeControl
label={ __( 'Kitoblar soni', 'kitoblar-katalogi' ) }
value={ son }
onChange={ ( q ) => setAttributes( { son: q } ) }
min={ 1 } max={ 20 }
/>
<TextControl
label={ __( 'Janr (slug)', 'kitoblar-katalogi' ) }
value={ janr }
onChange={ ( q ) => setAttributes( { janr: q } ) }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
<p>{ __( 'Kitoblar ro\'yxati:', 'kitoblar-katalogi' ) } { son }
{ janr && ` (${ janr })` }</p>
</div>
</>
);
}
Server-render (render.php) β front-end'da haqiqiy kitoblar (21-bob). Bu yerda escape kontekst bo'yicha:
<?php
/**
* Dynamic blok render. Mavjud: $attributes, $content, $block.
*/
$son = isset( $attributes['son'] ) ? absint( $attributes['son'] ) : 5;
$janr = isset( $attributes['janr'] ) ? sanitize_title( (string) $attributes['janr'] ) : '';
$args = [
'post_type' => 'kitob',
'post_status' => 'publish',
'posts_per_page' => $son,
'no_found_rows' => true,
];
if ( '' !== $janr ) {
$args['tax_query'] = [
[ 'taxonomy' => 'janr', 'field' => 'slug', 'terms' => $janr ],
];
}
$query = new WP_Query( $args );
if ( ! $query->have_posts() ) {
printf( '<p %s>%s</p>', get_block_wrapper_attributes(), esc_html__( 'Kitob topilmadi.', 'kitoblar-katalogi' ) );
return;
}
?>
<ul <?php echo get_block_wrapper_attributes(); ?>>
<?php foreach ( $query->posts as $post ) : ?>
<?php $muallif = (string) get_post_meta( $post->ID, '_kitkat_muallif', true ); ?>
<li>
<a href="<?php echo esc_url( get_permalink( $post ) ); ?>"><?php echo esc_html( get_the_title( $post ) ); ?></a>
<?php if ( '' !== $muallif ) : ?>— <?php echo esc_html( $muallif ); ?><?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
Blok'ning PHP registratsiyasi (orkestrator chaqiradi, 21-bob):
<?php
namespace Oqil\KitobKatalog\Blocks;
defined( 'ABSPATH' ) || exit;
final class KitobRoyxatBlock {
public function register(): void {
add_action( 'init', [ $this, 'blok_royxat' ] );
}
public function blok_royxat(): void {
// build/ ichidagi block.json'ni o'qiydi (apiVersion 3, dynamic).
register_block_type( YOL . 'build/kitob-royxat' );
}
}
π register_block_type() ga build papkasi yo'lini bering. U o'sha papkadagi block.json'ni o'qiydi va render maydonidagi render.php'ni avtomatik server-render uchun ishlatadi. JS faylni qo'lda enqueue qilish shart emas β block.json hammasini hal qiladi (19-bob).
Jonli sinov (o'z saytingizda): blok muharririda "Kitoblar ro'yxati" blokini qo'shing, o'ng paneldan son/janr ni o'zgartiring va sahifani ko'ring. Blok muharriri faqat ishlab turgan WordPress'da ishlaydi.
8-qadam: ixtiyoriy WooCommerce hook (27)¶
Faqat WooCommerce aktiv bo'lsa yuklanadi (orkestratordagi class_exists shart bilan). Mahsulot sahifasiga ISBN tab qo'shadi va HPOS muvofiqligini e'lon qiladi:
<?php
namespace Oqil\KitobKatalog\Integrations;
use Oqil\KitobKatalog\PostTypes\KitobMeta;
defined( 'ABSPATH' ) || exit;
final class WooCommerce {
public function register(): void {
add_action( 'before_woocommerce_init', [ $this, 'hpos_muvofiq' ] );
add_filter( 'woocommerce_product_tabs', [ $this, 'isbn_tab' ] );
}
public function hpos_muvofiq(): void {
if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
'custom_order_tables',
plugin_basename( \Oqil\KitobKatalog\FAYL ),
true
);
}
}
/** @param array<string, array<string, mixed>> $tabs */
public function isbn_tab( array $tabs ): array {
global $product;
if ( ! $product instanceof \WC_Product ) {
return $tabs; // Filter HAR DOIM qiymat qaytaradi (04-bob).
}
$isbn = (string) get_post_meta( $product->get_id(), KitobMeta::ISBN, true );
if ( '' === $isbn ) {
return $tabs;
}
$tabs['kitkat_isbn'] = [
'title' => __( 'ISBN', 'kitoblar-katalogi' ),
'priority' => 25,
'callback' => static function () use ( $isbn ): void {
echo '<p>' . esc_html( $isbn ) . '</p>';
},
];
return $tabs;
}
}
β οΈ HPOS muvofiqligini before_woocommerce_init da e'lon qiling. WooCommerce 8.2+ HPOS (High-Performance Order Storage) ishlatadi; muvofiqlikni e'lon qilmasangiz, admin ogohlantirish chiqaradi (27-bob). class_exists tekshiruvi β WC o'rnatilmagan saytda fatal xatoning oldini oladi.
9-qadam: i18n (24) va tozalash (uninstall)¶
Barcha foydalanuvchiga ko'rinadigan matn __(), esc_html__(), _e() ichida, bir xil text domain 'kitoblar-katalogi' bilan (yuqorida ko'rdingiz). .pot faylni yarating:
uninstall.php β plugin o'chirilganda (deaktivatsiya emas, butunlay o'chirilganda) ma'lumotni tozalaydi:
<?php
// Faqat WordPress o'chirish jarayonida chaqiriladi.
defined( 'WP_UNINSTALL_PLUGIN' ) || exit;
delete_option( 'kitkat_options' );
// Eslatma: CPT post'lari va meta'sini o'chirish β siyosat masalasi.
// Foydalanuvchi ma'lumotini ataylab saqlab qolishingiz ham mumkin.
π uninstall.php da har doim WP_UNINSTALL_PLUGIN ni tekshiring. Bu konstanta faqat WordPress o'chirish paytida aniqlanadi β to'g'ridan-to'g'ri kirishni to'sadi.
10-qadam: testlar (25)¶
PHPUnit bilan sof-PHP mantiqni (masalan ISBN sanitizatsiya) testlash:
<?php
namespace Oqil\KitobKatalog\Tests;
use PHPUnit\Framework\TestCase;
use Oqil\KitobKatalog\PostTypes\KitobMeta;
final class KitobMetaTest extends TestCase {
public function test_isbn_faqat_raqam_va_defis(): void {
$this->assertSame( '978-5-09', KitobMeta::isbn_tozala( 'ISBN: 978-5-09 abc' ) );
}
public function test_isbn_X_belgisini_saqlaydi(): void {
$this->assertSame( '0-306-40615-X', KitobMeta::isbn_tozala( '0-306-40615-X' ) );
}
public function test_bosh_qiymat(): void {
$this->assertSame( '', KitobMeta::isbn_tozala( '!!!' ) );
}
}
π‘ Sof mantiqni WP'siz test qiling. isbn_tozala WordPress funksiyalariga tayanmaydi β uni oddiy PHPUnit bilan, ishlab turgan WP'siz sinash mumkin. CPT/REST kabi WP'ga tayangan kodni esa wp-env + WP PHPUnit integration testi bilan sinaysiz (25-bob).
RUNBOOK: qurish, tekshirish, reliz¶
Endi plugin tayyor β uni qanday qurish va chiqarish? Quyidagi ketma-ketlik β har relizda bajaradigan darvozalar. Bittasi qizil bo'lsa, reliz to'xtaydi.
# 0) Bog'liqliklarni o'rnatish (bir marta)
composer install
npm install
# 1) Blokni qurish (JSX -> build/)
npm run build
# 2) Coding standard tekshiruvi (WordPress Coding Standards + security sniff)
composer exec phpcs
# 3) Testlar (sof-PHP + wp-env integration)
composer exec phpunit
# yoki WP integration uchun:
# wp-env start && composer exec phpunit
# 4) Versiyani oshirish: header "Version" + readme.txt "Stable tag" BIR XIL bo'lsin
# 5) Tarjima fayli yangilash
wp i18n make-pot . languages/kitoblar-katalogi.pot
# 6) wordpress.org SVN (28-bob)
# svn checkout / cp -> trunk/ ; svn cp trunk tags/1.0.0 ; svn ci -m "1.0.0"
π "Stable tag" = reliz tugmasi. wordpress.org readme.txt'dagi Stable tag qaysi tags/ papkasini foydalanuvchiga berishni hal qiladi. Header Version bilan bir xil bo'lishi shart β aks holda yangilanish chalkashadi (28-bob).
β οΈ build/ va vendor/ ni reliz'ga qo'shing, node_modules/ ni QO'SHMANG. wordpress.org foydalanuvchisi npm install qilmaydi β unga qurib bo'lingan build/ kerak. .gitignore'da build/ bo'lsa ham, SVN'ga uni qo'shing (yoki CI build'da yarating).
"Keyingi qadamlar": expertdan keyin nima?¶
Siz endi to'liq plugin yoza olasiz. O'sish yo'li:
- Composer paketlari. Umumiy kodni (logger, settings abstraksiyasi, REST helper) alohida Composer paketga ajrating va bir nechta plugin'da qayta ishlating.
- CI/CD. GitHub Actions: har push'da
phpcs+phpunit+npm run build; teg qo'yilganda avtomatik SVN deploy (10up/action-wordpress-plugin-deploy). - DI konteyner. Singleton'dan to'liq dependency injection konteynerga o'ting β testlash va kengaytirish osonroq.
- Premium model. Bepul "core" + pullik "pro" qo'shimcha plugin (freemium); litsenziya serveri va yangilanish API'si.
- Blok kutubxonasi. Bir nechta bog'liq blokni bitta plugin'da (
block.jsonhar biriga), umumiy komponentlar bilan. - Ko'p-plugin ekotizimi. O'z hook'laringiz (
do_action('kitkat_...'),apply_filters('kitkat_...')) bilan boshqa dasturchilarga sizning plugin'ingizni kengaytirish imkonini bering β siz qilgandek, ular ham yadroga tegmasdan.
Tabrik β yo'l yakuni¶
Siz functions.php'ga kod tashlaydigan havaskordan toza, xavfsiz, testlangan, tarqatishga tayyor plugin yozadigan muhandisga aylandingiz. 01-bobda "yadroni o'zgartirma, hook ishlat" falsafasidan boshlab, bu kapstonda bitta professional pluginda: CPT, taxonomy, meta, Settings API, REST, Gutenberg blok, WooCommerce, xavfsizlik, i18n, test va distribution β hammasini birlashtirdingiz.
Endi navbat sizniki: o'z g'oyangizni oling, wp-env bilan lokal sayt ko'taring, va yozib o'rganing β plugin o'qib emas, qurib o'rganiladi. Rasmiy Plugin Handbook va Block Editor Handbook β eng ishonchli hamrohingiz. Omad, va WordPress jamoasiga hissangizni qo'shing.
30-bob mashqlari¶
Mashqlar
kitoblar-katalogiplugini ustida ishlaydi (namespaceOqil\KitobKatalog, prefikskitkat_, CPTkitob, taxonomyjanr, RESTkitkat/v1, blokoqil/). Sof-PHP mashqlarniphpbilan, WP kodiniwp-envbilan o'z test saytingizda sinang.
Oson¶
- (Oson) Plugin header'ida
Requires PHPvaRequires at leastnima uchun kerak? Birini noto'g'ri qo'ysangiz nima bo'ladi? - (Oson) Orkestrator
Pluginsinfi nima vazifani bajaradi? Nega har komponentni alohida emas, bir joydanregister()qilamiz? - (Oson)
flush_rewrite_rules()ni nega faqat aktivatsiyada chaqiramiz, har so'rovda emas? - (Oson) Fayl daraxtida
src/vabuild/papkalari nima uchun ajratilgan? Qaysi biri JSX manbasi, qaysi biri kompilyatsiya natijasi? - (Oson) REST endpoint'da
permission_callback => '__return_true'qachon to'g'ri, qachon halokat? Bizningkitkat/v1/kitoblaruchun nega to'g'ri? - (Oson)
uninstall.phpdaWP_UNINSTALL_PLUGINni tekshirish nega muhim? U deaktivatsiyada ham chaqiriladimi?
O'rta¶
- (O'rta) Orkestrator
boot()da WooCommerce komponentiniclass_exists(\WooCommerce::class)shart bilan qo'shamiz. Nega? Shartsiz qo'shsangiz, WC o'rnatilmagan saytda nima bo'ladi?
Yechim
WooCommerce komponenti \WC_Product va \Automattic\WooCommerce\Utilities\FeaturesUtil kabi WC sinflariga tayanadi. Agar WC o'rnatilmagan bo'lsa, bu sinflar mavjud emas. Komponentni shartsiz yuklasangiz va u WC sinfini ishlatsa β fatal error (Class not found), butun sayt ishlamay qoladi.
class_exists darvozasi β integratsiyani ixtiyoriy qiladi: WC bor bo'lsa ishlaydi, yo'q bo'lsa jim turadi. Bu β har qanday tashqi plugin integratsiyasining standart naqshi.
- (O'rta)
KitobMeta::saqla()da to'rt tekshiruv bor (nonce, autosave, capability, sanitize). Har birini olib tashlasangiz qaysi zaiflik/xato ochiladi? Tartibni tushuntiring.
Yechim
- Nonce yo'q -> CSRF: boshqa sayt foydalanuvchi brauzeri orqali soxta
save_postso'rovi yuboradi. - Autosave tekshiruvi yo'q -> WordPress avtosaqlashida
$_POST['kitkat_muallif']bo'sh bo'ladi, mavjud qiymat ustiga bo'sh yoziladi (ma'lumot yo'qoladi). - Capability yo'q -> ruxsatsiz foydalanuvchi (yoki boshqa post egasi) meta'ni o'zgartiradi (IDOR).
- Sanitize yo'q -> XSS: tozalanmagan
$_POSTto'g'ridan-to'g'ri bazaga, keyin chiqishda zarar.
Tartib muhim: nonce va autosave erta qaytaradi (keraksiz ishni qilmaymiz), capability ruxsatni tekshiradi, sanitize esa eng oxirida ma'lumotni tozalaydi. wp_unslash sanitizatsiyadan oldin β WordPress qo'shgan slash'larni olib tashlaydi.
- (O'rta)
register_post_metadashow_in_rest => truevaauth_callbackbor. Bu ikkalasi birga nima qiladi?auth_callbackni olib tashlasangiz xavf nima?
Yechim
show_in_rest => true meta'ni REST API orqali o'qish va yozish uchun ochadi (blok muharriri shuni ishlatadi). auth_callback esa yozishni kim qila olishini nazorat qiladi:
Olib tashlasangiz, WordPress standart auth_callback'ni ishlatadi (post tahrirlash huquqiga bog'liq), lekin uni aniq belgilash β niyatni ravshan qiladi va xavfsizroq. Eng yomon holat: noto'g'ri sozlangan meta REST orqali ruxsatsiz yozilishi mumkin. Har doim aniq auth_callback bering.
- (O'rta) REST endpoint'da
sonargumentigavalidate_callbackqo'ydik (1..100). Negasanitize_callback => 'absint'o'zi yetarli emas? Ikkalasi qanday farq qiladi?
Yechim
sanitize_callbackqiymatni o'zgartiradi (xom -> toza).absint-5ni5ga,"abc"ni0ga aylantiradi β lekin baribir qabul qiladi.validate_callbackqiymatni rad etadi (noto'g'ri bo'lsa400xato). Bu yerda(int) $v > 0 && (int) $v <= 100cheklovini majburlaydi.
absint o'zi ?son=999999 ni 999999 qilib o'tkazadi β DoS xavfi. validate_callback 100 dan oshganini rad etadi. Ikkalasi birga: validate (chegara) + sanitize (tur). Bu defense-in-depth (29-bob): bir qatlam tursa, ikkinchisi himoya qiladi.
- (O'rta)
kitkat/v1/kitoblarendpoint'igaformatargumenti qo'shing:format=qisqabo'lsa faqatidvanomqaytsin, aks holda to'liq.validate_callbackbilan ruxsat etilgan qiymatlarni cheklang.
Yechim
'args' => [
// ... mavjud janr, son ...
'format' => [
'type' => 'string',
'default' => 'toliq',
'enum' => [ 'toliq', 'qisqa' ],
'sanitize_callback' => 'sanitize_key',
],
],
enum β REST'ning o'zi ruxsat etilgan qiymatlarni cheklaydigan eng sodda yo'li (validate_callback o'rniga). Callback ichida:
$format = $request['format'];
foreach ( $query->posts as $post ) {
$satr = [ 'id' => $post->ID, 'nom' => get_the_title( $post ) ];
if ( 'toliq' === $format ) {
$satr['havola'] = get_permalink( $post );
$satr['muallif'] = (string) get_post_meta( $post->ID, KitobMeta::MUALLIF, true );
$satr['isbn'] = (string) get_post_meta( $post->ID, KitobMeta::ISBN, true );
}
$natija[] = $satr;
}
enum β schema darajasidagi allowlist; noto'g'ri qiymat avtomatik 400 oladi.
- (O'rta) Settings API'da
tozala()sof-PHP funksiyasi['son' => '555']ni nimaga aylantiradi?'-3'chi?'abc'chi? Mantiqni tushuntiring.
Yechim
public function tozala( mixed $xom ): array {
$xom = is_array( $xom ) ? $xom : [];
$son = isset( $xom['son'] ) ? absint( $xom['son'] ) : 10;
return [ 'son' => max( 1, min( 100, $son ) ) ];
}
['son' => '555']->absint555 ->min(100, 555)= 100 -> 100.['son' => '-3']->absint3 (musbat) ->max(1, 3)= 3 -> 3. (absintbelgini olib tashlaydi.)['son' => 'abc']->absint('abc')= 0 ->max(1, 0)= 1 -> 1.'abc'(massiv emas) ->is_arrayfalse ->[]->sonyo'q -> default 10 -> 10.
max(1, min(100, $son)) β qiymatni 1..100 oralig'iga "qisadi" (clamp). Bu sof-PHP mantiq, WP'siz php bilan test qilinadi.
Qiyin¶
- (Qiyin) Orkestratorga o'z hook'ingizni qo'shing: kitob saqlangach
do_action('kitkat_kitob_saqlandi', $post_id, $muallif)ishga tushsin. Boshqa dasturchi bu hook'ga ulanib log yozsin. Filter va action farqini ko'rsating.
Yechim
KitobMeta::saqla() oxirida (saqlash muvaffaqiyatli bo'lgach):
update_post_meta( $post_id, self::MUALLIF, $muallif );
update_post_meta( $post_id, self::ISBN, $isbn );
// O'z action hook'imiz β boshqa dasturchilar ulanishi mumkin.
do_action( 'kitkat_kitob_saqlandi', $post_id, $muallif );
Boshqa plugin/tema ulanadi (masalan log yozadi):
add_action(
'kitkat_kitob_saqlandi',
static function ( int $post_id, string $muallif ): void {
error_log( sprintf( 'Kitob %d saqlandi, muallif: %s', $post_id, $muallif ) );
},
10,
2 // accepted_args = 2 (post_id + muallif).
);
Action vs filter: do_action β "hodisa yuz berdi" e'loni; qaytaruvchi qiymat yo'q (yon ta'sir: log, email). Agar saqlashdan oldin muallifni o'zgartirmoqchi bo'lsangiz β apply_filters('kitkat_muallif', $muallif, $post_id) ishlatasiz, u o'zgartirilgan qiymat qaytaradi. Bu β sizning plugin'ingizni kengaytiriladigan qiladi (boshqalar yadroga tegmasdan o'zgartiradi) β 04-bobning to'liq aylanasi.
- (Qiyin) GitHub Actions CI yarating: har push'da
composer install,npm install && npm run build,phpcsvaphpunitishga tushsin. Bittasi qizil bo'lsa, build to'xtasin.
Yechim
.github/workflows/ci.yml:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
tools: composer
- uses: actions/setup-node@v4
with:
node-version: '24'
- run: composer install --no-progress
- run: npm ci
- run: npm run build
- run: composer exec phpcs
- run: composer exec phpunit
Har qadam ketma-ket; biror qadam nol bo'lmagan kod bilan tugasa (phpcs xato topsa, test yiqilsa), GitHub Actions butun job'ni qizil belgilaydi va keyingi qadamlar ishlamaydi. Bu β reliz darvozasini avtomatlashtirish (RUNBOOK'dagi 1-3 qadamlar). npm ci β npm install'dan tezroq va package-lock.json'ga qat'iy amal qiladi (CI uchun afzal).
- (Qiyin)
KitoblarControllerni to'liq yozing (REST endpoint), so'ngWP_REST_Request'ni mock qilibkitoblar()callback'ini test qiling (WP integration testi). Nima sof-PHP, nima WP'ga tayanadi?
Yechim
To'liq controller yuqorida (6-qadam). WP integration testi (wp-env + WP PHPUnit, 25-bob):
<?php
namespace Oqil\KitobKatalog\Tests;
use WP_UnitTestCase;
use Oqil\KitobKatalog\Rest\KitoblarController;
final class KitoblarControllerTest extends WP_UnitTestCase {
public function test_publish_kitoblar_qaytadi(): void {
// Test ma'lumoti yaratamiz (factory WP'da mavjud).
self::factory()->post->create( [ 'post_type' => 'kitob', 'post_status' => 'publish', 'post_title' => 'Test kitob' ] );
$request = new \WP_REST_Request( 'GET', '/kitkat/v1/kitoblar' );
$request->set_param( 'son', 5 );
$controller = new KitoblarController();
$response = $controller->kitoblar( $request );
$this->assertSame( 200, $response->get_status() );
$data = $response->get_data();
$this->assertNotEmpty( $data );
$this->assertSame( 'Test kitob', $data[0]['nom'] );
}
}
Sof-PHP vs WP: kitoblar() callback'i WP_Query, get_the_title, get_post_meta ga tayanadi β bular faqat ishlab turgan WPda mavjud. Shuning uchun bu integration testi (WP_UnitTestCase, wp-env ichida ishlaydi), sof unit emas. isbn_tozala kabi mantiqni esa WP'siz test qilardik. Test piramidasi: ko'p sof unit (tez), kamroq integration (sekin, lekin haqiqiy) β 25-bob.
- (Qiyin) Plugin'ni butunlay bitta CPT registratsiyasidan boshlab, fayl daraxtini yarating,
composer.jsonPSR-4 ni sozlang, vacomposer dump-autoloadbilan autoload ishlashini tasdiqlang. To'liq minimal "skelet" bering.
Yechim
composer.json:
{
"name": "oqil/kitoblar-katalogi",
"type": "wordpress-plugin",
"license": "GPL-2.0-or-later",
"require": { "php": ">=8.3" },
"autoload": {
"psr-4": { "Oqil\\KitobKatalog\\": "src/" }
}
}
Minimal skelet:
kitoblar-katalogi/
βββ kitoblar-katalogi.php # header + require vendor/autoload.php + boot
βββ composer.json
βββ src/
βββ Plugin.php
βββ PostTypes/KitobPostType.php
kitoblar-katalogi.php ichida:
require __DIR__ . '/vendor/autoload.php';
add_action( 'plugins_loaded', static fn() => \Oqil\KitobKatalog\Plugin::instance()->boot() );
Tasdiqlash:
composer dump-autoload # vendor/autoload.php ni qayta yaratadi
php -r "require 'vendor/autoload.php'; var_dump(class_exists('Oqil\\KitobKatalog\\PostTypes\\KitobPostType'));"
# bool(true) -> PSR-4 autoload ishlaydi
PSR-4: namespace Oqil\KitobKatalog\PostTypes\KitobPostType -> fayl src/PostTypes/KitobPostType.php. Composer namespace prefiksini src/ papkasiga moslaydi. Bu sof-PHP β WordPress'siz php -r bilan tekshiriladi.
- (Qiyin)
readme.txtni to'liq yozing (wordpress.org formati) vaStable tag, headerVersionva SVNtags/papkasi qanday bog'lanishini tushuntiring. Reliz jarayonini bosqichma-bosqich bering.
Yechim
readme.txt (28-bob):
=== Kitoblar katalogi ===
Contributors: ioqil
Tags: books, catalog, custom post type, gutenberg, rest-api
Requires at least: 7.0
Tested up to: 7.0
Requires PHP: 8.3
Stable tag: 1.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Kitoblarni CPT, janr, REST va Gutenberg blok bilan boshqaradigan plugin.
== Description ==
Kitoblar katalogi: kitob CPT, janr taxonomy, muallif/ISBN meta, REST endpoint va "Kitoblar ro'yxati" bloki.
== Changelog ==
= 1.0.0 =
* Birinchi reliz.
Bog'lanish: wordpress.org Stable tag: 1.0.0 ni o'qiydi -> tags/1.0.0/ papkasidagi kodni foydalanuvchiga beradi. Plugin header'idagi Version: 1.0.0 tags/1.0.0 bilan bir xil bo'lishi shart.
Reliz bosqichlari:
1. Kodni tayyorla, npm run build, phpcs, phpunit -> yashil.
2. Header Version va readme Stable tag ni 1.0.0 ga qo'y.
3. SVN: trunk/ ga nusxala, svn ci.
4. svn cp trunk tags/1.0.0 -> teg yarat.
5. svn ci -m "1.0.0 reliz".
wordpress.org Stable tag'ni ko'rib, tags/1.0.0 ni tarqatadi. Header bilan mos kelmasa β yangilanish ko'rsatkichi buziladi.
- (Qiyin) Premium ("pro") qo'shimcha plugin g'oyasini loyihalang: bepul
kitoblar-katalogi"core" hook'lar chiqarsin, "pro" plugin ularga ulanib qo'shimcha xususiyat (masalan kitob reytingi) qo'shsin. Core'da qanday hook'lar kerak?
Yechim
Freemium arxitektura: "core" plugin'da kengaytirish nuqtalari (hook'lar) bo'lishi kerak β xuddi WordPress yadrosi sizga bergandek.
Core'da (kitoblar-katalogi):
// REST javobini kengaytirish uchun filter:
$satr = apply_filters( 'kitkat_rest_kitob_satr', $satr, $post->ID );
// Blok render'idan keyin qo'shimcha kontent uchun action:
do_action( 'kitkat_blok_kitobdan_keyin', $post->ID );
// Meta box maydonlarini kengaytirish uchun action:
do_action( 'kitkat_meta_box_maydonlar', $post );
"Pro" plugin (kitoblar-katalogi-pro) ulanadi:
// Reytingni REST javobiga qo'shadi:
add_filter( 'kitkat_rest_kitob_satr', static function ( array $satr, int $id ): array {
$satr['reyting'] = (float) get_post_meta( $id, '_kitkat_reyting', true );
return $satr;
}, 10, 2 );
Talablar: (1) Core hook'larni hujjatlang (boshqa dasturchilar ham ishlatadi). (2) Hook nomlarini barqaror saqlang (o'zgartirish β buzilish). (3) Pro plugin core mavjudligini tekshirsin (is_plugin_active yoki class_exists). (4) Litsenziya/yangilanish API'si pro uchun alohida (wordpress.org pulli plugin'ni qabul qilmaydi β o'z serveringizdan tarqatasiz). Bu β sizning plugin'ingizni platformaga aylantiradi: 04-bobning hook falsafasi to'liq doira bo'lib qaytadi.
β¬ οΈ Oldingi: 29 β Xavfsizlik auditi (chuqur) Β· π README