Revision: 1938
          http://mrbs.svn.sourceforge.net/mrbs/?rev=1938&view=rev
Author:   cimorrison
Date:     2011-09-08 18:05:32 +0000 (Thu, 08 Sep 2011)
Log Message:
-----------
Merged in latest changes from trunk

Modified Paths:
--------------
    mrbs/branches/datatables/web/Themes/default/header.inc
    mrbs/branches/datatables/web/edit_entry.php
    mrbs/branches/datatables/web/edit_entry_handler.php
    mrbs/branches/datatables/web/lang.en
    mrbs/branches/datatables/web/mrbs.css.php
    mrbs/branches/datatables/web/mrbs_sql.inc
    mrbs/branches/datatables/web/report.php

Property Changed:
----------------
    mrbs/branches/datatables/
    mrbs/branches/datatables/web/upgrade/5/pgsql.sql


Property changes on: mrbs/branches/datatables
___________________________________________________________________
Modified: svn:mergeinfo
   - /mrbs/branches/custom_entry_fields:1374-1396
/mrbs/branches/datepicker:1409-1416
/mrbs/branches/disabled_rooms:1601-1634
/mrbs/branches/from_to_bookings:1491-1587
/mrbs/branches/ics_attachments:1652-1741
/mrbs/branches/improve_css_2008_06:804-872
/mrbs/branches/only_unicode:1747-1749
/mrbs/branches/provisional_bookings:1242-1280
/mrbs/branches/provisional_bookings_new_style:1407-1570
/mrbs/trunk:1863-1933
   + /mrbs/branches/custom_entry_fields:1374-1396
/mrbs/branches/datepicker:1409-1416
/mrbs/branches/disabled_rooms:1601-1634
/mrbs/branches/from_to_bookings:1491-1587
/mrbs/branches/ics_attachments:1652-1741
/mrbs/branches/improve_css_2008_06:804-872
/mrbs/branches/only_unicode:1747-1749
/mrbs/branches/provisional_bookings:1242-1280
/mrbs/branches/provisional_bookings_new_style:1407-1570
/mrbs/trunk:1863-1937

Modified: mrbs/branches/datatables/web/Themes/default/header.inc
===================================================================
--- mrbs/branches/datatables/web/Themes/default/header.inc      2011-09-08 
18:00:20 UTC (rev 1937)
+++ mrbs/branches/datatables/web/Themes/default/header.inc      2011-09-08 
18:05:32 UTC (rev 1938)
@@ -353,6 +353,14 @@
     ?>
     adjustSlotSelectors(document.getElementById('main'));
     <?php
+    if (function_exists('json_encode'))
+    {
+      // If we're doing Ajax checking of the form then we have to validate
+      // the form when the datepicker is closed
+      ?>
+      checkValidBooking();
+      <?php
+    }
   }
   ?>
   if (formId)
@@ -422,6 +430,125 @@
 
 if ($page == 'edit_entry')
 {
+
+  // Add Ajax capabilities (but only if we can return the result as a JSON 
object)
+  if (function_exists('json_encode'))
+  {
+    // Form an array of all the form parameters that could affect whether a 
booking can be made, ie
+    // whether it will conflict with another booking or break a policy rule.  
(So for example 'name'
+    // and 'description' won't make any difference, but 'type' could be used 
in policy checking).
+    $params = array('area', 'rooms[]', 'create_by', 'type', 'all_day', 'id', 
'rep_id', 'edit_type',
+                    'start_seconds', 'start_day', 'start_month', 'start_year',
+                    'end_seconds', 'end_day', 'end_month', 'end_year',
+                    'rep_type', 'rep_end_day', 'rep_end_month', 
'rep_end_year', 'rep_day[]', 'rep_num_weeks');
+    
+    // function to check whether the proposed booking would (a) conflict with 
any other bookings
+    // and (b) conforms to the booking policies.   Makes an Ajax call to 
edit_entry_handler but does
+    // not actually make the booking.                
+    ?>
+    function checkValidBooking()
+    {
+      var params = {'ajax': 1}; // This is an Ajax request
+      var form = $('form#main');
+      var formInput;
+      <?php
+      
+      foreach ($params as $param)
+      {
+        // Scalar parameters (two types - checkboxes and the rest)
+        if (strpos($param, '[]') === FALSE)
+        {
+          ?>
+          formInput = form.find('[name="<?php echo $param ?>"]');
+          if (formInput.filter(':checkbox').length > 0)
+          {
+            params.<?php echo $param ?> = formInput.is(':checked') ? '1' : '';
+          }
+          else
+          {
+            params.<?php echo $param ?> = formInput.val();
+          }
+          <?php
+        }
+        // Array parameters (two types - checkboxes and the rest)
+        else
+        {
+          ?>
+          formInput = form.find('[name="<?php echo $param ?>"]');
+          params['<?php echo $param ?>'] = [];
+          if (formInput.filter(':checkbox').length > 0)
+          {
+            formInput.each(function(index) {
+                if ($(this).is(':checked'))
+                {
+                  params['<?php echo $param ?>'].push($(this).val());
+                }
+              });
+          }
+          else
+          {
+            formInput.each(function(index) {
+                params['<?php echo $param ?>'].push($(this).val());
+              });
+          }
+          <?php
+          // For some reason I don't understand, posting an empty array will
+          // give you a PHP array of ('') at the other end.    So to avoid
+          // that problem, delete the property if the array is empty
+          ?>
+          if (params['<?php echo $param ?>'].length == 0)
+          {
+            delete params['<?php echo $param ?>'];
+          }
+          <?php
+        }
+      }
+      ?>
+      $.post('edit_entry_handler.php', params, function(result) {
+          var conflictDiv = $('#conflict_check');
+          var checkMark = "\u2714";
+          var cross = "\u2718";
+          var titleText;
+          var patternSpan = /<span[\s\S]*span>/gi;
+          var patternTags = /<\S[^><]*>/g;
+          if (result.conflicts.length == 0)
+          {
+            conflictDiv.text(checkMark).attr('class', 'good').attr;
+            titleText = '<?php echo get_vocab("no_conflicts") ?>';
+          }
+          else
+          {
+            conflictDiv.text(cross).attr('class', 'bad');
+            titleText = '<?php echo get_vocab("conflict") ?>' + ":  \n\n";
+            for (var i=0; i<result.conflicts.length; i++)
+            {
+              titleText += '(' + (i+1).toString() + ') ';
+              <?php // strip out the <span> and its contents and then all 
other tags ?>
+              titleText += result.conflicts[i].replace(patternSpan, 
'').replace(patternTags, '') + "  \n";
+            }
+          }
+          conflictDiv.attr('title', titleText);
+          var policyDiv = $('#policy_check');
+          if (result.rules_broken.length == 0)
+          {
+            policyDiv.text(checkMark).attr('class', 'good');
+            titleText = '<?php echo get_vocab("no_rules_broken") ?>';
+          }
+          else
+          {
+            policyDiv.text(cross).attr('class', 'bad');
+            titleText = '<?php echo get_vocab("rules_broken") ?>' + ":  \n\n";
+            for (var i=0; i<result.rules_broken.length; i++)
+            {
+              titleText += '(' + (i+1).toString() + ') ' + 
result.rules_broken[i] + "  \n";
+            }
+          }
+          policyDiv.attr('title', titleText);
+        }, 'json');
+    }
+    <?php
+  }
+
   // Declare some variables to hold details of the slot selectors for each 
area.
   // We are going to store the contents of the selectors on page load
   // (when they will be fully populated with options) so that we can
@@ -961,6 +1088,9 @@
   // complete that field, but if it's an existing booking you might
   // want to edit any field)
   // (2) Adjust the slot selectors
+  // (3) Add some Ajax capabilities to the form (if we can) so that when
+  //  a booking parameter is changed MRBS checks to see whether there would
+  //  be any conflicts
   if ($page == 'edit_entry')
   {
   ?>
@@ -1050,6 +1180,43 @@
     OnAllDayClick(true);
     <?php
   }
+  
+  // Add Ajax capabilities (but only if we can return the result as a JSON 
object)
+  if (function_exists('json_encode'))
+  {
+    // Add a change event handler to each of the form fields so that when they 
change
+    // the validity of the booking is re-checked.   Use a click event for 
checkboxes
+    // as it seems that in some browsers the event fires before the value is 
changed.
+    
+    // Note that we also need to add change event handlers to the start and end
+    // datepicker input fields, but we have to that in datepicker_close()
+    ?>
+    var form = $('form#main');
+    var formInput;
+    <?php
+    foreach ($params as $param)
+    {
+      ?>
+      formInput = form.find('[name="<?php echo $param ?>"]');
+      if (formInput.filter(':checkbox').length > 0)
+      {
+        formInput.click(function() {
+            checkValidBooking();
+          });
+      }
+      else
+      {
+        formInput.change(function() {
+            checkValidBooking();
+          });
+      }  
+      <?php
+    }
+    ?>
+
+    checkValidBooking();
+    <?php
+  }
   ?>
 
   <?php

Modified: mrbs/branches/datatables/web/edit_entry.php
===================================================================
--- mrbs/branches/datatables/web/edit_entry.php 2011-09-08 18:00:20 UTC (rev 
1937)
+++ mrbs/branches/datatables/web/edit_entry.php 2011-09-08 18:05:32 UTC (rev 
1938)
@@ -1258,6 +1258,14 @@
     echo "<input class=\"submit\" type=\"submit\" name=\"save_button\" 
value=\"" .
       get_vocab("save") . "\" onclick=\"return validate('main')\">\n";
     echo "</div>\n";
+    
+    // divs to hold the results of the Ajax checking of the booking
+    echo "<div id=\"conflict_check\">\n";
+    echo "</div>\n";
+    
+    echo "<div id=\"policy_check\">\n";
+    echo "</div>\n";
+    
     echo "</fieldset>";
     ?>
   </fieldset>

Modified: mrbs/branches/datatables/web/edit_entry_handler.php
===================================================================
--- mrbs/branches/datatables/web/edit_entry_handler.php 2011-09-08 18:00:20 UTC 
(rev 1937)
+++ mrbs/branches/datatables/web/edit_entry_handler.php 2011-09-08 18:05:32 UTC 
(rev 1938)
@@ -18,6 +18,9 @@
 //    <input type="checkbox" name="foo[m]" value="1">
 
 
+// This page can be called with an Ajax call.  In this case it just checks
+// the validity of a proposed booking and does not make the booking.
+
 // Get non-standard form variables
 $formvars = array('create_by'         => 'string',
                   'name'              => 'string',
@@ -51,7 +54,8 @@
                   'end_day'           => 'int',
                   'end_month'         => 'int',
                   'end_year'          => 'int',
-                  'back_button'       => 'string');
+                  'back_button'       => 'string',
+                  'ajax'              => 'int');
                   
 foreach($formvars as $var => $var_type)
 {
@@ -223,7 +227,6 @@
   exit;
 }
 
-
 // Check that the user has permission to create/edit an entry for this room.
 // Get the id of the room that we are creating/editing
 if (isset($id))
@@ -258,55 +261,59 @@
   exit;
 }
 
-if ($name == '')
+// Form validation checks.   Normally checked for client side.
+// Don't bother with them if this is an Ajax request.
+if (!$ajax)
 {
-  print_header($day, $month, $year, $area, isset($room) ? $room : "");
-?>
-       <h1><?php echo get_vocab('invalid_booking'); ?></h1>
-       <p>
-         <?php echo get_vocab('must_set_description'); ?>
-       </p>
-<?php
-  // Print footer and exit
-  print_footer(TRUE);
-}       
+  if ($name == '')
+  {
+    print_header($day, $month, $year, $area, isset($room) ? $room : "");
+  ?>
+         <h1><?php echo get_vocab('invalid_booking'); ?></h1>
+         <p>
+           <?php echo get_vocab('must_set_description'); ?>
+         </p>
+  <?php
+    // Print footer and exit
+    print_footer(TRUE);
+  }       
 
 
-if (($rep_type == REP_N_WEEKLY) && ($rep_num_weeks < 2))
-{
-  print_header($day, $month, $year, $area, isset($room) ? $room : "");
-?>
-       <h1><?php echo get_vocab('invalid_booking'); ?></h1>
-       <p>
-         <?php echo get_vocab('you_have_not_entered')." 
".get_vocab("useful_n-weekly_value"); ?>
-       </p>
-<?php
-  // Print footer and exit
-  print_footer(TRUE);
-}
+  if (($rep_type == REP_N_WEEKLY) && ($rep_num_weeks < 2))
+  {
+    print_header($day, $month, $year, $area, isset($room) ? $room : "");
+  ?>
+         <h1><?php echo get_vocab('invalid_booking'); ?></h1>
+         <p>
+           <?php echo get_vocab('you_have_not_entered')." 
".get_vocab("useful_n-weekly_value"); ?>
+         </p>
+  <?php
+    // Print footer and exit
+    print_footer(TRUE);
+  }
 
-if (count($is_mandatory_field))
-{
-  foreach ($is_mandatory_field as $field => $value)
+  if (count($is_mandatory_field))
   {
-    $field = preg_replace('/^entry\./', '', $field);
-    if ($value && ($custom_fields[$field] == ''))
+    foreach ($is_mandatory_field as $field => $value)
     {
-      print_header($day, $month, $year, $area, isset($room) ? $room : "");
-?>
-       <h1><?php echo get_vocab('invalid_booking'); ?></h1>
-       <p>
-         <?php echo get_vocab('missing_mandatory_field')." \"".
-           get_loc_field_name($tbl_entry, $field)."\""; ?>
-       </p>
-<?php
-      // Print footer and exit
-      print_footer(TRUE);
+      $field = preg_replace('/^entry\./', '', $field);
+      if ($value && ($custom_fields[$field] == ''))
+      {
+        print_header($day, $month, $year, $area, isset($room) ? $room : "");
+  ?>
+         <h1><?php echo get_vocab('invalid_booking'); ?></h1>
+         <p>
+           <?php echo get_vocab('missing_mandatory_field')." \"".
+             get_loc_field_name($tbl_entry, $field)."\""; ?>
+         </p>
+  <?php
+        // Print footer and exit
+        print_footer(TRUE);
+      }
     }
-  }
-}        
+  }        
+}
 
-
 if ($enable_periods)
 {
   $resolution = 60;
@@ -465,7 +472,7 @@
 
 // Validate the booking for (a) conflicting bookings and (b) conformance to 
rules
 $valid_booking = TRUE;
-$conflicts = "";          // Holds a list of all the conflicts (ideally this 
would be an array)
+$conflicts = array();     // Holds a list of all the conflicts
 $rules_broken = array();  // Holds an array of the rules that have been broken
 $skip_lists = array();    // Holds a 2D array of bookings to skip past.  
Indexed
                           // by room id and start time
@@ -508,7 +515,7 @@
           // In both cases remember the conflict data.   (We don't at the
           // moment do anything with the data if we're skipping, but we might
           // in the future want to display a list of bookings we've skipped 
past)
-          $conflicts .= $tmp;
+          $conflicts = $conflicts + $tmp;  // array union
         }
         // if we're not an admin for this room, check that the booking
         // conforms to the booking policy
@@ -532,10 +539,11 @@
   else
   {
     $tmp = mrbsCheckFree($room_id, $starttime, $endtime-1, $ignore_id, 0);
+
     if (!empty($tmp))
       {
         $valid_booking = FALSE;
-        $conflicts .= $tmp;
+        $conflicts = $conflicts + $tmp;  // array union
       }
       // if we're not an admin for this room, check that the booking
       // conforms to the booking policy
@@ -552,6 +560,19 @@
 
 } // end foreach rooms
 
+
+// If this is an Ajax request then we're just trying to find out whether the 
booking
+// would succeed if made.   We now know that, so output the results and exit.
+if ($ajax && function_exists('json_encode'))
+{
+  $result = array();
+  $result['rules_broken'] = $rules_broken;
+  $result['conflicts'] = $conflicts;
+  echo json_encode($result);
+  exit;
+}
+
+
 // If the rooms were free, go ahead an process the bookings
 if ($valid_booking)
 {
@@ -758,7 +779,12 @@
     echo get_vocab("conflict").":\n";
     echo "</p>\n";
     echo "<ul>\n";
-    echo $conflicts;
+    // get rid of duplicate messages
+    $conflicts = array_unique($conflicts);
+    foreach ($conflicts as $conflict)
+    {
+      echo "<li>$conflict</li>\n";
+    }
     echo "</ul>\n";
   }
 }

Modified: mrbs/branches/datatables/web/lang.en
===================================================================
--- mrbs/branches/datatables/web/lang.en        2011-09-08 18:00:20 UTC (rev 
1937)
+++ mrbs/branches/datatables/web/lang.en        2011-09-08 18:05:32 UTC (rev 
1938)
@@ -146,7 +146,9 @@
 $vocab["error"]              = "Error";
 $vocab["sched_conflict"]     = "Scheduling Conflict";
 $vocab["conflict"]           = "The new booking will conflict with the 
following entries";
+$vocab["no_conflicts"]       = "No scheduling conflicts";
 $vocab["rules_broken"]       = "The new booking will conflict with the 
following policies";
+$vocab["no_rules_broken"]    = "No policy conflicts";
 $vocab["too_may_entrys"]     = "The selected options will create too many 
entries.<br>Please use different options!";
 $vocab["returncal"]          = "Return to calendar view";
 $vocab["failed_to_acquire"]  = "Failed to acquire exclusive database access";

Modified: mrbs/branches/datatables/web/mrbs.css.php
===================================================================
--- mrbs/branches/datatables/web/mrbs.css.php   2011-09-08 18:00:20 UTC (rev 
1937)
+++ mrbs/branches/datatables/web/mrbs.css.php   2011-09-08 18:05:32 UTC (rev 
1938)
@@ -609,7 +609,12 @@
 
 .form_general label.secondary {font-weight: normal; width: auto}
 
+div#conflict_check, div#policy_check {float: left; clear: none; width: auto; 
padding: 1em; cursor: default}
+div#conflict_check {margin-left: 2em}
+div.good {color: green}
+div.bad {color: red}
 
+
 /* ------------ EDIT_ENTRY_HANDLER.PHP ------------------*/
 .edit_entry_handler div#submit_buttons {float: left}
 .edit_entry_handler #submit_buttons form {float: left; margin: 1em 2em 1em 0}

Modified: mrbs/branches/datatables/web/mrbs_sql.inc
===================================================================
--- mrbs/branches/datatables/web/mrbs_sql.inc   2011-09-08 18:00:20 UTC (rev 
1937)
+++ mrbs/branches/datatables/web/mrbs_sql.inc   2011-09-08 18:05:32 UTC (rev 
1938)
@@ -13,7 +13,7 @@
  * 
  * Returns:
  *   nothing   - The area is free
- *   something - An error occured, the return value is human readable
+ *   something - An error occured, the return value is an array of conflicts
  */
 function mrbsCheckFree($room_id, $starttime, $endtime, $ignore, $repignore)
 {
@@ -54,8 +54,8 @@
   // Get the room's area ID for linking to day, week, and month views:
   $area = mrbsGetRoomArea($room_id);
 
-  // Build a string listing all the conflicts:
-  $err = "";
+  // Build an listing all the conflicts:
+  $err = array();
   for ($i = 0; ($row = sql_row_keyed($res, $i)); $i++)
   {
     $starts = getdate($row['start_time']);
@@ -80,11 +80,12 @@
        $row['name'] = get_vocab("private");
     }
 
-    $err .= "<li><a 
href=\"view_entry.php?id=".$row['id']."\">".$row['name']."</a>"
+    // enclose  the viewday etc. links in a span to make it easier for 
JavaScript to strip them out
+    $err[] = "<a 
href=\"view_entry.php?id=".$row['id']."\">".$row['name']."</a>"
       . " ( " . $startstr . ") "
-      . "(<a href=\"day.php?$param_ymd\">".get_vocab("viewday")."</a>"
+      . "<span>(<a href=\"day.php?$param_ymd\">".get_vocab("viewday")."</a>"
       . " | <a 
href=\"week.php?room=$room_id&amp;$param_ymd\">".get_vocab("viewweek")."</a>"
-      . " | <a 
href=\"month.php?room=$room_id&amp;$param_ym\">".get_vocab("viewmonth")."</a>)</li>\n";
+      . " | <a 
href=\"month.php?room=$room_id&amp;$param_ym\">".get_vocab("viewmonth")."</a>)</span>";
   }
 
   return $err;

Modified: mrbs/branches/datatables/web/report.php
===================================================================
--- mrbs/branches/datatables/web/report.php     2011-09-08 18:00:20 UTC (rev 
1937)
+++ mrbs/branches/datatables/web/report.php     2011-09-08 18:05:32 UTC (rev 
1938)
@@ -726,7 +726,8 @@
 
 // Also need to know whether they have admin rights
 $user = getUserName();
-$is_admin =  (isset($user) && authGetUserLevel($user)>=2) ;
+$user_level = authGetUserLevel($user);
+$is_admin =  ($user_level >= 2);
 
 // Set up for Ajax.   We need to know whether we're capable of dealing with 
Ajax
 // requests, which will only be if (a) the browser is using DataTables and (b)
@@ -1155,20 +1156,31 @@
         // Only show this part of the form if there are areas that allow 
private bookings
         if ($private_somewhere)
         {
-          echo "<div id=\"div_privacystatus\">\n";
-          echo "<label>" . get_vocab("privacy_status") . ":</label>\n";
-          echo "<div class=\"group\">\n";   
-          $options = array(PRIVATE_BOTH => 'both', PRIVATE_NO => 
'default_public', PRIVATE_YES => 'default_private');
-          foreach ($options as $option => $token)
+          // If they're not logged in then there's no point in showing this 
part of the form because
+          // they'll only be able to see public bookings anyway (and we don't 
want to alert them to
+          // the existence of porivate bookings)
+          if (empty($user_level))
           {
-            echo "<label>";
-            echo "<input class=\"radio\" type=\"radio\" name=\"match_private\" 
value=\"$option\"" .          
-                 (($match_private == $option) ? " checked=\"checked\"" : "") .
-                 ">" . get_vocab($token);
-            echo "</label>\n";
+            echo "<input type=\"hidden\" name=\"match_private\" value=\"" . 
PRIVATE_NO . "\">\n";
           }
-          echo "</div>\n";
-          echo "</div>\n";
+          // Otherwise give them the radio buttons
+          else
+          {
+            echo "<div id=\"div_privacystatus\">\n";
+            echo "<label>" . get_vocab("privacy_status") . ":</label>\n";
+            echo "<div class=\"group\">\n";   
+            $options = array(PRIVATE_BOTH => 'both', PRIVATE_NO => 
'default_public', PRIVATE_YES => 'default_private');
+            foreach ($options as $option => $token)
+            {
+              echo "<label>";
+              echo "<input class=\"radio\" type=\"radio\" 
name=\"match_private\" value=\"$option\"" .          
+                   (($match_private == $option) ? " checked=\"checked\"" : "") 
.
+                   ">" . get_vocab($token);
+              echo "</label>\n";
+            }
+            echo "</div>\n";
+            echo "</div>\n";
+          }
         }
         
         // Confirmation status


Property changes on: mrbs/branches/datatables/web/upgrade/5/pgsql.sql
___________________________________________________________________
Modified: svn:mergeinfo
   - /mrbs/branches/custom_entry_fields/web/upgrade/5/pgsql.sql:1374-1396
/mrbs/branches/datepicker/web/upgrade/5/pgsql.sql:1409-1416
/mrbs/branches/disabled_rooms/web/upgrade/5/pgsql.sql:1601-1634
/mrbs/branches/from_to_bookings/web/upgrade/5/pgsql.sql:1491-1587
/mrbs/branches/ics_attachments/web/upgrade/5/pgsql.sql:1652-1741
/mrbs/branches/only_unicode/web/upgrade/5/pgsql.sql:1747-1749
/mrbs/branches/provisional_bookings/web/upgrade/5/pgsql.sql:1242-1280
/mrbs/branches/provisional_bookings_new_style/web/upgrade/5/pgsql.sql:1407-1570
/mrbs/trunk/web/upgrade/5/pgsql.sql:1863-1933
   + /mrbs/branches/custom_entry_fields/web/upgrade/5/pgsql.sql:1374-1396
/mrbs/branches/datepicker/web/upgrade/5/pgsql.sql:1409-1416
/mrbs/branches/disabled_rooms/web/upgrade/5/pgsql.sql:1601-1634
/mrbs/branches/from_to_bookings/web/upgrade/5/pgsql.sql:1491-1587
/mrbs/branches/ics_attachments/web/upgrade/5/pgsql.sql:1652-1741
/mrbs/branches/only_unicode/web/upgrade/5/pgsql.sql:1747-1749
/mrbs/branches/provisional_bookings/web/upgrade/5/pgsql.sql:1242-1280
/mrbs/branches/provisional_bookings_new_style/web/upgrade/5/pgsql.sql:1407-1570
/mrbs/trunk/web/upgrade/5/pgsql.sql:1863-1937

This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.


------------------------------------------------------------------------------
Doing More with Less: The Next Generation Virtual Desktop 
What are the key obstacles that have prevented many mid-market businesses
from deploying virtual desktops?   How do next-generation virtual desktops
provide companies an easier-to-deploy, easier-to-manage and more affordable
virtual desktop model.http://www.accelacomm.com/jaw/sfnl/114/51426474/
_______________________________________________
Mrbs-commits mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mrbs-commits

Reply via email to