/**
  * Retrieve attributes attached to specified row_id in specified table
  * Returns a list (indexed array) of Attribute objects.
  *
  * @param $po_db Db Database connection object
  * @param $pn_table_num int The table number of the table to fetch attributes for
  * @param $pa_row_ids array List of row_ids to fetch attributes for
  * @param $pa_options array Optional array of options. Supported options include:
  *			resetCache = Clear cache before prefetch. [Default is false]
  *
  * @return boolean Always return true
  */
 public static function prefetchAttributes($po_db, $pn_table_num, $pa_row_ids, $pa_element_ids, $pa_options = null)
 {
     if (!sizeof($pa_row_ids)) {
         return true;
     }
     if (!is_array($pa_element_ids) || !sizeof($pa_element_ids)) {
         return true;
     }
     if (caGetOption('resetCache', $pa_options, false)) {
         ca_attributes::$s_get_attributes_cache = array();
     }
     // Make sure the element_id list looks like element_ids and does not have blanks
     $va_element_ids = array();
     foreach ($pa_element_ids as $vn_i => $vn_element_id) {
         if ($vn_element_id) {
             $va_element_ids[] = $vn_element_id;
         }
     }
     if (!is_array($va_element_ids) || !sizeof($va_element_ids)) {
         return true;
     }
     $qr_attrs = $po_db->query("\n\t\t\tSELECT \n\t\t\t\tcaa.attribute_id, caa.locale_id, caa.element_id element_set_id, caa.row_id,\n\t\t\t\tcaav.value_id, caav.item_id, caav.value_longtext1, caav.value_longtext2,\n\t\t\t\tcaav.value_decimal1, caav.value_decimal2, caav.value_integer1, caav.value_blob,\n\t\t\t\tcme.element_id, cme.datatype, cme.settings, cme.element_code\n\t\t\tFROM ca_attributes caa\n\t\t\tINNER JOIN ca_attribute_values AS caav ON caa.attribute_id = caav.attribute_id\n\t\t\tINNER JOIN ca_metadata_elements AS cme ON cme.element_id = caav.element_id\n\t\t\tWHERE\n\t\t\t\t(caa.table_num = ?) AND (caa.row_id IN (?)) AND (caa.element_id IN (?))\n\t\t\tORDER BY\n\t\t\t\tcaa.attribute_id\n\t\t", array((int) $pn_table_num, $pa_row_ids, $va_element_ids));
     if ($po_db->numErrors()) {
         return false;
     }
     $va_attrs = array();
     $vn_last_attribute_id = $vn_last_row_id = null;
     $vn_val_count = 0;
     $o_attr = $vn_last_element_id = null;
     while ($qr_attrs->nextRow()) {
         $va_raw_row = $qr_attrs->getRow();
         if ($vn_last_attribute_id != $va_raw_row['attribute_id']) {
             if ($vn_last_attribute_id && $vn_last_row_id) {
                 $va_attrs[$vn_last_row_id][$vn_last_element_id][] = $o_attr;
                 $vn_val_count = 0;
             }
             $vn_last_attribute_id = $va_raw_row['attribute_id'];
             $vn_last_row_id = $va_raw_row['row_id'];
             $vn_last_element_id = $va_raw_row['element_set_id'];
             // when creating the attribute you want element_id = to the "set" id (ie. the element_id in the ca_attributes row) so we overwrite
             // the element_id of the ca_attribute_values row before we pass the array to Attribute() below
             $o_attr = new Attribute(array_merge($va_raw_row, array('element_id' => $va_raw_row['element_set_id'])));
         }
         $o_attr->addValueFromRow($va_raw_row);
         $vn_val_count++;
     }
     if ($vn_val_count > 0) {
         $va_attrs[$vn_last_row_id][$vn_last_element_id][] = $o_attr;
     }
     $va_row_id_with_no_attributes = array_flip($pa_row_ids);
     foreach ($va_attrs as $vn_row_id => $va_attrs_by_element) {
         foreach ($va_attrs_by_element as $vn_element_id => $va_attrs_for_element) {
             unset($va_row_id_with_no_attributes[$vn_row_id]);
             ca_attributes::$s_get_attributes_cache[(int) $pn_table_num . '/' . (int) $vn_row_id][(int) $vn_element_id] = $va_attrs_for_element;
             // Limit cache size
             if (sizeof(ca_attributes::$s_get_attributes_cache) > ca_attributes::$s_attribute_cache_size) {
                 //array_shift(ca_attributes::$s_get_attributes_cache);
                 if (($vn_splice_length = ceil(sizeof(ca_attributes::$s_get_attributes_cache) - ca_attributes::$s_attribute_cache_size + ca_attributes::$s_attribute_cache_size * 0.5)) > ca_attributes::$s_attribute_cache_size) {
                     $vn_splice_length = ca_attributes::$s_attribute_cache_size;
                 }
                 array_splice(ca_attributes::$s_get_attributes_cache, 0, $vn_splice_length);
             }
         }
         foreach ($pa_element_ids as $vn_id) {
             if (!isset(ca_attributes::$s_get_attributes_cache[(int) $pn_table_num . '/' . (int) $vn_row_id][$vn_id])) {
                 ca_attributes::$s_get_attributes_cache[(int) $pn_table_num . '/' . (int) $vn_row_id][$vn_id] = false;
             }
         }
     }
     // Fill in cache entries for row_ids with no attributes as an empty array
     // to avoid repeated checks for values that don't exist
     foreach ($va_row_id_with_no_attributes as $vn_row_id => $vn_dummy) {
         foreach ($va_element_ids as $vn_element_id) {
             ca_attributes::$s_get_attributes_cache[(int) $pn_table_num . '/' . (int) $vn_row_id][(int) $vn_element_id] = array();
         }
         // Limit cache size
         if (sizeof(ca_attributes::$s_get_attributes_cache) > ca_attributes::$s_attribute_cache_size) {
             //array_shift(ca_attributes::$s_get_attributes_cache);
             if (($vn_splice_length = ceil(sizeof(ca_attributes::$s_get_attributes_cache) - ca_attributes::$s_attribute_cache_size + ca_attributes::$s_attribute_cache_size * 0.5)) > ca_attributes::$s_attribute_cache_size) {
                 $vn_splice_length = ca_attributes::$s_attribute_cache_size;
             }
             array_splice(ca_attributes::$s_get_attributes_cache, 0, $vn_splice_length);
         }
     }
     return true;
 }