|
eZ Publish
[4.0]
|
00001 <?php 00002 // 00003 // Definition of eZURLAlias class 00004 // 00005 // Created on: <24-Jan-2007 16:36:24 amos> 00006 // 00007 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ## 00008 // SOFTWARE NAME: eZ Publish 00009 // SOFTWARE RELEASE: 4.0.x 00010 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS 00011 // SOFTWARE LICENSE: GNU General Public License v2.0 00012 // NOTICE: > 00013 // This program is free software; you can redistribute it and/or 00014 // modify it under the terms of version 2.0 of the GNU General 00015 // Public License as published by the Free Software Foundation. 00016 // 00017 // This program is distributed in the hope that it will be useful, 00018 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 // GNU General Public License for more details. 00021 // 00022 // You should have received a copy of version 2.0 of the GNU General 00023 // Public License along with this program; if not, write to the Free 00024 // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 00025 // MA 02110-1301, USA. 00026 // 00027 // 00028 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ## 00029 // 00030 00031 /*! \file ezurlalias.php 00032 */ 00033 00034 /*! 00035 \class eZURLAliasML ezurlaliasml.php 00036 \brief Handles URL aliases in eZ Publish 00037 00038 URL aliases are different names for existing URLs in eZ Publish. 00039 Using URL aliases allows for having better looking urls on the webpage 00040 as well as having fixed URLs pointing to various locations. 00041 00042 This class handles storing, fetching, moving and subtree updates on 00043 eZ Publish URL aliases, this performed using methods from eZPersistentObject. 00044 00045 The table used to store path information is designed to keep each element in 00046 the path (separated by /) in one row, ie. not the entire path. 00047 Each row uses the *parent* field to say which element is the parent of the current one, 00048 a value of 0 means a top-level path element. 00049 The system also supports path elemens in multiple languages, each language 00050 is stored in separate rows but with the same path element ID, the exception is 00051 when the text of multiple languages are the same then they will simply share the 00052 same row. 00053 00054 Instead of manipulating path elements directly it is recommended to use one 00055 the higher level methods for fetching or storing a path. 00056 00057 For objects the methods getChildren() and getPath() can be used to fetch the child elements and path string. 00058 00059 Typically you will not have a path element object and should use on of these static functions: 00060 00061 - storePath() - Stores a given path with specified action, all parent are created if they don't exist. 00062 - fetchByPath() - Fetch path elements by path string, some wildcard support is also available. 00063 - translate() - Translate requested path string into the internal path. 00064 00065 For more detailed path element handling these static methods are available: 00066 00067 - fetchByAction() - Fetch a path element based on the action. 00068 - fetchByParentID() - Fetch path elements based on parent ID. 00069 - fetchPathByActionList() - Fetch path string based on action values, this is more optimized than getPath(). 00070 00071 - setLangMaskAlwaysAvailable() - Updates language mask for path elements based on actions. 00072 00073 Most of these methods have some common arguments, they can be: 00074 - $maskLanguages - If true then only elements which matches the currently prioritized languaes is processed. 00075 - $onlyPrioritized - If true then only the top prioritized language of the elements is considered. Requires $maskLanguages to be set to true. 00076 - $includeRedirections - If true then elements which redirects to this is also processed. 00077 00078 */ 00079 00080 //include_once( "kernel/classes/ezpersistentobject.php" ); 00081 //include_once( "kernel/classes/ezcontentlanguage.php" ); 00082 //include_once( 'lib/ezi18n/classes/ezchartransform.php' ); 00083 00084 class eZURLAliasML extends eZPersistentObject 00085 { 00086 // Return values from storePath() 00087 const LINK_ID_NOT_FOUND = 1; 00088 const LINK_ID_WRONG_ACTION = 2; 00089 const LINK_ALREADY_TAKEN = 3; 00090 const ACTION_INVALID = 51; 00091 const DB_ERROR = 101; 00092 00093 /*! 00094 Optionally computed path string for this element, used for caching purposes. 00095 */ 00096 public $Path; 00097 private static $charset = null; 00098 00099 /*! 00100 Initializes a new URL alias from database row. 00101 \note If 'path' is set it will be cached in $Path. 00102 */ 00103 function eZURLAliasML( $row ) 00104 { 00105 $this->eZPersistentObject( $row ); 00106 $this->Path = null; 00107 if ( isset( $row['path'] ) ) 00108 { 00109 $this->Path = $row['path']; 00110 } 00111 } 00112 00113 /*! 00114 \reimp 00115 */ 00116 static public function definition() 00117 { 00118 return array( "fields" => array( "id" => array( 'name' => 'ID', 00119 'datatype' => 'integer', 00120 'default' => 0, 00121 'required' => true ), 00122 "parent" => array( 'name' => 'Parent', 00123 'datatype' => 'integer', 00124 'default' => 0, 00125 'required' => true ), 00126 "lang_mask" => array( 'name' => 'LangMask', 00127 'datatype' => 'integer', 00128 'default' => 0, 00129 'required' => true ), 00130 "text" => array( 'name' => 'Text', 00131 'datatype' => 'string', 00132 'default' => '', 00133 'required' => true ), 00134 "text_md5" => array( 'name' => 'TextMD5', 00135 'datatype' => 'string', 00136 'default' => '', 00137 'required' => true ), 00138 "action" => array( 'name' => 'Action', 00139 'datatype' => 'string', 00140 'default' => '', 00141 'required' => true ), 00142 "action_type" => array( 'name' => 'ActionType', 00143 'datatype' => 'string', 00144 'default' => '', 00145 'required' => true ), 00146 "link" => array( 'name' => 'Link', 00147 'datatype' => 'integer', 00148 'default' => 0, 00149 'required' => true ), 00150 "is_alias" => array( 'name' => 'IsAlias', 00151 'datatype' => 'integer', 00152 'default' => 0, 00153 'required' => true ), 00154 "is_original" => array( 'name' => 'IsOriginal', 00155 'datatype' => 'integer', 00156 'default' => 0, 00157 'required' => true ), 00158 "alias_redirects" => array( 'name' => 'AliasRedirects', 00159 'datatype' => 'integer', 00160 'default' => 1, 00161 'required' => true ) ), 00162 "keys" => array( "parent", "text_md5" ), 00163 "function_attributes" => array( "children" => "getChildren", 00164 "path" => "getPath" ), 00165 "class_name" => "eZURLAliasML", 00166 "name" => "ezurlalias_ml" ); 00167 } 00168 00169 /*! 00170 Unicode-aware strtolower, performs the conversion by using eZCharTransform 00171 */ 00172 static function strtolower( $text ) 00173 { 00174 //We need to detect our internal charset 00175 if ( is_null( self::$charset ) ) 00176 { 00177 self::$charset = eZTextCodec::internalCharset(); 00178 } 00179 00180 //First try to use mbstring 00181 if ( extension_loaded( 'mbstring' ) ) 00182 { 00183 return mb_strtolower( $text, self::$charset ); 00184 } 00185 else 00186 { 00187 // Fall back if mbstring is not available 00188 $char = eZCharTransform::instance(); 00189 return $char->transformByGroup( $text, 'lowercase' ); 00190 } 00191 } 00192 00193 /*! 00194 Converts the action property into a real url which responds to the 00195 module/view on the site. 00196 */ 00197 function actionURL() 00198 { 00199 return eZURLAliasML::actionToUrl( $this->Action ); 00200 } 00201 00202 /*! 00203 Creates a new path element with given arguments, MD5 sum is automatically created. 00204 00205 \param $element The text string for the path element. 00206 \param $action Action string. 00207 \param $parentID ID of parent path element. 00208 \param $language ID or mask of languages 00209 \param $languageName Name of language(s), comma separated 00210 */ 00211 static function create( $element, $action, $parentID, $language ) 00212 { 00213 $row = array( 'text' => $element, 00214 'text_md5' => md5( eZURLALiasML::strtolower( $element ) ), 00215 'parent' => $parentID, 00216 'lang_mask' => $language, 00217 'action' => $action ); 00218 return new eZURLAliasML( $row ); 00219 } 00220 00221 /*! 00222 Overrides the default behaviour to automatically update TextMD5. 00223 */ 00224 function setAttribute( $name, $value ) 00225 { 00226 eZPersistentObject::setAttribute( $name, $value ); 00227 if ( $name == 'text' ) 00228 { 00229 $this->TextMD5 = md5( eZURLALiasML::strtolower( $value ) ); 00230 } 00231 else if ( $name == 'action' ) 00232 { 00233 $this->ActionType = null; 00234 } 00235 } 00236 00237 /*! 00238 Generates the md5 for the alias and stores the values. 00239 \note Transaction unsafe. If you call several transaction unsafe methods you must enclose 00240 the calls within a db transaction; thus within db->begin and db->commit. 00241 */ 00242 function store( $fieldFilters = null ) 00243 { 00244 if ( $this->ID === null ) 00245 { 00246 $this->ID = self::getNewID(); 00247 } 00248 if ( $this->Link === null ) 00249 { 00250 $this->Link = $this->ID; 00251 } 00252 if ( $this->TextMD5 === null ) 00253 { 00254 $this->TextMD5 = md5( eZURLALiasML::strtolower( $this->Text ) ); 00255 } 00256 $this->IsOriginal = ($this->ID == $this->Link) ? 1 : 0; 00257 if ( $this->IsAlias ) 00258 $this->IsOriginal = true; 00259 if ( $this->Action == "nop:" ) // nop entries can always be replaced 00260 $this->IsOriginal = false; 00261 if ( strlen( $this->ActionType ) == 0 ) 00262 { 00263 if ( preg_match( "#^(.+):#", $this->Action, $matches ) ) 00264 $this->ActionType = $matches[1]; 00265 else 00266 $this->ActionType = 'nop'; 00267 } 00268 00269 eZPersistentObject::store( $fieldFilters ); 00270 } 00271 00272 /*! 00273 \static 00274 Removes all path elements which matches the action name $actionName and value $actionValue. 00275 */ 00276 static public function removeByAction( $actionName, $actionValue ) 00277 { 00278 // If this is an original element we must get rid of all elements which points to it. 00279 $db = eZDB::instance(); 00280 $actionStr = $db->escapeString( $actionName . ':' . $actionValue ); 00281 $query = "DELETE FROM ezurlalias_ml WHERE action = '{$actionStr}'"; 00282 $db->query( $query ); 00283 } 00284 00285 /*! 00286 \static 00287 Removes a URL-Alias which has parent $parentID, MD5 text $textMD5 and language $language. 00288 If the entry has only the specified language and there are existing children the entry will be disabled instead of removed. 00289 If the entry has other languages other than the one which was specified the language bit is removed. 00290 00291 \param $parentID ID of the parent element 00292 \param $textMD5 MD5 of the lowercase version of the text, see eZURLAliasML::strtolower(). 00293 \param $language The language entry to remove, can be a string with the locale or a language object (eZContentLanguage). 00294 */ 00295 public static function removeSingleEntry( $parentID, $textMD5, $language ) 00296 { 00297 $parentID = (int)$parentID; 00298 if ( !is_object( $language ) ) 00299 $language = eZContentLanguage::fetchByLocale( $language ); 00300 $languageID = (int)$language->attribute( 'id' ); 00301 $db = eZDB::instance(); 00302 00303 $bitDel = $db->bitAnd( 'lang_mask' , (~$languageID) ); 00304 $bitMatch = $db->bitAnd( 'lang_mask', $languageID ) . ' > 0'; 00305 $bitMask = $db->bitAnd( 'lang_mask', ~1 ); 00306 00307 00308 // Fetch data for the given entry 00309 $rows = $db->arrayQuery( "SELECT * FROM ezurlalias_ml WHERE parent = {$parentID} AND text_md5 = '" . $db->escapeString( $textMD5 ) . "' AND $bitMatch" ); 00310 if ( count( $rows ) == 0 ) 00311 return false; 00312 00313 $id = (int)$rows[0]['id']; 00314 $mask = (int)$rows[0]['lang_mask']; 00315 if ( ($mask & ~($languageID | 1)) == 0 ) 00316 { 00317 // No more languages for this entry so we need to check for children 00318 $childRows = $db->arrayQuery( "SELECT * FROM ezurlalias_ml WHERE parent = {$id}" ); 00319 if ( count( $childRows ) > 0 ) 00320 { 00321 // Turn entry into a nop: to disable it 00322 $element = new eZURLAliasML( $rows[0] ); 00323 $element->LangMask = 1; 00324 $element->Action = "nop:"; 00325 $element->ActionType = "nop"; 00326 $element->IsAlias = 0; 00327 $element->store(); 00328 return; 00329 } 00330 } 00331 // Remove language bit from selected entries and remove entries which have no languages. 00332 $db->query( "UPDATE ezurlalias_ml SET lang_mask = $bitDel WHERE parent = {$parentID} AND text_md5 = '" . $db->escapeString( $textMD5 ) . "' AND $bitMatch" ); 00333 $db->query( "DELETE FROM ezurlalias_ml WHERE parent = {$parentID} AND text_md5 = '" . $db->escapeString( $textMD5 ) . "' AND $bitMask = 0" ); 00334 } 00335 00336 /*! 00337 Finds all the children of the current element. 00338 00339 For more control over the list use fetchByParentID(). 00340 */ 00341 function getChildren() 00342 { 00343 return eZURLAliasML::fetchByParentID( $this->ID, true, true, false ); 00344 } 00345 00346 /*! 00347 Calculates the full path for the current item and returns it. 00348 00349 \note If you know the action values of the path use fetchPathByActionList() instead, it is more optimized. 00350 \note The calculated path is cached in $Path. 00351 */ 00352 function getPath() 00353 { 00354 if ( $this->Path !== null ) 00355 return $this->Path; 00356 00357 // Fetch path 'text' elements of correct parent path 00358 $path = array( $this->Text ); 00359 $id = (int)$this->Parent; 00360 $db = eZDB::instance(); 00361 while ( $id != 0 ) 00362 { 00363 $query = "SELECT parent, lang_mask, text FROM ezurlalias_ml WHERE id={$id}"; 00364 $rows = $db->arrayQuery( $query ); 00365 if ( count( $rows ) == 0 ) 00366 { 00367 break; 00368 } 00369 $result = eZURLAliasML::choosePrioritizedRow( $rows ); 00370 if ( !$result ) 00371 { 00372 $result = $rows[0]; 00373 } 00374 $id = (int)$result['parent']; 00375 array_unshift( $path, $result['text'] ); 00376 } 00377 $this->Path = implode( '/', $path ); 00378 return $this->Path; 00379 } 00380 00381 /*! 00382 \static 00383 Stores the full path $path to point to action $action, any missing parents are created as placeholders (ie. nop:). 00384 00385 Returns an array containing the entry 'status' which is the status code, is \c true if all went well, a number otherwise (see class constants). 00386 Will contain 'path' for succesful creation or if the path already exists. 00387 00388 \param $path String containing full path, leading and trailing slashes are stripped. 00389 \param $action Action string for entry. 00390 \param $languageName The language to use for entry, can be a string (locale code, e.g. 'nor-NO') an eZContentLanguage object or false for the top prioritized language. 00391 \param $linkID Numeric ID for link field, if it is set to false the entry will point to itself. Use this for redirections. Use \c true if you want to create an link/alias which points to a module (ie. no entry in urlalias table). 00392 \param $alwaysAvailable If true the entry will be available in any language. 00393 \param $rootID ID of the parent element to start at, use 0/false for the very top. 00394 \param $cleanupElements If true each element in the path will be cleaned up according to the current URL transformation rules. 00395 \param $autoAdjustName If true it will adjust the name until it is unique in the path. Used together with $linkID. 00396 \param $reportErrors If true it will report found errors using eZDebug, if \c false errors are only return in 'status'. 00397 \param $aliasRedirects If true and an alias is being stored it will redirect (using HTTP 301) to it's destination. 00398 */ 00399 static function storePath( $path, $action, 00400 $languageName = false, $linkID = false, $alwaysAvailable = false, $rootID = false, 00401 $cleanupElements = true, $autoAdjustName = false, $reportErrors = true, $aliasRedirects = true ) 00402 { 00403 $path = eZURLAliasML::cleanURL( $path ); 00404 if ( $languageName === false ) 00405 { 00406 $languageName = eZContentLanguage::topPriorityLanguage(); 00407 } 00408 if ( is_object( $languageName ) ) 00409 { 00410 $languageObj = $languageName; 00411 $languageID = (int)$languageName->attribute( 'id' ); 00412 $languageName = $languageName->attribute( 'locale' ); 00413 } 00414 else 00415 { 00416 $languageObj = eZContentLanguage::fetchByLocale( $languageName ); 00417 $languageID = (int)$languageObj->attribute( 'id' ); 00418 } 00419 $languageMask = $languageID; 00420 if ( $alwaysAvailable ) 00421 $languageMask |= 1; 00422 00423 $path = eZURLAliasML::cleanURL( $path ); 00424 $elements = split( "/", $path ); 00425 00426 $db = eZDB::instance(); 00427 $parentID = 0; 00428 00429 // If the root ID is specified we will start the parent search from that 00430 if ( $rootID !== false ) 00431 { 00432 $parentID = $rootID; 00433 } 00434 $i = 0; 00435 // Top element is handled separately. 00436 $topElement = array_pop( $elements ); 00437 // Find correct parent, and create missing ones if necessary 00438 $createdPath = array(); 00439 foreach ( $elements as $element ) 00440 { 00441 $actionStr = $db->escapeString( $action ); 00442 if ( $cleanupElements ) 00443 $element = eZURLAliasML::convertToAlias( $element, 'noname' . (count($createdPath)+1) ); 00444 $elementStr = $db->escapeString( eZURLALiasML::strtolower( $element ) ); 00445 00446 $query = "SELECT * FROM ezurlalias_ml WHERE text_md5 = " . eZURLALiasML::md5( $db, $elementStr, false ) . " AND parent = {$parentID}"; 00447 $rows = $db->arrayQuery( $query ); 00448 if ( count( $rows ) == 0 ) 00449 { 00450 // Create a fake element to ensure we have a parent 00451 $elementObj = eZURLAliasML::create( $element, "nop:", $parentID, 1 ); 00452 $elementObj->store(); 00453 $parentID = (int)$elementObj->attribute( 'id' ); 00454 } 00455 else 00456 { 00457 $parentID = (int)$rows[0]['link']; 00458 } 00459 $createdPath[] = $element; 00460 00461 ++$i; 00462 } 00463 if ( $parentID != 0 ) 00464 { 00465 $sql = "SELECT text, parent FROM ezurlalias_ml WHERE id = {$parentID}"; 00466 $rows = $db->arrayQuery( $sql ); 00467 if ( count( $rows ) > 0 ) 00468 { 00469 // A special case. If the special entry with empty text is used as parent 00470 // the parent must be adjust to 0 (ie. real top level). 00471 if ( strlen( $rows[0]['text'] ) == 0 && $rows[0]['parent'] == 0 ) 00472 { 00473 $createdPath = array(); 00474 $parentID = 0; 00475 } 00476 } 00477 } 00478 00479 if ( !preg_match( "#^(.+):(.+)$#", $action, $matches ) ) 00480 { 00481 return array( 'status' => self::ACTION_INVALID, 00482 'error_message' => "The action value " . var_export( $action, true ) . " is invalid", 00483 'error_number' => self::ACTION_INVALID, 00484 'path' => null, 00485 'element' => null ); 00486 } 00487 $actionName = $matches[1]; 00488 $actionValue = $matches[2]; 00489 $existingElementID = null; 00490 $alwaysMask = $alwaysAvailable ? 1 : 0; 00491 00492 $actionStr = $db->escapeString( $action ); 00493 $actionTypeStr = $db->escapeString( $actionName ); 00494 00495 $createdElement = null; 00496 if ( $linkID === false ) 00497 { 00498 if ( $cleanupElements ) 00499 $topElement = eZURLAliasML::convertToAlias( $topElement, 'noname' . (count($createdPath)+1) ); 00500 00501 $adjustName = false; 00502 $curElementID = null; 00503 $newElementID = null; 00504 $newText = $topElement; 00505 $uniqueCounter = 0; 00506 00507 // Loop until we a valid entry point, which means: 00508 // 1. The entry does not exist yet, so create a new one 00509 // 2. The entry exists but is re-usable (e.g. nop or same action) 00510 // 3. The entry exists and cannot be re-used, instead the name is adjusted to be unique. 00511 while ( true ) 00512 { 00513 $newText = $topElement; 00514 if ( $uniqueCounter > 0 ) 00515 $newText .= ($uniqueCounter + 1); 00516 $textMD5 = eZURLALiasML::md5( $db, $newText ); 00517 00518 $query = "SELECT * FROM ezurlalias_ml WHERE parent = $parentID AND text_md5 = {$textMD5}"; 00519 $rows = $db->arrayQuery( $query ); 00520 if ( count( $rows ) == 0 ) 00521 { 00522 // No such entry, create a new one 00523 break; 00524 } 00525 00526 $row = $rows[0]; 00527 $curID = (int)$row['id']; 00528 $curAction = $row['action']; 00529 if ( $curAction == 'nop:' || $curAction == $action || $row['is_original'] == 0 ) 00530 { 00531 // We can reuse the element so record the ID 00532 $curElementID = $curID; 00533 $newElementID = $curID; 00534 break; 00535 } 00536 00537 if ( !$autoAdjustName ) 00538 { 00539 if ( $reportErrors ) 00540 eZDebug::writeError( "Tried to store path '{$path}' but the path already exists (ID: {$curID}) but with action '{$curAction}', the new action was '{$action}'" ); 00541 return array( 'status' => self::LINK_ALREADY_TAKEN, 00542 'path' => $path, 00543 'element' => null ); 00544 } 00545 // Need to adjust name, re-iterate 00546 ++$uniqueCounter; 00547 } 00548 $textEsc = $db->escapeString( $newText ); 00549 00550 // See if there is already a node in the same level with the same action 00551 if ( $newElementID === null ) 00552 { 00553 $query = "SELECT * FROM ezurlalias_ml\n" . 00554 "WHERE parent = $parentID AND action = '{$actionStr}' AND is_original = 1 AND is_alias = 0"; 00555 $rows = $db->arrayQuery( $query ); 00556 if ( count( $rows ) > 0 ) 00557 { 00558 $newElementID = (int)$rows[0]['id']; 00559 } 00560 } 00561 00562 // Create or update the element 00563 if ( $curElementID !== null ) 00564 { 00565 // Check if an already existing entry at the same level exists, with a different id 00566 // if so the id must be updated. 00567 $query = "SELECT * FROM ezurlalias_ml\n" . 00568 "WHERE parent = $parentID AND action = '{$actionStr}' AND is_original = 1 AND is_alias = 0"; 00569 $rows = $db->arrayQuery( $query ); 00570 if ( count( $rows ) > 0 ) 00571 { 00572 $existingEntryId = (int)$rows[0]['id']; 00573 00574 if ( $existingEntryId != $curElementID ) 00575 { 00576 // move history entry to the same id 00577 $query = "UPDATE ezurlalias_ml SET id = {$existingEntryId} " . 00578 "WHERE parent = $parentID AND text_md5 = {$textMD5}"; 00579 $res = $db->query( $query ); 00580 if ( !$res ) return eZURLAliasML::dbError( $db ); 00581 $curElementID = $existingEntryId; 00582 } 00583 } 00584 00585 $bitOr = $db->bitOr( $db->bitAnd( 'lang_mask', ~1 ), $languageMask ); 00586 // Note: The `text` field is updated too, this ensures case-changes are stored. 00587 $query = "UPDATE ezurlalias_ml SET link = id, lang_mask = {$bitOr}, text = '{$textEsc}', action = '{$actionStr}', action_type = '{$actionTypeStr}', is_alias = 0, is_original = 1\n" . 00588 "WHERE parent = $parentID AND text_md5 = {$textMD5}"; 00589 $res = $db->query( $query ); 00590 if ( !$res ) return eZURLAliasML::dbError( $db ); 00591 $newElementID = $curElementID; 00592 } 00593 else 00594 { 00595 $element = new eZURLAliasML( array( 'id'=> $newElementID, 00596 'link' => null, 00597 'parent' => $parentID, 00598 'text' => $newText, 00599 'lang_mask' => $languageID | $alwaysMask, 00600 'action' => $action ) ); 00601 $element->store(); 00602 $newElementID = (int)$element->attribute( 'id' ); 00603 $createdElement = $element; 00604 } 00605 $createdPath[] = $newText; 00606 00607 // OMS-urlalias-fix: We want to retain the lang_mask of url entries, but mark others as history elements is_original = 0 00608 // Furthermore this change is not performed on custom alias entries. 00609 $bitAnd = $db->bitAnd( 'lang_mask', $languageID ); 00610 00611 // First we look at the entries to mark as history entries, if an entry comprise more languages, it must not be set as history element. 00612 $query = "SELECT * FROM ezurlalias_ml\n" . 00613 "WHERE action = '{$actionStr}' AND (${bitAnd} > 0) AND is_original = 1 AND is_alias = 0 AND (parent != $parentID OR text_md5 != {$textMD5})"; 00614 $toBeUpdated = $db->arrayQuery( $query ); 00615 00616 // 0. Check if the entry to be updated represents multiple languages: 00617 // IF YES: 00618 // 1. "Downgrade" existing entry, by removing the active translation's language id from the language_mask. 00619 // IF NO: 00620 // 1. Mark entry as a history entry 00621 00622 if ( count( $toBeUpdated ) > 0 ) 00623 { 00624 $languageMask = $toBeUpdated[0]['lang_mask']; 00625 if ( ( $languageMask & ~( $languageID | 1 ) ) != 0 ) 00626 { 00627 // "Composite entry", downgrade current entry 00628 $currentEntry = new eZURLAliasML( $toBeUpdated[0] ); 00629 $currentEntry->LangMask = (int)$currentEntry->LangMask & ~$languageID; 00630 $currentEntry->store(); 00631 } 00632 else 00633 { 00634 // Mark as history element. 00635 $query = "UPDATE ezurlalias_ml SET is_original = 0\n" . 00636 "WHERE action = '{$actionStr}' AND (${bitAnd} > 0) AND is_original = 1 AND is_alias = 0 AND (parent != $parentID OR text_md5 != {$textMD5})"; 00637 $res = $db->query( $query ); 00638 if ( !$res ) return eZURLAliasML::dbError( $db ); 00639 } 00640 } 00641 00642 // OMS-urlalias-fix: instead entries without language we look at history elements with same action (and language) 00643 // Look for other nodes with the same action and language 00644 // if found make then link to the new entry 00645 $bitAnd = $db->bitAnd( 'lang_mask', $languageID ); 00646 $query = "SELECT * FROM ezurlalias_ml\n" . 00647 "WHERE action = '{$actionStr}' AND (${bitAnd} > 0) AND is_original = 0 AND (parent != $parentID OR text_md5 != {$textMD5})"; 00648 $rows = $db->arrayQuery( $query ); 00649 foreach ( $rows as $row ) 00650 { 00651 $idtmp = (int)$row['id']; 00652 if ( $idtmp == $newElementID ) 00653 { 00654 $idtmp = self::getNewID(); 00655 } 00656 $parentIDTmp = (int)$row['parent']; 00657 $textMD5Tmp = eZURLALiasML::md5( $db, $row['text'] ); 00658 00659 // OMS-urlalias-fix: We do not touch the lang_mask here 00660 $res = $db->query( "UPDATE ezurlalias_ml SET id = {$idtmp}, link = {$newElementID}, is_alias = 0, is_original = 0\n" . 00661 "WHERE parent = {$parentIDTmp} AND text_md5 = {$textMD5Tmp}" ); 00662 if ( !$res ) return eZURLAliasML::dbError( $db ); 00663 } 00664 $res = $db->query( $query ); 00665 if ( !$res ) return eZURLAliasML::dbError( $db ); 00666 00667 // Look for other nodes which is a link for the current action 00668 // if found make then link to the new entry 00669 // OMS-urlalias-fix: We only want to update the links of entries within the same language. 00670 // Also, only to be applied on normal entries, not custom aliases 00671 $bitAnd = $db->bitAnd( 'lang_mask', $languageID ); 00672 $query = "UPDATE ezurlalias_ml SET link = {$newElementID}, is_alias = 0, is_original = 0\n" . 00673 "WHERE action = '{$actionStr}' AND is_original = 0 AND is_alias = 0 AND (${bitAnd} > 0) AND (parent != $parentID OR text_md5 != {$textMD5})"; 00674 $res = $db->query( $query ); 00675 if ( !$res ) return eZURLAliasML::dbError( $db ); 00676 00677 00678 // Move children from old node to the new node 00679 // Conflicts: 00680 // New | Old | Action 00681 // ------------------------------- 00682 // Element | Link | Delete old 00683 // Element | Element | Will not happen, if so delete old 00684 // Element | Other | Reparent with new name 00685 // Element | nop | Delete old 00686 // Link | Link | Delete old 00687 // Link | Element | Delete new, reparent 00688 // Link | Other | Delete new, reparent 00689 // Link | nop | Delete old 00690 // nop | Link | Delete new, reparent 00691 // nop | Element | Delete new, reparent 00692 // nop | nop | Delete old 00693 00694 // TODO: Handle all conflict cases, for now only the `Delete old, reparent` action is done 00695 00696 // OMS-urlalias-fix: We are only updating child nodes within the same language, 00697 // and only for real system-generated url aliases. Custom aliases are left alone. 00698 $bitAnd = $db->bitAnd( 'lang_mask', $languageID ); 00699 $query = "SELECT id FROM ezurlalias_ml\n" . 00700 "WHERE action = '{$actionStr}' AND is_alias = 0 AND (parent != $parentID OR text_md5 != {$textMD5})"; 00701 $rows = $db->arrayQuery( $query ); 00702 foreach ( $rows as $row ) 00703 { 00704 $oldParentID = (int)$row['id']; 00705 $query = "UPDATE ezurlalias_ml SET parent = {$newElementID}\n" . 00706 "WHERE parent = {$oldParentID} AND (${bitAnd} > 0)"; 00707 $res = $db->query( $query ); 00708 if ( !$res ) return eZURLAliasML::dbError( $db ); 00709 } 00710 } 00711 else 00712 { 00713 // Check the link ID 00714 if ( $linkID !== true ) 00715 { 00716 $linkID = (int)$linkID; 00717 // Step 1, find existing ID 00718 $query = "SELECT * FROM ezurlalias_ml WHERE id = '{$linkID}'"; 00719 $rows = $db->arrayQuery( $query ); 00720 // Some sanity checking 00721 if ( count( $rows ) == 0 ) 00722 { 00723 if ( $reportErrors ) 00724 eZDebug::writeError( "The link ID $linkID does not exist, cannot create the link", 'eZURLAliasML::storePath' ); 00725 return array( 'status' => eZURLAliasML::LINK_ID_NOT_FOUND ); 00726 } 00727 if ( $rows[0]['action'] != $action ) 00728 { 00729 if ( $reportErrors ) 00730 eZDebug::writeError( "The link ID $linkID uses a different action ({$rows[0]['action']}) than the requested action ({$action}) for the link, cannot create the link", 'eZURLAliasML::storePath' ); 00731 return array( 'status' => eZURLAliasML::LINK_ID_WRONG_ACTION ); 00732 } 00733 // If the element which is pointed to is a link, then grab the link id from that instead 00734 if ( $rows[0]['link'] != $rows[0]['id'] ) 00735 { 00736 $linkID = (int)$rows[0]['link']; 00737 } 00738 } 00739 else 00740 { 00741 $linkID = null; 00742 } 00743 00744 if ( $cleanupElements ) 00745 $topElement = eZURLAliasML::convertToAlias( $topElement, 'noname' . (count($createdPath)+1) ); 00746 00747 $adjustName = false; 00748 $curElementID = null; 00749 $newText = $topElement; 00750 $uniqueCounter = 0; 00751 $rows = null; // Will be filled in by the while loop 00752 00753 // Loop until we a valid entry point, which means: 00754 // 1. The entry does not exist yet, so create a new one 00755 // 2. The entry exists but is re-usable (e.g. nop or same action) 00756 // 3. The entry exists and cannot be re-used, instead the name is adjusted to be unique. 00757 while ( true ) 00758 { 00759 $newText = $topElement; 00760 if ( $uniqueCounter > 0 ) 00761 $newText .= ($uniqueCounter + 1); 00762 $textMD5 = eZURLALiasML::md5( $db, $newText ); 00763 00764 $query = "SELECT * FROM ezurlalias_ml WHERE parent = $parentID AND text_md5 = {$textMD5}"; 00765 $rows = $db->arrayQuery( $query ); 00766 if ( count( $rows ) == 0 ) 00767 { 00768 // No such entry, create a new one 00769 break; 00770 } 00771 00772 $row = $rows[0]; 00773 $curID = (int)$row['id']; 00774 $curLink = (int)$row['link']; 00775 $curAction = $row['action']; 00776 if ( $curAction == $action ) 00777 { 00778 // If the current node is the same action and is not a link we 00779 // cannot replace it with a link node. 00780 if ( $curID != $curLink ) 00781 { 00782 // We can reuse the element so record the ID 00783 $curElementID = $curID; 00784 break; 00785 } 00786 } 00787 else if ( $curAction == 'nop:' || $row['is_original'] == 0 ) 00788 { 00789 // We can reuse the element so record the ID 00790 $curElementID = $curID; 00791 break; 00792 } 00793 00794 if ( !$autoAdjustName ) 00795 { 00796 if ( $reportErrors ) 00797 eZDebug::writeError( "Tried to store path '{$path}' but the path already exists (ID: {$curID}) but with action '{$curAction}', the new action was '{$action}'" ); 00798 return array( 'status' => self::LINK_ALREADY_TAKEN, 00799 'path' => $path, 00800 'element' => null ); 00801 } 00802 // Need to adjust name, re-iterate 00803 ++$uniqueCounter; 00804 } 00805 $textEsc = $db->escapeString( $newText ); 00806 00807 // Create or update the element 00808 if ( $curElementID !== null ) 00809 { 00810 $element = new eZURLAliasML( $rows[0] ); // $rows is from the while loop 00811 $element->LangMask |= $languageID | $alwaysMask; 00812 $element->IsAlias = 1; 00813 $element->Action = $action; 00814 // Note: The `text` field is updated too, this ensures case-changes are stored. 00815 $element->Text = $newText; 00816 $element->TextMD5 = null; 00817 $element->ActionType = null; 00818 $element->Link = null; 00819 } 00820 else 00821 { 00822 $element = new eZURLAliasML( array( 'id'=> null, 00823 'link' => null, 00824 'parent' => $parentID, 00825 'text' => $newText, 00826 'lang_mask' => $languageID | $alwaysMask, 00827 'action' => $action, 00828 'is_alias' => 1 ) ); 00829 } 00830 $element->AliasRedirects = $aliasRedirects ? 1 : 0; 00831 $element->store(); 00832 $createdPath[] = $topElement; 00833 $createdElement = $element; 00834 } 00835 return array( 'status' => true, 00836 'path' => join( "/", $createdPath ), 00837 'element' => $createdElement ); 00838 } 00839 00840 /*! 00841 \static 00842 \private 00843 00844 Returns a structure with the current database error. 00845 \note This is used by storePath(). 00846 */ 00847 static private function dbError( $db ) 00848 { 00849 return array( 'status' => self::DB_ERROR, 00850 'error_message' => $db->errorMessage(), 00851 'error_number' => $db->errorNumber(), 00852 'path' => null, 00853 'element' => null ); 00854 } 00855 00856 /*! 00857 \static 00858 Fetches real path element(s) which matches the action name $actionName and value $actionValue. 00859 00860 Lets say we have the following elements: 00861 00862 \code 00863 === ==== ====== =========== ========== 00864 id link parent text action 00865 === ==== ====== =========== ========== 00866 1 1 0 'ham' 'eznode:4' 00867 2 6 0 'spam' 'eznode:55' 00868 3 3 0 'bicycle' 'eznode:5' 00869 4 4 0 'superman' 'nop:' 00870 5 5 3 'repairman' 'eznode:42' 00871 6 6 3 'repoman' 'eznode:55' 00872 === ==== ====== =========== ========== 00873 \endcode 00874 00875 then we try to fetch a specific action: 00876 \code 00877 $elements = eZURLAliasML::fetchByAction( 'eznode', 5 ); 00878 \endcode 00879 00880 it would return: 00881 \code 00882 === ==== ====== =========== ========== 00883 id link parent text action 00884 === ==== ====== =========== ========== 00885 3 3 0 'bicycle' 'eznode:5' 00886 === ==== ====== =========== ========== 00887 \endcode 00888 00889 Now let's try with an element which is redirecting: 00890 \code 00891 $elements = eZURLAliasML::fetchByAction( 'eznode', 10 ); 00892 \endcode 00893 00894 it would return: 00895 \code 00896 === ==== ====== =========== ========== 00897 id link parent text action 00898 === ==== ====== =========== ========== 00899 2 6 0 'spam' 'eznode:55' 00900 === ==== ====== =========== ========== 00901 \endcode 00902 */ 00903 static public function fetchByAction( $actionName, $actionValue, $maskLanguages = false, $onlyPrioritized = false, $includeRedirections = false ) 00904 { 00905 $action = $actionName . ":" . $actionValue; 00906 $db = eZDB::instance(); 00907 $actionStr = $db->escapeString( $action ); 00908 $langMask = ''; 00909 if ( $maskLanguages ) 00910 { 00911 $langMask = "(" . trim( eZContentLanguage::languagesSQLFilter( 'ezurlalias_ml', 'lang_mask' ) ) . ") AND "; 00912 } 00913 $query = "SELECT * FROM ezurlalias_ml WHERE $langMask action = '$actionStr'"; 00914 if ( !$includeRedirections ) 00915 { 00916 $query .= " AND is_original = 1 AND is_alias = 0"; 00917 } 00918 $rows = $db->arrayQuery( $query ); 00919 if ( count( $rows ) == 0 ) 00920 return array(); 00921 $rows = eZURLAliasML::filterRows( $rows, $onlyPrioritized ); 00922 $objectList = eZPersistentObject::handleRows( $rows, 'eZURLAliasML', true ); 00923 return $objectList; 00924 } 00925 00926 /*! 00927 \static 00928 Fetches path element(s) which matches the parent ID $id. 00929 00930 Lets say we have the following elements: 00931 00932 === ==== ====== =========== ========== 00933 id link parent text action 00934 === ==== ====== =========== ========== 00935 1 1 0 'ham' 'eznode:4' 00936 2 6 0 'spam' 'eznode:55' 00937 3 3 0 'bicycle' 'eznode:5' 00938 4 4 0 'superman' 'nop:' 00939 5 5 3 'repairman' 'eznode:42' 00940 6 6 3 'repoman' 'eznode:55' 00941 === ==== ====== =========== ========== 00942 00943 then we try to fetch a specific ID: 00944 \code 00945 eZURLAliasML::fetchByParentID( 0 ); 00946 \endcode 00947 00948 it would return (ie. no redirections): 00949 \code 00950 === ==== ====== =========== ========== 00951 id link parent text action 00952 === ==== ====== =========== ========== 00953 1 1 0 'ham' 'eznode:4' 00954 3 3 0 'bicycle' 'eznode:5' 00955 4 4 0 'superman' 'nop:' 00956 === ==== ====== =========== ========== 00957 \endcode 00958 00959 Now let's try with an element which is redirecting: 00960 \code 00961 $includeRedirections = true; 00962 eZURLAliasML::fetchByParentID( 0, false, false, $includeRedirections ); 00963 \endcode 00964 00965 it would return: 00966 \code 00967 === ==== ====== =========== ========== 00968 id link parent text action 00969 === ==== ====== =========== ========== 00970 1 1 0 'ham' 'eznode:4' 00971 2 6 0 'spam' 'eznode:55' 00972 3 3 0 'bicycle' 'eznode:5' 00973 4 4 0 'superman' 'nop:' 00974 === ==== ====== =========== ========== 00975 \endcode 00976 */ 00977 static public function fetchByParentID( $id, $maskLanguages = false, $onlyPrioritized = false, $includeRedirections = true ) 00978 { 00979 $db = eZDB::instance(); 00980 $id = (int)$id; 00981 $langMask = trim( eZContentLanguage::languagesSQLFilter( 'ezurlalias_ml', 'lang_mask' ) ); 00982 $redirSQL = ''; 00983 if ( !$includeRedirections ) 00984 { 00985 $redirSQL = " AND is_original = 1"; 00986 } 00987 $langMask = ''; 00988 if ( $maskLanguages ) 00989 { 00990 $langMask = "(" . trim( eZContentLanguage::languagesSQLFilter( 'ezurlalias_ml', 'lang_mask' ) ) . ") AND "; 00991 } 00992 $query = "SELECT * FROM ezurlalias_ml WHERE $langMask parent = {$id} $redirSQL"; 00993 $rows = $db->arrayQuery( $query ); 00994 $rows = eZURLAliasML::filterRows( $rows, $onlyPrioritized ); 00995 return eZPersistentObject::handleRows( $rows, 'eZURLAliasML', true ); 00996 } 00997 00998 /*! 00999 \static 01000 Fetches the path string based on the action $actionName and the values $actionValues. 01001 The first entry in $actionValues would be the top-most path element in the path 01002 the second entry the child of the first path element and so on. 01003 01004 Lets say we have the following elements: 01005 \code 01006 === ==== ====== =========== ========== 01007 id link parent text action 01008 === ==== ====== =========== ========== 01009 1 1 0 'ham' 'eznode:4' 01010 2 6 0 'spam' 'eznode:55' 01011 3 3 0 'bicycle' 'eznode:5' 01012 4 4 0 'superman' 'nop:' 01013 5 5 3 'repairman' 'eznode:42' 01014 6 6 3 'repoman' 'eznode:55' 01015 === ==== ====== =========== ========== 01016 \endcode 01017 01018 then we try to fetch a specific ID: 01019 \code 01020 $path = eZURLAliasML::fetchPathByActionList( 'eznode', array( 3, 5 ) ); 01021 \endcode 01022 01023 it would return: 01024 \code 01025 'bicycle/repairman' 01026 \endcode 01027 01028 \note This function is faster than getPath() since it can fetch all elements in one SQL. 01029 \note If the fetched elements does not point to each other (parent/id) then null is returned. 01030 */ 01031 static public function fetchPathByActionList( $actionName, $actionValues ) 01032 { 01033 if ( !is_array( $actionValues ) || count( $actionValues ) == 0 ) 01034 { 01035 eZDebug::writeError( "Action values array must not be empty", __METHOD__ ); 01036 return null; 01037 } 01038 $db = eZDB::instance(); 01039 $actionList = array(); 01040 foreach ( $actionValues as $i => $value ) 01041 { 01042 $actionList[] = "'" . $db->escapeString( $actionName . ":" . $value ) . "'"; 01043 } 01044 $actionStr = join( ", ", $actionList ); 01045 $filterSQL = trim( eZContentLanguage::languagesSQLFilter( 'ezurlalias_ml', 'lang_mask' ) ); 01046 $query = "SELECT id, parent, lang_mask, text, action FROM ezurlalias_ml WHERE ( {$filterSQL} ) AND action in ( {$actionStr} ) AND is_original = 1 AND is_alias=0"; 01047 $rows = $db->arrayQuery( $query ); 01048 $actionMap = array(); 01049 foreach ( $rows as $row ) 01050 { 01051 $action = $row['action']; 01052 if ( !isset( $actionMap[$action] ) ) 01053 $actionMap[$action] = array(); 01054 $actionMap[$action][] = $row; 01055 } 01056 01057 $prioritizedLanguages = eZContentLanguage::prioritizedLanguages(); 01058 $path = array(); 01059 $lastID = false; 01060 foreach ( $actionValues as $actionValue ) 01061 { 01062 $action = $actionName . ":" . $actionValue; 01063 if ( !isset( $actionMap[$action] ) ) 01064 { 01065 // eZDebug::writeError( "The action '{$action}' was not found in the database for the current language language filter, cannot calculate path." ); 01066 return null; 01067 } 01068 $actionRows = $actionMap[$action]; 01069 $defaultRow = null; 01070 foreach( $prioritizedLanguages as $language ) 01071 { 01072 foreach ( $actionRows as $row ) 01073 { 01074 $langMask = (int)$row['lang_mask']; 01075 $wantedMask = (int)$language->attribute( 'id' ); 01076 if ( ( $wantedMask & $langMask ) > 0 ) 01077 { 01078 $defaultRow = $row; 01079 break 2; 01080 } 01081 // If the 'always available' bit is set then choose it as the default 01082 if ( ($langMask & 1) > 0 ) 01083 { 01084 $defaultRow = $row; 01085 } 01086 } 01087 } 01088 if ( $defaultRow ) 01089 { 01090 $id = (int)$defaultRow['id']; 01091 $paren = (int)$defaultRow['parent']; 01092 01093 // If the parent is 0 it means the element is at the top, ie. reset the path and lastID 01094 if ( $paren == 0 ) 01095 { 01096 $lastID = false; 01097 $path = array(); 01098 } 01099 01100 $path[] = $defaultRow['text']; 01101 01102 // Check for a valid path 01103 if ( $lastID !== false && $lastID != $paren ) 01104 { 01105 eZDebug::writeError( "The parent ID $paren of element with ID $id does not point to the last entry which had ID $lastID, incorrect path would be calculated, aborting" ); 01106 return null; 01107 } 01108 $lastID = $id; 01109 } 01110 else 01111 { 01112 // No row was found 01113 eZDebug::writeError( "Fatal error, no row was chosen for action " . $actionName . ":" . $actionValue ); 01114 return null; 01115 } 01116 } 01117 return join( "/", $path ); 01118 } 01119 01120 /*! 01121 \static 01122 Fetches the path element(s) which has the path $uriString. 01123 If $glob is set it will use $uriString as the folder to search in and $glob as 01124 the starting text to match against. 01125 01126 Lets say we have the following elements: 01127 \code 01128 === ==== ====== =========== ========== 01129 id link parent text action 01130 === ==== ====== =========== ========== 01131 1 1 0 'ham' 'eznode:4' 01132 2 6 0 'spam' 'eznode:55' 01133 3 3 0 'bicycle' 'eznode:5' 01134 4 4 0 'superman' 'nop:' 01135 5 5 3 'repairman' 'eznode:42' 01136 6 6 3 'repoman' 'eznode:55' 01137 === ==== ====== =========== ========== 01138 \endcode 01139 01140 Then we try to fetch a specific path: 01141 \code 01142 $elements = eZURLAliasML::fetchByPath( "bicycle/repairman" ); 01143 \endcode 01144 01145 we would get: 01146 \code 01147 === ==== ====== =========== ========== 01148 id link parent text action 01149 === ==== ====== =========== ========== 01150 5 5 3 'repairman' 'eznode:42' 01151 === ==== ====== =========== ========== 01152 \endcode 01153 01154 \code 01155 $elements = eZURLAliasML::fetchByPath( "bicycle", "rep" ); // bicycle/rep* 01156 \endcode 01157 01158 we would get: 01159 \code 01160 === ==== ====== =========== ========== 01161 id link parent text action 01162 === ==== ====== =========== ========== 01163 5 5 3 'repairman' 'eznode:42' 01164 6 6 3 'repoman' 'eznode:55' 01165 === ==== ====== =========== ========== 01166 \endcode 01167 */ 01168 static public function fetchByPath( $uriString, $glob = false ) 01169 { 01170 $uriString = eZURLAliasML::cleanURL( $uriString ); 01171 01172 $db = eZDB::instance(); 01173 if ( $uriString == '' && $glob !== false ) 01174 $elements = array(); 01175 else 01176 $elements = split( "/", $uriString ); 01177 $len = count( $elements ); 01178 $i = 0; 01179 $selects = array(); 01180 $tables = array(); 01181 $conds = array(); 01182 $prevTable = false; 01183 foreach ( $elements as $element ) 01184 { 01185 $table = "e" . $i; 01186 $langMask = trim( eZContentLanguage::languagesSQLFilter( $table, 'lang_mask' ) ); 01187 01188 if ( $glob === false && ($i == $len - 1) ) 01189 $selects[] = eZURLAliasML::generateFullSelect( $table ); 01190 else 01191 $selects[] = eZURLAliasML::generateSelect( $table, $i, $len ); 01192 $tables[] = "ezurlalias_ml " . $table; 01193 $conds[] = eZURLAliasML::generateCond( $table, $prevTable, $i, $langMask, $element ); 01194 $prevTable = $table; 01195 ++$i; 01196 } 01197 if ( $glob !== false ) 01198 { 01199 ++$len; 01200 $table = "e" . $i; 01201 $langMask = trim( eZContentLanguage::languagesSQLFilter( $table, 'lang_mask' ) ); 01202 01203 $selects[] = eZURLAliasML::generateFullSelect( $table, $i, $len ); 01204 $tables[] = "ezurlalias_ml " . $table; 01205 $conds[] = eZURLAliasML::generateGlobCond( $table, $prevTable, $i, $langMask, $glob ); 01206 $prevTable = $table; 01207 ++$i; 01208 } 01209 $elementOffset = $i - 1; 01210 $query = "SELECT DISTINCT " . join( ", ", $selects ) . "\nFROM " . join( ", ", $tables ) . "\nWHERE " . join( "\nAND ", $conds ); 01211 01212 $pathRows = $db->arrayQuery( $query ); 01213 $elements = array(); 01214 if ( count( $pathRows ) > 0 ) 01215 { 01216 foreach ( $pathRows as $pathRow ) 01217 { 01218 $redirectLink = false; 01219 $table = "e" . $elementOffset; 01220 $element = array( 'id' => $pathRow[$table . "_id"], 01221 'parent' => $pathRow[$table . "_parent"], 01222 'lang_mask' => $pathRow[$table . "_lang_mask"], 01223 'text' => $pathRow[$table . "_text"], 01224 'action' => $pathRow[$table . "_action"], 01225 'link' => $pathRow[$table . "_link"] ); 01226 $path = array(); 01227 $lastID = false; 01228 for ( $i = 0; $i < $len; ++$i ) 01229 { 01230 $table = "e" . $i; 01231 $id = $pathRow[$table . "_id"]; 01232 $link = $pathRow[$table . "_link"]; 01233 $path[] = $pathRow[$table . "_text"]; 01234 if ( $link != $id ) 01235 { 01236 // Mark the redirect link 01237 $redirectLink = $link; 01238 $redirectOffset = $i; 01239 } 01240 $lastID = $link; 01241 } 01242 if ( $redirectLink ) 01243 { 01244 $newLinkID = $redirectLink; 01245 // Resolve new links until a real element is found. 01246 // TODO: Add max redirection count? 01247 while ( $newLinkID ) 01248 { 01249 $query = "SELECT id, parent, lang_mask, text, link FROM ezurlalias_ml WHERE id={$newLinkID}"; 01250 $rows = $db->arrayQuery( $query ); 01251 if ( count( $rows ) == 0 ) 01252 { 01253 return false; 01254 } 01255 $newLinkID = false; 01256 if ( $rows[0]['id'] != $rows[0]['link'] ) 01257 $newLinkID = (int)$rows[0]['link']; 01258 } 01259 $id = (int)$newLinkID; 01260 $path = array(); 01261 01262 // Fetch path 'text' elements of correct parent path 01263 while ( $id != 0 ) 01264 { 01265 $query = "SELECT parent, lang_mask, text FROM ezurlalias_ml WHERE id={$id}"; 01266 $rows = $db->arrayQuery( $query ); 01267 if ( count( $rows ) == 0 ) 01268 { 01269 break; 01270 } 01271 $result = eZURLAliasML::choosePrioritizedRow( $rows ); 01272 if ( !$result ) 01273 { 01274 $result = $rows[0]; 01275 } 01276 $id = (int)$result['parent']; 01277 array_unshift( $path, $result['text'] ); 01278 } 01279 // Fill in end of path elements 01280 for ( $i = $redirectOffset; $i < $len; ++$i ) 01281 { 01282 $table = "e" . $i; 01283 $path[] = $pathRow[$table . "_text"]; 01284 } 01285 } 01286 $element['path'] = implode( '/', $path ); 01287 $elements[] = $element; 01288 } 01289 } 01290 $rows = array(); 01291 $ids = array(); 01292 // Discard duplicates 01293 foreach ( $elements as $element ) 01294 { 01295 $id = (int)$element['id']; 01296 if ( isset( $ids[$id] ) ) 01297 continue; 01298 $ids[$id] = true; 01299 $rows[] = $element; 01300 } 01301 $objectList = eZPersistentObject::handleRows( $rows, 'eZURLAliasML', true ); 01302 return $objectList; 01303 } 01304 01305 /*! 01306 \static 01307 The same as 'fetchByPath' but extracting nodeID from action. 01308 Only first entry will be processed if 'fetchByPath' returns multiple result(e.g. $glob is wildcard). 01309 \return nodeID on success or \c false otherwise. 01310 */ 01311 static public function fetchNodeIDByPath( $uriString, $glob = false ) 01312 { 01313 $nodeID = false; 01314 01315 $urlAliasMLList = eZURLAliasML::fetchByPath( $uriString, $glob ); 01316 if ( is_array( $urlAliasMLList ) && count( $urlAliasMLList ) > 0 ) 01317 $nodeID = eZURLAliasML::nodeIDFromAction( $urlAliasMLList[0]->Action ); 01318 01319 return $nodeID; 01320 } 01321 01322 /*! 01323 \static 01324 Transforms the URI if there exists an alias for it, the new URI is replaced in $uri. 01325 \return \c true is if successful, \c false otherwise 01326 \return The string with new url is returned if the translation was found, but the resource has moved. 01327 01328 Lets say we have the following elements: 01329 \code 01330 === ==== ====== =========== ========== 01331 id link parent text action 01332 === ==== ====== =========== ========== 01333 1 1 0 'ham' 'eznode:4' 01334 2 6 0 'spam' 'eznode:55' 01335 3 3 0 'bicycle' 'eznode:5' 01336 4 4 0 'superman' 'nop:' 01337 5 5 3 'repairman' 'eznode:42' 01338 6 6 3 'repoman' 'eznode:55' 01339 === ==== ====== =========== ========== 01340 \endcode 01341 01342 then we try to translate a path: 01343 \code 01344 $uri = "bicycle/repairman"; 01345 $result = eZURLAliasML::translate( $uri ); 01346 if ( $result ) 01347 { 01348 echo $result, "\n"; 01349 echo $uri, "\n"; 01350 } 01351 \endcode 01352 01353 we would get: 01354 \code 01355 '1' 01356 'content/view/full/42' 01357 \endcode 01358 01359 If we then were to try: 01360 \code 01361 $uri = "spam"; 01362 $result = eZURLAliasML::translate( $uri ); 01363 if ( $result ) 01364 { 01365 echo $result, "\n"; 01366 echo $uri, "\n"; 01367 } 01368 \endcode 01369 01370 we would get: 01371 \code 01372 'bicycle/repoman' 01373 'error/301' 01374 \endcode 01375 01376 Trying a non-existing path: 01377 \code 01378 $uri = "spam/a-lot"; 01379 $result = eZURLAliasML::translate( $uri ); 01380 if ( $result ) 01381 { 01382 echo $result, "\n"; 01383 echo $uri, "\n"; 01384 } 01385 \endcode 01386 01387 then $result would be empty: 01388 01389 Alterntively we can also do a reverse lookup: 01390 \code 01391 $uri = "content/view/full/55"; 01392 $result = eZURLAliasML::translate( $uri, true ); 01393 if ( $result ) 01394 { 01395 echo $result, "\n"; 01396 echo $uri, "\n"; 01397 } 01398 \endcode 01399 01400 we would get: 01401 \code 01402 '1' 01403 'bicycle/repoman' 01404 \endcode 01405 */ 01406 static public function translate( &$uri, $reverse = false ) 01407 { 01408 if ( $uri instanceof eZURI ) 01409 { 01410 $uriString = $uri->elements(); 01411 } 01412 else 01413 { 01414 $uriString = $uri; 01415 } 01416 $uriString = eZURLAliasML::cleanURL( $uriString ); 01417 $internalURIString = $uriString; 01418 $originalURIString = $uriString; 01419 01420 $ini = eZIni::instance(); 01421 01422 $prefixAdded = false; 01423 $prefix = $ini->hasVariable( 'SiteAccessSettings', 'PathPrefix' ) && 01424 $ini->variable( 'SiteAccessSettings', 'PathPrefix' ) != '' ? eZURLAliasML::cleanURL( $ini->variable( 'SiteAccessSettings', 'PathPrefix' ) ) : false; 01425 01426 if ( $prefix ) 01427 { 01428 $escapedPrefix = preg_quote( $prefix, '#' ); 01429 // Only prepend the path prefix if it's not already the first element of the url. 01430 if ( !preg_match( "#^$escapedPrefix(/.*)?$#i", $uriString ) ) 01431 { 01432 $exclude = $ini->hasVariable( 'SiteAccessSettings', 'PathPrefixExclude' ) 01433 ? $ini->variable( 'SiteAccessSettings', 'PathPrefixExclude' ) 01434 : false; 01435 $breakInternalURI = false; 01436 foreach ( $exclude as $item ) 01437 { 01438 $escapedItem = preg_quote( $item, '#' ); 01439 if ( preg_match( "#^$escapedItem(/.*)?$#i", $uriString ) ) 01440 { 01441 $breakInternalURI = true; 01442 break; 01443 } 01444 } 01445 01446 if ( !$breakInternalURI ) 01447 { 01448 $internalURIString = $prefix . '/' . $uriString; 01449 $prefixAdded = true; 01450 } 01451 } 01452 } 01453 01454 $db = eZDB::instance(); 01455 $elements = split( "/", $internalURIString ); 01456 $len = count( $elements ); 01457 if ( $reverse ) 01458 { 01459 return eZURLAliasML::reverseTranslate( $uri, $uriString, $internalURIString ); 01460 } 01461 01462 $i = 0; 01463 $selects = array(); 01464 $tables = array(); 01465 $conds = array(); 01466 foreach ( $elements as $element ) 01467 { 01468 $table = "e" . $i; 01469 01470 $selectString = "{$table}.id AS {$table}_id, "; 01471 $selectString .= "{$table}.link AS {$table}_link, "; 01472 $selectString .= "{$table}.text AS {$table}_text, "; 01473 $selectString .= "{$table}.text_md5 AS {$table}_text_md5, "; 01474 $selectString .= "{$table}.is_alias AS {$table}_is_alias, "; 01475 01476 if ( $i == $len - 1 ) 01477 $selectString .= "{$table}.action AS {$table}_action, "; 01478 01479 $selectString .= "{$table}.alias_redirects AS {$table}_alias_redirects"; 01480 $selects[] = $selectString; 01481 01482 $tables[] = "ezurlalias_ml " . $table; 01483 $langMask = trim( eZContentLanguage::languagesSQLFilter( $table, 'lang_mask' ) ); 01484 if ( $i == 0 ) 01485 { 01486 $conds[] = "{$table}.parent = 0 AND ({$langMask}) AND {$table}.text_md5 = " . eZURLALiasML::md5( $db, $element ); 01487 } 01488 else 01489 { 01490 $conds[] = "{$table}.parent = {$prevTable}.link AND ({$langMask}) AND {$table}.text_md5 = " . eZURLALiasML::md5( $db, $element ); 01491 } 01492 $prevTable = $table; 01493 ++$i; 01494 } 01495 01496 $query = "SELECT " . join( ", ", $selects ) . "\nFROM " . join( ", ", $tables ) . "\nWHERE " . join( "\nAND ", $conds ); 01497 $return = false; 01498 $urlAliasArray = $db->arrayQuery( $query, array( 'limit' => 1 ) ); 01499 if ( count( $urlAliasArray ) > 0 ) 01500 { 01501 $pathRow = $urlAliasArray[0]; 01502 $l = count( $pathRow ); 01503 $redirectLink = false; 01504 $redirectAction = false; 01505 $lastID = false; 01506 $action = false; 01507 $verifiedPath = array(); 01508 $doRedirect = false; 01509 01510 for ( $i = 0; $i < $len; ++$i ) 01511 { 01512 $table = "e" . $i; 01513 $id = $pathRow[$table . "_id"]; 01514 $link = $pathRow[$table . "_link"]; 01515 $text = $pathRow[$table . "_text"]; 01516 $isAlias = $pathRow[$table . '_is_alias']; 01517 $aliasRedirects = $pathRow[$table . '_alias_redirects']; 01518 $verifiedPath[] = $text; 01519 if ( $i == $len - 1 ) 01520 { 01521 $action = $pathRow[$table . "_action"]; 01522 } 01523 if ( $link != $id ) 01524 { 01525 $doRedirect = true; 01526 } 01527 else if ( $isAlias && $action !== false ) 01528 { 01529 if ( $aliasRedirects ) 01530 { 01531 // If the entry is an alias and we have an action we redirect to the original 01532 // url of that action. 01533 $redirectAction = $action; 01534 $doRedirect = true; 01535 } 01536 } 01537 $lastID = $link; 01538 } 01539 01540 if ( !$doRedirect ) 01541 { 01542 $verifiedPathString = implode( '/', $verifiedPath ); 01543 // Check for case difference 01544 if ( $prefixAdded ) 01545 { 01546 if ( strcmp( $originalURIString, substr( $verifiedPathString, strlen( $prefix ) + 1 ) ) != 0 ) 01547 { 01548 $doRedirect = true; 01549 } 01550 } 01551 else if ( strcmp( $verifiedPathString, $internalURIString ) != 0 ) 01552 { 01553 $doRedirect = true; 01554 } 01555 } 01556 01557 if ( preg_match( "#^module:(.+)$#", $action, $matches ) and $doRedirect ) 01558 { 01559 $uriString = 'error/301'; 01560 $return = $matches[1]; 01561 } 01562 else if ( $doRedirect ) 01563 { 01564 if ( $redirectAction !== false ) 01565 { 01566 $query = "SELECT id FROM ezurlalias_ml WHERE action = '" . $db->escapeString( $action ) . "' AND is_original = 1 AND is_alias = 0"; 01567 $rows = $db->arrayQuery( $query ); 01568 if ( count( $rows ) > 0 ) 01569 { 01570 $id = (int)$rows[0]['id']; 01571 } 01572 else 01573 { 01574 $id = false; 01575 $uriString = 'error/301'; 01576 $return = join( "/", $pathData ); 01577 } 01578 } 01579 else 01580 { 01581 $id = (int)$lastID; 01582 } 01583 01584 if ( $id !== false ) 01585 { 01586 $pathData = array(); 01587 // Figure out the correct path by iterating down the parents until we have all 01588 // elements figured out. 01589 01590 while ( $id != 0 ) 01591 { 01592 $query = "SELECT parent, lang_mask, text FROM ezurlalias_ml WHERE id={$id}"; 01593 $rows = $db->arrayQuery( $query ); 01594 if ( count( $rows ) == 0 ) 01595 { 01596 break; 01597 } 01598 $result = eZURLAliasML::choosePrioritizedRow( $rows ); 01599 if ( !$result ) 01600 { 01601 $result = $rows[0]; 01602 } 01603 $id = (int)$result['parent']; 01604 array_unshift( $pathData, $result['text'] ); 01605 } 01606 $uriString = 'error/301'; 01607 $return = join( "/", $pathData ); 01608 } 01609 01610 // Remove prefix of redirect uri if needed 01611 if ( $prefix && is_string( $return ) ) 01612 { 01613 if ( strncasecmp( $return, $prefix . '/', strlen( $prefix ) + 1 ) == 0 ) 01614 { 01615 $return = substr( $return, strlen( $prefix ) + 1 ); 01616 } 01617 } 01618 } 01619 else 01620 { 01621 $uriString = eZURLAliasML::actionToUrl( $action ); 01622 $return = true; 01623 } 01624 01625 if ( $uri instanceof eZURI ) 01626 { 01627 $uri->setURIString( $uriString, false ); 01628 } 01629 else 01630 { 01631 $uri = $uriString; 01632 } 01633 } 01634 01635 return $return; 01636 } 01637 01638 /*! 01639 \private 01640 \static 01641 Perform reverse translation of uri, that is from system-url to url alias. 01642 */ 01643 static public function reverseTranslate( &$uri, $uriString, $internalURIString ) 01644 { 01645 $db = eZDB::instance(); 01646 01647 $action = eZURLAliasML::urlToAction( $internalURIString ); 01648 if ( $action !== false ) 01649 { 01650 $langMask = trim( eZContentLanguage::languagesSQLFilter( 'ezurlalias_ml', 'lang_mask' ) ); 01651 $actionStr = $db->escapeString( $action ); 01652 $query = "SELECT id, parent, lang_mask, text, action FROM ezurlalias_ml WHERE ($langMask) AND action='{$actionStr}' AND is_original = 1 AND is_alias = 0"; 01653 $rows = $db->arrayQuery( $query ); 01654 $path = array(); 01655 $count = count( $rows ); 01656 if ( $count != 0 ) 01657 { 01658 $row = eZURLAliasML::choosePrioritizedRow( $rows ); 01659 if ( $row === false ) 01660 { 01661 $row = $rows[0]; 01662 } 01663 $paren = (int)$row['parent']; 01664 $path[] = $row['text']; 01665 // We have the parent so now do an iterative lookup until we have the top element 01666 while ( $paren != 0 ) 01667 { 01668 $query = "SELECT id, parent, lang_mask, text FROM ezurlalias_ml WHERE ($langMask) AND id=$paren AND is_original = 1 AND is_alias = 0"; 01669 $rows = $db->arrayQuery( $query ); 01670 $count = count( $rows ); 01671 if ( $count != 0 ) 01672 { 01673 $row = eZURLAliasML::choosePrioritizedRow( $rows ); 01674 if ( $row === false ) 01675 { 01676 $row = $rows[0]; 01677 } 01678 $paren = (int)$row['parent']; 01679 array_unshift( $path, $row['text'] ); 01680 } 01681 else 01682 { 01683 eZDebug::writeError( "Lookup of parent ID $paren failed, cannot perform reverse lookup of alias." ); 01684 return false; 01685 } 01686 } 01687 $uriString = join( '/', $path ); 01688 if ( $uri instanceof eZURI ) 01689 { 01690 $uri->setURIString( $uriString, false ); 01691 } 01692 else 01693 { 01694 $uri = $uriString; 01695 } 01696 return true; 01697 } 01698 else 01699 { 01700 return false; 01701 } 01702 } 01703 return false; 01704 } 01705 01706 /*! 01707 \static 01708 Checks if the text entry $text is unique on the current level in the URL path. 01709 If not the name is adjusted with a number at the end until it becomes unique. 01710 The unique text string is returned. 01711 01712 \param $text The text element which is to be checked 01713 \param $action The action string which is to be excluded from the check. Set to empty string to disable the exclusion. 01714 \param $linkCheck If true then it will see all existing entries as taken. 01715 */ 01716 static public function findUniqueText( $parentElementID, $text, $action, $linkCheck = false, $languageID = false ) 01717 { 01718 $db = eZDB::instance(); 01719 $uniqueNumber = 0; 01720 // If there is no parent we need to check against reserved words 01721 if ( $parentElementID == 0 ) 01722 { 01723 $moduleINI = eZINI::instance( 'module.ini' ); 01724 $reserved = $moduleINI->variable( 'ModuleSettings', 'ModuleList' ); 01725 foreach ( $reserved as $res ) 01726 { 01727 if ( strcasecmp( $text, $res ) == 0 ) 01728 { 01729 // The name is a reserved word so it needs to be changed 01730 ++$uniqueNumber; 01731 break; 01732 } 01733 } 01734 } 01735 $suffix = ''; 01736 if ( $uniqueNumber ) 01737 $suffix = $uniqueNumber + 1; 01738 01739 $actionSQL = ''; 01740 if ( strlen( $action ) > 0 ) 01741 { 01742 $actionEsc = $db->escapeString( $action ); 01743 $actionSQL = "AND action != '$actionEsc'"; 01744 } 01745 $languageSQL = ""; 01746 if ( $languageID !== false ) 01747 { 01748 $languageSQL = "AND " . $db->bitAnd( 'lang_mask', $languageID ) . ' > 0'; 01749 } 01750 // Loop until we find a unique name 01751 while ( true ) 01752 { 01753 $textEsc = eZURLALiasML::md5( $db, $text . $suffix ); 01754 $query = "SELECT * FROM ezurlalias_ml WHERE parent = $parentElementID $actionSQL $languageSQL AND text_md5 = $textEsc"; 01755 if ( !$linkCheck ) 01756 { 01757 $query .= " AND is_original = 1"; 01758 } 01759 $rows = $db->arrayQuery( $query ); 01760 if ( count( $rows ) == 0 ) 01761 { 01762 return $text . $suffix; 01763 } 01764 01765 ++$uniqueNumber; 01766 $suffix = $uniqueNumber + 1; 01767 } 01768 } 01769 01770 /*! 01771 \static 01772 Updates the lang_mask field for path elements which matches action $actionName and value $actionValue. 01773 If $langID is false then bit 0 (the *always available* bit) will be removed, otherwise it will set bit 0 for the chosen language and remove it for other languages. 01774 */ 01775 static public function setLangMaskAlwaysAvailable( $langID, $actionName, $actionValue ) 01776 { 01777 if ( !$actionName ) 01778 { 01779 eZDebug::writeError( "ActionName value must not be empty", __METHOD__ ); 01780 return null; 01781 } 01782 $db = eZDB::instance(); 01783 if ( is_array( $actionName ) ) 01784 { 01785 $actions = array(); 01786 foreach ( $actionName as $actionItem ) 01787 { 01788 $action = $actionItem[0] . ":" . $actionItem[1]; 01789 $actions[] = "'" . $db->escapeString( $action ) . "'"; 01790 } 01791 $actionSql = "action in (" . implode( ', ', $actions ) . ")"; 01792 } 01793 else 01794 { 01795 $action = $actionName . ":" . $actionValue; 01796 $actionSql = "action = '" . $db->escapeString( $action ) . "'"; 01797 } 01798 if ( $langID !== false ) 01799 { 01800 // Set the 0 bit for chosen language 01801 $bitOp = $db->bitOr( 'lang_mask', 1 ); 01802 $langWhere = ' AND ' . $db->bitAnd( 'lang_mask' , (int)$langID ) . ' > 0'; 01803 01804 $query = "UPDATE ezurlalias_ml SET lang_mask = $bitOp WHERE $actionSql $langWhere"; 01805 $db->query( $query ); 01806 01807 // Clear the 0 bit for all other languages 01808 $bitOp = $db->bitAnd( 'lang_mask', ~1 ); 01809 $langWhere = ' AND ' . $db->bitAnd( 'lang_mask' , (int)$langID ) . ' = 0'; 01810 01811 $query = "UPDATE ezurlalias_ml SET lang_mask = $bitOp WHERE $actionSql $langWhere"; 01812 $db->query( $query ); 01813 } 01814 else 01815 { 01816 $bitOp = $db->bitAnd( 'lang_mask', ~1 ); 01817 $query = "UPDATE ezurlalias_ml SET lang_mask = $bitOp WHERE $actionSql"; 01818 $db->query( $query ); 01819 } 01820 } 01821 01822 /** 01823 * Chooses the most prioritized row (based on language) of $rows and returns it. 01824 * @param array $rows 01825 * @return array|false The most prioritized row, or false if no match was found 01826 **/ 01827 static public function choosePrioritizedRow( $rows ) 01828 { 01829 $result = false; 01830 $score = 0; 01831 foreach ( $rows as $row ) 01832 { 01833 if ( $result ) 01834 { 01835 $newScore = eZURLAliasML::languageScore( $row['lang_mask'] ); 01836 if ( $newScore > $score ) 01837 { 01838 $result = $row; 01839 $score = $newScore; 01840 } 01841 } 01842 else 01843 { 01844 $result = $row; 01845 $score = eZURLAliasML::languageScore( $row['lang_mask'] ); 01846 } 01847 } 01848 01849 // If score is still 0, this means that the objects languages don't 01850 // match the INI settings, and these should be fix according to the doc. 01851 if ( $score == 0 ) 01852 { 01853 eZDebug::writeWarning( 01854 "None of the available languages are prioritized in the SiteLanguageList setting. An arbitrary language will be used.", 01855 __METHOD__ ); 01856 } 01857 01858 return $result; 01859 } 01860 01861 /*! 01862 \static 01863 \private 01864 Filters the DB rows $rows by selecting the most prioritized row per 01865 path element and returns the new row list. 01866 \param $onlyPrioritized If false all rows are returned, if true filtering is performed. 01867 */ 01868 static private function filterRows( $rows, $onlyPrioritized ) 01869 { 01870 if ( !$onlyPrioritized ) 01871 { 01872 return $rows; 01873 } 01874 $idMap = array(); 01875 foreach ( $rows as $row ) 01876 { 01877 if ( !isset( $idMap[$row['id']] ) ) 01878 { 01879 $idMap[$row['id']] = array(); 01880 } 01881 $idMap[$row['id']][] = $row; 01882 } 01883 01884 $rows = array(); 01885 foreach ( $idMap as $id => $langRows ) 01886 { 01887 $rows[] = eZURLAliasML::choosePrioritizedRow( $langRows ); 01888 } 01889 01890 return $rows; 01891 } 01892 01893 /*! 01894 \static 01895 \private 01896 Calculates the score of the language mask $mask based upon the currently 01897 prioritized languages and returns it. 01898 \note The higher the value the more the language is prioritized. 01899 */ 01900 static private function languageScore( $mask ) 01901 { 01902 $prioritizedLanguages = eZContentLanguage::prioritizedLanguages(); 01903 $scores = array(); 01904 $score = 1; 01905 $mask = (int)$mask; 01906 krsort( $prioritizedLanguages ); 01907 foreach ( $prioritizedLanguages as $prioritizedLanguage ) 01908 { 01909 $id = (int)$prioritizedLanguage->attribute( 'id' ); 01910 if ( $id & $mask ) 01911 { 01912 $scores[] = $score; 01913 } 01914 ++$score; 01915 } 01916 if ( count( $scores ) > 0 ) 01917 { 01918 return max( $scores ); 01919 } 01920 else 01921 { 01922 return 0; 01923 } 01924 } 01925 01926 /*! 01927 \static 01928 Decodes the action string $action into an internal path string and returns it. 01929 01930 The following actions are supported: 01931 - eznode - argument is node ID, path is 'content/view/full/<nodeID>' 01932 - module - argument is module/view/args, path is the arguments 01933 - nop - a no-op, path is '/' 01934 */ 01935 static public function actionToUrl( $action ) 01936 { 01937 if ( !preg_match( "#^([a-zA-Z0-9_]+):(.+)?$#", $action, $matches ) ) 01938 { 01939 eZDebug::writeError( "Action is not of valid syntax '{$action}'" ); 01940 return false; 01941 } 01942 01943 $type = $matches[1]; 01944 $args = ''; 01945 if ( isset( $matches[2] ) ) 01946 $args = $matches[2]; 01947 switch ( $type ) 01948 { 01949 case 'eznode': 01950 if ( !is_numeric( $args ) ) 01951 { 01952 eZDebug::writeError( "Arguments to eznode action must be an integer, got '{$args}'" ); 01953 return false; 01954 } 01955 $url = 'content/view/full/' . $args; 01956 break; 01957 01958 case 'module': 01959 $url = $args; 01960 break; 01961 01962 case 'nop': 01963 $url = '/'; 01964 break; 01965 01966 default: 01967 eZDebug::writeError( "Unknown action type '{$type}', cannot handle it" ); 01968 return false; 01969 } 01970 return $url; 01971 } 01972 01973 /*! 01974 \static 01975 Takes the url string $url and returns the action string for it. 01976 01977 The following path are supported: 01978 - content/view/full/<nodeID> => eznode:<nodeID> 01979 01980 If the url points to an existing module it will return module:<url> 01981 01982 \return false if the action could not be figured out. 01983 */ 01984 static public function urlToAction( $url ) 01985 { 01986 if ( preg_match( "#^content/view/full/([0-9]+)$#", $url, $matches ) ) 01987 { 01988 return "eznode:" . $matches[1]; 01989 } 01990 if ( preg_match( "#^([a-zA-Z0-9]+)/#", $url, $matches ) ) 01991 { 01992 $name = $matches[1]; 01993 $module = eZModule::exists( $name ); 01994 if ( $module !== null ) 01995 return 'module:' . $url; 01996 } 01997 return false; 01998 } 01999 02000 /*! 02001 \static 02002 Makes sure the URL \a $url does not contain leading and trailing slashes (/). 02003 \return the clean URL 02004 */ 02005 static public function cleanURL( $url ) 02006 { 02007 return trim( $url, '/ ' ); 02008 } 02009 02010 /*! 02011 \static 02012 Transform a semi-valid url into one that can be stored in the url-alias system. 02013 Removes leading/trailing slashes and repeated slashes. 02014 02015 \code 02016 echo eZURLAliasML::sanitizeURL( "" ); // Result "" 02017 echo eZURLAliasML::sanitizeURL( "users//the_dude" ); // Result "users/the_dude" 02018 echo eZURLAliasML::sanitizeURL( "archive/products/" ); // Result "archive/products" 02019 \endcode 02020 \return the sanitized URL 02021 */ 02022 static public function sanitizeURL( $url ) 02023 { 02024 $url = preg_replace( "#//+#", "/", trim( $url, '/' ) ); 02025 return $url; 02026 } 02027 02028 /*! 02029 \private 02030 \static 02031 Generates partial SELECT part of SQL based on table $table, counter $i and total length $len. 02032 */ 02033 static private function generateSelect( $table, $i, $len ) 02034 { 02035 if ( $i == $len - 1 ) 02036 { 02037 $select = "{$table}.id AS {$table}_id, {$table}.link AS {$table}_link, {$table}.text AS {$table}_text, {$table}.text_md5 AS {$table}_text_md5, {$table}.action AS {$table}_action"; 02038 } 02039 else 02040 { 02041 $select = "{$table}.id AS {$table}_id, {$table}.link AS {$table}_link, {$table}.text AS {$table}_text, {$table}.text_md5 AS {$table}_text_md5"; 02042 } 02043 return $select; 02044 } 02045 02046 /*! 02047 \private 02048 \static 02049 Generates full SELECT part of SQL based on table $table. 02050 */ 02051 static private function generateFullSelect( $table ) 02052 { 02053 $select = "{$table}.id AS {$table}_id, {$table}.parent AS {$table}_parent, {$table}.lang_mask AS {$table}_lang_mask, {$table}.text AS {$table}_text, {$table}.text_md5 AS {$table}_text_md5, {$table}.action AS {$table}_action, {$table}.link AS {$table}_link"; 02054 return $select; 02055 } 02056 02057 /*! 02058 \private 02059 \static 02060 Generates WHERE part of SQL based on table $table, previous table $prevTable, counter $i, language mask $langMask and text $element. 02061 */ 02062 static private function generateCond( $table, $prevTable, $i, $langMask, $element ) 02063 { 02064 $db = eZDB::instance(); 02065 if ( $i == 0 ) 02066 { 02067 $cond = "{$table}.parent = 0 AND ({$langMask}) AND {$table}.text_md5 = " . eZURLALiasML::md5( $db, $element ); 02068 } 02069 else 02070 { 02071 $cond = "{$table}.parent = {$prevTable}.link AND ({$langMask}) AND {$table}.text_md5 = " . eZURLALiasML::md5( $db, $element ); 02072 } 02073 return $cond; 02074 } 02075 02076 /*! 02077 \private 02078 \static 02079 Generates WHERE part of SQL for a wildcard match based on table $table, previous table $prevTable, counter $i, language mask $langMask and wildcard text $glob. 02080 \note $glob does not contain the wildcard character * but only the beginning of the matching text. 02081 */ 02082 static private function generateGlobCond( $table, $prevTable, $i, $langMask, $glob ) 02083 { 02084 $db = eZDB::instance(); 02085 if ( $i == 0 ) 02086 { 02087 $cond = "{$table}.parent = 0 AND ({$langMask}) AND {$table}.text LIKE '" . $db->escapeString( $glob ) . "%'"; 02088 } 02089 else 02090 { 02091 $cond = "{$table}.parent = {$prevTable}.link AND ({$langMask}) AND {$table}.text LIKE '" . $db->escapeString( $glob ) . "%'"; 02092 } 02093 return $cond; 02094 } 02095 02096 /*! 02097 \static 02098 Converts the path \a $urlElement into a new alias url which only conists of valid characters 02099 in the URL. 02100 For non-Unicode setups this means character in the range a-z, numbers and _, for Unicode 02101 setups it means all characters except space, &, ;, /, :, =, ?, [, ], (, ), - 02102 02103 Invalid characters are converted to -. 02104 \return the converted element 02105 02106 Example with a non-Unicode setup 02107 \example 02108 'My car' => 'My-car' 02109 'What is this?' => 'What-is-this' 02110 'This & that' => 'This-that' 02111 'myfile.tpl' => 'Myfile-tpl', 02112 'øæå' => 'oeaeaa' 02113 \endexample 02114 */ 02115 static public function convertToAlias( $urlElement, $defaultValue = false ) 02116 { 02117 //include_once( 'lib/ezi18n/classes/ezchartransform.php' ); 02118 $trans = eZCharTransform::instance(); 02119 02120 $ini = eZINI::instance(); 02121 $group = $ini->variable( 'URLTranslator', 'TransformationGroup' ); 02122 02123 $urlElement = $trans->transformByGroup( $urlElement, $group ); 02124 if ( strlen( $urlElement ) == 0 ) 02125 { 02126 if ( $defaultValue === false ) 02127 $urlElement = '_1'; 02128 else 02129 { 02130 $urlElement = $defaultValue; 02131 $urlElement = $trans->transformByGroup( $urlElement, $group ); 02132 } 02133 } 02134 return $urlElement; 02135 } 02136 02137 /*! 02138 \static 02139 Converts the path \a $urlElement into a new alias url which only conists of valid characters 02140 in the URL. 02141 This means character in the range a-z, numbers and _. 02142 02143 Invalid characters are converted to -. 02144 \return the converted element 02145 02146 \example 02147 'My car' => 'My-car' 02148 'What is this?' => 'What-is-this' 02149 'This & that' => 'This-that' 02150 'myfile.tpl' => 'Myfile-tpl', 02151 'øæå' => 'oeaeaa' 02152 \endexample 02153 02154 \note Provided for creating url alias as they were before 3.10. Also used to make path_identification_string. 02155 */ 02156 static public function convertToAliasCompat( $urlElement, $defaultValue = false ) 02157 { 02158 //include_once( 'lib/ezi18n/classes/ezchartransform.php' ); 02159 $trans = eZCharTransform::instance(); 02160 02161 $urlElement = $trans->transformByGroup( $urlElement, "urlalias_compat" ); 02162 if ( strlen( $urlElement ) == 0 ) 02163 { 02164 if ( $defaultValue === false ) 02165 $urlElement = '_1'; 02166 else 02167 { 02168 $urlElement = $defaultValue; 02169 $urlElement = $trans->transformByGroup( $urlElement, "urlalias_compat" ); 02170 } 02171 } 02172 return $urlElement; 02173 } 02174 02175 /*! 02176 \static 02177 Converts the path \a $pathURL into a new alias path with limited characters. 02178 For more information on the conversion see convertToAlias(). 02179 \note each element in the path (separated by / (slash) ) is converted separately. 02180 \return the converted path 02181 */ 02182 static public function convertPathToAlias( $pathURL ) 02183 { 02184 $result = array(); 02185 02186 $elements = explode( '/', $pathURL ); 02187 02188 foreach ( $elements as $element ) 02189 { 02190 $element = eZURLAliasML::convertToAlias( $element ); 02191 $result[] = $element; 02192 } 02193 02194 return implode( '/', $result ); 02195 } 02196 02197 /*! 02198 \static 02199 Grabs nodeID from action string. 02200 \return nodeID on success, \c false otherwise. 02201 */ 02202 static public function nodeIDFromAction( $action ) 02203 { 02204 $nodeID = false; 02205 $pos = strpos( $action, 'eznode:' ); 02206 if ( $pos === 0 ) // make sure $action starts from 'eznode:' 02207 $nodeID = substr( $action, strlen( 'eznode:' ) ); 02208 02209 return $nodeID; 02210 } 02211 02212 /*! 02213 \static 02214 Wraps a database md5 call around the string $text and returns the new SQL for it. 02215 02216 \param $escape If true it will lowercase the text and escape it. 02217 \note If the database is Oracle and the text is empty the MD5 is computed by PHP 02218 and returned. 02219 */ 02220 static private function md5( $db, $text, $escape = true ) 02221 { 02222 // Special case for Oracle since it cannot calculate MD5 for empty strings 02223 if ( strlen( $text ) == 0 && $db->databaseName() == 'oracle' ) 02224 return "'" . $db->escapeString( md5( $text ) ) . "'"; 02225 02226 if ( $escape ) 02227 $text = $db->escapeString( eZURLAliasML::strtolower( $text ) ); 02228 return $db->md5( "'" . $text . "'" ); 02229 } 02230 02231 static function getNewID() 02232 { 02233 $db = eZDB::instance(); 02234 if ( $db->supportsDefaultValuesInsertion() ) 02235 { 02236 $db->query( 'INSERT INTO ezurlalias_ml_incr DEFAULT VALUES' ); 02237 } 02238 else 02239 { 02240 // can not use VALUES(DEFAULT), because of http://bugs.mysql.com/bug.php?id=42270 02241 $db->query( 'INSERT INTO ezurlalias_ml_incr(id) VALUES(NULL)' ); 02242 } 02243 02244 return $db->lastSerialID( 'ezurlalias_ml_incr', 'id' ); 02245 } 02246 } 02247 02248 ?>