00001 <?php
00002
00012 abstract class SnapSearch extends SnapObject {
00013 const MODE_LIST = 0;
00014 const MODE_METADATA = 1;
00015 const MODE_OBJECTS = 2;
00016 const MODE_ALL = 3;
00017 const MODE_ALL_OBJECTS = 4;
00018
00019 const GREP_CASE_INSENSITIVE = 0;
00020 const GREP_MULTILINE = 1;
00021 const GREP_MATCH_NEWLINE = 2;
00022 const GREP_MATCH_ALL = 3;
00023
00040 public static function execute($req, $mode = self::MODE_METADATA) {
00041 STimer::start('snapSearch:buildQuery');
00042 $subQuery = array();
00043 foreach($req->getTypes() as $type => $enabled)
00044 if($enabled) {
00045 $subQuery[$type] = self::buildSelect($type, $req->getDomain(), $mode);
00046 $subQuery[$type] .= self::buildConstraints($type, $req->getConstraints($type), $req->getConjunction());
00047 }
00048
00049 if(count($subQuery) == 0)
00050 return array();
00051
00052 $subQueries = implode("\nUNION\n", $subQuery);
00053
00054 $id = intval(array_sum(explode(' ', microtime())));
00055
00056 $query = "CREATE OR REPLACE VIEW searchResults$id AS $subQueries";
00057
00058 STimer::end('snapSearch:buildQuery');
00059
00060 $result = SnapDBI::query($query);
00061 if(!$result) {
00062 self::setStaticError('Error building view: ' . SnapDBI::getLastStaticError());
00063 return null;
00064 }
00065
00066 $query = 'SELECT type, COUNT(*) AS cnt FROM searchResults' . $id . ' GROUP BY type';
00067
00068 $result = SnapDBI::query($query, true);
00069 if($result === null) {
00070 self::setStaticError('Error getting count: ' . SnapDBI::getLastStaticError());
00071 return null;
00072 }
00073
00074 $count = array();
00075 $total = 0;
00076 foreach($result as $r) {
00077 $total += $r['cnt'];
00078 if($r['type'] == 'Directory')
00079 $count['directories'] = $r['cnt'];
00080 else if($r['type'] == 'Resource')
00081 $count['resources'] = $r['cnt'];
00082 else if($r['type'] == 'Version')
00083 $count['versions'] = $r['cnt'];
00084 }
00085 $count['all'] = $total;
00086
00087 $query = "SELECT * FROM searchResults$id GROUP BY path ";
00088
00089 $ordering = $req->getOrdering();
00090 $order = '';
00091 foreach($ordering as $field => $dir) {
00092 if($dir == SnapSearchRequest::ORDER_ASCENDING)
00093 $dir = 'ASC';
00094 else
00095 $dir = 'DESC';
00096 $order .= "$field $dir, ";
00097 }
00098 if($order != '') {
00099 $query .= " ORDER BY " . rtrim($order, ', ');
00100 }
00101
00102 $offset = $req->getOffset();
00103 $limit = $req->getLimit();
00104
00105 if($offset > 0 && $limit > 0)
00106 $query .= " LIMIT $offset, $limit ";
00107 else if($limit > 0)
00108 $query .= " LIMIT $limit ";
00109 else if($offset > 0)
00110 $query .= " LIMIT $offset, 999999999999 ";
00111
00112 $result = SnapDBI::query($query, true);
00113 if($result === null) {
00114 self::setStaticError('Error doing query: ' . SnapDBI::getLastStaticError());
00115 return null;
00116 }
00117
00118 $query = 'DROP VIEW searchResults' . $id;
00119 SnapDBI::query($query);
00120
00121 if($mode == self::MODE_LIST) {
00122 $ret = array();
00123 $directories = array();
00124 $resources = array();
00125 $versions = array();
00126 $all = array();
00127 foreach($result as $r) {
00128 switch($r['type']) {
00129 case 'Directory':
00130 $directories[$r['path']] = array('id' => $r['obj_id'],
00131 'isLink' => $r['isLink'] == '1' ? true : false);
00132 $all[$r['path']] = array('id' => $r['obj_id'], 'type' => 'Directory',
00133 'isLink' => $r['isLink'] == '1' ? true : false);
00134 break;
00135 case 'Resource':
00136 $resources[$r['path']] = array('id' => $r['obj_id'],
00137 'isLink' > $r['isLink'] == '1' ? true : false);
00138 $all[$r['path']] = array('id' => $r['obj_id'], 'type' => 'Resource',
00139 'isLink' => $r['isLink'] == '1' ? true : false);
00140 break;
00141 case 'Version':
00142 $versions[$r['path']] = array('id' => $r['obj_id'],
00143 'isLink' => $r['isLink'] == '1' ? true : false);
00144 $all[$r['path']] = array('id' => $r['obj_id'], 'type' => 'Version',
00145 'isLink' => $r['isLink'] == '1' ? true : false);
00146 break;
00147 }
00148 }
00149 return array(
00150 'directories' => $directories,
00151 'resources' => $resources,
00152 'versions' => $versions,
00153 'all' => $all,
00154 'count' => $count
00155 );
00156 }
00157
00158 $directories = array();
00159 $resources = array();
00160 $versions = array();
00161 $all = array();
00162
00163 if($mode == self::MODE_OBJECTS || $mode == self::MODE_ALL_OBJECTS) {
00164 foreach($result as $r) {
00165 switch($r['type']) {
00166 case 'Directory':
00167 STimer::startAvg('snapSearch:loadDir');
00168 $obj = new SnapDirectory;
00169 foreach(SnapDirectory::$ATTRIBUTES as $a)
00170 if(isset($r["d_$a"]))
00171 $obj->set($a, $r["d_$a"]);
00172 $obj->set('id', $r['obj_id']);
00173 $directories[$r['path']] = array('id' => $r['obj_id'],
00174 'isLink' => $r['isLink'] == '1' ? true : false,
00175 'object' => $obj);
00176 $all[$r['path']] = array('id' => $r['obj_id'], 'type' => 'Directory',
00177 'isLink' => $r['isLink'] == '1' ? true : false,
00178 'object' => $obj);
00179 STimer::endAvg('snapSearch:loadDir');
00180 break;
00181 case 'Resource':
00182 STimer::startAvg('snapSearch:loadRes');
00183 $obj = new SnapResource;
00184 foreach(SnapResource::$ATTRIBUTES as $a)
00185 if(isset($r["r_$a"]))
00186 $obj->set($a, $r["r_$a"]);
00187 $obj->set('id', $r['obj_id']);
00188 $resources[$r['path']] = array('id' => $r['obj_id'],
00189 'isLink' => $r['isLink'] == '1' ? true : false,
00190 'object' => $obj);
00191 $all[$r['path']] = array('id' => $r['obj_id'], 'type' => 'Resource',
00192 'isLink' => $r['isLink'] == '1' ? true : false,
00193 'object' => $obj);
00194 STimer::endAvg('snapSearch:loadRes');
00195 break;
00196 case 'Version':
00197 STimer::startAvg('snapSearch:loadVers');
00198 $obj = new SnapVersion;
00199 foreach(SnapVersion::$ATTRIBUTES as $a)
00200 if(isset($r["v_$a"]))
00201 $obj->set($a, $r["v_$a"]);
00202 $obj->set('id', $r['obj_id']);
00203 $res = new SnapResource;
00204 foreach(SnapResource::$ATTRIBUTES as $a)
00205 if(isset($r["r_$a"]))
00206 $obj->set($a, $r["r_$a"]);
00207 $res->set('id', $r['v_resourceId']);
00208 $obj->set('resource', $res);
00209 $versions[$r['path']] = array('id' => $r['obj_id'],
00210 'isLink' => $r['isLink'] == '1' ? true : false,
00211 'object' => $obj);
00212 $all[$r['path']] = array('id' => $r['obj_id'], 'type' => 'Resource',
00213 'isLink' => $r['isLink'] == '1' ? true : false,
00214 'object' => $obj);
00215 STimer::endAvg('snapSearch:loadVers');
00216 break;
00217 }
00218 }
00219 }
00220 else {
00221 foreach($result as $r) {
00222 switch($r['type']) {
00223 case 'Directory':
00224 STimer::startAvg('snapSearch:loadDir');
00225 $obj = array('id' => $r['obj_id'], 'isLink' => $r['isLink'] == '1' ? true : false, 'type' => 'Directory');
00226 foreach(SnapDirectory::$ATTRIBUTES as $a)
00227 if(isset($r["d_$a"]))
00228 $obj[$a] = $r["d_$a"];
00229 $directories[$r['path']] = $obj;
00230 $all[$r['path']] = $obj;
00231 STimer::endAvg('snapSearch:loadDir');
00232 break;
00233 case 'Resource':
00234 STimer::startAvg('snapSearch:loadRes');
00235 $obj = array('id' => $r['obj_id'], 'isLink' => $r['isLink'] == '1' ? true : false, 'type' => 'Resource');
00236 foreach(SnapResource::$ATTRIBUTES as $a)
00237 if(isset($r["r_$a"]))
00238 $obj[$a] = $r["r_$a"];
00239 $resources[$r['path']] = $obj;
00240 $all[$r['path']] = $obj;
00241 STimer::endAvg('snapSearch:loadRes');
00242 break;
00243 case 'Version':
00244 STimer::startAvg('snapSearch:loadVers');
00245 $obj = array('id' => $r['obj_id'], 'isLink' => $r['isLink'] == '1' ? true : false, 'type' => 'Version');
00246 foreach(SnapVersion::$ATTRIBUTES as $a)
00247 if(isset($r["v_$a"]))
00248 $obj[$a] = $r["v_$a"];
00249 $obj['resource'] = array();
00250 foreach(SnapResource::$ATTRIBUTES as $a)
00251 if(isset($r["r_$a"]))
00252 $obj['resource'][$a] = $r["r_$a"];
00253 $versions[$r['path']] = $obj;
00254 $all[$r['path']] = $obj;
00255 STimer::endAvg('snapSearch:loadVers');
00256 break;
00257 }
00258 }
00259 }
00260
00261 return array(
00262 'directories' => $directories,
00263 'resources' => $resources,
00264 'versions' => $versions,
00265 'all' => $all,
00266 'count' => $count
00267 );
00268 }
00269
00299 public static function grep($pattern, $domain = '//', $types = array(), $options = array(), $statuses = array()) {
00300 $opt_insensitive = in_array(self::GREP_CASE_INSENSITIVE, $options);
00301 $opt_multiline = in_array(self::GREP_MULTILINE, $options);
00302 $opt_newlinematch = in_array(self::GREP_MATCH_NEWLINE, $options);
00303 $opt_global = in_array(self::GREP_MATCH_ALL, $options);
00304
00305 $req = new SnapSearchRequest();
00306 $req->setDomain($domain);
00307 $req->addType(SnapSearchRequest::TYPE_VERSION);
00308 $req->setLimit(0);
00309
00310 if(count($statuses) > 0)
00311 $req->addVersionConstraint('status', SnapSearchRequest::OP_MATCHES, $statuses);
00312
00313 if(count($types) > 0)
00314 $req->addVersionConstraint('contentType', SnapSearchRequest::OP_MATCHES, $types);
00315
00316 $req->setConjunction(SnapSearchRequest::CONJ_AND);
00317
00318 $results = self::execute($req, self::MODE_ALL);
00319
00320 $fl = '';
00321 if($opt_insensitive)
00322 $fl .= 'i';
00323 if($opt_multiline)
00324 $fl .= 'm';
00325 if($opt_newlinematch)
00326 $fl .= 's';
00327
00328 $out = array();
00329 foreach($results['versions'] as $path => $r) {
00330 STimer::startAvg('grepVersion');
00331 $content = $r['content'];
00332
00333 if($opt_global) {
00334 if(preg_match_all("/$pattern/$fl", $content, $matches, PREG_OFFSET_CAPTURE)) {
00335 $m = array();
00336 foreach($matches[0] as $match) {
00337 $end = strpos($content, "\n", $match[1]);
00338 if($end === false)
00339 $end = strlen($content) - 1;
00340 $start = strrpos(substr($content, 0, $match[1]), "\n");
00341 if($start === false)
00342 $start = 0;
00343 $m[] = array(ltrim(rtrim(substr($content, $start, $end - $start), "\n"), "\n"), $match[0]);
00344 }
00345 $out[$path] = $m;
00346 }
00347 }
00348 else {
00349 if(preg_match("/$pattern/$fl", $content, $matches, PREG_OFFSET_CAPTURE)) {
00350 $end = strpos($content, "\n", $matches[0][1]);
00351 if($end === false)
00352 $end = strlen($content) - 1;
00353 $start = strrpos(substr($content, 0, $matches[0][1]), "\n");
00354 if($start === false)
00355 $start = 0;
00356 $out[$path] = array(ltrim(rtrim(substr($content, $start, $end - $start), "\n"), "\n"), $matches[0]);
00357 }
00358 }
00359 STimer::endAvg('grepVersion');
00360 }
00361
00362 return $out;
00363 }
00364
00377 private static function buildSelect($type, $domain, $mode) {
00378 switch($type) {
00379 case SnapSearchRequest::TYPE_DIRECTORY:
00380 if($mode == self::MODE_LIST)
00381 $query = <<<END_SQL
00382 SELECT DISTINCT 'Directory' AS type, PathCache.path, Directory.id AS obj_id,
00383 IF(parentId <> Directory.canonParentId, '1', '0') AS isLink
00384
00385 END_SQL;
00386 else
00387 $query = <<<END_SQL
00388 SELECT DISTINCT 'Directory' AS type, PathCache.path, Directory.id AS obj_id,
00389 DirectoryLink.shortName AS d_shortName,
00390 Directory.name AS d_name,
00391 Directory.description AS d_description,
00392 Directory.created AS d_created,
00393 Directory.modified AS d_modified,
00394 Directory.canonParentId AS d_canonParentId,
00395 Directory.refCount AS d_refCount,
00396 NULL AS r_shortName,
00397 NULL AS r_name,
00398 NULL AS r_description,
00399 NULL AS r_created,
00400 NULL AS r_modified,
00401 NULL AS r_canonParentId,
00402 NULL AS r_refCount,
00403 NULL AS r_contentType,
00404 NULL AS r_approvalDate,
00405 NULL AS r_validDate,
00406 NULL AS r_liveVersionId,
00407 NULL AS r_devVersionId,
00408 NULL AS v_created,
00409 NULL AS v_modified,
00410 NULL AS v_author,
00411 NULL AS v_status,
00412 NULL AS v_ordinal,
00413 NULL AS v_content,
00414 NULL AS v_resourceId,
00415 NULL AS v_prevVersionID,
00416 IF(parentId <> Directory.canonParentId, '1', '0') AS isLink
00417
00418 END_SQL;
00419 $query .= <<<END_SQL
00420 FROM PathCache
00421 LEFT JOIN Directory ON PathCache.directoryId = Directory.id
00422 LEFT JOIN DirectoryLink ON PathCache.directoryId = DirectoryLink.childId
00423 LEFT JOIN PathCache AS pc2 ON DirectoryLink.parentId = pc2.directoryId
00424 WHERE ((PathCache.path LIKE BINARY '$domain%' AND pc2.path LIKE BINARY '$domain%')
00425 OR PathCache.path = BINARY '$domain') AND PathCache.directoryId IS NOT NULL
00426 END_SQL;
00427 break;
00428 case SnapSearchRequest::TYPE_RESOURCE:
00429 if($mode == self::MODE_LIST)
00430 $query = <<<END_SQL
00431 SELECT DISTINCT 'Resource' AS type, PathCache.path, Resource.id AS obj_id,
00432 IF(parentId <> Resource.canonParentId, '1', '0') AS isLink
00433
00434 END_SQL;
00435 else
00436 $query = <<<END_SQL
00437 SELECT DISTINCT 'Resource' AS type, PathCache.path, Resource.id AS obj_id,
00438 NULL AS d_shortName,
00439 NULL AS d_name,
00440 NULL AS d_description,
00441 NULL AS d_created,
00442 NULL AS d_modified,
00443 NULL AS d_canonParentId,
00444 NULL AS d_refCount,
00445 ResourceLink.shortName AS r_shortName,
00446 Resource.name AS r_name,
00447 Resource.description AS r_description,
00448 Resource.created AS r_created,
00449 Resource.modified AS r_modified,
00450 Resource.canonParentId AS r_canonParentId,
00451 Resource.refCount AS r_refCount,
00452 Resource.contentType AS r_contentType,
00453 Resource.approvalDate AS r_approvalDate,
00454 Resource.validDate AS r_validDate,
00455 Resource.liveVersionId AS r_liveVersionId,
00456 Resource.devVersionId AS r_devVersionId,
00457 NULL AS v_created,
00458 NULL AS v_modified,
00459 NULL AS v_author,
00460 NULL AS v_status,
00461 NULL AS v_ordinal,
00462 NULL AS v_content,
00463 NULL AS v_resourceId,
00464 NULL AS v_prevVersionID,
00465 IF(parentId <> Resource.canonParentId, '1', '0') AS isLink
00466
00467 END_SQL;
00468 $query .= <<<END_SQL
00469 FROM PathCache
00470 LEFT JOIN Resource ON PathCache.resourceId = Resource.id
00471 LEFT JOIN ResourceLink ON PathCache.resourceId = ResourceLink.childId
00472 LEFT JOIN PathCache AS pc2 ON ResourceLink.parentId = pc2.directoryId
00473 WHERE ((PathCache.path LIKE BINARY '$domain%' AND pc2.path LIKE BINARY '$domain%')
00474 OR PathCache.path = BINARY '$domain') AND PathCache.resourceId IS NOT NULL
00475 END_SQL;
00476 break;
00477 case SnapSearchRequest::TYPE_VERSION:
00478 if($mode == self::MODE_ALL || $mode == self::MODE_ALL_OBJECTS)
00479 $content = 'Version.content';
00480 else
00481 $content = 'NULL';
00482 if($mode == self::MODE_LIST)
00483 $query = <<<END_SQL
00484 SELECT DISTINCT 'Version' AS type, PathCache.path, Version.id AS obj_id,
00485 IF(parentId <> Resource.canonParentId, '1', '0') AS isLink
00486
00487 END_SQL;
00488 else
00489 $query = <<<END_SQL
00490 SELECT DISTINCT 'Version' AS type, PathCache.path, Version.id AS obj_id,
00491 NULL AS d_shortName,
00492 NULL AS d_name,
00493 NULL AS d_description,
00494 NULL AS d_created,
00495 NULL AS d_modified,
00496 NULL AS d_canonParentId,
00497 NULL AS d_refCount,
00498 ResourceLink.shortName AS r_shortName,
00499 Resource.name AS r_name,
00500 Resource.description AS r_description,
00501 Resource.created AS r_created,
00502 Resource.modified AS r_modified,
00503 Resource.canonParentId AS r_canonParentId,
00504 Resource.refCount AS r_refCount,
00505 Resource.contentType AS r_contentType,
00506 Resource.approvalDate AS r_approvalDate,
00507 Resource.validDate AS r_validDate,
00508 Resource.liveVersionId AS r_liveVersionId,
00509 Resource.devVersionId AS r_devVersionId,
00510 Version.created AS v_created,
00511 Version.modified AS v_modified,
00512 Version.author AS v_author,
00513 Version.status AS v_status,
00514 Version.ordinal AS v_ordinal,
00515 $content AS v_content,
00516 Version.resourceId AS v_resourceId,
00517 Version.prevVersionId AS v_prevVersionID,
00518 IF(parentId <> Resource.canonParentId, '1', '0') AS isLink
00519
00520 END_SQL;
00521 $query .= <<<END_SQL
00522 FROM PathCache
00523 LEFT JOIN Version ON PathCache.versionId = Version.id
00524 LEFT JOIN Resource ON Version.resourceId = Resource.id
00525 LEFT JOIN ResourceLink ON Version.resourceId = ResourceLink.childId
00526 LEFT JOIN PathCache AS pc2 ON ResourceLink.parentId = pc2.directoryId
00527 WHERE (PathCache.path LIKE BINARY '$domain%' AND pc2.path LIKE BINARY '$domain%') AND PathCache.versionId IS NOT NULL
00528 END_SQL;
00529 break;
00530 }
00531
00532 return $query;
00533 }
00534
00550 private static function buildConstraints($type, $constraints, $conj) {
00551 if(count($constraints) == 0)
00552 return '';
00553
00554 $basePrefix = ($type == SnapSearchRequest::TYPE_DIRECTORY ? 'Directory.' :
00555 ($type == SnapSearchRequest::TYPE_RESOURCE ? 'Resource.' : 'Version.'));
00556
00557 $query = '';
00558 $subConstraints = array();
00559 foreach($constraints as $c) {
00560 $op = $c[0];
00561 $field = $c[1];
00562 $value = $c[2];
00563
00564 if($field == 'contentType' || $field == 'validDate' || $field == 'approvalDate'
00565 || $field == 'devVersionId' || $field == 'liveVersionId')
00566 $prefix = 'Resource.';
00567 else if($field == 'shortName')
00568 $prefix = substr($basePrefix, 0, -1) . 'Link.';
00569 else
00570 $prefix = $basePrefix;
00571
00572 if(is_array($value) && count($value) > 0) {
00573 $sq = '(';
00574 foreach($value as $v) {
00575 $opReal = self::convertOp($v, $op);
00576 $sq .= "{$prefix}$field $opReal OR ";
00577 }
00578 $sq = substr($sq, 0, -4) . ')';
00579 }
00580 else if(!is_array($value)) {
00581 $opReal = self::convertOp($value, $op);
00582 $sq = "{$prefix}$field $opReal";
00583 }
00584 else
00585 continue;
00586 $subConstraints[] = $sq;
00587 }
00588
00589 if($conj == SnapSearchRequest::CONJ_OR)
00590 $conj = ' OR ';
00591 else
00592 $conj = ' AND ';
00593
00594 if(count($subConstraints) > 0)
00595 $query = ' AND ' . implode($conj, $subConstraints);
00596 else
00597 $query = '';
00598
00599 return $query;
00600 }
00601
00613 private static function convertOp($v, $op) {
00614 $v = mysql_escape_string($v);
00615 switch($op) {
00616 case SnapSearchRequest::OP_CONTAINS:
00617 $opReal = " LIKE '%$v%'";
00618 break;
00619 case SnapSearchRequest::OP_NOCONTAIN:
00620 $opReal = " NOT LIKE '%$v%'";
00621 break;
00622 default:
00623 case SnapSearchRequest::OP_MATCHES:
00624 $opReal = " = '$v'";
00625 break;
00626 case SnapSearchRequest::OP_NOMATCH:
00627 $opReal = " <> '$v'";
00628 break;
00629 }
00630 return $opReal;
00631 }
00632 }
00633
00634 ?>