| 1 |
<?php
|
| 2 |
// $Id: update_status_aggregator.module,v 1.13 2008/03/25 19:13:09 yrocq Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* Lets users check a remote site if some module are not up-to-date.
|
| 7 |
*
|
| 8 |
* Adds a text field when a node is displayed
|
| 9 |
* so that authenticated users may make notes.
|
| 10 |
|
| 11 |
TODO:
|
| 12 |
* add filter question related to the view
|
| 13 |
* export view definition to be included with the release
|
| 14 |
* make views of list of sites that use any particular module
|
| 15 |
* make it so that it runs cron on the remote site when config page is saved.
|
| 16 |
* Add permission
|
| 17 |
*/
|
| 18 |
|
| 19 |
/**
|
| 20 |
* Drupal help Hook
|
| 21 |
*/
|
| 22 |
function update_status_aggregator_help($path, $arg=array()) {
|
| 23 |
switch ($path) {
|
| 24 |
case 'admin/help/update_status_aggregator':
|
| 25 |
return '<p>'. t('Update status aggregator is two modules: a
|
| 26 |
"client" and a "server". The client, update notifier, to be enabled on all sites that are to report to the central tracker, sends a list of installed modules and their status to the tracking site via xmlrpc when cron is run. This information is taken from the update status module. The server component, enabled on the tracking site, receives these lists from all the clients and creates nodes for each module. These nodes will then get updated everytime the cron is run on the tracked sites. For the tracked sites to have the right to create these nodes, the keys of the tracked sites need to be entered into the tracker site.') .'</p>';
|
| 27 |
}
|
| 28 |
}
|
| 29 |
|
| 30 |
/**
|
| 31 |
* Drupal perm Hook
|
| 32 |
*/
|
| 33 |
function update_status_aggregator_perm() {
|
| 34 |
return array(
|
| 35 |
'create update status aggregator module',
|
| 36 |
'edit own update status aggregator module',
|
| 37 |
'create update status aggregator site',
|
| 38 |
'edit own update status aggregator site',
|
| 39 |
);
|
| 40 |
}
|
| 41 |
|
| 42 |
/**
|
| 43 |
* Drupal access Hook
|
| 44 |
*/
|
| 45 |
function update_status_aggregator_access($op, $node) {
|
| 46 |
global $user;
|
| 47 |
|
| 48 |
if ($node->type == "update_status_aggregator_module") {
|
| 49 |
if ($op == 'create') {
|
| 50 |
return user_access('create update status aggregator module');
|
| 51 |
}
|
| 52 |
|
| 53 |
if ($op == 'update' || $op == 'delete') {
|
| 54 |
if (user_access('edit own update status aggregator module') && ($user->uid == $node->uid)) {
|
| 55 |
return TRUE;
|
| 56 |
}
|
| 57 |
}
|
| 58 |
}
|
| 59 |
elseif ($node->type == "update_status_aggregator_site") {
|
| 60 |
if ($op == 'create') {
|
| 61 |
return user_access('create update status aggregator site');
|
| 62 |
}
|
| 63 |
|
| 64 |
if ($op == 'update' || $op == 'delete') {
|
| 65 |
if (user_access('edit own update status aggregator site') && ($user->uid == $node->uid)) {
|
| 66 |
return TRUE;
|
| 67 |
}
|
| 68 |
}
|
| 69 |
}
|
| 70 |
}
|
| 71 |
|
| 72 |
/**
|
| 73 |
* Implementation of hook_views_tables().
|
| 74 |
*/
|
| 75 |
function update_status_aggregator_views_tables() {
|
| 76 |
$tables['update_status_aggregator'] = array(
|
| 77 |
'name' => 'update_status_aggregator',
|
| 78 |
'provider' => 'internal', // won't show up in external list.
|
| 79 |
'join' => array(
|
| 80 |
'type' => 'inner',
|
| 81 |
'left' => array(
|
| 82 |
'table' => 'node',
|
| 83 |
'field' => 'vid'
|
| 84 |
),
|
| 85 |
'right' => array(
|
| 86 |
'field' => 'vid'
|
| 87 |
),
|
| 88 |
),
|
| 89 |
"fields" => array(
|
| 90 |
"module" => array(
|
| 91 |
'name' => t('Update status aggregator Module'),
|
| 92 |
'help' => t('Update status aggregator Module'),
|
| 93 |
'sortable' => TRUE,
|
| 94 |
),
|
| 95 |
"site" => array(
|
| 96 |
'name' => t('Update status aggregator site'),
|
| 97 |
'help' => t('Update status aggregator site'),
|
| 98 |
'handler' => 'views_handler_site',
|
| 99 |
'sortable' => TRUE,
|
| 100 |
),
|
| 101 |
"status" => array(
|
| 102 |
'name' => t('Update status aggregator status'),
|
| 103 |
'help' => t('Update status aggregator status'),
|
| 104 |
'handler' => 'views_handler_status',
|
| 105 |
'sortable' => TRUE,
|
| 106 |
),
|
| 107 |
),
|
| 108 |
'filters' => array(
|
| 109 |
'status' => array(
|
| 110 |
'name' => 'update_status_aggregator: Status',
|
| 111 |
'help' => 'Status of the module',
|
| 112 |
'operator' => views_handler_operator_eqneq(),
|
| 113 |
'value' => array(
|
| 114 |
'#type' => 'select',
|
| 115 |
'#options' => 'Update_status_aggregator_status_list',
|
| 116 |
'#multiple' => TRUE
|
| 117 |
),
|
| 118 |
'cacheable' => FALSE
|
| 119 |
),
|
| 120 |
'site' => array(
|
| 121 |
'name' => 'Update_status_aggregator: Site',
|
| 122 |
'help' => 'Site of the module',
|
| 123 |
'operator' => views_handler_operator_eqneq(),
|
| 124 |
'value' => array(
|
| 125 |
'#type' => 'select',
|
| 126 |
'#options' => 'update_status_aggregator_sites_list',
|
| 127 |
'#multiple' => TRUE
|
| 128 |
),
|
| 129 |
'cacheable' => FALSE
|
| 130 |
),
|
| 131 |
'module' => array(
|
| 132 |
'name' => 'Update_status_aggregator: Module',
|
| 133 |
'help' => 'Module',
|
| 134 |
'operator' => views_handler_operator_eqneq(),
|
| 135 |
'value' => array(
|
| 136 |
'#type' => 'select',
|
| 137 |
'#options' => 'update_status_aggregator_modules_list',
|
| 138 |
'#multiple' => TRUE
|
| 139 |
),
|
| 140 |
'cacheable' => FALSE
|
| 141 |
)
|
| 142 |
)
|
| 143 |
);
|
| 144 |
return $tables;
|
| 145 |
}
|
| 146 |
|
| 147 |
function update_status_aggregator_views_default_views() {
|
| 148 |
$view = new stdClass();
|
| 149 |
$view->name = 'update_status_aggregator';
|
| 150 |
$view->description = 'Example of view for update status aggregator';
|
| 151 |
$view->access = array(
|
| 152 |
0 => '2',
|
| 153 |
);
|
| 154 |
$view->view_args_php = '';
|
| 155 |
$view->page = TRUE;
|
| 156 |
$view->page_title = 'Module updates';
|
| 157 |
$view->page_header = '';
|
| 158 |
$view->page_header_format = '1';
|
| 159 |
$view->page_footer = '';
|
| 160 |
$view->page_footer_format = '1';
|
| 161 |
$view->page_empty = '';
|
| 162 |
$view->page_empty_format = '1';
|
| 163 |
$view->page_type = 'table';
|
| 164 |
$view->url = 'module_updates';
|
| 165 |
$view->use_pager = TRUE;
|
| 166 |
$view->nodes_per_page = '10';
|
| 167 |
$view->sort = array(
|
| 168 |
);
|
| 169 |
$view->argument = array(
|
| 170 |
);
|
| 171 |
$view->field = array(
|
| 172 |
array(
|
| 173 |
'tablename' => 'update_status_aggregator',
|
| 174 |
'field' => 'site',
|
| 175 |
'label' => '',
|
| 176 |
),
|
| 177 |
array(
|
| 178 |
'tablename' => 'update_status_aggregator',
|
| 179 |
'field' => 'module',
|
| 180 |
'label' => '',
|
| 181 |
),
|
| 182 |
array(
|
| 183 |
'tablename' => 'update_status_aggregator',
|
| 184 |
'field' => 'status',
|
| 185 |
'label' => '',
|
| 186 |
),
|
| 187 |
);
|
| 188 |
$view->filter = array(
|
| 189 |
array(
|
| 190 |
'tablename' => 'update_status_aggregator',
|
| 191 |
'field' => 'status',
|
| 192 |
'operator' => '=',
|
| 193 |
'options' => '',
|
| 194 |
'value' => '4',
|
| 195 |
),
|
| 196 |
array(
|
| 197 |
'tablename' => 'update_status_aggregator',
|
| 198 |
'field' => 'site',
|
| 199 |
'operator' => '=',
|
| 200 |
'options' => '',
|
| 201 |
'value' => '9',
|
| 202 |
),
|
| 203 |
array(
|
| 204 |
'tablename' => 'update_status_aggregator',
|
| 205 |
'field' => 'module',
|
| 206 |
'operator' => '=',
|
| 207 |
'options' => '',
|
| 208 |
'value' => '',
|
| 209 |
),
|
| 210 |
);
|
| 211 |
$view->exposed_filter = array(
|
| 212 |
array(
|
| 213 |
'tablename' => 'update_status_aggregator',
|
| 214 |
'field' => 'status',
|
| 215 |
'label' => 'Status',
|
| 216 |
'optional' => '1',
|
| 217 |
'is_default' => '0',
|
| 218 |
'operator' => '0',
|
| 219 |
'single' => '0',
|
| 220 |
),
|
| 221 |
array(
|
| 222 |
'tablename' => 'update_status_aggregator',
|
| 223 |
'field' => 'site',
|
| 224 |
'label' => 'Site',
|
| 225 |
'optional' => '1',
|
| 226 |
'is_default' => '0',
|
| 227 |
'operator' => '0',
|
| 228 |
'single' => '0',
|
| 229 |
),
|
| 230 |
array(
|
| 231 |
'tablename' => 'update_status_aggregator',
|
| 232 |
'field' => 'module',
|
| 233 |
'label' => 'Module',
|
| 234 |
'optional' => '1',
|
| 235 |
'is_default' => '0',
|
| 236 |
'operator' => '0',
|
| 237 |
'single' => '1',
|
| 238 |
),
|
| 239 |
);
|
| 240 |
$view->requires = array(update_status_aggregator);
|
| 241 |
$views[$view->name] = $view;
|
| 242 |
|
| 243 |
$view->disabled = TRUE;
|
| 244 |
|
| 245 |
return $views;
|
| 246 |
}
|
| 247 |
|
| 248 |
/**
|
| 249 |
* Implementation of hook_xmlrpc().
|
| 250 |
*
|
| 251 |
* Maps external names of XML-RPC methods to callback functions.
|
| 252 |
*/
|
| 253 |
function update_status_aggregator_xmlrpc() {
|
| 254 |
return array(
|
| 255 |
array(
|
| 256 |
'update_status_aggregator.notify', // External method name.
|
| 257 |
'update_status_aggregator_xml_notify', // Drupal function to run.
|
| 258 |
array('string', 'struct'),
|
| 259 |
t('Returns the list of module to update.') // Description.
|
| 260 |
)
|
| 261 |
);
|
| 262 |
}
|
| 263 |
|
| 264 |
|
| 265 |
/**
|
| 266 |
* Implementation of hook_node_info().
|
| 267 |
*/
|
| 268 |
function update_status_aggregator_node_info() {
|
| 269 |
return array(
|
| 270 |
'update_status_aggregator_module' => array(
|
| 271 |
'name' => t('Update status aggregator module'),
|
| 272 |
'module' => 'update_status_aggregator',
|
| 273 |
'description' => t('Tracker/Server module to receive update notifications from tracked sites.'),
|
| 274 |
'has_title' => TRUE,
|
| 275 |
'title_label' => t('Name of the module'),
|
| 276 |
'has_body' => FALSE,
|
| 277 |
),
|
| 278 |
'update_status_aggregator_site' => array(
|
| 279 |
'name' => t('Update status aggregator site'),
|
| 280 |
'module' => 'update_status_aggregator',
|
| 281 |
'description' => t("Notification module that will enable reporting of modules' status to site with update_status_aggregator module enabled."),
|
| 282 |
'has_title' => TRUE,
|
| 283 |
'title_label' => t('Remote site name'),
|
| 284 |
'has_body' => FALSE,
|
| 285 |
),
|
| 286 |
);
|
| 287 |
}
|
| 288 |
|
| 289 |
|
| 290 |
/**
|
| 291 |
* Implementation of hook_load().
|
| 292 |
*/
|
| 293 |
|
| 294 |
function update_status_aggregator_load($node) {
|
| 295 |
if ($node->type == "update_status_aggregator_module") {
|
| 296 |
return db_fetch_object(db_query('SELECT module, usa.status, n.title as site FROM {update_status_aggregator} usa INNER JOIN {node} n ON n.nid = usa.site WHERE usa.vid = %d', $node->vid));
|
| 297 |
}
|
| 298 |
elseif ($node->type == "update_status_aggregator_site") {
|
| 299 |
return db_fetch_object(db_query('SELECT drupal_key FROM {update_status_aggregator_keys} WHERE vid = %d', $node->vid));
|
| 300 |
}
|
| 301 |
}
|
| 302 |
|
| 303 |
|
| 304 |
/**
|
| 305 |
* Implementation of hook_view().
|
| 306 |
*
|
| 307 |
* Display the content in a very simple way.
|
| 308 |
*
|
| 309 |
*/
|
| 310 |
function update_status_aggregator_view($node, $teaser = FALSE, $page = FALSE) {
|
| 311 |
if (!$teaser) {
|
| 312 |
if ($node->type == "update_status_aggregator_module") {
|
| 313 |
$node = node_prepare($node, $teaser);
|
| 314 |
$node->content['short_name'] = array(
|
| 315 |
'#value' => t("Module") .": ". $node->module ."<br/>",
|
| 316 |
'#weight' => 2
|
| 317 |
);
|
| 318 |
$node->content['site'] = array(
|
| 319 |
'#value' => t("Site") .": ". $node->site ."<br/>",
|
| 320 |
'#weight' => 2
|
| 321 |
);
|
| 322 |
$status = status_int_to_string($node->status);
|
| 323 |
$node->content['status'] = array(
|
| 324 |
'#value' => t("Status") .": ". $status ."<br/>",
|
| 325 |
'#weight' => 2
|
| 326 |
);
|
| 327 |
}
|
| 328 |
elseif ($node->type == "update_status_aggregator_site") {
|
| 329 |
$node = node_prepare($node, $teaser);
|
| 330 |
$node->content['drupal_key'] = array(
|
| 331 |
'#value' => t("key") .": ". $node->drupal_key ."<br/>",
|
| 332 |
'#weight' => 0
|
| 333 |
);
|
| 334 |
}
|
| 335 |
}
|
| 336 |
if ($teaser) {
|
| 337 |
$node = node_prepare($node, $teaser);
|
| 338 |
}
|
| 339 |
return $node;
|
| 340 |
}
|
| 341 |
|
| 342 |
|
| 343 |
/**
|
| 344 |
* Implementation of hook_insert().
|
| 345 |
*/
|
| 346 |
function update_status_aggregator_insert($node) {
|
| 347 |
if ($node->type == "update_status_aggregator_module") {
|
| 348 |
db_query("INSERT INTO {update_status_aggregator} (nid, vid, module, site, status) VALUES (%d, %d, '%s', '%s', %d)",
|
| 349 |
$node->nid, $node->vid, $node->short_name, $node->site, $node->status);
|
| 350 |
}
|
| 351 |
elseif ($node->type == "update_status_aggregator_site") {
|
| 352 |
db_query("INSERT INTO {update_status_aggregator_keys} (nid, vid, drupal_key) VALUES (%d, %d, '%s')",
|
| 353 |
$node->nid, $node->vid, $node->drupal_key);
|
| 354 |
}
|
| 355 |
}
|
| 356 |
|
| 357 |
|
| 358 |
/**
|
| 359 |
* Implementation of hook_update().
|
| 360 |
*/
|
| 361 |
function update_status_aggregator_update($node) {
|
| 362 |
if ($node->revision) {
|
| 363 |
update_status_aggregator_insert($node);
|
| 364 |
}
|
| 365 |
else {
|
| 366 |
if ($node->type == "update_status_aggregator_module") {
|
| 367 |
db_query("UPDATE {update_status_aggregator} SET module = '%s', site = '%s', status = %d WHERE vid = %d",
|
| 368 |
$node->short_name, $node->site, $node->status, $node->vid);
|
| 369 |
}
|
| 370 |
elseif ($node->type == "update_status_aggregator_site") {
|
| 371 |
db_query("UPDATE {update_status_aggregator_keys} SET drupal_key = '%s' WHERE vid = %d", $node->drupal_key, $node->vid);
|
| 372 |
}
|
| 373 |
}
|
| 374 |
}
|
| 375 |
|
| 376 |
|
| 377 |
/**
|
| 378 |
* Implementation of hook_delete().
|
| 379 |
*/
|
| 380 |
function update_status_aggregator_delete(&$node) {
|
| 381 |
if ($node->type == "update_status_aggregator_module") {
|
| 382 |
db_query('DELETE FROM {update_status_aggregator} WHERE nid = %d', $node->nid);
|
| 383 |
}
|
| 384 |
elseif ($node->type == "update_status_aggregator_site") {
|
| 385 |
db_query('DELETE FROM {update_status_aggregator_keys} WHERE nid = %d', $node->nid);
|
| 386 |
db_query("DELETE usa, n FROM {update_status_aggregator} usa, {node} n WHERE usa.site=%d AND n.type='update_status_aggregator_module' AND usa.vid=n.vid", $node->nid);
|
| 387 |
}
|
| 388 |
}
|
| 389 |
|
| 390 |
|
| 391 |
/**
|
| 392 |
* Implementation of hook_form().
|
| 393 |
*/
|
| 394 |
function update_status_aggregator_form($node) {
|
| 395 |
$type = node_get_types('type', $node);
|
| 396 |
if ($node->type == "update_status_aggregator_module") {
|
| 397 |
$form['title'] = array(
|
| 398 |
'#type' => 'textfield',
|
| 399 |
'#title' => check_plain($type->title_label),
|
| 400 |
'#required' => TRUE,
|
| 401 |
'#default_value' => $node->title,
|
| 402 |
'#weight' => -5
|
| 403 |
);
|
| 404 |
$form['short_name'] = array(
|
| 405 |
'#type' => 'textfield',
|
| 406 |
'#title' => t('Module'),
|
| 407 |
'#size' => 60,
|
| 408 |
'#maxlength' => 128,
|
| 409 |
'#default_value' => $node->module,
|
| 410 |
'#required' => TRUE,
|
| 411 |
);
|
| 412 |
$form['site'] = array(
|
| 413 |
'#type' => 'textfield',
|
| 414 |
'#title' => t("Remote Drupal website's name"),
|
| 415 |
'#size' => 60,
|
| 416 |
'#maxlength' => 128,
|
| 417 |
'#default_value' => $node->site,
|
| 418 |
'#required' => TRUE,
|
| 419 |
);
|
| 420 |
$form['status'] = array(
|
| 421 |
'#type' => 'textfield',
|
| 422 |
'#title' => t('Status'),
|
| 423 |
'#size' => 60,
|
| 424 |
'#maxlength' => 128,
|
| 425 |
'#default_value' => $node->status,
|
| 426 |
'#required' => TRUE,
|
| 427 |
);
|
| 428 |
}
|
| 429 |
elseif ($node->type == "update_status_aggregator_site") {
|
| 430 |
$form['title'] = array(
|
| 431 |
'#type' => 'textfield',
|
| 432 |
'#title' => check_plain($type->title_label),
|
| 433 |
'#required' => TRUE,
|
| 434 |
'#default_value' => $node->title,
|
| 435 |
'#weight' => -5
|
| 436 |
);
|
| 437 |
$form['drupal_key'] = array(
|
| 438 |
'#type' => 'textfield',
|
| 439 |
'#title' => t('Key'),
|
| 440 |
'#size' => 60,
|
| 441 |
'#maxlength' => 128,
|
| 442 |
'#default_value' => $node->drupal_key,
|
| 443 |
'#required' => TRUE,
|
| 444 |
);
|
| 445 |
}
|
| 446 |
return $form;
|
| 447 |
}
|
| 448 |
|
| 449 |
function update_status_aggregator_xml_notify($pack) {
|
| 450 |
$key = $pack['key'];
|
| 451 |
$site = db_fetch_object(
|
| 452 |
db_query("SELECT usak.nid from {update_status_aggregator_keys} usak LEFT JOIN {node} n on n.vid = usak.vid WHERE drupal_key = '%s'" , $key));
|
| 453 |
|
| 454 |
// Site key has not been added to the server
|
| 455 |
|
| 456 |
if (!$site) {
|
| 457 |
return xmlrpc_error(1, t("You are not allowed to post module updates on this site"));
|
| 458 |
}
|
| 459 |
|
| 460 |
watchdog('update_status_aggregator', t('Fetching key : !key', array('key' => $key)));
|
| 461 |
|
| 462 |
// Fetching module informations
|
| 463 |
|
| 464 |
foreach ($pack['modules'] as $module => $status) {
|
| 465 |
/*
|
| 466 |
* skip unknow modules
|
| 467 |
*/
|
| 468 |
if (!empty($module)) {
|
| 469 |
$pre = array(
|
| 470 |
'type' => 'update_status_aggregator_module',
|
| 471 |
'body' => '',
|
| 472 |
'title' => $module,
|
| 473 |
'created' => time(),
|
| 474 |
'uid' => 1,
|
| 475 |
'status' => 1
|
| 476 |
);
|
| 477 |
|
| 478 |
$sql = "SELECT nid FROM {update_status_aggregator} WHERE module='%s' AND site='%d'";
|
| 479 |
$nid = db_result(db_query($sql, $module, $site->nid));
|
| 480 |
if ($nid) {
|
| 481 |
$pre['nid'] = $nid;
|
| 482 |
}
|
| 483 |
|
| 484 |
$pre['short_name'] = $module;
|
| 485 |
$pre['site'] = $site->nid;
|
| 486 |
$pre['status'] = $status;
|
| 487 |
|
| 488 |
$node = node_submit($pre);
|
| 489 |
node_save($node);
|
| 490 |
}
|
| 491 |
}
|
| 492 |
return t("notification send with success");
|
| 493 |
}
|
| 494 |
|
| 495 |
function status_int_to_string($number) {
|
| 496 |
$status = update_status_aggregator_status_list();
|
| 497 |
return $status[$number];
|
| 498 |
}
|
| 499 |
|
| 500 |
function update_status_aggregator_status_list() {
|
| 501 |
return array(
|
| 502 |
UPDATE_STATUS_NOT_SECURE => t('Need security update'),
|
| 503 |
UPDATE_STATUS_REVOKED => t('Revoked'),
|
| 504 |
UPDATE_STATUS_NOT_SUPPORTED => t('Not supported'),
|
| 505 |
UPDATE_STATUS_NOT_CURRENT => t('New release available'),
|
| 506 |
UPDATE_STATUS_CURRENT => t('Up to date'),
|
| 507 |
UPDATE_STATUS_NOT_CHECKED => t('Not checked'),
|
| 508 |
UPDATE_STATUS_UNKNOWN => t('Unknown'),
|
| 509 |
);
|
| 510 |
}
|
| 511 |
|
| 512 |
function update_status_aggregator_sites_list() {
|
| 513 |
$site = array();
|
| 514 |
$result = db_query("SELECT nid, title FROM {node} WHERE type='update_status_aggregator_site'");
|
| 515 |
|
| 516 |
while ($row = db_fetch_array($result)) {
|
| 517 |
$site[$row['nid']] = $row['title'];
|
| 518 |
}
|
| 519 |
|
| 520 |
return $site;
|
| 521 |
}
|
| 522 |
|
| 523 |
function update_status_aggregator_modules_list() {
|
| 524 |
$modules = array();
|
| 525 |
$result = db_query("SELECT module FROM {update_status_aggregator} GROUP BY module");
|
| 526 |
|
| 527 |
while ($row = db_fetch_array($result)) {
|
| 528 |
$modules[$row['module']] = $row['module'];
|
| 529 |
}
|
| 530 |
|
| 531 |
return $modules;
|
| 532 |
}
|
| 533 |
|
| 534 |
function views_handler_status($fieldinfo, $fielddata, $value, $data) {
|
| 535 |
return status_int_to_string($value);
|
| 536 |
}
|
| 537 |
|
| 538 |
function views_handler_site($fieldinfo, $fielddata, $value, $data) {
|
| 539 |
static $site = array();
|
| 540 |
|
| 541 |
if (!$site[$value]) {
|
| 542 |
$site[$value] = db_result(db_query("SELECT title FROM {node} WHERE nid='%d'", $value));
|
| 543 |
}
|
| 544 |
|
| 545 |
return $site[$value];
|
| 546 |
}
|