public static function fetchKeyword($alphabet, $classid, $offset, $limit, $owner = false, $sortBy = array(), $parentNodeID = false, $includeDuplicates = true, $strictMatching = false)
 {
     $classIDArray = array();
     if (is_numeric($classid)) {
         $classIDArray = array($classid);
     } else {
         if (is_array($classid)) {
             $classIDArray = $classid;
         }
     }
     $showInvisibleNodesCond = eZContentObjectTreeNodeNoLanguage::createShowInvisibleSQLString(true, false);
     $limitation = false;
     $limitationList = eZContentObjectTreeNodeNoLanguage::getLimitationList($limitation);
     $sqlPermissionChecking = eZContentObjectTreeNodeNoLanguage::createPermissionCheckingSQL($limitationList);
     $db_params = array();
     $db_params['offset'] = $offset;
     $db_params['limit'] = $limit;
     $keywordNodeArray = array();
     $lastKeyword = '';
     $db = eZDB::instance();
     //in SELECT clause below we will use a full keyword value
     //or just a part of ezkeyword.keyword matched to $alphabet respective to $includeDuplicates parameter.
     //In the case $includeDuplicates = ture we need only a part
     //of ezkeyword.keyword to be fetched in field to allow DISTINCT to remove rows with the same node id's
     $sqlKeyword = 'ezkeyword.keyword';
     if (!$includeDuplicates) {
         $sqlKeyword = $db->subString('ezkeyword.keyword', 1, strlen($alphabet)) . ' AS keyword ';
     }
     $alphabet = $db->escapeString($alphabet);
     $sortingInfo = array();
     $sortingInfo['attributeFromSQL'] = '';
     $sqlTarget = $sqlKeyword . ',ezcontentobject_tree.node_id';
     if (is_array($sortBy) && count($sortBy) > 0) {
         switch ($sortBy[0]) {
             case 'keyword':
             case 'name':
                 $sortingString = '';
                 if ($sortBy[0] == 'name') {
                     $sortingString = 'ezcontentobject.name';
                 } elseif ($sortBy[0] == 'keyword') {
                     if ($includeDuplicates) {
                         $sortingString = 'ezkeyword.keyword';
                     } else {
                         $sortingString = 'keyword';
                     }
                 }
                 $sortOrder = true;
                 // true is ascending
                 if (isset($sortBy[1])) {
                     $sortOrder = $sortBy[1];
                 }
                 $sortingOrder = $sortOrder ? ' ASC' : ' DESC';
                 $sortingInfo['sortingFields'] = $sortingString . $sortingOrder;
                 break;
             default:
                 $sortingInfo = eZContentObjectTreeNodeNoLanguage::createSortingSQLStrings($sortBy);
         }
         // Fixing the attributeTargetSQL
         switch ($sortBy[0]) {
             case 'keyword':
                 $sortingInfo['attributeTargetSQL'] = '';
                 break;
             case 'name':
                 $sortingInfo['attributeTargetSQL'] = ', ezcontentobject.name';
                 break;
             case 'attribute':
             case 'class_name':
                 break;
             default:
                 $sortingInfo['attributeTargetSQL'] .= ', ' . strtok($sortingInfo["sortingFields"], " ");
         }
         $sqlTarget .= $sortingInfo['attributeTargetSQL'];
     } else {
         $sortingInfo['sortingFields'] = 'ezkeyword.keyword ASC';
     }
     //Adding DISTINCT to avoid duplicates,
     //check if DISTINCT keyword was added before providing clauses for sorting.
     if (!$includeDuplicates && substr($sqlTarget, 0, 9) != 'DISTINCT ') {
         $sqlTarget = 'DISTINCT ' . $sqlTarget;
     }
     $sqlOwnerString = is_numeric($owner) ? "AND ezcontentobject.owner_id = '{$owner}'" : '';
     $parentNodeIDString = is_numeric($parentNodeID) ? "AND ezcontentobject_tree.parent_node_id = '{$parentNodeID}'" : '';
     $sqlClassIDString = '';
     if (is_array($classIDArray) and count($classIDArray)) {
         $sqlClassIDString = 'AND ' . $db->generateSQLINStatement($classIDArray, 'ezkeyword.class_id', false, false, 'int') . ' ';
     }
     // composing sql for matching tag word, it could be strict equiality or LIKE clause
     // dependent of $strictMatching parameter.
     $sqlMatching = "ezkeyword.keyword LIKE '{$alphabet}%'";
     if ($strictMatching) {
         $sqlMatching = "ezkeyword.keyword = '{$alphabet}'";
     }
     $query = "SELECT {$sqlTarget}\n                  FROM ezkeyword\n                       INNER JOIN ezkeyword_attribute_link ON (ezkeyword_attribute_link.keyword_id = ezkeyword.id)\n                       INNER JOIN ezcontentobject_attribute ON (ezcontentobject_attribute.id = ezkeyword_attribute_link.objectattribute_id)\n                       INNER JOIN ezcontentobject ON (ezcontentobject_attribute.version = ezcontentobject.current_version AND ezcontentobject_attribute.contentobject_id = ezcontentobject.id)\n                       INNER JOIN ezcontentobject_tree ON (ezcontentobject_tree.contentobject_id = ezcontentobject.id)\n                       INNER JOIN ezcontentclass ON (ezcontentclass.id = ezcontentobject.contentclass_id)\n                       {$sortingInfo['attributeFromSQL']}\n                       {$sqlPermissionChecking['from']}\n                  WHERE\n                  {$sqlMatching}\n                  {$showInvisibleNodesCond}\n                  {$sqlPermissionChecking['where']}\n                  {$sqlClassIDString}\n                  {$sqlOwnerString}\n                  {$parentNodeIDString}\n                  AND ezcontentclass.version = 0\n                  AND ezcontentobject.status = " . eZContentObject::STATUS_PUBLISHED . "\n                  AND ezcontentobject_tree.main_node_id = ezcontentobject_tree.node_id\n                  ORDER BY {$sortingInfo['sortingFields']}";
     $keyWords = $db->arrayQuery($query, $db_params);
     $trans = eZCharTransform::instance();
     foreach ($keyWords as $keywordArray) {
         $keyword = $keywordArray['keyword'];
         $nodeID = $keywordArray['node_id'];
         $nodeObject = eZContentObjectTreeNodeNoLanguage::fetch($nodeID);
         if ($nodeObject != null) {
             $keywordLC = $trans->transformByGroup($keyword, 'lowercase');
             if ($lastKeyword == $keywordLC) {
                 $keywordNodeArray[] = array('keyword' => '', 'link_object' => $nodeObject);
             } else {
                 $keywordNodeArray[] = array('keyword' => $keyword, 'link_object' => $nodeObject);
             }
             $lastKeyword = $keywordLC;
         } else {
             $lastKeyword = $trans->transformByGroup($keyword, 'lowercase');
         }
     }
     return array('result' => $keywordNodeArray);
 }
 static function subTreeMultiPaths($nodesParams, $listParams = NULL)
 {
     if (!is_array($nodesParams) || !count($nodesParams)) {
         eZDebug::writeWarning(__METHOD__ . ': Nodes parameter must be an array with at least one key.');
         return null;
     }
     if ($listParams === null) {
         $listParams = array('SortBy' => false, 'Offset' => false, 'Limit' => false, 'GroupBy' => false);
     }
     $offset = isset($listParams['Offset']) && is_numeric($listParams['Offset']) ? $listParams['Offset'] : false;
     $limit = isset($listParams['Limit']) && is_numeric($listParams['Limit']) ? $listParams['Limit'] : false;
     $groupBy = isset($listParams['GroupBy']) ? $listParams['GroupBy'] : false;
     if (!isset($listParams['SortBy'])) {
         $listParams['SortBy'] = false;
     }
     $sortBy = $listParams['SortBy'];
     $queryNodes = '';
     foreach ($nodesParams as $nodeParams) {
         $nodeID = $nodeParams['ParentNodeID'];
         if (!is_numeric($nodeID) && !is_array($nodeID)) {
             eZDebug::writeWarning(__METHOD__ . ': Nodes parameter must be numeric or an array with numeric values.');
             $retValue = null;
             return $retValue;
         }
         if ($nodeParams === null) {
             $nodeParams = array('Depth' => false, 'Language' => false, 'AttributeFilter' => false, 'ExtendedAttributeFilter' => false, 'ClassFilterType' => false, 'ClassFilterArray' => false);
         }
         //$onlyTranslated   = ( isset( $nodeParams['OnlyTranslated']    ) )                       ? $nodeParams['OnlyTranslated']     : false;
         $language = isset($nodeParams['Language']) ? $nodeParams['Language'] : false;
         $depth = isset($nodeParams['Depth']) && is_numeric($nodeParams['Depth']) ? $nodeParams['Depth'] : false;
         $depthOperator = isset($nodeParams['DepthOperator']) ? $nodeParams['DepthOperator'] : false;
         $asObject = isset($nodeParams['AsObject']) ? $nodeParams['AsObject'] : true;
         $mainNodeOnly = isset($nodeParams['MainNodeOnly']) ? $nodeParams['MainNodeOnly'] : false;
         $ignoreVisibility = isset($nodeParams['IgnoreVisibility']) ? $nodeParams['IgnoreVisibility'] : false;
         if (!isset($nodeParams['ClassFilterType'])) {
             $nodeParams['ClassFilterType'] = false;
         }
         $sortingInfo = eZContentObjectTreeNodeNoLanguage::createSortingSQLStrings($sortBy);
         $attributeFilter = eZContentObjectTreeNodeNoLanguage::createAttributeFilterSQLStrings($nodeParams['AttributeFilter'], $sortingInfo, $language);
         if ($language) {
             if (!is_array($language)) {
                 $language = array($language);
             }
             eZContentLanguage::setPrioritizedLanguages($language);
         }
         $classCondition = eZContentObjectTreeNodeNoLanguage::createClassFilteringSQLString($nodeParams['ClassFilterType'], $nodeParams['ClassFilterArray']);
         $extendedAttributeFilter = eZContentObjectTreeNodeNoLanguage::createExtendedAttributeFilterSQLStrings($nodeParams['ExtendedAttributeFilter']);
         $mainNodeOnlyCond = eZContentObjectTreeNodeNoLanguage::createMainNodeConditionSQLString($mainNodeOnly);
         $pathStringCond = '';
         $notEqParentString = '';
         // If the node(s) doesn't exist we return null.
         if (!eZContentObjectTreeNodeNoLanguage::createPathConditionAndNotEqParentSQLStrings($pathStringCond, $notEqParentString, $nodeID, $depth, $depthOperator)) {
             $retValue = null;
             return $retValue;
         }
         $languageFilter = ' AND ' . eZContentLanguage::languagesSQLFilter('ezcontentobject');
         if ($language) {
             eZContentLanguage::clearPrioritizedLanguages();
         }
         $limitation = isset($nodeParams['Limitation']) && is_array($nodeParams['Limitation']) ? $nodeParams['Limitation'] : false;
         $limitationList = eZContentObjectTreeNodeNoLanguage::getLimitationList($limitation);
         $sqlPermissionChecking = eZContentObjectTreeNodeNoLanguage::createPermissionCheckingSQL($limitationList);
         // Determine whether we should show invisible nodes.
         $showInvisibleNodesCond = eZContentObjectTreeNodeNoLanguage::createShowInvisibleSQLString(!$ignoreVisibility);
         $queryNodes .= " (\n                          {$pathStringCond}\n                          {$extendedAttributeFilter['joins']}\n                          {$sortingInfo['attributeWhereSQL']}\n                          {$attributeFilter['where']}\n                          ezcontentclass.version=0 AND\n                          {$notEqParentString}\n                          {$mainNodeOnlyCond}\n                          {$classCondition}\n                          " . eZContentLanguage::sqlFilter('ezcontentobject_name', 'ezcontentobject') . "\n                          {$showInvisibleNodesCond}\n                          {$sqlPermissionChecking['where']}\n                          {$languageFilter}\n                      )\n                      OR";
     }
     $groupBySelectText = '';
     $groupBySQL = $extendedAttributeFilter['group_by'];
     if (!$groupBySQL) {
         eZContentObjectTreeNodeNoLanguage::createGroupBySQLStrings($groupBySelectText, $groupBySQL, $groupBy);
     } else {
         if ($groupBy) {
             eZDebug::writeError("Cannot use group_by parameter together with extended attribute filter which sets group_by!", __METHOD__);
         }
     }
     $query = "SELECT DISTINCT " . "ezcontentobject.contentclass_id, ezcontentobject.current_version, ezcontentobject.id, ezcontentobject.initial_language_id, ezcontentobject.language_mask, " . "ezcontentobject.modified, ezcontentobject.owner_id, ezcontentobject.published, ezcontentobject.remote_id AS object_remote_id, " . "ezcontentobject.section_id, ezcontentobject.status, ezcontentobject_tree.contentobject_is_published, ezcontentobject_tree.contentobject_version, " . "ezcontentobject_tree.depth, ezcontentobject_tree.is_hidden, ezcontentobject_tree.is_invisible, ezcontentobject_tree.main_node_id, ezcontentobject_tree.modified_subnode, " . "ezcontentobject_tree.node_id, ezcontentobject_tree.parent_node_id, ezcontentobject_tree.path_identification_string, ezcontentobject_tree.path_string, " . "ezcontentobject_tree.priority, ezcontentobject_tree.remote_id, ezcontentobject_tree.sort_field, ezcontentobject_tree.sort_order, ezcontentclass.serialized_name_list as class_serialized_name_list, " . "ezcontentclass.identifier as class_identifier, ezcontentclass.is_container {$groupBySelectText}, ezcontentobject_name.name, ezcontentobject_name.real_translation " . "{$sortingInfo['attributeTargetSQL']}, {$nodeParams['ResultID']} AS resultid " . "FROM ezcontentobject_tree " . "INNER JOIN ezcontentobject ON (ezcontentobject.id = ezcontentobject_tree.contentobject_id) " . "INNER JOIN ezcontentclass ON (ezcontentclass.id = ezcontentobject.contentclass_id) " . "INNER JOIN ezcontentobject_name ON ( " . "    ezcontentobject_name.contentobject_id = ezcontentobject_tree.contentobject_id AND " . "    ezcontentobject_name.content_version = ezcontentobject_tree.contentobject_version " . ") " . "{$sortingInfo['attributeFromSQL']} " . "{$attributeFilter['from']} " . "{$extendedAttributeFilter['tables']} " . "{$sqlPermissionChecking['from']} " . "WHERE " . substr($queryNodes, 0, -2) . " " . $groupBySQL;
     if ($sortingInfo['sortingFields']) {
         $query .= " ORDER BY {$sortingInfo['sortingFields']}";
     }
     $db = eZDB::instance();
     $server = count($sqlPermissionChecking['temp_tables']) > 0 ? eZDBInterface::SERVER_SLAVE : false;
     if (!$offset && !$limit) {
         $nodeListArray = $db->arrayQuery($query, array(), $server);
     } else {
         $nodeListArray = $db->arrayQuery($query, array('offset' => $offset, 'limit' => $limit), $server);
     }
     if ($asObject) {
         $retNodeList = eZContentObjectTreeNodeNoLanguage::makeObjectsArray($nodeListArray);
     } else {
         $retNodeList = $nodeListArray;
     }
     // cleanup temp tables
     $db->dropTempTableList($sqlPermissionChecking['temp_tables']);
     return $retNodeList;
 }