Home > Web Front-end > CSS Tutorial > Getting Started With WordPress Block Development

Getting Started With WordPress Block Development

Christopher Nolan
Release: 2025-03-10 11:11:10
Original
141 people have browsed it

Getting Started With WordPress Block Development

WordPress development is currently undergoing a transitional period. Whether you are a newbie or a senior developer, the introduction of "Site-wide Editing" (FSE) features, including Block Editor (WordPress 5.0) and Site Editor (WordPress 5.9), has subverted the traditional way we build WordPress themes and plugins.

Although we have been in contact with Block Editor for five years, its development is still challenging because the documentation is either missing or outdated. This reflects more the current situation of rapid iteration of FSE functions.

For example, in 2018, a series of articles on introduction to Gutenberg development was published on CSS-tricks. But time has passed, and although that development method is still effective, it is no longer recommended (in addition, the create-guten-block project it is based on is no longer maintained).

This article is intended to help you start WordPress block development and follow the current approach. Of course, things may change after this article is published. But I will try to focus on its core idea, because even if the tool develops over time, the core concept is likely to remain the same.

What exactly is a WordPress block?

First, let's clarify some terms, such as "block". All developments of these features prior to WordPress 5.0 were used with the code name "Gutenberg" (inventor of printing).

Since then, "Gutenberg" has been used to describe everything related to blocks, including block editors and site editors, so it has become so complicated that some people hate the name. Most importantly, there is also a Gutenberg plugin for testing experimental features that may be included in the future. If you think calling all of this “website-wide editing” can solve the problem, there are also some concerns.

So, in this article, when we refer to "blocks", we refer to components that create content in WordPress block editor. Blocks are inserted into pages or articles and provide structure for specific types of content. WordPress comes with some "core" blocks for common content types, such as paragraphs, lists, images, videos, audio, and more.

In addition to these core blocks, we can also create custom blocks. This is the focus of WordPress block development (you can also filter core blocks to modify their functionality, but you may not need them for the time being).

The role of blocks

Before we dive into creating a block, we must first understand how the block works internally. This will definitely save us a lot of trouble in the future.

I like to understand blocks abstractly as: an entity with certain attributes (called attributes) that represent certain content. I know this sounds vague, but please be patient with me to explain. Blocks are basically manifested in two ways: as graphical interfaces in the block editor, or as data blocks in the database.

When you open the WordPress block editor and insert a block (for example, a Pullquote block), you get a nice interface. You can click on the interface and edit the referenced text. The Settings panel on the right side of the Block Editor UI provides options for adjusting text and setting the appearance of a block.

When you finish creating a beautiful quote and clicking Publish, the entire post is stored in the wp_posts table of the database. This is nothing new due to Gutenberg. This is how things have always worked - WordPress stores article content in a specified table in the database. But the new one is that the representation of the Pullquote block is part of the content stored in the post_content field of the wp_posts table.

What does this expression look like? Let's take a look:

<blockquote>
    <p>It is not an exaggeration to say that peas can be described as nothing less than perfect spheres of joy.</p>
    <cite>The Encyclopedia of world peas</cite>
  </blockquote>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Looks like normal HTML, right? ! In technical terms, this is the "serialized" block. Note the JSON data in HTML comments, "textAlign": "right". This is a

attribute - a block-related attribute.

Suppose you close the block editor and then open it again after a while. The block editor will retrieve the contents in the relevant post_content field. The editor then parses the retrieved content and when encountering the following:

<code>...</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
It will talk to itself:

Okay, this looks like a Pullquote block. Well...it also has a property...I do have a JavaScript file that tells me how to construct a graphical interface of a Pullquote block in the editor based on its properties. I should do this now so that this block is presented in its full glory.

As a block developer, your job is:

    Tell WordPress that you want to register a specific type of block, as well as related details.
  1. Provides JavaScript files to the block editor, which will help it render the block in the editor while "serializing" it to save it to the database.
  2. Provides any other resources required for the block to function properly, such as styles and fonts.
It is important to note that all this conversion from serialized data to graphical interfaces—and vice versa— occurs only in the block editor. On the front end, the content is displayed and stored in exactly the same way. So, in a sense, blocks are a clever way to put data into a database.

Hope this gives you a clearer understanding of how blocks work.

Blocks are just plugins

Blocks are just plugins. Well, technically you can put

blocks in the theme or multiple blocks in one plugin. However, usually if you want to make a block, you will want to make a plugin. So if you've ever created a WordPress plugin, you've partially mastered how to make WordPress blocks. But let's assume you've never set up a WordPress plugin, let alone blocks. Where do you even start?

Set block

We have already introduced what a block is. Let's start setting up to make one.

Make sure Node is installed

This will give you access to npm and npx commands, where npm installs the dependencies of the block and helps compile the content, while npx runs commands on the package without installing the package. If you are using macOS, you may have Node installed and you can use nvm to update the version. If you are using Windows, you need to download and install Node.

Create project folder

Now you may come across other tutorials that jump directly to the command line and instruct you to install a package called @wordpress/create-block. This package is great because it generates a complete project folder with all the dependencies and tools you need to start your development.

I personally take this approach when setting up my own blocks, but please tolerate me for a moment, as I want to eliminate the subjective content it introduces and focus only on the necessary parts of the basic development environment for understanding.

These are the documents I want to point out specifically:

  • readme.txt: This is a bit like the front of the plugin directory, which is usually used to describe the plugin and provide more details about usage and installation. If you submit a block to the WordPress plugin directory, this file helps populate the plugin page. If you plan to create a GitHub repository for the block plugin, you might also consider using a README.md file with the same information so that it displays well there.
  • package.json: This defines the Node package required for development. We will open it when installing. In classic WordPress plugin development, you may be used to using Composer and composer.json files. This is its equivalent.
  • plugin.php: This is the main plugin file, yes, it is classic PHP! We will place the plugin title and metadata here and use it to register the plugin.

In addition to these files, there is also an src directory, which should contain the source code of the block.

Having these files and src directories is enough to start. In that group, please note that we actually only need a file (plugin.php) to make the plugin. The rest either provides information or is used to manage the development environment.

The aforementioned @wordpress/create-block package builds these files for us (and more). You can think of it as an automation tool, not a necessity. It does make the work easier anyway, so you can freely use it to build blocks by running the following command:

<blockquote>
    <p>It is not an exaggeration to say that peas can be described as nothing less than perfect spheres of joy.</p>
    <cite>The Encyclopedia of world peas</cite>
  </blockquote>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Installing Block Dependencies

Suppose you have prepared the three files mentioned in the previous section and it is time to install the dependencies. First, we need to specify the dependencies we will need. We do this by editing package.json. When using the @wordpress/create-block utility, the following is generated for us (annotation was added; JSON does not support annotation, so if you copy the code, please delete the annotation):

<blockquote>
    <p>It is not an exaggeration to say that peas can be described as nothing less than perfect spheres of joy.</p>
    <cite>The Encyclopedia of world peas</cite>
  </blockquote>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

View uncommented version ```json { "name": "block-example", "version": "0.1.0", "description": "Example block scaffolded with Create Block tool.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", "main": "build/index.js", "scripts": { "build": "wp-scripts build", "format": "wp-scripts format", "lint:css": "wp-scripts lint-style", "lint:js": "wp-scripts lint-js", "packages-update": "wp-scripts packages-update", "plugin-zip": "wp-scripts plugin-zip", "start": "wp-scripts start" }, "devDependencies": { "@wordpress/scripts": "^24.1.0" } }

<code>...</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Now that package.json is set up, we should be able to install all these dependencies by navigating to the project folder in the command line and running npm install.

Add plugin title

If you are from classic WordPress plugin development, you probably know that all plugins have a piece of information in the main plugin file, which helps WordPress recognize the plugin and display information about it on the Plug-in screen in the WordPress admin panel.

The following is a plugin created for me by @wordpress/create-block for a creatively called "Hello World":

npx @wordpress/create-block
Copy after login
Copy after login
Copy after login
Copy after login

This is in the main plugin file and you can name it as you like. You can name it a common name like index.php or plugin.php. The create-block package automatically names it as the project name you provided at installation. Since I'm referring to this example as "Block Example", the package gives us a block-example.php file with all of this.

You need to change some details, such as making yourself an author, etc. Not all of this is necessary. If I start with "head", then it may be closer to this:

{
  // 定义项目名称
  "name": "block-example",
  // 使用语义版本控制设置项目版本号
  "version": "0.1.0",
  // 项目的简短描述
  "description": "Example block scaffolded with Create Block tool.",
  // 您可以将其替换为您自己
  "author": "The WordPress Contributors",
  // 标准许可信息
  "license": "GPL-2.0-or-later",
  // 定义主JavaScript文件
  "main": "build/index.js",
  // 我们在开发过程中构建和编译插件所需的一切
  "scripts": {
    "build": "wp-scripts build",
    "format": "wp-scripts format",
    "lint:css": "wp-scripts lint-style",
    "lint:js": "wp-scripts lint-js",
    "packages-update": "wp-scripts packages-update",
    "plugin-zip": "wp-scripts plugin-zip",
    "start": "wp-scripts start"
  },
  // 定义使用脚本包的哪个版本(撰写本文时为24.1.0)
  // https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/
  "devDependencies": {
    "@wordpress/scripts": "^24.1.0"
  }
}
Copy after login
Copy after login
Copy after login

I won't go into detail about the exact purpose of each line, as this is already a complete pattern in the WordPress plugin manual.

File structure

We have viewed the files required for the block. However, if you are using @wordpress/create-block, you will see many other files in the project folder.

The following is what is currently included:

<code>
这里的@wordpress/scripts包是主要的依赖项。如您所见,它是一个devDependency,这意味着它有助于开发。如何?它公开了wp-scripts二进制文件,我们可以使用它来编译我们的代码,从src目录到build目录,等等。

WordPress维护了许多其他软件包,用于各种目的。例如,@wordpress/components软件包为WordPress区块编辑器提供了一些预制UI组件,可用于为区块创建一致的用户体验,并符合WordPress设计标准。

您实际上*不需要*安装这些软件包,即使您想使用它们也是如此。这是因为这些@wordpress依赖项不会与您的区块代码捆绑在一起。相反,任何引用实用程序软件包代码的导入语句——例如@wordpress/components——都用于在编译期间构造“资产”文件。此外,这些导入语句被转换为将导入映射到全局对象的属性的语句。例如,import { \_\_ } from "@wordpress/i18n"被转换为const \_\_ = window.wp.i18n.\_\_的缩小版本。(window.wp.i18n是一个保证在全局范围内可用的对象,一旦相应的i18n软件包文件被排队)。

在插件文件中注册区块期间,隐式使用“资产”文件来告诉WordPress区块的软件包依赖项。这些依赖项会自动排队。所有这些都在幕后完成,前提是您使用的是scripts软件包。也就是说,您仍然可以选择在package.json文件中本地安装依赖项以进行代码完成和参数信息:

```json
// etc.
"devDependencies": {
  "@wordpress/scripts": "^24.1.0"
},
"dependencies": {
  "@wordpress/components": "^19.17.0"
}</code>
Copy after login

Wow, so much! Let's point out new content:

  • build/: This folder receives compiled assets when processing production-purpose files.
  • node_modules: This folder holds all development dependencies installed when we run npm install.
  • src/: This folder holds the source code of the plugin, which will be compiled and sent to the build directory. We'll look at each of them later.
  • .editorconfig: This contains configurations for tweaking the code editor for code consistency.
  • .gitignore: This is a standard repository file that identifies local files that should be excluded from version control tracking. Your node_modules should definitely be included here.
  • package-lock.json: This is an automatically generated file that tracks the updates of required packages installed using npm install.

Block metadata

I want to dig into the src directory with you, but first focus on one of the files: block.json. If you are using create-block, it is ready for you; if not, keep creating it. WordPress is pushing hard to use it as a standard specification method to register a block by providing metadata that provides context for WordPress to simultaneously identify the block and render it in the block editor.

The following is what @wordpress/create-block generates for me:

<blockquote>
    <p>It is not an exaggeration to say that peas can be described as nothing less than perfect spheres of joy.</p>
    <cite>The Encyclopedia of world peas</cite>
  </blockquote>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Actually, we can include a lot of different information here, but we really only need name and title. A super-lite version might look like this:

<blockquote>
    <p>It is not an exaggeration to say that peas can be described as nothing less than perfect spheres of joy.</p>
    <cite>The Encyclopedia of world peas</cite>
  </blockquote>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
  • $schema defines the schema format used to verify the content of the file. It sounds like a required thing, but it is totally optional as it allows supported code editors to verify syntax and provides additional features such as tooltips and autocomplete.
  • apiVersion refers to which version of the Block API used by the plugin. Today, version 2 is the latest version.
  • name is a required unique string that helps identify plugins. Note that I've added css-tricks/ in front of it, which I use as a namespace to help avoid conflicts with other plugins that may have the same name. You can choose to use something like your acronym (such as as/block-example).
  • version is recommended by WordPress as a cache clearing mechanism when releasing new versions.
  • title is another required field that sets the name used anywhere the display plugin is.
  • category groups blocks with other blocks and displays them together in the block editor. Categories currently exist include text, media, design, widgets, themes, and embeddings, and you can even create custom categories.
  • icon allows you to select something from the Dashicons library to visually represent your block in the block editor. I use the format-quote icon because we are making our own pullquote and something like that in this example. We can take advantage of existing icons instead of having to create our own, which is great, although this is certainly possible.
  • editorScript is the location where the main JavaScript file index.js is located.

Register block

One more thing before we get into the actual code is to register the plugin. We just set up all this metadata and we need a way to get WordPress to use it. This way WordPress knows where to find all the plugin assets so that they can be queued for use in the block editor.

Registering a block is a two-step process. We need to register it in PHP and JavaScript. For the PHP aspect, open the main plugin file (in this case block-example.php) and add the following after the plugin title:

<code>...</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

This is what the create-block utility generates for me, which is why the function is named in such a way. We can use different names. The key is to avoid conflicts with other plugins, so it's better to use your namespace here to make it as unique as possible:

npx @wordpress/create-block
Copy after login
Copy after login
Copy after login
Copy after login

If the block.json containing all block metadata is in src, why do we point to the build directory? This is because our code still needs to be compiled. The scripts package processes the code of files in the src directory and places the compiled files used in production in the build directory, while copy block.jsonfile.

Okay, let's go to the JavaScript aspect of the registration block. Open src/index.js and make sure it looks like this:

{
  // 定义项目名称
  "name": "block-example",
  // 使用语义版本控制设置项目版本号
  "version": "0.1.0",
  // 项目的简短描述
  "description": "Example block scaffolded with Create Block tool.",
  // 您可以将其替换为您自己
  "author": "The WordPress Contributors",
  // 标准许可信息
  "license": "GPL-2.0-or-later",
  // 定义主JavaScript文件
  "main": "build/index.js",
  // 我们在开发过程中构建和编译插件所需的一切
  "scripts": {
    "build": "wp-scripts build",
    "format": "wp-scripts format",
    "lint:css": "wp-scripts lint-style",
    "lint:js": "wp-scripts lint-js",
    "packages-update": "wp-scripts packages-update",
    "plugin-zip": "wp-scripts plugin-zip",
    "start": "wp-scripts start"
  },
  // 定义使用脚本包的哪个版本(撰写本文时为24.1.0)
  // https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/
  "devDependencies": {
    "@wordpress/scripts": "^24.1.0"
  }
}
Copy after login
Copy after login
Copy after login

We are entering the React and JSX world! This tells WordPress:

  • Import registerBlockType module from @wordpress/blocks package.
  • Import metadata from block.json.
  • Import Edit and Save components from their corresponding files. We will add code to these files later.
  • Register blocks and use Edit and Save components to render blocks and save contents to the database.

What's going on with Edit and save functions? One of the subtleties of WordPress block development is the distinction between "backend" and "frontend", which are used to render the content of the block in these contexts, where edit handles the backend rendering, save writes the content from the block editor to the database to render the content on the frontend of the website.

Quick Test

We can quickly operate and see if our blocks work in the block editor and render on the front end. Let's open index.js again and use the edit and save functions to return some basics to illustrate how they work:

<blockquote>
    <p>It is not an exaggeration to say that peas can be described as nothing less than perfect spheres of joy.</p>
    <cite>The Encyclopedia of world peas</cite>
  </blockquote>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

This is basically a slim version of our previous code, just that we point directly to the metadata in block.json to get the block name and omit the Edit and Save components because we run the function directly from here.

We can compile it by running npm run build on the command line. After that, we can access the block named "Block Example" in the block editor:

If we put the block into the content area, we will get the message returned from the edit function:

If we save and publish the article, when viewing in front-end, we should get the message returned from the save function:

Create a block

It looks like everything is normal! Now that we have confirmed that everything is fine, we can restore to what is in index.js before the test:

<code>...</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Note that the edit and save functions are associated with the two existing files generated by @wordpress/create-block in the src directory and contain other imports we need in each file. More importantly, these files create Edit and Save components containing block tags.

Backend tag (src/edit.js)

npx @wordpress/create-block
Copy after login
Copy after login
Copy after login
Copy after login

Look at what we did? We are importing props from the @wordpress/block-editor package, which allows us to generate classes that can be used for styles later. We also import the __ internationalization function for processing translation.

Front-end tag (src/save.js)

This will create a Save component, we will use almost the same content as src/edit.js, but with slightly different text:

{
  // 定义项目名称
  "name": "block-example",
  // 使用语义版本控制设置项目版本号
  "version": "0.1.0",
  // 项目的简短描述
  "description": "Example block scaffolded with Create Block tool.",
  // 您可以将其替换为您自己
  "author": "The WordPress Contributors",
  // 标准许可信息
  "license": "GPL-2.0-or-later",
  // 定义主JavaScript文件
  "main": "build/index.js",
  // 我们在开发过程中构建和编译插件所需的一切
  "scripts": {
    "build": "wp-scripts build",
    "format": "wp-scripts format",
    "lint:css": "wp-scripts lint-style",
    "lint:js": "wp-scripts lint-js",
    "packages-update": "wp-scripts packages-update",
    "plugin-zip": "wp-scripts plugin-zip",
    "start": "wp-scripts start"
  },
  // 定义使用脚本包的哪个版本(撰写本文时为24.1.0)
  // https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/
  "devDependencies": {
    "@wordpress/scripts": "^24.1.0"
  }
}
Copy after login
Copy after login
Copy after login

Similarly, we got a nice class that can be used in CSS:

Style block

We just introduced how to use block properties to create classes. You are reading this on a CSS-related website, so I feel like I'd miss something if we didn't specifically cover how to write block styles.

Distinguish between front-end and back-end styles

If you look at block.json in the src directory, you will find two style-related fields:

  • editorStyle provides the path to the style applied to the backend.
  • style is the path to the shared styles applied to the front-end and back-end.

Kev Quirk has a detailed article showing his way of making the backend editor look like a frontend UI.

Recall that the @wordpress/scripts package copies the block.json file when it processes the code in the /src directory and places the compiled assets in the /build directory. The build/block.json file used to register the block. This means that any path we provide in src/block.json should be written relative to build/block.json.

Using Sass

We can put several CSS files into the build directory, refer to the path in src/block.json, run build, and then it's done. But this does not take full advantage of the @wordpress/scripts compilation process, which can compile Sass into CSS. Instead, we put the style files in the src directory and import them in JavaScript.

When doing this, we need to pay attention to how @wordpress/scripts handles styles:

  • The file named style.css or style.scss or style.sass imported into JavaScript code will be compiled into style-index.css.
  • All other style files will be compiled and bundled into index.css.

@wordpress/scripts packages are bundled using webpack and styled using PostCSS plugin. PostCSS can be extended with other plugins. The scripts package uses plugins from Sass, SCSS, and Autoprefixer, all of which can be used without installing additional packages.

In fact, when you start the initial block with @wordpress/create-block, you can get started with SCSS files very well, you can use these files to get started quickly:

  • editor.scss contains all the styles applied to the backend editor.
  • style.scss contains all styles shared by the front-end and back-end.

Now let's do it by writing some Sass that we will compile into block CSS. Although these examples are not very Sassized, I still write them to a SCSS file to demonstrate the compilation process.

Front-end and back-end styles

OK, let's start with the styles applied to the front-end and back-end. First, we need to create src/style.scss (if you are using @wordpress/create-block, it already exists) and make sure we import it, we can do this in index.js:

<blockquote>
    <p>It is not an exaggeration to say that peas can be described as nothing less than perfect spheres of joy.</p>
    <cite>The Encyclopedia of world peas</cite>
  </blockquote>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Open src/style.scss and put some basic styles in it using the class we generated from the block attribute:

<code>...</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

That's all now! When we run build, this compiles to build/style.css and is referenced by the block editor and front-end.

Back-end style

You may need to write block editor-specific styles. To do this, create src/editor.scss (again, @wordpress/create-block does this for you) and put some styles in it:

npx @wordpress/create-block
Copy after login
Copy after login
Copy after login
Copy after login

Then import it in edit.js, which contains our Edit component (we can import it anywhere, but since these styles are for editors, it's more logical to import components here):

<blockquote>
    <p>It is not an exaggeration to say that peas can be described as nothing less than perfect spheres of joy.</p>
    <cite>The Encyclopedia of world peas</cite>
  </blockquote>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Now, when we run npm run build, the style will be applied to blocks in both contexts:

Reference style in block.json

We imported the style files in edit.js and index.js, but remember that the compilation step generates two CSS files for us in the build directory: index.css and style-index.css. We need to reference these generated files in the block metadata.

Let's add a few statements to the block.json metadata:

<code>...</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Run npm run build again, install and activate the plugin on your WordPress website and you're ready to use it!

You can run your build in monitoring mode using npm run start, and your code will be automatically compiled every time you change the code and save it.

We just touched the surface

The actual block uses the Settings sidebar of the Block Editor and other features provided by WordPress to create a rich user experience. Also, since the block actually has two versions - edit and save - you also need to think about how to organize your code to avoid duplication of code.

But hopefully this helps to remove the mystery of the overall process of creating WordPress blocks. This is indeed a new era of WordPress development. Learning new ways of doing things is difficult, but I look forward to seeing it develop. Tools like @wordpress/create-block help, but even then it's nice to know what it's doing and why.

Will the content we introduce here change? Very likely! But at least you have a baseline for reference, as we continue to focus on the maturity of WordPress blocks, including best practices for making them.

Reference

Again, my goal is to plan an efficient path to enter block development in this fast-growing era, and WordPress documents are a little hard to keep up. Here are some resources I used to organize these contents:

  • Key Concepts (WordPress Block Editor Manual)
  • Create a Block Tutorial (WordPress Block Editor Manual)
  • Metadata in block.json (WordPress Block Editor Manual)
  • Source code of core block (WordPress GitHub)
  • webpack configuration used in @wordpress/scripts package (WordPress GitHub)
  • Backend Engineer Learning to Build Block Editor Blocks, Part 1 (Tom McFarlin)
  • Ryan Welcher's Twitch live broadcast (Ryan is an Automattic developer advocate)

The above is the detailed content of Getting Started With WordPress Block Development. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template