diff --git a/include/class.client.php b/include/class.client.php
index 1d41624bd46f360f9cef063eed0fdc32470768df..31e185c8bef7c763731c6e492214e5522414ee75 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -312,16 +312,29 @@ class  EndUser extends BaseAuthenticatedUser {
             'user_id' => $this->getId(),
         ));
 
-        // TODO: Implement UNION ALL support in the ORM
+        // Also add collaborator tickets to the list. This may seem ugly;
+        // but the general rule for SQL is that a single query can only use
+        // one index. Therefore, to scan two indexes (by user_id and
+        // thread.collaborators.user_id), we need two queries. A union will
+        // help out with that.
         $mine->union($collab->filter(array(
             'thread__collaborators__user_id' => $this->getId(),
             Q::not(array('user_id' => $this->getId()))
         )));
 
-        if ($this->getOrgId()) {
+        if ($orgid = $this->getOrgId()) {
+            // Also generate a separate query for all the tickets owned by
+            // either my organization or ones that I'm collaborating on
+            // which are not part of the organization.
             $myorg = clone $basic;
-            $myorg->filter(array('user__org_id' => $this->getOrgId()))
-                ->values('user__org_id');
+            $myorg->values('user__org_id');
+            $collab = clone $myorg;
+
+            $myorg->filter(array('user__org_id' => $orgid));
+            $myorg->union($collab->filter(array(
+                'thread__collaborators__user_id' => $this->getId(),
+                Q::not(array('user__org_id' => $orgid))
+            )));
         }
 
         return array('mine' => $mine, 'myorg' => $myorg);
diff --git a/include/class.nav.php b/include/class.nav.php
index aafb4dc2562c92c4cc8cf09e2308cabf6343040a..e69014817a1496e555f15de1f94fae2bd6e7dbb9 100644
--- a/include/class.nav.php
+++ b/include/class.nav.php
@@ -344,7 +344,7 @@ class UserNav {
                 $navs['new']=array('desc'=>__('Open a New Ticket'),'href'=>'open.php','title'=>'');
             if($user && $user->isValid()) {
                 if(!$user->isGuest()) {
-                    $navs['tickets']=array('desc'=>sprintf(__('Tickets (%d)'),$user->getNumTickets()),
+                    $navs['tickets']=array('desc'=>sprintf(__('Tickets (%d)'),$user->getNumTickets($user->canSeeOrgTickets())),
                                            'href'=>'tickets.php',
                                             'title'=>__('Show all tickets'));
                 } else {
diff --git a/include/class.organization.php b/include/class.organization.php
index 75024ec6bf832b6070b8883302de53e3456c3662..0c08a4f30c08281e8b473747944b3c81046a1b0d 100644
--- a/include/class.organization.php
+++ b/include/class.organization.php
@@ -35,6 +35,9 @@ class OrganizationModel extends VerySimpleModel {
     const COLLAB_PRIMARY_CONTACT =  0x0002;
     const ASSIGN_AGENT_MANAGER =    0x0004;
 
+    const SHARE_PRIMARY_CONTACT =   0x0008;
+    const SHARE_EVERYBODY =         0x0010;
+
     const PERM_CREATE =     'org.create';
     const PERM_EDIT =       'org.edit';
     const PERM_DELETE =     'org.delete';
@@ -100,6 +103,14 @@ class OrganizationModel extends VerySimpleModel {
         return $this->check(self::ASSIGN_AGENT_MANAGER);
     }
 
+    function shareWithPrimaryContacts() {
+        return $this->check(self::SHARE_PRIMARY_CONTACT);
+    }
+
+    function shareWithEverybody() {
+        return $this->check(self::SHARE_EVERYBODY);
+    }
+
     function getUpdateDate() {
         return $this->updated;
     }
@@ -206,6 +217,8 @@ implements TemplateVariable {
                 'collab-all-flag' => Organization::COLLAB_ALL_MEMBERS,
                 'collab-pc-flag' => Organization::COLLAB_PRIMARY_CONTACT,
                 'assign-am-flag' => Organization::ASSIGN_AGENT_MANAGER,
+                'sharing-primary' => Organization::SHARE_PRIMARY_CONTACT,
+                'sharing-all' => Organization::SHARE_EVERYBODY,
         ) as $ck=>$flag) {
             if ($this->check($flag))
                 $base[$ck] = true;
@@ -393,6 +406,16 @@ implements TemplateVariable {
                 $this->clearStatus($flag);
         }
 
+        foreach (array(
+                'sharing-primary' => Organization::SHARE_PRIMARY_CONTACT,
+                'sharing-all' => Organization::SHARE_EVERYBODY,
+        ) as $ck=>$flag) {
+            if ($vars['sharing'] == $ck)
+                $this->setStatus($flag);
+            else
+                $this->clearStatus($flag);
+        }
+
         // Set staff and primary contacts
         $this->set('domain', $vars['domain']);
         $this->set('manager', $vars['manager'] ?: '');
@@ -427,7 +450,6 @@ implements TemplateVariable {
         if (!($org = Organization::lookup(array('name' => $vars['name'])))) {
             $org = Organization::create(array(
                 'name' => $vars['name'],
-                'created' => new SqlFunction('NOW'),
                 'updated' => new SqlFunction('NOW'),
             ));
             $org->save(true);
@@ -459,10 +481,17 @@ implements TemplateVariable {
         return $valid ? self::fromVars($form->getClean()) : null;
     }
 
+    static function create($vars=false) {
+        $org = parent::create($vars);
+
+        $org->created = new SqlFunction('NOW');
+        $org->setStatus(self::SHARE_PRIMARY_CONTACT);
+        return $org;
+    }
+
     // Custom create called by installer/upgrader to load initial data
     static function __create($ht, &$error=false) {
 
-        $ht['created'] = new SqlFunction('NOW');
         $org = Organization::create($ht);
         // Add dynamic data (if any)
         if ($ht['fields']) {
diff --git a/include/class.orm.php b/include/class.orm.php
index 44d292f664038f825478fc01bc6e2e848e78e4aa..9798d94819db755e51664e83e739b4ec4dfde6f6 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -1069,6 +1069,10 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
         return $this;
     }
 
+    function copy() {
+        return clone $this;
+    }
+
     function all() {
         return $this->getIterator()->asArray();
     }
@@ -2233,10 +2237,13 @@ class MySqlCompiler extends SqlCompiler {
             $vals = array_map(array($this, 'input'), $b);
             $b = '('.implode(', ', $vals).')';
         }
+        // MySQL is almost always faster with a join. Use one if possible
         // MySQL doesn't support LIMIT or OFFSET in subqueries. Instead, add
         // the query as a JOIN and add the join constraint into the WHERE
         // clause.
-        elseif ($b instanceof QuerySet && ($b->isWindowed() || $b->countSelectFields() > 1)) {
+        elseif ($b instanceof QuerySet
+            && ($b->isWindowed() || $b->countSelectFields() > 1 || $b->chain)
+        ) {
             $f1 = $b->values[0];
             $view = $b->asView();
             $alias = $this->pushJoin($view, $a, $view, array('constraint'=>array()));
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 55e3f9fbbb1da0e82d2aecf0df3258ad6e4d96e3..f849340c57ebf4d9a849d2da055e0a5124be5af8 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -358,6 +358,18 @@ implements RestrictedAccess, Threadable {
         if ($user->getId() == $this->getUserId())
             return true;
 
+        // Organization
+        if ($user->canSeeOrgTickets()
+            && ($U = $this->getUser())
+            && ($U->getOrgId() == $user->getOrgId())
+        ) {
+            // The owner of this ticket is in the same organization as the
+            // user in question, and the organization is configured to allow
+            // the user in question to see other tickets in the
+            // organization.
+            return true;
+        }
+
         // Collaborator?
         // 1) If the user was authorized via this ticket.
         if ($user->getTicketId() == $this->getId()
diff --git a/include/class.user.php b/include/class.user.php
index 4fcb2c3cee37b3fdf58e62f639a87f09bb540507..8d5b6836859e2aa0531bdbd659f1c680bb61c8ea 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -432,6 +432,12 @@ implements TemplateVariable {
         return (string) $account->getStatus();
     }
 
+    function canSeeOrgTickets() {
+        return $this->org && (
+                $this->org->shareWithEverybody()
+            || ($this->isPrimaryContact() && $this->org->shareWithPrimaryContacts()));
+    }
+
     function register($vars, &$errors) {
 
         // user already registered?
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index 0b10183a927130e06f18c72d782472493a615792..fef80c6008f3bae8555da341d00dd55a237e316e 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -16,36 +16,27 @@ if (isset($_REQUEST['status'])) {
     $settings['status'] = $_REQUEST['status'];
 }
 
+$org_tickets = $thisclient->canSeeOrgTickets();
 if ($settings['keywords']) {
     // Don't show stat counts for searches
     $openTickets = $closedTickets = -1;
 }
 elseif ($settings['topic_id']) {
-    $openTickets = $thisclient->getNumTopicTicketsInState($settings['topic_id'], 'open');
-    $closedTickets = $thisclient->getNumTopicTicketsInState($settings['topic_id'], 'closed');
+    $openTickets = $thisclient->getNumTopicTicketsInState($settings['topic_id'],
+        'open', $org_tickets);
+    $closedTickets = $thisclient->getNumTopicTicketsInState($settings['topic_id'],
+        'closed', $org_tickets);
 }
 else {
-    $openTickets = $thisclient->getNumOpenTickets();
-    $closedTickets = $thisclient->getNumClosedTickets();
+    $openTickets = $thisclient->getNumOpenTickets($org_tickets);
+    $closedTickets = $thisclient->getNumClosedTickets($org_tickets);
 }
 
-$tickets = TicketModel::objects();
+$tickets = Ticket::objects();
 
 $qs = array();
 $status=null;
 
-if ($settings['status'])
-    $status = strtolower($settings['status']);
-    switch ($status) {
-    default:
-        $status = 'open';
-    case 'open':
-    case 'closed':
-		$results_type = ($status == 'closed') ? __('Closed Tickets') : __('Open Tickets');
-        $tickets->filter(array('status__state' => $status));
-        break;
-}
-
 $sortOptions=array('id'=>'number', 'subject'=>'cdata__subject',
                     'status'=>'status__name', 'dept'=>'dept__name','date'=>'created');
 $orderWays=array('DESC'=>'-','ASC'=>'');
@@ -62,11 +53,40 @@ if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])])
 $x=$sort.'_sort';
 $$x=' class="'.strtolower($_REQUEST['order'] ?: 'desc').'" ';
 
-// Add visibility constraints
-$tickets->filter(Q::any(array(
-    'user_id' => $thisclient->getId(),
-    'thread__collaborators__user_id' => $thisclient->getId(),
-)));
+$basic_filter = Ticket::objects();
+if ($settings['topic_id']) {
+    $basic_filter = $basic_filter->filter(array('topic_id' => $settings['topic_id']));
+}
+
+if ($settings['status'])
+    $status = strtolower($settings['status']);
+    switch ($status) {
+    default:
+        $status = 'open';
+    case 'open':
+    case 'closed':
+		$results_type = ($status == 'closed') ? __('Closed Tickets') : __('Open Tickets');
+        $basic_filter->filter(array('status__state' => $status));
+        break;
+}
+
+// Add visibility constraints — use a union query to use multiple indexes,
+// use UNION without "ALL" (false as second parameter to union()) to imply
+// unique values
+$visibility = $basic_filter->copy()
+    ->values_flat('ticket_id')
+    ->filter(array('user_id' => $thisclient->getId()))
+    ->union($basic_filter->copy()
+        ->values_flat('ticket_id')
+        ->filter(array('thread__collaborators__user_id' => $thisclient->getId()))
+    , false);
+
+if ($thisclient->canSeeOrgTickets()) {
+    $visibility = $visibility->union(
+        $basic_filter->copy()->values_flat('ticket_id')
+            ->filter(array('user__org_id' => $thisclient->getOrgId()))
+    , false);
+}
 
 // Perform basic search
 if ($settings['keywords']) {
@@ -75,14 +95,10 @@ if ($settings['keywords']) {
         $tickets->filter(array('number__startswith'=>$q));
     } else { //Deep search!
         // Use the search engine to perform the search
-        $tickets = $ost->searcher->find($q, $tickets);
+        $tickets = $ost->searcher->find($q, $tickets)->distinct('ticket_id');
     }
 }
 
-if ($settings['topic_id']) {
-    $tickets = $tickets->filter(array('topic_id' => $settings['topic_id']));
-}
-
 TicketForm::ensureDynamicDataView();
 
 $total=$tickets->count();
@@ -91,6 +107,7 @@ $pageNav=new Pagenate($total, $page, PAGE_LIMIT);
 $qstr = '&'. Http::build_query($qs);
 $qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']);
 $pageNav->setURL('tickets.php', $qs);
+$tickets->filter(array('ticket_id__in' => $visibility));
 $pageNav->paginate($tickets);
 
 $showing =$total ? $pageNav->showing() : "";
@@ -104,7 +121,6 @@ if($search)
 
 $negorder=$order=='-'?'ASC':'DESC'; //Negate the sorting
 
-$tickets->order_by($order.$order_by);
 $tickets->values(
     'ticket_id', 'number', 'created', 'isanswered', 'source', 'status_id',
     'status__state', 'status__name', 'cdata__subject', 'dept_id',
@@ -122,8 +138,9 @@ $tickets->values(
     <?php echo __('Help Topic'); ?>:
     <select name="topic_id" class="nowarn" onchange="javascript: this.form.submit(); ">
         <option value="">&mdash; <?php echo __('All Help Topics');?> &mdash;</option>
-<?php foreach (Topic::getHelpTopics(true) as $id=>$name) {
-        $count = $thisclient->getNumTopicTickets($id);
+<?php
+foreach (Topic::getHelpTopics(true) as $id=>$name) {
+        $count = $thisclient->getNumTopicTickets($id, $org_tickets);
         if ($count == 0)
             continue;
 ?>
diff --git a/include/i18n/en_US/help/tips/org.yaml b/include/i18n/en_US/help/tips/org.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0cb4219f5afe31dd8357efee506f35718b2c1ad4
--- /dev/null
+++ b/include/i18n/en_US/help/tips/org.yaml
@@ -0,0 +1,36 @@
+#
+# This is the view / management page for an organization in the user
+# directory
+#
+# Fields:
+# title - Shown in bold at the top of the popover window
+# content - The body of the help popover
+# links - List of links shows below the content
+#   title - Link title
+#   href - href of link (links starting with / are translated to the
+#       helpdesk installation path)
+#
+# The key names such as 'helpdesk_name' should not be translated as they
+# must match the HTML #ids put into the page template.
+#
+---
+org_sharing:
+    title: Ticket Sharing
+    content: >
+        <p>
+        Organization ticket sharing allows members access to tickets owned
+        by other members of the organization.
+        </p>
+        <p class="info-banner">
+        <i class="icon-info-sign"></i>
+        Collaborators always have access to tickets.
+        </p>
+
+email_domain:
+    title: Email Domain
+    content: >
+        Users can be automatically added to this organization based on their
+        email domain(s). Use the box below to enter one or more domains
+        separated by commas. For example, enter <code>mycompany.com</code>
+        for users with email addresses ending in @mycompany.com
+
diff --git a/include/staff/templates/org-profile.tmpl.php b/include/staff/templates/org-profile.tmpl.php
index 90420dab8b8909fac2d7a53d1ed0e28a31b85376..520c73ebfd2c6356fff050714aea0cb99222b163 100644
--- a/include/staff/templates/org-profile.tmpl.php
+++ b/include/staff/templates/org-profile.tmpl.php
@@ -20,7 +20,7 @@ if ($info['error']) {
         ><i class="icon-fixed-width icon-cogs faded"></i>&nbsp;<?php
         echo __('Settings'); ?></a></li>
 </ul>
-<form method="post" class="org" action="<?php echo $action; ?>">
+<form method="post" class="org" action="<?php echo $action; ?>" data-tip-namespace="org">
 <div id="orgprofile_container">
 <div class="tab_content" id="profile" style="margin:5px;">
 <?php
@@ -98,6 +98,22 @@ if ($ticket && $ticket->getOwnerId() == $user->getId())
                     </select>
                     <br/><span class="error"><?php echo $errors['contacts']; ?></span>
                 </td>
+            </tr>
+            <tr>
+                <td width="180">
+                    <?php echo __('Ticket Sharing'); ?>:
+                </td>
+                <td>
+                    <select name="sharing">
+                        <option value=""><?php echo __('Disable'); ?></option>
+                        <option value="sharing-primary" <?php echo $info['sharing-primary'] ? 'selected="selected"' : '';
+                            ?>><?php echo __('Primary contacts see all tickets'); ?></option>
+                        <option value="sharing-all" <?php echo $info['sharing-all'] ? 'selected="selected"' : '';
+                            ?>><?php echo __('All members see all tickets'); ?></option>
+                    </select>
+                    <i class="help-tip icon-question-sign" href="#org_sharing"></i>
+                </td>
+            </tr>
             <tr>
                 <th colspan="2">
                     <?php echo __('Automated Collaboration'); ?>:
@@ -123,7 +139,8 @@ if ($ticket && $ticket->getOwnerId() == $user->getId())
             </tr>
             <tr>
                 <th colspan="2">
-                    <?php echo __('Main Domain'); ?>
+                    <?php echo __('Email Domain'); ?>
+                    <i class="help-tip icon-question-sign" href="#email_domain"></i>
                 </th>
             </tr>
             <tr>
diff --git a/tickets.php b/tickets.php
index 3467bc9ecf7d9f7e705caf97aaee615a61d3a12a..2c80ec91a0acea906a7ff5d1b9fcb050188fe78c 100644
--- a/tickets.php
+++ b/tickets.php
@@ -128,7 +128,7 @@ if($ticket && $ticket->checkUserAccess($thisclient)) {
     }
     else
         $inc='view.inc.php';
-} elseif($thisclient->getNumTickets()) {
+} elseif($thisclient->getNumTickets($thisclient->canSeeOrgTickets())) {
     $inc='tickets.inc.php';
 } else {
     $nav->setActiveNav('new');