Tagger 20071023 1507 - Moving alpha2 to RC - still no dragging
git-svn-id: file:///home/shish/svn/shimmie2/trunk@561 7f39781d-f577-437e-ae19-be835c7a54ca
This commit is contained in:
		
							parent
							
								
									bcb5eecc56
								
							
						
					
					
						commit
						5d273e6879
					
				| @ -1,93 +1,155 @@ | ||||
| <?php | ||||
| // Tagger - Advanced Tagging
 | ||||
| // Author: Artanis (Erik Youngren <artanis.00@gmail.com>)
 | ||||
| // Do not remove this notice.
 | ||||
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||||
|  * Tagger - Advanced Tagging v2                                              * | ||||
|  * Author: Artanis (Erik Youngren <artanis.00@gmail.com>)                    * | ||||
|  * Do not remove this notice.                                                * | ||||
|  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| 
 | ||||
| class tagger extends Extension { | ||||
| class Tagger extends Extension { | ||||
| 	var $theme; | ||||
| 	 | ||||
| 	public function receive_event ($event) { | ||||
| 		if(is_null($this->theme)) | ||||
| 			$this->theme = get_theme_object("tagger", "taggerTheme"); | ||||
| 			 | ||||
| 		if(is_a($event,"InitExtEvent")) { | ||||
| 			global $config; | ||||
| 			if ($config->get_int("ext-tagger_tags-min") == -1) | ||||
| 				$config->set_int("ext-tagger_tags-min",2); | ||||
| 		if(is_a($event,'DisplayingImageEvent')) { | ||||
| 			global $page, $config, $user; | ||||
| 			 | ||||
| 			if ($config->get_string("ext-tagger_clear-tagme") == -1) | ||||
| 				$config->set_bool("ext-tagger_clear-tagme",false); | ||||
| 			if($config->get_bool("tag_edit_anon") || ($user->id != $config->get_int("anon_id"))) | ||||
| 				$this->theme->build_tagger($page,$event); | ||||
| 		} | ||||
| 	} | ||||
| } if(isset($_GET['debug'])) add_event_listener( new tagger()); | ||||
| 
 | ||||
| 			if ($config->get_string("ext-tagger_show-hidden") == -1) | ||||
| 				$config->set_bool("ext-tagger_show-hidden",false); | ||||
| // Tagger AJAX back-end
 | ||||
| class TaggerXML extends Extension { | ||||
| 	public function receive_event($event) { | ||||
| 		if(is_a($event,'PageRequestEvent') | ||||
| 			&& $event->page_name == "tagger" | ||||
| 			&& $event->get_arg(0) == "tags") | ||||
| 		{ | ||||
| 			global $page; | ||||
| 			 | ||||
| 			//$match_tags = null;
 | ||||
| 			//$image_tags = null;
 | ||||
| 			$tags=null; | ||||
| 			if (isset($_GET['s'])) { // tagger/tags[/...]?s=$string
 | ||||
| 				// return matching tags in XML form
 | ||||
| 				$tags = $this->match_tag_list($_GET['s']); | ||||
| 			} else if($event->get_arg(1)) { // tagger/tags/$int
 | ||||
| 				// return arg[1] AS image_id's tag list in XML form
 | ||||
| 				$tags = $this->image_tag_list($event->get_arg(1)); | ||||
| 			} | ||||
| 			 | ||||
| 		if(is_a($event,"DisplayingImageEvent")) { | ||||
| 			//show tagger box
 | ||||
| 			global $database; | ||||
| 			global $page; | ||||
| 			global $config; | ||||
| 			$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n". | ||||
| 			"<tags xmlns='http://www.w3.org/1999/xhtml'>". | ||||
| 				$tags. | ||||
| 			"</tags>"; | ||||
| 			 | ||||
| 			$base_href = $config->get_string('base_href'); | ||||
| 			$tags_min = (isset($_GET['tagger_min']) && $_GET['tagger_min']>0)?$_GET['tagger_min']:$config->get_int('ext-tagger_tags-min',2); | ||||
| 			$hidden = $config->get_string( | ||||
| 				'ext-tagger_show-hidden','N')=='N' ? | ||||
| 				" AND substring(tag,1,1) != '.' " : null; | ||||
| 			$page->set_mode("data"); | ||||
| 			$page->set_type("text/xml"); | ||||
| 			$page->set_data($xml); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	private function match_tag_list ($s) { | ||||
| 		global $database, $config, $event; | ||||
| 		 | ||||
| 		$values = array(); | ||||
| 		 | ||||
| 		// Match
 | ||||
| 		$p = strlen($s) == 1? " ":"\_"; | ||||
| 		$sq = "%".$p.$s."%"; | ||||
| 		$match = "concat(?,tag) LIKE ?"; | ||||
| 		array_push($values,$p,$sq); | ||||
| 		// Exclude
 | ||||
| //		$exclude = $event->get_arg(1)? "AND NOT IN ".$this->image_tags($event->get_arg(1)) : null;
 | ||||
| 		 | ||||
| 		// Hidden Tags
 | ||||
| 		$hidden = $config->get_string('ext-tagger_show-hidden','N')=='N' ? | ||||
| 			"AND substring(tag,1,1) != '.'" : null; | ||||
| 		 | ||||
| 		$q_where = "WHERE {$match} {$hidden} AND count > 0"; | ||||
| 		 | ||||
| 		// FROM based on return count
 | ||||
| 		$q_from = null; | ||||
| 		$count = $this->count($q_where,$values); | ||||
| 		if ($count > 60) { | ||||
| 			$q_from = "FROM (SELECT * FROM `tags` {$q_where} ". | ||||
| 				"ORDER BY count DESC LIMIT 0,30) AS `c_tags`"; | ||||
| 			$q_where = null; | ||||
| 			$count = array("max"=>$count); | ||||
| 		} else { | ||||
| 			$q_from = "FROM `tags`"; | ||||
| 			$count = null; | ||||
| 		} | ||||
| 		 | ||||
| 		$tags = $database->Execute(" | ||||
| 				SELECT tag | ||||
| 				FROM `tags` | ||||
| 				WHERE count>=?{$hidden} | ||||
| 				ORDER BY tag",array($tags_min));
 | ||||
| 			SELECT * | ||||
| 			{$q_from} | ||||
| 			{$q_where} | ||||
| 			ORDER BY tag",
 | ||||
| 			$values); | ||||
| 		 | ||||
| 			$this->theme->build($page, $tags); | ||||
| 			global $user; | ||||
| 			if($tags->_numOfRows > 1000 && $user->is_admin()) { | ||||
| 				$page->add_block( new Block ( | ||||
| 					"Warning - ext/tagger", | ||||
| 					"<h4>It is likely that Tagger will not function</h4>
 | ||||
| 					Currently the javascript code chokes on large numbers of | ||||
| 					tags. The tag list currently numbers | ||||
| 					<b>{$tags->_numOfRows}</b>.<br/> | ||||
| 					You can increase the minimum use requirement for the tag | ||||
| 					list in the <a href='".make_link('setup')."'>Board Config</a> | ||||
| 					to reduce the size of this list.<br/> | ||||
| 					This is a limitation of the method in which Tagger operates. | ||||
| 					I am working on a solution, I do not know when such a | ||||
| 					solution will be ready.",
 | ||||
| 					"main",0)); | ||||
| 			} | ||||
| 		return $this->list_to_xml($tags,"search",$s,$count); | ||||
| 	} | ||||
| 	 | ||||
| 		if(is_a($event,"PageRequestEvent")) { | ||||
| 			if($event->page_name == "about" && $event->get_arg(0) == "tagger") { | ||||
| 				global $page; | ||||
| 				$this->theme->show_about($page); | ||||
| 			} | ||||
| 			if($event->page_name == "tagger") { | ||||
| 				global $page; | ||||
| //				$this->theme->configTagger($page);
 | ||||
| 			} | ||||
| 	private function image_tag_list ($image_id) { | ||||
| 		global $database; | ||||
| 		$tags = $database->Execute(" | ||||
| 			SELECT tags.* | ||||
| 			FROM image_tags JOIN tags ON image_tags.tag_id = tags.id | ||||
| 			WHERE image_id=? ORDER BY tag", array($image_id));
 | ||||
| 		return $this->list_to_xml($tags,"image",$image_id); | ||||
| 	} | ||||
| 	 | ||||
| 		if(is_a($event, 'UserBlockBuildingEvent')) { | ||||
| 			if($event->user->is_admin()) { | ||||
| //				$event->add_link("Tagger Config", make_link("tagger"));
 | ||||
| 	private function list_to_xml ($tags,$type,$query,$misc=null) { | ||||
| 		$r = $tags->_numOfRows; | ||||
| 		 | ||||
| 		$s_misc = ""; | ||||
| 		if(!is_null($misc)) | ||||
| 			foreach($misc as $attr => $val)	$s_misc .= " ".$attr."=\"".$val."\""; | ||||
| 		 | ||||
| 		$result = "<list id=\"$type\" query=\"$query\" rows=\"$r\"{$s_misc}>";
 | ||||
| 		foreach($tags as $tag) { | ||||
| 			$result .= $this->tag_to_xml($tag); | ||||
| 		} | ||||
| 		return $result."</list>"; | ||||
| 	} | ||||
| 	 | ||||
| 		if(is_a($event, 'SetupBuildingEvent')) { | ||||
| 			$sb = new SetupBlock("Tagger - Power Tagging"); | ||||
| 			$sb->add_bool_option("ext-tagger_show-hidden", "Show Hidden Tags"); | ||||
| 			$sb->add_bool_option( | ||||
| 				"ext-tagger_clear-tagme", | ||||
| 				"<br/>Remove '<a href='".make_link("post/list/tagme/1")."'>tagme</a>' on use"); | ||||
| 			$sb->add_int_option( | ||||
| 				"ext-tagger_tags-min", | ||||
| 				"<br/>Ignore tags used fewer than "); $sb->add_label("times."); | ||||
| 			$event->panel->add_block($sb); | ||||
| 	private function tag_to_xml ($tag) { | ||||
| 		return | ||||
| 			"<tag  ". | ||||
| 				"id=\"".$tag['id']."\" ". | ||||
| 				"count=\"".$tag['count']."\">". | ||||
| 				html_escape($tag['tag']). | ||||
| 				"</tag>"; | ||||
| 	} | ||||
| 	 | ||||
| 	private function count($query,$values) { | ||||
| 		global $database; | ||||
| 		return $database->Execute( | ||||
| 			"SELECT COUNT(*) FROM `tags` $query",$values)->fields['COUNT(*)']; | ||||
| 	} | ||||
| } | ||||
| add_event_listener( new tagger()); | ||||
| 	 | ||||
| 	private function image_tags ($image_id) { | ||||
| 		global $database; | ||||
| 		$list = "("; | ||||
| 		$i_tags = $database->Execute( | ||||
| 			"SELECT tag_id FROM `image_tags` WHERE image_id=?", | ||||
| 			array($image_id)); | ||||
| 
 | ||||
| 		$b = false; | ||||
| 		foreach($i_tags as $tag) { | ||||
| 			if($b) | ||||
| 				$list .= ","; | ||||
| 			$b = true; | ||||
| 			$list .= $tag['tag_id']; | ||||
| 			 | ||||
| 		} | ||||
| 		$list .= ")"; | ||||
| 		 | ||||
| 		return $list; | ||||
| 	} | ||||
| } add_event_listener( new taggerXML(),10); | ||||
| ?>
 | ||||
|  | ||||
| @ -1,210 +1,205 @@ | ||||
| // Tagger - Advanced Tagging
 | ||||
| // Author: Artanis (Erik Youngren <artanis.00@gmail.com>)
 | ||||
| // Do not remove this notice.
 | ||||
| // All other code copyright by their authors, see comments for details.
 | ||||
| 
 | ||||
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ | ||||
| *                              Tagger Management                              * | ||||
| \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| // Global settings and oft-used objects
 | ||||
| var remove_tagme = null; | ||||
| var tags_field = false; | ||||
| var set_button = false; | ||||
| 
 | ||||
| // Put everything in a class? better?
 | ||||
| 
 | ||||
| function taggerInit() { | ||||
| 	// get imgdata hooks
 | ||||
| 	tags_field = getTagsField(); | ||||
| 	set_button = getSetButton(); | ||||
| 
 | ||||
| 	// Set up Tagger
 | ||||
| 	// Get last position
 | ||||
| 	c = getCookie('shimmie-tagger-position'); | ||||
| 	c = c ? c.replace(/px/g,"").split(" ") : new Array(null,null); | ||||
| 	taggerResetPos(c[0],c[1]); | ||||
| 	 | ||||
| 	tagger_tagIndicators(); | ||||
| 	DragHandler.attach(byId("tagger_titlebar")); | ||||
| 	remove_tagme = byId('tagme'); | ||||
| 	 | ||||
| 	// save position cookie on unload.
 | ||||
| 	window.onunload = function(e) { | ||||
| 		taggerSavePosition(); | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| function taggerResetPos(x,y) { | ||||
| 	tagger = byId("tagger_window"); | ||||
| 
 | ||||
| 	var pos = new Array(); | ||||
| 	if(!x || !y) { | ||||
| 		tagger.style.top=""; | ||||
| 		tagger.style.left=""; | ||||
| 		tagger.style.right="25px"; | ||||
| 		tagger.style.bottom="25px"; | ||||
| 		// get location in (left,top) terms
 | ||||
| 		pos = findPos(tagger); | ||||
| 	} else { | ||||
| 		pos[0] = x; | ||||
| 		pos[1] = y; | ||||
| 	} | ||||
| 	 | ||||
| 	tagger.style.top = pos[1]+"px"; | ||||
| 	tagger.style.left = pos[0]+"px"; | ||||
| 	tagger.style.right=""; | ||||
| 	tagger.style.bottom=""; | ||||
| } | ||||
| 
 | ||||
| function taggerSavePosition() { | ||||
| 	tw = byId('tagger_window'); | ||||
| 	if (tw) { | ||||
| 		xy = tw.style.left +" "+ tw.style.top | ||||
| 		setCookie('shimmie-tagger-position',xy); | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| function tagger_tagIndicators() { | ||||
| 	arObjTags = getElementsByTagNames('a',byId('tagger_body')); | ||||
| 	 | ||||
| 	for (i in arObjTags) { | ||||
| 		objTag = arObjTags[i]; | ||||
| 		if(tagExists(objTag)) { | ||||
| 			objTag.style.fontWeight="bold"; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ | ||||
| *                                   Tagging                                   * | ||||
| * Tagger - Advanced Tagging v2                                                * | ||||
| * Author: Artanis (Erik Youngren <artanis.00@gmail.com>)                      * | ||||
| * Do not remove this notice.                                                  * | ||||
| \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| 
 | ||||
| function toggleTag(objTag) { | ||||
| 	if(!tagExists(objTag)) { | ||||
| 		addTag(objTag); | ||||
| 		if (remove_tagme && objTag.getAttribute('tag') != 'tagme') { | ||||
| 			remTag(remove_tagme); | ||||
| /* Tagger Window Object | ||||
|  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| function Tagger() { | ||||
| // components
 | ||||
| 	this.t_parent  = null; | ||||
| 	this.t_title   = null; | ||||
| 	this.t_toolbar = null; | ||||
| 	this.t_menu    = null; | ||||
| 	this.t_body    = null; | ||||
| 	this.t_tags    = null; | ||||
| 	this.t_form    = null; | ||||
| 	this.t_status  = null; | ||||
| // data
 | ||||
| 	this.searchTags  = null; | ||||
| 	this.appliedTags = null; | ||||
| // methods
 | ||||
| 	this.initialize     = initialize; | ||||
| 	this.submit         = submit; | ||||
| 	this.tagSearch      = tagSearch; | ||||
| 	this.searchRequest  = searchRequest; | ||||
| 	this.searchReceive  = searchReceive; | ||||
| 	this.tagListReceive = tagListReceive; | ||||
| 	this.tagPublish     = tagPublish; | ||||
| 	this.prepTags       = prepTags; | ||||
| 	this.createTag      = createTag; | ||||
| 	this.buildPages     = buildPages; | ||||
| 	this.tagsToString   = tagsToString; | ||||
| 	this.toggleTag      = toggleTag; | ||||
| 	this.setAlert       = setAlert; | ||||
| 	 | ||||
| // definitions
 | ||||
| 	function initialize () { | ||||
| 	// components
 | ||||
| 		this.t_parent  = document.getElementById("tagger_parent"); | ||||
| 		this.t_title   = document.getElementById("tagger_titlebar"); | ||||
| 		this.t_toolbar = document.getElementById("tagger_toolbar"); | ||||
| 		this.t_menu    = document.getElementById("tagger_p-menu"); | ||||
| 		this.t_body    = document.getElementById("tagger_body"); | ||||
| 		this.t_tags    = document.getElementById("tagger_tags"); | ||||
| 		this.t_form    = this.t_tags.parentNode; | ||||
| 		this.t_status  = document.getElementById("tagger_statusbar"); | ||||
| 	//pages
 | ||||
| 		//this.buildPages();
 | ||||
| 	// initial data
 | ||||
| 		ajaxXML(query+"/"+image_id,tagListReceive); | ||||
| 	// reveal
 | ||||
| 		this.t_parent.style.display = ""; | ||||
| 	} | ||||
| 	} else { | ||||
| 		remTag(objTag); | ||||
| 	function submit() { | ||||
| 		this.t_tags.value = Tagger.tagsToString(Tagger.appliedTags); | ||||
| 	} | ||||
| 	t = byId("tagger_new-tag"); | ||||
| 	if(t.value) { t.select(); } | ||||
| } | ||||
| 
 | ||||
| function addTag (objTag) {	 | ||||
| 	delim = tags_field.value==" "?"":" "; | ||||
| 
 | ||||
| 	tags_field.value += delim + objTag.getAttribute('tag'); | ||||
| 	 | ||||
| 	if(objTag.value != 'Add') { | ||||
| 		objTag.style.fontWeight = "bold"; | ||||
| 	function tagSearch(s,ms) { | ||||
| 		clearTimeout(tagger_filter_timer); | ||||
| 		tagger_filter_timer = setTimeout("Tagger.searchRequest('"+s+"')",ms); | ||||
| 	} | ||||
| } | ||||
| 	function searchRequest(s) { | ||||
| 		var s_query = !s? query+"?s" : query+"?s="+sqlescape(s); | ||||
| 
 | ||||
| function remTag (objTag) {	 | ||||
| 	aTags = tags_field.value.split(" "); | ||||
| 	 | ||||
| 	tags_field.value=""; | ||||
| 	for(i in aTags) { | ||||
| 		aTag = aTags[i]; | ||||
| 		if(aTag != objTag.getAttribute('tag')) { | ||||
| 			if(tags_field.value=="") { | ||||
| 				tags_field.value += aTag; | ||||
| 			} else { | ||||
| 				tags_field.value += " "+aTag; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if(objTag.value != 'Add') { | ||||
| 		objTag.style.fontWeight = ""; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function tagExists(objTag) { | ||||
| 	return tags_field.value.match(reescape(objTag.getAttribute('tag'))); | ||||
| } | ||||
| 
 | ||||
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ | ||||
| *                                  Filtering                                  * | ||||
| \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| 
 | ||||
| function tagger_filter() { | ||||
| 	var filter = byId('tagger_new-tag'); | ||||
| 	var arObjTags = getElementsByTagNames('a',byId('tagger_body')); | ||||
| 	var prepend = filter.value.length<2? " ":"_"; | ||||
| 	var search = prepend + reescape(filter.value); | ||||
| 	 | ||||
| 	for(i in arObjTags) { | ||||
| 		objTag = arObjTags[i]; | ||||
| 		tag = prepend + objTag.getAttribute('tag'); | ||||
| 
 | ||||
| 		if(tag.match(search) && taggerFilterMode(objTag)) { | ||||
| 			objTag.style.display=''; | ||||
| 		} else { | ||||
| 			objTag.style.display='none'; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| function taggerToggleMode() { | ||||
| 	var obj = byId('tagger_mode'); | ||||
| 	 | ||||
| 	if(obj.getAttribute('mode')=='all') { | ||||
| 		obj.setAttribute('mode', 'applied'); | ||||
| 		obj.innerHTML = 'View All Tags'; | ||||
| 	} else { | ||||
| 		obj.setAttribute('mode','all'); | ||||
| 		obj.innerHTML = 'View Applied Tags'; | ||||
| 	} | ||||
| 	tagger_filter(true); | ||||
| } | ||||
| function taggerFilterMode(objTag) { | ||||
| 	var obj = byId('tagger_mode'); | ||||
| 	if(obj.getAttribute('mode') == 'all') { | ||||
| 		if(!this.searchTags) { | ||||
| 			ajaxXML(s_query,searchReceive); | ||||
| 			return true; | ||||
| 		} else { | ||||
| 		return objTag.style.fontWeight=='bold'; | ||||
| 	} | ||||
| } | ||||
| 			var prv_s = this.searchTags.getAttribute('query'); | ||||
| 		 | ||||
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ | ||||
| *                                     Misc                                    * | ||||
| \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| 
 | ||||
| function getTagsField() { | ||||
| 	var nodes = getElementsByTagNames('input,textarea',byId('imgdata')); | ||||
| 	for (i in nodes) { | ||||
| 		node = nodes[i]; | ||||
| 		if (node.getAttribute('name') == 'tags') | ||||
| 			return node; | ||||
| 	} | ||||
| 			if(s==prv_s) { | ||||
| 				return false; | ||||
| } | ||||
| 			}else if(!s || s.length <= 2 || s.length<prv_s.length || | ||||
| 				this.searchTags.getAttribute("max")) | ||||
| 			{ | ||||
| 				ajaxXML(s_query,searchReceive); | ||||
| 				return true; | ||||
| 				 | ||||
| function pushSet(form_id) { | ||||
| 	if(set_button) { | ||||
| 		set_button.click(); | ||||
| 			} else if(s.length > 2 && s.match(reescape(prv_s))) { | ||||
| 				var len = this.searchTags.childNodes.length; | ||||
| 				 | ||||
| 				for (var i=len-1; i>=0; i--) { | ||||
| 					var tag = this.searchTags.childNodes[i]; | ||||
| 					var t_name = tag.firstChild.data; | ||||
| 					 | ||||
| 					if(!t_name.match(reescape(s))) { | ||||
| 						this.searchTags.removeChild(tag); | ||||
| 						// TODO: Fix so back searches are not needlessly re-queried.
 | ||||
| 						//tag.setAttribute("style","display:none;");
 | ||||
| 					} else { | ||||
| 						//tag.setAttribute("style","");
 | ||||
| 					} | ||||
| 				} | ||||
| } | ||||
| 				 | ||||
| function getSetButton() { | ||||
| 	var form_nodes = getElementsByTagNames('input',byId('imgdata')); | ||||
| 	for (i in form_nodes) { | ||||
| 		node = form_nodes[i]; | ||||
| 		if (node.getAttribute('value')=="Set" && node.getAttribute('type')=="submit") { | ||||
| 			return node; | ||||
| 				if (len != this.searchTags.childNodes.length) { | ||||
| 					this.searchTags.setAttribute('query',s); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	function searchReceive(xml) { | ||||
| 		Tagger.searchTags = document.importNode(xml.getElementsByTagName("list")[0],true); | ||||
| 		tagPublish(Tagger.searchTags,document.getElementById("tagger_p-search")); | ||||
| 		 | ||||
| 		if(Tagger.searchTags.getAttribute("max")) { | ||||
| 			Tagger.setAlert("maxout","Limited to "+Tagger.searchTags.getAttribute("rows")+" of "+Tagger.searchTags.getAttribute("max")+" tags"); | ||||
| 		} else { | ||||
| 			Tagger.setAlert("maxout",false); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	function tagListReceive(xml) { | ||||
| 		Tagger.appliedTags = document.importNode(xml.getElementsByTagName("list")[0],true); | ||||
| 		tagPublish(Tagger.appliedTags,document.getElementById("tagger_p-applied")); | ||||
| 	} | ||||
| 	function tagPublish(tag_list,page) { | ||||
| 		page.innerHTML = ""; | ||||
| 		Tagger.prepTags(tag_list); | ||||
| 		page.appendChild(tag_list); | ||||
| 	} | ||||
| 	function prepTags(tag_list) { | ||||
| 		var len = tag_list.childNodes.length; | ||||
| 		 | ||||
| 		for(var i=0; i<len;i++) { | ||||
| 			var tag = tag_list.childNodes[i]; | ||||
| 			tag.onclick = function() { toggleTag(this); document.getElementById("tagger_filter").select(); }; | ||||
| 			tag.style.display="block"; | ||||
| 			tag.setAttribute("title",tag.getAttribute("count")+" uses"); | ||||
| 		} | ||||
| 	} | ||||
| 	function createTag(tag_name) { | ||||
| 		if (tag_name.length>0) { | ||||
| 			var tag = document.createElement("tag"); | ||||
| 			tag.setAttribute("count","0"); | ||||
| 			tag.setAttribute("id","newTag_"+tag_name); | ||||
| 			tag.onclick = function() { toggleTag(this); }; | ||||
| 			tag.appendChild(document.createTextNode(tag_name)); | ||||
| 			Tagger.appliedTags.appendChild(tag); | ||||
| 		} | ||||
| 	} | ||||
| 	function buildPages () { | ||||
| 		var pages = getElementsByTagNames("div",document.getElementById("tagger_body")); | ||||
| 		var len = pages.length; | ||||
| 		for(var i=0; i<len; i++) { | ||||
| 			this.t_menu.innerHTML += "<li onclick='Tagger.togglePages("+ | ||||
| 				"\""+pages[i].getAttribute("id")+"\")'>"+ | ||||
| 				pages[i].getAttribute('name')+"</li>"; | ||||
| 		} | ||||
| 	} | ||||
| 	function tagsToString(tags) { | ||||
| 		var len = tags.childNodes.length; | ||||
| 		var str = ""; | ||||
| 		for (var i=0; i<len; i++) { | ||||
| 			str += tags.childNodes[i].firstChild.data+" "; | ||||
| 		} | ||||
| 		return str; | ||||
| 	} | ||||
| 	function toggleTag(tag) { | ||||
| 		if(tag.parentNode == Tagger.appliedTags) { | ||||
| 			Tagger.appliedTags.removeChild(tag); | ||||
| 		} else { | ||||
| 			Tagger.appliedTags.appendChild(tag); | ||||
| 		} | ||||
| 	} | ||||
| 	function setAlert(type,arg) { | ||||
| 		var alert = document.getElementById("tagger_alert_"+type); | ||||
| 		if (alert) { | ||||
| 			if (arg==false) { | ||||
| 				//remove existing
 | ||||
| 				alert.parentNode.removeChild(alert); | ||||
| 				return; | ||||
| 			} | ||||
| 			//update prior
 | ||||
| 			alert.innerHTML = arg; | ||||
| 		} else if (arg!=false) { | ||||
| 			//create
 | ||||
| 			var status = document.createElement("div"); | ||||
| 			status.setAttribute("id","tagger_alert_"+type); | ||||
| 			status.innerHTML = arg; | ||||
| 			Tagger.t_status.appendChild(status); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ | ||||
| *                                quirksmode.org                               * | ||||
| \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| /* AJAX | ||||
|  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| function ajaxXML(url, callback) { | ||||
| 	//var http = getHTTPObject();
 | ||||
| 	var http = (new XMLHttpRequest() || new ActiveXObject("Microsoft.XMLHTTP")); | ||||
| 	http.open("GET", url, true); | ||||
| 	http.onreadystatechange = function() { | ||||
| 		if(http.readyState == 4) callback(http.responseXML); | ||||
| 	} | ||||
| 	http.send(null); | ||||
| } | ||||
| 
 | ||||
| // http://www.quirksmode.org/dom/getElementsByTagNames.html
 | ||||
| /* Miscellaneous Code | ||||
|  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| 
 | ||||
| // Quirksmode
 | ||||
| // http://www.quirksmode.org/dom/getElementsByTagNames.htmlgetElementdocument.getElementById
 | ||||
| function getElementsByTagNames(list,obj) { | ||||
| 	if (!obj) var obj = document; | ||||
| 	var tagNames = list.split(','); | ||||
| @ -229,7 +224,6 @@ function getElementsByTagNames(list,obj) { | ||||
| 	} | ||||
| 	return resultArray; | ||||
| } | ||||
| 
 | ||||
| // http://www.quirksmode.org/js/findpos.html
 | ||||
| function findPos(obj) { | ||||
| 	var curleft = curtop = 0; | ||||
| @ -264,3 +258,23 @@ function reescape(str){ | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
| 
 | ||||
| // Modified from above
 | ||||
| function sqlescape(str){ | ||||
| 	var resp="#%&_" | ||||
| 	var found=false | ||||
| 	var ret="" | ||||
| 	for(var i=0;i<str.length;i++) { | ||||
| 		found=false | ||||
| 		for(var j=0;j<resp.length;j++) { | ||||
| 			if(str.charAt(i)==resp.charAt(j)) { | ||||
| 				found=true;break | ||||
| 			} | ||||
| 		} | ||||
| 		if(found) { | ||||
| 			ret+="\\" | ||||
| 		} | ||||
| 		ret+=str.charAt(i) | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||||
|  * Tagger - Advanced Tagging                                                 * | ||||
|  * Tagger - Advanced Tagging v2                                              * | ||||
|  * Author: Artanis (Erik Youngren <artanis.00@gmail.com>)                    * | ||||
|  * Do not remove this notice.                                                * | ||||
|  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| @ -8,65 +8,83 @@ | ||||
| 	text-align:left; | ||||
| } | ||||
| 
 | ||||
| #tagger_window { | ||||
| #tagger_parent { | ||||
| 	position:fixed; | ||||
| 	text-align:left; | ||||
| 	min-width:250px; | ||||
| 	top:25px; | ||||
| 	right:25px; | ||||
| 	max-width:300px; | ||||
| 
 | ||||
| } | ||||
| #tagger_parent * { | ||||
| 	background-color:#EEE; | ||||
| } | ||||
| 
 | ||||
| #tagger_titlebar { | ||||
| 	background-color:#DDDDDD; | ||||
| 	background-image:none; | ||||
| 	background-color:#ddd; | ||||
| 	border:2px solid; | ||||
| 	cursor:move; | ||||
| 	font-weight:bold; | ||||
| 	/*margin-bottom:.5em;*/ | ||||
| 	-moz-border-radius:2em 2em 0 0; | ||||
| 	-moz-border-radius:5px 5px 0 0; | ||||
| 	padding:.25em; | ||||
| 	position:relative; | ||||
| 	text-align:center; | ||||
| 	top:-0em; | ||||
| } | ||||
| 
 | ||||
| #tagger_toolbar, #tagger_body { | ||||
| 	padding:.25em; | ||||
| 	border-style:solid; | ||||
| 	border-width: 0px 2px 0px 2px;	 | ||||
| } | ||||
| #tagger_body { | ||||
| 	background-color:#EEEEEE; | ||||
| 	border:2px solid; | ||||
| 	border-top:none; | ||||
| 	overflow:scroll; | ||||
| 	padding:1em; | ||||
| 	max-height:200px; | ||||
| 	max-height:250px; | ||||
| 	overflow:auto; | ||||
| 	overflow-x:hidden; | ||||
| 	overflow-y:auto; | ||||
| } | ||||
| #tagger_filter { | ||||
| 	background-color:#EEEEEE; | ||||
| 
 | ||||
| #tagger_statusbar { | ||||
| 	background-color:#ddd; | ||||
| 	border:2px solid; | ||||
| 	border-bottom:none; | ||||
| 	border-top:none; | ||||
| 	margin-bottom:-1px; | ||||
| 	/*-moz-border-radius:1em 1em 0 0;*/ | ||||
| 	padding:1em; | ||||
| 	padding-bottom:1px; | ||||
| 	text-align:center; | ||||
| 	font-weight: bold; | ||||
| 	-moz-border-radius:0 0 5px 5px; | ||||
| 	padding:.25em; | ||||
| } #tagger_statusbar * { background-color:#ddd; } | ||||
| 
 | ||||
| #tagger_body div { | ||||
| 	padding-bottom:.125em; | ||||
| 	margin-bottom:.125em; | ||||
| 	border-top:1px solid; | ||||
| } | ||||
| #tagger_filter input { | ||||
| 
 | ||||
| /* Tagger Styling | ||||
|  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| #Tagger form { | ||||
| 	display:inline; | ||||
| } | ||||
| #Tagger input { | ||||
| 	width:auto; | ||||
| } | ||||
| #Tagger input[type=text] { | ||||
| 	background-color:white; | ||||
| } | ||||
| 
 | ||||
| #tagger_body a { | ||||
| /* Custom Element Base Styles | ||||
|  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| 
 | ||||
| list { | ||||
| 	display:inherit; | ||||
| } | ||||
| tag { | ||||
| 	font-size:1.25em; | ||||
| 	display:block; | ||||
| } | ||||
| tag:hover { | ||||
| 	cursor:pointer; | ||||
| 	font-weight: bold; | ||||
| 	background-color:#ddd; | ||||
| } | ||||
| list[id=image] tag:hover { | ||||
| 
 | ||||
| #Use #Tagger { | ||||
| 	-moz-column-gap:20px; | ||||
| 	-moz-column-count:3; | ||||
| } | ||||
| list[id=search] tag:hover { | ||||
| 
 | ||||
| #Use #Tagger li { | ||||
| 	margin-bottom:1em; | ||||
| 	max-width:30em; | ||||
| } | ||||
| 
 | ||||
| #Tagger .tagger_js { | ||||
| 	cursor:pointer; | ||||
| } | ||||
|  | ||||
| @ -1,198 +1,64 @@ | ||||
| <?php | ||||
| // Tagger - Advanced Tagging
 | ||||
| // Author: Artanis (Erik Youngren <artanis.00@gmail.com>)
 | ||||
| // Do not remove this notice.
 | ||||
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||||
|  * Tagger - Advanced Tagging v2                                              * | ||||
|  * Author: Artanis (Erik Youngren <artanis.00@gmail.com>)                    * | ||||
|  * Do not remove this notice.                                                * | ||||
|  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||||
| 
 | ||||
| class taggerTheme extends Themelet { | ||||
| 	public function build_tagger ($page, $event) { | ||||
| 		// Initialization code
 | ||||
| 		// TODO: AJAX test and fallback.
 | ||||
| 		$page->add_block(new Block(null, | ||||
| 			"<script type='text/javascript'>
 | ||||
| 				var query = '".make_link("tagger/tags")."'; | ||||
| 				var image_id = ".$event->get_image()->id."; | ||||
| 				var tagger_filter_timer = null; | ||||
| 				var Tagger = new Tagger(); | ||||
| 				Tagger.initialize(); | ||||
| 			</script>","main",1000));
 | ||||
| 		 | ||||
| 	public function build($event,$tags) { | ||||
| 		// When overriding this function, take care that all tag attributes are
 | ||||
| 		// maintained UNCHANGED. There are no attributes in the HTML code below
 | ||||
| 		// that go unused by the javascript. If you fail to do this,
 | ||||
| 		// the extension will BREAK!
 | ||||
| 		//
 | ||||
| 		// Now that that's sunk in, chaniging title attributes is fine.
 | ||||
| 		global $config; | ||||
| 		global $page; | ||||
| 		 | ||||
| 		// set up data
 | ||||
| 		$base_href = $config->get_string('base_href'); | ||||
| 		$tagme     = $config->get_string( | ||||
| 			'ext-tagger_clear-tagme','N')=="Y"? | ||||
| 			"<input type='hidden' id='tagme' tag='tagme'></input>": | ||||
| 			null; | ||||
| 		$url_about = make_link("about/tagger"); | ||||
| 		 | ||||
| 		// build floater tags
 | ||||
| 		$h_tags = ""; | ||||
| 		foreach($tags as $tag) { | ||||
| 			$h_tags .= $this->tag_to_html($tag); | ||||
| 		} | ||||
| 		 | ||||
| 		$html = " | ||||
| 		<div style='font-size:0.7em;'> | ||||
| 			Collapse this block to hide Tagger | ||||
| 		</div> | ||||
| 		<a href='$url_about'>About Tagger</a> | ||||
| 		<hr/> | ||||
| 		<button onclick='taggerResetPos();' class='tagger_js'>Default Location</button>".
 | ||||
| 		// Tagger Floater
 | ||||
| 		"<div id='tagger_window'>
 | ||||
| 			<div id='tagger_titlebar' title='Drag to move'>Tagger</div>			 | ||||
| 			<div id='tagger_filter'> | ||||
| 				<input type='text' id='tagger_new-tag' value='' size='12'  | ||||
| 					onfocus='this.select();' onkeyup='tagger_filter();' | ||||
| 					focus='' title='Type to search' > | ||||
| 				</input> | ||||
| 				<input type='button' value='Add' tag='' title='Add typed tag' | ||||
| 					onclick=' | ||||
| 						this.attributes.tag.value= | ||||
| 							byId(\"tagger_new-tag\").value;
 | ||||
| 						toggleTag(this);'> | ||||
| 				</input> | ||||
| 				<input type='button' value='Set' onclick='pushSet();' | ||||
| 					title='Save tags'></input> | ||||
| 				$tagme | ||||
| 				<hr/> | ||||
| 				<a id='tagger_mode' class='tagger_js' mode='all' | ||||
| 					onclick='taggerToggleMode()'>View Applied Tags</a> | | ||||
| 				<a onclick='tagger_tagIndicators(); tagger_filter(true);' | ||||
| 					class='tagger_js' >Refresh Filter</a> | ||||
| 			</div> | ||||
| 			<div id='tagger_body'>$h_tags</div> | ||||
| 			<img src='$base_href/ext/tagger/onload.gif'	style='display:none;' | ||||
| 				onload='taggerInit();' /> | ||||
| 		</div>";
 | ||||
| 		 | ||||
| 		// Tagger block
 | ||||
| 		$page->add_block( new Block( | ||||
| 			"Tagger", | ||||
| 			$html, | ||||
| 			"left")); | ||||
| 		$page->add_header( | ||||
| 			"<script
 | ||||
| 				src='$base_href/ext/tagger/webtoolkit.drag.js' | ||||
| 				type='text/javascript'></script>");
 | ||||
| 			$this->html($event->get_image()), | ||||
| 			"main")); | ||||
| 	} | ||||
| 	private function html($image) { | ||||
| 		$i_image_id = int_escape($image->id); | ||||
| 		$h_source = html_escape($image->source); | ||||
| 		if(isset($_GET['search'])) {$h_query = "search=".url_escape($_GET['search']);} | ||||
| 		else {$h_query = "";} | ||||
| 		 | ||||
| 		$url_form = make_link("tag_edit/set"); | ||||
| 		 | ||||
| 	final public function show_about ($event) { | ||||
| 		// The about page for Tagger. No override. Feel free to CSS it, though.
 | ||||
| 		global $page; | ||||
| 		global $config; | ||||
| 		$base_href = $config->get_string('base_href'); | ||||
| 		$html = <<< EOD | ||||
| <div id="tagger_parent" style="display:none;"> | ||||
| 	<div id="tagger_titlebar">Tagger</div> | ||||
| 	 | ||||
| 		$script1 = "$base_href/ext/tagger/script.js"; | ||||
| 		$script2 = "$base_href/ext/tagger/webtoolkit.drag.js"; | ||||
| 	<div id="tagger_toolbar"> | ||||
| 		<input type="text" value="" id="tagger_filter" onkeyup="Tagger.tagSearch(this.value, 500);"></input> | ||||
| 		<input type="button" value="Add" onclick="Tagger.createTag(byId('tagger_filter').value);"></input> | ||||
| 		<form action="$url_form" method="POST" onsubmit="Tagger.submit();"> | ||||
| 			<input type='hidden' name='image_id' value='$i_image_id' id="image_id"></input> | ||||
| 			<input type='hidden' name='query' value='$h_query'></input> | ||||
| 			<input type='hidden' name='source' value='$h_source'></input> | ||||
| 			<input type="hidden" name="tags" value="" id="tagger_tags"></input> | ||||
| 
 | ||||
| 		$html = str_replace("\"",""",str_replace("\'","'"," | ||||
| 		<ul id='Tagger'> | ||||
| 			<li> | ||||
| 				If Tagger is in your way, click and drag it\'s title bar to move | ||||
| 				it to a more convienient location. | ||||
| 			<li> | ||||
| 				Click the links to add the tag to the image\'s tag list, when | ||||
| 				done, press	the Set button to save the tags. | ||||
| 			</li> | ||||
| 			<li> | ||||
| 				<p>Tagger gets all the tags with 2 or more uses, so the list can | ||||
| 				get quite large. If you are having trouble finding the tag you | ||||
| 				are looking for, you can enter it into the box at the top and as | ||||
| 				you type, Tagger will remove tags that do not match to aid your | ||||
| 				search. Usually, you\'ll only need one or two letters to trim | ||||
| 				the list down to the tag you are looking for. | ||||
| 				</p> | ||||
| 				<p>One letter filters will look only at the first letter of the | ||||
| 				tag. Two or more letters will search the beginning of every | ||||
| 				word in every tag. | ||||
| 				</p> | ||||
| 				<p>If the tag is not in the list, finish typing out the tag and | ||||
| 				click \"Add\" to add the tag to the image\'s tag list.
 | ||||
| 				</p> | ||||
| 				<p>Tags must have two uses to appear in Tagger\'s list, so | ||||
| 				you'll have to enter the tag for at least one other image for it | ||||
| 				to show up. | ||||
| 				</p> | ||||
| 			</li> | ||||
| 			<li><h4>Requirements</h4> | ||||
| 				<p>Tagger requires javascript for its functionality. Sorry, but | ||||
| 				there\'s no other way to accomplish the tag list | ||||
| 				modifications. | ||||
| 				</p> | ||||
| 				<p>If you have javascript completely disabled, you will not be | ||||
| 				able to use Tagger. | ||||
| 				</p> | ||||
| 				<p>Depending on your method of disabling javascript, you may be | ||||
| 				able to whitelist scripts. The script files used by Tagger are | ||||
| 				<a href='$script1'>script.js</a> and | ||||
| 				<a href='$script2'>webtoolkit.drag.js</a>. | ||||
| 				</p> | ||||
| 			</li> | ||||
| 		</ul>"));
 | ||||
| 		 | ||||
| 		$page->set_title("Shimmie - About / Tagger - Advanced Tagging"); | ||||
| 		$page->set_heading("About / Tagger - Advanced Tagging"); | ||||
| 		$page->add_block( new Block("Author", | ||||
| 			"Artanis (Erik Youngren <artanis.00@gmail.com>)","main",0)); | ||||
| 		$page->add_block( new Block("Use", $html,"main",1)); | ||||
| 	} | ||||
| 	 | ||||
| 	function configTagger($page/*,$presets*/) { | ||||
| 		$presets = array( | ||||
| 			'test_set_01' => 'tag set 01', | ||||
| 			'test_set_02' => 'tag set 02', | ||||
| 			'tsuryuya' => 'tsuryuya hair_green eyes_yellow suzumiya_haruhi_no_yuutsu', | ||||
| 			'suzumiya_haruhi' => 'suzumiya_haruhi hair_brown eyes_brown suzumiya_haruhi_no_yuutsu'); | ||||
| 		 | ||||
| 		$html="<table>"; | ||||
| 		$html .= "<tr><th>Set Name</th><th>Tags</th></tr>"; | ||||
| 		foreach($presets as $keyname => $tags) { | ||||
| 			$html .= "<tr><td>$keyname</td><td>$tags</td></tr>"; | ||||
| 		} | ||||
| 		$html .= "</table>"; | ||||
| 		 | ||||
| 			 | ||||
| 		$page->set_title("Shimmie / Configure / Tagger"); | ||||
| 		$page->set_heading("Configure / Tagger"); | ||||
| 		$page->add_block( new Block("Tag Presets",$html,'main',0)); | ||||
| 	} | ||||
| 
 | ||||
| 	final function tag_to_html ($tag) { | ||||
| 		// Important for script.js, no override. You can CSS this, though.
 | ||||
| 		// If you must, remove the 'final' keyword, but MAKE SURE the entire <A>
 | ||||
| 		// tag is COPIED TO THE NEW FUNCTION EXACTLY! If you fail to do this,
 | ||||
| 		// the extension will BREAK!
 | ||||
| 		$tag_name = $tag['tag']; | ||||
| 		$tag_id = $this->tag_id($tag_name); | ||||
| 		$stag = $this->trimTag($tag_name,20,"_"); | ||||
| 		 | ||||
| 		$html = " | ||||
| 			<a id='$tag_id' title='Apply "$tag_name"' tag='$tag_name' | ||||
| 				onclick='toggleTag(this)'>$stag</a>";
 | ||||
| 			<input type="submit" value="Set"></input> | ||||
| 		</form> | ||||
| 		<!--<ul id="tagger_p-menu"></ul> | ||||
| 		<br style="clear:both;"/>--> | ||||
| 	</div> | ||||
| 	 | ||||
| 	<div id="tagger_body"> | ||||
| 		<div id="tagger_p-applied" name="Applied Tags"></div> | ||||
| 		<div id="tagger_p-search" name="Searched Tags"></div> | ||||
| 	</div> | ||||
| 	<div id="tagger_statusbar"></div> | ||||
| </div> | ||||
| EOD; | ||||
| 		return $html; | ||||
| 	} | ||||
| 	 | ||||
| 	// Important for script.js, no override.
 | ||||
| 	final function tag_id ($tag) { | ||||
| 		$tag_id = ""; | ||||
| 		$m=null; | ||||
| 		for($i=0; $i < strlen($tag); $i++) { | ||||
| 			$l = substr($tag,$i,1); | ||||
| 			$m=null; | ||||
| 			preg_match("[\pP]",$l,$m); | ||||
| 			$tag_id .= !isset($m[0]) ? $l:"_"; | ||||
| 		} | ||||
| 		 | ||||
| 		return trim(str_replace("__","_",$tag_id)," _"); | ||||
| 	} | ||||
| 	 | ||||
| 	function trimTag($s,$len=80,$br=" ") { | ||||
| 		if(strlen($s) > $len) { | ||||
| 			$s = substr($s, 0,$len-1); | ||||
| 			$s = substr($s,0, strrpos($s,$br))."..."; | ||||
| 		} | ||||
| 		return $s; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| ?>
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user