kenken999/php
0
1<?php2/**3 * Class for display admin_rights_list4 */5class RightsPage extends ListPage6{7 /**8 * Array of non admin tables9 *10 * @var array11 */12 var $tables = array();13 14 /**15 * Array of all pages16 *17 * @var array18 */19 var $pages = array();20 21 /**22 * Array of non all possible permission masks for tables23 * { "<table>": "<mask>" }24 *25 * @var array26 */27 var $pageMasks = array();28 /**29 * Array of non admin tables rights30 *31 * @var array32 */33 var $rights = array();34 /**35 * Array of page-level restrictions36 *37 * @var array38 */39 var $pageRestrictions = array();40 /**41 * Array with groups data from DB42 *43 * @var array44 */45 var $groups = array();46 47 48 /**49 * Array of smarty groups50 *51 * @var array52 */53 var $smartyGroups = array();54 /**55 * Array with checkboxes prefixes and access masks56 * @var array57 */58 var $cbxNames;59 60 var $permissionNames = array();61 62 var $sortedTables;63 var $menuOrderedTables;64 var $alphaOrderedTables;65 66 /**67 * Contructor68 *69 * @param array $params70 * @return RightsPage71 */72 function __construct(&$params)73 {74 // copy properties to object75 RunnerPage::__construct($params);76 77 $this->permissionNames["A"] = true;78 $this->permissionNames["D"] = true;79 $this->permissionNames["E"] = true;80 $this->permissionNames["S"] = true;81 $this->permissionNames["P"] = true;82 $this->permissionNames["I"] = true;83 $this->permissionNames["M"] = true;84 85 $this->cbxNames = array(86 'add' => array('mask' => 'A', 'rightName' => 'add'),87 'edt' => array('mask' => 'E', 'rightName' => 'edit'),88 'del' => array('mask' => 'D', 'rightName' => 'delete'),89 'lst' => array('mask' => 'S', 'rightName' => 'list'),90 'exp' => array('mask' => 'P', 'rightName' => 'export'),91 'imp' => array('mask' => 'I', 'rightName' => 'import'),92 'adm' => array('mask' => 'M')93 );94 95 // Set language params, if have more than one language96 97 $this->initLogin();98 99 $this->setLangParams();100 101 $this->sortTables();102 103 $this->fillGroupsArr();104 105 $this->fillPagesArr();106 }107 108 function fillPagesArr() {109 $pages = allTablePages();110 foreach( $pages as $table => $_tablePages ) {111 $this->pages[ $table ] = array();112 foreach( $pages[ $table ] as $pageType => $pageIds ) {113 if( $table == GLOBAL_PAGES && $pageType != 'menu' ) {114 continue;115 }116 foreach( $pageIds as $p ) {117 $this->pages[ $table ][$p] = Security::pageType2permission( $pageType );118 }119 }120 }121 }122 123 /**124 * select groups list125 */126 function fillGroupsArr()127 {128 global $cman;129 $grConnection = $cman->getForUserGroups();130 131 $this->groups[-1] = array( "label" => "<"."Admin".">" );132 $this->groups[-2] = array( "label" => "<"."Default".">" );133 $this->groups[-3] = array( "label" => "<"."Guest".">" );134 135 $groupIdField = "GroupID";136 $groupLabelField = "Label";137 $groupProviderField = "Provider";138 139 $dataSource = Security::getUgGroupsDatasource();140 $dc = new DsCommand();141 if( storageGet( "groups_provider_field" ) ) {142 $dc->order[] = array( "column" => $groupProviderField, "dir" => "ASC" );143 }144 $dc->order[] = array( "column" => $groupLabelField, "dir" => "ASC" );145 146 $qResult = $dataSource->getList($dc );147 storageSet( "groups_provider_field", $qResult->fieldExists( $groupProviderField ) );148 while( $tdata = $qResult->fetchAssoc() )149 {150 $label = $tdata[ $groupLabelField ];151 $renameable = true;152 $providerCode = $tdata[ $groupProviderField ];153 if( $providerCode ) {154 $provider = Security::findProvider( $providerCode );155 $renameable = $provider["type"] == stDB;156 $providerLabel = GetMLString( $provider["label"] );157 if( $providerLabel ) {158 $label = $providerLabel . ":" . $label;159 }160 }161 $this->groups[ $tdata[ $groupIdField ] ] = array( "label" => $label, "renameable" => $renameable );162 }163 }164 165 /**166 * Fill and prepare rights array167 * Call it only after save new data, for get fresh data168 */169 function fillSmartyAndRights()170 {171 $first = true;172 foreach($this->groups as $id => $gr)173 {174 $name = $gr["label"];175 $sg = array();176 $sg["group_attrs"] = "value=\"".$id."\"";177 if( $gr["renameable"] )178 $sg["group_attrs"] .= " data-renameable";179 if( $first )180 {181 $sg["group_class"] = "active";182 $first = false;183 }184 $sg["groupname"] = runner_htmlspecialchars($name);185 $this->smartyGroups[] = $sg;186 }187 }188 189 /**190 * Fill rights array191 * Call it only after save new data, for get fresh data192 */193 function getRights()194 {195 // It's expected that $this->tName is equal to 'admin_right' so the page's db connection is used #9875196 $sql = "select ". $this->connection->addFieldWrappers( "GroupID" )197 .", ". $this->connection->addFieldWrappers( "TableName" )198 .", ". $this->connection->addFieldWrappers( "AccessMask" )199 .", ". $this->connection->addFieldWrappers( "Page" )200 ." from ". $this->connection->addTableWrappers( "Chat2ugrights" )201 ." order by ". $this->connection->addFieldWrappers( "GroupID" );202 203 $qResult = $this->connection->query( $sql );204 while( $tdata = $qResult->fetchNumeric() )205 {206 $group = $tdata[0];207 $table = $tdata[1];208 $mask = $tdata[2];209 $strPages = $tdata[3];210 211 $pages = array();212 if( $strPages )213 $pages = my_json_decode( $strPages );214 215 // check whether the table exists in the project216 if( !isset($this->tables[ $table ]) )217 continue;218 219 // check whether the group exists220 if( !isset($this->groups[ $group ]) )221 continue;222 223 // add permissions224 if( !isset($this->rights[ $table ]) ) {225 $this->rights[ $table ] = array();226 $this->pageRestrictions[ $table ] = array();227 }228 $this->rights[ $table ][ $group ] = $this->fixMask($mask, $this->pageMasks[ $table ]);229 if( $pages )230 $this->pageRestrictions[ $table ][ $group ] = $pages;231 }232 233 if( count( array_keys( $this->pages[ GLOBAL_PAGES ] ) ) > 1 ) {234 if( !isset( $this->rights[ GLOBAL_PAGES ] ) ) {235 // add data to check all menu pages236 $this->rights[ GLOBAL_PAGES ] = array();237 $this->pageRestrictions[ GLOBAL_PAGES ] = array();238 }239 240 foreach( $this->groups as $groupId => $d ) {241 if( !isset( $this->rights[ GLOBAL_PAGES ][ $groupId ] ) ) {242 $this->rights[ GLOBAL_PAGES ][ $groupId ] = "S";243 }244 }245 }246 }247 248 /**249 * Prepare JS arrays with groups and tables data250 */251 function addJsGroupsAndRights()252 {253 $this->jsSettings['tableSettings'][$this->tName]['warnOnLeaving'] = true;254 $this->jsSettings['tableSettings'][$this->tName]['rights'] = $this->rights;255 $this->jsSettings['tableSettings'][$this->tName]['pageRestrictions'] = $this->pageRestrictions;256 $this->jsSettings['tableSettings'][$this->tName]['groups'] = $this->groups;257 $this->jsSettings['tableSettings'][$this->tName]['tables'] = $this->tables;258 $this->jsSettings['tableSettings'][$this->tName]['allPages'] = $this->pages;259 $this->jsSettings['tableSettings'][$this->tName]['pageMasks'] = $this->pageMasks;260 $this->jsSettings['tableSettings'][$this->tName]['menuOrderedTables'] = $this->menuOrderedTables;261 $this->jsSettings['tableSettings'][$this->tName]['alphaOrderedTables'] = $this->alphaOrderedTables;262 }263 264 function commonAssign()265 {266 $this->xt->assign_loopsection("groups", $this->smartyGroups);267 268 parent::commonAssign();269 270 // assign headcheckboxes271 foreach( $this->permissionNames as $perm => $t )272 {273 $this->xt->assign( $perm."_headcheckbox", " id=\"colbox".$perm."\" data-perm=\"".$perm."\"");274 }275 276 // assign attrs277 $this->xt->assign("delgroup_attrs", "id=\"delGroupBtn\"");278 $this->xt->assign("rengroup_attrs", "id=\"renGroupBtn\"");279 $this->xt->assign("savegroup_attrs", "id=\"saveGroupBtn\"");280 $this->xt->assign("savebutton_attrs", "id=\"saveBtn\"");281 $this->xt->assign("resetbutton_attrs", "id=\"resetBtn\"");282 $this->xt->assign("cancelgroup_attrs", "id=\"cancelBtn\"");283 284 // assign blocks285 $this->xt->assign("grid_block", true);286 $this->xt->assign("menu_block", true);287 $this->xt->assign("left_block", true);288 $this->xt->assign("rights_block", true);289 $this->xt->assign("message_block", true);290 $this->xt->assign("security_block", true);291 $this->xt->assign("savebuttons_block", true);292 $this->xt->assign("search_records_block", true);293 $this->xt->assign("recordcontrols_block", true);294 295 // assign user settings296 // The user might rewrite $_SESSION["UserName"] value with HTML code in an event, so no encoding will be performed while printing this value.297 $this->xt->assign("username", $_SESSION["UserName"]);298 if ($this->createLoginPage)299 $this->xt->assign("userid", runner_htmlspecialchars( Security::getUserName() ));300 301 $this->hideElement("message");302 }303 304 function getBreadcrumbMenuId() {305 return "adminarea";306 }307 308 /**309 * Sort tables array310 * @param unknown_type $tables311 */312 function sortTables()313 {314 // build $this->alphaOrderedTables and $this->sortedTables315 $this->sortedTables = array();316 // order tables by caption317 foreach($this->tables as $table => $tbl)318 {319 $this->sortedTables[] = array($table, $tbl[1]);320 }321 usort( $this->sortedTables, "rightsSortFunc" );322 323 $this->alphaOrderedTables = array();324 foreach($this->sortedTables as $t)325 {326 $this->alphaOrderedTables[] = $t[0];327 }328 329 // build $this->menuOrderedTables330 $this->menuOrderedTables = array();331 $menuObject = RunnerMenu::getMenuObject( "main" );332 $menuNodes = $menuObject->collectNodes();333 $addedTables = array();334 $groupsMap = array();335 $allTables = GetTablesListWithoutSecurity();336 337 $addedTables[GLOBAL_PAGES] = true;338 $arr["table"] = GLOBAL_PAGES;339 $arr["items"] = array();340 $arr["collapsed"] = true;341 $this->menuOrderedTables[] = $arr;342 343 foreach( $menuNodes as $mNode ) {344 $nodeType = $mNode->type;345 $nodeId = $mNode->id;346 $table = $mNode->table;347 $title = $mNode->title;348 $pageType = $mNode->pageType;349 350 $parentNodeId = $mNode->parentItem ? $mNode->parentItem->id : 0;351 352 $arr = array();353 if( $pageType == "webreports" || $nodeType == "Separator" ) {354 continue;355 }356 357 if( $table && !$addedTables[ $table ] 358 && array_search( $table, $allTables ) !== false ) {359 $addedTables[ $table ] = true;360 $arr["table"] = $table;361 }362 if( $parentNodeId ) {363 $arr["parent"] = $groupsMap[ $parentNodeId ];364 $this->menuOrderedTables[ $arr["parent"] ]["items"][] = count($this->menuOrderedTables);365 }366 367 if( $nodeType == "Group" ) {368 $arr["groupId"] = count($this->menuOrderedTables);369 }370 371 if( $nodeType == "Group" ) {372 $groupsMap[ $nodeId ] = count($this->menuOrderedTables);373 // add all groups374 $arr["title"] = $title;375 $arr["items"] = array();376 $arr["collapsed"] = true;377 }378 379 $this->menuOrderedTables[] = $arr;380 }381 // add the rest of tables alphabetically382 if(count($this->alphaOrderedTables) > count($addedTables))383 {384 $unlistedId = count($this->menuOrderedTables);385 $arr = array();386 $arr["collapsed"] = true;387 $arr["title"] = "Unlisted tables";388 $arr["items"] = array();389 $this->menuOrderedTables[] = $arr;390 foreach( $this->alphaOrderedTables as $table)391 {392 if( !$addedTables[ $table ] )393 {394 $this->menuOrderedTables[$unlistedId]["items"][] = count( $this->menuOrderedTables );395 $this->menuOrderedTables[] = array( "table" => $table, "parent" => $unlistedId);396 }397 }398 }399 }400 401 /**402 * Get items count in group403 * @param item index404 */405 function getItemsCount($itemIdx)406 {407 $count = 0;408 foreach($this->menuOrderedTables[$itemIdx]["items"] as $idx)409 {410 if(isset($this->menuOrderedTables[$idx]["items"]))411 $count += $this->getItemsCount($idx);412 if(isset($this->menuOrderedTables[$idx]["table"]))413 $count++;414 }415 return $count;416 }417 418 /**419 * Fills info in array about grid.420 * @param array $rowInfoArr array with total info, that assignes grid421 */422 function fillTablesGrid(&$rowInfoArr)423 {424 // fill $rowInfoArr array425 $rowClass = false;426 $recno = 1;427 $editlink = "";428 $copylink = "";429 $parentStack = array();430 431 $hasGroupsToExpand = false;432 433 foreach($this->menuOrderedTables as $idx => $tbl)434 {435 $table = @$tbl["table"];436 $parent = @$tbl["parent"];437 438 if( $table == GLOBAL_PAGES && count( array_keys( $this->pages[$table] ) ) < 2 ) {439 continue;440 }441 442 // update menu structure443 if(!isset($parent))444 {445 $parentStack = array();446 }447 else448 {449 $stackPos = array_search( $parent, $parentStack );450 if( $stackPos === FALSE )451 $parentStack[] = $parent;452 else453 {454 $parentStack = array_slice( $parentStack, 0, $stackPos + 1);455 }456 }457 458 if( strlen($table) )459 {460 $caption = $this->tables[$table][1]; 461 $shortTable = $this->tables[$table][0];462 $row = array();463 464 if( $caption == $table )465 $tablename = runner_htmlspecialchars( $table );466 else if( $table == GLOBAL_PAGES )467 $tablename = mlang_message('MENU_PAGE');468 else469 $tablename = "<span dir='LTR'>".runner_htmlspecialchars( $caption )470 ." (".runner_htmlspecialchars( $table ).")</span>";471 472 $row["tablename"] = $tablename;473 474 $row["table_row_attrs"] = " id=\"row_".$shortTable."\"";475 $row["tablecheckbox_attrs"]= "id=\"rowbox".$shortTable."\" data-table=\"".$shortTable."\" data-checked=0";476 $row["tbl_cell"] = " id=\"tblcell".$shortTable."\"";477 478 $row["tablecheckbox"] = $table != GLOBAL_PAGES;479 if( $table != GLOBAL_PAGES ) {480 // create permission controls481 $mask = $this->pageMasks[$table];482 foreach( $this->permissionNames as $perm => $x )483 {484 if( strpos($mask, $perm) === FALSE )485 continue;486 487 $row[$perm."_group"] = true;488 $row[$perm."_checkbox"] = " id=\"box".$perm.$shortTable."\" data-checked=0";489 $row[$perm."_cell"] = " id=\"cell".$perm.$shortTable."\"";490 }491 }492 493 $row["hide_pages_attrs"] .= 'data-hide-pages data-hidden data-table="'.$shortTable.'"';494 $row["show_pages_attrs"] .= 'data-show-pages data-table="'.$shortTable.'"';495 $this->fillPageRows( $table, $shortTable, $row, count( $parentStack ) );496 497 }498 else499 {500 $title = $tbl["title"];501 $row = array();502 $row["tablename"] = runner_htmlspecialchars($title);503 504 $row["tablecheckbox_attrs"]= " data-checked=-2";505 $row["table_row_attrs"] = " id=\"grouprow_".$idx."\"";506 $row["hide_pages_attrs"] .= 'data-hidden';507 $row["show_pages_attrs"] .= 'data-hidden';508 }509 if( isset($parent) )510 $row["table_row_attrs"] .= ' data-level="' . count($parentStack) . '"';511 512 $childrenCount = $this->getItemsCount($idx);513 if( isset($tbl["items"]) && $childrenCount )514 {515 $hasGroupsToExpand = true;516 $row["tablename"] .= "<span class='tablecount' dir='LTR'> (".$this->getItemsCount($idx).")</span>";517 $row["table_row_attrs"] .= " data-groupid=\"".$idx."\"";518 $row["groupControl"] = true;519 $row["groupControlState"] = " data-state='closed'";520 $row["groupControlClass"] = " data-state='closed'";521 $row["tblrowclass"] .= " menugroup";522 if( !strlen($table) )523 {524 // the item is just a group525 // add the class to hide it in alpha mode526 $row["tblrowclass"] .= " menugrouponly";527 }528 }529 else if( !strlen($table) )530 {531 // empty menu group532 continue;533 }534 // hide second-level tables initially535 if($parent)536 {537 $row["table_row_attrs"] .= " style='display:none;' data-ingroup='true' ";538 }539 540 $rowInfoArr[] = $row;541 }542 543 if ( !$hasGroupsToExpand )544 $this->hideItemType("rights_expand_all");545 }546 547 function fillPageRows($table, $shortTable, &$row, $level ) {548 $allPages = tablePages( $table );549 $pages = array();550 foreach ($allPages as $ptype => $pids) {551 if ($table == GLOBAL_PAGES && $ptype != "menu") {552 continue;553 }554 foreach ($pids as $p) {555 $pages[$p] = $ptype;556 }557 }558 // second param for ASP559 ksort( $pages, SORT_STRING );560 561 $pageRows = array();562 foreach( $pages as $pageId => $pageType ) {563 $pageRow = array();564 565 $perm = Security::pageType2permission( $pageType );566 $pageRow[$perm."_pagebox"] = true;567 $pageRow["pagebox"] = true;568 $pageRow[$perm."_pagecheckbox"] = " data-table=\"".$shortTable."\" data-page=\"".$pageId."\" id=\"pagebox".$perm.$shortTable.'_'.$pageId."\" data-checked=0";569 $pageRow["pagecheckbox"] = "data-permission=\"".$perm."\" data-table=\"".$shortTable."\" data-page=\"".$pageId."\" id=\"wholepagebox_".$shortTable.'_'.$pageId."\" data-checked=0";570 $pageRow[$perm."_cell"] = " id=\"pagecell".$perm.$shortTable.'_'.$pageId."\"";571 $pageRow["rights_page"] = runner_htmlspecialchars($pageId);572 $pageRow["page_row_attrs"] = 'data-hidden data-table="'.$shortTable.'" data-page="'.$pageId.'" data-level="'.$level.'"';573 $pageRows[] = $pageRow;574 }575 576 $row["page_row"] = array();577 $row["page_row"]["data"] = &$pageRows;578 }579 580 /**581 * Fill premissions grid582 */583 function fillGridData()584 {585 // fill $rowinfo array586 $rowInfo = array();587 $this->fillTablesGrid($rowInfo);588 $this->xt->assign_loopsection("grid_row", $rowInfo);589 }590 591 /**592 * Fill session vars, override parent, do nothing593 */594 function setSessionVariables()595 {596 }597 598 /**599 * Main function, call to build page600 * Do not change methods call oreder!!601 */602 function prepareForBuildPage()603 {604 // prepare array, only after save, for get new data605 $this->fillSmartyAndRights();606 // get rights, only after save, for fresh data607 $this->getRights();608 // fill grid data609 $this->fillGridData();610 // add common js code611 $this->addCommonJs();612 // add common html code613 $this->addCommonHtml();614 // Set common assign615 $this->commonAssign();616 }617 618 /**619 * show page at the end of its proccess, depending on mode620 */621 function showPage()622 {623 $this->display($this->templatefile);624 }625 626 /**627 * Adds HTML and JS628 */629 function addCommonHtml()630 {631 $this->body ["begin"] .= GetBaseScriptsForPage($this->isDisplayLoading);632 633 // assign body end634 $this->body['end'] = XTempl::create_method_assignment( "assignBodyEnd", $this );635 }636 637 /**638 * A stub639 */640 function prepareForResizeColumns()641 {642 }643 644 /**645 * Add js files and scripts646 */647 function addCommonJs() {648 // call parent if need RunnerJS API649 RunnerPage::addCommonJs();650 651 $this->addJsGroupsAndRights();652 }653 654 /**655 * Removes permissions from $mask that are not defined in $possibleMask656 * I.e. $mask = "ADE", $possibleMask = "AESP", return "AE"657 */658 function fixMask($mask, $possibleMask)659 {660 $outMask = "";661 $l = strlen($possibleMask);662 for($i=0; $i < $l; ++$i)663 {664 if(strpos($mask, $possibleMask[$i]) !== FALSE)665 $outMask .= $possibleMask[$i];666 }667 return $outMask;668 }669 670 function saveRights( &$modifiedRights )671 {672 foreach($modifiedRights as $group => $rights)673 {674 foreach($modifiedRights[$group] as $table => $tableRights)675 {676 $this->updateTablePermissions( $table, $group, $tableRights );677 }678 }679 echo my_json_encode(array( 'success' => true ));680 }681 682 /**683 * Save permissions for those pages only, that are defined in the project.684 * This is required when using the same permission tables in several projects685 * @param String table686 * @param Number group687 * @param Array tableRights array(688 * "permissions" => "<mask>",689 * "pages" => array( <restricted pages> => true )690 * )691 */692 function updateTablePermissions( $table, $group, $tableRights )693 {694 $mask = $tableRights["permissions"];695 $rightWTableName = $this->connection->addTableWrappers( "Chat2ugrights" );696 $accessMaskWFieldName = $this->connection->addFieldWrappers( "AccessMask" );697 $groupisWFieldName = $this->connection->addFieldWrappers( "GroupID" );698 $pageWFieldName = $this->connection->addFieldWrappers( "Page" );699 $tableNameWFieldName = $this->connection->addFieldWrappers( "TableName" );700 $groupWhere = $groupisWFieldName."=". $group701 ." and ". $tableNameWFieldName ."=". $this->connection->prepareString( $table );702 703 $strPages = "";704 $pages = $tableRights["pages"];705 if( $pages ) {706 $strPages = my_json_encode( $pages );707 }708 // It's expected that $this->tName is equal to 'admin_right' so the page's db connection is used #9875709 $sql = "select ". $accessMaskWFieldName ." from ". $rightWTableName. " where " . $groupWhere;710 // select rights from the database711 $data = $this->connection->query( $sql )->fetchNumeric();712 if( $data )713 {714 // correct the mask according to the table's pageMask715 $savedMask = $data[0];716 $pageMask = $this->pageMasks[$table];717 $correctedMask = "";718 719 foreach( $this->permissionNames as $perm => $t )720 {721 if( strpos( $pageMask, $perm ) !== false )722 {723 if( strpos( $mask, $perm ) !== false )724 $correctedMask.= $perm;725 }726 else727 {728 if( strpos( $savedMask, $perm ) !== false )729 $correctedMask.= $perm;730 }731 }732 $mask = $correctedMask;733 734 if( strlen($mask) && !( $table == GLOBAL_PAGES && ( $strPages == "" || strpos( $mask, "S" ) === false ) ) ) {735 // update the table name as well to address table renaming ( uppercase/lowercase ) issues736 $sql = "update ". $rightWTableName ." set ".737 $accessMaskWFieldName ."='". $mask ."',".738 $tableNameWFieldName."=".$this->connection->prepareString( $table ).739 "," . $pageWFieldName . "=" . $this->connection->prepareString( $strPages ).740 " where ". $groupWhere;741 } else {742 // an empty access mask or all menu pages are chosen743 $sql = "delete from ". $rightWTableName ." where ". $groupWhere;744 }745 }746 else747 {748 // an empty access mask or all menu pages are chosen749 if( !strlen($mask) || ( $table == GLOBAL_PAGES && $strPages == "" ) )750 return;751 752 $sql = "insert into ". $rightWTableName .753 " (". $groupisWFieldName .", ".$tableNameWFieldName.", ". $accessMaskWFieldName. ", ". $pageWFieldName .")"754 ." values (". $group .", ".$this->connection->prepareString( $table ).", '". $mask ."', "755 .$this->connection->prepareString( $strPages ).")";756 }757 758 $this->connection->exec( $sql );759 }760 761 /**762 * A stub763 */764 function buildSearchPanel() {}765 public function assignSimpleSearch() {}766}767 768function rightsSortFunc($a, $b)769{770 if($a[1]==$b[1])771 return 0;772 if($a[1]<$b[1])773 return -1;774 return 1;775}776 777?>