From 9febcedf936d1d806ae70eaceb3feb3349c08a26 Mon Sep 17 00:00:00 2001
From: Fabrixxm <fabrix.xm@gmail.com>
Date: Tue, 17 Dec 2013 05:19:06 -0500
Subject: [PATCH] user contacts: fix #797 , add searching in url and nickname,
 add batch actions

---
 include/Contact.php                           |   3 +-
 mod/contacts.php                              | 151 ++++++++++++++----
 view/templates/contact_template.tpl           |  12 +-
 view/templates/contacts-template.tpl          |  41 ++++-
 view/theme/duepuntozero/style.css             |   3 +
 view/theme/quattro/dark/style.css             |  13 ++
 view/theme/quattro/green/style.css            |  13 ++
 view/theme/quattro/lilac/style.css            |  13 ++
 view/theme/quattro/quattro.less               |   7 +-
 .../quattro/templates/contact_template.tpl    |   6 +-
 10 files changed, 206 insertions(+), 56 deletions(-)

diff --git a/include/Contact.php b/include/Contact.php
index ce1edbcc21..af77869989 100644
--- a/include/Contact.php
+++ b/include/Contact.php
@@ -215,13 +215,14 @@ function contact_photo_menu($contact) {
 		$status_link = $profile_link . "?url=status";
 		$photos_link = $profile_link . "?url=photos";
 		$profile_link = $profile_link . "?url=profile";
-		$contact_drop_link = $a->get_baseurl() . '/contacts/' . $contact['id'] . '/drop';
 		$pm_url = $a->get_baseurl() . '/message/new/' . $contact['id'];
 	}
 
 	$poke_link = $a->get_baseurl() . '/poke/?f=&c=' . $contact['id'];
 	$contact_url = $a->get_baseurl() . '/contacts/' . $contact['id'];
 	$posts_link = $a->get_baseurl() . '/network/0?nets=all&cid=' . $contact['id'];
+	$contact_drop_link = $a->get_baseurl() . "/contacts/" . $contact['id'] . '/drop?confirm=1';
+	
 
 	$menu = Array(
 		'poke' => array(t("Poke"), $poke_link),
diff --git a/mod/contacts.php b/mod/contacts.php
index a405cedbc6..d19c75ab96 100644
--- a/mod/contacts.php
+++ b/mod/contacts.php
@@ -67,11 +67,61 @@ function contacts_init(&$a) {
 
 }
 
+function contacts_batch_actions(&$a){
+	$contacts_id = $_POST['contact_batch'];
+	if (!is_array($contacts_id)) return;
+	
+	$orig_records = q("SELECT * FROM `contact` WHERE `id` IN (%s) AND `uid` = %d AND `self` = 0",
+		implode(",", $contacts_id),
+		intval(local_user())
+	);
+	
+	$count_actions=0;
+	foreach($orig_records as $orig_record) {
+		$contact_id = $orig_record['id'];
+		if (x($_POST, 'contacts_batch_update')) {
+			_contact_update($contact_id);
+			$count_actions++;
+		}
+		if (x($_POST, 'contacts_batch_block')) {
+			$r  = _contact_block($contact_id, $orig_record);
+			if ($r) $count_actions++;
+		}
+		if (x($_POST, 'contacts_batch_ignore')) {
+			$r = _contact_ignore($contact_id, $orig_record);
+			if ($r) $count_actions++;
+		}
+		if (x($_POST, 'contacts_batch_archive')) {
+			$r = _contact_archive($contact_id, $orig_record);
+			if ($r) $count_actions++;
+		}
+		if (x($_POST, 'contacts_batch_drop')) {
+			_contact_drop($contact_id, $orig_record);
+			$count_actions++;
+		}
+	}
+	if ($count_actions>0) {
+		info ( sprintf( tt("%d contact edited.", "%d contacts edited", $count_actions), $count_actions) );
+	}
+	
+	if(x($_SESSION,'return_url'))
+		goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']);
+	else
+		goaway($a->get_baseurl(true) . '/contacts');
+
+}
+
+
 function contacts_post(&$a) {
 	
 	if(! local_user())
 		return;
 
+	if ($a->argv[1]==="batch") {
+		contacts_batch_actions($a);
+		return;
+	}
+
 	$contact_id = intval($a->argv[1]);
 	if(! $contact_id)
 		return;
@@ -134,6 +184,49 @@ function contacts_post(&$a) {
 
 }
 
+/*contact actions*/
+function _contact_update($contact_id) {
+	// pull feed and consume it, which should subscribe to the hub.
+	proc_run('php',"include/poller.php","$contact_id"); 
+}
+function _contact_block($contact_id, $orig_record) {
+	$blocked = (($orig_record['blocked']) ? 0 : 1);
+	$r = q("UPDATE `contact` SET `blocked` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1",
+		intval($blocked),
+		intval($contact_id),
+		intval(local_user())
+	);
+	return $r;
+
+}
+function _contact_ignore($contact_id, $orig_record) {
+	$readonly = (($orig_record['readonly']) ? 0 : 1);
+	$r = q("UPDATE `contact` SET `readonly` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1",
+		intval($readonly),
+		intval($contact_id),
+		intval(local_user())
+	);
+	return $r;
+}
+function _contact_archive($contact_id, $orig_record) {
+	$archived = (($orig_record['archive']) ? 0 : 1);
+	$r = q("UPDATE `contact` SET `archive` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1",
+		intval($archived),
+		intval($contact_id),
+		intval(local_user())
+	);
+	if ($archived) {
+		q("UPDATE `item` SET `private` = 2 WHERE `contact-id` = %d AND `uid` = %d", intval($contact_id), intval(local_user()));
+	}
+	return $r;
+}
+function _contact_drop($contact_id, $orig_record) {
+	require_once('include/Contact.php');
+	$a = get_app();
+
+	terminate_friendship($a->user,$a->contact,$orig_record);
+	contact_remove($orig_record['id']);
+}
 
 
 function contacts_content(&$a) {
@@ -168,57 +261,38 @@ function contacts_content(&$a) {
 		}
 		
 		if($cmd === 'update') {
-
-			// pull feed and consume it, which should subscribe to the hub.
-			proc_run('php',"include/poller.php","$contact_id");
+			_contact_update($contact_id);
 			goaway($a->get_baseurl(true) . '/contacts/' . $contact_id);
 			// NOTREACHED
 		}
 
 		if($cmd === 'block') {
-			$blocked = (($orig_record[0]['blocked']) ? 0 : 1);
-			$r = q("UPDATE `contact` SET `blocked` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1",
-				intval($blocked),
-				intval($contact_id),
-				intval(local_user())
-			);
+			$r = _contact_block($contact_id, $orig_record[0]);
 			if($r) {
-				//notice( t('Contact has been ') . (($blocked) ? t('blocked') : t('unblocked')) . EOL );
 				info( (($blocked) ? t('Contact has been blocked') : t('Contact has been unblocked')) . EOL );
 			}
+			
 			goaway($a->get_baseurl(true) . '/contacts/' . $contact_id);
 			return; // NOTREACHED
 		}
 
 		if($cmd === 'ignore') {
-			$readonly = (($orig_record[0]['readonly']) ? 0 : 1);
-			$r = q("UPDATE `contact` SET `readonly` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1",
-				intval($readonly),
-				intval($contact_id),
-				intval(local_user())
-			);
+			$r = _contact_ignore($contact_id, $orig_record[0]);
 			if($r) {
 				info( (($readonly) ? t('Contact has been ignored') : t('Contact has been unignored')) . EOL );
 			}
+			
 			goaway($a->get_baseurl(true) . '/contacts/' . $contact_id);
 			return; // NOTREACHED
 		}
 
 
 		if($cmd === 'archive') {
-			$archived = (($orig_record[0]['archive']) ? 0 : 1);
-			$r = q("UPDATE `contact` SET `archive` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1",
-				intval($archived),
-				intval($contact_id),
-				intval(local_user())
-			);
-			if ($archived) {
-				q("UPDATE `item` SET `private` = 2 WHERE `contact-id` = %d AND `uid` = %d", intval($contact_id), intval(local_user()));
-			}
+			$r = _contact_archive($contact_id, $orig_record[0]);
 			if($r) {
-				//notice( t('Contact has been ') . (($archived) ? t('archived') : t('unarchived')) . EOL );
 				info( (($archived) ? t('Contact has been archived') : t('Contact has been unarchived')) . EOL );
-			}
+			}			
+			
 			goaway($a->get_baseurl(true) . '/contacts/' . $contact_id);
 			return; // NOTREACHED
 		}
@@ -251,15 +325,13 @@ function contacts_content(&$a) {
 			}
 			// Now check how the user responded to the confirmation query
 			if($_REQUEST['canceled']) {
-				goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']);
-
+				if(x($_SESSION,'return_url'))
+					goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']);
+				else
+					goaway($a->get_baseurl(true) . '/contacts');
 			}
 
-			require_once('include/Contact.php');
-
-			terminate_friendship($a->user,$a->contact,$orig_record[0]);
-
-			contact_remove($orig_record[0]['id']);
+			_contact_drop($contact_id, $orig_record[0]);
 			info( t('Contact has been removed.') . EOL );
 			if(x($_SESSION,'return_url'))
 				goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']);
@@ -526,7 +598,7 @@ function contacts_content(&$a) {
 		$search_txt = dbesc(protect_sprintf(preg_quote($search)));
 		$searching = true;
 	}
-	$sql_extra .= (($searching) ? " AND `name` REGEXP '$search_txt' " : "");
+	$sql_extra .= (($searching) ? " AND (name REGEXP '$search_txt' OR url REGEXP '$search_txt'  OR nick REGEXP '$search_txt') " : "");
 
 	if($nets)
 		$sql_extra .= sprintf(" AND network = '%s' ", dbesc($nets));
@@ -604,6 +676,7 @@ function contacts_content(&$a) {
 	
 	$tpl = get_markup_template("contacts-template.tpl");
 	$o .= replace_macros($tpl, array(
+		'$baseurl' => $a->get_baseurl(),
 		'$header' => t('Contacts') . (($nets) ? ' - ' . network_to_name($nets) : ''),
 		'$tabs' => $t,
 		'$total' => $total,
@@ -613,6 +686,14 @@ function contacts_content(&$a) {
 		'$submit' => t('Find'),
 		'$cmd' => $a->cmd,
 		'$contacts' => $contacts,
+		'$contact_drop_confirm' => t('Do you really want to delete this contact?'),
+		'$batch_actions' => array(
+			'contacts_batch_update' => t('Update'),
+			'contacts_batch_block' => t('Block')."/".t("Unblock"),
+			"contacts_batch_ignore" => t('Ignore')."/".t("Unignore"),
+			"contacts_batch_archive" => t('Archive')."/".t("Unarchive"),
+			"contacts_batch_drop" => t('Delete'),
+		),
 		'$paginate' => paginate($a),
 
 	)); 
diff --git a/view/templates/contact_template.tpl b/view/templates/contact_template.tpl
index 8e0e1acc7f..196254960b 100644
--- a/view/templates/contact_template.tpl
+++ b/view/templates/contact_template.tpl
@@ -1,8 +1,3 @@
-{{*
- *	AUTOMATICALLY GENERATED TEMPLATE
- *	DO NOT EDIT THIS FILE, CHANGES WILL BE OVERWRITTEN
- *
- *}}
 
 <div class="contact-entry-wrapper" id="contact-entry-wrapper-{{$contact.id}}" >
 	<div class="contact-entry-photo-wrapper" >
@@ -12,15 +7,16 @@
 
 			<a href="{{$contact.url}}" title="{{$contact.img_hover}}" /><img src="{{$contact.thumb}}" {{$contact.sparkle}} alt="{{$contact.name}}" /></a>
 
+			<input type="checkbox" class="contact-select" name="contact_batch[]" value="{{$contact.id}}">
 			{{if $contact.photo_menu}}
 			<span onclick="openClose('contact-photo-menu-{{$contact.id}}');" class="fakelink contact-photo-menu-button" id="contact-photo-menu-button-{{$contact.id}}">menu</span>
                 <div class="contact-photo-menu" id="contact-photo-menu-{{$contact.id}}">
                     <ul>
-						{{foreach $contact.photo_menu as $c}}
+						{{foreach $contact.photo_menu as $k=>$c}}
 						{{if $c.2}}
-						<li><a target="redir" href="{{$c.1}}">{{$c.0}}</a></li>
+						<li><a class="{{$k}}" target="redir" href="{{$c.1}}">{{$c.0}}</a></li>
 						{{else}}
-						<li><a href="{{$c.1}}">{{$c.0}}</a></li>
+						<li><a class="{{$k}}" href="{{$c.1}}">{{$c.0}}</a></li>
 						{{/if}}
 						{{/foreach}}
                     </ul>
diff --git a/view/templates/contacts-template.tpl b/view/templates/contacts-template.tpl
index 66f3f5c87b..7dc6e0cec3 100644
--- a/view/templates/contacts-template.tpl
+++ b/view/templates/contacts-template.tpl
@@ -1,8 +1,4 @@
-{{*
- *	AUTOMATICALLY GENERATED TEMPLATE
- *	DO NOT EDIT THIS FILE, CHANGES WILL BE OVERWRITTEN
- *
- *}}
+
 <h1>{{$header}}{{if $total}} ({{$total}}){{/if}}</h1>
 
 {{if $finding}}<h4>{{$finding}}</h4>{{/if}}
@@ -18,11 +14,44 @@
 
 {{$tabs}}
 
-
+<form action="{{$baseurl}}/contacts/batch/" method="POST">
 {{foreach $contacts as $contact}}
 	{{include file="contact_template.tpl"}}
 {{/foreach}}
 <div id="contact-edit-end"></div>
+<div class="submit">
+{{foreach $batch_actions as $n=>$l}}
+ <input class="batch-action" name="{{$n}}" value="{{$l}}" type="submit">
+ {{/foreach}}
+ </div>
+</form>
+<script>
+ $(document).ready(function() {
+  // javascript dialog to batch actions
+  $(".batch-action").click(function(e){
+    if (confirm($(this).attr('value')+" ?")) {
+     return true;
+    } else {
+     e.preventDefault();
+     return false;
+    }
+  });
+ 
+  // add javascript confirm dialog to "drop" links. Plain html url have "?confirm=1" to show confirmation form, we need to remove it
+  $(".drop").each(function() {
+   $(this).attr('href', $(this).attr('href').replace("confirm=1","") );
+   $(this).click(function(e){
+    if (confirm("{{$contact_drop_confirm}}")) {
+     return true;
+    } else {
+     e.preventDefault();
+     return false;
+    }
+   });
+   
+  });
+ });
+ </script>
 
 {{$paginate}}
 
diff --git a/view/theme/duepuntozero/style.css b/view/theme/duepuntozero/style.css
index 3567062a4e..77362e5524 100644
--- a/view/theme/duepuntozero/style.css
+++ b/view/theme/duepuntozero/style.css
@@ -1617,6 +1617,9 @@ input#dfrn-url {
 	margin-top: 20px;
 }
 
+.contact-select {	position: absolute; top: 64px; left:64px; display:none; }
+.contact-select:checked,
+.contact-entry-photo:hover .contact-select {	display:block; }
 
 .contact-photo-menu-button {
 	position: absolute;
diff --git a/view/theme/quattro/dark/style.css b/view/theme/quattro/dark/style.css
index e72e557038..f09b240b34 100644
--- a/view/theme/quattro/dark/style.css
+++ b/view/theme/quattro/dark/style.css
@@ -1349,6 +1349,19 @@ span[id^="showmore-wrap"] {
 .contact-photo-wrapper {
   position: relative;
 }
+.contact-select {
+  position: absolute;
+  top: 64px;
+  left: 64px;
+  display: none;
+}
+.contact-select:checked,
+.contact-photo:hover .contact-select {
+  display: block;
+}
+.submit {
+  clear: both;
+}
 .contact-photo {
   width: 48px;
   height: 48px;
diff --git a/view/theme/quattro/green/style.css b/view/theme/quattro/green/style.css
index 8e6a020b8a..2c664b52dc 100644
--- a/view/theme/quattro/green/style.css
+++ b/view/theme/quattro/green/style.css
@@ -1349,6 +1349,19 @@ span[id^="showmore-wrap"] {
 .contact-photo-wrapper {
   position: relative;
 }
+.contact-select {
+  position: absolute;
+  top: 64px;
+  left: 64px;
+  display: none;
+}
+.contact-select:checked,
+.contact-photo:hover .contact-select {
+  display: block;
+}
+.submit {
+  clear: both;
+}
 .contact-photo {
   width: 48px;
   height: 48px;
diff --git a/view/theme/quattro/lilac/style.css b/view/theme/quattro/lilac/style.css
index d35ad52ed0..c75178437c 100644
--- a/view/theme/quattro/lilac/style.css
+++ b/view/theme/quattro/lilac/style.css
@@ -1349,6 +1349,19 @@ span[id^="showmore-wrap"] {
 .contact-photo-wrapper {
   position: relative;
 }
+.contact-select {
+  position: absolute;
+  top: 64px;
+  left: 64px;
+  display: none;
+}
+.contact-select:checked,
+.contact-photo:hover .contact-select {
+  display: block;
+}
+.submit {
+  clear: both;
+}
 .contact-photo {
   width: 48px;
   height: 48px;
diff --git a/view/theme/quattro/quattro.less b/view/theme/quattro/quattro.less
index 6287a05ef8..8749267501 100644
--- a/view/theme/quattro/quattro.less
+++ b/view/theme/quattro/quattro.less
@@ -527,7 +527,8 @@ section {
 	.contact-photo-wrapper { margin-left: 16px; }
 	.contact-photo { 
 		width: 32px; height: 32px; 
-	}	
+	}
+	
 	.contact-photo-menu-button {
 		top: 15px !important;
 		left: 0px !important;
@@ -738,6 +739,10 @@ span[id^="showmore-wrap"] {
 
 
 .contact-photo-wrapper { position: relative; }
+.contact-select {	position: absolute; top:64px; left:64px; display:none; }
+.contact-select:checked,
+.contact-photo:hover .contact-select {	display:block; }
+.submit { clear: both; }
 .contact-photo { 
 	width: 48px; height: 48px;
 	img { width: 48px; height: 48px; }
diff --git a/view/theme/quattro/templates/contact_template.tpl b/view/theme/quattro/templates/contact_template.tpl
index c74a513b8f..a63ffb3c9a 100644
--- a/view/theme/quattro/templates/contact_template.tpl
+++ b/view/theme/quattro/templates/contact_template.tpl
@@ -1,8 +1,3 @@
-{{*
- *	AUTOMATICALLY GENERATED TEMPLATE
- *	DO NOT EDIT THIS FILE, CHANGES WILL BE OVERWRITTEN
- *
- *}}
 
 <div class="contact-wrapper" id="contact-entry-wrapper-{{$id}}" >
 	<div class="contact-photo-wrapper" >
@@ -12,6 +7,7 @@
 
 			<a href="{{$contact.url}}" title="{{$contact.img_hover}}" /><img src="{{$contact.thumb}}" {{$contact.sparkle}} alt="{{$contact.name}}" /></a>
 
+			<input type="checkbox" class="contact-select" name="contact_batch[]" value="{{$contact.id}}">
 			{{if $contact.photo_menu}}
 			<a href="#" rel="#contact-photo-menu-{{$contact.id}}" class="contact-photo-menu-button icon s16 menu" id="contact-photo-menu-button-{{$contact.id}}">menu</a>
 			<ul class="contact-photo-menu menu-popup" id="contact-photo-menu-{{$contact.id}}">