Example #1
0
 public function dispatch()
 {
     $va_post = $this->getRequestBodyArray();
     // make sure only requests that are actually identical get pulled from cache
     $vs_cache_key = md5(serialize($va_post) . $this->opo_request->getFullUrlPath() . serialize($this->opo_request->getParameters(array('POST', 'GET', 'REQUEST'))) . $this->getRequestMethod());
     if (!$this->opo_request->getParameter('noCache', pInteger)) {
         if (ExternalCache::contains($vs_cache_key, 'SearchJSONService')) {
             return ExternalCache::fetch($vs_cache_key, 'SearchJSONService');
         }
     }
     switch ($this->getRequestMethod()) {
         case "GET":
         case "POST":
             if (sizeof($va_post) == 0) {
                 $vm_return = $this->search();
             } else {
                 if (is_array($va_post["bundles"])) {
                     $vm_return = $this->search($va_post["bundles"]);
                 } else {
                     $this->addError(_t("Invalid request body format"));
                     $vm_return = false;
                 }
             }
             break;
         default:
             $this->addError(_t("Invalid HTTP request method for this service"));
             $vm_return = false;
     }
     $vn_ttl = defined('__CA_SERVICE_API_CACHE_TTL__') ? __CA_SERVICE_API_CACHE_TTL__ : 60 * 60;
     // save for an hour by default
     ExternalCache::save($vs_cache_key, $vm_return, 'SearchJSONService', $vn_ttl);
     return $vm_return;
 }
 public function dispatch()
 {
     // make sure only requests that are actually identical get pulled from cache
     $vs_cache_key = md5(serialize($this->opo_request->getParameters(array('POST', 'GET', 'REQUEST'))) . $this->opo_request->getRawPostData() . $this->opo_request->getRequestMethod() . $this->opo_request->getFullUrlPath() . $this->opo_request->getScriptName() . ($this->opo_request->isLoggedIn() ? $this->opo_request->getUserID() : ''));
     if (ExternalCache::contains($vs_cache_key, 'BrowseService')) {
         return ExternalCache::fetch($vs_cache_key, 'BrowseService');
     }
     switch ($this->getRequestMethod()) {
         case "OPTIONS":
             $vm_return = $this->getFacetInfo();
             break;
         case "GET":
         case "POST":
             if (sizeof($this->getRequestBodyArray()) > 0) {
                 $vm_return = $this->getBrowseResults();
             } else {
                 $this->addError(_t("Getting results for browses without criteria is not supported"));
                 return false;
             }
             break;
         default:
             $this->addError(_t("Invalid HTTP request method"));
             return false;
     }
     $vn_ttl = defined('__CA_SERVICE_API_CACHE_TTL__') ? __CA_SERVICE_API_CACHE_TTL__ : 60 * 60;
     // save for an hour by default
     ExternalCache::save($vs_cache_key, $vm_return, 'BrowseService', $vn_ttl);
     return $vm_return;
 }
 /**
  *
  */
 public function renderWidget($ps_widget_id, &$pa_settings)
 {
     parent::renderWidget($ps_widget_id, $pa_settings);
     if ($pa_settings['feed_url']) {
         $vs_feed_url = $pa_settings['feed_url'];
     } else {
         $vs_feed_url = "http://icom.museum/rss.xml";
     }
     $vs_feed_url_md5 = md5($vs_feed_url);
     if (ExternalCache::contains($vs_feed_url_md5, 'Meow')) {
         $feed = ExternalCache::fetch($vs_feed_url_md5, 'Meow');
     } else {
         try {
             $feed = Zend_Feed::import($vs_feed_url);
         } catch (Exception $e) {
             return null;
         }
         ExternalCache::save($vs_feed_url_md5, $feed, 'Meow');
         $feed->__wakeup();
     }
     //A little style definition
     $rssViewContent = "" . "<STYLE type=\"text/css\">" . ".rssViewerWidgetContent, .rssViewerWidgetContent P, .rssViewerWidgetContent DIV, .rssViewerWidgetContent H1," . ".rssViewerWidgetContent H2" . " {margin:0px;padding:0px;margin-right:10px;padding-right:20px;}" . ".rssViewerWidgetContent H3 " . " {margin:0px;padding:0px;margin-right:10px;margin-top:10px;padding-right:20px;}" . "</STYLE>" . "<span class=\"rssViewerWidgetContent\">";
     //Initializing count to the limit : number_of_feeds
     $vn_c = 0;
     //Initializing description variable to store description with or without images
     $description = "";
     // Reading RSS feeds title, URL and description
     $this->opo_view->setVar('title', $feed->title());
     $this->opo_view->setVar('description', $feed->description());
     $this->opo_view->setVar('link', $feed->link());
     $rssViewContent .= "<h1><a href=\"" . $feed->link() . "\" target=\"_blank\">" . $feed->title() . "</a></h1>\n" . $feed->description();
     // Main loop : reading the items
     foreach ($feed as $item) {
         $vn_c++;
         if ($vn_c <= $pa_settings['number_of_feeds']) {
             // retrieve content
             $this->opo_view->setVar('item_title', $item->title());
             $this->opo_view->setVar('item_description', $item->description());
             $this->opo_view->setVar('item_link', $item->link());
             $description = $item->description();
             // when filtering images is on, remove IMG tags from description
             // when filtering HTML is on, remove all HTML tags
             if ($pa_settings['filter_html'] == 2) {
                 $description = strip_tags($description);
             } elseif ($pa_settings['filter_html'] == 1) {
                 $description = preg_replace("/<img[^>]+\\>/i", " ", $description);
             }
             // HTML generation of the content to display, 1 span surrounding each feed
             $rssViewContent .= "" . "<h3><a href=\"" . $item->link() . "\" target=\"blank\">" . $item->title() . "</a></h3>\n" . "" . $description . "\n";
         }
         $rssViewContent .= "</span>";
     }
     $this->opo_view->setVar('item_content', $rssViewContent);
     $this->opo_view->setVar('request', $this->getRequest());
     return $this->opo_view->render('main_html.php');
 }
Example #4
0
 /**
  * Fetches an entry from the cache.
  * @param string $ps_key
  * @param string $ps_namespace
  * @return mixed The cached data or FALSE, if no cache entry exists for the given key.
  */
 public static function fetch($ps_key, $ps_namespace = 'default')
 {
     if (MemoryCache::contains($ps_key, $ps_namespace)) {
         return MemoryCache::fetch($ps_key, $ps_namespace);
     }
     if (ExternalCache::contains($ps_key, $ps_namespace)) {
         //Debug::msg("[CompositeCache] got {$ps_namespace}:{$ps_key} from external cache");
         // copy data into 'L1' cache so that subsequent fetch() and contain() calls are fast
         $vm_data = ExternalCache::fetch($ps_key, $ps_namespace);
         MemoryCache::save($ps_key, $vm_data, $ps_namespace);
         return $vm_data;
     }
     return false;
 }
Example #5
0
 /**
  *
  */
 public function renderWidget($ps_widget_id, &$pa_settings)
 {
     parent::renderWidget($ps_widget_id, $pa_settings);
     $vs_feed_url = 'http://feeds.feedburner.com/ICanHasCheezburger';
     $vs_feed_url_md5 = md5($vs_feed_url);
     if (ExternalCache::contains($vs_feed_url_md5, 'Meow')) {
         $feed = ExternalCache::fetch($vs_feed_url_md5, 'Meow');
     } else {
         try {
             $feed = Zend_Feed::import($vs_feed_url);
         } catch (Exception $e) {
             return null;
         }
         ExternalCache::save($vs_feed_url_md5, $feed, 'Meow');
         $feed->__wakeup();
     }
     $this->opo_view->setVar('title', $feed->title());
     $vn_i = (int) rand(0, $feed->count() - 1);
     // pick a random cat
     $vn_c = 0;
     foreach ($feed as $item) {
         if ($vn_c < $vn_i) {
             $vn_c++;
             continue;
             // skip until we get to our random cat
         }
         $this->opo_view->setVar('item_title', $item->title());
         $this->opo_view->setVar('item_description', $item->description());
         $this->opo_view->setVar('item_link', $item->link());
         // Find the image URL in the encoded HTML content...
         if (preg_match("!(https://i.chzbgr.com/maxW500/[^\"']+)!i", $item->encoded(), $va_matches)) {
             $vs_url = $va_matches[1];
             $vn_width = 430;
             // force width of image to 430 pixels
             //$vn_height = floor($vn_width / 1.57);		// assume aspect ratio is 1.57 (typical). This results is an occasional squished cat but who's counting?
             $this->opo_view->setVar('item_image', "<img src='{$vs_url}' width='{$vn_width}'/>");
             break;
         }
         // if we fall through to here it means we couldn't find an image link in the encoded HTML content
         // so we just skip to the next one and see if there's a cat in there.
         $vn_c++;
     }
     $this->opo_view->setVar('request', $this->getRequest());
     return $this->opo_view->render('main_html.php');
 }
Example #6
0
 /**
  * Dispatch service call
  * @param string $ps_endpoint
  * @param RequestHTTP $po_request
  * @return array
  * @throws Exception
  */
 public static function dispatch($ps_endpoint, $po_request)
 {
     $vs_cache_key = $po_request->getHash();
     if (!$po_request->getParameter('noCache', pInteger)) {
         if (ExternalCache::contains($vs_cache_key, "SimpleAPI_{$ps_endpoint}")) {
             return ExternalCache::fetch($vs_cache_key, "SimpleAPI_{$ps_endpoint}");
         }
     }
     $va_endpoint_config = self::getEndpointConfig($ps_endpoint);
     // throws exception if it can't be found
     switch ($va_endpoint_config['type']) {
         case 'search':
             $vm_return = self::runSearchEndpoint($va_endpoint_config, $po_request);
             break;
         case 'detail':
         default:
             $vm_return = self::runDetailEndpoint($va_endpoint_config, $po_request);
             break;
     }
     $vn_ttl = defined('__CA_SERVICE_API_CACHE_TTL__') ? __CA_SERVICE_API_CACHE_TTL__ : 60 * 60;
     // save for an hour by default
     ExternalCache::save($vs_cache_key, $vm_return, "SimpleAPI_{$ps_endpoint}", $vn_ttl);
     return $vm_return;
 }
 /**
  * Fetches list of dependencies for a given table
  */
 public function getDependencies($ps_subject_table)
 {
     /* handle total cache miss (completely new cache has been generated) */
     if (ExternalCache::contains('ca_table_dependency_array')) {
         $va_cache_data = ExternalCache::fetch('ca_table_dependency_array');
     }
     /* cache outdated? (i.e. changes to search_indexing.conf) */
     $va_configfile_stat = stat($this->opo_search_config->get('search_indexing_config'));
     if ($va_configfile_stat['mtime'] != ExternalCache::fetch('ca_table_dependency_array_mtime')) {
         ExternalCache::save('ca_table_dependency_array_mtime', $va_configfile_stat['mtime']);
         $va_cache_data = array();
     }
     if (isset($va_cache_data[$ps_subject_table]) && is_array($va_cache_data[$ps_subject_table])) {
         /* cache hit */
         /* return data from cache */
         //Debug::msg("Got table dependency array for table {$ps_subject_table} from external cache");
         return $va_cache_data[$ps_subject_table];
     } else {
         /* cache miss */
         //Debug::msg("Cache miss for {$ps_subject_table}");
         /* build dependency graph, store it in cache and return it */
         $va_deps = $this->__getDependencies($ps_subject_table);
         $va_cache_data[$ps_subject_table] = $va_deps;
         ExternalCache::save('ca_table_dependency_array', $va_cache_data);
         return $va_deps;
     }
 }
Example #8
0
 /**
  * Restore session form a temporary service auth token
  * @param string $ps_token
  * @param string|null $ps_name
  * @return Session|bool The restored session, false on failure
  */
 public static function restoreFromServiceAuthToken($ps_token, $ps_name = null)
 {
     $o_config = Configuration::load();
     $vs_app_name = $o_config->get("app_name");
     if (!ExternalCache::contains($ps_token, 'ServiceAuthTokensToSessionID')) {
         return false;
     }
     $vs_session_id = ExternalCache::fetch($ps_token, 'ServiceAuthTokensToSessionID');
     $_COOKIE[$vs_app_name] = $vs_session_id;
     return new Session($vs_app_name);
 }
Example #9
0
 /**
  *
  */
 public function getGlobalParameter($ps_param)
 {
     if (ExternalCache::contains("browse_global_{$ps_param}", 'Browse')) {
         return ExternalCache::fetch("browse_global_{$ps_param}", 'Browse');
     }
     return false;
 }
Example #10
0
 /**
  *
  */
 private static function _loadData()
 {
     $vn_year = date("Y");
     $vn_day = date("j");
     // Does data exist in cache? Is it current?
     $va_data = ExternalCache::fetch('EuroBankData');
     if (is_array($va_data) && $va_data['year'] == $vn_year && $va_data['day'] == $vn_day) {
         return $va_data['rates'];
     }
     // Load data from source
     if ($vs_data = @file_get_contents(WLPlugCurrencyConversionEuroBank::CONVERSION_SERVICE_URL)) {
         if (!($o_data = new SimpleXMLElement($vs_data))) {
             throw new Exception(_t("Cannot parse data from %1", WLPlugCurrencyConversionEuroBank::CONVERSION_SERVICE_URL));
             return null;
         }
         $va_data = array('rates' => array(), 'year' => $vn_year, 'day' => $vn_day);
         foreach ($o_data->Cube->Cube->children() as $o_currency) {
             $o_attributes = $o_currency->attributes();
             $vs_currency = (string) $o_attributes->currency;
             $vn_rate = (string) $o_attributes->rate;
             $va_data['rates'][$vs_currency] = $vn_rate;
         }
         $va_data['rates']['EUR'] = 1.0;
         // add Euro to list
         ExternalCache::save('EuroBankData', $va_data);
         return $va_data['rates'];
     }
     throw new Exception(_t("Cannot fetch data from %1", WLPlugCurrencyConversionEuroBank::CONVERSION_SERVICE_URL));
     return null;
 }
Example #11
0
 /**
  * Load a configuration file. In addition to the parameters described below two global variables can also affect loading:
  *
  *		$g_ui_locale - if it contains the current locale code, this code will be used when computing the MD5 signature of the current configuration for caching purposes. By setting this to the current locale simultaneous caching of configurations for various locales (eg. config files with gettext-translated strings in them) is enabled.
  *		$g_configuration_cache_suffix - any text it contains is used along with the configuration path and $g_ui_locale to compute the MD5 signature of the current configuration for caching purposes. By setting this to some value you can support simultaneous caching of configurations for several different modes. This is mainly used to support caching of theme-specific configurations. Since the theme can change based upon user agent, we need to potentially keep several computed configurations cached at the same time, one for each theme used.
  *		
  * @param string $ps_file_path Absolute path to configuration file to parse
  * @param bool $pb_die_on_error If true, request processing will halt with call to die() on error in parsing config file
  * @param bool $pb_dont_cache If true, file will be parsed even if it's already cached
  *
  *
  */
 public function __construct($ps_file_path = __CA_APP_CONFIG__, $pb_die_on_error = false, $pb_dont_cache = false)
 {
     global $g_ui_locale, $g_configuration_cache_suffix;
     $this->ops_config_file_path = $ps_file_path ? $ps_file_path : __CA_APP_CONFIG__;
     # path to configuration file
     // cache key for on-disk caching
     $vs_path_as_md5 = md5($_SERVER['HTTP_HOST'] . $this->ops_config_file_path . '/' . $g_ui_locale . (isset($g_configuration_cache_suffix) ? '/' . $g_configuration_cache_suffix : ''));
     #
     # Is configuration file already cached?
     #
     $va_config_path_components = explode("/", $this->ops_config_file_path);
     $vs_config_filename = array_pop($va_config_path_components);
     $vs_local_conf_file_path = null;
     if (defined('__CA_LOCAL_CONFIG_DIRECTORY__') && file_exists(__CA_LOCAL_CONFIG_DIRECTORY__ . '/' . $vs_config_filename)) {
         $vs_local_conf_file_path = __CA_LOCAL_CONFIG_DIRECTORY__ . '/' . $vs_config_filename;
     } elseif (defined('__CA_DEFAULT_THEME_CONFIG_DIRECTORY__') && file_exists(__CA_DEFAULT_THEME_CONFIG_DIRECTORY__ . '/' . $vs_config_filename)) {
         $vs_local_conf_file_path = __CA_DEFAULT_THEME_CONFIG_DIRECTORY__ . '/' . $vs_config_filename;
     }
     if ((!defined('__CA_DISABLE_CONFIG_CACHING__') || !__CA_DISABLE_CONFIG_CACHING__) && !$pb_dont_cache) {
         // check that setup.php and global.conf haven't changed. If they have we're going to want to
         // regenerate all config file caches so they reflect inherited changes
         $va_global_config_stat = @stat(__CA_CONF_DIR__ . '/global.conf');
         $va_setup_stat = @stat(__CA_BASE_DIR__ . '/setup.php');
         if ($va_global_config_stat['mtime'] != ExternalCache::fetch('ca_global_config_mtime', 'Configuration') || $va_setup_stat['mtime'] != ExternalCache::fetch('ca_setup_mtime', 'Configuration')) {
             // either global.conf or setup.php have changed so clear the cache
             ExternalCache::flush();
             // save current times for global.conf and setup.php
             ExternalCache::save('ca_global_config_mtime', $va_global_config_stat['mtime'], 'Configuration', 0);
             ExternalCache::save('ca_setup_mtime', $va_setup_stat['mtime'], 'Configuration', 0);
         }
         $va_cache_data = ExternalCache::fetch($vs_path_as_md5, 'Configuration');
         #
         # is cache outdated? (changes to config file have invalidated it)
         #
         $vb_cache_is_invalid = false;
         $va_configfile_stat = @stat($this->ops_config_file_path);
         if ($va_configfile_stat['mtime'] != ExternalCache::fetch('ca_config_file_mtime_' . $vs_path_as_md5, 'Configuration')) {
             // config file has changed
             ExternalCache::save('ca_config_file_mtime_' . $vs_path_as_md5, $va_configfile_stat['mtime'], 'Configuration');
             $vb_cache_is_invalid = true;
         }
         if ($vs_local_conf_file_path) {
             $va_local_configfile_stat = @stat($vs_local_conf_file_path);
             if ($va_local_configfile_stat['mtime'] != ExternalCache::fetch('ca_config_file_local_mtime_' . $vs_path_as_md5, 'Configuration')) {
                 // local config file has changed
                 ExternalCache::save('ca_config_file_local_mtime_' . $vs_path_as_md5, $va_local_configfile_stat['mtime'], 'Configuration');
                 $vb_cache_is_invalid = true;
             }
         }
         if (!$vb_cache_is_invalid) {
             // Get it out of the cache because cache is ok
             $this->ops_config_settings = $va_cache_data;
             $this->ops_md5_path = md5($this->ops_config_file_path);
             return;
         }
     }
     # load hash
     $this->ops_config_settings = array();
     # try loading global.conf file
     $vs_global_path = join("/", $va_config_path_components) . '/global.conf';
     if (file_exists($vs_global_path)) {
         $this->loadFile($vs_global_path, false);
     }
     //
     // Insert current user locale as constant into configuration.
     //
     $this->ops_config_settings['scalars']['LOCALE'] = $g_ui_locale;
     #
     # load specified config file
     #
     if (file_exists($this->ops_config_file_path) && $this->loadFile($this->ops_config_file_path, false)) {
         $this->ops_config_settings["ops_config_file_path"] = $this->ops_config_file_path;
     }
     #
     # try to load optional "local" config file (extra, optional, config file that can override values in the specified config file with "local" values)
     #
     if ($vs_local_conf_file_path) {
         $this->loadFile($vs_local_conf_file_path, false, false);
     }
     if ($vs_path_as_md5 && !$pb_dont_cache) {
         ExternalCache::save($vs_path_as_md5, $this->ops_config_settings, 'Configuration');
     }
 }
Example #12
0
/**
 * @param $ps_type
 * @param $ps_template
 * @param null $pa_options
 * @return array|bool|false|mixed
 */
function caGetPrintTemplateDetails($ps_type, $ps_template, $pa_options = null)
{
    $vs_template_path = caGetPrintTemplateDirectoryPath($ps_type);
    if (file_exists("{$vs_template_path}/local/{$ps_template}.php")) {
        $vs_template_path = "{$vs_template_path}/local/{$ps_template}.php";
    } elseif (file_exists("{$vs_template_path}/{$ps_template}.php")) {
        $vs_template_path = "{$vs_template_path}/{$ps_template}.php";
    } else {
        return false;
    }
    $vs_cache_key = caMakeCacheKeyFromOptions($pa_options, $ps_type . '/' . $vs_template_path);
    if (ExternalCache::contains($vs_cache_key, 'PrintTemplateDetails')) {
        $va_list = ExternalCache::fetch($vs_cache_key, 'PrintTemplateDetails');
        if (ExternalCache::fetch("{$vs_cache_key}_mtime", 'PrintTemplateDetails') >= filemtime($vs_template_path)) {
            //Debug::msg('[caGetPrintTemplateDetails] cache hit');
            return $va_list;
        }
    }
    //Debug::msg('[caGetPrintTemplateDetails] cache miss');
    $vs_template = file_get_contents($vs_template_path);
    $va_info = array();
    foreach (array("@name", "@type", "@pageSize", "@pageOrientation", "@tables", "@marginLeft", "@marginRight", "@marginTop", "@marginBottom", "@horizontalGutter", "@verticalGutter", "@labelWidth", "@labelHeight", "@elementCode") as $vs_tag) {
        if (preg_match("!{$vs_tag}([^\n\n]+)!", $vs_template, $va_matches)) {
            $va_info[str_replace("@", "", $vs_tag)] = trim($va_matches[1]);
        } else {
            $va_info[str_replace("@", "", $vs_tag)] = null;
        }
    }
    $va_info['tables'] = preg_split("![,;]{1}!", $va_info['tables']);
    $va_info['path'] = $vs_template_path;
    ExternalCache::save($vs_cache_key, $va_info, 'PrintTemplateDetails');
    ExternalCache::save("{$vs_cache_key}_mtime", filemtime($vs_template_path), 'PrintTemplateDetails');
    return $va_info;
}
Example #13
0
 /**
  * Return names of all session vars
  */
 public function getVarKeys()
 {
     if (ExternalCache::contains($this->getSessionID(), 'SessionVars')) {
         $va_vars = ExternalCache::fetch($this->getSessionID(), 'SessionVars');
         return array_keys($va_vars);
     }
     return array();
 }
Example #14
0
 /**
  * Load configuration from external cache into memory
  */
 public static function loadConfigCacheInMemory()
 {
     if (!is_null(self::$s_config_cache)) {
         return;
     }
     if (ExternalCache::contains('ConfigurationCache')) {
         self::$s_config_cache = ExternalCache::fetch('ConfigurationCache');
     }
 }
 public function testLongerTTL()
 {
     ExternalCache::save('foo', array(), 'barNamespace', 2);
     $vm_ret = ExternalCache::contains('foo', 'barNamespace');
     $this->assertTrue($vm_ret, 'The key we just set should exist');
     $vm_ret = ExternalCache::fetch('foo', 'barNamespace');
     $this->assertEquals(array(), $vm_ret, 'The value we set should be returned');
     sleep(1);
     $vm_ret = ExternalCache::contains('foo', 'barNamespace');
     $this->assertTrue($vm_ret, 'The key should still be there');
     sleep(1);
     $vm_ret = ExternalCache::contains('foo', 'barNamespace');
     $this->assertFalse($vm_ret, 'The key should have expired by now');
 }
Example #16
0
 /**
  * Returns the next incomplete list response based on the given resumption
  * token.
  *
  * @param string $token Resumption token
  * @uses listResponse()
  */
 private function resumeListResponse($oaiData, $token)
 {
     $va_token_info = ExternalCache::fetch($token, 'OAIPMHService');
     if (!$va_token_info || $va_token_info['verb'] != $this->opo_request->getParameter('verb', pString)) {
         $this->throwError(self::OAI_ERR_BAD_RESUMPTION_TOKEN);
     } else {
         $this->listResponse($oaiData, $va_token_info['verb'], $va_token_info['metadata_prefix'], $va_token_info['cursor'], $va_token_info['set'], $va_token_info['from'], $va_token_info['until']);
     }
 }
Example #17
0
 /**
  *
  */
 public function __construct($pb_dont_cache = false)
 {
     // is there an on-disk cache of the internal graph?
     if (!$pb_dont_cache && ExternalCache::contains('ca_datamodel_graph')) {
         if ($va_graph = ExternalCache::fetch('ca_datamodel_graph')) {
             $this->opo_graph = new Graph($va_graph);
             return;
         }
     }
     $o_config = Configuration::load();
     if ($vs_data_model_path = $o_config->get("data_model")) {
         $o_datamodel = Configuration::load($vs_data_model_path);
         $this->opo_graph = new Graph();
         # add tables
         if (!($va_tables = $o_datamodel->getAssoc("tables"))) {
             $va_tables = array();
         }
         foreach ($va_tables as $vs_table => $vn_num) {
             $this->opo_graph->addNode($vs_table);
             $this->opo_graph->addAttribute("num", $vn_num, $vs_table);
             $this->opo_graph->addNode("t#" . $vn_num);
             $this->opo_graph->addAttribute("name", $vs_table, "t#" . $vn_num);
         }
         # add relationships
         if (!($va_relationships = $o_datamodel->getList("relationships"))) {
             $va_relationships = array();
         }
         foreach ($va_relationships as $vs_relationship) {
             $va_keys = preg_split("/[\t ]*=[\t ]*/", $vs_relationship);
             $vn_num_keys = sizeof($va_keys);
             switch ($vn_num_keys) {
                 case 2:
                     $vs_key1 = $va_keys[0];
                     $va_tmp = preg_split('/[ ]+/', $va_keys[1]);
                     $vs_key2 = $va_tmp[0];
                     list($vs_table1, $vs_field1) = explode(".", $vs_key1);
                     list($vs_table2, $vs_field2) = explode(".", $vs_key2);
                     $vn_weight = isset($va_tmp[1]) && intval($va_tmp[1]) > 0 ? intval($va_tmp[1]) : 10;
                     break;
                 default:
                     die("Fatal error: syntax error in datamodel relationship specification: '{$vs_relationship}'\n");
                     break;
             }
             if (!$this->opo_graph->hasNode($vs_table1)) {
                 die("Fatal error: invalid table '{$vs_table1}' in relationship in datamodel definition\n");
             }
             if (!$this->opo_graph->hasNode($vs_table2)) {
                 die("Fatal error: invalid table '{$vs_table2}' in relationship in datamodel definition\n");
             }
             if (!($va_attr = $this->opo_graph->getAttributes($vs_table1, $vs_table2))) {
                 $va_attr = array();
                 $this->opo_graph->addRelationship($vs_table1, $vs_table2);
             }
             $va_attr["relationships"][$vs_table1][$vs_table2][] = array($vs_field1, $vs_field2);
             $va_attr["relationships"][$vs_table2][$vs_table1][] = array($vs_field2, $vs_field1);
             $va_attr['WEIGHT'] = $vn_weight;
             $this->opo_graph->setAttributes($va_attr, $vs_table1, $vs_table2);
         }
         $va_graph_data = $this->opo_graph->getInternalData();
         ExternalCache::save('ca_datamodel_graph', $va_graph_data);
     }
 }