Professional DotNetNuke ASP.NET Portals wrox phần 9 pdf

45 293 0
Professional DotNetNuke ASP.NET Portals wrox phần 9 pdf

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

16_595636 ch12.qxd 5/10/05 9:53 PM Page 330 Skinning DotNetNuke The ability to skin DotNetNuke was introduced in version 2 of the application and was a much- anticipated addition. The term skinning refers to an application’s ability to change the look of the design by a setting in the application. This allows for the separation of the application logic from the user interface or object abstraction. As you learned in previous chapters, DotNetNuke utilizes a three-tier object-oriented design approach, with the user interface segmented as its own tier. This is what allows skinning to work and the application to be able to present a unique feel depending on the parameters passed to the page. This chapter looks at the finer points of skinning and pro- vides you with the tools to start building your own skins for DotNetNuke. DotNetNuke utilizes templates to accomplish this because they allow for the separation of the pre- sentation and layout attributes from the application logic required to display content to the user. We studied various approaches to allow this functionality and have created a solution that will allow both developers and designers independence when implementing DotNetNuke sites. This allows for faster deployment times and, more importantly, reduced expense with getting your por- tal functional and performing its intended purpose. The abstraction of the user interface elements from a page can be accomplished using different methodologies. The method chosen includes some degree of parsing to merge the presentation with the business logic. Therefore, defining where, when, and how this parsing will take place becomes critical to the success of the entire solution. The use of tokens or identifiers in the user interface files to represent dynamic functionality is a popular technique employed in many skinning solutions. DotNetNuke utilizes this approach in its skinning engine solution — as the page is processed the token is replaced to the proper skin object or control for the function the token identifies. This is accomplished when you install the skin into the application, which we will discuss later. DotNetNuke allows you to create skins using your favorite editor, which gives you as a skin developer a good amount of flexibility — you only need to follow the rules for creating a skin; the tool you use to create it is up to you. You can either create an .ascx or .html skin. The type you 17_595636 ch13.qxd 5/10/05 10:03 PM Page 331 create depends upon your choice of editor and the set of rules you choose to follow. Allowing you to cre- ate the skins in HTML or ASP.NET was a conscious choice made to allow for the most flexibility with creating these skins and to help bridge the gap between designers and developers. The Core Team real- izes there are still many more HTML developers in the world than ASP.NET developers, so allowing skins to be created in HTML allows many more individuals to utilize this functionality of the application without having to learn new skills other than how to place the tokens within your skin design. Now that you have a little history of why the engine was architected, let’s look at the finer points of skinning. File Organization Skins files must meet certain conditions before they will install into the application. Once the require- ments are met you can upload a compressed zip file containing your skin utilizing the built-in File Manager, and the application will convert your files for use as a portal skin. Skins may be applied at several levels within the application; you can define a skin to be host-, portal-, or page-level, depending upon your needs. Once you upload your skin the application will create a directory for the files under the Portals/_default/Skins directory. If you look at the file structure of the application’s root you will also notice there is a Containers directory where any containers you create for your skins will be stored. These file directories are mapped to their corresponding skin ID, which uniquely identifies the skin in the application. These settings are stored in the Skins table and allow the application to correctly deter- mine the skin to load at runtime. Processing Pages and Loading Skins The application uses a single page to process the functionality of displaying information to the user, Default.aspx. This page is the container for all the controls and skin elements the application needs to effectively serve its purpose of displaying the content to the portal user. You could refer to Default.apsx as a placeholder for the other information because its content is very basic if you view the source of the page from your IDE. It includes a placeholder for the content to be loaded and some error handling for the application. As you can see in Listing 13-1, there is a lot more that will be injected in the page than it would appear from looking at the code for the page. Listing 13-1: Default.aspx Source Code <%@ Page CodeBehind=”Default.aspx.vb” language=”vb” AutoEventWireup=”false” Explicit=”True” Inherits=”DotNetNuke.Framework.CDefault” %> <%@ Register TagPrefix=”dnn” Namespace=”DotNetNuke.Common.Controls” Assembly=”DotNetNuke” %> <!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”> <HTML> <HEAD id=”Head”> <TITLE> <%= Title %> </TITLE> <%= Comment %> <META NAME=”DESCRIPTION” CONTENT=”<%= Description %>”> <META NAME=”KEYWORDS” CONTENT=”<%= KeyWords %>”> <META NAME=”COPYRIGHT” CONTENT=”<%= Copyright %>”> 332 Chapter 13 17_595636 ch13.qxd 5/10/05 10:03 PM Page 332 <META NAME=”GENERATOR” CONTENT=”<%= Generator %>”> <META NAME=”AUTHOR” CONTENT=”<%= Author %>”> <META NAME=”RESOURCE-TYPE” CONTENT=”DOCUMENT”> <META NAME=”DISTRIBUTION” CONTENT=”GLOBAL”> <META NAME=”ROBOTS” CONTENT=”INDEX, FOLLOW”> <META NAME=”REVISIT-AFTER” CONTENT=”1 DAYS”> <META NAME=”RATING” CONTENT=”GENERAL”> <style id=”StylePlaceholder” runat=”server”></style> <asp:placeholder id=”CSS” runat=”server”></asp:placeholder> <asp:placeholder id=”FAVICON” runat=”server”></asp:placeholder> <script src=”<%= Page.ResolveUrl(“js/dnncore.js”) %>”></script> </HEAD> <BODY ID=”Body” runat=”server” ONSCROLL=”__dnn_bodyscroll()” BOTTOMMARGIN=”0” LEFTMARGIN=”0” TOPMARGIN=”0” RIGHTMARGIN=”0” MARGINWIDTH=”0” MARGINHEIGHT=”0”> <noscript></noscript> <dnn:Form id=”Form” runat=”server” ENCTYPE=”multipart/form-data” style=”height:100%;> <asp:Label ID=”SkinError” Runat=”server” CssClass=”NormalRed” Visible=”False”></asp:Label> <asp:placeholder id=”SkinPlaceHolder” runat=”server” /> <INPUT ID=”ScrollTop” runat=”server” NAME=”ScrollTop” TYPE=”hidden”> <INPUT ID=”__dnnVariable” runat=”server” NAME=”__dnnVariable” TYPE=”hidden”> </dnn:Form> </BODY> </HTML> So how does all this work? When a URL is requested and the user enters the application the request is inspected and the proper skin is determined from the database tables. Once the proper skin is identified the user controls are injected based on the definitions in the skin. The logic that allows this functionality is defined in the Admin/Skins/skin.vb file. Listing 13-2 looks at the logic that allows the skin to be determined and loaded. Listing 13-2: Skin.vb Init_Page Directives Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init ‘ ‘ CODEGEN: This call is required by the ASP.NET Web Form Designer. ‘ InitializeComponent() ‘ set global page settings InitializePage() ‘ process the current request ManageRequest() ‘ load skin control Dim ctlSkin As UserControl Dim objSkins As New UI.Skins.SkinController (continued) 333 Skinning DotNetNuke 17_595636 ch13.qxd 5/10/05 10:03 PM Page 333 Listing 13-2: (continued) ‘ skin preview If (Not Request.QueryString(“SkinSrc”) Is Nothing) Then PortalSettings.ActiveTab.SkinSrc = _ objSkins.FormatSkinSrc(QueryStringDecode(Request.QueryString(“SkinSrc”)) & “.ascx”, PortalSettings) ctlSkin = LoadSkin(PortalSettings.ActiveTab.SkinSrc) End If ‘ load assigned skin If ctlSkin Is Nothing Then If IsAdminControl() = True Or PortalSettings.ActiveTab.IsAdminTab _ Then Dim objSkin As UI.Skins.SkinInfo objSkin = objSkins.GetSkin(SkinInfo.RootSkin, _ PortalSettings.PortalId, SkinType.Admin) If Not objSkin Is Nothing Then PortalSettings.ActiveTab.SkinSrc = _ objSkins.FormatSkinSrc(objSkin.SkinSrc, PortalSettings) Else PortalSettings.ActiveTab.SkinSrc = “” End If ElseIf PortalSettings.ActiveTab.SkinSrc <> “” Then PortalSettings.ActiveTab.SkinSrc = _ objSkins.FormatSkinSrc(PortalSettings.ActiveTab.SkinSrc, PortalSettings) End If If PortalSettings.ActiveTab.SkinSrc <> “” Then ctlSkin = LoadSkin(PortalSettings.ActiveTab.SkinSrc) End If End If ‘ error loading skin - load default If ctlSkin Is Nothing Then ‘ could not load skin control - load default skin If IsAdminControl() = True Or PortalSettings.ActiveTab.IsAdminTab _ Then PortalSettings.ActiveTab.SkinSrc = Common.Globals.HostPath & _ SkinInfo.RootSkin & glbDefaultSkinFolder & glbDefaultAdminSkin Else PortalSettings.ActiveTab.SkinSrc = Common.Globals.HostPath & _ SkinInfo.RootSkin & glbDefaultSkinFolder & glbDefaultSkin End If ctlSkin = LoadSkin(PortalSettings.ActiveTab.SkinSrc) End If ‘ set skin path PortalSettings.ActiveTab.SkinPath = _ objSkins.FormatSkinPath(PortalSettings.ActiveTab.SkinSrc) ‘ set skin id to an explicit short name to reduce page payload and make it standards compliant ctlSkin.ID = “dnn” 334 Chapter 13 17_595636 ch13.qxd 5/10/05 10:03 PM Page 334 ‘ add CSS links ManageStyleSheets(False) ‘ add Favicon ManageFavicon() ‘ add skin to page SkinPlaceHolder.Controls.Add(ctlSkin) ‘ add CSS links ManageStyleSheets(True) End Sub As you can see in Listing 13-2, you first call the ManageRequest function, which determines the URL of the requesting user and information about who is requesting the resource. This not only allows you to determine the URL of the request, but also allows you to determine whether the user should have access to the page they are requesting. After learning the identity of the user and which resource they are requesting, you can determine the skin you need to use for this request from the database. So you create the new object ctlSkin and determine the skin you need to load for the page. Then you look at the other attributes and load the proper style sheet and Icon, if they are defined, and finally bind the skin user control and attributes to the page the user has requested. This is what allows DotNetNuke to perform dynamic re-allocation of the application’s look on the fly. You should note there is a performance hit with these DB calls and allowing the portal to utilize this dynamic skinning solution, as there is with any application that can change its appearance on the fly, but it is one of the killer features DotNetNuke con- tains and more than worth the additional overhead the process requires. Packaging Skins and Containers Now that you understand the process of getting skin bound to the proper page, let’s look at the different parts of a skin package. The package is a compilation of the files and definitions you will use to contain the files and tell DotNetNuke to process your skin when you install it into your application instance. A skin or container package can contain the following file types: ❑ *.htm,*.html files: These files can contain the layout representing how you want the various skin objects to be located in your design. These files will be converted to *.ascx files upon instal- lation to the application. ❑ *.ascx files: These are skin definition user controls that are precompiled in the format the skinning engine requires. ❑ *.css files: These files contain the style sheet definitions you will use to define the files in your skin or container. ❑ *.gif, *.jpeg, *.jpg, *.png: These file extensions are used in support of the graphics files included in your skin. ❑ *.* Other files: You can use any other resource files in your package, but these must be of an allowed file type in the host allowed file settings on the Host Settings page. 335 Skinning DotNetNuke 17_595636 ch13.qxd 5/10/05 10:03 PM Page 335 A package can contain multiple skins or containers. This allows you to create complementing skins for a site in one package. Since the layout will allow for various panes that contain the module content at run- time, this is a powerful feature because you may not want the same layout for all pages in a site, but you will probably want common graphics and defined styles throughout the same site. This ability of pack- aging multiple skins and containers allows you to install all of the skins for a portal in one installation. A skin package should make use of a manifest file to identify the various files and elements to be included in the skin. Including this file allows you to combine all the files into one package and give the application the needed instructions for processing the skin to your specifications. Although the manifest file adds some overhead to the skin creation process, it does greatly enhance the abilities of the installa- tion process and allows greater control in a single step. We will discuss the finer points of creating mani- fest files later in this chapter because this is a very important control mechanism for controlling the installation of your skins. Creating Your Skin You have two methods for creating your skin. The method you choose will depend upon your comfort level with the technology and your personal preference. Skins can be created using HTML, or if you pre- fer, you can create *.ascx skins with VS.NET. This allows you to develop your skin in a comfortable envi- ronment and provides flexibility while creating skins. If you are more of a designer who has developed traditional web sites in the past, you may prefer creating your skins in HTML using your favorite editor, but if you are more of a programmer type, you may prefer to use ASP.NET code and develop your skin with Visual Studio. Both these methods are basically the same except you will use the tokens when developing in HTML and you will utilize the user controls when creating your skin. Of course the file extension will change depending upon your choice of methods. At a minimum you will want to develop at least two skins for each package, one to display to your users and another to display the administrative modules discussed in Chapters 4 and 5. The reason for this is that the user content areas will likely need multiple panes to properly lay out the content as your busi- ness needs require, but the administrative areas will likely only display a single module per page, so these two layouts will need to be architected differently to adequately serve the purpose of the area. There are several different steps to creating a skin. The order in which you perform these steps is not important, but we find the following a valid method to get everything accomplished and your skin into production. To simplify the development of skin files as well as expedite the packaging process later, it is recom- mended that you use the following organizational folder structure: \Skins \SkinName (this is the package you are developing) (this is where you create the skin package zip files for deployment) \containers (this is a static name to identify the container files for the skin package) (this will contain all resource files related to your containers) \skins (this is a static name to identify the skin files for the skin package (this will contain all resource files related to your skins) 336 Chapter 13 17_595636 ch13.qxd 5/10/05 10:04 PM Page 336 This provides an easy structure to develop your skins and you will find this structure also simplifies preparing your package for deployment and installation into your portal. The free-form nature of skin- ning provides you a level of creative freedom within designing your skins. Designers may wish to create the initial site designs as full graphical images and then slice the images to meet their needs after the concept is mature. One thing to be aware of when creating skins is that you need to include all user interface elements in your design. This includes static elements such as graphics and text, and it should also include active elements such as Login links, a navigation/menu system, and the other skin objects required for DotNetNuke to adequately display your content to your users. This is where you need to be aware of the technical issues that will arise in terms of how to most effec- tively divide the graphical elements into the HTML representation you need. HTML layout is very dif- ferent from free-flow graphics, and the translation from a graphical image to an HTML representation will determine the layout of the final design. Decisions such as what resolution you want to target your site for will need to be made at this stage. If you want the site to remain fixed regardless of resolution of the user’s browser or if the design should adapt to the resolution, you should decide these items and make adjustments to your design based on those decisions. Several example skins are included in the download that show the differences between fixed-width skins and full-width skins, so you can see the differences between these two approaches. Now that you have your design completed, you will need to actually build the skin. As mentioned ear- lier, you can use any HTML editor, Visual Studio, or if you prefer, even your favorite text editor to build the skin for your design. The one thing to remember is the HTML in your skin must be well formed or the process will fail. This means you must ensure you close any HTML tags you open, including image tags. Image tags do not normally have closing tags so you should include the trailing / after the image content and before you end the tag. Most modern HTML editors will handle the process of ensuring tags are closed for you, but you should still double-check your work to make sure a bug is not introduced into your skin with a missing tag. This brings us to the location of images and support files to be used in your skin. Normally you will want to include your support files within the same folder as the rest of your skin elements, but this is not a requirement and you can place them in any folder you wish, but care must be taken to ensure the paths will remain valid after the skin upload process. As part of the upload process the skinning engine will add an explicit path to your images for you. This is to help with the performance of your skin during runtime. The skinning image will automatically append a path of /Portals/_default/YourSkinName/ to your image paths you have specified. One thing to watch out for with this when you are building the content for your site is that if these paths change between development and production, your image paths will be incorrect and the image will not show properly. An example of when this can happen is when you are building a site on your local machine in a virtual directory of http://localhost/VirtualDirectory and then you decide to move to production to a location of http://www.YourDomain.com. The easiest way to make this move is to back up your local database, then restore it to your production database, ftp your files to your production system, and make the appropriate changes to the Portal Alias and web.config file. But what you will find when you move the site to production is that the image paths will no longer work. If you run into this issue you have two methods to correct it. You can install the skin again, effectively overwriting the original skin, or you can choose to directly edit your skin file and correct the image paths manually. The best thing is to take these types of items into account before you start building a development site and plan accordingly. 337 Skinning DotNetNuke 17_595636 ch13.qxd 5/10/05 10:04 PM Page 337 Now that you have your design and a good idea of how your skin will be architected, you need to add the skin objects to the proper location in your skin file. This is so DotNetNuke will know where to insert the various content panes, portal elements, and navigation objects. Depending on the method you chose to create your skin, this process will change to meet the method. If you are using ASCX skins, then you will need to specify the @Register and the actual user control tag in your skin. For example, <dnn:Login runat=”server” id=”dnnlogin”> will insert the login user control in the section of your skin where you specify the control. If you are using HTML skins, then you will simply need to include the token for the accompanying skin element. So for HTML skins you will simply add [Login] in the location you would like the login control to appear and the engine will replace it with the actual control when the skin is parsed during upload. Each of the different skin objects has their own unique functions, and you must understand the use of each to build a functional skin. Table 13-1 lists each of these objects and describes their purpose, as well as provides an example of their use in each of the two methods. Table 13-1: Skin Objects Token Control Description [SOLPARTMENU] < dnn:SolPartMenu runat=”server” Displays the hierarchical naviga- id=”dnnSolPartMenu”> tion menu (formerly [MENU]). [LOGIN] < dnn:Login runat=”server” Dual state control — displays id=”dnnLogin”> “Login” for anonymous users and “Logout” for authenticated users. [BANNER] < dnn:Banner runat=”server” Displays a random banner ad. id=”dnnBanner”> [BREADCRUMB] < dnn:Breadcrumb runat=”server” Displays the path to the currently id=”dnnBreadcrumb”> selected tab in the form of Page- Name1 > PageName2 > PageName3. [COPYRIGHT] < dnn:Copyright runat=”server” Displays the copyright notice for id=”dnnCopyright”> the portal. [CURRENTDATE] < dnn:CurrentDate runat=”server” Displays the current date. id=”dnnCurrentDate”> [DOTNETNUKE] < dnn:DotNetNuke runat=”server” Displays the Copyright notice for id=”dnnDotnetNuke”> DotNetNuke (not required). [HELP] < dnn:Help runat=”server” Displays a link for Help, which will id=”dnnHelp”> launch the user’s e-mail client and send mail to the portal Administrator. [HOSTNAME] < dnn:HostName runat=”server” Displays the Host Title linked to id=”dnnHostName”> the Host URL. [LINKS] < dnn:Links runat=”server” Displays a flat menu of links id=”dnnLinks”> related to the current tab level and parent node. This is useful for search engine spiders and robots. [LOGO] < dnn:Logo runat=”server” Displays the portal logo. id=”dnnLogo”> 338 Chapter 13 17_595636 ch13.qxd 5/10/05 10:04 PM Page 338 Token Control Description [PRIVACY] < dnn:Privacy runat=”server” Displays a link to the Privacy id=”dnnPrivacy”> Information for the portal. [SIGNIN] < dnn:Signin runat=”server” Displays the signin control for id=”dnnSignin”> providing your username and password. [TERMS] < dnn:Terms runat=”server” Displays a link to the Terms and id=”dnnTerms”> Conditions for the portal. [USER] < dnn:User runat=”server” Dual state control — displays a id=”dnnUser”> “Register” link for anonymous users or the user’s name for authenticated users. [CONTENTPANE] <div runat=”server” Injects a placeholder for module id=”ContentPane”> content. These are the skin objects available for use in your skin creation, and there are also some additional objects available for use in the creation of containers. You can place these objects anywhere in your skin and control the placement of the portal elements. We should note that not all these elements are required, but you should choose the elements you need to accomplish your purpose. Each skin must have at least one content pane, and it must be identified as the content pane or the modules will not display correctly. Quite a few attributes are also available for use in the skin creation process. These attributes will be defined in the skin.xml or the manifest file we mentioned earlier in the chapter. This file is where you will tell DotNetNuke how you want to utilize the various skin objects. For example, you may want your navigation menu to display horizontally in your skin; this setting will be set in the skin.xml file so the engine will know how to properly insert the menu into the skin. Table 13-2 lists the attributes available to you for use in the manifest file for the skin objects. Table 13-2: Skin Attributes Token Attribute Default Description [SOLPARTMENU] separatecss true CSS defined in a style sheet (values: true, false) backcolor #333333 Background color forecolor white Forecolor of menu item when selected highlightcolor white Color of top and left border to give a highlight effect iconbackground #333333 Background color in area where icon is color displayed Table continued on following page 339 Skinning DotNetNuke 17_595636 ch13.qxd 5/10/05 10:04 PM Page 339 [...]... Settings.ascx DotNetNuke. Modules.Survey.dll DotNetNuke. Modules.Survey.SqlDataProvider.dll (continued) 3 59 Chapter 14 Listing 14-2: (continued) 01.00.00.SqlDataProvider Uninstall.SqlDataProvider < /dotnetnuke> Packaging Modules DotNetNuke private assemblies... 01.00.00 DotNetNuke. Modules.Survey.SurveyController, DotNetNuke. Modules.Survey SurveyResources.zip DotNetNuke. Survey Survey.ascx View http://www .dotnetnuke. com Edit... < /dotnetnuke> V3.0 only V2.0 only Contains one or more file nodes Let’s break this down and examine the individual xml nodes The root element is the dotnetnuke node This node contains two attributes: version and type The version attribute takes a numeric value DotNetNuke supports two versions — “2.0” and “3.0.”... mechanism for distributing code add-ons Modules A module, also known as a DotNetNuke Private Assembly (PA), represents a discrete set of application code that is used to extend the functionality of the DotNetNuke portal A module provides a user interface for managing and displaying custom content in the portal Unlike the other DotNetNuke code addons, skin objects and providers, modules are designed... skip n breadcrumb tabs before displaying [LOGIN] 344 Skinning DotNetNuke Token Attribute Default Description [COPYRIGHT] CssClass SelectedTab The style name of portal copyright link [CURRENTDATE] CssClass SelectedTab The style name of date text DateFormat MMMM dd, yyyy The format of the date text [DOTNETNUKE] CssClass Normal The style name of DotNetNuke portal engine copyright text [HELP] CssClass OtherTabs... for the Survey module The Survey module is included with DotNetNuke as an example of how to build, package, and deploy modules The module package and source code can be found in the /desktopmodules/survey directory of the standard DotNetNuke installation Listing 14-2: Sample Manifest for the Survey Module ... skins with DotNetNuke Basically the point of all this is if you can design and program in HTML and can follow the few simple rules enforced by the skinning engine, then you can build beautiful designs for your DotNetNuke site Many examples of both free and commercial skins are available for you to use as references when creating your skins and containers There are quite a few examples on the DotNetNuke. .. information in the preceding chapters, and package them for distribution 352 Distribution This chapter examines how DotNetNuke add-ons can be distributed and installed As DotNetNuke has progressed, we have continued to add functionality to allow developers to package and distribute extensions to the DotNetNuke framework These add-ons allow the administrators and users to customize the portal to suit their... extracted and saved to the portal directories Figure 14-1 shows the survey module package that is included with DotNetNuke Figure 14-1 Special File Types DotNetNuke recognizes four specific file types in the private assembly archive While other file types may be included in the package, DotNetNuke treats these types as special cases The following list looks at each of these types and how they are handled... definition entries and install the module files to the appropriate directories required by DotNetNuke The dnn file is copied to the module folder defined in the manifest .Dll file type: Dll files are NET assemblies In DotNetNuke, these assemblies may represent the compiled module code, a dataprovider assembly, or even an ASP.NET Server Control used in the module All dll files are installed to the application . the current date. id=”dnnCurrentDate”> [DOTNETNUKE] < dnn :DotNetNuke runat=”server” Displays the Copyright notice for id=”dnnDotnetNuke”> DotNetNuke (not required). [HELP] < dnn:Help. 16_ 595 636 ch12.qxd 5/10/05 9: 53 PM Page 330 Skinning DotNetNuke The ability to skin DotNetNuke was introduced in version 2 of the application. If ElseIf PortalSettings.ActiveTab.SkinSrc <> “” Then PortalSettings.ActiveTab.SkinSrc = _ objSkins.FormatSkinSrc(PortalSettings.ActiveTab.SkinSrc, PortalSettings) End If If PortalSettings.ActiveTab.SkinSrc

Ngày đăng: 06/08/2014, 09:20

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan