KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > opencms > workplace > CmsFolderTree


1 /*
2 * File : $Source: /usr/local/cvs/opencms/src-modules/com/opencms/workplace/CmsFolderTree.java,v $
3 * Date : $Date: 2005/06/27 23:22:07 $
4 * Version: $Revision: 1.2 $
5 *
6 * This library is part of OpenCms -
7 * the Open Source Content Mananagement System
8 *
9 * Copyright (C) 2001 The OpenCms Group
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
20 *
21 * For further information about OpenCms, please see the
22 * OpenCms Website: http://www.opencms.org
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License along with this library; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 */

28
29
30 package com.opencms.workplace;
31
32 import org.opencms.file.CmsFile;
33 import org.opencms.file.CmsFolder;
34 import org.opencms.file.CmsObject;
35 import org.opencms.file.CmsResource;
36 import org.opencms.file.types.I_CmsResourceType;
37 import org.opencms.i18n.CmsEncoder;
38 import org.opencms.main.CmsException;
39 import org.opencms.main.OpenCms;
40 import org.opencms.security.CmsPermissionSet;
41 import org.opencms.workplace.CmsWorkplace;
42
43 import com.opencms.core.I_CmsSession;
44 import com.opencms.legacy.CmsXmlTemplateLoader;
45 import com.opencms.template.A_CmsXmlContent;
46
47 import java.util.ArrayList JavaDoc;
48 import java.util.Enumeration JavaDoc;
49 import java.util.Hashtable JavaDoc;
50 import java.util.List JavaDoc;
51 import java.util.Vector JavaDoc;
52
53 /**
54  * Template class for displaying a folder tree <P>
55  * Reads template files of the content type <code>CmsXmlWpTemplateFile</code>.
56  *
57  *
58  * @author Michael Emmerich
59  * @version $Revision: 1.2 $ $Date: 2005/06/27 23:22:07 $
60  */

61
62 public class CmsFolderTree extends CmsWorkplaceDefault {
63
64
65     /** Definition of the Datablock TREELINK */
66     private static final String JavaDoc C_TREELINK = "TREELINK";
67
68
69     /** Definition of the Datablock TREESTYLE */
70     private static final String JavaDoc C_TREESTYLE = "TREESTYLE";
71
72
73     /** Definition of the Datablock TREETAB */
74     private static final String JavaDoc C_TREETAB = "TREETAB";
75
76
77     /** Definition of the Datablock TREEENTRY */
78     private static final String JavaDoc C_TREEENTRY = "TREEENTRY";
79
80
81     /** Definition of the Datablock TREEVAR */
82     private static final String JavaDoc C_TREEVAR = "TREEVAR";
83
84
85     /** Definition of the Datablock TREEFOLDER */
86     private static final String JavaDoc C_TREEFOLDER = "TREEFOLDER";
87
88
89     /** Definition of the Datablock TREESWITCH */
90     private static final String JavaDoc C_TREESWITCH = "TREESWITCH";
91
92
93     /** Definition of the Datablock TREELINE */
94     private static final String JavaDoc C_TREELINE = "TREELINE";
95
96
97     /** Definition of the Datablock TREELINEDISABLED */
98     private static final String JavaDoc C_TREELINEDISABLED = "TREELINEDISABLED";
99
100
101     /** Definition of the Datablock TREEIMG_EMPTY0 */
102     private static final String JavaDoc C_TREEIMG_EMPTY0 = "TREEIMG_EMPTY0";
103
104
105     /** Definition of the Datablock TREEIMG_EMPTY */
106     private static final String JavaDoc C_TREEIMG_EMPTY = "TREEIMG_EMPTY";
107
108
109     /** Definition of the Datablock TREEIMG_FOLDEROPEN */
110     private static final String JavaDoc C_TREEIMG_FOLDEROPEN = "TREEIMG_FOLDEROPEN";
111
112
113     /** Definition of the Datablock TREEIMG_FOLDERCLOSE */
114     private static final String JavaDoc C_TREEIMG_FOLDERCLOSE = "TREEIMG_FOLDERCLOSE";
115
116
117     /** Definition of the Datablock TREEIMG_MEND */
118     private static final String JavaDoc C_TREEIMG_MEND = "TREEIMG_MEND";
119
120
121     /** Definition of the Datablock TREEIMG_PEND */
122     private static final String JavaDoc C_TREEIMG_PEND = "TREEIMG_PEND";
123
124
125     /** Definition of the Datablock TREEIMG_END */
126     private static final String JavaDoc C_TREEIMG_END = "TREEIMG_END";
127
128
129     /** Definition of the Datablock TREEIMG_MCROSS */
130     private static final String JavaDoc C_TREEIMG_MCROSS = "TREEIMG_MCROSS";
131
132
133     /** Definition of the Datablock TREEIMG_PCROSS */
134     private static final String JavaDoc C_TREEIMG_PCROSS = "TREEIMG_PCROSS";
135
136
137     /** Definition of the Datablock TREEIMG_CROSS */
138     private static final String JavaDoc C_TREEIMG_CROSS = "TREEIMG_CROSS";
139
140
141     /** Definition of the Datablock TREEIMG_VERT */
142     private static final String JavaDoc C_TREEIMG_VERT = "TREEIMG_VERT";
143
144
145     /** Style for files in a project. */
146     private static final String JavaDoc C_FILE_INPROJECT = "treefolder";
147
148
149     /** Style for files not in a project. */
150     private static final String JavaDoc C_FILE_NOTINPROJECT = "treefoldernip";
151
152
153     /** Definition of Treelist */
154     private static final String JavaDoc C_TREELIST = "TREELIST";
155
156
157     /** Definition of Treelist */
158     private static final String JavaDoc C_FILELIST = "FILELIST";
159
160
161     /** Storage for caching icons */
162     private Hashtable JavaDoc m_iconCache = new Hashtable JavaDoc();
163
164     /**
165      * Check if this resource should be displayed in the filelist.
166      * @param cms The CmsObject
167      * @param res The resource to be checked.
168      * @return True or false.
169      * @throws CmsException if something goes wrong.
170      */

171
172     private boolean checkAccess(CmsObject cms, CmsResource res) throws CmsException {
173         //boolean access = false;
174
if(res.getState() == CmsResource.STATE_DELETED){
175             return false;
176         }
177         
178         return cms.hasPermissions(res, CmsPermissionSet.ACCESS_VIEW);
179     }
180
181     /**
182      * Check if this resource should be displayed in the filelist.
183      * @param cms The CmsObject
184      * @param res The resource to be checked.
185      * @return True or false.
186      * @throws CmsException if something goes wrong.
187      */

188
189     private boolean checkWriteable(CmsObject cms, CmsResource res) throws CmsException {
190         return cms.hasPermissions(res, CmsPermissionSet.ACCESS_WRITE);
191     }
192
193     /**
194      * Overwrites the getContent method of the CmsWorkplaceDefault.<br>
195      * Gets the content of the foldertree template and processe the data input.
196      * @param cms The CmsObject.
197      * @param templateFile The foldertree template file
198      * @param elementName not used
199      * @param parameters Parameters of the request and the template.
200      * @param templateSelector Selector of the template tag to be displayed.
201      * @return Bytearre containgine the processed data of the template.
202      * @throws Throws CmsException if something goes wrong.
203      */

204
205     public byte[] getContent(CmsObject cms, String JavaDoc templateFile, String JavaDoc elementName,
206             Hashtable JavaDoc parameters, String JavaDoc templateSelector) throws CmsException {
207         CmsXmlWpTemplateFile xmlTemplateDocument = new CmsXmlWpTemplateFile(cms, templateFile);
208         I_CmsSession session = CmsXmlTemplateLoader.getSession(cms.getRequestContext(), true);
209
210         // get the formname
211
String JavaDoc formname = (String JavaDoc)parameters.get(CmsWorkplaceDefault.C_PARA_FORMNAME);
212         if(formname != null) {
213             session.putValue(CmsWorkplaceDefault.C_PARA_FORMNAME, formname);
214         }
215         formname = (String JavaDoc)session.getValue(CmsWorkplaceDefault.C_PARA_FORMNAME);
216
217         // get the varname
218
String JavaDoc varname = (String JavaDoc)parameters.get(CmsWorkplaceDefault.C_PARA_VARIABLE);
219         if(varname != null) {
220             session.putValue(CmsWorkplaceDefault.C_PARA_VARIABLE, varname);
221         }
222         varname = (String JavaDoc)session.getValue(CmsWorkplaceDefault.C_PARA_VARIABLE);
223
224         // check if the files should be displayed as well
225
String JavaDoc files = (String JavaDoc)parameters.get(CmsWorkplaceDefault.C_PARA_VIEWFILE);
226         if(files != null) {
227             if(files.equals("yes")) {
228                 session.putValue(CmsWorkplaceDefault.C_PARA_VIEWFILE, files);
229             }
230             else {
231                 session.removeValue(CmsWorkplaceDefault.C_PARA_VIEWFILE);
232             }
233         }
234         // check if the oflinefiles are selectable
235
String JavaDoc offselect = (String JavaDoc)parameters.get("onlineselect");
236         if(offselect != null){
237             if ("yes".equals(offselect)){
238                 session.putValue("onlineselect_in_foldertree", offselect);
239             }else{
240                 session.removeValue("onlineselect_in_foldertree");
241             }
242         }
243
244         //set the required datablocks
245
xmlTemplateDocument.setData("FORMNAME", formname);
246         xmlTemplateDocument.setData("VARIABLE", varname);
247
248         // process the selected template
249
return startProcessing(cms, xmlTemplateDocument, "", parameters, "template");
250     }
251
252     /**
253      * Selects the icon that is displayed in the file list.<br>
254      * This method includes cache to prevent to look up in the filesystem for each
255      * icon to be displayed
256      * @param cms The CmsObject.
257      * @param type The resource type of the file entry.
258      * @param config The configuration file.
259      * @return String containing the complete name of the iconfile.
260      * @throws Throws CmsException if something goes wrong.
261      */

262     private String JavaDoc getIcon(CmsObject cms, I_CmsResourceType type, CmsXmlWpConfigFile config) throws CmsException {
263         // check if this icon is in the cache already
264
String JavaDoc icon = (String JavaDoc)m_iconCache.get(type.getTypeName());
265         // no icon was found, so check if there is a icon file in the filesystem
266
if(icon == null) {
267             String JavaDoc filename = CmsWorkplaceDefault.C_ICON_PREFIX + type.getTypeName().toLowerCase() + CmsWorkplaceDefault.C_ICON_EXTENSION;
268             try {
269                 // read the icon file
270
cms.readResource(CmsWorkplace.VFS_PATH_RESOURCES + filename);
271                 // add the icon to the cache
272
icon = filename;
273                 m_iconCache.put(type.getTypeName(), icon);
274             }
275             catch(CmsException e) {
276                 // no icon was found, so use the default
277
icon = CmsWorkplaceDefault.C_ICON_DEFAULT;
278                 m_iconCache.put(type.getTypeName(), icon);
279             }
280         }
281         return icon;
282     }
283
284     /**
285      * Creates the folder tree i.
286      * @throws Throws CmsException if something goes wrong.
287      */

288
289     public Object JavaDoc getTree(CmsObject cms, String JavaDoc tagcontent, A_CmsXmlContent doc, Object JavaDoc userObj) throws CmsException {
290
291         StringBuffer JavaDoc output = new StringBuffer JavaDoc();
292         I_CmsSession session = CmsXmlTemplateLoader.getSession(cms.getRequestContext(), true);
293         CmsXmlWpConfigFile configFile = this.getConfigFile(cms);
294         String JavaDoc filelist = null;
295         String JavaDoc currentFolder;
296         String JavaDoc currentFilelist;
297         String JavaDoc rootFolder;
298         String JavaDoc files = null;
299         boolean displayFiles = false;
300         boolean enableOnlineFiles = false;
301
302         // if a foldername was included, overwrite the value in the session for later use.
303
currentFolder = CmsXmlTemplateLoader.getRequest(cms.getRequestContext()).getParameter(CmsWorkplaceDefault.C_PARA_FOLDERTREE);
304         
305         if (currentFolder.trim().equalsIgnoreCase("/")) {
306             currentFolder = (String JavaDoc)session.getValue(CmsWorkplaceDefault.C_PARA_FOLDERTREE);
307             
308             if (currentFolder==null) {
309                 currentFolder = "/";
310                 session.putValue(CmsWorkplaceDefault.C_PARA_FOLDERTREE, currentFolder);
311             }
312         }
313         else if (currentFolder!=null) {
314             session.putValue(CmsWorkplaceDefault.C_PARA_FOLDERTREE, currentFolder);
315         }
316
317         // get the current folder to be displayed as maximum folder in the tree.
318
// currentFilelist = (String)session.getValue(C_PARA_FILELIST);
319
currentFilelist = CmsWorkplaceAction.getCurrentFolder(CmsXmlTemplateLoader.getRequest(cms.getRequestContext()).getOriginalRequest());
320         if(currentFilelist == null) {
321             currentFilelist = cms.getSitePath(cms.readFolder("/"));
322         }
323
324         // check if the files must be displayed as well
325
files = (String JavaDoc)session.getValue(CmsWorkplaceDefault.C_PARA_VIEWFILE);
326         if(files != null) {
327             displayFiles = true;
328         }
329         // check if the onlineresources are selectable
330
String JavaDoc offselect = (String JavaDoc)session.getValue("onlineselect_in_foldertree");
331         if(offselect != null){
332             enableOnlineFiles = true;
333         }
334
335         // get current and root folder
336
rootFolder = cms.getSitePath(cms.readFolder("/"));
337
338         //get the template
339
CmsXmlWpTemplateFile template = (CmsXmlWpTemplateFile)doc;
340         if(filelist != null) {
341             template.setData("PREVIOUS", filelist);
342         }
343         else {
344             template.setData("PREVIOUS", currentFilelist);
345         }
346         String JavaDoc tab = template.getProcessedDataValue(C_TREEIMG_EMPTY0, this);
347         showTree(cms, rootFolder, currentFolder, currentFilelist, template, output, tab,
348                 displayFiles, enableOnlineFiles, configFile);
349         return output.toString();
350     }
351
352     /**
353      * Indicates if the results of this class are cacheable.
354      *
355      * @param cms CmsObject Object for accessing system resources
356      * @param templateFile Filename of the template file
357      * @param elementName Element name of this template in our parent template.
358      * @param parameters Hashtable with all template class parameters.
359      * @param templateSelector template section that should be processed.
360      * @return <EM>true</EM> if cacheable, <EM>false</EM> otherwise.
361      */

362
363     public boolean isCacheable(CmsObject cms, String JavaDoc templateFile, String JavaDoc elementName,
364             Hashtable JavaDoc parameters, String JavaDoc templateSelector) {
365         return false;
366     }
367
368     /**
369      * Generates a subtree of the folder tree.
370      * @param cms The CmsObject.
371      * @param curFolder The rootfolder of ther subtree to display
372      * @param endfolder The last folder to be displayed.
373      * @param filelist The folder that is displayed in the file list
374      * @param template The foldertree template file.
375      * @param output The output buffer where all data is written to.
376      * @param tab The prefix-HTML code fo this subtree.
377      * @param displayFiles Flag to signal if to display the files as well.
378      */

379
380     private void showTree(CmsObject cms, String JavaDoc curfolder, String JavaDoc endfolder, String JavaDoc filelist,
381             CmsXmlWpTemplateFile template, StringBuffer JavaDoc output, String JavaDoc tab,
382             boolean displayFiles, boolean offselect, CmsXmlWpConfigFile configFile) throws CmsException {
383         String JavaDoc newtab = new String JavaDoc();
384         String JavaDoc folderimg = new String JavaDoc();
385         String JavaDoc treeswitch = new String JavaDoc();
386         CmsResource lastFolder = null;
387         Vector JavaDoc subfolders = new Vector JavaDoc();
388         Vector JavaDoc list = new Vector JavaDoc();
389         List JavaDoc untestedSubfolders = (List JavaDoc) new ArrayList JavaDoc();
390         List JavaDoc untestedSubfiles = (List JavaDoc) new ArrayList JavaDoc();
391         List JavaDoc untestedlist = cms.getSubFolders(curfolder);
392
393         // remove invisible folders
394
for(int i = 0;i < untestedlist.size();i++) {
395             CmsFolder subfolder = (CmsFolder)untestedlist.get(i);
396             if(checkAccess(cms, subfolder)) {
397                 list.addElement(subfolder);
398             }
399         }
400
401         // load the files as well if nescessary
402
if(displayFiles) {
403             List JavaDoc untestedfileslist = cms.getFilesInFolder(curfolder);
404             for(int i = 0;i < untestedfileslist.size();i++) {
405                 CmsFile file = (CmsFile)untestedfileslist.get(i);
406                 if(checkAccess(cms, file)) {
407                     list.addElement(file);
408                 }
409             }
410         }
411         Enumeration JavaDoc en = list.elements();
412         if(list.size() > 0) {
413             lastFolder = (CmsResource)list.lastElement();
414         }
415         else {
416             lastFolder = null;
417         }
418
419         //CmsFolder folder=null;
420
while(en.hasMoreElements()) {
421             CmsResource res = (CmsResource)en.nextElement();
422
423             // check if this folder is visible
424
if(checkAccess(cms, res)) {
425                 subfolders = new Vector JavaDoc();
426                 if(res.isFolder()) {
427                     untestedSubfolders = cms.getSubFolders(cms.getSitePath(res));
428
429                     // now filter all invisible subfolders
430
for(int i = 0;i < untestedSubfolders.size();i++) {
431                         CmsFolder subfolder = (CmsFolder)untestedSubfolders.get(i);
432                         if(checkAccess(cms, subfolder)) {
433                             subfolders.addElement(subfolder);
434                         }
435                     }
436
437                     // load the files as well if nescessary
438
if(displayFiles) {
439                         untestedSubfiles = cms.getFilesInFolder(cms.getSitePath(res));
440                         for(int i = 0;i < untestedSubfiles.size();i++) {
441                             CmsFile subfile = (CmsFile)untestedSubfiles.get(i);
442                             if(checkAccess(cms, subfile)) {
443                                 subfolders.addElement(subfile);
444                             }
445                         }
446                     }
447                 }
448
449                 // check if this folder must diplayes open
450
if(res.isFolder()) {
451                     if(cms.getSitePath(res).equals(filelist)) {
452                         folderimg = template.getProcessedDataValue(C_TREEIMG_FOLDEROPEN, this);
453                     }
454                     else {
455                         folderimg = template.getProcessedDataValue(C_TREEIMG_FOLDERCLOSE, this);
456                     }
457                 }
458                 else {
459                     I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(res.getTypeId());
460                     String JavaDoc icon = getIcon(cms, type, configFile);
461                     template.setData("icon", configFile.getWpPicturePath() + icon);
462                     folderimg = template.getProcessedDataValue("TREEIMG_FILE", this);
463                 }
464
465                 // now check if a treeswitch has to displayed
466

467                 // is this the last element of the current folder, so display the end image
468
if(cms.getSitePath(res).equals(cms.getSitePath(lastFolder))) {
469
470                     // if there are any subfolders extisintg, use the + or - box
471
if(subfolders.size() > 0) {
472
473                         // test if the + or minus must be displayed
474
if(endfolder.startsWith(cms.getSitePath(res))) {
475                             template.setData(C_TREELINK, CmsWorkplaceDefault.C_WP_FOLDER_TREE + "?" + CmsWorkplaceDefault.C_PARA_FOLDERTREE
476                                     + "=" + CmsEncoder.escape(curfolder,
477                                     cms.getRequestContext().getEncoding()));
478                             treeswitch = template.getProcessedDataValue(C_TREEIMG_MEND, this);
479                         }
480                         else {
481                             template.setData(C_TREELINK, CmsWorkplaceDefault.C_WP_FOLDER_TREE + "?" + CmsWorkplaceDefault.C_PARA_FOLDERTREE
482                                     + "=" + CmsEncoder.escape(cms.getSitePath(res),
483                                     cms.getRequestContext().getEncoding()));
484                             treeswitch = template.getProcessedDataValue(C_TREEIMG_PEND, this);
485                         }
486                     }
487                     else {
488                         treeswitch = template.getProcessedDataValue(C_TREEIMG_END, this);
489                     }
490                 }
491                 else {
492                     // use the cross image
493
// if there are any subfolders extisintg, use the + or - box
494
if(subfolders.size() > 0) {
495
496                         // test if the + or minus must be displayed
497
if(endfolder.startsWith(cms.getSitePath(res))) {
498                             template.setData(C_TREELINK, CmsWorkplaceDefault.C_WP_FOLDER_TREE + "?" + CmsWorkplaceDefault.C_PARA_FOLDERTREE
499                                     + "=" + CmsEncoder.escape(curfolder,
500                                     cms.getRequestContext().getEncoding()));
501                             treeswitch = template.getProcessedDataValue(C_TREEIMG_MCROSS, this);
502                         }
503                         else {
504                             template.setData(C_TREELINK, CmsWorkplaceDefault.C_WP_FOLDER_TREE + "?" + CmsWorkplaceDefault.C_PARA_FOLDERTREE
505                                     + "=" + CmsEncoder.escape(cms.getSitePath(res),
506                                     cms.getRequestContext().getEncoding()));
507                             treeswitch = template.getProcessedDataValue(C_TREEIMG_PCROSS, this);
508                         }
509                     }
510                     else {
511                         treeswitch = template.getProcessedDataValue(C_TREEIMG_CROSS, this);
512                     }
513                 }
514                 if(cms.getSitePath(res).equals(cms.getSitePath(lastFolder))) {
515                     newtab = tab + template.getProcessedDataValue(C_TREEIMG_EMPTY, this);
516                 }
517                 else {
518                     newtab = tab + template.getProcessedDataValue(C_TREEIMG_VERT, this);
519                 }
520
521                 // test if the folder is in the current project
522
//if(res.inProject(cms.getRequestContext().currentProject())) {
523
if (cms.isInsideCurrentProject(cms.getSitePath(res))) {
524                     template.setData(C_TREESTYLE, C_FILE_INPROJECT);
525                 }
526                 else {
527                     template.setData(C_TREESTYLE, C_FILE_NOTINPROJECT);
528                 }
529
530                 // set all data for the treeline tag
531
template.setData(C_FILELIST, CmsWorkplaceAction.getExplorerFileUri(CmsXmlTemplateLoader.getRequest(cms.getRequestContext()).getOriginalRequest()) + "?" + CmsWorkplaceDefault.C_PARA_FILELIST
532                         + "=" + cms.getSitePath(res));
533                 template.setData(C_TREELIST, CmsWorkplaceDefault.C_WP_EXPLORER_TREE + "?" + CmsWorkplaceDefault.C_PARA_FILELIST
534                         + "=" + cms.getSitePath(res));
535                 template.setData(C_TREEENTRY, res.getName());
536                 template.setData(C_TREEVAR, cms.getSitePath(res));
537                 template.setData(C_TREETAB, tab);
538                 template.setData(C_TREEFOLDER, folderimg);
539                 template.setData(C_TREESWITCH, treeswitch);
540
541                 // test if the folder is in the current project and if the user has
542

543                 // write access to this folder.
544
// CHECK: The logic in CmsFolderTree was changed
545
// if((res.inProject(cms.getRequestContext().currentProject()) && checkWriteable(cms,
546
// (CmsResource)res)) || offselect) {
547
if(checkWriteable(cms, res) || offselect) {
548                     template.setData(C_TREESTYLE, C_FILE_INPROJECT);
549                     output.append(template.getProcessedDataValue(C_TREELINE, this));
550                 } else {
551                     template.setData(C_TREESTYLE, C_FILE_NOTINPROJECT);
552                     output.append(template.getProcessedDataValue(C_TREELINEDISABLED, this));
553                 }
554
555                 //finally process all subfolders if nescessary
556
if((endfolder.startsWith(cms.getSitePath(res))) && (endfolder.endsWith("/"))) {
557                     showTree(cms, cms.getSitePath(res), endfolder, filelist, template,
558                             output, newtab, displayFiles, offselect, configFile);
559                 }
560             }
561         }
562     }
563 }
564
Popular Tags