Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+264 -48
View File
@@ -2,9 +2,9 @@
/**
* @brief MySQL Search Index
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 21 Aug 2014
* @version SVN_VERSION_NUMBER
*/
@@ -33,10 +33,14 @@ class _Index extends \IPS\Content\Search\Index
{
/* Get the index data */
$indexData = $this->indexData( $object );
$class = get_class( $object );
/* If we got the data... */
if( $indexData )
{
/* MySQL specific look up hash for easy referencing across tables */
$indexData['index_class_type_id_hash'] = md5( $indexData['index_class'] . ':' . $indexData['index_object_id'] );
/* If nobody has permission to access it, just remove it */
if ( !$indexData['index_permissions'] )
{
@@ -45,52 +49,159 @@ class _Index extends \IPS\Content\Search\Index
/* Otherwise, go ahead... */
else
{
/* Index it */
$newIndexId = \IPS\Db::i()->replace( 'core_search_index', $indexData, TRUE );
/* If that was successful... */
if ( $newIndexId )
$existingData = NULL;
$existingIndexId = NULL;
$resetLastComment = FALSE;
$newIndexId = NULL;
try
{
$class = get_class( $object );
$databaseColumnId = $object::$databaseColumnId;
$existingData = \IPS\Db::i()->select( 'index_id, index_class, index_object_id, index_item_id, index_hidden, index_is_last_comment, index_author', 'core_search_index', array( 'index_class=? AND index_object_id=?', $indexData['index_class'], $indexData['index_object_id'] ) )->first();
$existingIndexId = $existingData['index_id'];
}
catch( \Exception $e ) { }
/* Adjust tags */
$tags = NULL;
if ( array_key_exists( 'index_tags', $indexData ) )
{
$tags = array_filter( array_merge ( array( $indexData['index_prefix'] ), explode( ',', $indexData['index_tags'] ) ) );
$prefix = $indexData['index_prefix'];
unset( $indexData['index_tags'] );
unset( $indexData['index_prefix'] );
}
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
{
/* We do not allow hidden or needing approval comments to become flagged as the last comment as this means users without hidden view permission never see the item in an item only stream */
$indexData['index_is_last_comment'] = 0;
/* Set index_item_index_id on other index items */
if ( $object instanceof \IPS\Content\Item )
$resetLastComment = TRUE;
}
else if ( $indexData['index_is_last_comment'] and $indexData['index_item_id'] )
{
$classes = array( $class );
/* If this is the latest comment, unflag what was set before on both item and comment */
if ( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
if ( ! $itemClass::$firstCommentRequired )
{
$classes[] = $itemClass;
}
if ( isset( $itemClass::$reviewClass ) )
{
$classes[] = $itemClass::$reviewClass;
}
}
else if ( $object instanceof \IPS\Content\Item )
{
$subClasses = array( $class );
if ( isset( $class::$commentClass ) )
{
$subClasses[] = $class::$commentClass;
$classes[] = $class::$commentClass;
}
if ( isset( $class::$reviewClass ) )
{
$subClasses[] = $class::$reviewClass;
}
\IPS\Db::i()->update( 'core_search_index', array( 'index_item_index_id' => $newIndexId ), array( array( \IPS\Db::i()->in( 'index_class', $subClasses ) ), array( 'index_item_id=?', $object->$databaseColumnId ) ) );
}
elseif ( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
if ( $itemClass::$firstCommentRequired and $object->isFirst() )
{
$itemColumnId = $class::$databaseColumnMap['item'];
\IPS\Db::i()->update( 'core_search_index', array( 'index_item_index_id' => $newIndexId ), array( \IPS\Db::i()->in( 'index_class', array( $class, $class::$itemClass ) ) . ' AND index_item_id=?', $object->$itemColumnId ) );
$classes[] = $class::$reviewClass;
}
}
/* And also set core_follow.follow_index_id */
if ( $object instanceof \IPS\Content\Comment )
{
$class = $class::$itemClass;
}
\IPS\Db::i()->update( 'core_follow', array( 'follow_index_id' => $newIndexId ), array( 'follow_app=? AND follow_area=? AND follow_rel_id=?', $object::$application, mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) ), $object->$databaseColumnId ) );
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 0 ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=?', $indexData['index_item_id'] ) );
}
/* If this is the latest comment, unflag what was set before */
if ( $indexData['index_is_last_comment'] and $indexData['index_item_id'] )
if ( $existingData !== NULL and ( $indexData['index_class'] == $existingData['index_class'] and $indexData['index_object_id'] == $existingData['index_object_id'] ) )
{
\IPS\Db::i()->update( 'core_search_index', $indexData, array( 'index_class=? and index_object_id=?', $indexData['index_class'], $indexData['index_object_id'] ) );
$newIndexId = $existingIndexId;
}
else
{
if ( $existingData !== NULL )
{
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 0 ), array( 'index_class=? AND index_item_id=? AND index_id<>?', $indexData['index_class'], $indexData['index_item_id'], $newIndexId ) );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? and index_object_id=?', $indexData['index_class'], $indexData['index_object_id'] ) );
}
try
{
$newIndexId = \IPS\Db::i()->insert( 'core_search_index', $indexData );
}
catch( \IPS\Db\Exception $e )
{
if ( $e->getCode() == 1062 )
{
/* Duplicate key which could be caused by a race condition on rebuild. Use replace in this case, as it is more expensive than an insert, so we only use it when we have to */
$newIndexId = \IPS\Db::i()->replace( 'core_search_index', $indexData );
}
}
}
/* If that was successful... */
if ( $newIndexId )
{
/* Remove existing tags */
if ( $existingIndexId )
{
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id=?', $existingIndexId ) );
}
/* Add add them back if we have any */
if ( count( $tags ) )
{
foreach( $tags as $tag )
{
\IPS\Db::i()->replace( 'core_search_index_tags', array( 'index_id' => $newIndexId, 'index_tag' => $tag, 'index_is_prefix' => ( $tag == $prefix ) ) );
}
}
/* Populate the map table, we always populate it under the item class regardless */
if ( $existingData == NULL or ( $existingData !== NULL and ( $existingData['index_author'] != $indexData['index_author'] OR $existingData['index_item_id'] != $indexData['index_item_id'] ) ) )
{
\IPS\Db::i()->replace( 'core_search_index_item_map', array( 'index_author_id' => $indexData['index_author'], 'index_item_id' => $indexData['index_item_id'], 'index_class' => ( $object instanceof \IPS\Content\Comment ? $object::$itemClass : $class ) ) );
}
$databaseColumnId = $object::$databaseColumnId;
/* Set index_item_index_id on other index items */
if ( $existingIndexId != $newIndexId )
{
if ( $object instanceof \IPS\Content\Item )
{
$subClasses = array( $class );
if ( isset( $class::$commentClass ) )
{
$subClasses[] = $class::$commentClass;
}
if ( isset( $class::$reviewClass ) )
{
$subClasses[] = $class::$reviewClass;
}
\IPS\Db::i()->update( 'core_search_index', array( 'index_item_index_id' => $newIndexId ), array( array( \IPS\Db::i()->in( 'index_class', $subClasses ) ), array( 'index_item_id=?', $object->$databaseColumnId ) ) );
}
elseif ( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
if ( $itemClass::$firstCommentRequired and $object->isFirst() )
{
$itemColumnId = $class::$databaseColumnMap['item'];
\IPS\Db::i()->update( 'core_search_index', array( 'index_item_index_id' => $newIndexId ), array( \IPS\Db::i()->in( 'index_class', array( $class, $class::$itemClass ) ) . ' AND index_item_id=?', $object->$itemColumnId ) );
}
}
/* And also set core_follow.follow_index_id */
if ( $object instanceof \IPS\Content\Comment )
{
$class = $class::$itemClass;
}
\IPS\Db::i()->update( 'core_follow', array( 'follow_index_id' => $newIndexId ), array( 'follow_app=? AND follow_area=? AND follow_rel_id=?', $object::$application, mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) ), $object->$databaseColumnId ) );
}
}
if ( $resetLastComment )
{
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'] );
}
}
}
@@ -118,22 +229,93 @@ class _Index extends \IPS\Content\Search\Index
{
$class = get_class( $object );
$idColumn = $class::$databaseColumnId;
/* Tags */
$this->_deleteTagsFromIndex( $class, $object->$idColumn );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? AND index_object_id=?', $class, $object->$idColumn ) );
/* If this was a comment, we really need to reset the index_is_last_comment flag if it was set */
if ( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
$classes = array( $class );
if ( ! $itemClass::$firstCommentRequired )
{
$classes[] = $itemClass;
}
if ( isset( $itemClass::$reviewClass ) )
{
$classes[] = $itemClass::$reviewClass;
}
try
{
$this->resetLastComment( $classes, $object->mapped('item') );
}
catch( \Exception $ex ) { }
/* We need to see if this is the only comment the author has in this item and if so, remove their map */
if ( ! \IPS\Db::i()->select( 'COUNT(*)', 'core_search_index', array( \IPS\Db::i()->in('index_class', $classes ) . ' and index_item_id=? and index_author=?', $object->mapped('item'), (int) $object->mapped('author') ) )->first() )
{
try
{
\IPS\Db::i()->delete( 'core_search_index_item_map', array( 'index_class=? AND index_item_id=? and index_author_id=?', $itemClass, $object->mapped('item'), (int) $object->mapped('author') ) );
}
catch( \Exception $ex ) { }
}
}
else if ( $object instanceof \IPS\Content\Item )
{
/* Just remove all rows matching the item and class. */
\IPS\Db::i()->delete( 'core_search_index_item_map', array( 'index_class=? AND index_item_id=?', $class, $object->$idColumn ) );
}
if ( isset( $class::$commentClass ) )
{
$commentClass = $class::$commentClass;
$this->_deleteTagsFromIndex( $commentClass, $object->$idColumn );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? AND index_item_id=?', $commentClass, $object->$idColumn ) );
}
if ( isset( $class::$reviewClass ) )
{
$reviewClass = $class::$reviewClass;
$this->_deleteTagsFromIndex( $reviewClass, $object->$idColumn );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? AND index_item_id=?', $reviewClass, $object->$idColumn ) );
}
}
/**
* Return the index IDs associated with this class and $id
*
* @param \IPS\Content object $class The class
* @param int $id The index_item_id
* @return array
*/
protected function _deleteTagsFromIndex( $class, $id )
{
try
{
$ids = iterator_to_array( \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_class=? AND index_item_id=?', $class, $id ) ) );
}
catch( \Exception $ex )
{
$ids = FALSE;
}
if ( is_array( $ids ) and count( $ids ) < 1000 )
{
\IPS\Db::i()->delete( 'core_search_index_tags', array( \IPS\Db::i()->in( 'index_id', $ids ) ) );
}
else
{
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id IN( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_class=? AND index_item_id=?', $class, $id ) ) ) );
}
}
/**
* Removes all content for a classs
*
@@ -149,24 +331,27 @@ class _Index extends \IPS\Content\Search\Index
$where[] = array( 'index_container_id=?', $containerId );
}
\IPS\Db::i()->delete( 'core_search_index_item_map', array( 'index_item_id IN( ? )', \IPS\Db::i()->select( 'index_item_id', 'core_search_index', $where ) ) );
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id IN( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index', $where ) ) );
\IPS\Db::i()->delete( 'core_search_index', $where );
}
/**
* Mass Update (when permissions change, for example)
*
* @param string $class The class
* @param int|NULL $containerId The container ID to update, or NULL
* @param int|NULL $itemId The item ID to update, or NULL
* @param string|NULL $newPermissions New permissions (if applicable)
* @param int|NULL $newHiddenStatus New hidden status (if applicable) special value 2 can be used to indicate hidden only by parent
* @param int|NULL $newContainer New container ID (if applicable)
* @param int|NULL $authorId The author ID to update, or NULL
* @param int|NULL $newItemId The new item ID (if applicable)
* @param int|NULL $newItemAuthorId The new item author ID (if applicable)
* @param string $class The class
* @param int|NULL $containerId The container ID to update, or NULL
* @param int|NULL $itemId The item ID to update, or NULL
* @param string|NULL $newPermissions New permissions (if applicable)
* @param int|NULL $newHiddenStatus New hidden status (if applicable) special value 2 can be used to indicate hidden only by parent
* @param int|NULL $newContainer New container ID (if applicable)
* @param int|NULL $authorId The author ID to update, or NULL
* @param int|NULL $newItemId The new item ID (if applicable)
* @param int|NULL $newItemAuthorId The new item author ID (if applicable)
* @param bool $addAuthorToPermissions If true, the index_author_id will be added to $newPermissions - used when changing the permissions for a node which allows access only to author's items
* @return void
*/
public function massUpdate( $class, $containerId = NULL, $itemId = NULL, $newPermissions = NULL, $newHiddenStatus = NULL, $newContainer = NULL, $authorId = NULL, $newItemId = NULL, $newItemAuthorId = NULL )
public function massUpdate( $class, $containerId = NULL, $itemId = NULL, $newPermissions = NULL, $newHiddenStatus = NULL, $newContainer = NULL, $authorId = NULL, $newItemId = NULL, $newItemAuthorId = NULL, $addAuthorToPermissions = FALSE )
{
$where = array( array( 'index_class=?', $class ) );
if ( $containerId !== NULL )
@@ -204,6 +389,12 @@ class _Index extends \IPS\Content\Search\Index
{
\IPS\Db::i()->update( 'core_search_index', $update, $where );
}
if ( $addAuthorToPermissions )
{
$addAuthorToPermissionsWhere = $where;
$addAuthorToPermissionsWhere[] = array( 'index_author<>0' );
\IPS\Db::i()->update( 'core_search_index', "index_permissions = CONCAT( index_permissions, ',m.', index_author )", $addAuthorToPermissionsWhere );
}
if ( $newHiddenStatus !== NULL )
{
@@ -239,7 +430,6 @@ class _Index extends \IPS\Content\Search\Index
if ( $item::$firstCommentRequired )
{
$update['index_title'] = NULL;
$update['index_tags'] = NULL;
}
\IPS\Db::i()->update( 'core_search_index', $update, array( 'index_class=? AND index_item_id=?', $item::$commentClass, $item->$idColumn ) );
@@ -264,11 +454,37 @@ class _Index extends \IPS\Content\Search\Index
{
if ( $cutoff )
{
\IPS\DB::i()->delete( 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) );
\IPS\Db::i()->delete( 'core_search_index_item_map', array( 'index_item_id IN( ? )', \IPS\Db::i()->select( 'index_item_id', 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) ) ) );
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id IN( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) ) ) );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) );
}
else
{
\IPS\DB::i()->delete( 'core_search_index' );
\IPS\Db::i()->delete( 'core_search_index_item_map' );
\IPS\Db::i()->delete( 'core_search_index_tags' );
\IPS\Db::i()->delete( 'core_search_index' );
}
}
/**
* Reset the last comment flag in any given class/index_item_id
*
* @param array $classes The classes (when first post is required, this is typically just \IPS\forums\Topic\Post but for others, it will be both item and comment classes)
* @param int|NULL $indexItemId The index item ID
* @return void
*/
public function resetLastComment( $classes, $indexItemId )
{
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 0 ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=?', $indexItemId ) );
try
{
$latest = \IPS\Db::i()->select( 'index_object_id, index_date_updated, index_class', 'core_search_index', array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? and index_hidden=0', $indexItemId ), 'index_date_created DESC', array( 0, 1 ) )->first();
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 1 ), array( 'index_class=? AND index_object_id=?', $latest['index_class'], $latest['index_object_id'] ) );
/* Now reset the item index with the latest comment time */
\IPS\Db::i()->update( 'core_search_index', array( 'index_date_updated' => $latest['index_date_updated'] ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? AND index_object_id=?', $indexItemId, $indexItemId ) );
}
catch( \Exception $ex ) { }
}
}