Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+49 -14
View File
@@ -23,6 +23,16 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _Application extends \IPS\Application
{
/**
* [Node] Get Node Description
*
* @return string|null
*/
protected function get__description()
{
return NULL;
}
/**
* [Node] Get Icon for tree
*
@@ -34,6 +44,32 @@ class _Application extends \IPS\Application
return 'random';
}
/**
* [Node] Get whether or not this node is locked to current enabled/disabled status
*
* @note Return value NULL indicates the node cannot be enabled/disabled
* @return bool|null
*/
protected function get__locked()
{
/* We don't allow the application to be disabled since its hooks are required for redirects */
return TRUE;
}
public function getButtons( $url, $subnode=FALSE )
{
$buttons = parent::getButtons( $url, $subnode );
if( isset( $buttons['delete'] ) )
{
$buttons['delete']['title'] = 'uninstall';
$buttons['delete']['data']['delete-message'] = \IPS\Member::loggedIn()->language()->addToStack('converter_uninstall');
$buttons['delete']['data']['delete-warning'] = \IPS\Member::loggedIn()->language()->addToStack('converter_uninstall_warning');
}
return $buttons;
}
/**
* Install Other
*
@@ -45,23 +81,22 @@ class _Application extends \IPS\Application
try
{
\IPS\Db::i()->select( '*', 'core_login_handlers', array( "login_key=?", 'Convert' ) )->first();
\IPS\Db::i()->select( '*', 'core_login_methods', array( "login_classname=?", 'IPS\\convert\\Login' ) )->first();
}
catch( \UnderflowException $e )
{
$position = \IPS\Db::i()->select( 'MAX(login_order)', 'core_login_handlers' )->first();
\IPS\Db::i()->insert( 'core_login_handlers', array(
'login_key' => "Convert",
'login_enabled' => 1,
'login_settings' => json_encode( array( 'auth_types' => \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL ) ),
'login_order' => $position + 1,
'login_acp' => 1
) );
if ( isset( \IPS\Data\Store::i()->loginHandlers ) )
{
unset( \IPS\Data\Store::i()->loginHandlers );
}
$position = \IPS\Db::i()->select( 'MAX(login_order)', 'core_login_methods' )->first();
$handler = new \IPS\convert\Login;
$handler->classname = 'IPS\\convert\\Login';
$handler->order = $position + 1;
$handler->acp = TRUE;
$handler->settings = array( 'auth_types' => \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL );
$handler->enabled = TRUE;
$handler->register = FALSE;
$handler->save();
\IPS\Lang::saveCustom( 'core', "login_method_{$handler->id}", 'Converter' );
}
}
+49 -4
View File
@@ -27,6 +27,7 @@ class _converters extends \IPS\Api\Controller
* GET /convert/converters
* Get list of converter applications and what can be converted from each application
*
* @apiclientonly
* @apiparam string applications Comma-delimited list of applications to filter by
* @return array
*/
@@ -54,7 +55,7 @@ class _converters extends \IPS\Api\Controller
foreach( new \DirectoryIterator( $directory->getPathname() ) as $softwares )
{
/* Skip ../ and any non-files */
if( $softwares->isDot() OR !$softwares->isFile() )
if( $softwares->isDot() OR !$softwares->isFile() OR mb_substr( $softwares->getFilename(), -3 ) !== 'php' )
{
continue;
}
@@ -70,6 +71,12 @@ class _converters extends \IPS\Api\Controller
continue;
}
/* A work around for an odd-case */
if( mb_strtolower( $namespace ) == 'xenfororm' )
{
$namespace = 'xenforo';
}
/* Initialize this application's array if necessary */
if( !isset( $response[ mb_strtolower( $namespace ) ] ) )
{
@@ -80,9 +87,25 @@ class _converters extends \IPS\Api\Controller
$rawConvert = array_keys( $classname::canConvert() );
$canConvert = array();
/* Get extra_steps */
$extraSteps = array();
foreach( $classname::canConvert() as $convert )
{
if( isset( $convert['extra_steps'] ) )
{
$extraSteps = array_merge( $extraSteps, $convert['extra_steps'] );
}
}
/* Get lang strings for what we can convert */
foreach( $rawConvert as $convertType )
{
/* Skip extra steps */
if( in_array( $convertType, $extraSteps ) )
{
continue;
}
$canConvert[] = \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->addToStack(
preg_replace_callback( "/([A-Z])/", function( $match ){
return "_" . mb_strtolower( $match[1] );
@@ -90,15 +113,37 @@ class _converters extends \IPS\Api\Controller
);
}
/* Reflect the class to figure out if redirects are enabled */
$reflect = new \ReflectionMethod( $classname, 'checkRedirects' );
/* Now store the data for this application that we need */
$response[ mb_strtolower( $namespace ) ][ mb_strtolower( $application ) ] = array(
$response[ mb_strtolower( $namespace ) ][ mb_strtolower( $application ) ][] = array(
'name' => $classname::softwareName(),
'key' => $classname::softwareKey(),
'canConvert' => $canConvert
'canConvert' => $canConvert,
'redirections' => ( str_replace( '\_', '\\', $reflect->getDeclaringClass()->getName() ) === mb_substr( $classname, 1 ) )
);
if( mb_strtolower( $application ) == 'core' )
{
$response[ mb_strtolower( $namespace ) ]['@name'] = $classname::softwareName();
}
}
}
/* Make sure a name is defined */
foreach( $response as $software => $data )
{
if( !isset( $data['@name'] ) )
{
$first = array_shift( $data );
$response[ $software ]['@name'] = $first[0]['name'];
}
}
/* Sort */
ksort( $response );
return new \IPS\Api\Response( 200, $response );
}
}
+6 -6
View File
@@ -1,14 +1,14 @@
{
"Db": {
"type": "C",
"class": "\\IPS\\Db"
},
"FrontDispatcher": {
"type": "C",
"class": "\\IPS\\Dispatcher\\Front"
},
"FileSystem": {
"Db": {
"type": "C",
"class": "\\IPS\\File\\FileSystem"
"class": "\\IPS\\Db"
},
"LoginHandler": {
"type": "C",
"class": "\\IPS\\Login\\Handler"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<javascript app="convert">
<file javascript_app="convert" javascript_location="admin" javascript_path="controllers/convert" javascript_name="ips.convert.menu.js" javascript_type="controller" javascript_version="102002" javascript_position="1000050">/**
<file javascript_app="convert" javascript_location="admin" javascript_path="controllers/convert" javascript_name="ips.convert.menu.js" javascript_type="controller" javascript_version="103005" javascript_position="1000050">/**
* Invision Community
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
*
+30 -2
View File
@@ -1,14 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<language>
<app key="convert" version="102002">
<app key="convert" version="103005">
<word key="__app_convert" js="0">Converter</word>
<word key="menu__convert_manage" js="0">Conversions</word>
<word key="menu__convert_manage_manage_start" js="0">Start New Conversion</word>
<word key="menu__convert_manage_manage" js="0">Manage Conversions</word>
<word key="converter_uninstall" js="0">Uninstall Converter</word>
<word key="converter_uninstall_warning" js="0">Uninstalling this application will prevent the URL redirections from your old platform working. This action is irreversible.</word>
<word key="convert_sw" js="0">Application</word>
<word key="convert_app_key" js="0">Source Software</word>
<word key="convert_name" js="0">Identifier</word>
<word key="convert_parent" js="0">Parent Conversion</word>
<word key="convert_finished" js="0">Finished?</word>
<word key="convert_no_parent" js="0"><![CDATA[<em>No Parent</em>]]></word>
<word key="convert_start" js="0">Start New Conversion</word>
<word key="conversion_deleted" js="0">Conversion Deleted</word>
@@ -99,13 +102,22 @@
<word key="convert_xf_preconvert" js="0">You can typically obtain these details from the file located at /path/to/xenforo/library/config.php</word>
<word key="convert_coppermine_filehint" js="0">This is typically: /path/to/coppermine/albums</word>
<word key="convert_photoplog_hint" js="0">This is typically: /path/to/photoplog/images</word>
<word key="convert_invision_mismatch" js="0"><![CDATA[Both of your Invision Community installations <strong>must</strong> be using the same version, please upgrade them to the latest version available.]]></word>
<word key="convert_invision_upload_path" js="0">This is typically: /path/to/ips4/uploads</word>
<word key="convert_invision_upload_input" js="0">Upload Path</word>
<word key="convert_invision_upload_input_desc" js="0">Your source Invision Community must have all files stored in the default /uploads location</word>
<word key="convert_ubbthreads_photo_path" js="0">This is typically the absolute path to your domain root (do not include any subfolders if UBBThreads is in a subfolder)</word>
<word key="convert_photopost_image_path" js="0">This is typically: /path/to/photopost/data</word>
<word key="use_fluid_view_convert" js="0">Use fluid view for forums?</word>
<word key="reaction_convert_help" js="0">Choose which reaction to convert to</word>
<word key="rep_positive" js="0">Up Vote</word>
<word key="rep_negative" js="0">Down Vote</word>
<word key="rep_neutral" js="0">Neutral</word>
<word key="rep_like" js="0">Like</word>
<word key="nothing_to_finish" js="0">There was not anything that needed to be finished, the conversion may have already been finished.</word>
<word key="convert_mod_rewrite_urls" js="0"><![CDATA[You have not enabled the setting <a href='{internal.app=core&module=promotion&controller=seo}'>Rewrite Urls</a> in Invision Community. If you do not enable this option, URLs may not correctly redirect to the new locations.]]></word>
<word key="convert_configure_permissions" js="0"><![CDATA[After your conversion is complete, you must configure permissions for your member groups. Go to the <a href='{internal.app=core&module=members&controller=groups}'>members group</a> page and click the 'padlock' icon next to each member group.]]></word>
<word key="conversion_app_finished" js="0">This conversion has been marked as completed.</word>
<word key="converter_supports_settings" js="0"><![CDATA[This converter supports translating your old settings into their Invision Community equivalents. If you would like to do that, please <a href='%s'>click here</a> before beginning.]]></word>
<word key="converting_settings" js="0">Converting Settings</word>
<word key="converting_settings_desc" js="0">The following settings will be converted to their Invision Community equivalents.</word>
@@ -137,7 +149,9 @@
<word key="emoticon_missing_code" js="0">Emoticon missing code</word>
<word key="emoticon_no_file" js="0">No emoticon file provided</word>
<word key="emoticon_no_filename" js="0">No emoticon filename provided</word>
<word key="convert_reactions" js="0">Reactions</word>
<word key="follow_failed_insert" js="0">Follow failed to insert</word>
<word key="convert_follows" js="0">Follows</word>
<word key="convert_profile_field_groups" js="0">Profile Field Groups</word>
<word key="profile_field_group_missing_ids" js="0">Profile Field Group missing ID</word>
<word key="profile_field_group_missing_name" js="0">Profile Field Group missing name</word>
@@ -151,7 +165,7 @@
<word key="photo_type" js="0">Photos to Retain</word>
<word key="photo_type_desc" js="0">This software supports multiple types of images to represent an account. Only one of these can be retained, however. If a photo of the selected type does not exist for a member, than the other will attempt to be used instead.</word>
<word key="photo_location" js="0">Location of Photos</word>
<word key="photo_location_desc" js="0">If member photos are presently stored in the database, then select 'Database', otherwise select 'File System' and then enter the path to profile photos in the textbox provided.</word>
<word key="photo_location_desc" js="0">If member photos are presently stored in the database, then select 'Database', otherwise select 'File System' and then enter the absolute path to profile photos in the textbox provided.</word>
<word key="photo_location_nodb_desc" js="0">The path to profile photos.</word>
<word key="photo_hash" js="0">Avatar Hash</word>
<word key="gallery_location" js="0">Gallery Location</word>
@@ -168,6 +182,7 @@
<word key="member_name_in_use" js="0">Name '%s' is currently in use, member renamed to '%s'</word>
<word key="member_name_too_long" js="0">Name '%s' is too long, member renamed to '%s'</word>
<word key="member_name_invalid_chars" js="0">Name '%s' contained invalid characters, member renamed to '%s'</word>
<word key="member_missing_name" js="0">Members name missing, member renamed to %s</word>
<word key="username_or_display_name" js="0">Name to keep</word>
<word key="user_name" js="0">User Name</word>
<word key="display_name" js="0">Display Name</word>
@@ -205,23 +220,32 @@
<word key="rank_missing_title" js="0">Rank Missing Title</word>
<word key="rank_location" js="0">Rank Image Location</word>
<word key="core_rating_duplicate" js="0">Duplicate Rating</word>
<word key="convert_ratings" js="0">Ratings</word>
<word key="convert_profanity_filters" js="0">Profanity Filters</word>
<word key="convert_banfilters" js="0">Ban Filters</word>
<word key="convert_banfilters2" js="0">Ban Filters</word>
<word key="convert_question_and_answers" js="0">Question and Answer Verification</word>
<word key="convert_reputation_levels" js="0">Reputation Levels</word>
<word key="convert_warn_actions" js="0">Warning Actions</word>
<word key="convert_warn_reasons" js="0">Warning Reasons</word>
<word key="convert_moderators" js="0">Moderators</word>
<word key="convert_forums_forums" js="0">Forums</word>
<word key="convert_forums_forums_followers" js="0">Forum followers</word>
<word key="convert_forum_password" js="0">Password for %s</word>
<word key="convert_forum_password_desc" js="0"><![CDATA[This forum has a password set, however we are unable to decrypt the password for conversion into Invision Community. Please enter the password you wish to use for this forum. <strong>Note:</strong> Not entering a password here will result in no password being set for the forum.]]></word>
<word key="convert_forums_topics_multimods" js="0">Multi-moderation Settings</word>
<word key="convert_forums_question_ratings" js="0">Question Ratings</word>
<word key="convert_forums_answer_ratings" js="0">Answer Ratings</word>
<word key="convert_forums_topics" js="0">Topics</word>
<word key="convert_forums_topics_followers" js="0">Topic followers</word>
<word key="convert_forums_posts" js="0">Posts</word>
<word key="convert_forums_archive_posts" js="0">Archived Posts</word>
<word key="convert_forums_rss_imports" js="0">RSS Feeds</word>
<word key="convert_forums_rss_imported" js="0">Imported RSS Content</word>
<word key="convert_attachments" js="0">Attachments</word>
<word key="convert_forums_attachments" js="0">Post Attachments</word>
<word key="attach_location" js="0">Path to attachments</word>
<word key="convert_acronyms" js="0">Acronyms</word>
<word key="convert_blogs" js="0">Blogs</word>
<word key="convert_blog_entries" js="0">Blog Entries</word>
<word key="convert_blog_comments" js="0">Entry Comments</word>
@@ -235,6 +259,8 @@
<word key="convert_calendar_reminders" js="0">Event Reminders</word>
<word key="convert_gallery_categories" js="0">Categories</word>
<word key="convert_gallery_albums" js="0">Albums</word>
<word key="convert_gallery_album_comments" js="0">Album Comments</word>
<word key="convert_gallery_album_reviews" js="0">Album Reviews</word>
<word key="convert_gallery_images" js="0">Images</word>
<word key="convert_gallery_comments" js="0">Image Comments</word>
<word key="convert_gallery_reviews" js="0">Image Reviews</word>
@@ -254,6 +280,7 @@
<word key="convert_cms_database_categories" js="0">Database Categories</word>
<word key="convert_cms_database_records" js="0">Database Records</word>
<word key="convert_cms_database_comments" js="0">Database Comments</word>
<word key="convert_cms_database_reviews" js="0">Database Reviews</word>
<word key="convert_cms_media" js="0">Media</word>
<word key="convert_cms_blocks" js="0">Blocks</word>
<word key="convert_cms_attachments" js="0">Attachments</word>
@@ -279,6 +306,7 @@
<word key="queue_rebuilding_tag_cache" js="0">Rebuilding %s tags</word>
<word key="queue_recounting_question_ratings" js="0">Rebuilding Question Ratings</word>
<word key="queue_recounting_answer_ratings" js="0">Rebuilding Answer Ratings</word>
<word key="queue_recounting_club_members" js="0">Recounting Club Members</word>
<word key="continue_conversion" js="0">Continue Conversion</word>
<word key="convert_nexus_customers" js="0">Customers</word>
<word key="convert_nexus_package_groups" js="0">Package Groups</word>
+14
View File
@@ -225,6 +225,20 @@
"zerofill": false,
"auto_increment": false,
"binary": false
},
"finished": {
"name": "finished",
"type": "BIT",
"length": 1,
"decimals": null,
"values": null,
"allow_null": true,
"default": "0",
"comment": "Has app been finished?",
"unsigned": false,
"zerofill": false,
"auto_increment": false,
"binary": false
}
},
"indexes": {
+52 -1
View File
@@ -73,5 +73,56 @@
"101117": "4.2.0 Beta 8",
"102000": "4.2.0",
"102001": "4.2.0",
"102002": "4.2.0"
"102002": "4.2.0",
"102003": "4.2.0",
"102004": "4.2.1",
"102005": "4.2.2 Beta 1",
"102006": "4.2.2 Beta 2",
"102007": "4.2.2",
"102008": "4.2.2",
"102009": "4.2.2",
"102010": "4.2.3 Beta 1",
"102011": "4.2.3",
"102012": "4.2.3",
"102013": "4.2.3",
"102014": "4.2.4 Beta 1",
"102015": "4.2.4 Beta 2",
"102016": "4.2.4",
"102017": "4.2.4",
"102018": "4.2.4",
"102019": "4.2.4",
"102020": "4.2.4",
"102021": "4.2.5 Beta 1",
"102022": "4.2.5 Beta 2",
"102023": "4.2.5 Beta 3",
"102024": "4.2.5",
"102025": "4.2.5",
"102026": "4.2.5",
"102027": "4.2.5",
"102028": "4.2.5",
"102029": "4.2.5",
"102030": "4.2.6 Beta 1",
"102031": "4.2.6 Beta 1",
"102032": "4.2.6 Beta 2",
"102033": "4.2.6 Beta 3",
"102034": "4.2.6",
"102035": "4.2.6",
"102036": "4.2.6",
"102037": "4.2.7 Beta 1",
"102038": "4.2.7 Beta 2",
"102039": "4.2.7 Beta 3",
"102040": "4.2.7 Beta 4",
"102041": "4.2.7 Beta 5",
"102042": "4.2.7",
"102043": "4.2.7",
"102044": "4.2.7",
"102045": "4.2.7",
"102046": "4.2.7",
"102047": "4.2.8",
"102048": "4.2.8",
"103000": "4.3.0 Beta 1",
"103002": "4.3.0 Beta 2",
"103003": "4.3.0 Beta 3",
"103004": "4.3.0 Beta 4",
"103005": "4.3.0"
}
@@ -0,0 +1,346 @@
<?php
/**
* @brief Background Task
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Converter
* @since 08 Aug 2017
*/
namespace IPS\convert\extensions\core\Queue;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Background Task
*/
class _InvisionCommunityRebuildContent
{
/**
* @brief Number of content items to rebuild per cycle
*/
public $rebuild = 50;
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
$classname = $data['class'];
\IPS\Log::debug( "Getting preQueueData for " . $classname, 'ICrebuildPosts' );
try
{
$data['count'] = $classname::db()->select( 'MAX(' . $classname::$databasePrefix . $classname::$databaseColumnId . ')', $classname::$databaseTable, ( is_subclass_of( $classname, 'IPS\Content\Comment' ) ) ? $classname::commentWhere() : array() )->first();
$data['realCount'] = $classname::db()->select( 'COUNT(*)', $classname::$databaseTable, ( is_subclass_of( $classname, 'IPS\Content\Comment' ) ) ? $classname::commentWhere() : array() )->first();
/* We're going to use the < operator, so we need to ensure the most recent item is rebuilt */
$data['runPid'] = $data['count'] + 1;
}
catch( \Exception $ex )
{
throw new \OutOfRangeException;
}
\IPS\Log::debug( "PreQueue count for " . $classname . " is " . $data['count'], 'ICrebuildPosts' );
if( $data['count'] == 0 )
{
return null;
}
$data['indexed'] = 0;
return $data;
}
/**
* Run Background Task
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return int|null New offset or NULL if complete
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function run( &$data, $offset )
{
$classname = $data['class'];
$exploded = explode( '\\', $classname );
if ( !class_exists( $classname ) or !\IPS\Application::appIsEnabled( $exploded[1] ) )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
/* Make sure there's even content to parse */
if( !isset( $classname::$databaseColumnMap['content'] ) )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
/* Intentionally no try/catch as it means app doesn't exist */
try
{
$this->app = \IPS\convert\App::load( $data['app'] );
/* This extension is ONLY for InvisionCommunity conversions */
if( $this->app->app_key != 'invisioncommunity' )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
}
catch( \OutOfRangeException $e )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
$softwareClass = $this->app->getSource( FALSE, FALSE );
\IPS\Log::debug( "Running " . $classname . ", with an offset of " . $offset, 'ICrebuildPosts' );
$where = ( is_subclass_of( $classname, 'IPS\Content\Comment' ) ) ? ( is_array( $classname::commentWhere() ) ? array( $classname::commentWhere() ) : array() ) : array();
$select = $classname::db()->select( '*', $classname::$databaseTable, array_merge( $where, array( array( $classname::$databasePrefix . $classname::$databaseColumnId . ' < ?', $data['runPid'] ) ) ), $classname::$databasePrefix . $classname::$databaseColumnId . ' DESC', array( 0, $this->rebuild ) );
$iterator = new \IPS\Patterns\ActiveRecordIterator( $select, $classname );
$last = NULL;
foreach( $iterator as $item )
{
$idColumn = $classname::$databaseColumnId;
/* Is this converted content? */
try
{
/* Just checking, we don't actually need anything */
$this->app->checkLink( $item->$idColumn, $data['link'] );
}
catch( \OutOfRangeException $e )
{
$last = $item->$idColumn;
$data['indexed']++;
continue;
}
$contentColumn = $classname::$databaseColumnMap['content'];
$source = new \IPS\Xml\DOMDocument( '1.0', 'UTF-8' );
$source->loadHTML( \IPS\Xml\DOMDocument::wrapHtml( $item->$contentColumn ) );
if( mb_stristr( $item->$contentColumn, 'data-mentionid' ) )
{
/* Get mentions */
$mentions = $source->getElementsByTagName( 'a' );
foreach( $mentions as $element )
{
if( $element->hasAttribute( 'data-mentionid' ) )
{
$this->updateMention( $element );
}
}
}
/* embeds */
if( mb_stristr( $item->$contentColumn, 'data-embedcontent' ) )
{
/* Get mentions */
$embeds = $source->getElementsByTagName( 'iframe' );
foreach( $embeds as $element )
{
if( $element->hasAttribute( 'data-embedcontent' ) )
{
$this->updateEmbed( $element );
}
}
}
/* quotes */
if( mb_stristr( $item->$contentColumn, 'data-ipsquote' ) )
{
/* Get mentions */
$quotes = $source->getElementsByTagName( 'blockquote' );
foreach( $quotes as $element )
{
if( $element->hasAttribute( 'data-ipsquote' ) )
{
$this->updateQuote( $element );
}
}
}
/* Get DOMDocument output */
$content = \IPS\Text\DOMParser::getDocumentBodyContents( $source );
/* Replace file storage tags */
$content = preg_replace( '/&lt;fileStore\.([\d\w\_]+?)&gt;/i', '<fileStore.$1>', $content );
/* DOMDocument::saveHTML will encode the base_url brackets, so we need to make sure it's in the expected format. */
$item->$contentColumn = str_replace( '&lt;___base_url___&gt;', '<___base_url___>', $content );
$item->save();
$last = $item->$idColumn;
$data['indexed']++;
}
/* Store the runPid for the next iteration of this Queue task. This allows the progress bar to show correctly. */
$data['runPid'] = $last;
if( $last === NULL )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
/* Return the number rebuilt so far, so that the rebuild progress bar text makes sense */
return $data['indexed'];
}
/**
* Get Progress
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
* @throws \OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function getProgress( $data, $offset )
{
$class = $data['class'];
$exploded = explode( '\\', $class );
if ( !class_exists( $class ) or !\IPS\Application::appIsEnabled( $exploded[1] ) )
{
throw new \OutOfRangeException;
}
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack('rebuilding_stuff', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl', FALSE, array( 'strtolower' => TRUE ) ) ) ) ), 'complete' => $data['realCount'] ? ( round( 100 / $data['realCount'] * $data['indexed'], 2 ) ) : 100 );
}
/**
* Update mentions with new display name, ID and URL
*
* @param \DOMElement $element
* @return void
*/
public function updateMention( \DOMElement $element )
{
try
{
/* Get new member ID */
$newMemberId = $this->app->getLink( $element->getAttribute( 'data-mentionid' ), 'core_members' );
/* Get new member */
$member = \IPS\Member::load( $newMemberId );
$element->setAttribute( 'data-mentionid', $newMemberId );
$element->setAttribute( 'href', str_replace( \IPS\Settings::i()->base_url, '<___base_url___>/', $member->url() ) );
$element->setAttribute( 'data-ipshover-target', str_replace( \IPS\Settings::i()->base_url, '<___base_url___>/', $member->url()->setQueryString( 'do', 'hovercard' ) ) );
$element->nodeValue = '@' . $member->name;
}
catch( \Exception $e ) {}
}
/**
* @brief Mapping of content types to converter lookups - Add more for other apps when we support them
*/
protected $embedLocations = array( 'forums' => array( 'content' => 'forums_topics', 'comment' => 'forums_posts' ) );
/**
* Update local embeds for new names, IDs
*
* @param \DOMElement $element
* @return void
*/
public function updateEmbed( \DOMElement $element )
{
try
{
$url = \IPS\Http\Url::createFromString( str_replace( '<___base_url___>', rtrim( \IPS\Settings::i()->base_url, '/' ), $element->getAttribute( 'src' ) ) );
if( !in_array( $url->hiddenQueryString['app'], array_keys( $this->embedLocations ) ) )
{
return;
}
$url->hiddenQueryString['id'] = $this->app->getLink( $url->hiddenQueryString['id'], $this->embedLocations[ $url->hiddenQueryString['app'] ]['content'] );
try
{
if( isset( $url->queryString['comment'] ) )
{
$url = $url->setQueryString( 'comment', $this->app->getLink( $url->queryString['comment'], $this->embedLocations[ $url->hiddenQueryString['app'] ]['comment'] ) );
}
}
catch( \OutOfRangeException $e )
{
$url = $url->stripQueryString( 'comment' );
}
try
{
if( isset( $url->queryString['embedComment'] ) )
{
$url = $url->setQueryString( 'embedComment', $this->app->getLink( $url->queryString['embedComment'], $this->embedLocations[ $url->hiddenQueryString['app'] ]['comment'] ) );
}
}
catch( \OutOfRangeException $e )
{
$url = $url->stripQueryString( 'embedComment' );
}
$element->setAttribute( 'src', str_replace( \IPS\Settings::i()->base_url, '<___base_url___>/', (string) $url->correctFriendlyUrl() ) );
}
catch( \Exception $e ) {}
}
/**
* Update quotes for new names, IDs
*
* @param \DOMElement $element
* @return void
*/
public function updateQuote( \DOMElement $element )
{
try
{
/* Lookup the memnbers new ID */
$newMemberId = $this->app->getLink( $element->getAttribute( 'data-ipsquote-userid' ), 'core_members' );
/* Get new member */
$member = \IPS\Member::load( $newMemberId );
/* Get old username */
$oldUsername = $element->getAttribute( 'data-ipsquote-username' );
$element->setAttribute( 'data-ipsquote-username', $member->name );
$element->setAttribute( 'data-ipsquote-userid', $member->member_id );
/* Is this forums? */
if( $element->hasAttribute( 'data-ipsquote-contentapp' ) AND $element->hasAttribute( 'data-ipsquote-contentapp' ) == 'forums' )
{
$element->setAttribute( 'data-ipsquote-contentcommentid', $this->app->getLink( $element->getAttribute( 'data-ipsquote-contentcommentid' ), 'forums_posts' ) );
$element->setAttribute( 'data-ipsquote-contentid', $this->app->getLink( $element->getAttribute( 'data-ipsquote-contentid' ), 'forums_topics' ) );
}
/* find the citation to update the username */
foreach ( $element->childNodes as $child )
{
if ( $child instanceof \DOMElement and $child->getAttribute('class') == 'ipsQuote_citation' )
{
$child->nodeValue = str_replace( $oldUsername, $member->name, $child->nodeValue );
}
}
}
catch( \Exception $e ) {}
}
}
@@ -149,11 +149,11 @@ class _RebuildContent
if( isset( $classname::$itemClass ) )
{
$itemClass = $classname::$itemClass;
$module = ucfirst( $itemClass::$module );
$module = mb_ucfirst( $itemClass::$module );
}
else
{
$module = ucfirst( $classname::$module );
$module = mb_ucfirst( $classname::$module );
}
$extension = NULL;
@@ -220,7 +220,7 @@ class _RebuildContent
}
/* Then pass off to legacy parser to bring up to 4.x */
$item->$contentColumn = \IPS\Text\LegacyParser::parseStatic( $item->$contentColumn, $member, $canUseHtml, $classname::$application . '_' . ucfirst( $module ), $id1, $id2, NULL, isset( $classname::$itemClass ) ? $classname::$itemClass : get_class( $item ) );
$item->$contentColumn = \IPS\Text\LegacyParser::parseStatic( $item->$contentColumn, $member, $canUseHtml, $classname::$application . '_' . mb_ucfirst( $module ), $id1, $id2, NULL, isset( $classname::$itemClass ) ? $classname::$itemClass : get_class( $item ) );
}
catch( \InvalidArgumentException $e )
{
@@ -63,10 +63,32 @@ class _RebuildGalleryImages
throw new \IPS\Task\Queue\OutOfRangeException;
}
/* Intentionally no try/catch as it means app doesn't exist */
try
{
$app = \IPS\convert\App::load( $data['app'] );
}
catch( \OutOfRangeException $e )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
$last = NULL;
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'gallery_images', array( "image_id>?", $offset ), 'image_id ASC', array( 0, 10 ) ), 'IPS\gallery\Image' ) AS $image )
{
/* Is this converted content? */
try
{
/* Just checking, we don't actually need anything */
$app->checkLink( $image->id, 'gallery_images' );
}
catch( \OutOfRangeException $e )
{
$last = $image->id;
continue;
}
try
{
$image->buildThumbnails();
@@ -82,12 +82,10 @@ class _RebuildNonContent
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
$softwareClass = $app->getSource( TRUE, FALSE );
if( method_exists( $extension, 'rebuildContent' ) )
{
$did = $extension->rebuildContent( $offset, $softwareClass::fixPostData( $this->rebuild ) );
$did = $extension->rebuildContent( $offset, $this->rebuild );
}
else
{
@@ -56,6 +56,8 @@ class _RecountAnswerRatings
throw new \IPS\Task\Queue\OutOfRangeException;
}
$last = NULL;
/* Intentionally no try/catch as it means app doesn't exist */
try
{
@@ -0,0 +1,82 @@
<?php
/**
* @brief Background Task
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Converter
* @since 26 July 2017
*/
namespace IPS\convert\extensions\core\Queue;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Background Task
*/
class _RecountClubMembers
{
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
$data['count'] = \IPS\Db::i()->select( 'count(id)', 'core_clubs' )->first();
if( $data['count'] == 0 )
{
return NULL;
}
return $data;
}
/**
* Run Background Task
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return int New offset
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function run( $data, $offset )
{
$last = NULL;
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_clubs', array( "id>?", $offset ), "id ASC", array( 0, 200 ) ), 'IPS\Member\Club' ) AS $club )
{
$club->recountMembers();
$last = $club->id;
}
if( $last === NULL )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
return $last;
}
/**
* Get Progress
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
* @throws \OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function getProgress( $data, $offset )
{
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack( 'queue_recounting_club_members' ), 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $offset, 2 ) ) : 100 );
}
}
@@ -56,6 +56,8 @@ class _RecountQuestionRatings
throw new \IPS\Task\Queue\OutOfRangeException;
}
$last = NULL;
/* Intentionally no try/catch as it means app doesn't exist */
try
{
-44
View File
@@ -1,44 +0,0 @@
//<?php
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
exit;
}
class convert_hook_FileSystem extends _HOOK_CLASS_
{
/**
* @brief Enable copy functionality.
*/
public static $converterCopy = FALSE;
/**
* Save File
*
* @return void
* @throws \RuntimeException
*/
public function save()
{
/* Save the file */
if( $this->temporaryFilePath AND static::$converterCopy )
{
/* Make the folder */
$folder = $this->configuration['dir'] . '/' . $this->getFolder();
if( !@\copy( $this->temporaryFilePath, "{$folder}/{$this->filename}" ) )
{
\IPS\Log::log( "Could not copy file from {$this->temporaryFilePath} to {$folder}/{$this->filename}" , 'FileSystem' );
throw new \RuntimeException( 'COULD_NOT_COPY_FILE' );
}
@chmod( "{$folder}/{$this->filename}", \IPS\IPS_FILE_PERMISSION );
/* Reset temporary file data */
$this->temporaryFilePath = NULL;
}
parent::save();
}
}
@@ -0,0 +1,20 @@
//<?php
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
exit;
}
abstract class convert_hook_LoginHandler extends _HOOK_CLASS_
{
/**
* Get all handler classes
*/
public static function handlerClasses()
{
$handlers = parent::handlerClasses();
$handlers[] = 'IPS\convert\Login';
return $handlers;
}
}
@@ -48,6 +48,11 @@ class _convert extends \IPS\Dispatcher\Controller
\IPS\Output::i()->error( 'conversion_app_not_found', '2V101/2', 404 );
}
if ( $app->finished )
{
\IPS\Output::i()->error( 'conversion_app_finished', '2V368/1', 403, '' );
}
$softwareClass = $app->getSource();
$libraryClass = $softwareClass->getLibrary();
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'converting_x_to_x', FALSE, array( 'sprintf' => array( $softwareClass::softwareName(), \IPS\Application::load( $app->sw )->_title ) ) );
@@ -56,6 +61,26 @@ class _convert extends \IPS\Dispatcher\Controller
try
{
$menuRows = $libraryClass->menuRows();
/* "Extra steps" should not show in the list of things to convert, but still need to be defined in menu rows */
$extraSteps = array();
foreach( $softwareClass::canConvert() as $row )
{
if( isset( $row['extra_steps'] ) )
{
$extraSteps = array_merge( $extraSteps, $row['extra_steps'] );
}
}
$menuRows = array_filter( $menuRows, function( $row ) use ( $extraSteps ) {
if( in_array( $row['step_method'], $extraSteps ) )
{
return FALSE;
}
return TRUE;
});
}
catch( \IPS\convert\Exception $e )
{
@@ -151,6 +176,48 @@ class _convert extends \IPS\Dispatcher\Controller
{
\IPS\Output::i()->error( 'conversion_app_not_found', '2V101/7', 404 );
}
/* If we decided to truncate, we need to do so before getMoreInfo() is generated */
if ( isset( \IPS\Request::i()->empty_local_data ) AND \IPS\Request::i()->empty_local_data == 1 )
{
$app->getSource()->getLibrary()->emptyLocalData( \IPS\Request::i()->method );
/* If this step has extra steps, we need to unset the completed flag. */
$canConvert = $app->getSource()->getLibrary()->getConvertableItems();
$session = $app->_session;
if ( isset( $canConvert[ \IPS\Request::i()->method ]['extra_steps'] ) )
{
$next = NULL;
foreach( $canConvert[ \IPS\Request::i()->method ]['extra_steps'] as $next )
{
if( $key = array_search( $next, $session['completed'] ) )
{
unset( $session['completed'][ $key ] );
}
}
}
/* Since we're emptying everything, remove the main step from the completed list
so that 'continue' functionality can be used on the next run. */
if( $key = array_search( \IPS\Request::i()->method, $session['completed'] ) )
{
unset( $session['completed'][ $key ] );
}
/* Set the new session data */
$app->_session = $session;
/* Reset finish flag if they're reconverting an area that uses rebuilds */
foreach( $app->getSource()->getLibrary()->menuRows() as $v )
{
if( isset( $v['requires_rebuild'] ) AND $v['requires_rebuild'] )
{
$app->finished = FALSE;
$app->save();
break;
}
}
}
/* Do we need more information? */
$softwareClass = $app->getSource();
@@ -164,17 +231,12 @@ class _convert extends \IPS\Dispatcher\Controller
$form = new \IPS\Helpers\Form( \IPS\Request::i()->method . '_more_info', 'continue' );
$form->hiddenValues['per_cycle'] = \IPS\Request::i()->per_cycle;
if ( isset( \IPS\Request::i()->empty_local_data ) AND \IPS\Request::i()->empty_local_data == 1 )
{
$form->hiddenValues['empty_local_data'] = 1;
}
if ( isset( \IPS\Request::i()->reconfigure ) )
{
$form->hiddenValues['reconfigure'] = 1;
}
$hints = array();
/* Show the 'more info' fields */
foreach( $getMoreInfo as $key => $input )
{
$fieldClass = $input['field_class'];
@@ -191,12 +253,8 @@ class _convert extends \IPS\Dispatcher\Controller
unset( $values['per_cycle'] );
$app->saveMoreInfo( \IPS\Request::i()->method, $values );
$extra = '';
if ( isset( \IPS\Request::i()->empty_local_data ) AND \IPS\Request::i()->empty_local_data == 1 )
{
$extra .= "&empty_local_data=1";
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=convert&module=manage&controller=convert&do=runStep&id={$app->app_id}&per_cycle={$per_cycle}&method=" . \IPS\Request::i()->method . $extra ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=convert&module=manage&controller=convert&do=runStep&id={$app->app_id}&per_cycle={$per_cycle}&method=" . \IPS\Request::i()->method ) );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'more_info_needed' );
@@ -206,30 +264,6 @@ class _convert extends \IPS\Dispatcher\Controller
}
}
/* If we decided to truncate, do so now */
if ( isset( \IPS\Request::i()->empty_local_data ) AND \IPS\Request::i()->empty_local_data == 1 )
{
$app->getSource()->getLibrary()->emptyLocalData( \IPS\Request::i()->method );
/* If this step has extra steps, we need to unset the completed flag. */
$canConvert = $app->getSource()->getLibrary()->getConvertableItems();
if ( isset( $canConvert[ \IPS\Request::i()->method ]['extra_steps'] ) )
{
$next = NULL;
$session = $app->_session;
foreach( $canConvert[ \IPS\Request::i()->method ]['extra_steps'] as $next )
{
if( $key = array_search( $next, $session['completed'] ) )
{
unset( $session['completed'][ $key ] );
}
}
/* Set the new session data */
$app->_session = $session;
}
}
/* Are we continuing? We need to tell the library that, but we need to use sessions to do it so it only does it on the first cycle. */
if ( isset( \IPS\Request::i()->continue ) )
{
@@ -352,7 +386,7 @@ class _convert extends \IPS\Dispatcher\Controller
if ( $values = $form->values() )
{
$softwareClass->convertSettings( $values );
unset( \IPS\Data\Store::i()->settings );
\IPS\Settings::i()->clearCache();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=convert&module=manage&controller=convert&id={$app->app_id}" ), 'saved' );
}
@@ -372,7 +406,7 @@ class _convert extends \IPS\Dispatcher\Controller
{
\IPS\Output::i()->error( 'no_conversion_app', '2V101/E' );
}
try
{
$app = \IPS\convert\App::load( \IPS\Request::i()->id );
@@ -386,7 +420,7 @@ class _convert extends \IPS\Dispatcher\Controller
\IPS\Request::i()->confirmedDelete( 'convert_finish_confirm_title', 'convert_finish_confirm', 'finish' );
$softwareClass = $app->getSource();
$messages = array();
/* If we have a parent, run them. */
@@ -395,31 +429,43 @@ class _convert extends \IPS\Dispatcher\Controller
if ( $app->parent )
{
$parent = \IPS\convert\App::load( $app->parent );
if ( method_exists( $parent->getSource(), 'finish' ) )
if ( method_exists( $parent->getSource(), 'finish' ) AND !$parent->finished )
{
$return = $parent->getSource()->finish();
$parent->app->log( 'app_finished', __METHOD__, \IPS\convert\App::LOG_NOTICE );
$parent->log( 'app_finished', __METHOD__, \IPS\convert\App::LOG_NOTICE );
$parent->finished = TRUE;
$parent->save();
if ( is_array( $return ) )
{
$messages = array_merge( $messages, $return );
}
}
elseif( $parent->finished )
{
$parent->log( 'app_finished_skipped', __METHOD__, \IPS\convert\App::LOG_NOTICE );
}
/* Run siblings if need be */
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'convert_apps', array( "parent=? AND app_id!=?", $parent->app_id, $app->app_id ) ), 'IPS\convert\App' ) AS $sibling )
{
if ( method_exists( $sibling->getSource(), 'finish' ) )
if ( method_exists( $sibling->getSource(), 'finish' ) AND !$sibling->finished )
{
$return = $sibling->getSource()->finish();
$sibling->app->log( 'app_finished', __METHOD__, \IPS\convert\App::LOG_NOTICE );
$sibling->log( 'app_finished', __METHOD__, \IPS\convert\App::LOG_NOTICE );
$sibling->finished = TRUE;
$sibling->save();
if ( is_array( $return ) )
{
$messages = array_merge( $messages, $return );
}
}
elseif( $sibling->finished )
{
$sibling->log( 'app_finished_skipped', __METHOD__, \IPS\convert\App::LOG_NOTICE );
}
}
}
else
@@ -434,32 +480,50 @@ class _convert extends \IPS\Dispatcher\Controller
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'convert_apps', array( "parent=?", $app->app_id ) ), 'IPS\convert\App' ) AS $child )
{
$childSoftwareClass = $child->getSource();
if ( method_exists( $childSoftwareClass, 'finish' ) )
if ( method_exists( $childSoftwareClass, 'finish' ) AND !$child->finished )
{
$return = $childSoftwareClass->finish();
$childSoftwareClass->app->log( 'app_finished', __METHOD__, \IPS\convert\App::LOG_NOTICE );
$child->log( 'app_finished', __METHOD__, \IPS\convert\App::LOG_NOTICE );
$child->finished = TRUE;
$child->save();
if ( is_array( $return ) )
{
$messages = array_merge( $messages, $return );
}
}
elseif( $child->finished )
{
$child->log( 'app_finished_skipped', __METHOD__, \IPS\convert\App::LOG_NOTICE );
}
}
}
/* And finally, run this one */
if ( method_exists( $softwareClass, 'finish' ) )
if ( method_exists( $softwareClass, 'finish' ) AND !$app->finished )
{
$return = $softwareClass->finish();
$softwareClass->app->log( 'app_finished', __METHOD__, \IPS\convert\App::LOG_NOTICE );
$app->log( 'app_finished', __METHOD__, \IPS\convert\App::LOG_NOTICE );
$app->finished = TRUE;
$app->save();
if ( is_array( $return ) )
{
$messages = array_merge( $messages, $return );
}
}
elseif( $app->finished )
{
$app->log( 'app_finished_skipped', __METHOD__, \IPS\convert\App::LOG_NOTICE );
}
/* Any Messages? */
if( !count( $messages ) )
{
$messages = array( 'nothing_to_finish' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=convert&module=manage&controller=manage" ), implode( ', ', array_map( function( $key ) {
return \IPS\Member::loggedIn()->language()->addToStack( $key );
}, $messages ) ) );
@@ -146,7 +146,7 @@ class _create extends \IPS\Dispatcher\Controller
{
foreach( $sw AS $key )
{
$where[] = array( 'sw=? AND app_key=?', $app, $key );
$where[] = array( 'sw=? AND app_key=? AND finished=?', $app, $key, 0 );
}
}
@@ -280,6 +280,12 @@ class _create extends \IPS\Dispatcher\Controller
{
throw new \InvalidArgumentException( 'invalid_prefix' );
}
/* Can't find the table */
catch( \IPS\convert\Exception $e )
{
$form->error = $e->getMessage();
return $form;
}
}
catch( \InvalidArgumentException $e )
{
@@ -40,10 +40,13 @@ class _manage extends \IPS\Dispatcher\Controller
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( 'convert_mod_rewrite_urls', 'warning' );
}
/* Remind the user that they must configure permissions after the conversion is complete */
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( 'convert_configure_permissions', 'information' );
/* Create the table */
$table = new \IPS\Helpers\Table\Db( 'convert_apps', \IPS\Http\Url::internal( 'app=convert&module=manage&controller=manage' ) );
$table->langPrefix = 'convert_';
$table->include = array( 'sw', 'app_key', 'name', 'parent' );
$table->include = array( 'sw', 'app_key', 'name', 'parent', 'finished' );
$table->parsers = array(
'sw' => function( $val, $row )
@@ -98,6 +101,17 @@ class _manage extends \IPS\Dispatcher\Controller
{
return \IPS\Member::loggedIn()->language()->addToStack( 'convert_cannot_parent' );
}
},
'finished' => function( $val )
{
if ( $val )
{
return '&#10003;';
}
else
{
return '&#10007;';
}
}
);
@@ -135,38 +149,42 @@ class _manage extends \IPS\Dispatcher\Controller
get_class( \IPS\convert\App::constructFromData( $row )->getSource() );
$return = array();
$return[] = array(
'icon' => 'chevron-circle-right',
'title' => 'continue',
'link' => \IPS\Http\Url::internal( "app=convert&module=manage&controller=convert&id={$row['app_id']}" )
);
if ( $row['parent'] == 0 )
if ( !$row['finished'] )
{
$return[] = array(
'icon' => 'check',
'title' => 'finish',
'link' => \IPS\Http\Url::internal( "app=convert&module=manage&controller=convert&do=finish&id={$row['app_id']}" ),
'data' => array(
'confirm' => '', 'confirmSubMessage' => \IPS\Member::loggedIn()->language()->get( 'convert_finish_confirm' )
)
'icon' => 'chevron-circle-right',
'title' => 'continue',
'link' => \IPS\Http\Url::internal( "app=convert&module=manage&controller=convert&id={$row['app_id']}" )
);
if ( $row['parent'] == 0 )
{
$return[] = array(
'icon' => 'check',
'title' => 'finish',
'link' => \IPS\Http\Url::internal( "app=convert&module=manage&controller=convert&do=finish&id={$row['app_id']}" ),
'data' => array(
'confirm' => '', 'confirmSubMessage' => \IPS\Member::loggedIn()->language()->get( 'convert_finish_confirm' )
)
);
}
$return[] = array(
'icon' => 'pencil',
'title' => 'edit',
'link' => \IPS\Http\Url::internal( "app=convert&module=manage&controller=manage&do=edit&id={$row['app_id']}" ),
);
$return[] = array(
'icon' => 'times-circle',
'title' => 'delete',
'link' => \IPS\Http\Url::internal( "app=convert&module=manage&controller=manage&do=delete&id={$row['app_id']}" ),
'data' => array( 'delete' => '' ),
);
}
$return[] = array(
'icon' => 'pencil',
'title' => 'edit',
'link' => \IPS\Http\Url::internal( "app=convert&module=manage&controller=manage&do=edit&id={$row['app_id']}" ),
);
$return[] = array(
'icon' => 'times-circle',
'title' => 'delete',
'link' => \IPS\Http\Url::internal( "app=convert&module=manage&controller=manage&do=delete&id={$row['app_id']}" ),
'data' => array( 'delete' => '' ),
);
return $return;
}
catch( \InvalidArgumentException $e )
@@ -46,6 +46,13 @@ class _Upgrade
'query' => "ALTER TABLE " . \IPS\Db::i()->prefix . "core_members CHANGE misc conv_password_extra VARCHAR(255) null default null;"
);
}
else if ( \IPS\Db::i()->checkForColumn( 'core_members', 'conv_password_extra' ) )
{
$queries[] = array(
'table' => 'core_members',
'query' => "ALTER TABLE " . \IPS\Db::i()->prefix . "core_members CHANGE conv_password_extra conv_password_extra VARCHAR(255) null default null;"
);
}
else
{
$queries[] = array(
@@ -27,6 +27,8 @@ class _Upgrade
'table' => 'convert_link_posts',
'query' => "ALTER TABLE " . \IPS\Db::i()->prefix . "convert_link_posts CHANGE foreign_id foreign_id VARCHAR(255) NOT NULL DEFAULT '',
ADD INDEX local_lookup(ipb_id,type,app),
DROP INDEX foreign_id,
ADD INDEX foreign_id (foreign_id(191)),
DROP INDEX ipb_id,
DROP INDEX `type`,
ADD INDEX `type` (`type`),
@@ -36,6 +38,8 @@ class _Upgrade
'table' => 'convert_link_pms',
'query' => "ALTER TABLE " . \IPS\Db::i()->prefix . "convert_link_pms CHANGE foreign_id foreign_id VARCHAR(255) NOT NULL DEFAULT '',
ADD INDEX local_lookup(ipb_id,type,app),
DROP INDEX foreign_id,
ADD INDEX foreign_id (foreign_id(191)),
DROP INDEX ipb_id,
DROP INDEX `type`,
ADD INDEX `type` (`type`),
@@ -45,6 +49,8 @@ class _Upgrade
'table' => 'convert_link_topics',
'query' => "ALTER TABLE " . \IPS\Db::i()->prefix . "convert_link_topics CHANGE foreign_id foreign_id VARCHAR(255) NOT NULL DEFAULT '',
ADD INDEX local_lookup(ipb_id,type,app),
DROP INDEX foreign_id,
ADD INDEX foreign_id (foreign_id(191)),
DROP INDEX ipb_id,
DROP INDEX `type`,
ADD INDEX `type` (`type`),
@@ -55,7 +61,7 @@ class _Upgrade
'query' => "ALTER TABLE " . \IPS\Db::i()->prefix . "convert_link CHANGE foreign_id foreign_id VARCHAR(255) NOT NULL DEFAULT '',
ADD INDEX local_lookup(ipb_id,type,app),
DROP INDEX foreign_id,
ADD INDEX foreign_id (foreign_id(250)),
ADD INDEX foreign_id (foreign_id(191)),
DROP INDEX ipb_id,
DROP INDEX `type`,
ADD INDEX `type` (`type`),
+404 -401
View File
@@ -22,414 +22,417 @@ class _Upgrade
*/
public function step1()
{
if ( \IPS\Db::i()->checkForTable( 'custom_bbcode' ) )
if ( !\IPS\Db::i()->checkForTable( 'convert_custom_bbcode' ) )
{
\IPS\Db::i()->renameTable( 'custom_bbcode', 'convert_custom_bbcode' );
}
else
{
\IPS\Db::i()->createTable( array (
'name' => 'convert_custom_bbcode',
'columns' => array(
'bbcode_id' => array(
'allow_null' => false,
'auto_increment' => true,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 10,
'name' => 'bbcode_id',
'type' => 'INT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
if ( \IPS\Db::i()->checkForTable( 'custom_bbcode' ) )
{
\IPS\Db::i()->renameTable( 'custom_bbcode', 'convert_custom_bbcode' );
}
else
{
\IPS\Db::i()->createTable( array (
'name' => 'convert_custom_bbcode',
'columns' => array(
'bbcode_id' => array(
'allow_null' => false,
'auto_increment' => true,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 10,
'name' => 'bbcode_id',
'type' => 'INT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_title' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 255,
'name' => 'bbcode_title',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array (),
'zerofill' => false,
),
'bbcode_desc' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'bbcode_desc',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_tag' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 255,
'name' => 'bbcode_tag',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_replace' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'bbcode_replace',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_useoption' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_useoption',
'type' => 'TINYINT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_example' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'bbcode_example',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_switch_option' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_switch_option',
'type' => 'INT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_menu_option_text' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 200,
'name' => 'bbcode_menu_option_text',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_menu_content_text' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 200,
'name' => 'bbcode_menu_content_text',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_single_tag' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_single_tag',
'type' => 'TINYINT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_groups' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 255,
'name' => 'bbcode_groups',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_sections' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 255,
'name' => 'bbcode_sections',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_php_plugin' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 255,
'name' => 'bbcode_php_plugin',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_no_parsing' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_no_parsing',
'type' => 'TINYINT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_protected' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_protected',
'type' => 'TINYINT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_aliases' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 255,
'name' => 'bbcode_aliases',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_optional_option' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_optional_option',
'type' => 'TINYINT',
'unsigned' => false,
'values' => array (),
'zerofill' => false,
),
'bbcode_image' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 255,
'name' => 'bbcode_image',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_app' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 50,
'name' => 'bbcode_app',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_custom_regex' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'bbcode_custom_regex',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
),
'bbcode_title' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 255,
'name' => 'bbcode_title',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array (),
'zerofill' => false,
),
'bbcode_desc' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'bbcode_desc',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_tag' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 255,
'name' => 'bbcode_tag',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_replace' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'bbcode_replace',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_useoption' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_useoption',
'type' => 'TINYINT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_example' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'bbcode_example',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_switch_option' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_switch_option',
'type' => 'INT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_menu_option_text' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 200,
'name' => 'bbcode_menu_option_text',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_menu_content_text' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 200,
'name' => 'bbcode_menu_content_text',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_single_tag' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_single_tag',
'type' => 'TINYINT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_groups' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 255,
'name' => 'bbcode_groups',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_sections' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 255,
'name' => 'bbcode_sections',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_php_plugin' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 255,
'name' => 'bbcode_php_plugin',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_no_parsing' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_no_parsing',
'type' => 'TINYINT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_protected' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_protected',
'type' => 'TINYINT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_aliases' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 255,
'name' => 'bbcode_aliases',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_optional_option' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 1,
'name' => 'bbcode_optional_option',
'type' => 'TINYINT',
'unsigned' => false,
'values' => array (),
'zerofill' => false,
),
'bbcode_image' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 255,
'name' => 'bbcode_image',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_app' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 50,
'name' => 'bbcode_app',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'bbcode_custom_regex' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'bbcode_custom_regex',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
),
'indexes' => array(
'PRIMARY' => array(
'type' => 'primary',
'name' => 'PRIMARY',
'length' => array( 0 => NULL ),
'columns' => array( 0 => 'bbcode_id' ),
),
)
) );
'indexes' => array(
'PRIMARY' => array(
'type' => 'primary',
'name' => 'PRIMARY',
'length' => array( 0 => NULL ),
'columns' => array( 0 => 'bbcode_id' ),
),
)
) );
}
}
if ( \IPS\Db::i()->checkForTable( 'bbcode_mediatag' ) )
if ( !\IPS\Db::i()->checkForTable( 'convert_bbcode_mediatag' ) )
{
\IPS\Db::i()->renameTable( 'bbcode_mediatag', 'convert_bbcode_mediatag' );
}
else
{
\IPS\Db::i()->createTable( array(
'name' => 'convert_bbcode_mediatag',
'columns' => array(
'mediatag_id' => array(
'allow_null' => false,
'auto_increment' => true,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 10,
'name' => 'mediatag_id',
'type' => 'SMALLINT',
'unsigned' => true,
'values' => array(),
'zerofill' => false,
if ( \IPS\Db::i()->checkForTable( 'bbcode_mediatag' ) )
{
\IPS\Db::i()->renameTable( 'bbcode_mediatag', 'convert_bbcode_mediatag' );
}
else
{
\IPS\Db::i()->createTable( array(
'name' => 'convert_bbcode_mediatag',
'columns' => array(
'mediatag_id' => array(
'allow_null' => false,
'auto_increment' => true,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 10,
'name' => 'mediatag_id',
'type' => 'SMALLINT',
'unsigned' => true,
'values' => array(),
'zerofill' => false,
),
'mediatag_name' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 255,
'name' => 'mediatag_name',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'mediatag_match' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'mediatag_match',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'mediatag_replace' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'mediatag_replace',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'mediatag_position' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 6,
'name' => 'mediatag_position',
'type' => 'SMALLINT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
),
'mediatag_name' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '',
'length' => 255,
'name' => 'mediatag_name',
'type' => 'VARCHAR',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'mediatag_match' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'mediatag_match',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'mediatag_replace' => array(
'allow_null' => true,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => NULL,
'length' => 0,
'name' => 'mediatag_replace',
'type' => 'TEXT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
'mediatag_position' => array(
'allow_null' => false,
'auto_increment' => false,
'binary' => false,
'comment' => '',
'decimals' => NULL,
'default' => '0',
'length' => 6,
'name' => 'mediatag_position',
'type' => 'SMALLINT',
'unsigned' => false,
'values' => array(),
'zerofill' => false,
),
),
'indexes' => array(
'PRIMARY' => array(
'type' => 'primary',
'name' => 'PRIMARY',
'length' => array( 0 => NULL ),
'columns' => array( 0 => 'mediatag_id' ),
),
)
) );
'indexes' => array(
'PRIMARY' => array(
'type' => 'primary',
'name' => 'PRIMARY',
'length' => array( 0 => NULL ),
'columns' => array( 0 => 'mediatag_id' ),
),
)
) );
}
}
return TRUE;
}
// You can create as many additional methods (step2, step3, etc.) as is necessary.
// Each step will be executed in a new HTTP request
}
@@ -0,0 +1,31 @@
{
"1": {
"method": "addColumn",
"params": [
"convert_apps",
{
"name": "finished",
"type": "BIT",
"length": 1,
"decimals": null,
"values": null,
"allow_null": true,
"default": "0",
"comment": "Has app been finished?",
"unsigned": false,
"zerofill": false,
"auto_increment": false,
"binary": false
}
]
},
"2": {
"method": "update",
"params": [
"convert_apps",
{
"finished": true
}
]
}
}
@@ -0,0 +1,15 @@
{
"1": {
"method": "update",
"params": [
"core_applications",
{
"app_enabled": 1
},
[
"app_directory=?",
"convert"
]
]
}
}
@@ -0,0 +1,41 @@
<?php
/**
* @brief 4.3.0 Beta 1 Upgrade Code
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Converter
* @since 16 Oct 2017
*/
namespace IPS\convert\setup\upg_103000;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* 4.3.0 Beta 1 Upgrade Code
*/
class _Upgrade
{
/**
* Install new login handler
*
* @return array If returns TRUE, upgrader will proceed to next step. If it returns any other value, it will set this as the value of the 'extra' GET parameter and rerun this step (useful for loops)
*/
public function step1()
{
/* Install new login handler */
\IPS\Application::load('convert')->installOther();
/* Remove legacy login handler */
\IPS\Db::i()->delete( 'core_login_handlers', array( 'login_key=?', 'Convert' ) );
return TRUE;
}
}
+46 -3
View File
@@ -460,6 +460,46 @@ class _App extends \IPS\Patterns\ActiveRecord
throw new \OutOfRangeException;
}
}
/**
* Delete a stored foreignId <-> IPS4 ID relation
*
* @param mixed $foreignId Foreign data ID
* @param string $type The type of item
*/
public function deleteLink( $foreignId, $type )
{
/* Build cache key */
if ( !is_array( $type ) )
{
$type = array( $type );
}
$key = md5( json_encode( $type ) );
$table = 'convert_link';
if ( in_array( $type, array( 'core_message_topics', 'core_message_posts', 'core_message_topic_user_map', 'forums_topics', 'forums_posts' ) ) )
{
if ( in_array( $type, array( 'core_message_topics', 'core_message_posts', 'core_message_topic_user_map' ) ) )
{
$table = 'convert_link_pms';
}
else
{
$tableType = str_replace( 'forums_', '', $type );
$table = "convert_link_{$tableType}";
}
}
\IPS\Db::i()->delete( $table, array( 'foreign_id=? AND type=? AND app=?', $foreignId, $type, $this->app_id ) );
/* Remove this from the link cache */
if( isset( $this->linkCache[ $key ][ $foreignId ] ) )
{
unset( $this->linkCache[ $key ][ $foreignId ] );
}
}
/**
* @brief Sibling Cache
@@ -510,10 +550,13 @@ class _App extends \IPS\Patterns\ActiveRecord
break;
}
if( $this->_data['app_key'] == 'photopost8' )
/* Change any app keys to newer versions */
switch( $this->data['app_key'] )
{
$this->app_key = 'photopost';
$this->save();
case 'photopost8':
$this->app_key = 'photopost';
$this->save();
break;
}
if ( ! class_exists( 'IPS\\convert\\Software\\' . ucwords( $this->_data['sw'] ) . '\\' . ucwords( $this->_data['app_key'] ) ) )
+29 -4
View File
@@ -54,11 +54,19 @@ abstract class _Library
* @brief \IPS\convert\Software instance for the software we are converting from
*/
public $software = NULL;
/**
* @brief Obscure filenames?
*
* This is only referenced in certain content types that may be referenced in already parsed data such as attachments and emoticons
* - Designed for use with the Invision Community converter.
*/
public static $obscureFilenames = TRUE;
/**
* @brief Array of field types
*/
protected static $fieldTypes = array( 'Address', 'Checkbox', 'CheckboxSet', 'Codemirror', 'Color', 'Date', 'Editor', 'Email', 'Member', 'Number', 'Password', 'Poll', 'Radio', 'Rating', 'Select', 'Tel', 'Text', 'TextArea', 'Upload', 'Url', 'YesNo' );
protected static $fieldTypes = array( 'Address', 'Checkbox', 'CheckboxSet', 'Codemirror', 'Color', 'Date', 'Editor', 'Email', 'Item', 'Member', 'Number', 'Password', 'Poll', 'Radio', 'Rating', 'Select', 'Tel', 'Text', 'TextArea', 'Upload', 'Url', 'YesNo' );
/**
* Constructor
@@ -185,9 +193,9 @@ abstract class _Library
/* Set as last key value */
$this->setLastKeyValue( $lastId );
/* Set this to FALSE so we don't do this again. */
$_SESSION['convertContinue'] = FALSE;
/* Unset this so we don't do this again. */
unset( $_SESSION['convertContinue'] );
}
/* Fetch data from the software */
@@ -353,6 +361,10 @@ abstract class _Library
public function emptyLocalData( $method )
{
$truncate = $this->truncate( $method );
/* Get the link type */
$menuRows = $this->menuRows();
foreach( $truncate as $table => $where )
{
/* Kind of a hacky way to make sure we truncate the right forums archive table */
@@ -364,10 +376,23 @@ abstract class _Library
{
\IPS\Db::i()->delete( $table, $where );
}
/* Do we have a specific link type? */
$linkType = NULL;
if( isset( $menuRows[ $method ] ) AND isset( $menuRows[ $method ]['link_type'] ) )
{
$linkType = $menuRows[ $method ]['link_type'];
}
/* Remove links... we don't really care about which table they are in right now */
foreach( array( 'convert_link', 'convert_link_pms', 'convert_link_posts', 'convert_link_topics' ) as $linkTable )
{
if( $linkType !== NULL )
{
\IPS\Db::i()->delete( $linkTable, array( "type=? AND app=?", $linkType, $this->software->app->app_id ) );
continue;
}
\IPS\Db::i()->delete( $linkTable, array( "type=? AND app=?", $table, $this->software->app->app_id ) );
}
}
@@ -36,7 +36,6 @@ class _Blog extends Core
public function menuRows()
{
$return = array();
$classname = get_class( $this->software );
foreach( $this->getConvertableItems() as $k => $v )
{
@@ -110,7 +109,8 @@ class _Blog extends Core
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 1000,
'dependencies' => array( 'convertClubBlogs' ),
'link_type' => 'core_club_blogs_entries'
'link_type' => 'core_club_blogs_entries',
'requires_rebuild' => TRUE
);
break;
@@ -122,7 +122,8 @@ class _Blog extends Core
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 1000,
'dependencies' => array( 'convertClubBlogEntries' ),
'link_type' => 'core_club_blogs_comments'
'link_type' => 'core_club_blogs_comments',
'requires_rebuild' => TRUE
);
break;
@@ -140,6 +141,8 @@ class _Blog extends Core
}
}
$return = $this->software->extraMenuRows( $return );
return $return;
}
@@ -181,7 +184,7 @@ class _Blog extends Core
case 'convertAttachments':
$return['convertAttachments'] = array(
'core_attachments' => \IPS\Db::i()->in( \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', array( "location_key=?", 'blog_Entries' ) ) ),
'core_attachments' => \IPS\Db::i()->in( 'attach_id', (string) \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', array( "location_key=?", 'blog_Entries' ) ) ),
'core_attachments_map' => array( "location_key=?", 'blog_Entries' )
);
break;
@@ -985,7 +988,7 @@ class _Blog extends Core
* @param string|NULL $filedata If $filepath is NULL, this should contain the raw contents of the attachment
* @return integer|boolean The ID of the inserted attachment, or FALSE on failure.
*/
public function convertAttachment( $info=array(), $map=array(), $filepath = NULL, $filedata = NULL )
public function convertAttachment( $info=array(), $map=array(), $filepath = NULL, $filedata = NULL, $thumbnailpath = NULL )
{
$map['id1_type'] = 'blog_entries';
$map['id1_from_parent'] = FALSE;
@@ -1000,7 +1003,7 @@ class _Blog extends Core
$map['id2_from_parent'] = FALSE;
}
return parent::convertAttachment( $info, $map, $filepath, $filedata );
return parent::convertAttachment( $info, $map, $filepath, $filedata, $thumbnailpath );
}
/**
@@ -33,7 +33,6 @@ class _Calendar extends Core
public function menuRows()
{
$return = array();
$classname = get_class( $this->software );
foreach( $this->getConvertableItems() as $k => $v )
{
@@ -72,6 +71,7 @@ class _Calendar extends Core
'per_cycle' => 1500,
'dependencies' => array( 'convertCalendarCalendars' ),
'link_type' => 'calendar_events',
'requires_rebuild' => TRUE
);
break;
@@ -84,6 +84,7 @@ class _Calendar extends Core
'per_cycle' => 2000,
'dependencies' => array( 'convertCalendarEvents' ),
'link_type' => 'calendar_event_comments',
'requires_rebuild' => TRUE
);
break;
@@ -96,6 +97,7 @@ class _Calendar extends Core
'per_cycle' => 2000,
'dependencies' => array( 'convertCalendarEvents' ),
'link_type' => 'calendar_event_reviews',
'requires_rebuild' => TRUE
);
break;
@@ -148,6 +150,8 @@ class _Calendar extends Core
break;
}
}
$return = $this->software->extraMenuRows( $return );
return $return;
}
@@ -172,7 +176,11 @@ class _Calendar extends Core
switch( $k )
{
case 'convertCalendarCalendars':
$return['convertCalendarCalendars'] = array( 'calendar_calendars' => NULL, 'core_clubs_node_map' => array( "node_class=?", "IPS\\calendar\\Calendar" ) );
$return['convertCalendarCalendars'] = array(
'calendar_calendars' => NULL,
'core_clubs_node_map' => array( "node_class=?", "IPS\\calendar\\Calendar" ),
'core_permission_index' => array( 'app=? AND perm_type=?', 'calendar', 'calendar' )
);
break;
case 'convertCalendarEvents':
@@ -279,7 +287,7 @@ class _Calendar extends Core
if ( !isset( $info['cal_position'] ) )
{
$position = \IPS\Db::i()->select( 'MAX(position)', 'calendar_calendars' )->first();
$position = \IPS\Db::i()->select( 'MAX(cal_position)', 'calendar_calendars' )->first();
$info['cal_position'] = $position + 1;
}
@@ -1304,7 +1312,7 @@ class _Calendar extends Core
* @param string|NULL $filedata Binary data for the file, or NULL.
* @return integer|boolean The ID of the newly inserted attachment, or FALSE on failure.
*/
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL )
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL, $thumbnailpath=NULL )
{
$map['location_key'] = 'calendar_Calendar';
$map['id1_type'] = 'calendar_events';
@@ -1325,6 +1333,6 @@ class _Calendar extends Core
$map['id2_type'] = 'calendar_event_reviews';
}
return parent::convertAttachment( $info, $map, $filepath, $filedata );
return parent::convertAttachment( $info, $map, $filepath, $filedata, $thumbnailpath );
}
}
+121 -39
View File
@@ -117,39 +117,48 @@ class _Cms extends Core
$count += \IPS\Db::i()->select( 'COUNT(*)', "cms_custom_database_{$database}" )->first();
$links[] = "cms_custom_database_{$database}";
}
$dependencies = array();
if ( array_key_exists( 'convertCmsDatabaseCategories', $this->getConvertableItems() ) )
{
$dependencies[] = 'convertCmsDatabaseCategories';
}
$return[ $k ] = array(
'step_title' => 'convert_cms_database_records',
'step_method' => 'convertCmsDatabaseRecords',
'ips_rows' => $count,
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 100,
'dependencies' => array( 'convertCmsDatabases', 'convertCmsDatabaseCategories' ),
'link_type' => $links,
'step_title' => 'convert_cms_database_records',
'step_method' => 'convertCmsDatabaseRecords',
'ips_rows' => $count,
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 100,
'dependencies' => array( 'convertCmsDatabases' ),
'link_type' => $links,
'requires_rebuild' => TRUE
);
break;
case 'convertCmsDatabaseComments':
$return[ $k ] = array(
'step_title' => 'convert_cms_database_comments',
'step_method' => 'convertCmsDatabaseComments',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'cms_database_comments' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 2000,
'dependencies' => array( 'convertCmsDatabaseRecords' ),
'link_type' => 'cms_database_comments',
'step_title' => 'convert_cms_database_comments',
'step_method' => 'convertCmsDatabaseComments',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'cms_database_comments' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 2000,
'dependencies' => array( 'convertCmsDatabaseRecords' ),
'link_type' => 'cms_database_comments',
'requires_rebuild' => TRUE
);
break;
case 'convertCmsDatabaseReviews':
$return[ $k ] = array(
'step_title' => 'convert_cms_database_reviews',
'step_method' => 'convertCmsDatabaseReviews',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'cms_database_reviews' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 2000,
'dependencies' => array( 'convertCmsDatabaseRecords' ),
'link_type' => 'cms_database_reviews',
'step_title' => 'convert_cms_database_reviews',
'step_method' => 'convertCmsDatabaseReviews',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'cms_database_reviews' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 2000,
'dependencies' => array( 'convertCmsDatabaseRecords' ),
'link_type' => 'cms_database_reviews',
'requires_rebuild' => TRUE
);
break;
@@ -234,6 +243,8 @@ class _Cms extends Core
}
}
$return = $this->software->extraMenuRows( $return );
return $return;
}
@@ -283,7 +294,7 @@ class _Cms extends Core
break;
case 'convertCmsDatabaseCategories':
$return['convertCmsDatabaseDategories'] = array( 'cms_database_categories' => NULL );
$return['convertCmsDatabaseCategories'] = array( 'cms_database_categories' => NULL );
break;
case 'convertCmsDatabaseRecords':
@@ -296,6 +307,8 @@ class _Cms extends Core
}
$return['convertCmsDatabaseRecords'] = $toReturn;
}
$return['convertCmsDatabaseRecords']['core_tags'] = array( 'tag_meta_app=?', 'cms' );
break;
case 'convertCmsDatabaseComments':
@@ -322,7 +335,7 @@ class _Cms extends Core
}
}
return $return[ $method ];
return isset( $return[ $method ] ) ? $return[ $method ] : array();
}
/**
@@ -426,12 +439,12 @@ class _Cms extends Core
$info['page_js_css_ids'] = '';
$info['page_js_css_objects'] = json_encode( array( 'css' => NULL, 'js' => NULL ) );
if ( !isset( $info['page_name'] ) )
if ( !isset( $info['page_name'] ) OR empty( $info['page_name'] ) )
{
$info['page_name'] = "Untitled Page {$info['page_id']}";
}
if ( !isset( $info['page_seo_name'] ) )
if ( !isset( $info['page_seo_name'] ) OR empty( $info['page_seo_name'] ) )
{
$info['page_seo_name'] = \IPS\Http\Url::seoTitle( $info['page_name'] );
}
@@ -483,6 +496,23 @@ class _Cms extends Core
{
$info['page_folder_id'] = 0;
}
/* We cannot have a page name the same as a folder name in this folder */
try
{
/* Check folder */
if ( intval( $info['page_folder_id'] ) == \IPS\cms\Pages\Folder::load( $info['page_seo_name'], 'folder_name' )->parent_id )
{
$info['page_seo_name'] = \IPS\Http\Url::seoTitle( $info['page_name'] . '-' . $info['page_id'] );
}
}
catch ( \OutOfRangeException $e ) {}
/* Check that we don't have the same FURL as an app */
if ( \IPS\cms\Pages\Page::isFurlCollision( $info['page_seo_name'] ) )
{
$info['page_seo_name'] = \IPS\Http\Url::seoTitle( $info['page_name'] . '-page' );
}
if ( !isset( $info['page_full_path'] ) )
{
@@ -1060,7 +1090,7 @@ class _Cms extends Core
}
else
{
$fieldName = ucfirst( $field['field_type'] );
$fieldName = mb_ucfirst( $field['field_type'] );
}
if ( isset( $field['field_desc'] ) )
@@ -1104,6 +1134,32 @@ class _Cms extends Core
{
$field['field_extra'] = '';
}
/* An Item Field needs extra data set */
if( $field['field_type'] == 'Item' )
{
$fieldExtra = !empty( $field['field_extra'] ) ? $field['field_extra'] : array();
if( isset( $fieldExtra['database'] ) )
{
try
{
$fieldExtra['database'] = $this->software->app->getLink( $fieldExtra['database'], 'cms_databases' );
}
catch( \OutOfRangeException $e )
{
/* Can't convert this if we don't know what it's referencing */
$this->software->app->log( 'cms_database_field_item_no_reference', __METHOD__, \IPS\convert\APP::LOG_NOTICE, $field['field_id'] );
continue;
}
}
else
{
$fieldExtra['database'] = $inserted_id;
}
$field['field_extra'] = json_encode( $fieldExtra );
}
if ( !isset( $field['field_html'] ) )
{
@@ -1345,6 +1401,7 @@ class _Cms extends Core
case 'Youtube':
case 'Spotify':
case 'Soundcloud':
case 'Item':
$columnDefinition['type'] = 'TEXT';
break;
@@ -1452,7 +1509,7 @@ class _Cms extends Core
if ( array_key_exists( 'category_desc', $info ) )
{
$desc = $info['category_desc'];
$desc = !empty( $info['category_desc'] ) ? $info['category_desc'] : '';
unset( $info['category_desc'] );
}
else
@@ -1567,6 +1624,25 @@ class _Cms extends Core
{
$info['category_meta_description'] = NULL;
}
if ( isset( $info['category_parent_id'] ) )
{
if ( $info['category_parent_id'] !== 0 )
{
try
{
$info['category_parent_id'] = $this->software->app->getLink( $info['category_parent_id'], 'cms_database_categories' );
}
catch( \OutOfRangeException $e )
{
/* Does the ID exist in the source? */
$info['category_conv_parent'] = $info['category_parent_id'];
/* Default to a category unless a later converted forum updates the parent_id */
$info['category_parent_id'] = 0;
}
}
}
/* Forum Integration */
try
@@ -1699,6 +1775,8 @@ class _Cms extends Core
$inserted_id = \IPS\Db::i()->insert( 'cms_database_categories', $info );
$this->software->app->addLink( $inserted_id, $id, 'cms_database_categories' );
\IPS\Db::i()->update( 'cms_database_categories', array( "category_parent_id" => $inserted_id ), array( "category_conv_parent=? AND category_conv_parent<>?", $id, 0 ) );
\IPS\Lang::saveCustom( 'cms', "content_cat_name_{$inserted_id}", $info['category_name'] );
\IPS\Lang::saveCustom( 'cms', "content_cat_name_{$inserted_id}_desc", $desc );
@@ -1709,8 +1787,10 @@ class _Cms extends Core
/**
* Convert a Record
*
* @param array $info The data to insert.
* @param array $fields Custom Field Data.
* @param array $info The data to insert.
* @param array $fields Custom Field Data.
* @param string|NULL $imagepath Path to record image
* @param string|NULL $imagedata Image data
* @return integer|boolean The ID of the newly inserted record, or FALSE on failure.
*/
public function convertCmsDatabaseRecord( $info=array(), $fields=array(), $imagepath=NULL, $imagedata=NULL )
@@ -1918,6 +1998,12 @@ class _Cms extends Core
if ( isset( $info['record_image'] ) AND ( !is_null( $imagepath ) OR !is_null( $imagedata ) ) )
{
$container = NULL;
if( $info['record_saved'] )
{
$container = 'monthly_' . date( 'Y', $info['record_saved'] ) . '_' . date( 'm', $info['record_saved'] );
}
try
{
if ( is_null( $imagedata ) AND !is_null( $imagepath ) )
@@ -1925,10 +2011,10 @@ class _Cms extends Core
$imagedata = @file_get_contents( $imagepath );
$imagepath = NULL;
}
$file = \IPS\File::create( 'cms_Records', $info['record_image'], $imagedata );
$file = \IPS\File::create( 'cms_Records', $info['record_image'], $imagedata, $container, TRUE );
$info['record_image'] = (string) $file;
$info['record_image_thumb'] = (string) $file->thumbnail();
$info['record_image_thumb'] = (string) $file->thumbnail( 'cms_Records' );
}
catch( \Exception $e )
{
@@ -2174,10 +2260,6 @@ class _Cms extends Core
$info['comment_author'] = NULL;
}
}
else
{
$info['comment_author'] = NULL;
}
if ( isset( $info['comment_edit_date'] ) )
{
@@ -2322,7 +2404,7 @@ class _Cms extends Core
/* This seems silly, but we really do need a rating */
if ( !isset( $info['review_rating'] ) OR $info['review_rating'] < 1 )
{
$this->software->app->log( 'calendar_event_review_invalid_rating', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['review_id'] );
$this->software->app->log( 'cms_database_review_invalid_rating', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['review_id'] );
return FALSE;
}
@@ -2648,7 +2730,7 @@ class _Cms extends Core
* @param string|NULL $filedata Binary data for the file, or NULL.
* @return integer|boolean The ID of the newly inserted attachment, or FALSE on failure.
*/
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL )
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL, $thumbnailpath = NULL )
{
$database = $this->software->app->getLink( str_replace( '-review', '', $map['id3'] ), 'cms_databases' );
@@ -2669,6 +2751,6 @@ class _Cms extends Core
$map['id3'] = $database;
return parent::convertAttachment( $info, $map, $filepath, $filedata );
return parent::convertAttachment( $info, $map, $filepath, $filedata, $thumbnailpath );
}
}
+479 -59
View File
@@ -230,6 +230,18 @@ class _Core extends \IPS\convert\Library
'link_type' => 'core_member_ranks',
);
break;
case 'convertReactions':
$return[ $k ] = array(
'step_title' => 'convert_reactions',
'step_method' => 'convertReactions',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'core_reactions' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 100,
'dependencies' => array(),
'link_type' => 'core_reactions',
);
break;
case 'convertStatuses':
$return[ $k ] = array(
@@ -252,6 +264,7 @@ class _Core extends \IPS\convert\Library
'per_cycle' => 2000,
'dependencies' => array( 'convertMembers', 'convertStatuses' ),
'link_type' => 'core_member_status_replies',
'requires_rebuild' => TRUE
);
break;
@@ -335,7 +348,8 @@ class _Core extends \IPS\convert\Library
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 1000,
'dependencies' => array( 'convertPrivateMessages' ),
'link_type' => array( 'core_message_posts' )
'link_type' => array( 'core_message_posts' ),
'requires_rebuild' => TRUE
);
break;
@@ -455,6 +469,8 @@ class _Core extends \IPS\convert\Library
break;
}
}
$return = $this->software->extraMenuRows( $return );
return $return;
}
@@ -517,7 +533,13 @@ class _Core extends \IPS\convert\Library
break;
case 'convertGroups':
$return['convertGroups'] = array( 'core_groups' => array( \IPS\Db::i()->in( 'g_id', array( \IPS\Settings::i()->member_group, \IPS\Settings::i()->guest_group, \IPS\Settings::i()->admin_group ), TRUE ) ), 'core_group_promotions' => NULL );
$return['convertGroups'] = array(
'core_admin_permission_rows' => array( 'row_id_type=? AND ' . \IPS\Db::i()->in( 'row_id', array( \IPS\Settings::i()->member_group, \IPS\Settings::i()->guest_group, \IPS\Settings::i()->admin_group ), TRUE ), 'group' ),
'core_groups' => array( \IPS\Db::i()->in( 'g_id', array( \IPS\Settings::i()->member_group, \IPS\Settings::i()->guest_group, \IPS\Settings::i()->admin_group ), TRUE ) ),
'core_group_promotions' => NULL,
'core_leaders' => array( 'leader_type=? AND ' . \IPS\Db::i()->in( 'leader_type_id', array( \IPS\Settings::i()->member_group, \IPS\Settings::i()->guest_group, \IPS\Settings::i()->admin_group ), TRUE ), 'g' ),
'core_moderators' => array( 'type=? AND ' . \IPS\Db::i()->in( 'id', array( \IPS\Settings::i()->member_group, \IPS\Settings::i()->guest_group, \IPS\Settings::i()->admin_group ), TRUE ), 'g' )
);
break;
case 'convertIgnoredUsers':
@@ -529,7 +551,10 @@ class _Core extends \IPS\convert\Library
break;
case 'convertLeaderGroups':
$return['convertLeaderGroups'] = array( 'core_leaders_groups' => NULL );
$return['convertLeaderGroups'] = array(
'core_leaders_groups' => NULL,
'core_leaders' => NULL
);
break;
case 'convertRanks':
@@ -546,10 +571,14 @@ class _Core extends \IPS\convert\Library
case 'convertMembers':
$return['convertMembers'] = array(
'core_members' => array( "member_id<>?", \IPS\Member::loggedIn()->member_id ),
'core_pfields_content' => array( "member_id<>?", \IPS\Member::loggedIn()->member_id ),
'core_admin_permission_rows' => array( 'row_id_type=? AND row_id<>?', 'member', \IPS\Member::loggedIn()->member_id ),
'core_leaders' => array( 'leader_type=? AND leader_type_id<>?', 'm', \IPS\Member::loggedIn()->member_id ),
'core_login_links' => array( "token_member<>?", \IPS\Member::loggedIn()->member_id ),
'core_member_history' => NULL,
'core_members_warn_actions' => NULL
'core_members' => array( "member_id<>?", \IPS\Member::loggedIn()->member_id ),
'core_members_warn_actions' => NULL,
'core_moderators' => array( 'type=? AND id<>?', 'm', \IPS\Member::loggedIn()->member_id ),
'core_pfields_content' => array( "member_id<>?", \IPS\Member::loggedIn()->member_id )
);
break;
@@ -584,10 +613,21 @@ class _Core extends \IPS\convert\Library
case 'convertProfileFields':
if ( $method == $k )
{
$columns = array();
foreach( \IPS\Db::i()->select( 'pf_id', 'core_pfields_data' ) AS $field )
{
\IPS\Db::i()->dropIndex( 'core_pfields_content', 'field_' . $field );
\IPS\Db::i()->dropColumn( 'core_pfields_content', 'field_' . $field );
$field = 'field_' . $field;
/* Check the column exists before listing it for removal */
if( \IPS\Db::i()->checkForColumn( 'core_pfields_content', $field ) )
{
$columns[] = $field;
}
}
if( count( $columns ) )
{
/* Drop all columns at once */
\IPS\Db::i()->dropColumn( 'core_pfields_content', $columns );
}
}
@@ -617,6 +657,10 @@ class _Core extends \IPS\convert\Library
case 'convertClubMembers':
$return['convertClubMembers'] = array( 'core_clubs_memberships' => NULL );
break;
case 'convertReactions':
$return['convertReactions'] = array( 'core_reactions' => NULL );
break;
}
}
@@ -785,6 +829,11 @@ class _Core extends \IPS\convert\Library
$this->software->app->log( 'announcement_missing_content', __METHOD__, \IPS\convert\App::LOG_WARNING );
return FALSE;
}
else
{
$softwareClass = $this->software;
$info['announce_content'] = $softwareClass::fixPostData( $info['announce_content'] );
}
if ( !isset( $info['announce_member_id'] ) )
{
@@ -792,6 +841,16 @@ class _Core extends \IPS\convert\Library
return FALSE;
}
try
{
$info['announce_member_id'] = $this->software->app->getLink( $info['announce_member_id'], 'core_members' );
}
catch( \OutOfRangeException $e )
{
$this->software->app->log( 'announcement_missing_member', __METHOD__, \IPS\convert\App::LOG_WARNING );
return FALSE;
}
if ( !isset( $info['announce_views'] ) )
{
$info['announce_views'] = 0;
@@ -832,6 +891,15 @@ class _Core extends \IPS\convert\Library
$info['announce_location'] = '*';
}
if ( !isset( $info['announce_page_location'] ) )
{
$info['announce_page_location'] = 'sidebar';
}
elseif( !is_array( $info['announce_page_location'] ) )
{
$info['announce_page_location'] = array( $info['announce_page_location'] );
}
$obj = new \IPS\core\Announcements\Announcement;
foreach ( $info AS $field => $value)
{
@@ -852,13 +920,14 @@ class _Core extends \IPS\convert\Library
/**
* Convert an Attachment
*
* @param array $info Data to insert
* @param array $map Array of Map data
* @param NULL|string $filepath The path to the attachment files or NULL if loading from the database.
* @param NULL|string $filedata If loading from the database, the content of the Binary column.
* @param array $info Data to insert
* @param array $map Array of Map data
* @param NULL|string $filepath The path to the attachment files or NULL if loading from the database.
* @param NULL|string $filedata If loading from the database, the content of the Binary column.
* @param NULL|string $thumnailpath Path to thumbnail image
* @return boolean|integer The ID of the newly inserted attachment, or FALSE on failure.
*/
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL )
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL, $thumbnailpath=NULL )
{
if ( !isset( $info['attach_id'] ) )
{
@@ -953,18 +1022,27 @@ class _Core extends \IPS\convert\Library
{
$info['attach_hits'] = 0;
}
/* Figure out the container */
$container = 'monthly_' . date( 'Y', $info['attach_date'] ) . '_' . date( 'm', $info['attach_date'] );
if( isset( $info['attach_container'] ) )
{
$container = $info['attach_container'];
}
/* Create the file */
$file = NULL;
try
{
/* We need the file storage to copy the file rather than move it */
\IPS\File\FileSystem::$converterCopy = TRUE;
\IPS\File::$copyFiles = TRUE;
/* Create the file */
$file = \IPS\File::create( 'core_Attachments', $info['attach_file'], $filedata, 'monthly_' . date( 'Y', $info['attach_date'] ) . '_' . date( 'm', $info['attach_date'] ), TRUE, $filepath );
$file = \IPS\File::create( 'core_Attachment', $info['attach_file'], $filedata, $container, TRUE, $filepath, static::$obscureFilenames );
/* Revert file system to default functionality */
\IPS\File\FileSystem::$converterCopy = FALSE;
\IPS\File::$copyFiles = FALSE;
unset( $filedata );
$info['attach_location'] = (string) $file;
@@ -979,6 +1057,55 @@ class _Core extends \IPS\convert\Library
$this->software->app->log( $e->getMessage(), __METHOD__, \IPS\convert\App::LOG_WARNING, $info['attach_id'] );
return FALSE;
}
if( $file === NULL )
{
$this->software->app->log( 'attachment_creation_fail', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['attach_id'] );
return FALSE;
}
if( $thumbnailpath AND isset( $info['attach_thumb_location'] ) AND $info['attach_thumb_location'] )
{
$thumbFilename = explode( '/', $info['attach_thumb_location'] );
$thumbFilename = array_pop( $thumbFilename );
/* Figure out the container */
$thumbnailContainer = 'monthly_' . date( 'Y', $info['attach_date'] ) . '_' . date( 'm', $info['attach_date'] );
if( isset( $info['attach_thumb_container'] ) )
{
$thumbnailContainer = $info['attach_thumb_container'];
}
/* Create the file */
try
{
/* We need the file storage to copy the file rather than move it */
\IPS\File::$copyFiles = TRUE;
/* Create the file */
$thumbnail = \IPS\File::create( 'core_Attachment', $thumbFilename, NULL, $thumbnailContainer, TRUE, $thumbnailpath, static::$obscureFilenames );
/* Revert file system to default functionality */
\IPS\File::$copyFiles = FALSE;
$info['attach_thumb_location'] = (string) $thumbnail;
}
catch( \Exception $e )
{
$this->software->app->log( 'attachment_thumb_creation_fail', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['attach_id'] );
}
catch( \ErrorException $e )
{
$this->software->app->log( $e->getMessage(), __METHOD__, \IPS\convert\App::LOG_WARNING, $info['attach_id'] );
}
}
else
{
unset( $info['attach_thumb_location'] );
}
/* Unset container settings */
unset( $info['attach_container'], $info['attach_thumb_container'] );
if ( !isset( $info['attach_filesize'] ) )
{
@@ -1378,12 +1505,22 @@ class _Core extends \IPS\convert\Library
$this->software->app->log( 'edit_history_missing_old', __METHOD__, \IPS\convert\App::LOG_NOTICE, $info['id'] );
$info['old'] = '';
}
else
{
$softwareClass = $this->software;
$info['old'] = $softwareClass::fixPostData( $info['old'] );
}
if ( !isset( $info['new'] ) )
{
$this->software->app->log( 'edit_history_missing_new', __METHOD__, \IPS\convert\App::LOG_NOTICE, $info['id'] );
$info['new'] = '';
}
else
{
$softwareClass = $this->software;
$info['new'] = $softwareClass::fixPostData( $info['new'] );
}
if ( isset( $info['public'] ) )
{
@@ -1506,7 +1643,7 @@ class _Core extends \IPS\convert\Library
try
{
$file = \IPS\File::create( 'core_Emoticons', $info['filename'], $filedata, 'emoticons', FALSE, NULL, FALSE );
$file = \IPS\File::create( 'core_Emoticons', $info['filename'], $filedata, 'emoticons', FALSE, NULL, static::$obscureFilenames );
unset( $info['filename'] );
$info['image'] = (string) $file;
$dims = $file->getImageDimensions();
@@ -1536,7 +1673,7 @@ class _Core extends \IPS\convert\Library
$filedatax2 = file_get_contents( rtrim( $filepathx2, '/' ) . '/' . $info['filenamex2'] );
$filepathx2 = NULL;
}
$filex2 = \IPS\File::create( 'core_Emoticons', $info['filenamex2'], $filedatax2, 'emoticons', FALSE, NULL, FALSE );
$filex2 = \IPS\File::create( 'core_Emoticons', $info['filenamex2'], $filedatax2, 'emoticons', FALSE, NULL, static::$obscureFilenames );
unset( $info['filenamex2'] );
$info['image_2x'] = (string) $filex2;
}
@@ -1635,7 +1772,6 @@ class _Core extends \IPS\convert\Library
$info['follow_notify_meta'] = ( isset( $info['follow_notify_meta'] ) ) ? ( is_array( $info['follow_notify_meta'] ) ? json_encode( $info['follow_notify_meta'] ) : $info['follow_notify_meta'] ) : '';
$info['follow_notify_freq'] = ( isset( $info['follow_notify_freq'] ) AND in_array( $info['follow_notify_freq'], array( 'none', 'immediate', 'daily', 'weekly' ) ) ) ? $info['follow_notify_freq'] : 'none';
$info['follow_visible'] = ( isset( $info['follow_visible'] ) ) ? $info['follow_visible'] : 1;
$info['follow_index_id'] = ( isset( $info['follow_index_id'] ) ) ? $info['follow_index_id'] : NULL;
/* DateTime Stuff */
if ( isset( $info['follow_added'] ) )
@@ -2407,6 +2543,11 @@ class _Core extends \IPS\convert\Library
$this->software->app->log( 'status_reply_no_content', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['reply_id'] );
return FALSe;
}
else
{
$softwareClass = $this->software;
$info['reply_content'] = $softwareClass::fixPostData( $info['reply_content'] );
}
if ( !isset( $info['reply_approved'] ) )
{
@@ -2475,6 +2616,11 @@ class _Core extends \IPS\convert\Library
$this->software->app->log( 'status_update_no_content', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['status_id'] );
return FALSE;
}
else
{
$softwareClass = $this->software;
$info['status_content'] = $softwareClass::fixPostData( $info['status_content'] );
}
if ( !isset( $info['status_replies'] ) )
{
@@ -2532,6 +2678,11 @@ class _Core extends \IPS\convert\Library
$this->software->app->addLink( $inserted_id, $id, 'core_member_status_updates' );
return $inserted_id;
}
/**
* @brief Cache login handlers
*/
protected static $_loginMethods = NULL;
/**
* Convert a Member
@@ -2592,7 +2743,7 @@ class _Core extends \IPS\convert\Library
}
/* Is it using any blocked characters? */
if ( \IPS\Settings::i()->username_characters AND !preg_match( '/^[' . str_replace( '\-', '-', preg_quote( \IPS\Settings::i()->username_characters, '/' ) ) . ']*$/i', $info['name'] ) )
if ( \IPS\Settings::i()->username_characters AND !preg_match( '/^[' . str_replace( '\-', '-', preg_quote( \IPS\Settings::i()->username_characters, '/' ) ) . ']*$/iu', $info['name'] ) )
{
$newName = preg_replace( '/^[' . str_replace( '\-', '-', preg_quote( \IPS\Settings::i()->username_characters, '/' ) ) . ']*$/iu', '', $info['name'] );
$this->software->app->log( sprintf( \IPS\Member::loggedIn()->language()->get( 'member_name_invalid_chars' ), $info['name'], $newName ), __METHOD__, \IPS\convert\App::LOG_NOTICE, $info['member_id'] );
@@ -2676,7 +2827,7 @@ class _Core extends \IPS\convert\Library
$group = \IPS\Settings::i()->member_group;
if ( isset( $info['ips_group_id'] ) )
if ( array_key_exists( 'ips_group_id', $info ) )
{
/* Make sure that the user group is actually valid */
try
@@ -2693,12 +2844,14 @@ class _Core extends \IPS\convert\Library
{
$groupLink = $this->software->app->getLink( $info['member_group_id'], 'core_groups' );
/* Make sure that the user group is actually valid */
try
/* Don't put any users in the guest group, regardless of configuration */
if( $groupLink == \IPS\Settings::i()->guest_group )
{
$group = \IPS\Member\Group::load( $groupLink )->g_id;
throw new \OutOfRangeException;
}
catch( \ OutOfRangeException $ex ) {}
/* Make sure that the user group is actually valid */
$group = \IPS\Member\Group::load( $groupLink )->g_id;
}
catch( \OutOfRangeException $e ) {}
}
@@ -2759,17 +2912,17 @@ class _Core extends \IPS\convert\Library
$info['restrict_post'] = 0;
}
if ( !isset( $info['bday_day'] ) )
if ( !isset( $info['bday_day'] ) OR !$info['bday_day'] )
{
$info['bday_day'] = NULL;
}
if ( !isset( $info['bday_month'] ) )
if ( !isset( $info['bday_month'] ) OR !$info['bday_month'] )
{
$info['bday_month'] = NULL;
}
if ( !isset( $info['bday_year'] ) )
if ( !isset( $info['bday_year'] ) OR !$info['bday_year'] )
{
$info['bday_year'] = NULL;
}
@@ -2949,6 +3102,16 @@ class _Core extends \IPS\convert\Library
$info['members_profile_views'] = 0;
}
/* Enable 'view_sigs' for everyone, unless otherwise defined */
if( !isset( $info['members_bitoptions']['view_sigs'] ) )
{
if( !isset( $info['members_bitoptions'] ) )
{
$info['members_bitoptions'] = array();
}
$info['members_bitoptions']['view_sigs'] = TRUE;
}
$bitoptions = 0;
if ( isset( $info['members_bitoptions'] ) AND is_array( $info['members_bitoptions'] ) )
{
@@ -2975,14 +3138,9 @@ class _Core extends \IPS\convert\Library
}
$info['members_bitoptions2'] = $bitoptions2;
/* These are no longer used */
$info['fb_bwoptions'] = 0;
$info['tc_bwoptions'] = 0;
/* We won't know these */
$info['members_day_posts'] = '0,0';
$info['notification_cnt'] = 0;
$info['ipsconnect_id'] = 0;
if ( isset( $info['pp_last_visitors'] ) )
{
@@ -3003,7 +3161,7 @@ class _Core extends \IPS\convert\Library
{
throw new \OutOfRangeException;
}
$newVisitorId = $this->software->app->getLink( $memberId, 'core_members' )->first();
$newVisitorId = $this->software->app->getLink( $memberId, 'core_members' );
if ( $date instanceof \IPS\DateTime )
{
@@ -3036,7 +3194,7 @@ class _Core extends \IPS\convert\Library
}
/* We need to make sure this is not set as we need it to be something specific later */
unset( $info['pp_photo_type'], $info['pp_main_photo'], $info['pp_thumb_photo'] );
unset( $info['pp_photo_type'], $info['pp_main_photo'], $info['pp_thumb_photo'], $info['pp_cover_photo'] );
/* Profile Photos! */
if ( !is_null( $profilePhotoName ) AND ( !is_null( $profilePhotoPath ) OR !is_null( $profileFileData ) ) )
@@ -3084,7 +3242,8 @@ class _Core extends \IPS\convert\Library
}
$cover = \IPS\File::create( 'core_Profile', $coverPhotoName, $coverFileData );
$info['pp_cover_photo'] = (string) $cover;
$info['pp_cover_offset'] = 0; /* Designs are different so this will never likely be correct */
/* Designs are different so this will never likely be correct, we'll try it if one is supplied though */
$info['pp_cover_offset'] = isset( $info['pp_cover_offset'] ) ? $info['pp_cover_offset'] : 0;
}
catch( \Exception $e )
{
@@ -3130,6 +3289,11 @@ class _Core extends \IPS\convert\Library
{
$info['signature'] = NULL;
}
else
{
$softwareClass = $this->software;
$info['signature'] = $softwareClass::fixPostData( $info['signature'] );
}
if ( isset( $info['pconversation_filters'] ) )
{
@@ -3174,9 +3338,7 @@ class _Core extends \IPS\convert\Library
{
$info['allow_admin_mails'] = FALSE;
}
$info['ipsconnect_revalidate_url'] = NULL;
if ( !isset( $info['members_disable_pm'] ) )
{
$info['members_disable_pm'] = 0;
@@ -3215,15 +3377,22 @@ class _Core extends \IPS\convert\Library
$info['member_last_post'] = NULL;
}
if( isset( $info['fb_uid'] ) )
if( isset( $info['fb_uid'] ) AND !empty( $info['fb_uid'] ) )
{
$info['fb_uid'] = (int) $info['fb_uid'];
$fbUid = $info['fb_uid'];
/* Load, since there's an ID, cache. */
if( static::$_loginMethods === NULL )
{
static::$_loginMethods = iterator_to_array( \IPS\Db::i()->select( '*', 'core_login_methods' )->setKeyField('login_classname') );
}
}
if( isset( $info['fb_token'] ) and empty( $info['fb_token'] ) )
if( isset( $info['fb_token'] ) and !empty( $info['fb_token'] ) )
{
$info['fb_token'] = null;
$fbToken = $info['fb_token'];
}
unset( $info['fb_uid'], $info['fb_token'] );
$info['member_streams'] = NULL;
$info['create_menu'] = NULL;
@@ -3234,6 +3403,19 @@ class _Core extends \IPS\convert\Library
$inserted_id = \IPS\Db::i()->insert( 'core_members', $info );
$this->software->app->addLink( $inserted_id, $id, 'core_members' );
\IPS\Db::i()->replace( 'core_pfields_content', $this->_formatMemberProfileFieldContent( $inserted_id, $profileFields ), TRUE );
/* Social Logins */
if ( isset( $fbUid ) and isset( $fbToken ) and $fbUid and isset( static::$_loginMethods['IPS\\Login\\Handler\\OAuth2\\Facebook'] ) )
{
\IPS\Db::i()->insert( 'core_login_links', array(
'token_login_method' => static::$_loginMethods['IPS\\Login\\Handler\\OAuth2\\Facebook']['login_id'],
'token_member' => $inserted_id,
'token_identifier' => $fbUid,
'token_linked' => TRUE,
'token_access_token' => $fbToken,
) );
}
return $inserted_id;
}
@@ -3258,10 +3440,40 @@ class _Core extends \IPS\convert\Library
return FALSE;
}
if( is_array( $info['log_data'] ) )
if( !is_array( $info['log_data'] ) )
{
$info['log_data'] = json_encode( $info['log_data'] );
$info['log_data'] = json_decode( $info['log_data'], TRUE );
}
/* Convert IDs in log data */
switch( $info['log_type'] )
{
case 'group_promotion':
case 'member_group_id':
try
{
$info['log_data']['new'] = $this->software->app->getLink( $info['log_data']['new'], 'core_groups' );
}
catch( \Exception $e ) {}
break;
case 'mgroup_others':
case 'group_promotion_o':
$groups = array();
foreach( explode( ',', $info['log_data']['new'] ) as $groupId )
{
try
{
$groups[] = $this->software->app->getLink( $groupId, 'core_groups' );
}
catch( \Exception $e ) {}
}
$info['log_data']['new'] = implode( ',', array_unique( $groups ) );
break;
// We purposefully don't re-map warnings, wid should be the result from the convertWarnLog() method call
}
/* JSON Encode history data */
$info['log_data'] = json_encode( $info['log_data'] );
if ( !isset( $info['log_member'] ) )
{
@@ -3683,6 +3895,11 @@ class _Core extends \IPS\convert\Library
$topic['mt_title'] = "Untitled Conversation {$topic['mt_id']}";
$this->software->app->log( 'private_message_topic_title_missing', __METHOD__, \IPS\convert\App::LOG_NOTICE, $topic['mt_id'] );
}
elseif( mb_strlen( $topic['mt_title'] ) > 255 )
{
$topic['mt_title'] = mb_substr( $topic['mt_title'], 0, 255 );
$this->software->app->log( 'private_message_topic_title_truncated', __METHOD__, \IPS\convert\App::LOG_NOTICE, $topic['mt_id'] );
}
/* No Longer Used */
$topic['mt_hasattach'] = 0;
@@ -3782,7 +3999,7 @@ class _Core extends \IPS\convert\Library
/* We already know this */
$map['map_topic_id'] = $topicInsertedId;
if ( !isset( $map['map_folder_id'] ) )
if ( !isset( $map['map_folder_id'] ) OR ( isset( $map['map_folder_id'] ) AND empty( $map['map_folder_id'] ) ) )
{
$map['map_folder_id'] = 'myconvo';
}
@@ -4023,8 +4240,24 @@ class _Core extends \IPS\convert\Library
$info['id'] = $this->software->app->getLink( $info['id'], 'core_groups' );
break;
}
\IPS\Db::i()->insert( 'core_moderators', $info );
try
{
\IPS\Db::i()->insert( 'core_moderators', $info );
}
catch( \IPS\Db\Exception $e )
{
/* duplicate entry */
if( $e->getCode() == 1062 )
{
$this->software->app->log( 'moderator_data_duplicate', __METHOD__, \IPS\convert\App::LOG_WARNING );
return FALSE;
}
else
{
throw $e;
}
}
return TRUE;
}
@@ -4401,6 +4634,18 @@ class _Core extends \IPS\convert\Library
try
{
$info['pf_group_id'] = $this->software->app->getLink( '__orphaned__', 'core_pfields_groups' );
/* Make sure it exists */
try
{
\IPS\Db::i()->select( 'pf_group_id', 'core_pfields_groups', array( 'pf_group_id=?', $info['pf_group_id'] ) )->first();
}
catch( \UnderflowException $e )
{
/* Delete the relation if it no longer exists */
$this->software->app->deleteLink( '__orphaned__', 'core_pfields_groups' );
throw new \OutOfRangeException;
}
}
catch( \OutOfRangeException $e )
{
@@ -4454,7 +4699,36 @@ class _Core extends \IPS\convert\Library
return $inserted_id;
}
/**
* @brief All local custom fields
*/
protected $cachedFields = NULL;
/**
* Get profile fields using cache
*
* @param int $fieldId Custom field id (local)
* @return array
* @throws UnderflowException
*/
protected function _getField( $fieldId )
{
if( $this->cachedFields === NULL )
{
$fields = iterator_to_array( \IPS\Db::i()->select( '*', 'core_pfields_data' )->setKeyField('pf_id') );
$this->cachedFields = ( is_array( $fields ) ) ? $fields : array();
}
if( !isset( $this->cachedFields[ $fieldId ] ) )
{
throw new \UnderflowException;
}
return $this->cachedFields[ $fieldId ];
}
/**
* Format Member Profile Field Content
*
@@ -4484,7 +4758,7 @@ class _Core extends \IPS\convert\Library
$link = $this->software->app->getLink( $id, 'core_pfields_data' );
/* Make sure the field itself was not removed. */
$field = \IPS\Db::i()->select( '*', 'core_pfields_data', array( "pf_id=?", $link ) )->first();
$field = $this->_getField( $link );
}
catch( \OutOfRangeException $e )
{
@@ -4594,7 +4868,19 @@ class _Core extends \IPS\convert\Library
{
$info['starter_id'] = 0;
}
if ( isset( $info['poll_close_date'] ) )
{
if ( $info['poll_close_date'] instanceof \IPS\DateTime )
{
$info['poll_close_date'] = $info['poll_close_date']->getTimestamp();
}
}
else
{
$info['poll_close_date'] = -1;
}
if ( !isset( $info['votes'] ) )
{
$voteCount = 0;
@@ -4623,7 +4909,18 @@ class _Core extends \IPS\convert\Library
$info['poll_view_voters'] = 0;
}
/* Encode our choices... there isn't really a whole lot of checking we can do here */
/* Parse the choices to make sure they don't have any HTML in it we don't allow */
foreach( $info['choices'] as $key => $choice )
{
foreach( $choice['choice'] as $k => $c )
{
$tempParsed = \IPS\Text\LegacyParser::parseStatic( $c, NULL, TRUE );
$info['choices'][ $key ]['choice'][ $k ] = strip_tags( \IPS\Text\Parser::parseStatic( $tempParsed, true, null, null, true, true, true, function( $config ) {
$config->set( 'HTML.AllowedElements', 'a,img' );
} ), '<a><img>' );
}
}
$info['choices'] = json_encode( $info['choices'] );
if ( $hasId )
@@ -4677,7 +4974,7 @@ class _Core extends \IPS\convert\Library
continue;
}
if ( !isset( $vote['ip_address'] ) OR filter_var( $info['ip_address'], FILTER_VALIDATE_IP ) === FALSE )
if ( !isset( $vote['ip_address'] ) OR filter_var( $vote['ip_address'], FILTER_VALIDATE_IP ) === FALSE )
{
$vote['ip_address'] = '127.0.0.1';
}
@@ -4732,7 +5029,12 @@ class _Core extends \IPS\convert\Library
return FALSE;
}
if ( !isset( $info['swop'] ) )
if( !isset( $info['action'] ) OR $info['action'] != 'moderate' )
{
$info['action'] = 'swap';
}
if ( $info['action'] == 'swap' AND !isset( $info['swop'] ) )
{
$this->software->app->log( 'profanity_filter_no_swop', __METHOD__, \IPS\convert\App::LOG_WARNING );
return FALSE;
@@ -5007,6 +5309,101 @@ class _Core extends \IPS\convert\Library
return $inserted_id;
}
/**
* Convert reaction
*
* @param array $info Data to insert
* @param NULL|string $filePath Path to files, or NULL if loading from the database.
* @param NULL|string $fileData If loading from the database, the content of the Binary column.
* @return bool|int ID of new record on success, FALSE on failure
*/
public function convertReaction( $info=array(), $filePath=NULL, $fileData=NULL )
{
if ( !isset( $info['reaction_id'] ) )
{
$this->software->app->log( 'reaction_missing_ids', __METHOD__, \IPS\convert\App::LOG_WARNING );
return FALSE;
}
if ( !isset( $info['reaction_value'] ) )
{
$this->software->app->log( 'reaction_missing_value', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['reaction_id'] );
return FALSE;
}
if ( !isset( $info['filename'] ) )
{
$this->software->app->log( 'reaction_no_filename', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['reaction_id'] );
return FALSE;
}
if ( is_null( $filePath ) AND is_null( $fileData ) )
{
$this->software->app->log( 'reaction_no_file', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['reaction_id'] );
return FALSE;
}
if ( is_null( $fileData ) AND !is_null( $filePath ) )
{
if ( file_exists( rtrim( $filePath, '/' ) . '/' . $info['filename'] ) )
{
$fileData = @file_get_contents( rtrim( $filePath, '/' ) . '/' . $info['filename'] );
$filePath = NULL;
}
else
{
$this->software->app->log( 'reaction_no_file', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['reaction_id'] );
return FALSE;
}
}
if ( !isset( $info['reaction_title'] ) )
{
$name = "Unnamed Reaction";
$this->software->app->log( 'reaction_missing_title', __METHOD__, \IPS\convert\App::LOG_WARNING );
}
else
{
$name = $info['reaction_title'];
unset( $info['reaction_title'] );
}
if( !isset( $info['reaction_position'] ) )
{
$newPosition = (int) \IPS\Db::i()->select( 'MAX(reaction_position) + 1', 'core_reactions' )->first();
$info['reaction_position'] = $newPosition;
}
try
{
$file = \IPS\File::create( 'core_Reaction', $info['filename'], $fileData, 'emoticons', FALSE, NULL );
unset( $info['filename'] );
$info['reaction_icon'] = (string) $file;
$file->getImageDimensions();
}
catch( \Exception $e )
{
$this->software->app->log( 'reaction_file_corrupt', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['reaction_id'] );
return FALSE;
}
if( !isset( $info['reaction_enabled'] ) )
{
$info['reaction_enabled'] = 1;
}
$id = $info['reaction_id'];
unset( $info['reaction_id'] );
$insertedId = \IPS\Db::i()->insert( 'core_reactions', $info );
$this->software->app->addLink( $insertedId, $id, 'core_reactions' );
\IPS\Lang::saveCustom( 'core', "reactionTitle_{$insertedId}", $name );
return $insertedId;
}
/**
* Convert a Report
@@ -5241,6 +5638,13 @@ class _Core extends \IPS\convert\Library
{
try
{
/* Try to lookup a reaction ID link */
try
{
$info['reaction'] = $this->software->app->getLink( $info['reaction'], 'core_reactions' );
}
catch( \OutOfRangeException $e ) {}
$reaction = \IPS\Content\Reaction::load( $info['reaction'] );
}
catch( \OutOfRangeException $e )
@@ -5490,7 +5894,7 @@ class _Core extends \IPS\convert\Library
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $setting['value'] ), array( "conf_key=?", $setting['key'] ) );
}
unset( \IPS\Data\Store::i()->settings );
\IPS\Settings::i()->clearCache();
return $settings;
}
@@ -5748,9 +6152,11 @@ class _Core extends \IPS\convert\Library
$info['cover_photo'] = NULL;
}
}
/* We won't know this */
$info['cover_offset'] = NULL;
if ( !isset( $info['cover_offset'] ) )
{
$info['cover_offset'] = NULL;
}
if ( !isset( $info['featured'] ) )
{
@@ -5879,8 +6285,22 @@ class _Core extends \IPS\convert\Library
{
$info['invited_by'] = NULL;
}
try
{
\IPS\Db::i()->insert( 'core_clubs_memberships', $info );
}
catch( \IPS\Db\Exception $e )
{
/* Duplicate row */
if( $e->getCode() == 1062 )
{
return FALSE;
}
throw $e;
}
\IPS\Db::i()->insert( 'core_clubs_memberships', $info );
return $info['member_id'];
}
}
@@ -90,6 +90,7 @@ class _Downloads extends Core
'per_cycle' => 250,
'dependencies' => $dependencies,
'link_type' => 'downloads_files',
'requires_rebuild' => TRUE
);
break;
@@ -102,6 +103,7 @@ class _Downloads extends Core
'per_cycle' => 1500,
'dependencies' => array( 'convertDownloadsFiles' ),
'link_type' => 'downloads_comments',
'requires_rebuild' => TRUE
);
break;
@@ -114,6 +116,7 @@ class _Downloads extends Core
'per_cycle' => 1500,
'dependencies' => array( 'convertDownloadsFiles' ),
'link_type' => 'downloads_reviews',
'requires_rebuild' => TRUE
);
break;
@@ -178,6 +181,8 @@ class _Downloads extends Core
break;
}
}
$return = $this->software->extraMenuRows( $return );
return $return;
}
@@ -202,7 +207,7 @@ class _Downloads extends Core
switch( $k )
{
case 'convertDownloadsCategories':
$return['convertDownloadsCategories'] = array( 'downloads_categories' => NULL );
$return['convertDownloadsCategories'] = array( 'downloads_categories' => NULL, 'core_permission_index' => array( 'app=? AND perm_type=?', 'downloads', 'category' ) );
break;
case 'convertDownloadsCfields':
@@ -1010,13 +1015,11 @@ class _Downloads extends Core
}
/* We need the file storage to copy the file rather than move it */
\IPS\File\FileSystem::$converterCopy = TRUE;
\IPS\File::$copyFiles = TRUE;
if ( $record['record_type'] == 'link' )
{
$record['record_location'] = $record['file_path'];
unset( $record['file_path'] );
$record['record_size'] = 0;
}
else if ( $record['record_type'] == 'upload' )
@@ -1034,7 +1037,7 @@ class _Downloads extends Core
{
$file = \IPS\File::create( 'downloads_Screenshots', $record['record_realname'], $record['file_data'], NULL, TRUE, $record['file_path'] );
$record['record_location'] = (string) $file;
$record['record_thumb'] = (string) $file->thumbnail( 'downloads_Files' );
$record['record_thumb'] = (string) $file->thumbnail( 'downloads_Screenshots' );
$record['record_size'] = $file->filesize();
}
}
@@ -1050,8 +1053,8 @@ class _Downloads extends Core
}
/* Revert file system to default functionality */
\IPS\File\FileSystem::$converterCopy = FALSE;
unset( $record['file_data'], $file );
\IPS\File::$copyFiles = FALSE;
unset( $record['file_data'], $file, $record['file_path'] );
if ( isset( $record['record_time'] ) )
{
@@ -1239,11 +1242,11 @@ class _Downloads extends Core
$this->software->app->log( 'download_review_missing_ids', __METHOD__, \IPS\convert\App::LOG_WARNING );
return FALSE;
}
if ( empty( $info['review_text'] ) )
/* We'll allow reviews to be posted without text, since they may just be a rating - we allow for this for upgrades from 3.x. */
if ( !isset( $info['review_text'] ) )
{
$this->software->app->log( 'download_review_missing_content', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['review_id'] );
return FALSE;
$info['review_text'] = '';
}
if ( !isset( $info['review_rating'] ) OR $info['review_rating'] < 1 )
@@ -1428,7 +1431,7 @@ class _Downloads extends Core
* @param string|NULL $filedata Binary data for the file, or NULL.
* @return integer|boolean The ID of the newly inserted attachment, or FALSE on failure.
*/
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL )
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL, $thumbnailpath = NULL )
{
$map['location_key'] = 'downloads_Downloads';
$map['id1_type'] = 'downloads_files';
@@ -1449,7 +1452,7 @@ class _Downloads extends Core
$map['id2_type'] = 'downloads_reviews';
}
return parent::convertAttachment( $info, $map, $filepath, $filedata );
return parent::convertAttachment( $info, $map, $filepath, $filedata, $thumbnailpath );
}
/**
+26 -15
View File
@@ -68,13 +68,14 @@ class _Forums extends Core
case 'convertForumsPosts':
$return[ $k ] = array(
'step_title' => 'convert_forums_posts',
'step_method' => 'convertForumsPosts',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'forums_posts' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 1000,
'dependencies' => array( 'convertForumsTopics' ),
'link_type' => 'forums_posts',
'step_title' => 'convert_forums_posts',
'step_method' => 'convertForumsPosts',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'forums_posts' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 1000,
'dependencies' => array( 'convertForumsTopics' ),
'link_type' => 'forums_posts',
'requires_rebuild' => TRUE
);
break;
@@ -201,6 +202,8 @@ class _Forums extends Core
break;
}
}
$return = $this->software->extraMenuRows( $return );
return $return;
}
@@ -244,7 +247,8 @@ class _Forums extends Core
$return['convertForumsForums'] = array(
'core_follow' => array( 'follow_app=? AND follow_area=?', 'forums', 'forum' ),
'core_clubs_node_map' => array( "node_class=?", "IPS\\forums\\Forum" ),
'forums_forums' => NULL
'forums_forums' => NULL,
'core_permission_index' => array( 'app=? AND perm_type=?', 'forums', 'forum' )
);
break;
@@ -800,6 +804,8 @@ class _Forums extends Core
'parent_id' => -1
) );
}
$info['club_id'] = NULL;
}
}
}
@@ -985,7 +991,11 @@ class _Forums extends Core
}
else
{
$info['last_poster_name'] = $info['starter_name'];
/*
* We want to make sure that we're not setting NULL here, although allowed in some places it can
* cause issues with setting the last comment during the post-conversion topic deletion task.
*/
$info['last_poster_name'] = $info['starter_name'] ?: '';
}
}
@@ -2050,13 +2060,14 @@ class _Forums extends Core
/**
* Convert an Attachment
*
* @param array $info Data to insert
* @param array $map Array of Map data
* @param NULL|string $filepath The path to the attachment files or NULL if loading from the database.
* @param NULL|string $filedata If loading from the database, the content of the Binary column.
* @param array $info Data to insert
* @param array $map Array of Map data
* @param NULL|string $filepath The path to the attachment files or NULL if loading from the database.
* @param NULL|string $filedata If loading from the database, the content of the Binary column.
* @param NULL|string $thumnailpath Path to thumbnail image
* @return boolean|integer The ID of the newly inserted attachment, or FALSE on failure.
*/
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL )
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL, $thumbnailpath=NULL )
{
/* The Core Library handles most of this. We just need to make sure forum specific data is set. */
$map['id1_type'] = 'forums_topics';
@@ -2065,7 +2076,7 @@ class _Forums extends Core
$map['id2_from_parent'] = FALSE;
$map['location_key'] = 'forums_Forums';
return parent::convertAttachment( $info, $map, $filepath, $filedata );
return parent::convertAttachment( $info, $map, $filepath, $filedata, $thumbnailpath );
}
/**
+426 -46
View File
@@ -70,6 +70,32 @@ class _Gallery extends Core
'link_type' => 'gallery_albums',
);
break;
case 'convertGalleryAlbumComments':
$return[ $k ] = array(
'step_method' => 'convertGalleryAlbumComments',
'step_title' => 'convert_gallery_album_comments',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'gallery_album_comments' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 1500,
'dependencies' => array( 'convertGalleryAlbums' ),
'link_type' => 'gallery_album_comments',
'requires_rebuild' => TRUE
);
break;
case 'convertGalleryAlbumReviews':
$return[ $k ] = array(
'step_method' => 'convertGalleryAlbumReviews',
'step_title' => 'convert_gallery_album_reviews',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'gallery_album_reviews' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 1500,
'dependencies' => array( 'convertGalleryAlbums' ),
'link_type' => 'gallery_album_reviews',
'requires_rebuild' => TRUE
);
break;
case 'convertGalleryImages':
$dependencies = array();
@@ -93,6 +119,7 @@ class _Gallery extends Core
'per_cycle' => 100,
'dependencies' => $dependencies,
'link_type' => 'gallery_images',
'requires_rebuild' => TRUE
);
break;
@@ -105,6 +132,7 @@ class _Gallery extends Core
'per_cycle' => 1500,
'dependencies' => array( 'convertGalleryImages' ),
'link_type' => 'gallery_comments',
'requires_rebuild' => TRUE
);
break;
@@ -117,6 +145,7 @@ class _Gallery extends Core
'per_cycle' => 1500,
'dependencies' => array( 'convertGalleryImages' ),
'link_type' => 'gallery_reviews',
'requires_rebuild' => TRUE
);
break;
@@ -193,6 +222,8 @@ class _Gallery extends Core
break;
}
}
$return = $this->software->extraMenuRows( $return );
return $return;
}
@@ -217,12 +248,24 @@ class _Gallery extends Core
switch( $k )
{
case 'convertGalleryCategories':
$return['convertGalleryCategories'] = array( 'gallery_categories' => NULL, 'core_clubs_node_map' => array( "node_class=?", "IPS\\gallery\\Category" ) );
$return['convertGalleryCategories'] = array(
'gallery_categories' => NULL,
'core_clubs_node_map' => array( "node_class=?", "IPS\\gallery\\Category" ),
'core_permission_index' => array( 'app=? AND perm_type=?', 'gallery', 'category' )
);
break;
case 'convertGalleryAlbums':
$return['convertGalleryAlbums'] = array( 'gallery_albums' => NULL );
break;
case 'convertGalleryAlbumComments':
$return['convertGalleryAlbumComments'] = array( 'gallery_album_comments' => NULL );
break;
case 'convertGalleryAlbumReviews':
$return['convertGalleryAlbumReviews'] = array( 'gallery_album_reviews' => NULL );
break;
case 'convertGalleryImages':
$return['convertGalleryImages'] = array( 'gallery_images' => NULL );
@@ -520,6 +563,11 @@ class _Gallery extends Core
{
$info['album_allow_rating'] = 1;
}
if ( !isset( $info['album_submit_type'] ) )
{
$info['album_submit_type'] = 0;
}
if ( !isset( $info['album_position'] ) )
{
@@ -573,7 +621,21 @@ class _Gallery extends Core
{
try
{
return $this->software->app->getLink( '__orphaned__', 'gallery_categories' );
$id = $this->software->app->getLink( '__orphaned__', 'gallery_categories' );
/* Check the category exists */
try
{
\IPS\gallery\Category::load( $id );
}
catch( \OutOfRangeException $e )
{
/* Delete the relation if it no longer exists */
$this->software->app->deleteLink( '__orphaned__', 'gallery_categories' );
throw new \OutOfRangeException;
}
return $id;
}
catch( \OutOfRangeException $e )
{
@@ -612,7 +674,7 @@ class _Gallery extends Core
if ( isset( $info['image_album_id'] ) )
{
/* We only want to do this if the Album ID is explicitly not 0, as that means it has no album, which is okay */
if ( $info['image_album_id'] > 0 )
if ( !empty( $info['image_album_id'] ) )
{
try
{
@@ -683,26 +745,6 @@ class _Gallery extends Core
}
}
if ( !isset( $info['image_caption'] ) )
{
if ( !is_null( $filepath ) )
{
$caption = explode( '/', $info['image_caption'] );
$caption = array_pop( $caption );
}
else
{
$caption = "Untitled Image {$info['image_id']}";
}
$info['image_caption'] = $caption;
}
if ( mb_strlen( $info['image_caption'] ) > 255 )
{
$info['image_caption'] = mb_substr( $info['image_caption'], 0, 200 );
}
if ( !isset( $info['image_description'] ) )
{
$info['image_description'] = NULL;
@@ -710,9 +752,7 @@ class _Gallery extends Core
/* We will build these later */
$info['image_masked_file_name'] = NULL;
$info['image_medium_file_name'] = NULL;
$info['image_small_file_name'] = NULL;
$info['image_thumb_file_name'] = NULL;
/* Figure this out before we bother creating the image */
if ( !isset( $info['image_file_type'] ) )
@@ -735,7 +775,7 @@ class _Gallery extends Core
{
$file_name = explode( '/', $filepath );
$file_name = array_pop( $file_name );
$info['image_file_name'] = $file_name;
$info['image_file_name'] = mb_substr( $file_name, 0, 255 );
}
else
{
@@ -744,6 +784,54 @@ class _Gallery extends Core
return FALSE;
}
}
else
{
$info['image_file_name'] = mb_substr( $info['image_file_name'], 0, 255 );
}
/* Image Caption */
if ( !isset( $info['image_caption'] ) )
{
if ( !is_null( $filepath ) )
{
$caption = explode( '/', $info['image_file_name'] );
$caption = array_pop( $caption );
}
else
{
$caption = "Untitled Image {$info['image_id']}";
}
$info['image_caption'] = $caption;
}
if ( mb_strlen( $info['image_caption'] ) > 255 )
{
$info['image_caption'] = mb_substr( $info['image_caption'], 0, 200 );
}
/* Dates */
foreach( array( 'image_date', 'image_last_comment', 'image_updated' ) AS $date )
{
if ( isset( $info[ $date ] ) )
{
if ( $info[ $date ] instanceof \IPS\DateTime )
{
$info[ $date ] = $info[ $date ]->getTimestamp();
}
}
else
{
$info[ $date ] = time();
}
}
/* Figure out the container */
$container = 'monthly_' . date( 'Y', $info['image_date'] ) . '_' . date( 'm', $info['image_date'] );
if( isset( $info['image_container'] ) )
{
$container = $info['image_container'];
}
/* Okay, let's create our image. We can use data returned from this later if it's missing. */
try
@@ -758,7 +846,7 @@ class _Gallery extends Core
$filedata = file_get_contents( $filepath );
}
$image = \IPS\File::create( 'gallery_Images', $info['image_file_name'], $filedata );
$image = \IPS\File::create( 'gallery_Images', $info['image_file_name'], $filedata, $container );
$info['image_original_file_name'] = (string) $image;
if ( !isset( $info['image_file_size'] ) )
@@ -790,23 +878,7 @@ class _Gallery extends Core
$info[ $count ] = 0;
}
}
/* Dates */
foreach( array( 'image_date', 'image_last_comment', 'image_updated' ) AS $date )
{
if ( isset( $info[ $date ] ) )
{
if ( $info[ $date ] instanceof \IPS\DateTime )
{
$info[ $date ] = $info[ $date ]->getTimestamp();
}
}
else
{
$info[ $date ] = time();
}
}
if ( !isset( $info['image_pinned'] ) )
{
$info['image_pinned'] = 0;
@@ -935,6 +1007,314 @@ class _Gallery extends Core
return $inserted_id;
}
/**
* Convert an album comment
*
* @param array $info Data to insert
* @return integer|boolean The ID of the newly inserted comment, or FALSE on failure.
*/
public function convertGalleryAlbumComment( $info=array() )
{
if ( !isset( $info['comment_id'] ) )
{
$this->software->app->log( 'gallery_album_comment_missing_ids', __METHOD__, \IPS\convert\App::LOG_WARNING );
return FALSE;
}
if ( empty( $info['comment_text'] ) )
{
$this->software->app->log( 'gallery_album_comment_missing_content', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['comment_id'] );
return FALSE;
}
if ( isset( $info['comment_album_id'] ) )
{
try
{
$info['comment_album_id'] = $this->software->app->getLink( $info['comment_album_id'], 'gallery_albums' );
}
catch( \OutOfRangeException $e )
{
$this->software->app->log( 'gallery_album_comment_missing_image', __METHOD__, \IPS\convert\ApP::LOG_WARNING, $info['comment_id'] );
return FALSE;
}
}
else
{
$this->software->app->log( 'gallery_album_omment_missing_image', __METHOD__, \IPS\convert\ApP::LOG_WARNING, $info['comment_id'] );
return FALSE;
}
if ( isset( $info['comment_edit_time'] ) )
{
if ( $info['comment_edit_time'] instanceof \IPS\DateTime )
{
$info['comment_edit_time'] = $info['comment_edit_time']->getTimestamp();
}
}
else
{
$info['comment_edit_time'] = 0;
}
if ( isset( $info['comment_author_id'] ) )
{
try
{
$info['comment_author_id'] = $this->software->app->getLink( $info['comment_author_id'], 'core_members', TRUE );
}
catch( \OutOfRangeException $e )
{
$info['comment_author_id'] = 0;
}
}
else
{
$info['comment_author_id'] = 0;
}
if ( !isset( $info['comment_author_name'] ) )
{
$author = \IPS\Member::load( $info['comment_author_id'] );
if ( $author->member_id )
{
$info['comment_author_name'] = $author->name;
}
else
{
$info['comment_author_name'] = "Guest";
}
}
if ( !isset( $info['comment_ip_address'] ) OR filter_var( $info['comment_ip_address'], FILTER_VALIDATE_IP ) === FALSE )
{
$info['comment_ip_address'] = '127.0.0.1';
}
if ( isset( $info['comment_post_date'] ) )
{
if ( $info['comment_post_date'] instanceof \IPS\DateTime )
{
$info['comment_post_date'] = $info['comment_post_date']->getTimestamp();
}
}
else
{
$info['comment_post_date'] = time();
}
if ( !isset( $info['comment_approved'] ) )
{
$info['comment_approved'] = 1;
}
if ( !isset( $info['comment_append_edit'] ) )
{
$info['comment_append_edit'] = 0;
}
if ( !isset( $info['comment_edit_name'] ) )
{
$info['comment_edit_name'] = NULL;
}
$id = $info['comment_id'];
unset( $info['comment_id'] );
$inserted_id = \IPS\Db::i()->insert( 'gallery_album_comments', $info );
$this->software->app->addLink( $inserted_id, $id, 'gallery_album_comments' );
return $inserted_id;
}
/**
* Convert an album review
*
* @param array $info Data to insert
* @return integer|boolean The ID of the newly inserted reiew, or FALSE on failure.
*/
public function convertGalleryAlbumReview( $info=array() )
{
if ( !isset( $info['review_id'] ) )
{
$this->software->app->log( 'gallery_album_review_missing_ids', __METHOD__, \IPS\convert\App::LOG_WARNING );
return FALSE;
}
if ( empty( $info['review_content'] ) )
{
$this->software->app->log( 'gallery_album_review_missing_content', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['review_id'] );
return FALSE;
}
if ( !isset( $info['review_rating'] ) OR $info['review_rating'] < 1 )
{
$this->software->app->log( 'gallery_album_review_missing_rating', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['review_id'] );
return FALSE;
}
if ( isset( $info['review_author'] ) )
{
try
{
$info['review_author'] = $this->software->app->getLink( $info['review_author'], 'core_members', TRUE );
}
catch( \OutOfRangeException $e )
{
$this->software->app->log( 'gallery_album_review_missing_author', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['review_id'] );
return FALSE;
}
}
else
{
$this->software->app->log( 'gallery_album_review_missing_author', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['review_id'] );
return FALSE;
}
if ( isset( $info['review_album_id'] ) )
{
try
{
$info['review_album_id'] = $this->software->app->getLink( $info['review_album_id'], 'gallery_albums' );
}
catch( \OutOfRangeException $e )
{
$this->software->app->log( 'gallery_album_review_missing_album', __METHOD__, \IPS\convert\App::LOG_WARNING, $info['review_id'] );
return FALSE;
}
}
if ( !isset( $info['review_author_name'] ) )
{
$info['review_author_name'] = \IPS\Member::load( $info['review_author'] )->name;
}
if ( isset( $info['review_date'] ) )
{
if ( $info['review_date'] instanceof \IPS\DateTime )
{
$info['review_date'] = $info['review_date']->getTimestamp();
}
}
else
{
$info['review_date'] = time();
}
if ( !isset( $info['review_ip_address'] ) OR filter_var( $info['review_ip_address'], FILTER_VALIDATE_IP ) === FALSE )
{
$info['review_ip_address'] = '127.0.0.1';
}
if ( isset( $info['review_edit_time'] ) )
{
if ( $info['review_edit_time'] instanceof \IPS\DateTime )
{
$info['review_edit_time'] = $info['review_edit_time']->getTimestamp();
}
}
else
{
$info['review_edit_time'] = 0;
}
if ( !isset( $info['review_edit_member_name'] ) )
{
$info['review_edit_member_name'] = '';
}
if ( !isset( $info['review_edit_show'] ) )
{
$info['review_edit_show'] = 0;
}
if ( isset( $info['review_votes_data'] ) )
{
if ( !is_array( $info['review_votes_data'] ) )
{
$info['review_votes_data'] = json_decode( $info['review_votes_data'], TRUE );
}
$newVoters = array();
if ( !is_null( $info['review_votes_data'] ) AND count( $info['review_votes_data'] ) )
{
foreach( $info['review_votes_data'] AS $member => $vote )
{
try
{
$memberId = $this->software->app->getLink( $member, 'core_members', TRUE );
}
catch( \OutOfRangeException $e )
{
continue;
}
$newVoters[ $memberId ] = $vote;
}
}
if ( count( $newVoters ) )
{
$info['review_votes_data'] = json_encode( $newVoters );
}
else
{
$info['review_votes_data'] = NULL;
}
}
else
{
$info['review_votes_data'] = NULL;
}
if ( !isset( $info['review_votes'] ) )
{
if ( is_null( $info['review_votes_data'] ) )
{
$info['review_votes'] = 0;
}
else
{
$info['review_votes'] = count( json_decode( $info['review_votes_data'], TRUE ) );
}
}
if ( !isset( $info['review_votes_helpful'] ) )
{
if ( is_null( $info['review_votes_data'] ) )
{
$info['review_votes_helpful'] = 0;
}
else
{
$helpful = 0;
foreach( json_decode( $info['review_votes_data'], TRUE ) AS $member => $vote )
{
if ( $vote == 1 )
{
$helpful += 1;
}
}
$info['review_votes_helpful'] = $helpful;
}
}
if ( !isset( $info['review_approved'] ) )
{
$info['review_approved'] = 1;
}
$id = $info['review_id'];
unset( $info['review_id'] );
$inserted_id = \IPS\Db::i()->insert( 'gallery_album_reviews', $info );
$this->software->app->addLink( $inserted_id, $id, 'gallery_album_reviews' );
return $inserted_id;
}
/**
* Convert a comment
@@ -1253,7 +1633,7 @@ class _Gallery extends Core
* @param string|NULL $filedata The binary data of the attachment, or NULL.
* @return integer|boolean The ID of the newly inserted attachment, or FALSE on failure.
*/
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL )
public function convertAttachment( $info=array(), $map=array(), $filepath=NULL, $filedata=NULL, $thumbnailpath = NULL )
{
$map['location_key'] = 'gallery_Gallery';
$map['id1_type'] = 'gallery_images';
@@ -1274,7 +1654,7 @@ class _Gallery extends Core
$map['id2_type'] = 'gallery_reviews';
}
return parent::convertAttachment( $info, $map, $filepath, $filedata );
return parent::convertAttachment( $info, $map, $filepath, $filedata, $thumbnailpath );
}
/**
+34 -28
View File
@@ -153,25 +153,27 @@ class _Nexus extends \IPS\convert\Library
}
$return[ $k ] = array(
'step_title' => 'convert_nexus_packages',
'step_method' => 'convertNexusPackages',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'nexus_packages' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 100,
'dependencies' => $dependencies,
'link_type' => 'nexus_packages',
'step_title' => 'convert_nexus_packages',
'step_method' => 'convertNexusPackages',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'nexus_packages' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 100,
'dependencies' => $dependencies,
'link_type' => 'nexus_packages',
'requires_rebuild' => TRUE
);
break;
case 'convertNexusReviews':
$return[ $k ] = array(
'step_title' => 'convert_nexus_reviews',
'step_method' => 'convertNexusReviews',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'nexus_reviews' )->first(),
'source_rows' => $this->software->sourceRows( $v['table'], $v['where'] ),
'per_cycle' => 1500,
'dependencies' => array( 'convertNexusPackages' ),
'link_type' => 'nexus_reviews',
'step_title' => 'convert_nexus_reviews',
'step_method' => 'convertNexusReviews',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'nexus_reviews' )->first(),
'source_rows' => $this->software->sourceRows( $v['table'], $v['where'] ),
'per_cycle' => 1500,
'dependencies' => array( 'convertNexusPackages' ),
'link_type' => 'nexus_reviews',
'requires_rebuild' => TRUE
);
break;
@@ -296,30 +298,34 @@ class _Nexus extends \IPS\convert\Library
case 'convertNexusSupportRequests':
$return[ $k ] = array(
'step_title' => 'convert_nexus_support_requests',
'step_method' => 'convertNexusSupportRequests',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'nexus_support_requests' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 1500,
'dependencies' => array( 'convertNexusCustomers', 'convertNexusSupportDepartments', 'convertNexusSupportFields' ),
'link_type' => 'nexus_support_requests',
'step_title' => 'convert_nexus_support_requests',
'step_method' => 'convertNexusSupportRequests',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'nexus_support_requests' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 1500,
'dependencies' => array( 'convertNexusCustomers', 'convertNexusSupportDepartments', 'convertNexusSupportFields' ),
'link_type' => 'nexus_support_requests',
'requires_rebuild' => TRUE
);
break;
case 'convertNexusSupportReplies':
$return[ $k ] = array(
'step_title' => 'convert_nexus_support_replies',
'step_method' => 'convertNexusSupportReplies',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'nexus_support_replies' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 2000,
'dependencies' => array( 'convertNexusSupportRequests' ),
'link_type' => 'nexus_support_replies'
'step_title' => 'convert_nexus_support_replies',
'step_method' => 'convertNexusSupportReplies',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'nexus_support_replies' )->first(),
'source_rows' => $this->software->countRows( $v['table'], $v['where'] ),
'per_cycle' => 2000,
'dependencies' => array( 'convertNexusSupportRequests' ),
'link_type' => 'nexus_support_replies',
'requires_rebuild' => TRUE
);
break;
}
}
$return = $this->software->extraMenuRows( $return );
return $return;
}
+689
View File
@@ -0,0 +1,689 @@
<?php
/**
* @brief Converter Login Handler
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 15 October 2017
*/
namespace IPS\convert;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Standard Internal Database Login Handler
*/
class _Login extends \IPS\Login\Handler
{
use \IPS\Login\Handler\UsernamePasswordHandler;
/**
* Get title
*
* @return string
*/
public static function getTitle()
{
return 'login_handler_Convert';
}
/**
* Authenticate
*
* @param \IPS\Login $login The login object
* @param string $usernameOrEmail The username or email address provided by the user
* @param string $password The plaintext password provided by the user
* @return \IPS\Member
* @throws \IPS\Login\Exception
*/
public function authenticateUsernamePassword( \IPS\Login $login, $usernameOrEmail, $password )
{
/* Get member(s) */
$where = array();
$params = array();
if ( $this->authType() & \IPS\Login::AUTH_TYPE_USERNAME )
{
$where[] = 'name=?';
$params[] = $usernameOrEmail;
if ( $usernameOrEmail !== \IPS\Request::legacyEscape( $usernameOrEmail ) )
{
$where[] = 'name=?';
$params[] = \IPS\Request::legacyEscape( $usernameOrEmail );
}
}
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
{
$where[] = 'email=?';
$params[] = $usernameOrEmail;
if ( $usernameOrEmail !== \IPS\Request::legacyEscape( $usernameOrEmail ) )
{
$where[] = 'email=?';
$params[] = \IPS\Request::legacyEscape( $usernameOrEmail );
}
}
$members = new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array_merge( array( implode( ' OR ', $where ) ), $params ) ), 'IPS\Member' );
/* If we didn't match any, throw an exception */
if ( !count( $members ) )
{
$type = 'username_or_email';
switch ( $this->authType() )
{
case \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL:
$type = 'username_or_email';
break;
case \IPS\Login::AUTH_TYPE_USERNAME:
$type = 'username';
break;
case \IPS\Login::AUTH_TYPE_EMAIL:
$type = 'email_address';
break;
}
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack('login_err_no_account', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $type ) ) ) ), \IPS\Login\Exception::NO_ACCOUNT );
}
/* Table switcher for new converters */
try
{
foreach( $members as $member )
{
if( $this->authenticatePasswordForMember( $member, $password ) )
{
return $member;
}
}
}
catch( \IPS\Db\Exception $e )
{
/* Converter tables no longer exist */
if( $e->getCode() == 1146 )
{
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
}
}
/* Still here? Throw a password incorrect exception */
throw new \IPS\Login\Exception( 'login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL, isset( $member ) ? $member : NULL );
}
/**
* @brief Convert app cache
*/
protected static $_apps;
/**
* Authenticate
*
* @param \IPS\Member $member The member
* @param string $password The plaintext password provided by the user
* @return bool
*/
public function authenticatePasswordForMember( \IPS\Member $member, $password )
{
if( static::$_apps === NULL )
{
static::$_apps = new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'convert_apps', array( 'login=?', 1 ) ), 'IPS\convert\App' );
}
foreach( static::$_apps as $app )
{
/* Strip underscores from keys */
$sw = str_replace( "_", "", $app->key );
/* Get converter classname */
try
{
$application = $app->getSource( FALSE, FALSE );
}
/* Converter application class no longer exists, but we want to continue since we may have a login method here */
catch( \InvalidArgumentException $e )
{
$application = NULL;
}
/* Check at least one of the login methods exist */
if ( !method_exists( $this, $sw ) AND ( $application === NULL OR !method_exists( $application, 'login' ) ) )
{
continue;
}
/* We still want to use the parent methods (no sense in recreating them) so copy conv_password_extra to misc */
$member->misc = $member->conv_password_extra;
/* New login method */
if( class_exists( $application ) AND method_exists( $application, 'login' ) )
{
$success = $application::login( $member, $password );
}
/* Deprecated method */
else
{
$success = $this->$sw( $member, $password );
}
unset( $member->misc );
unset( $member->changed['misc'] );
if ( $success )
{
/* Update password and return */
$member->conv_password = NULL;
$member->conv_password_extra = NULL;
$member->setLocalPassword( $password );
$member->save();
$member->memberSync( 'onPassChange', array( $password ) );
return $member;
}
}
return FALSE;
}
/**
* Can this handler process a login for a member?
*
* @return bool
*/
public function canProcess( \IPS\Member $member )
{
return (bool) $member->conv_password;
}
/**
* Can this handler process a password change for a member?
*
* @return bool
*/
public function canChangePassword( \IPS\Member $member )
{
return FALSE;
}
/**
* Change Password
*
* @param \IPS\Member $member The member
* @param string $newPassword New Password
* @return void
*/
public function changePassword( \IPS\Member $member, $newPassword )
{
$member->setLocalPassword( $newPassword );
$member->save();
}
/**
* Show in Account Settings?
*
* @param \IPS\Member|NULL $member The member, or NULL for if it should show generally
* @return bool
*/
public function showInUcp( \IPS\Member $member = NULL )
{
return FALSE;
}
/**
* AEF
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function aef( $member, $password )
{
if ( \IPS\Login::compareHashes( $member->conv_password, md5( $member->misc . $password ) ) )
{
return TRUE;
}
else
{
return FALSE;
}
}
/**
* BBPress Standalone
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function bbpressstandalone( $member, $password )
{
return $this->bbpress( $member, $password );
}
/**
* BBPress
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function bbpress( $member, $password )
{
$success = false;
$password = html_entity_decode( $password );
$hash = $member->conv_password;
if ( \strlen( $hash ) == 32 )
{
$success = ( bool ) ( \IPS\Login::compareHashes( $member->conv_password, md5( $password ) ) );
}
// Nope, not md5.
if ( ! $success )
{
$hashLibrary = new \IPS\convert\Login\HashCryptPrivate;
$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$crypt = $hashLibrary->hashCryptPrivate( $password, $hash, $itoa64, 'P' );
if ( $crypt[ 0 ] == '*' )
{
$crypt = crypt( $password, $hash );
}
if ( $crypt == $hash )
{
$success = true;
}
}
// Nope
if ( ! $success )
{
// No - check against WordPress.
// Note to self - perhaps push this to main bbpress method.
$success = \IPS\convert\Software\Core\Wordpress::login( $member, $password );
}
return $success;
}
/**
* BBPress 2.3
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function bbpress23( $member, $password )
{
return $this->bbpress( $member, $password );
}
/**
* Community Server
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function cs( $member, $password )
{
$encodedHashPass = base64_encode( pack( "H*", sha1( base64_decode( $member->misc ) . $password ) ) );
if ( \IPS\Login::compareHashes( $member->conv_password, $encodedHashPass ) )
{
return TRUE;
}
else
{
return FALSE;
}
}
/**
* CSAuth
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function csauth( $member, $password )
{
$wsdl = 'https://internal.auth.com/Service.asmx?wsdl';
$dest = 'https://interal.auth.com/Service.asmx';
$single_md5_pass = md5( $password );
try
{
$client = new SoapClient( $wsdl, array( 'trace' => 1 ) );
$client->__setLocation( $dest );
$loginparams = array( 'username' => $member->name, 'password' => $password );
$result = $client->AuthCS( $loginparams );
switch( $result->AuthCSResult )
{
case 'SUCCESS' :
return TRUE;
case 'WRONG_AUTH' :
return FALSE;
default :
return FALSE;
}
}
catch( Exception $ex )
{
return FALSE;
}
}
/**
* Discuz
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function discuz( $member, $password )
{
if ( \IPS\Login::compareHashes( $member->conv_password, md5( md5( $password ) . $member->misc ) ) )
{
return TRUE;
}
return FALSE;
}
/**
* FudForum
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function fudforum( $member, $password )
{
$success = false;
$single_md5_pass = md5( $password );
$hash = $member->conv_password;
if ( \strlen( $hash ) == 40 )
{
$success = ( \IPS\Login::compareHashes( $member->conv_password, sha1( $member->misc . sha1( $password ) ) ) ) ? TRUE : FALSE;
}
else
{
$success = ( \IPS\Login::compareHashes( $member->conv_password, $single_md5_pass ) ) ? TRUE : FALSE;
}
return $success;
}
/**
* FusionBB
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function fusionbb( $member, $password )
{
/* FusionBB Has multiple methods that can be used to check a hash, so we need to cycle through them */
/* md5( md5( salt ) . md5( pass ) ) */
if ( \IPS\Login::compareHashes( $member->conv_password, md5( md5( $member->misc ) . md5( $password ) ) ) )
{
return TRUE;
}
/* md5( md5( salt ) . pass ) */
if ( \IPS\Login::compareHashes( $member->conv_password, md5( md5( $member->misc ) . $password ) ) )
{
return TRUE;
}
/* md5( pass ) */
if ( \IPS\Login::compareHashes( $member->conv_password, md5( $password ) ) )
{
return TRUE;
}
return FALSE;
}
/**
* Ikonboard
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function ikonboard( $member, $password )
{
if ( \IPS\Login::compareHashes( $member->conv_password, crypt( $password, $member->misc ) ) )
{
return TRUE;
}
else if ( \IPS\Login::compareHashes( $member->conv_password, md5( $password . mb_strtolower( $member->conv_password_extra ) ) ) )
{
return TRUE;
}
else
{
return FALSE;
}
}
/**
* Kunena
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function kunena( $member, $password )
{
// Kunena authenticates using internal Joomla functions.
// This is required, however, if the member only converts from
// Kunena and not Joomla + Kunena.
return \IPS\convert\Software\Core\Joomla::login( $member, $password );
}
/**
* PHP Legacy (2.x)
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function phpbblegacy( $member, $password )
{
return \IPS\convert\Software\Core\Phpbb::login( $member, $password );
}
/**
* Vbulletin 5.1
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function vb51connect( $member, $password )
{
return \IPS\convert\Software\Core\Vbulletin5::login( $member, $password );
}
/**
* Vbulletin 5
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function vb5connect( $member, $password )
{
return \IPS\convert\Software\Core\Vbulletin5::login( $member, $password );
}
/**
* Vbulletin 3.8
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function vbulletinlegacy( $member, $password )
{
return \IPS\convert\Software\Core\Vbulletin::login( $member, $password );
}
/**
* Vbulletin 3.6
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function vbulletinlegacy36( $member, $password )
{
return \IPS\convert\Software\Core\Vbulletin::login( $member, $password );
}
/**
* SMF Legacy
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function smflegacy( $member, $password )
{
if ( \IPS\Login::compareHashes( $member->conv_password, sha1( mb_strtolower( $member->name ) . $password ) ) )
{
return TRUE;
}
else
{
return FALSE;
}
}
/**
* Telligent
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function telligentcs( $member, $password )
{
return $this->cs( $member, $password );
}
/**
* WoltLab 4.x
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function woltlab( $member, $password )
{
$testHash = FALSE;
/* If it's not blowfish, then we don't have a salt for it. */
if ( !preg_match( '/^\$2[ay]\$(0[4-9]|[1-2][0-9]|3[0-1])\$[a-zA-Z0-9.\/]{53}/', $member->conv_password ) )
{
$salt = mb_substr( $member->conv_password, 0, 29 );
$testHash = crypt( crypt( $password, $salt ), $salt );
}
if ( $testHash AND \IPS\Login::compareHashes( $member->conv_password, $testHash ) )
{
return TRUE;
}
elseif ( $this->woltlablegacy( $member, $password ) )
{
return TRUE;
}
return FALSE;
}
/**
* WoltLab 3.x
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function woltlablegacy( $member, $password )
{
if ( \IPS\Login::compareHashes( $member->conv_password, sha1( $member->misc . sha1( $member->misc . sha1( $password ) ) ) ) )
{
return TRUE;
}
else
{
return FALSE;
}
}
/**
* PHP Fusion
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function phpfusion( $member, $password )
{
return ( bool ) \IPS\Login::compareHashes( $member->conv_password, md5( md5( $password ) ) );
}
/**
* fluxBB
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function fluxbb( $member, $password )
{
$success = false;
$hash = $member->conv_password;
if ( \strlen( $hash ) == 40 )
{
if ( \IPS\Login::compareHashes( $hash, sha1( $member->misc . sha1( $password ) ) ) )
{
$success = TRUE;
}
elseif ( \IPS\Login::compareHashes( $hash, sha1( $password ) ) )
{
$success = TRUE;
}
}
else
{
$success = ( \IPS\Login::compareHashes( $hash, md5( $password ) ) ) ? TRUE : FALSE;
}
return $success;
}
/**
* Simplepress Forum
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
protected function simplepress( $member, $password )
{
return \IPS\convert\Software\Core\Wordpress::login( $member, $password );
}
}
@@ -0,0 +1,132 @@
<?php
namespace IPS\convert\Login;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
class _HashCryptPrivate
{
/**
* Private crypt hashing
*
* @access public
* @param string Password
* @param string Settings
* @param string Hash-lookup
* @return string password hash
*/
public function hashCryptPrivate( $password, $setting, &$itoa64 )
{
$output = '*';
// Check for correct hash
if ( \substr( $setting, 0, 3 ) != '$H$' )
{
return $output;
}
$count_log2 = \strpos( $itoa64, $setting[3] );
if ( $count_log2 < 7 || $count_log2 > 30 )
{
return $output;
}
$count = 1 << $count_log2;
$salt = \substr( $setting, 4, 8 );
if ( \strlen($salt) != 8 )
{
return $output;
}
/**
* We're kind of forced to use MD5 here since it's the only
* cryptographic primitive available in all versions of PHP
* currently in use. To implement our own low-level crypto
* in PHP would result in much worse performance and
* consequently in lower iteration counts and hashes that are
* quicker to crack (by non-PHP code).
*/
if ( PHP_VERSION >= 5 )
{
$hash = md5( $salt . $password, true );
do
{
$hash = md5( $hash . $password, true );
}
while ( --$count );
}
else
{
$hash = pack( 'H*', md5( $salt . $password ) );
do
{
$hash = pack( 'H*', md5( $hash . $password ) );
}
while ( --$count );
}
$output = \substr( $setting, 0, 12 );
$output .= $this->_hashEncode64( $hash, 16, $itoa64 );
return $output;
}
/**
* Private function to encode phpBB3 hash
*
* @access protected
* @param string Input
* @param count Iteration
* @param string Hash-lookup
* @return string phpbb3 password hash encoded bit
*/
protected function _hashEncode64($input, $count, &$itoa64)
{
$output = '';
$i = 0;
do
{
$value = ord( $input[$i++] );
$output .= $itoa64[$value & 0x3f];
if ( $i < $count )
{
$value |= ord($input[$i]) << 8;
}
$output .= $itoa64[($value >> 6) & 0x3f];
if ( $i++ >= $count )
{
break;
}
if ( $i < $count )
{
$value |= ord($input[$i]) << 16;
}
$output .= $itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count)
{
break;
}
$output .= $itoa64[($value >> 18) & 0x3f];
}
while ( $i < $count );
return $output;
}
}
@@ -0,0 +1,243 @@
<?php
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Portable PHP password hashing framework.
* @package phpass
* @since 2.5
* @version 0.5
* @link http://www.openwall.com/phpass/
*/
#
# Portable PHP password hashing framework.
#
# Version 0.5 / genuine.
#
# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
# the public domain. Revised in subsequent years, still public domain.
#
# There's absolutely no warranty.
#
# The homepage URL for this framework is:
#
# http://www.openwall.com/phpass/
#
# Please be sure to update the Version line if you edit this file in any way.
# It is suggested that you leave the main version number intact, but indicate
# your project name (after the slash) and add your own revision information.
#
# Please do not change the "private" password hashing method implemented in
# here, thereby making your hashes incompatible. However, if you must, please
# change the hash type identifier (the "$P$") to something different.
#
# Obviously, since this code is in the public domain, the above are not
# requirements (there can be none), but merely suggestions.
#
class PasswordHash
{
var $itoa64;
var $iteration_count_log2;
var $portable_hashes;
var $random_state;
function __construct($iteration_count_log2, $portable_hashes)
{
$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
$iteration_count_log2 = 8;
$this->iteration_count_log2 = $iteration_count_log2;
$this->portable_hashes = $portable_hashes;
$this->random_state = microtime();
if (function_exists('getmypid'))
$this->random_state .= getmypid();
}
function PasswordHash($iteration_count_log2, $portable_hashes)
{
self::__construct($iteration_count_log2, $portable_hashes);
}
function get_random_bytes($count)
{
$output = '';
if (@is_readable('/dev/urandom') &&
($fh = @fopen('/dev/urandom', 'rb'))) {
$output = fread($fh, $count);
fclose($fh);
}
if (strlen($output) < $count) {
$output = '';
for ($i = 0; $i < $count; $i += 16) {
$this->random_state =
md5(microtime() . $this->random_state);
$output .= md5($this->random_state, TRUE);
}
$output = substr($output, 0, $count);
}
return $output;
}
function encode64($input, $count)
{
$output = '';
$i = 0;
do {
$value = ord($input[$i++]);
$output .= $this->itoa64[$value & 0x3f];
if ($i < $count)
$value |= ord($input[$i]) << 8;
$output .= $this->itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count)
break;
if ($i < $count)
$value |= ord($input[$i]) << 16;
$output .= $this->itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count)
break;
$output .= $this->itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
function gensalt_private($input)
{
$output = '$P$';
$output .= $this->itoa64[min($this->iteration_count_log2 +
((PHP_VERSION >= '5') ? 5 : 3), 30)];
$output .= $this->encode64($input, 6);
return $output;
}
function crypt_private($password, $setting)
{
$output = '*0';
if (substr($setting, 0, 2) === $output)
$output = '*1';
$id = substr($setting, 0, 3);
# We use "$P$", phpBB3 uses "$H$" for the same thing
if ($id !== '$P$' && $id !== '$H$')
return $output;
$count_log2 = strpos($this->itoa64, $setting[3]);
if ($count_log2 < 7 || $count_log2 > 30)
return $output;
$count = 1 << $count_log2;
$salt = substr($setting, 4, 8);
if (strlen($salt) !== 8)
return $output;
# We were kind of forced to use MD5 here since it's the only
# cryptographic primitive that was available in all versions
# of PHP in use. To implement our own low-level crypto in PHP
# would have resulted in much worse performance and
# consequently in lower iteration counts and hashes that are
# quicker to crack (by non-PHP code).
$hash = md5($salt . $password, TRUE);
do {
$hash = md5($hash . $password, TRUE);
} while (--$count);
$output = substr($setting, 0, 12);
$output .= $this->encode64($hash, 16);
return $output;
}
function gensalt_blowfish($input)
{
# This one needs to use a different order of characters and a
# different encoding scheme from the one in encode64() above.
# We care because the last character in our encoded string will
# only represent 2 bits. While two known implementations of
# bcrypt will happily accept and correct a salt string which
# has the 4 unused bits set to non-zero, we do not want to take
# chances and we also do not want to waste an additional byte
# of entropy.
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$output = '$2a$';
$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
$output .= '$';
$i = 0;
do {
$c1 = ord($input[$i++]);
$output .= $itoa64[$c1 >> 2];
$c1 = ($c1 & 0x03) << 4;
if ($i >= 16) {
$output .= $itoa64[$c1];
break;
}
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 4;
$output .= $itoa64[$c1];
$c1 = ($c2 & 0x0f) << 2;
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 6;
$output .= $itoa64[$c1];
$output .= $itoa64[$c2 & 0x3f];
} while (1);
return $output;
}
function HashPassword($password)
{
$random = '';
if (CRYPT_BLOWFISH === 1 && !$this->portable_hashes) {
$random = $this->get_random_bytes(16);
$hash =
crypt($password, $this->gensalt_blowfish($random));
if (strlen($hash) === 60)
return $hash;
}
if (strlen($random) < 6)
$random = $this->get_random_bytes(6);
$hash =
$this->crypt_private($password,
$this->gensalt_private($random));
if (strlen($hash) === 34)
return $hash;
# Returning '*' on error is safe here, but would _not_ be safe
# in a crypt(3)-like function used _both_ for generating new
# hashes and for validating passwords against existing hashes.
return '*';
}
function CheckPassword($password, $stored_hash)
{
$hash = $this->crypt_private($password, $stored_hash);
if ($hash[0] === '*')
$hash = crypt($password, $stored_hash);
# This is not constant-time. In order to keep the code simple,
# for timing safety we currently rely on the salts being
# unpredictable, which they are at least in the non-fallback
# cases (that is, when we use /dev/urandom and bcrypt).
return $hash === $stored_hash;
}
}
+11
View File
@@ -335,6 +335,17 @@ abstract class _Software
$classname = 'IPS\\convert\\Library\\' . ucwords( $library );
return new $classname( $this );
}
/**
* Allows software to add additional menu row options
*
* @param array $rows Existing rows
* @return array
*/
public function extraMenuRows( $rows )
{
return $rows;
}
/**
* Returns a block of text, or a language string, that explains what the admin must do to start this conversion
@@ -320,7 +320,6 @@ class _Vbulletin extends \IPS\convert\Software
'follow_notify_freq' => $frequency,
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -455,7 +454,6 @@ class _Vbulletin extends \IPS\convert\Software
'follow_notify_freq' => $frequency,
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -560,7 +558,17 @@ class _Vbulletin extends \IPS\convert\Software
{
if ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) )
{
$filedata = $this->db->select( '*', 'filedata', array( "filedataid=?", $attachment['filedataid'] ) )->first();
try
{
$filedata = $this->db->select( '*', 'filedata', array( "filedataid=?", $attachment['filedataid'] ) )->first();
}
catch( \UnderflowException $e )
{
/* If the filedata row is missing, there isn't much we can do. */
$libraryClass->setLastKeyValue( $attachment['attachmentid'] );
$this->app->log( 'vb_attach_missing_filedata', __METHOD__, \IPS\convert\App::LOG_WARNING, $attachment['attachmentid'] );
continue;
}
}
else
{
@@ -597,16 +605,24 @@ class _Vbulletin extends \IPS\convert\Software
}
$attach_id = $libraryClass->convertAttachment( $info, $map, $path, $data );
/* Do some re-jiggery on the post itself to make sure attachment displays */
$entry_id = $this->app->getLink( $attachment[ $column ], 'blog_entries' );
$post = \IPS\Db::i()->select( 'entry_content', 'blog_entries', array( "entry_id=?", $entry_id ) )->first();
if ( preg_match( "/\[ATTACH(.+?)?\]".$attachment['attachmentid']."\[\/ATTACH\]/i", $post ) )
if ( $attach_id !== FALSE )
{
$post = preg_replace( "/\[ATTACH=(.+?)?\]" . $attachment['attachmentid'] . "\[\/ATTACH\]/i", '[attachment=' . $attach_id . ':name]', $post );
\IPS\Db::i()->update( 'blog_entries', array( 'entry_content' => $post ), array( "entry_id=?", $entry_id ) );
try
{
/* Do some re-jiggery on the post itself to make sure attachment displays */
$entry_id = $this->app->getLink( $attachment[ $column ], 'blog_entries' );
$post = \IPS\Db::i()->select( 'entry_content', 'blog_entries', array( "entry_id=?", $entry_id ) )->first();
if ( preg_match( "/\[ATTACH(.+?)?\]".$attachment['attachmentid']."\[\/ATTACH\]/i", $post ) )
{
$post = preg_replace( "/\[ATTACH=(.+?)?\]" . $attachment['attachmentid'] . "\[\/ATTACH\]/i", '[attachment=' . $attach_id . ':name]', $post );
\IPS\Db::i()->update( 'blog_entries', array( 'entry_content' => $post ), array( "entry_id=?", $entry_id ) );
}
}
catch( \UnderflowException $e ) {}
catch( \OutOfRangeException $e ) {}
}
$libraryClass->setLastKeyValue( $attachment['attachmentid'] );
@@ -200,7 +200,7 @@ class _Mybb extends \IPS\convert\Software
'event_content' => $row['description'],
'event_saved' => $row['dateline'],
'event_start_date' => \IPS\calendar\Date::ts( $row['starttime'] ),
'event_end_date' => \IPS\calendar\Date::ts( $row['endtime'] ),
'event_end_date' => $row['endtime'] ? \IPS\calendar\Date::ts( $row['endtime'] ) : NULL,
'event_approved' => $row['visible'],
'event_recurring' => $recurring,
) );
@@ -237,7 +237,7 @@ class _Vbulletin extends \IPS\convert\Software
'event_content' => $event['event'],
'event_saved' => $event['dateline'],
'event_start_date' => \IPS\calendar\Date::ts( $event['dateline_from'] ),
'event_end_date' => \IPS\calendar\Date::ts( $event['dateline_to'] ),
'event_end_date' => $event['dateline_to'] ? \IPS\calendar\Date::ts( $event['dateline_to'] ) : NULL,
'event_approved' => $event['visible'],
'event_recurring' => $recurring
);
@@ -462,7 +462,7 @@ class _Vbulletin extends \IPS\convert\Software
'rating_real' => $nodeinfo['ratingtotal'],
'rating_hits' => $nodeinfo['ratingnum'],
'rating_value' => $nodeinfo['rating'],
'record_locked' => $node['comments_enabled'],
'record_locked' => ( $node['comments_enabled'] ) ? 0 : 1,
'record_views' => $nodeinfo['viewcount'],
'record_allow_comments' => $node['comments_enabled'],
'record_saved' => $node['publishdate'],
@@ -498,7 +498,11 @@ class _Vbulletin extends \IPS\convert\Software
// Uncomment this once Tags are implemented for the cms;
# \IPS\Task::queue( 'convert', 'RebuildTagCache', array( 'app' => $this->app->app_id, 'link' => 'cms_custom_database_' . $database, 'class' => 'IPS\cms\Records' . $database ), 3, array( 'app', 'link', 'class' ) );
\IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'cms_custom_database_' . $database, 'class' => 'IPS\cms\Records' . $database ), 2, array( 'app', 'link', 'class' ) );
try
{
\IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'cms_custom_database_' . $database, 'class' => 'IPS\cms\Records' . $database ), 2, array( 'app', 'link', 'class' ) );
}
catch ( \OutOfRangeException $e ) {}
}
return array( "f_recount_cms_categories", "f_rebuild_cms_tags" );
@@ -184,6 +184,7 @@ class _Wordpress extends \IPS\convert\Software
\IPS\Task::queue( 'convert', 'RebuildTagCache', array( 'app' => $this->app->app_id, 'link' => 'cms_records', 'class' => 'IPS\cms\Records' . $database ), 3, array( 'app', 'link', 'class' ) );
\IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'cms_custom_database_' . $database, 'class' => 'IPS\cms\Records' . $database ), 2, array( 'app', 'link', 'class' ) );
\IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'cms_database_comments', 'class' => 'IPS\cms\Records\Comment' . $database ), 2, array( 'app', 'link', 'class' ) );
return array( "f_recount_cms_categories", "f_rebuild_cms_tags" );
}
@@ -28,7 +28,7 @@ class _Xenforo extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "XenForo Page Nodes";
return "XenForo Page Nodes (1.5.x/2.0.x)";
}
/**
@@ -87,24 +87,6 @@ class _Xenforo extends \IPS\convert\Software
return array( 'core' => array( 'xenforo' ) );
}
/**
* Helper to fetch a xenforo phrase
*
* @param string $title Phrase string
* @return string|null Phrase
*/
protected function getPhrase( $title )
{
try
{
return $this->db->select( 'phrase_text', 'xf_phrase', array( "title=?", $title ) )->first();
}
catch( \UnderflowException $e )
{
return NULL;
}
}
/**
* Convert CMS page
*
@@ -20,6 +20,16 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
class _Xenfororm extends \IPS\convert\Software
{
/**
* @brief The similarities between XF1 and XF2 are close enough that we can use the same converter
*/
public static $isLegacy = NULL;
/**
* @brief XF2 Has prefixes on RM tables
*/
public static $tablePrefix = '';
/**
* Software Name
*
@@ -28,7 +38,45 @@ class _Xenfororm extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "XenForo Resource Manager Articles";
return "XenForo Resource Manager Articles (1.5.x/2.0.x)";
}
/**
* Constructor
*
* @param \IPS\convert\App The application to reference for database and other information.
* @param bool Establish a DB connection
* @return void
* @throws \InvalidArgumentException
*/
public function __construct( \IPS\convert\App $app, $needDB=TRUE )
{
$return = parent::__construct( $app, $needDB );
if ( $needDB )
{
try
{
/* Is this XF1 or XF2 */
if ( static::$isLegacy === NULL )
{
$version = $this->db->select( 'MAX(version_id)', 'xf_template', array( "addon_id=?", 'XF' ) )->first();
if ( $version < 2000010 )
{
static::$isLegacy = TRUE;
}
else
{
static::$tablePrefix = 'rm_';
static::$isLegacy = FALSE;
}
}
}
catch( \Exception $e ) {} # If we can't query, we won't be able to do anything anyway
}
return $return;
}
/**
@@ -55,12 +103,12 @@ class _Xenfororm extends \IPS\convert\Software
'where' => NULL,
),
'convertCmsDatabaseCategories' => array(
'table' => 'xf_resource_category',
'table' => ( static::$isLegacy ? 'xf_resource_category' : 'xf_rm_category' ),
'where' => array( "allow_fileless=?", 1 )
),
'convertCmsDatabaseRecords' => array(
'table' => 'xf_resource',
'where' => array( "is_fileless=?", 1 )
'table' => 'xf_' . static::$tablePrefix . 'resource',
'where' => static::$isLegacy ? array( "is_fileless=?", 1 ) : array( 'resource_type=?', 'fileless' )
)
);
}
@@ -118,24 +166,6 @@ class _Xenfororm extends \IPS\convert\Software
return array( 'core' => array( 'xenforo' ) );
}
/**
* Helper to fetch a xenforo phrase
*
* @param string $title Phrase
* @return string|null
*/
protected function getPhrase( $title )
{
try
{
return $this->db->select( 'phrase_text', 'xf_phrase', array( "title=?", $title ) )->first();
}
catch( \UnderflowException $e )
{
return NULL;
}
}
/**
* Convert CMS databases
*
@@ -198,7 +228,7 @@ class _Xenfororm extends \IPS\convert\Software
$libraryClass::setKey( 'resource_category_id' );
foreach( $this->fetch( 'xf_resource_category', 'resource_category_id', array( 'allow_fileless=?', 1 ) ) AS $row )
foreach( $this->fetch( ( static::$isLegacy ? 'xf_resource_category' : 'xf_rm_category' ), 'resource_category_id', array( 'allow_fileless=?', 1 ) ) AS $row )
{
$forumoverride = 0;
$forumid = 0;
@@ -211,7 +241,8 @@ class _Xenfororm extends \IPS\convert\Software
$info = array(
'category_id' => $row['resource_category_id'],
'category_database_id' => 1,
'category_name' => $row['category_title'],
'category_name' => isset( $row['category_title'] ) ? $row['category_title'] : $row['title'],
'category_parent_id' => $row['parent_category_id'],
'category_position' => $row['display_order'],
'category_fields' => array( 'resource_title', 'resource_tagline', 'resource_content' ),
);
@@ -233,9 +264,9 @@ class _Xenfororm extends \IPS\convert\Software
$libraryClass::setKey( 'resource_id' );
foreach( $this->fetch( 'xf_resource', 'resource_id', array( 'is_fileless=?', 1 ) ) AS $row )
foreach( $this->fetch( 'xf_' . static::$tablePrefix . 'resource', 'resource_id', static::$isLegacy ? array( "is_fileless=?", 1 ) : array( 'resource_type=?', 'fileless' ) ) AS $row )
{
$post = $this->db->select( 'message, post_date', 'xf_resource_update', array( "resource_id=?", $row['resource_id'] ), "post_date DESC" )->first();
$post = $this->db->select( 'message, post_date', 'xf_' . static::$tablePrefix . 'resource_update', array( "resource_id=?", $row['resource_id'] ), "post_date DESC" )->first();
switch( $row['resource_state'] )
{
@@ -84,6 +84,30 @@ class _Expressionengine extends \IPS\convert\Software
);
}
/**
* Allows software to add additional menu row options
*
* @param array $rows Existing rows
* @return array
*/
public function extraMenuRows( $rows )
{
if( isset( $rows['convertPrivateMessageReplies'] ) )
{
$rows['convertPmAttachments'] = array(
'step_method' => 'convertPmAttachments',
'step_title' => 'convert_attachments',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'core_attachments' )->first(),
'source_rows' => $this->countRows( static::canConvert()['convertPmAttachments']['table'], static::canConvert()['convertPmAttachments']['where'] ),
'per_cycle' => 100,
'dependencies' => array( 'convertPrivateMessageReplies' ),
'link_type' => 'core_attachments',
);
}
return $rows;
}
/**
* Can we convert passwords from this software.
*
@@ -288,7 +312,7 @@ class _Expressionengine extends \IPS\convert\Software
\IPS\Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
/* Attachments */
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
return array( "f_search_index_rebuild", "f_clear_caches", "f_rebuild_pms", "f_signatures_rebuild", "f_rebuild_attachments" );
}
@@ -724,4 +748,39 @@ class _Expressionengine extends \IPS\convert\Software
return NULL;
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public function login( $member, $password )
{
$length = \strlen( $member->conv_password );
$providedHash = FALSE;
switch( $length )
{
/* MD5 */
case 32:
$providedHash = md5( $password );
break;
/* SHA1 */
case 40:
$providedHash = sha1( $password );
break;
/* SHA256 */
case 64:
$providedHash = hash( 'sha256', $password );
break;
/* SHA512 */
case 128:
$providedHash = hash( 'sha512', $password );
break;
}
return ( \IPS\Login::compareHashes( $member->conv_password, $providedHash ) ) ? TRUE : FALSE;
}
}
File diff suppressed because it is too large. Load diff
@@ -229,4 +229,32 @@ class _Joomla extends \IPS\convert\Software
$libraryClass->setLastKeyValue( $row['id'] );
}
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public function login( $member, $password )
{
/* Joomla 3 */
if( preg_match( '/^\$2[ay]\$(0[4-9]|[1-2][0-9]|3[0-1])\$[a-zA-Z0-9.\/]{53}/', $member->conv_password ) )
{
require_once \IPS\ROOT_PATH . "/applications/convert/sources/Login/PasswordHash.php";
$ph = new \PasswordHash( 8, TRUE );
return $ph->CheckPassword( $password, $member->conv_password ) ? TRUE : FALSE;
}
/* Joomla 2 */
if ( \IPS\Login::compareHashes( $member->conv_password, md5( $password . $member->misc ) ) )
{
return TRUE;
}
else
{
return FALSE;
}
}
}
@@ -283,10 +283,30 @@ class _Mybb extends \IPS\convert\Software
/* First Post Data */
\IPS\Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
/* Attachments */
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
return array( "f_search_index_rebuild", "f_clear_caches", "f_rebuild_pms", "f_signatures_rebuild", "f_announce_rebuild" );
}
/**
* Fix post data
*
* @param string raw post data
* @return string parsed post data
*/
public static function fixPostData( $post )
{
/* Replace align tags with left/right/center/justify */
$post = preg_replace( '/\[align=(justify|left|right)\](.+?)\[\/align\]/i', '[$1]$2[/$1]', $post );
/* remove justify, since we don't support it */
$post = str_replace( array( '[justify]', '[/justify]' ), '', $post );
return $post;
}
/**
* Convert announcements
*
@@ -899,7 +919,7 @@ class _Mybb extends \IPS\convert\Software
/* Warnings */
foreach( $this->db->select( '*', 'warnings', array( "uid=? AND pid=?", $row['uid'], 0 ) ) AS $warning )
{
$libraryClass->convertWarnLog( array(
$warnId = $libraryClass->convertWarnLog( array(
'wl_id' => $warning['wid'],
'wl_member' => $warning['uid'],
'wl_moderator' => $warning['issuedby'],
@@ -909,6 +929,17 @@ class _Mybb extends \IPS\convert\Software
'wl_note_mods' => $warning['notes'],
'wl_expire_date' => $warning['expires'],
) );
/* Add a member history record for this member */
$libraryClass->convertMemberHistory( array(
'log_id' => 'w' . $warning['wid'],
'log_member' => $warning['uid'],
'log_by' => $warning['issuedby'],
'log_type' => 'warning',
'log_data' => array( 'wid' => $warnId ),
'log_date' => $warning['dateline']
)
);
}
$libraryClass->setLastKeyValue( $row['uid'] );
@@ -1078,4 +1109,23 @@ class _Mybb extends \IPS\convert\Software
return NULL;
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public function login( $member, $password )
{
if ( \IPS\Login::compareHashes( $member->conv_password, md5( md5( $member->misc ) . md5( $password ) ) ) )
{
return TRUE;
}
else
{
return FALSE;
}
}
}
@@ -7,7 +7,6 @@
* @package Invision Community
* @subpackage Converter
* @since 6 December 2016
* @note Only redirect scripts are supported right now
*/
namespace IPS\convert\Software\Core;
@@ -29,7 +28,7 @@ class _Photopost extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "Photopost";
return "Photopost (8.x)";
}
/**
@@ -46,11 +45,242 @@ class _Photopost extends \IPS\convert\Software
/**
* Content we can convert from this software.
*
* @return NULL
* @return array
*/
public static function canConvert()
{
return NULL;
return array(
'convertGroups' => array(
'table' => 'usergroups',
'where' => NULL,
),
'convertMembers' => array(
'table' => 'users',
'where' => NULL,
)
);
}
/**
* List of conversion methods that require additional information
*
* @return array
*/
public static function checkConf()
{
return array(
'convertGroups',
'convertMembers'
);
}
/**
* Get More Information
*
* @param string $method Conversion method
* @return array
*/
public function getMoreInfo( $method )
{
$return = array();
switch( $method )
{
case 'convertGroups':
$return['convertGroups'] = array();
$options = array();
$options['none'] = 'None';
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_groups' ), 'IPS\Member\Group' ) AS $group )
{
$options[$group->g_id] = $group->name;
}
foreach( $this->db->select( '*', 'usergroups' ) AS $group )
{
\IPS\Member::loggedIn()->language()->words["map_group_{$group['groupid']}"] = $group['groupname'];
\IPS\Member::loggedIn()->language()->words["map_group_{$group['groupid']}_desc"] = \IPS\Member::loggedIn()->language()->addToStack( 'map_group_desc' );
$return['convertGroups']["map_group_{$group['groupid']}"] = array(
'field_class' => 'IPS\\Helpers\\Form\\Select',
'field_default' => NULL,
'field_required' => FALSE,
'field_extra' => array( 'options' => $options ),
'field_hint' => NULL,
);
}
break;
case 'convertMembers':
$return['convertMembers'] = array();
\IPS\Member::loggedIn()->language()->words['photo_location_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'photo_location_nodb_desc' );
$return['convertMembers']['photo_location'] = array(
'field_class' => 'IPS\\Helpers\\Form\\Text',
'field_default' => NULL,
'field_required' => TRUE,
'field_extra' => array(),
'field_hint' => "The path to the folder where avatars are saved (no trailing slash - usually /path_to_photopost/data/avatars):",
'field_validation' => function( $value ) { if ( !@is_dir( $value ) ) { throw new \DomainException( 'path_invalid' ); } },
);
/* And decide what to do about these... */
foreach( array( 'homepage', 'icq', 'aim', 'yahoo', 'location', 'interests', 'occupation', 'bio' ) AS $field )
{
\IPS\Member::loggedIn()->language()->words["field_{$field}"] = \IPS\Member::loggedIn()->language()->addToStack( 'pseudo_field', FALSE, array( 'sprintf' => ucwords( $field ) ) );
\IPS\Member::loggedIn()->language()->words["field_{$field}_desc"] = \IPS\Member::loggedIn()->language()->addToStack( 'pseudo_field_desc' );
$return['convertMembers']["field_{$field}"] = array(
'field_class' => 'IPS\\Helpers\\Form\\Radio',
'field_default' => 'no_convert',
'field_required' => TRUE,
'field_extra' => array(
'options' => array(
'no_convert' => \IPS\Member::loggedIn()->language()->addToStack( 'no_convert' ),
'create_field' => \IPS\Member::loggedIn()->language()->addToStack( 'create_field' ),
),
'userSuppliedInput' => 'create_field'
),
'field_hint' => NULL
);
}
break;
}
return ( isset( $return[ $method ] ) ) ? $return[ $method ] : array();
}
/**
* Finish - Adds everything it needs to the queues and clears data store
*
* @return array Message to display
*/
public function finish()
{
/* Search Index Rebuild */
\IPS\Content\Search\Index::i()->rebuild();
/* Clear Cache and Store */
\IPS\Data\Store::i()->clearAll();
\IPS\Data\Cache::i()->clearAll();
/* Non-Content Rebuilds */
\IPS\Task::queue( 'convert', 'RebuildNonContent', array( 'app' => $this->app->app_id, 'link' => 'core_members', 'extension' => 'core_Signatures' ), 2, array( 'app', 'link', 'extension' ) );
/* Content Counts */
\IPS\Task::queue( 'core', 'RecountMemberContent', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
return array( "f_search_index_rebuild", "f_clear_caches", "f_signatures_rebuild" );
}
/**
* Convert groups
*
* @return void
*/
public function convertGroups()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'groupid' );
foreach( $this->fetch( 'usergroups', 'groupid' ) AS $row )
{
$info = array(
'g_id' => $row['groupid'],
'g_name' => $row['groupname']
);
$merge = $this->app->_session['more_info']['convertGroups']["map_group_{$row['groupid']}"] != 'none' ? $this->app->_session['more_info']['convertGroups']["map_group_{$row['groupid']}"] : NULL;
$libraryClass->convertGroup( $info, $merge );
$libraryClass->setLastKeyValue( $row['groupid'] );
}
/* Now check for group promotions */
if( count( $libraryClass->groupPromotions ) )
{
foreach( $libraryClass->groupPromotions as $groupPromotion )
{
$libraryClass->convertGroupPromotion( $groupPromotion );
}
}
}
/**
* Convert members
*
* @return void
*/
public function convertMembers()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'userid' );
foreach( $this->fetch( 'users', 'userid' ) AS $row )
{
$birthdayYear = $birthdayMonth = $birthdayDay = NULL;
/* Is there a birthdate? */
if( $row['birthday'] != '0000-00-00' )
{
list( $birthdayYear, $birthdayMonth, $birthdayDay ) = explode( '-', $row['birthday'] );
}
$info = array(
'member_id' => $row['userid'],
'email' => $row['email'],
'name' => $row['username'],
'md5_password' => $row['password'],
'member_group_id' => $row['usergroupid'],
'joined' => $row['joindate'] ?: NULL,
'ip_address' => $row['ipaddress'],
'last_visit' => $row['laston'],
'last_activity' => $row['laston'],
'bday_day' => $birthdayDay,
'bday_month' => $birthdayMonth,
'bday_year' => $birthdayYear,
'signature' => static::fixPostData( $row['signature'] )
);
/* Pseudo Fields */
$profileFields = array();
foreach( array( 'homepage', 'icq', 'aim', 'yahoo', 'location', 'interests', 'occupation', 'bio' ) AS $pseudo )
{
/* Are we retaining? */
if ( $this->app->_session['more_info']['convertMembers']["field_{$pseudo}"] == 'no_convert' )
{
/* No, skip */
continue;
}
try
{
/* We don't actually need this, but we need to make sure the field was created */
$this->app->getLink( $pseudo, 'core_pfields_data' );
}
catch( \OutOfRangeException $e )
{
$libraryClass->convertProfileField( array(
'pf_id' => $pseudo,
'pf_name' => $this->app->_session['more_info']['convertMembers']["field_{$pseudo}"],
'pf_desc' => '',
'pf_type' => 'Text',
'pf_content' => '[]',
'pf_member_hide' => 0,
'pf_max_input' => 255,
'pf_member_edit' => 1,
'pf_show_on_reg' => 0,
'pf_admin_only' => 0,
) );
}
$profileFields[ $pseudo ] = $row[ $pseudo ];
}
$libraryClass->convertMember( $info, $profileFields, $row['avatar'], rtrim( $this->app->_session['more_info']['convertMembers']['photo_location'], '/' ) );
$libraryClass->setLastKeyValue( $row['userid'] );
}
}
/**
@@ -28,7 +28,7 @@ class _Phpbb extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "phpBB 3.1";
return "phpBB (3.1.x/3.2.x)";
}
/**
@@ -98,6 +98,24 @@ class _Phpbb extends \IPS\convert\Software
);
}
/**
* Allows software to add additional menu row options
*
* @param array $rows Existing rows
* @return array
*/
public function extraMenuRows( $rows )
{
if( isset( $rows['convertBanfilters'] ) )
{
$rows['convertBanfilters2'] = $rows['convertBanfilters'];
$rows['convertBanfilters2']['step_method'] = 'convertBanfilters2';
$rows['convertBanfilters2']['source_rows'] = $this->countRows( static::canConvert()['convertBanfilters2']['table'], static::canConvert()['convertBanfilters2']['where'] );
}
return $rows;
}
/**
* Count Source Rows for a specific step
*
@@ -310,6 +328,9 @@ class _Phpbb extends \IPS\convert\Software
/* Content Counts */
\IPS\Task::queue( 'core', 'RecountMemberContent', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
/* Attachments */
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
/* First Post Data */
\IPS\Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
@@ -698,8 +719,7 @@ class _Phpbb extends \IPS\convert\Software
{
$userfields = $this->db->select( '*', 'profile_fields_data', array( "user_id=?", $row['user_id'] ) )->first();
}
catch( \UnderflowException $e )
{}
catch( \UnderflowException $e ) {}
foreach( $this->db->select( '*', 'profile_fields' ) AS $field )
{
@@ -720,6 +740,10 @@ class _Phpbb extends \IPS\convert\Software
}
}
}
/* PM Folders */
$pmFolders = iterator_to_array( $this->db->select( 'folder_name', 'privmsgs_folder', array( 'user_id=?', $row['user_id'] ) ) );
$info['pconversation_filters'] = count( $pmFolders ) ? $pmFolders : NULL;
/* Finally */
$libraryClass->convertMember( $info, $pfields, $filename, $filepath );
@@ -946,7 +970,7 @@ class _Phpbb extends \IPS\convert\Software
$maps = array();
/* First one first initial author */
$maps[$row['author_id']] = array(
$maps[ $row['author_id'] ] = array(
'map_user_id' => $row['author_id'],
'map_is_starter' => 1,
'map_last_topic_reply' => $row['message_time'],
@@ -973,13 +997,18 @@ class _Phpbb extends \IPS\convert\Software
to a custom folder then they will have a row in privmsgs_to and we will end up resetting map_is_starter to 0 otherwise */
if( isset( $maps[ $to['user_id'] ] ) )
{
if( $to['folder_id'] > 0 )
{
$maps[ $to['user_id'] ]['map_folder_id'] = $this->_getPmFolder( $to['user_id'], $to['folder_id'] );
}
continue;
}
$maps[ $to['user_id'] ] = array(
'map_user_id' => $to['user_id'],
'map_is_starter' => 0,
'map_last_topic_reply' => $row['message_time']
'map_last_topic_reply' => $row['message_time'],
'map_folder_id' => ( $to['folder_id'] > 0 ) ? $this->_getPmFolder( $to['user_id'], $to['folder_id'] ) : FALSE
);
}
@@ -1045,4 +1074,108 @@ class _Phpbb extends \IPS\convert\Software
return NULL;
}
/**
* @brief Lookup cache
*/
protected static $pmFolders = array();
/**
* @brief PhpBB Source Folders
*/
protected static $phpBbFolders = NULL;
/**
* Get array key for the folder
*
* @param int $user User ID
* @param string $folderId Folder Name
* @return boolean
*/
protected function _getPmFolder( $user, $folderId )
{
/* Check for cached folder info */
if( isset( static::$pmFolders[ $user ][ $folderId ] ) )
{
return static::$pmFolders[ $user ][ $folderId ];
}
/* Check for cached phpBB data */
if( static::$phpBbFolders === NULL )
{
static::$phpBbFolders = iterator_to_array( $this->db->select( 'folder_id,folder_name', 'privmsgs_folder' )->setKeyField( 'folder_id' )->setValueField( 'folder_name' ) );
}
try
{
/* Does the referenced folder exist in PhpBB? */
if( !isset( static::$phpBbFolders[ $folderId ] ) )
{
throw new \OutOfRangeException;
}
$member = \IPS\Member::load( $this->app->getLink( (int) $user, 'core_members' ) );
$filters = json_decode( $member->pconversation_filters, TRUE );
/* No PM Folders or it's a guest */
if( $filters === NULL OR !$member->member_id )
{
throw new \OutOfRangeException;
}
}
catch( \OutOfRangeException $e )
{
return static::$pmFolders[ $user ][ $folderId ] = FALSE;
}
/* Search PM folders and return match */
return static::$pmFolders[ $user ][ $folderId ] = array_search( static::$phpBbFolders[ $folderId ], $filters );
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public function login( $member, $password )
{
$password = html_entity_decode( $password );
$success = FALSE;
$hash = $member->conv_password;
/* phpBB 3.1 */
if( preg_match( '/^\$2[ay]\$(0[4-9]|[1-2][0-9]|3[0-1])\$[a-zA-Z0-9.\/]{53}/', $hash ) )
{
require_once \IPS\ROOT_PATH . "/applications/convert/sources/Login/PasswordHash.php";
$ph = new \PasswordHash( 8, TRUE );
$success = $ph->CheckPassword( $password, $member->conv_password ) ? TRUE : FALSE;
}
$hashLibrary = new \IPS\convert\Login\HashCryptPrivate;
if ( $success === FALSE )
{
/* phpBB3 */
$single_md5_pass = md5( $password );
$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
if ( \strlen( $hash ) == 34 )
{
$success = ( \IPS\Login::compareHashes( $hash, $hashLibrary->hashCryptPrivate( $password, $hash, $itoa64 ) ) ) ? TRUE : FALSE;
}
else
{
$success = ( \IPS\Login::compareHashes( $hash, $single_md5_pass ) ) ? TRUE : FALSE;
}
}
/* phpBB2 */
if ( !$success )
{
$success = ( \IPS\Login::compareHashes( $hash, $hashLibrary->hashCryptPrivate( $single_md5_pass, $hash, $itoa64 ) ) ) ? TRUE : FALSE ;
}
return $success;
}
}
@@ -28,7 +28,7 @@ class _Punbb extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "PunBB 1.x";
return "PunBB (1.x)";
}
/**
@@ -454,4 +454,35 @@ class _Punbb extends \IPS\convert\Software
return NULL;
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public function login( $member, $password )
{
$success = FALSE;
if ( mb_strlen( $member->conv_password ) == 40 )
{
/* Password with salt */
$success = \IPS\Login::compareHashes( $member->conv_password, sha1( $member->conv_password_extra . sha1( $password ) ) );
if ( !$success )
{
/* No salt */
$success = \IPS\Login::compareHashes( $member->conv_password, sha1( $password ) );
}
}
else
{
/* MD5 */
$success = \IPS\Login::compareHashes( $member->conv_password, md5( $password ) );
}
return $success;
}
}
@@ -28,7 +28,7 @@ class _Smf extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "Simple Machines Forum";
return "Simple Machines Forum (2.0.x)";
}
/**
@@ -226,7 +226,7 @@ class _Smf extends \IPS\convert\Software
'field_class' => 'IPS\\Helpers\\Form\\Radio',
'field_default' => 'user_name',
'field_required' => TRUE,
'field_extra' => array( 'options' => array( 'user_name', 'display_name' ) ),
'field_extra' => array( 'options' => array( 'user_name' => 'user_name', 'display_name' => 'display_name' ) ),
'field_hint' => NULL,
);
@@ -298,7 +298,7 @@ class _Smf extends \IPS\convert\Software
\IPS\Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
/* Attachments */
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
return array( "f_search_index_rebuild", "f_clear_caches", "f_rebuild_pms", "f_signatures_rebuild", "f_rebuild_attachments" );
}
@@ -318,7 +318,7 @@ class _Smf extends \IPS\convert\Software
$post = str_replace( $search, $replace, $post );
// And img tags
$post = preg_replace("#\[img width=(\d+)(?: height=(\d+))?\]([a-zA-Z0-9.:\-/%\?&_]+)\[\/img\]#i", "[img]$3[/img]", $post);
$post = preg_replace("#\[img width=(\d+)(?: height=(\d+))?\]([a-zA-Z0-9.:\-/%\?&_=~\#\(\);:]+)\[\/img\]#i", "[img]$3[/img]", $post);
//Quotes
$post = preg_replace("#\[quote author=(.+?) link=(.+?)=(\d+)\.msg(\d+)(.+?)date=(\d+)\](.+)\[\/quote\]#i", "[quote name=\"$1\" post=\"$4\" timestamp=\"$6\"]$7[/quote]", $post);
@@ -648,7 +648,7 @@ class _Smf extends \IPS\convert\Software
'email' => $row['email_address'],
'name' => $name,
'password' => $row['passwd'],
'password_extra' => $row['password_salt'],
'password_extra' => $row['member_name'], // We need to keep the original member name for the SMF password hash
'member_group_id' => $row['id_group'],
'joined' => $row['date_registered'],
'ip_address' => $row['member_ip'],
@@ -900,4 +900,45 @@ class _Smf extends \IPS\convert\Software
return NULL;
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public function login( $member, $password )
{
if ( \IPS\Login::compareHashes( $member->conv_password, sha1( mb_strtolower( $member->name ) . html_entity_decode( $password ) ) ) )
{
return TRUE;
}
else if ( \IPS\Login::compareHashes( $member->conv_password, sha1( mb_strtolower( $member->name ) . $password ) ) )
{
return TRUE;
}
/* In 4.2.6 we save the original members name as the salt so that we don't have one that has been modified in the conversion process */
else if ( \IPS\Login::compareHashes( $member->conv_password, sha1( mb_strtolower( $member->conv_password_extra ) . $password ) ) )
{
return TRUE;
}
else
{
require_once \IPS\ROOT_PATH . "/applications/convert/sources/Login/PasswordHash.php";
$ph = new \PasswordHash( 8, TRUE );
if( $ph->CheckPassword( mb_strtolower( $member->name ) . $password, $member->conv_password ) )
{
return TRUE;
}
/* In 4.2.6 we save the original members name as the salt so that we don't have one that has been modified in the conversion process */
else if ( $ph->CheckPassword( mb_strtolower( $member->conv_password_extra ) . $password, $member->conv_password ) )
{
return TRUE;
}
}
return FALSE;
}
}
@@ -132,6 +132,32 @@ class _UBBthreads extends \IPS\convert\Software
),
);
}
/**
* Allows software to add additional menu row options
*
* @param array $rows Existing rows
* @return array
*/
public function extraMenuRows( $rows )
{
$count = $this->countRows( static::canConvert()['convertMembersFollowers']['table'], static::canConvert()['convertMembersFollowers']['where'] );
if( $count )
{
$rows['convertMembersFollowers'] = array(
'step_method' => 'convertMembersFollowers',
'step_title' => 'convert_follows',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'core_follow', array( 'follow_app=? and follow_area=?', 'core', 'member' ) )->first(),
'source_rows' => $count,
'per_cycle' => 500,
'dependencies' => array( 'convertMembers' ),
'link_type' => 'core_follow',
);
}
return $rows;
}
/**
* Can we convert passwords from this software.
@@ -265,6 +291,15 @@ class _UBBthreads extends \IPS\convert\Software
'field_hint' => \IPS\Member::loggedIn()->language()->addToStack( 'username_hint' ),
);
$return['convertMembers']['photo_location'] = array(
'field_class' => 'IPS\\Helpers\\Form\\Text',
'field_default' => NULL,
'field_required' => TRUE,
'field_extra' => array(),
'field_hint' => \IPS\Member::loggedIn()->language()->addToStack('convert_ubbthreads_photo_path'),
'field_validation' => function( $value ) { if ( !@is_dir( $value ) ) { throw new \DomainException( 'path_invalid' ); } },
);
foreach( array( 'homepage', 'occupation', 'hobbies', 'location', 'icq', 'yahoo', 'aim', 'msn' ) AS $field )
{
\IPS\Member::loggedIn()->language()->words["field_{$field}"] = \IPS\Member::loggedIn()->language()->addToStack( 'pseudo_field', FALSE, array( 'sprintf' => $field ) );
@@ -315,7 +350,7 @@ class _UBBthreads extends \IPS\convert\Software
\IPS\Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
/* Attachments */
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
return array( "f_search_index_rebuild", "f_clear_caches", "f_rebuild_pms", "f_signatures_rebuild", "f_rebuild_attachments" );
}
@@ -584,8 +619,16 @@ class _UBBthreads extends \IPS\convert\Software
/* Profile photo */
$profilePhotoName = NULL;
$profilePhotoData = NULL;
$profilePhotoPath = NULL;
if ( !empty( $row['USER_AVATAR'] ) AND ( $row['USER_AVATAR'] != 'http://' ) )
/* Is it a file? */
if( !empty( $row['USER_AVATAR'] ) AND mb_substr( $row['USER_AVATAR'], 0, 1 ) == '/' )
{
$profilePhotoPath = pathinfo( rtrim( $this->app->_session['more_info']['convertMembers']['photo_location'], '/' ) . $row['USER_AVATAR'], PATHINFO_DIRNAME );
$profilePhotoName = pathinfo( rtrim( $this->app->_session['more_info']['convertMembers']['photo_location'], '/' ) . $row['USER_AVATAR'], PATHINFO_BASENAME );
}
/* Or an URL? */
elseif ( !empty( $row['USER_AVATAR'] ) AND ( $row['USER_AVATAR'] != 'http://' ) AND mb_substr( $row['USER_AVATAR'], 0, 4 ) )
{
try
{
@@ -595,7 +638,7 @@ class _UBBthreads extends \IPS\convert\Software
catch( \IPS\Http\Request\Exception $e ) { }
}
$libraryClass->convertMember( $info, $pfields, $profilePhotoName, NULL, $profilePhotoData );
$libraryClass->convertMember( $info, $pfields, $profilePhotoName, $profilePhotoPath, $profilePhotoData );
$libraryClass->setLastKeyValue( $row['USER_ID'] );
}
}
@@ -609,7 +652,7 @@ class _UBBthreads extends \IPS\convert\Software
{
$libraryClass = $this->getLibrary();
foreach ( $this->fetch( 'WATCH_LISTS', 'WATCH_ID', array( 'WATCH_TYPE=?', 'u' ), 'USER_ID ASC, WATCH_ID ASC' ) as $row )
foreach ( $this->fetch( 'WATCH_LISTS', 'WATCH_ID', array( 'WATCH_TYPE=?', 'u' ) ) as $row )
{
$libraryClass->convertFollow( array(
'follow_app' => 'core',
@@ -769,7 +812,7 @@ class _UBBthreads extends \IPS\convert\Software
$data = $this->app->getLink( (int) $matches[1], array( 'members', 'core_members' ) );
$item = \IPS\Member::load( $data );
if( $item->canView() )
if( \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'core', 'members' ) ) )
{
return $item->url();
}
@@ -782,4 +825,37 @@ class _UBBthreads extends \IPS\convert\Software
return NULL;
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public function login( $member, $password )
{
$hash = $member->members_pass_hash;
$salt = $member->members_pass_salt;
if ( \IPS\Login::compareHashes( $hash, md5( $password ) ) )
{
return TRUE;
}
// Not using md5, UBB salts the password with the password
// IPB already md5'd it though, *sigh*
if ( \IPS\Login::compareHashes( $hash, md5( md5( $salt ) . crypt( $password, $password ) ) ) )
{
return TRUE;
}
// Now standard IPB check.
if ( \IPS\Login::compareHashes( $hash, md5( md5( $salt ) . md5( $password ) ) ) )
{
return TRUE;
}
return FALSE;
}
}
@@ -27,7 +27,7 @@ class _Vanilla extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "Vanilla 2";
return "Vanilla 2 (2.2.x/2.3.x)";
}
/**
@@ -118,8 +118,8 @@ class _Vanilla extends \IPS\convert\Software
* Resolve the filesystem location of a file from a path stored in the database
* There are several different ways Vanilla may store these, including as remote URL's
*
* @param $location File location retrieved from the database
* @param $uploadsPath Configured uploads path
* @param string $location File location retrieved from the database
* @param string $uploadsPath Configured uploads path
* @return \IPS\Http\Url|string|void
*/
public static function parseMediaLocation( $location, $uploadsPath )
@@ -325,7 +325,7 @@ class _Vanilla extends \IPS\convert\Software
\IPS\Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
/* Attachments */
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
return array( "f_search_index_rebuild", "f_clear_caches", "f_rebuild_pms", "f_signatures_rebuild", "f_rebuild_attachments" );
}
@@ -566,4 +566,31 @@ class _Vanilla extends \IPS\convert\Software
$libraryClass->setLastKeyValue( $row['MessageID'] );
}
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public function login( $member, $password )
{
/* Vanilla 2.2 */
if( preg_match( '/^\$2[ay]\$(0[4-9]|[1-2][0-9]|3[0-1])\$[a-zA-Z0-9.\/]{53}/', $member->conv_password ) OR mb_substr( $member->conv_password, 0, 3 ) == '$P$' )
{
require_once \IPS\ROOT_PATH . "/applications/convert/sources/Login/PasswordHash.php";
$ph = new \PasswordHash( 8, TRUE );
return $ph->CheckPassword( $password, $member->conv_password ) ? TRUE : FALSE;
}
if ( \IPS\Login::compareHashes( $member->conv_password, md5( md5( str_replace( '&#39;', "'", html_entity_decode( $password ) ) ) . $member->misc ) ) )
{
return TRUE;
}
else
{
return FALSE;
}
}
}
@@ -166,6 +166,43 @@ class _Vbulletin extends \IPS\convert\Software
)
);
}
/**
* Count Source Rows for a specific step
*
* @param string $table The table containing the rows to count.
* @param array|NULL $where WHERE clause to only count specific rows, or NULL to count all.
* @param bool $recache Skip cache and pull directly (updating cache)
* @return integer
* @throws \IPS\convert\Exception
*/
public function countRows( $table, $where=NULL, $recache=FALSE )
{
switch( $table )
{
case 'userchangelog':
return $this->db->select( 'count(changeid)', 'userchangelog', array( $this->db->in( 'fieldname', array_keys( static::$changeLogTypes ) ) ) )->first();
break;
case 'pm':
/* vB doesn't put an index on a column that makes this converison much quicker, so we'll do it. */
if( !$this->db->checkForIndex( 'pm', 'parent_id' ) )
{
$this->db->addIndex( 'pm', array(
'type' => 'key',
'name' => 'parent_id',
'columns' => array( 'parentpmid', 'folderid' )
) );
}
return parent::countRows( $table, $where, $recache );
break;
default:
return parent::countRows( $table, $where, $recache );
break;
}
}
/**
* Can we convert passwords from this software.
@@ -479,12 +516,15 @@ class _Vbulletin extends \IPS\convert\Software
/* Content Counts */
\IPS\Task::queue( 'core', 'RecountMemberContent', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
/* Clubs */
\IPS\Task::queue( 'convert', 'RecountClubMembers', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
/* First Post Data */
\IPS\Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
/* Attachments */
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
return array( "f_search_index_rebuild", "f_clear_caches", "f_rebuild_pms", "f_signatures_rebuild", "f_announce_rebuild" );
}
@@ -882,7 +922,7 @@ class _Vbulletin extends \IPS\convert\Software
/**
* @brief Member history type map
*/
protected $changeLogTypes = array( 'username' => 'display_name', 'email' => 'email_change', 'usergroupid' => 'member_group_id', 'membergroupids' => 'mgroup_others' );
protected static $changeLogTypes = array( 'username' => 'display_name', 'email' => 'email_change', 'usergroupid' => 'member_group_id', 'membergroupids' => 'mgroup_others' );
/**
* Convert member history
@@ -892,50 +932,15 @@ class _Vbulletin extends \IPS\convert\Software
public function convertMemberHistory()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'changeid' );
$fixGroups = function( $string )
foreach( $this->fetch( 'userchangelog', 'changeid', array( $this->db->in( 'fieldname', array_keys( static::$changeLogTypes ) ) ) ) AS $change )
{
$groups = explode( ',', $string );
$newGroups = array();
array_walk( $groups, function( &$item, $key ) use ( &$newGroups )
{
try
{
$item = $this->app->getLink( $item, 'core_groups' );
$newGroups[] = $item;
}
catch( \OutOfRangeException $e ){}
});
return implode( ',', $newGroups );
};
foreach( $this->fetch( 'userchangelog', 'changeid' ) AS $change )
{
if( !in_array( $change['fieldname'], array_keys( $this->changeLogTypes ) ) )
{
$libraryClass->setLastKeyValue( $change['changeid'] );
continue;
}
/* Any processing we need to do */
switch( $this->changeLogTypes[ $change['fieldname'] ] )
{
case 'member_group_id':
case 'mgroup_others':
$change['oldvalue'] = $fixGroups( $change['oldvalue'] );
$change['newvalue'] = $fixGroups( $change['newvalue'] );
break;
}
$libraryClass->convertMemberHistory( array(
'log_id' => $change['changeid'],
'log_member' => $change['userid'],
'log_by' => $change['adminid'],
'log_type' => $this->changeLogTypes[ $change['fieldname'] ],
'log_type' => static::$changeLogTypes[ $change['fieldname'] ],
'log_data' => array( 'old' => $change['oldvalue'], 'new' => $change['newvalue'] ),
'log_date' => $change['change_time']
)
@@ -1241,7 +1246,7 @@ class _Vbulletin extends \IPS\convert\Software
}
}
$libraryClass->convertMember( $info, $profileFields, $filename, $filepath, $filedata );
$libraryClass->convertMember( $info, $profileFields, $filename, $filepath );
/* Any friends need converting to followers? */
foreach( $this->db->select( '*', 'userlist', array( "type=? AND friend=? AND userid=?", 'buddy', 'yes', $user['userid'] ) ) AS $follower )
@@ -1933,10 +1938,11 @@ class _Vbulletin extends \IPS\convert\Software
}
/* Profile URLs can be in one of 3 formats really...
* /member.php/1-name
* /members/1-name
* /member.php?u=1
*/
* /member.php/1-name
* /members/1-name
* /member.php?u=1
* /member.php?1-name
*/
$path = $url->data[ \IPS\Http\Url::COMPONENT_PATH ];
if( mb_strpos( $path, 'member.php' ) !== FALSE )
{
@@ -1944,6 +1950,10 @@ class _Vbulletin extends \IPS\convert\Software
{
$oldId = \IPS\Request::i()->u;
}
elseif( preg_match( '#^(\d+)-[^/]+#i', $url->data[ \IPS\Http\Url::COMPONENT_QUERY ], $matches ) )
{
$oldId = $matches[1];
}
else
{
$queryStringPieces = explode( '-', mb_substr( $path, mb_strpos( $path, 'member.php/' ) + mb_strlen( 'member.php/' ) ) );
@@ -1971,4 +1981,23 @@ class _Vbulletin extends \IPS\convert\Software
return NULL;
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public static function login( $member, $password )
{
if ( \IPS\Login::compareHashes( $member->conv_password, md5( md5( str_replace( '&#39;', "'", html_entity_decode( $password ) ) ) . $member->conv_password_extra ) ) )
{
return TRUE;
}
else
{
return FALSE;
}
}
}
@@ -158,6 +158,10 @@ class _Vbulletin5 extends \IPS\convert\Software
$where = array( $this->db->in( 'nodeid', $clubs ) );
return parent::countRows( $table, $where, $recache );
break;
case 'userchangelog':
return $this->db->select( 'count(changeid)', 'userchangelog', array( $this->db->in( 'fieldname', array_keys( static::$changeLogTypes ) ) ) )->first();
break;
default:
return parent::countRows( $table, $where, $recache );
@@ -464,8 +468,14 @@ class _Vbulletin5 extends \IPS\convert\Software
/* Content Counts */
\IPS\Task::queue( 'core', 'RecountMemberContent', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
/* Clubs */
\IPS\Task::queue( 'convert', 'RecountClubMembers', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
/* First Post Data */
\IPS\Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
/* Attachments */
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
return array( "f_search_index_rebuild", "f_clear_caches", "f_rebuild_pms", "f_signatures_rebuild" );
}
@@ -488,6 +498,77 @@ class _Vbulletin5 extends \IPS\convert\Software
/* Remove video tags and allow our parser to handle the embeds it supports */
$post = preg_replace( "#\[video=[a-z]+;[a-z0-9_]+\](.*?)\[\/video\]#i", '$1', $post );
/* IMG2 BBcode */
$matches = array();
preg_match_all( "/\[IMG2=JSON](.+?)\[\/IMG2\]/i", $post, $matches );
if ( count( $matches ) )
{
foreach( $matches[0] as $k => $v )
{
try
{
/* Make sure the JSON value exists */
if( !isset( $matches[1][ $k ] ) )
{
throw new \DomainException;
}
$decoded = json_decode( $matches[1][ $k ], TRUE );
/* Make sure the JSON is valid and contains src */
if( !isset( $decoded['src'] ) OR empty( $decoded['src'] ) )
{
throw new \DomainException;
}
$post = str_replace( $v, "[img]{$decoded['src']}[/img]", $post );
}
catch( \DomainException $e )
{
continue;
}
}
}
/* Mentions */
$matches = array();
preg_match_all( '#\[user(=\"?(\d+)\"?)?\](.+?)\[\/user\]#i', $post, $matches ); // this purposefully matches more than the default vB mention.
if ( count( $matches ) )
{
if ( isset( $matches[1] ) )
{
$mentions = array();
foreach( $matches[1] AS $k => $v )
{
if ( isset( $matches[3][$k] ) )
{
$name = trim( $matches[3][ $k ], '@' );
$mentions[ $name ] = $v;
}
}
$maps = array();
$urls = array();
$cardUrls = array();
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'name', array_keys( $mentions ) ) ) ), 'IPS\Member' ) AS $member )
{
$maps[ $member->name ] = $member->member_id;
$urls[ $member->name ] = $member->url();
$cardUrls[ $member->name ] = $member->url()->setQueryString( 'do', 'hovercard' );
}
foreach( $mentions AS $member_name => $member_id )
{
$maps[ $member_name ] = preg_quote( $maps[ $member_name ], '#' );
$memberNameEscaped = preg_quote( $member_name, '#' );
$post = preg_replace( "#\[user(=\"?(\d+)\"?)?\]@?{$memberNameEscaped}\[\/user\]#i", "<a contenteditable=\"false\" rel=\"\" href=\"{$urls[ $member_name ]}\" data-mentionid=\"{$maps[ $member_name ]}\" data-ipshover-target=\"{$cardUrls[ $member_name ]}\" data-ipshover=\"\">@{$member_name}</a>", $post );
}
}
}
return $post;
}
@@ -822,7 +903,7 @@ class _Vbulletin5 extends \IPS\convert\Software
/**
* @brief Member history type map
*/
protected $changeLogTypes = array( 'username' => 'display_name', 'email' => 'email_change', 'usergroupid' => 'member_group_id', 'membergroupids' => 'mgroup_others' );
protected static $changeLogTypes = array( 'username' => 'display_name', 'email' => 'email_change', 'usergroupid' => 'member_group_id', 'membergroupids' => 'mgroup_others' );
/**
* Convert member history
@@ -832,53 +913,18 @@ class _Vbulletin5 extends \IPS\convert\Software
public function convertMemberHistory()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'changeid' );
$fixGroups = function( $string )
foreach( $this->fetch( 'userchangelog', 'changeid', array( $this->db->in( 'fieldname', array_keys( static::$changeLogTypes ) ) ) ) AS $change )
{
$groups = explode( ',', $string );
$newGroups = array();
array_walk( $groups, function( &$item, $key ) use ( &$newGroups )
{
try
{
$item = $this->app->getLink( $item, 'core_groups' );
$newGroups[] = $item;
}
catch( \OutOfRangeException $e ){}
});
return implode( ',', $newGroups );
};
foreach( $this->fetch( 'userchangelog', 'changeid' ) AS $change )
{
if( !in_array( $change['fieldname'], array_keys( $this->changeLogTypes ) ) )
{
$libraryClass->setLastKeyValue( $change['changeid'] );
continue;
}
/* Any processing we need to do */
switch( $this->changeLogTypes[ $change['fieldname'] ] )
{
case 'member_group_id':
case 'mgroup_others':
$change['oldvalue'] = $fixGroups( $change['oldvalue'] );
$change['newvalue'] = $fixGroups( $change['newvalue'] );
break;
}
$libraryClass->convertMemberHistory( array(
'log_id' => $change['changeid'],
'log_member' => $change['userid'],
'log_by' => $change['adminid'],
'log_type' => $this->changeLogTypes[ $change['fieldname'] ],
'log_type' => static::$changeLogTypes[ $change['fieldname'] ],
'log_data' => array( 'old' => $change['oldvalue'], 'new' => $change['newvalue'] ),
'log_date' => $change['change_time'],
'log_ip_address' => long2ip( $change['ipaddress'] )
'log_ip_address' => long2ip( (int) $change['ipaddress'] )
)
);
@@ -1221,7 +1267,7 @@ class _Vbulletin5 extends \IPS\convert\Software
$libraryClass->convertMemberHistory( array(
'log_id' => 'w' . $warn['nodeid'],
'log_member' => $warn['infracteduserid'],
'log_by' => $warn['userid'],
'log_by' => $warn['actionuserid'],
'log_type' => 'warning',
'log_data' => array( 'wid' => $warnId ),
'log_date' => $warn['actiondateline']
@@ -1344,18 +1390,19 @@ class _Vbulletin5 extends \IPS\convert\Software
'map_user_active' => 1,
'map_user_banned' => 0,
'map_has_unread' => 0,
'map_is_starter' => ( $pm['userid'] == $pm['userid'] ) ? 1 : 0,
'map_is_starter' => 1,
);
foreach( $this->db->select( '*', 'node', array( "parentid=?", $pm['nodeid'] ) ) AS $post )
/* All other maps */
foreach( $this->db->select( '*', 'sentto', array( "nodeid=? AND userid!=?", $pm['nodeid'], $pm['userid'] ) ) AS $map )
{
$maps[ $post['userid'] ] = array(
'map_user_id' => $post['userid'],
$maps[ $map['userid'] ] = array(
'map_user_id' => $map['userid'],
'map_topic_id' => $pm['nodeid'],
'map_user_active' => 1,
'map_user_active' => !$map['deleted'],
'map_user_banned' => 0,
'map_has_unread' => 0,
'map_is_starter' => ( $post['userid'] == $pm['userid'] ) ? 1 : 0,
'map_has_unread' => !$map['msgread'],
'map_is_starter' => 0
);
}
@@ -1880,4 +1927,42 @@ class _Vbulletin5 extends \IPS\convert\Software
return NULL;
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public static function login( $member, $password )
{
/* If no algorithm is defined, use the legacy handler */
if ( \strpos( $member->misc, 'blowfish' ) === FALSE and \strpos( $member->misc, 'legacy' ) === FALSE )
{
return \IPS\convert\Software\Core\Vbulletin::login( $member, $password );
}
/* Which do we need to use? */
$algo = explode( ':', $member->misc );
switch( $algo[ 0 ] )
{
case 'blowfish':
/* vBulletin uses PasswordHash, however they md5 once the password prior to checking */
$md5_once_password = md5( $password );
require_once \IPS\ROOT_PATH . "/applications/convert/sources/Login/PasswordHash.php";
$ph = new \PasswordHash( $algo[ 1 ], FALSE );
return $ph->CheckPassword( $md5_once_password, $member->conv_password );
break;
case 'legacy':
/* Legacy Passwords are stored in a format of HASH SALT so we need to explode on the space. */
$token = explode( ' ', $member->conv_password );
$member->conv_password = $token[0];
$member->conv_password_extra = $token[1];
return \IPS\convert\Software\Core\Vbulletin::login( $member, $password );
break;
}
}
}
@@ -231,4 +231,34 @@ class _Wordpress extends \IPS\convert\Software
return array( "f_search_index_rebuild", "f_clear_caches" );
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public function login( $member, $password )
{
$success = FALSE;
// If the hash is still md5...
if ( \strlen( $member->conv_password ) <= 32 )
{
$success = ( \IPS\Login::compareHashes( $member->conv_password, md5( $password ) ) ) ? TRUE : FALSE;
}
// New pass hash check
else
{
// Init the pass class
require_once \IPS\ROOT_PATH . "/applications/convert/sources/Login/PasswordHash.php";
$ph = new \PasswordHash( 8, TRUE );
// Check it
$success = $ph->CheckPassword( $password, $member->conv_password ) ? TRUE : FALSE;
}
return $success;
}
}
@@ -1,7 +1,7 @@
<?php
/**
* @brief Converter XenForo 1.x Master Class
* @brief Converter XenForo 1.x/2.x Master Class
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @package Invision Community
@@ -20,6 +20,48 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
class _Xenforo extends \IPS\convert\Software
{
/**
* @brief The similarities between XF1 and XF2 are close enough that we can use the same converter
*/
public static $isLegacy = NULL;
/**
* Constructor
*
* @param \IPS\convert\App The application to reference for database and other information.
* @param bool Establish a DB connection
* @return void
* @throws \InvalidArgumentException
*/
public function __construct( \IPS\convert\App $app, $needDB=TRUE )
{
$return = parent::__construct( $app, $needDB );
if ( $needDB )
{
try
{
/* Is this XF1 or XF2 */
if ( static::$isLegacy === NULL )
{
$version = $this->db->select( 'MAX(version_id)', 'xf_template', array( "addon_id=?", 'XF' ) )->first();
if ( $version < 2000010 )
{
static::$isLegacy = TRUE;
}
else
{
static::$isLegacy = FALSE;
}
}
}
catch( \Exception $e ) {} # If we can't query, we won't be able to do anything anyway
}
return $return;
}
/**
* Software Name
*
@@ -27,7 +69,7 @@ class _Xenforo extends \IPS\convert\Software
*/
public static function softwareName()
{
return "XenForo (1.x)";
return "XenForo (1.5.x/2.0.x)";
}
/**
@@ -85,10 +127,10 @@ class _Xenforo extends \IPS\convert\Software
'where' => NULL
),
'convertMemberHistory' => array(
'table' => 'xf_user_change_log',
'table' => ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) ? 'xf_change_log' : 'xf_user_change_log',
'where' => NULL
),
'convertWarnReason' => array(
'convertWarnReasons' => array(
'table' => 'xf_warning_definition',
'where' => NULL
),
@@ -123,6 +165,34 @@ class _Xenforo extends \IPS\convert\Software
);
}
/**
* Count Source Rows for a specific step
*
* @param string $table The table containing the rows to count.
* @param array|NULL $where WHERE clause to only count specific rows, or NULL to count all.
* @param bool $recache Skip cache and pull directly (updating cache)
* @return integer
* @throws \IPS\convert\Exception
*/
public function countRows( $table, $where=NULL, $recache=FALSE )
{
switch( $table )
{
/* XF1 */
case 'xf_user_change_log':
return $this->db->select( 'count(log_id)', 'xf_user_change_log', array( $this->db->in( 'field', array_keys( static::$changeLogTypes ) ) ) )->first();
break;
/* XF2 */
case 'xf_change_log':
return $this->db->select( 'count(log_id)', 'xf_change_log', array( 'content_type=? AND ' . $this->db->in( 'field', array_keys( static::$changeLogTypes ) ), 'user' ) )->first();
break;
default:
return parent::countRows( $table, $where, $recache );
break;
}
}
/**
* Can we convert passwords from this software.
*
@@ -218,7 +288,7 @@ class _Xenforo extends \IPS\convert\Software
foreach( $this->db->select( '*', 'xf_user_field' ) AS $field )
{
\IPS\Member::loggedIn()->language()->words["map_pfield_{$field['field_id']}"] = $this->getPhrase( "user_field_{$field['field_id']}" );
\IPS\Member::loggedIn()->language()->words["map_pfield_{$field['field_id']}"] = $this->getPhrase( "user_field_{$field['field_id']}", "user_field_title.{$field['field_id']}" );
\IPS\Member::loggedIn()->language()->words["map_pfield_{$field['field_id']}_desc"] = \IPS\Member::loggedIn()->language()->addToStack( 'map_pfield_desc' );
$return['convertProfileFields']["map_pfield_{$field['field_id']}"] = array(
@@ -324,7 +394,7 @@ class _Xenforo extends \IPS\convert\Software
continue;
}
$title = $this->getPhrase( 'option_' . $setting );
$title = $this->getPhrase( 'option_' . $setting, 'option.' . $setting );
if ( !$title )
{
@@ -340,13 +410,15 @@ class _Xenforo extends \IPS\convert\Software
/**
* Helper to fetch a xenforo phrase
*
* @param string $title Phrase
* @param string $xfOneTitle XF1 Phrase title
* @param string $xfTwoTitle XF2 Phrase Title
* @return string|null
*/
protected function getPhrase( $title )
protected function getPhrase( $xfOneTitle, $xfTwoTitle )
{
try
{
$title = ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) ? $xfTwoTitle : $xfOneTitle;
return $this->db->select( 'phrase_text', 'xf_phrase', array( "title=?", $title ) )->first();
}
catch( \UnderflowException $e )
@@ -379,6 +451,9 @@ class _Xenforo extends \IPS\convert\Software
/* First Post Data */
\IPS\Task::queue( 'convert', 'RebuildConversationFirstIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
/* Attachments */
\IPS\Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
/* Rebuild Leaderboard */
\IPS\Task::queue( 'core', 'RebuildReputationLeaderboard', array(), 4 );
\IPS\Db::i()->delete('core_reputation_leaderboard_history');
@@ -395,7 +470,7 @@ class _Xenforo extends \IPS\convert\Software
public static function fixPostData( $post )
{
// run everything through htmlspecialchars to prevent XSS
$post = htmlspecialchars( $post, \IPS\HTMLENTITIES | ENT_QUOTES, 'UTF-8', FALSE );
$post = htmlspecialchars( $post, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE );
// find YouTube ID's and replace.
$post = preg_replace( '#\[media=youtube\](.+?)\[/media\]#i', '[media]http://www.youtube.com/watch?v=$1[/media]', $post );
@@ -410,9 +485,9 @@ class _Xenforo extends \IPS\convert\Software
$mentions = array();
foreach( $matches[1] AS $k => $v )
{
if ( isset( $matches[2][$k] ) )
if ( isset( $matches[2][ $k ] ) )
{
$name = trim( $matches[2][$k], '@' );
$name = trim( $matches[2][ $k ], '@' );
$mentions[$name] = $v;
}
}
@@ -422,17 +497,17 @@ class _Xenforo extends \IPS\convert\Software
$cardUrls = array();
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'name', array_keys( $mentions ) ) ) ), 'IPS\Member' ) AS $member )
{
$maps[$member->name] = $member->member_id;
$urls[$member->name] = $member->url();
$cardUrls[$member->name] = $member->url()->setQueryString( 'do', 'hovercard' );
$maps[ $member->name ] = $member->member_id;
$urls[ $member->name ] = $member->url();
$cardUrls[ $member->name ] = $member->url()->setQueryString( 'do', 'hovercard' );
}
foreach( $mentions AS $member_name => $member_id )
{
$maps[$member_name] = preg_quote( $maps[$member_name], '#' );
$member_name = preg_quote( $member_name, '#' );
$maps[ $member_name ] = preg_quote( $maps[ $member_name ], '#' );
$memberNameEscaped = preg_quote( $member_name, '#' );
$post = preg_replace( "#\[user={$member_id}\]@?{$member_name}\[\/user\]#i", "<a contenteditable=\"false\" rel=\"\" href=\"{$urls[$member_name]}\" data-mentionid=\"{$maps[$member_name]}\" data-ipshover-target=\"{$cardUrls[$member_name]}\" data-ipshover=\"\">@{$member_name}</a>", $post );
$post = preg_replace( "#\[user={$member_id}\]@?{$memberNameEscaped}\[\/user\]#i", "<a contenteditable=\"false\" rel=\"\" href=\"{$urls[ $member_name ]}\" data-mentionid=\"{$maps[ $member_name ]}\" data-ipshover-target=\"{$cardUrls[ $member_name ]}\" data-ipshover=\"\">@{$member_name}</a>", $post );
}
}
}
@@ -530,18 +605,52 @@ class _Xenforo extends \IPS\convert\Software
/* We need to figure out where our file lives - if it's remote, then we need to use file_get_contents() and pass the raw data. */
$filepath = NULL;
$filedata = NULL;
if ( mb_substr( $row['image_url'], 0, 4 ) == 'http' )
$filepathx2 = NULL;
$filedatax2 = NULL;
$filenamex2 = NULL;
/* Sprites */
if( $row['sprite_mode'] )
{
$images = $this->_emoticonsFromSprite( rtrim( $this->app->_session['more_info']['convertEmoticons']['emoticon_path'], '/' ) . '/' . $row['image_url'], \unserialize( $row['sprite_params'] ) );
$filedata = $images['emoticon'];
if( isset( $images['emoticon_x2'] ) )
{
$filedatax2 = $images['emoticon_x2'];
/* We always save the sprites to PNG */
$filenamex2 = "{$row['title']}.png";
}
/* We always save the sprites to PNG */
$filename = "{$row['title']}.png";
}
elseif ( mb_substr( $row['image_url'], 0, 4 ) == 'http' )
{
$filedata = file_get_contents( $row['image_url'] );
$fileurl = explode( '/', $row['image_url'] );
$filename = array_pop( $fileurl );
if( static::$isLegacy == FALSE AND !empty( $row['image_url_2x'] ) )
{
$filedata = file_get_contents( $row['image_url_2x'] );
$fileurlx2 = explode( '/', $row['image_url_2x'] );
$filenamex2 = array_pop( $fileurlx2 );
}
}
else
{
$fileurl = explode( '/', $row['image_url'] );
$filename = array_pop( $fileurl );
$filepath = rtrim( $this->app->_session['more_info']['convertEmoticons']['emoticon_path'], '/' ) . '/' . implode( '/', $fileurl );
if( static::$isLegacy == FALSE AND !empty( $row['image_url_2x'] ) )
{
$fileurlx2 = explode( '/', $row['image_url_2x'] );
$filenamex2 = array_pop( $fileurlx2 );
$filepathx2 = rtrim( $this->app->_session['more_info']['convertEmoticons']['emoticon_path'], '/' ) . '/' . implode( '/', $fileurlx2 );
}
}
/* XenForo allows multiple codes - we don't. */
@@ -552,7 +661,7 @@ class _Xenforo extends \IPS\convert\Software
try
{
$category = $this->db->select( '*', 'xf_smilie_category', array( "smilie_category_id=?", $row['smilie_category_id'] ) )->first();
$title = $this->getPhrase( "smilie_category_{$category['smilie_category_id']}_title" );
$title = $this->getPhrase( "smilie_category_{$category['smilie_category_id']}_title", "smilie_category_title.{$category['smilie_category_id']}" );
if ( is_null( $title ) )
{
@@ -579,10 +688,11 @@ class _Xenforo extends \IPS\convert\Software
'id' => $row['smilie_id'],
'typed' => $code,
'filename' => $filename,
'filenamex2' => $filenamex2,
'emo_position' => $row['display_order'],
);
$libraryClass->convertEmoticon( $info, $set, $this->app->_session['more_info']['convertEmoticons']['keep_existing_emoticons'], $filepath, $filedata );
$libraryClass->convertEmoticon( $info, $set, $this->app->_session['more_info']['convertEmoticons']['keep_existing_emoticons'], $filepath, $filedata, $filepathx2, $filedatax2 );
$libraryClass->setLastKeyValue( $row['smilie_id'] );
}
@@ -603,8 +713,8 @@ class _Xenforo extends \IPS\convert\Software
$info['pf_id'] = $row['field_id'];
$merge = $this->app->_session['more_info']['convertProfileFields']["map_pfield_{$row['field_id']}"] != 'none' ? $this->app->_session['more_info']['convertProfileFields']["map_pfield_{$row['field_id']}"] : NULL;
$info['pf_type'] = $this->_fieldMap( $row['field_type'] );
$info['pf_name'] = $this->getPhrase( "user_field_{$row['field_id']}" );
$info['pf_desc'] = $this->getPhrase( "user_field_{$row['field_id']}_desc" );
$info['pf_name'] = $this->getPhrase( "user_field_{$row['field_id']}", "user_field_title.{$row['field_id']}" );
$info['pf_desc'] = $this->getPhrase( "user_field_{$row['field_id']}_desc", "user_field_desc.{$row['field_id']}" );
$info['pf_content'] = ( !in_array( $row['field_type'], array( 'textbox', 'textarea' ) ) ) ? \unserialize( $row['field_choices'] ) : NULL;
$info['pf_not_null'] = $row['required'];
$info['pf_member_hide'] = ( $row['viewable_profile'] > 0 ) ? 0 : 1;
@@ -734,7 +844,7 @@ class _Xenforo extends \IPS\convert\Software
*
* @return void
*/
public function convertWarnReason()
public function convertWarnReasons()
{
$libraryClass = $this->getLibrary();
@@ -742,7 +852,7 @@ class _Xenforo extends \IPS\convert\Software
foreach( $this->fetch( 'xf_warning_definition', 'warning_definition_id' ) AS $row )
{
$title = $this->getPhrase( "warning_definition_{$row['warning_definition_id']}_title" );
$title = $this->getPhrase( "warning_definition_{$row['warning_definition_id']}_title", "warning_title.{$row['warning_definition_id']}" );
$remove = ( $row['expiry_type'] == 'never' ) ? 0 : $row['expiry_default'];
switch( $row['expiry_type'] )
@@ -777,7 +887,7 @@ class _Xenforo extends \IPS\convert\Software
/**
* @brief Member history type map
*/
protected $changeLogTypes = array( 'username' => 'display_name', 'email' => 'email_change', 'user_group_id' => 'member_group_id', 'secondar_group_ids' => 'mgroup_others' );
protected static $changeLogTypes = array( 'username' => 'display_name', 'email' => 'email_change', 'user_group_id' => 'member_group_id', 'secondary_group_ids' => 'mgroup_others' );
/**
* Convert member history
@@ -790,47 +900,27 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass::setKey( 'log_id' );
$fixGroups = function( $string )
$userField = 'user_id';
$table = 'xf_user_change_log';
if( ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) )
{
$groups = explode( ',', $string );
$newGroups = array();
array_walk( $groups, function( &$item, $key ) use ( &$newGroups )
{
try
{
$item = $this->app->getLink( $item, 'core_groups' );
$newGroups[] = $item;
}
catch( \OutOfRangeException $e ){}
});
return implode( ',', $newGroups );
};
foreach( $this->fetch( 'xf_user_change_log', 'log_id' ) AS $change )
$table = 'xf_change_log';
$userField = 'content_id';
$where = array( 'content_type=? AND ' . $this->db->in( 'field', array_keys( static::$changeLogTypes ) ), 'user' );
}
else
{
if( !in_array( $change['field'], array_keys( $this->changeLogTypes ) ) )
{
$libraryClass->setLastKeyValue( $change['log_id'] );
continue;
}
/* Any processing we need to do */
switch( $this->changeLogTypes[ $change['field'] ] )
{
case 'member_group_id':
case 'mgroup_others':
$change['old_value'] = $fixGroups( $change['old_value'] );
$change['new_value'] = $fixGroups( $change['new_value'] );
break;
}
$where = array( $this->db->in( 'field', array_keys( static::$changeLogTypes ) ) );
}
foreach( $this->fetch( $table, 'log_id', $where ) AS $change )
{
$libraryClass->convertMemberHistory( array(
'log_id' => $change['log_id'],
'log_member' => $change['user_id'],
'log_by' => ( $change['edit_user_id'] !== $change['user_id'] ) ? $change['edit_user_id'] : null,
'log_type' => $this->changeLogTypes[ $change['field'] ],
'log_member' => $change[ $userField ],
'log_by' => ( $change['edit_user_id'] !== $change[ $userField ] ) ? $change['edit_user_id'] : null,
'log_type' => static::$changeLogTypes[ $change['field'] ],
'log_data' => array( 'old' => $change['old_value'], 'new' => $change['new_value'] ),
'log_date' => $change['edit_date']
)
@@ -886,8 +976,8 @@ class _Xenforo extends \IPS\convert\Software
catch( \UnderflowException $e )
{
/* Ut oh... do something random. */
$hash = md5( uniqid() );
$salt = md5( uniqid() );
$hash = md5( mt_rand() );
$salt = md5( mt_rand() );
}
/* IP Address */
@@ -1415,4 +1505,103 @@ class _Xenforo extends \IPS\convert\Software
return NULL;
}
/**
* Process a login
*
* @param \IPS\Member $member The member
* @param string $password Password from form
* @return bool
*/
public function login( $member, $password )
{
$password = html_entity_decode( $password );
// XF 1.2
require_once \IPS\ROOT_PATH . "/applications/convert/sources/Login/PasswordHash.php";
$ph = new \PasswordHash( 10, TRUE );
if ( $ph->CheckPassword( $password, $member->conv_password ) )
{
return TRUE;
}
// XF 1.1
if ( extension_loaded( 'hash' ) )
{
$hashedPassword = hash( 'sha256', hash( 'sha256', $password ) . $member->misc );
}
else
{
$hashedPassword = sha1( sha1( $password ) . $member->misc );
}
if ( \IPS\Login::compareHashes( $member->conv_password, $hashedPassword ) )
{
return TRUE;
}
// If they converted vB > XF > IPB then passwords may be in vB format still - try that.
return \IPS\convert\Software\Core\Vbulletin::login( $member, $password );
}
/**
* @brief Cache sprite image objects
*/
protected $_spriteImages = array();
/**
* Return image from sprite - We must use GD for this, it's generally available on most servers
* And we don't have any relevant methods in either image handler for doing this completly within
* the image class
*
* @param string $sprite Sprite path
* @param array $spriteParams Sprite parameters
* @return array
*/
protected function _emoticonsFromSprite( $sprite, $spriteParams )
{
$key = md5( $sprite );
/* Set up image canvas */
if( !isset( $this->_spriteImages[ $key ] ) )
{
if( !file_exists( $sprite ) )
{
return array( 'emoticon' => NULL );
}
$this->_spriteImages[ $key ] = new \IPS\Image\Gd( file_get_contents( $sprite ) );
}
/* x2 Emoticon? */
$multiplier = 1;
if( $this->_spriteImages[ $key ]->width > $spriteParams['w'] )
{
$multiplier = 2;
}
$emoticon = \IPS\Image\Gd::newImageCanvas( $spriteParams['w'] * $multiplier, $spriteParams['h'] * $multiplier, array( 0, 0, 0 ) );
/* Set the background to transparent */
imagefill( $emoticon->image, 0, 0, imagecolorallocatealpha( $emoticon->image, 0, 0, 0, 127 ) );
/* Extract sprite */
imagecopy( $emoticon->image, $this->_spriteImages[ $key ]->image, 0, 0, abs( $spriteParams['x'] ) * $multiplier, abs( $spriteParams['y'] ) * $multiplier, $spriteParams['w'] * $multiplier, $spriteParams['h'] * $multiplier );
/* x2 Emoticon? */
$return = array();
if( $multiplier == 2 )
{
$return['emoticon_x2'] = (string) $emoticon;
/* Resize */
$emoticon->resize( $spriteParams['w'], $spriteParams['h'] );
}
$return['emoticon'] = (string) $emoticon;
unset( $emoticon );
return $return;
}
}
@@ -20,6 +20,54 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
class _Xenforo extends \IPS\convert\Software
{
/**
* @brief The similarities between XF1 and XF2 are close enough that we can use the same converter
*/
public static $isLegacy = NULL;
/**
* @brief XF2 Has prefixes on RM tables
*/
public static $tablePrefix = '';
/**
* Constructor
*
* @param \IPS\convert\App The application to reference for database and other information.
* @param bool Establish a DB connection
* @return void
* @throws \InvalidArgumentException
*/
public function __construct( \IPS\convert\App $app, $needDB=TRUE )
{
$return = parent::__construct( $app, $needDB );
if ( $needDB )
{
try
{
/* Is this XF1 or XF2 */
if ( static::$isLegacy === NULL )
{
$version = $this->db->select( 'MAX(version_id)', 'xf_template', array( "addon_id=?", 'XF' ) )->first();
if ( $version < 2000010 )
{
static::$isLegacy = TRUE;
}
else
{
static::$tablePrefix = 'rm_';
static::$isLegacy = FALSE;
}
}
}
catch( \Exception $e ) {} # If we can't query, we won't be able to do anything anyway
}
return $return;
}
/**
* Software Name
*
@@ -28,7 +76,7 @@ class _Xenforo extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "XenForo Resource Manager";
return "XenForo Resource Manager (1.5.x/2.0.x)";
}
/**
@@ -51,24 +99,56 @@ class _Xenforo extends \IPS\convert\Software
{
return array(
'convertDownloadsCfields' => array(
'table' => 'xf_resource_field',
'table' => 'xf_' . static::$tablePrefix . 'resource_field',
'where' => NULL
),
'convertDownloadsCategories' => array(
'table' => 'xf_resource_category',
'table' => ( static::$isLegacy ? 'xf_resource_category' : 'xf_rm_category' ),
'where' => NULL
),
'convertDownloadsFiles' => array(
'table' => 'xf_resource',
'where' => array( "is_fileless=?", 0 )
'table' => 'xf_' . static::$tablePrefix . 'resource',
'where' => static::$isLegacy ? array( "is_fileless=?", 0 ) : array( 'resource_type=?', 'download' )
),
'convertDownloadsReviews' => array(
'table' => 'xf_resource_rating',
'table' => 'xf_' . static::$tablePrefix . 'resource_rating',
'where' => NULL
)
);
}
/**
* Count Source Rows for a specific step
*
* @param string $table The table containing the rows to count.
* @param array|NULL $where WHERE clause to only count specific rows, or NULL to count all.
* @param bool $recache Skip cache and pull directly (updating cache)
* @return integer
* @throws \IPS\convert\Exception
*/
public function countRows( $table, $where=NULL, $recache=FALSE )
{
switch( $table )
{
case 'xf_resource_field':
/* We need to do this for the connection check because all tables have different names in XF2 */
if( $this->db->checkForTable( 'xf_resource_field' ) )
{
return $this->db->select( 'COUNT(*)', 'xf_resource_field' )->first();
}
elseif( $this->db->checkForTable( 'xf_rm_resource_field' ) )
{
return $this->db->select( 'COUNT(*)', 'xf_rm_resource_field' )->first();
}
break;
default:
return parent::countRows( $table, $where, $recache );
break;
}
}
/**
* Requires Parent
*
@@ -153,13 +233,15 @@ class _Xenforo extends \IPS\convert\Software
/**
* Helper to fetch a xenforo phrase
*
* @param string $title Phrase
* @param string $xfOneTitle XF1 Phrase title
* @param string $xfTwoTitle XF2 Phrase Title
* @return string|null
*/
protected function getPhrase( $title )
protected function getPhrase( $xfOneTitle, $xfTwoTitle )
{
try
{
$title = ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) ? $xfTwoTitle : $xfOneTitle;
return $this->db->select( 'phrase_text', 'xf_phrase', array( "title=?", $title ) )->first();
}
catch( \UnderflowException $e )
@@ -177,15 +259,26 @@ class _Xenforo extends \IPS\convert\Software
{
$libraryClass = $this->getLibrary();
foreach( $this->fetch( 'xf_resource_field', 'field_id' ) AS $row )
foreach( $this->fetch( 'xf_' . static::$tablePrefix . 'resource_field', 'field_id' ) AS $row )
{
$inputFormat = '';
if( $row['match_type'] == 'regex' AND static::$isLegacy === FALSE )
{
$inputFormat = ( $row['match_type'] == 'regex' ) ? '/' . $row['match_regex'] .'/i' : '';
}
elseif( $row['match_type'] == 'regex' )
{
$json = json_decode( $row['match_params'] , TRUE );
$inputFormat = '/' . $json['regex'] .'/i';
}
$info = array(
'cf_id' => $row['field_id'],
'cf_type' => $this->_mapFieldType( $row['field_type'], $row['match_type'] ),
'cf_content' => \unserialize( $row['field_choices'] ),
'cf_content' => static::$isLegacy ? \unserialize( $row['field_choices'] ) : json_decode( $row['field_choices'], TRUE ),
'cf_not_null' => $row['required'],
'cf_max_input' => $row['max_length'],
'cf_input_format' => ( $row['match_type'] == 'regex' ) ? '/' . $row['match_regex'] .'/i' : '',
'cf_input_format' => $inputFormat,
'cf_position' => $row['display_order'],
'cf_multiple' => ( in_array( $row['field_type'], array( 'checkbox', 'multiselect' ) ) ) ? 1 : 0
);
@@ -255,11 +348,11 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass::setKey( 'resource_category_id' );
foreach( $this->fetch( 'xf_resource_category', 'resource_category_id' ) AS $row )
foreach( $this->fetch( ( static::$isLegacy ? 'xf_resource_category' : 'xf_rm_category' ), 'resource_category_id' ) AS $row )
{
/* Custom Fields for this category */
$ccfields = array();
foreach( $this->db->select( 'field_id', 'xf_resource_field_category', array( "resource_category_id=?", $row['resource_category_id'] ) ) AS $field )
foreach( $this->db->select( 'field_id', ( static::$isLegacy ? 'xf_resource_field_category' : 'xf_rm_category_field' ), array( "resource_category_id=?", $row['resource_category_id'] ) ) AS $field )
{
$ccfields[] = $field;
}
@@ -268,7 +361,7 @@ class _Xenforo extends \IPS\convert\Software
$prefix = '';
if ( $row['thread_prefix_id'] AND $row['thread_node_id'] )
{
$prefix = $this->getPhrase( "thread_prefix_{$row['thread_prefix_id']}" );
$prefix = $this->getPhrase( "thread_prefix_{$row['thread_prefix_id']}", "thread_prefix.{$row['thread_prefix_id']}" );
if ( is_null( $prefix ) )
{
@@ -279,8 +372,8 @@ class _Xenforo extends \IPS\convert\Software
$info = array(
'cid' => $row['resource_category_id'],
'cparent' => $row['parent_category_id'],
'cname' => $row['category_title'],
'cdesc' => $row['category_description'],
'cname' => isset( $row['category_title'] ) ? $row['category_title'] : $row['title'],
'cdesc' => isset( $row['category_description'] ) ? $row['category_description'] : $row['description'],
'copen' => ( $row['allow_local'] OR $row['allow_external'] ) ? 1 : 0,
'cposition' => $row['display_order'],
'ccfields' => $ccfields,
@@ -291,7 +384,7 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass->convertDownloadsCategory( $info );
/* Follows */
foreach( $this->db->select( '*', 'xf_resource_category_watch', array( "notify_on=? AND resource_category_id=?", 'resource', $row['resource_category_id'] ) ) AS $follow )
foreach( $this->db->select( '*', ( static::$isLegacy ? 'xf_resource_category_watch' : 'xf_rm_category_watch' ), array( "notify_on=? AND resource_category_id=?", 'resource', $row['resource_category_id'] ) ) AS $follow )
{
$libraryClass->convertFollow( array(
'follow_app' => 'downloads',
@@ -305,8 +398,7 @@ class _Xenforo extends \IPS\convert\Software
'follow_notify_meta' => '',
'follow_notify_freq' => ( $follow['send_alert'] OR $follow['send_email'] ) ? 'immediate' : 'none',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
'follow_visible' => 1
) );
}
@@ -325,21 +417,21 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass::setKey( 'resource_id' );
foreach( $this->fetch( 'xf_resource', 'resource_id', array( "is_fileless=?", 0 ) ) AS $row )
foreach( $this->fetch( 'xf_' . static::$tablePrefix . 'resource', 'resource_id', ( static::$isLegacy ? array( "is_fileless=?", 0 ) : array( 'resource_type=?', 'download' ) ) ) AS $row )
{
/* Latest Version Stuff */
$latest = $this->db->select( '*', 'xf_resource_update', array( "xf_resource_update.resource_id=?", $row['resource_id'] ), "xf_resource_update.post_date DESC" )
->join( 'xf_resource_version', "xf_resource_update.resource_update_id = xf_resource_version.resource_update_id" )
$latest = $this->db->select( '*', 'xf_' . static::$tablePrefix . 'resource_update', array( "xf_" . static::$tablePrefix . "resource_update.resource_id=?", $row['resource_id'] ), "xf_" . static::$tablePrefix . "resource_update.post_date DESC" )
->join( 'xf_' . static::$tablePrefix . 'resource_version', "xf_" . static::$tablePrefix . "resource_update.post_date = xf_" . static::$tablePrefix . "resource_version.release_date" )
->first();
$latest_version = $latest['version_string'];
$latest_changelog = $latest['message'];
$latestVersion = $latest['version_string'];
$latestChangelog = $latest['message'];
/* Featured */
$featured = 0;
try
{
$this->db->select( '*', 'xf_resource_feature', array( "resource_id=?", $row['resource_id'] ) )->first();
$this->db->select( '*', 'xf_' . static::$tablePrefix . 'resource_feature', array( "resource_id=?", $row['resource_id'] ) )->first();
$featured = 1;
}
@@ -350,19 +442,20 @@ class _Xenforo extends \IPS\convert\Software
'file_desc' => $row['tag_line'],
'file_name' => $row['title'],
'file_cat' => $row['resource_category_id'],
'file_downloads' => $this->db->select( 'COUNT(*)', 'xf_resource_download', array( 'resource_id=?', $row['resource_id'] ) )->first(),
'file_downloads' => $this->db->select( 'COUNT(*)', 'xf_' . static::$tablePrefix . 'resource_download', array( 'resource_id=?', $row['resource_id'] ) )->first(),
'file_reviews' => $row['review_count'],
'file_submitted' => $row['resource_date'],
'file_updated' => $row['last_update'],
'file_submitter' => $row['user_id'],
'file_topicid' => $row['discussion_thread_id'],
'file_version' => $latest_version,
'file_changelog' => $latest_changelog,
'file_version' => $latestVersion,
'file_changelog' => $latestChangelog,
'file_featured' => $featured,
);
$records = array();
$recordit = $this->db->select( '*', 'xf_resource_update', array( "xf_resource_update.resource_id=?", $row['resource_id'] ) )->join( 'xf_resource_version', "xf_resource_update.resource_update_id = xf_resource_version.resource_update_id" );
$recordit = $this->db->select( '*', 'xf_' . static::$tablePrefix . 'resource_update', array( "xf_" . static::$tablePrefix . "resource_update.resource_id=?", $row['resource_id'] ) )->join( 'xf_' . static::$tablePrefix . 'resource_version', "xf_" . static::$tablePrefix . "resource_update.post_date = xf_" . static::$tablePrefix . "resource_version.release_date" );
foreach( $recordit AS $record )
{
if ( $record['download_url'] )
@@ -419,24 +512,24 @@ class _Xenforo extends \IPS\convert\Software
}
$cfields = array();
foreach( $this->db->select( 'field_id, field_value', 'xf_resource_field_value', array( "resource_id=?", $row['resource_id'] ) )->setKeyField( 'field_id' )->setValueField( 'field_value' ) AS $key => $value )
foreach( $this->db->select( 'field_id, field_value', 'xf_' . static::$tablePrefix . 'resource_field_value', array( "resource_id=?", $row['resource_id'] ) )->setKeyField( 'field_id' )->setValueField( 'field_value' ) AS $key => $value )
{
$field_type = $this->db->select( 'field_type', 'xf_resource_field', array( "field_id=?", $key ) )->first();
$field_type = $this->db->select( 'field_type', 'xf_' . static::$tablePrefix . 'resource_field', array( "field_id=?", $key ) )->first();
if ( in_array( $field_type, array( 'checkbox', 'multiselect' ) ) )
{
$cfields[$key] = json_encode( \unserialize( $value ) );
$cfields[$key] = static::$isLegacy ? json_encode( \unserialize( $value ) ) : $value;
}
else
{
$cfields[$key] = $value;
}
}
$libraryClass->convertDownloadsFile( $info, $records, $cfields );
/* Follows */
foreach( $this->db->select( '*', 'xf_resource_watch', array( "resource_id=?", $row['resource_id'] ) ) AS $follow )
foreach( $this->db->select( '*', 'xf_' . static::$tablePrefix . 'resource_watch', array( "resource_id=?", $row['resource_id'] ) ) AS $follow )
{
$libraryClass->convertFollow( array(
'follow_app' => 'downloads',
@@ -450,8 +543,7 @@ class _Xenforo extends \IPS\convert\Software
'follow_notify_meta' => '',
'follow_notify_freq' => ( $follow['email_subscribe'] ) ? 'immediate' : 'none',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
'follow_visible' => 1
) );
}
@@ -470,7 +562,7 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass::setKey( 'resource_rating_id' );
foreach( $this->fetch( 'xf_resource_rating', 'resource_rating_id' ) AS $row )
foreach( $this->fetch( 'xf_' . static::$tablePrefix . 'resource_rating', 'resource_rating_id' ) AS $row )
{
switch( $row['rating_state'] )
{
@@ -503,7 +595,7 @@ class _Xenforo extends \IPS\convert\Software
if ( $this->app->_session['more_info']['convertDownloadsReviews']['convert_author_replies_to_comments'] AND $row['author_response'] )
{
/* Get Resource */
$resource = $this->db->select( '*', 'xf_resource', array( "resource_id=?", $row['resource_id'] ) )->first();
$resource = $this->db->select( '*', 'xf_' . static::$tablePrefix . 'resource', array( "resource_id=?", $row['resource_id'] ) )->first();
$member = $this->db->select( '*', 'xf_user', array( "user_id=?", $row['user_id'] ) )->first();
$post = "<blockquote class=\"ipsQuote\" data-ipsQuote-username=\"{$member['username']}\">{$row['message']}</blockquote><p>{$row['author_response']}</p>";
@@ -519,4 +611,69 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass->setLastKeyValue( $row['resource_rating_id'] );
}
}
/**
* Check if we can redirect the legacy URLs from this software to the new locations
*
* @return NULL|\IPS\Http\Url
*/
public function checkRedirects()
{
$url = \IPS\Request::i()->url();
if( preg_match( '#/(resources/categories|resources)/(.+?)\.([0-9]+)#i', $url->data[ \IPS\Http\Url::COMPONENT_PATH ], $matches ) )
{
$oldId = (int) $matches[3];
switch( $matches[1] )
{
case 'resources/categories':
$class = '\IPS\downloads\Category';
$types = array( 'downloads_categories' );
break;
case 'resources':
$class = '\IPS\downloads\File';
$types = array( 'downloads_files' );
break;
}
}
if( isset( $class ) )
{
try
{
try
{
$data = (string) $this->app->getLink( $oldId, $types );
}
catch( \OutOfRangeException $e )
{
$data = (string) $this->app->getLink( $oldId, $types, FALSE, TRUE );
}
$item = $class::load( $data );
if( $item instanceof \IPS\Content )
{
if( $item->canView() )
{
return $item->url();
}
}
elseif( $item instanceof \IPS\Node\Model )
{
if( $item->can( 'view' ) )
{
return $item->url();
}
}
}
catch( \Exception $e )
{
return NULL;
}
}
return NULL;
}
}
@@ -119,7 +119,8 @@ class _Bbpress extends \IPS\convert\Software
'id' => $row['ID'],
'name' => $row['post_title'],
'description' => $row['post_content'],
'parent_id' => $row['post_parent'],
'parent_id' => $row['post_parent'] ?: NULL,
'sub_can_post' => 1
);
$libraryClass->convertForumsForum( $info );
@@ -74,6 +74,21 @@ class _Expressionengine extends \IPS\convert\Software
);
}
/**
* Allows software to add additional menu row options
*
* @param array $rows Existing rows
* @return array
*/
public function extraMenuRows( $rows )
{
$rows['convertOtherPosts'] = $rows['convertForumsPosts'];
$rows['convertOtherPosts']['step_method'] = 'convertOtherPosts';
$rows['convertOtherPosts']['source_rows'] = $this->countRows( static::canConvert()['convertOtherPosts']['table'], static::canConvert()['convertOtherPosts']['where'] );
return $rows;
}
/**
* Requires Parent
*
@@ -0,0 +1,524 @@
<?php
/**
* @brief Converter Invision Community Class
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @package Invision Community
* @subpackage Converter
* @since 26 July 2017
*/
namespace IPS\convert\Software\Forums;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
class _Invisioncommunity extends \IPS\convert\Software
{
/**
* @brief Whether the versions of IPS4 match
*/
public static $versionMatch = FALSE;
/**
* @brief Whether the database has been required
*/
public static $dbNeeded = FALSE;
public function __construct( \IPS\convert\App $app, $needDB=TRUE )
{
/* Set filename obscuring flag */
\IPS\convert\Library::$obscureFilenames = FALSE;
$return = parent::__construct( $app, $needDB );
if( $needDB )
{
static::$dbNeeded = TRUE;
$version = $this->db->select( 'app_version', 'core_applications', array( 'app_directory=?', 'core' ) )->first();
/* We're matching against the human version since the long version can change with patches */
if( $version == \IPS\Application::load( 'core')->version )
{
static::$versionMatch = TRUE;
}
/* Get parent sauce */
$this->parent = $this->app->get__parent()->getSource();
}
return $return;
}
/**
* Software Name
*
* @return string
*/
public static function softwareName()
{
/* Child classes must override this method */
return 'Invision Community (' . \IPS\Application::load( 'core' )->version . ')';
}
/**
* Software Key
*
* @return string
*/
public static function softwareKey()
{
/* Child classes must override this method */
return "invisioncommunity";
}
/**
* Content we can convert from this software.
*
* @return array
*/
public static function canConvert()
{
if( !static::$versionMatch AND static::$dbNeeded )
{
throw new \IPS\convert\Exception( 'convert_invision_mismatch' );
}
return array(
'convertForumsForums' => array(
'table' => 'forums_forums',
'where' => NULL,
),
'convertForumsTopics' => array(
'table' => 'forums_topics',
'where' => NULL,
),
'convertForumsPosts' => array(
'table' => 'forums_posts',
'where' => NULL,
),
'convertForumsTopicsMultimods' => array(
'table' => 'forums_topic_mmod',
'where' => NULL,
),
'convertForumsRssImports' => array(
'table' => 'forums_rss_import',
'where' => NULL,
),
'convertForumsRssImported' => array(
'table' => 'forums_rss_imported',
'where' => NULL,
),
'convertForumsQuestionRatings' => array(
'table' => 'forums_question_ratings',
'where' => NULL,
),
'convertForumsAnswerRatings' => array(
'table' => 'forums_answer_ratings',
'where' => NULL,
),
'convertAttachments' => array(
'table' => 'core_attachments',
'where' => NULL
),
);
}
/**
* Requires Parent
*
* @return boolean
*/
public static function requiresParent()
{
return TRUE;
}
/**
* List of conversion methods that require additional information
*
* @return array
*/
public static function checkConf()
{
return array(
'convertAttachments'
);
}
/**
* Get More Information
*
* @param string $method Method name
* @return array
*/
public function getMoreInfo( $method )
{
$return = array();
switch( $method )
{
case 'convertAttachments':
\IPS\Member::loggedIn()->language()->words["upload_path"] = \IPS\Member::loggedIn()->language()->addToStack( 'convert_invision_upload_input' );
\IPS\Member::loggedIn()->language()->words["upload_path_desc"] = \IPS\Member::loggedIn()->language()->addToStack( 'convert_invision_upload_input_desc' );
$return[ $method ] = array(
'upload_path' => array(
'field_class' => 'IPS\\Helpers\\Form\\Text',
'field_default' => isset( $this->parent->app->_session['more_info']['convertEmoticons']['upload_path'] ) ? $this->parent->app->_session['more_info']['convertEmoticons']['upload_path'] : NULL,
'field_required' => TRUE,
'field_extra' => array(),
'field_hint' => \IPS\Member::loggedIn()->language()->addToStack('convert_invision_upload_path'),
'field_validation' => function( $value ) { if ( !@is_dir( $value ) ) { throw new \DomainException( 'path_invalid' ); } },
)
);
break;
}
return ( isset( $return[ $method ] ) ) ? $return[ $method ] : array();
}
/**
* Possible Parent Conversions
*
* @return array
*/
public static function parents()
{
return array( 'core' => array( 'invisioncommunity' ) );
}
/**
* Finish - Adds everything it needs to the queues and clears data store
*
* @return array Messages to display
*/
public function finish()
{
/* Content Rebuilds */
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\forums\Forum', 'count' => 0 ), 5, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildItemCounts', array( 'class' => 'IPS\forums\Topic' ), 3, array( 'class' ) );
\IPS\Task::queue( 'convert', 'RebuildFirstPostIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
\IPS\Task::queue( 'convert', 'DeleteEmptyTopics', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
\IPS\Task::queue( 'convert', 'InvisionCommunityRebuildContent', array( 'app' => $this->app->app_id, 'link' => 'forums_posts', 'class' => 'IPS\forums\Topic\Post' ), 2, array( 'app', 'link', 'class' ) );
/* Caches */
\IPS\Task::queue( 'convert', 'RebuildTagCache', array( 'app' => $this->app->app_id, 'link' => 'forums_topics', 'class' => 'IPS\forums\Topic' ), 3, array( 'app', 'link', 'class' ) );
/* Rebuild Leaderboard */
\IPS\Task::queue( 'core', 'RebuildReputationLeaderboard', array(), 4 );
\IPS\Db::i()->delete('core_reputation_leaderboard_history');
return array( "f_forum_last_post_data", "f_rebuild_posts", "f_recounting_forums", "f_recounting_topics", "f_topic_tags_recount" );
}
/**
* Convert forums
*
* @return void
*/
public function convertForumsForums()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'id' );
foreach( $this->fetch( 'forums_forums', 'id' ) AS $row )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $row, 'forums_forums', 'forums' );
/* Add name after clearing other columns */
$row['name'] = $this->parent->getWord( 'forums_forum_' . $row['id'] );
$row['description'] = $this->parent->getWord( 'forums_forum_' . $row['id'] . '_desc' );
$libraryClass->convertForumsForum( $row );
/* Convert Follows */
foreach( $this->db->select( '*', 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_rel_id=?', 'forums', 'forum', $row['id'] ) ) as $follow )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $follow, 'core_follow', 'core' );
/* Change follow data */
$follow['follow_rel_id_type'] = 'forums_forums';
$libraryClass->convertFollow( $follow );
}
$libraryClass->setLastKeyValue( $row['id'] );
}
}
/**
* Convert topics
*
* @return void
*/
public function convertForumsTopics()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'tid' );
foreach( $this->fetch( 'forums_topics', 'tid' ) AS $row )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $row, 'forums_topics', 'forums' );
$libraryClass->convertForumsTopic( $row );
/* Convert Follows */
foreach( $this->db->select( '*', 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_rel_id=?', 'forums', 'topic', $row['tid'] ) ) as $follow )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $follow, 'core_follow', 'core' );
/* Change follow data */
$follow['follow_rel_id_type'] = 'forums_topics';
$libraryClass->convertFollow( $follow );
}
/* Convert Ratings */
foreach( $this->db->select( '*', 'core_ratings', array( 'class=? AND item_id=?', 'IPS\\forums\\Topic', $row['tid'] ) ) as $rating )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $rating, 'core_ratings', 'core' );
/* Change rating data */
$rating['item_link'] = 'forums_topics';
$libraryClass->convertRating( $rating );
}
/* Convert Tags */
foreach( $this->db->select( '*', 'core_tags', array( 'tag_meta_app=? AND tag_meta_area=? AND tag_meta_id=?', 'forums', 'forums', $row['tid'] ) ) as $tag )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $tag, 'core_tags', 'core' );
$libraryClass->convertTag( $tag );
}
$libraryClass->setLastKeyValue( $row['tid'] );
}
}
/**
* Convert posts
*
* @return void
*/
public function convertForumsPosts()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'pid' );
foreach( $this->fetch( 'forums_posts', 'pid' ) AS $row )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $row, 'forums_posts', 'forums' );
$libraryClass->convertForumsPost( $row );
/* Convert Edit History */
foreach( $this->db->select( '*', 'core_edit_history', array( 'class=? AND comment_id=?', 'IPS\\forums\\Topic\\Post', $row['pid'] ) ) as $history )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $history, 'core_edit_history', 'core' );
$libraryClass->convertEditHistory( $history );
}
/* Reputation */
foreach( $this->db->select( '*', 'core_reputation_index', array( "app=? AND type=? AND type_id=?", 'forums', 'pid', $row['pid'] ) ) AS $rep )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $rep, 'core_reputation_index', 'core' );
$libraryClass->convertReputation( $rep );
}
$libraryClass->setLastKeyValue( $row['pid'] );
}
}
/**
* Convert topics mmod
*
* @return void
*/
public function convertForumsTopicsMultimods()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'mm_id' );
foreach( $this->fetch( 'forums_topic_mmod', 'mm_id' ) AS $row )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $row, 'forums_topic_mmod', 'forums' );
/* Add name after clearing other columns */
$row['mm_name'] = $this->parent->getWord( 'forums_mmod_' . $row['mm_id'] );
$libraryClass->convertForumsTopicMultimod( $row );
$libraryClass->setLastKeyValue( $row['mm_id'] );
}
}
/**
* Convert rss imports
*
* @return void
*/
public function convertForumsRssImports()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'rss_import_id' );
foreach( $this->fetch( 'forums_rss_import', 'rss_import_id' ) AS $row )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $row, 'forums_rss_import', 'forums' );
$libraryClass->convertForumsRssImport( $row );
$libraryClass->setLastKeyValue( $row['rss_import_id'] );
}
}
/**
* Convert rss imported
*
* @return void
*/
public function convertForumsRssImported()
{
$libraryClass = $this->getLibrary();
foreach( $this->fetch( 'forums_rss_imported', 'rss_imported_tid' ) AS $row )
{
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $row, 'forums_rss_imported', 'forums' );
$libraryClass->convertForumsRssImported( $row );
}
}
/**
* Convert attachments
*
* @return void
*/
public function convertAttachments()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'attach_id' );
foreach( $this->fetch( 'core_attachments', 'attach_id' ) AS $row )
{
try
{
$attachmentMap = $this->db->select( '*', 'core_attachments_map', array( 'attachment_id=? AND location_key=?', $row['attach_id'], 'forums_Forums' ) )->first();
}
catch( \UnderflowException $e )
{
$libraryClass->setLastKeyValue( $row['attach_id'] );
continue;
}
/* Remove non-standard columns */
$this->parent->unsetNonStandardColumns( $row, 'core_attachments' );
$this->parent->unsetNonStandardColumns( $attachmentMap, 'core_attachments_map' );
/* Remap rows */
$name = explode( '/', $row['attach_location'] );
$row['attach_container'] = isset( $name[1] ) ? $name[0] : NULL;
$thumbName = explode( '/', $row['attach_thumb_location'] );
$row['attach_thumb_container'] = isset( $thumbName[1] ) ? $thumbName[0] : NULL;
$filePath = $this->app->_session['more_info']['convertAttachments']['upload_path'] . '/' . $row['attach_location'];
$thumbnailPath = $this->app->_session['more_info']['convertAttachments']['upload_path'] . '/' . $row['attach_thumb_location'];
unset( $row['attach_file'] );
$libraryClass->convertAttachment( $row, $attachmentMap, $filePath, NULL, $thumbnailPath );
$libraryClass->setLastKeyValue( $row['attach_id'] );
}
}
/**
* Check if we can redirect the legacy URLs from this software to the new locations
*
* @return NULL|\IPS\Http\Url
*/
public function checkRedirects()
{
$url = \IPS\Request::i()->url();
if( preg_match( '#/ips4merge/(forum|topic)/([0-9]+)-(.+?)#i', $url->data[ \IPS\Http\Url::COMPONENT_PATH ], $matches ) )
{
$oldId = (int) $matches[2];
switch( $matches[1] )
{
case 'forum':
$class = '\IPS\forums\Forum';
$types = array( 'forums', 'forums_forums' );
break;
case 'topic':
$class = '\IPS\forums\Topic';
$types = array( 'topics', 'forums_topics' );
if( \IPS\Request::i()->do == 'findComment' AND \IPS\Request::i()->comment )
{
$class = '\IPS\forums\Topic\Post';
$types = array( 'posts', 'forums_posts' );
$oldId = \IPS\Request::i()->comment;
}
break;
}
}
if( isset( $class ) )
{
try
{
try
{
$data = (string) $this->app->getLink( $oldId, $types );
}
catch( \OutOfRangeException $e )
{
$data = (string) $this->app->getLink( $oldId, $types, FALSE, TRUE );
}
$item = $class::load( $data );
if( $item instanceof \IPS\Content )
{
if( $item->canView() )
{
return $item->url();
}
}
elseif( $item instanceof \IPS\Node\Model )
{
if( $item->can( 'view' ) )
{
return $item->url();
}
}
}
catch( \Exception $e )
{
return NULL;
}
}
return NULL;
}
}
@@ -185,6 +185,17 @@ class _Mybb extends \IPS\convert\Software
return array( "f_forum_last_post_data", "f_rebuild_posts", "f_recounting_forums", "f_recounting_topics", "f_topic_tags_recount" );
}
/**
* Fix Post Data
*
* @param string Post
* @return string Fixed Posts
*/
public static function fixPostData( $post )
{
return \IPS\convert\Software\Core\Mybb::fixPostData( $post );
}
/**
* Convert forums
*
@@ -237,7 +248,6 @@ class _Mybb extends \IPS\convert\Software
'follow_notify_freq' => 'immediate',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -413,7 +423,6 @@ class _Mybb extends \IPS\convert\Software
'follow_notify_freq' => 'immediate',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -498,7 +507,7 @@ class _Mybb extends \IPS\convert\Software
/* Warnings */
foreach( $this->db->select( '*', 'warnings', array( "pid=?", $row['pid'] ) ) AS $warn )
{
$libraryClass->convertWarnLog( array(
$warnId = $libraryClass->convertWarnLog( array(
'wl_id' => $warn['wid'],
'wl_member' => $warn['uid'],
'wl_moderator' => $warn['issuedby'],
@@ -506,6 +515,17 @@ class _Mybb extends \IPS\convert\Software
'wl_points' => $warn['points'],
'wl_note_mods' => $warn['notes'],
) );
/* Add a member history record for this member */
$libraryClass->convertMemberHistory( array(
'log_id' => 'w' . $warn['wid'],
'log_member' => $warn['uid'],
'log_by' => $warn['issuedby'],
'log_type' => 'warning',
'log_data' => array( 'wid' => $warnId ),
'log_date' => $warn['dateline']
)
);
}
$libraryClass->setLastKeyValue( $row['pid'] );
@@ -28,7 +28,7 @@ class _Phpbb extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "phpBB 3.x";
return "phpBB (3.1.x/3.2.x)";
}
/**
@@ -229,7 +229,6 @@ class _Phpbb extends \IPS\convert\Software
'follow_notify_freq' => 'immediate',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -371,7 +370,6 @@ class _Phpbb extends \IPS\convert\Software
'follow_notify_freq' => 'immediate',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -495,10 +493,24 @@ class _Phpbb extends \IPS\convert\Software
$post = \IPS\Db::i()->select( 'post', 'forums_posts', array( "pid=?", $pid ) )->first();
$attachmentName = preg_quote( $row['real_filename'], '/' );
if ( preg_match( "/\[attachment=(.+?)?\]\<\!\-\- ia0 \-\-\>{$attachmentName}\<\!\-\- ia0 \-\-\>\[\/attachment\]/i", $post ) )
{
$post = preg_replace( "/\[attachment=(.+?)?\]\<\!\-\- ia0 \-\-\>{$attachmentName}\<\!\-\- ia0 \-\-\>\[\/attachment\]/i", '[attachment=' . $attachId . ':name]', $post );
$regex31 = '/\[attachment=(.+?)?\]\<\!\-\- ia[\d]+ \-\-\>' . $attachmentName . '\<\!\-\- ia[\d]+ \-\-\>\[\/attachment\]/i';
$regex32 = '/\<ATTACHMENT filename\="(.+?)?" index\="(.+?)?"\>\<s\>\[attachment\=(.+?)?\]\<\/s\>' . $attachmentName . '<e\>\[\/attachment\]\<\/e\>\<\/ATTACHMENT\>/i';
$changed = FALSE;
if( preg_match( $regex31, $post ) )
{
$changed = TRUE;
$post = preg_replace( $regex31, '[attachment=' . $attachId . ':name]', $post );
}
elseif( preg_match( $regex32, $post ) )
{
$changed = TRUE;
$post = preg_replace( $regex32, '[attachment=' . $attachId . ':name]', $post );
}
if( $changed )
{
\IPS\Db::i()->update( 'forums_posts', array( 'post' => $post ), array( "pid=?", $pid ) );
}
}
@@ -28,7 +28,7 @@ class _Punbb extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "PunBB 1.x";
return "PunBB (1.x)";
}
/**
@@ -249,7 +249,6 @@ class _Punbb extends \IPS\convert\Software
'follow_notify_freq' => 'immediate',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -29,7 +29,7 @@ class _Smf extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "Simple Machines Forum";
return "Simple Machines Forum (2.0.x)";
}
/**
@@ -75,6 +75,21 @@ class _Smf extends \IPS\convert\Software
);
}
/**
* Allows software to add additional menu row options
*
* @param array $rows Existing rows
* @return array
*/
public function extraMenuRows( $rows )
{
$rows['convertForumsBoards'] = $rows['convertForumsForums'];
$rows['convertForumsBoards']['step_method'] = 'convertForumsBoards';
$rows['convertForumsBoards']['source_rows'] = $this->countRows( static::canConvert()['convertForumsBoards']['table'], static::canConvert()['convertForumsBoards']['where'] );
return $rows;
}
/**
* Count Source Rows for a specific step
*
@@ -366,7 +381,6 @@ class _Smf extends \IPS\convert\Software
'follow_notify_freq' => 'immediate',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -83,6 +83,47 @@ class _UBBthreads extends \IPS\convert\Software
);
}
/**
* Allows software to add additional menu row options
*
* @param array $rows Existing rows
* @return array
*/
public function extraMenuRows( $rows )
{
$count = $this->countRows( static::canConvert()['convertForumsTopicsRatings']['table'], static::canConvert()['convertForumsTopicsRatings']['where'] );
if( $count )
{
$rows['convertForumsTopicsRatings'] = array(
'step_method' => 'convertForumsTopicsRatings',
'step_title' => 'convert_ratings',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'core_ratings' )->first(),
'source_rows' => $count,
'per_cycle' => 1500,
'dependencies' => array( 'convertForumsTopics' ),
'link_type' => 'topic_ratings',
);
}
$count = $this->countRows( static::canConvert()['convertForumsTopicsFollowers']['table'], static::canConvert()['convertForumsTopicsFollowers']['where'] );
if( $count )
{
$rows['convertForumsTopicsFollowers'] = array(
'step_method' => 'convertForumsTopicsFollowers',
'step_title' => 'convert_follows',
'ips_rows' => \IPS\Db::i()->select( 'COUNT(*)', 'core_follow' )->first(),
'source_rows' => $count,
'per_cycle' => 1000,
'dependencies' => array( 'convertForumsTopics' ),
'link_type' => 'core_follows',
);
}
return $rows;
}
/**
* Count Source Rows for a specific step
*
@@ -247,7 +288,7 @@ class _UBBthreads extends \IPS\convert\Software
{
$libraryClass = $this->getLibrary();
foreach ( $this->fetch( 'WATCH_LISTS', 'WATCH_ID', array( 'WATCH_TYPE=?', 'f' ), 'USER_ID ASC, WATCH_ID ASC' ) as $row )
foreach ( $this->fetch( 'WATCH_LISTS', 'WATCH_ID', array( 'WATCH_TYPE=?', 'f' ) ) as $row )
{
$libraryClass->convertFollow( array(
'follow_app' => 'forums',
@@ -390,7 +431,7 @@ class _UBBthreads extends \IPS\convert\Software
{
$libraryClass = $this->getLibrary();
foreach ( $this->fetch( 'WATCH_LISTS', 'WATCH_ID', array( 'WATCH_TYPE=?', 't' ), 'USER_ID ASC, WATCH_ID ASC' ) as $row )
foreach ( $this->fetch( 'WATCH_LISTS', 'WATCH_ID', array( 'WATCH_TYPE=?', 't' ) ) as $row )
{
$libraryClass->convertFollow( array(
'follow_app' => 'forums',
@@ -489,7 +530,25 @@ class _UBBthreads extends \IPS\convert\Software
return NULL;
}
if( preg_match( '#/ubbthreads.php/topics/([0-9]+)($|/)#i', $url->data[ \IPS\Http\Url::COMPONENT_PATH ], $matches ) )
//ubbthreads.php?ubb=showflat&Number=?
if( isset( \IPS\Request::i()->Number ) AND isset( \IPS\Request::i()->ubb ) )
{
try
{
$data = (string) $this->app->getLink( (int) \IPS\Request::i()->Number, array( 'posts', 'forums_posts' ) );
$post = \IPS\forums\Topic\Post::load( $data );
if( $post->item()->canView() )
{
return $post->url();
}
}
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
elseif( preg_match( '#/ubbthreads.php/topics/([0-9]+)($|/)#i', $url->data[ \IPS\Http\Url::COMPONENT_PATH ], $matches ) )
{
try
{
@@ -510,6 +569,18 @@ class _UBBthreads extends \IPS\convert\Software
}
catch( \Exception $e )
{
try
{
$data = (string) $this->app->getLink( (int) $matches[1], array( 'posts', 'forums_posts' ) );
$post = \IPS\forums\Topic\Post::load( $data );
if( $post->item()->canView() )
{
return $post->url();
}
}
catch ( \OutOfRangeException $e ) { }
return NULL;
}
}
@@ -530,6 +601,23 @@ class _UBBthreads extends \IPS\convert\Software
return NULL;
}
}
elseif( preg_match( '#ubbthreads.php/ubb/showflat/Number/([0-9]+)($|/)#i', $url->data[ \IPS\Http\Url::COMPONENT_PATH ], $matches ) )
{
try
{
$data = (string) $this->app->getLink( (int) $matches[1], array( 'posts', 'forums_posts' ) );
$post = \IPS\forums\Topic\Post::load( $data );
if( $post->item()->canView() )
{
return $post->url();
}
}
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
return NULL;
}
@@ -20,6 +20,31 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
class _Vanilla extends \IPS\convert\Software
{
/**
* @brief Store the result of the check for reaction support
*/
protected static $_supportsReactions = FALSE;
/**
* Constructor
*
* @param \IPS\convert\App The application to reference for database and other information.
* @param bool Establish a DB connection
* @return void
* @throws \InvalidArgumentException
*/
public function __construct( \IPS\convert\App $app, $needDB=TRUE )
{
$return = parent::__construct( $app, $needDB );
/* Check for reaction support - This is a Vanilla2 addon, so it may not be installed */
if ( $needDB )
{
static::$_supportsReactions = $this->db->checkForTable( 'Action' );
}
return $return;
}
/**
* Software Name
*
@@ -28,7 +53,7 @@ class _Vanilla extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "Vanilla 2";
return "Vanilla 2 (2.2.x/2.3.x)";
}
/**
@@ -94,6 +119,20 @@ class _Vanilla extends \IPS\convert\Software
);
}
/**
* Allows software to add additional menu row options
*
* @param array $rows Existing rows
* @return array
*/
public function extraMenuRows( $rows )
{
$rows['convertForumsPosts2'] = $rows['convertForumsPosts'];
$rows['convertForumsPosts2']['step_method'] = 'convertForumsPosts2';
return $rows;
}
/**
* Count Source Rows for a specific step
*
@@ -170,30 +209,33 @@ class _Vanilla extends \IPS\convert\Software
);
break;
case 'convertForumsPosts':
/* Get our reactions to let the admin map them */
$options = array();
$descriptions = array();
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_reactions' ), 'IPS\Content\Reaction' ) AS $reaction )
/* Get our reactions to let the admin map them - this is a Vanilla2 addon so it may not be installed */
if( static::$_supportsReactions )
{
$options[ $reaction->id ] = $reaction->_icon->url;
$descriptions[ $reaction->id ] = \IPS\Member::loggedIn()->language()->addToStack('reaction_title_' . $reaction->id ) . '<br>' . $reaction->_description;
}
$options = array();
$descriptions = array();
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_reactions' ), 'IPS\Content\Reaction' ) AS $reaction )
{
$options[ $reaction->id ] = $reaction->_icon->url;
$descriptions[ $reaction->id ] = \IPS\Member::loggedIn()->language()->addToStack('reaction_title_' . $reaction->id ) . '<br>' . $reaction->_description;
}
$return['convertForumsPosts'] = array();
$return['convertForumsPosts'] = array();
foreach( $this->db->select( '*', 'action' ) as $reaction )
{
\IPS\Member::loggedIn()->language()->words['reaction_' . $reaction['ActionID'] ] = $reaction['Name'];
\IPS\Member::loggedIn()->language()->words['reaction_' . $reaction['ActionID'] . '_desc' ] = \IPS\Member::loggedIn()->language()->addToStack('reaction_convert_help');
foreach( $this->db->select( '*', 'Action' ) as $reaction )
{
\IPS\Member::loggedIn()->language()->words['reaction_' . $reaction['ActionID'] ] = $reaction['Name'];
\IPS\Member::loggedIn()->language()->words['reaction_' . $reaction['ActionID'] . '_desc' ] = \IPS\Member::loggedIn()->language()->addToStack('reaction_convert_help');
$return['convertForumsPosts']['reaction_' . $reaction['ActionID'] ] = array(
'field_class' => 'IPS\\Helpers\\Form\\Radio',
'field_default' => NULL,
'field_required' => TRUE,
'field_extra' => array( 'parse' => 'image', 'options' => $options, 'descriptions' => $descriptions ),
'field_hint' => NULL,
'field_validation' => NULL,
);
$return['convertForumsPosts']['reaction_' . $reaction['ActionID'] ] = array(
'field_class' => 'IPS\\Helpers\\Form\\Radio',
'field_default' => NULL,
'field_required' => TRUE,
'field_extra' => array( 'parse' => 'image', 'options' => $options, 'descriptions' => $descriptions ),
'field_hint' => NULL,
'field_validation' => NULL,
);
}
}
break;
case 'convertAttachments':
@@ -246,16 +288,21 @@ class _Vanilla extends \IPS\convert\Software
public static function fixPostData( $post )
{
/**
* Vanilla actually only supports plaintext posts out of the box, but there is a popular quotes plugin I think
* we should accommodate for.
* Vanilla has a quotes plugin that seems to have changed formats quite often. - We'll try to match as many formats as we can
*/
$quoted = preg_replace_callback( '#<blockquote class="Quote" rel="([^"]+)">(.+)</blockquote>#', function( $match )
{
$match[1] = str_replace( '"', '', $match[1] );
return "[quote name=\"{$match[1]}\"]{$match[2]}[/quote]";
}, $post );
$post = preg_replace( '/(?:<blockquote\s+(?:class=\"(?:User)?Quote\")?(?:\s+rel=\"(?:[^\"]+)\")?>)(?:\s+)?<div class=\"QuoteAuthor\">([^\"]+)<\/div>(?:\s+)?<div class=\"QuoteText\">(?:<p>)?(.*?)(?:<\/p>)?<\/div>(?:\s+)?<\/blockquote>/i',
'[quote name="$1"]$2[/quote]' ,
$post );
return $quoted ?: $post;
$post = preg_replace( '/(?:<blockquote\s+(?:class=\"Quote (?:User)?Quote\")?(?:\s+rel=\"(?:[^\"]+)\")?>)(?:\s+)?<div class=\"QuoteText\">(?:<p>)?(.*?)(?:<\/p>)?<\/div>(?:\s+)?<\/blockquote>/i',
'[quote]$1[/quote]' ,
$post );
$post = preg_replace( '/<blockquote\s+rel=\"([^\"]+)\"?>(.*?)<\/blockquote>/i',
'[quote name="$1"]$2[/quote]' ,
$post );
return $post;
}
/**
@@ -374,16 +421,24 @@ class _Vanilla extends \IPS\convert\Software
foreach( $discussions AS $row )
{
$row['DateLastComment'] = 0;
$row['LastCommentUserID'] = 0;
$row['LastCommentUserName'] = '';
/* If last post info is empty, fetch it */
if( $row['DateLastComment'] === NULL )
{
$data = $this->db->select( 'Comment.InsertUserID, Comment.DateInserted, User.Name', 'Comment', array( 'DiscussionID=?', $row['DiscussionID'] ), 'CommentID DESC', array( 0, 1 ) )
->join( 'User', 'Comment.InsertUserID=User.UserID' )
->first();
try
{
$data = $this->db->select( 'Comment.InsertUserID, Comment.DateInserted, User.Name', 'Comment', array( 'DiscussionID=?', $row['DiscussionID'] ), 'CommentID DESC', array( 0, 1 ) )
->join( 'User', 'Comment.InsertUserID=User.UserID' )
->first();
$row['DateLastComment'] = $data['DateInserted'];
$row['LastCommentUserID'] = $data['InsertUserId'];
$row['LastCommentUserName'] = $data['Name'];
$row['DateLastComment'] = $data['DateInserted'];
$row['LastCommentUserID'] = $data['InsertUserId'];
$row['LastCommentUserName'] = $data['Name'];
}
catch( \UnderflowException $e ) {}
}
$info = array(
@@ -472,21 +527,24 @@ class _Vanilla extends \IPS\convert\Software
$libraryClass->convertForumsPost( $info );
$libraryClass->setLastKeyValue( $row['DiscussionID'] );
/* Reputation */
foreach( $this->db->select( '*', 'reaction', array( "ParentType=? AND ParentID=?", 'comment', $row['DiscussionID'] ) ) AS $rep )
/* Reputation - Reactions are only supported if the YAGA addon was used. */
if( static::$_supportsReactions )
{
$reaction = $this->app->_session['more_info']['convertForumsPosts']['reaction_' . $rep['ActionID'] ];
foreach( $this->db->select( '*', 'Reaction', array( "ParentType=? AND ParentID=?", 'comment', $row['DiscussionID'] ) ) AS $rep )
{
$reaction = $this->app->_session['more_info']['convertForumsPosts']['reaction_' . $rep['ActionID'] ];
$libraryClass->convertReputation( array(
'id' => $rep['ReactionID'],
'app' => 'forums',
'type' => 'pid',
'type_id' => 'fp-' . $row['DiscussionID'],
'member_id' => $rep['InsertUserID'],
'member_received' => $rep['ParentAuthorID'],
'rep_date' => VanillaCore::mysqlToDateTime( $row['DateInserted'] ),
'reaction' => $reaction
) );
$libraryClass->convertReputation( array(
'id' => $rep['ReactionID'],
'app' => 'forums',
'type' => 'pid',
'type_id' => 'fp-' . $row['DiscussionID'],
'member_id' => $rep['InsertUserID'],
'member_received' => $rep['ParentAuthorID'],
'rep_date' => VanillaCore::mysqlToDateTime( $row['DateInserted'] ),
'reaction' => $reaction
) );
}
}
}
}
@@ -170,7 +170,7 @@ class _Vbulletin extends \IPS\convert\Software
/* Content Rebuilds */
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\forums\Forum', 'count' => 0 ), 5, array( 'class' ) );
\IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'forums_posts', 'class' => 'IPS\forums\Topic\Post' ), 2, array( 'app', 'link', 'class' ) );
\IPS\Task::queue( 'core', 'RebuildItemCounts', array( 'class' => 'IPS\forums\Topic' ), 3, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildItemCounts', array( 'class' => 'IPS\forums\Topic' ), 2, array( 'class' ) ); // This must run before the CMS item count task runs.
\IPS\Task::queue( 'convert', 'RebuildFirstPostIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
\IPS\Task::queue( 'convert', 'DeleteEmptyTopics', array( 'app' => $this->app->app_id ), 4, array( 'app' ) );
@@ -365,7 +365,6 @@ class _Vbulletin extends \IPS\convert\Software
'follow_notify_freq' => $frequency,
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -521,7 +520,6 @@ class _Vbulletin extends \IPS\convert\Software
'follow_notify_freq' => $frequency,
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -660,7 +658,7 @@ class _Vbulletin extends \IPS\convert\Software
/* Warnings */
foreach( $this->db->select( '*', 'infraction', array( "postid=?", $post['postid'] ) ) AS $warn )
{
$libraryClass->convertWarnLog( array(
$warnId = $libraryClass->convertWarnLog( array(
'wl_id' => $warn['infractionid'],
'wl_member' => $warn['userid'],
'wl_moderator' => $warn['whoadded'],
@@ -669,6 +667,17 @@ class _Vbulletin extends \IPS\convert\Software
'wl_note_member' => $warn['note'],
'wl_note_mods' => $warn['customreason'],
) );
/* Add a member history record for this member */
$libraryClass->convertMemberHistory( array(
'log_id' => 'w' . $warn['infractionid'],
'log_member' => $warn['userid'],
'log_by' => $warn['whoadded'],
'log_type' => 'warning',
'log_data' => array( 'wid' => $warnId ),
'log_date' => $warn['dateline']
)
);
}
/* If we have a latest edit, then update the main post - this should really be in the library, as the converters should not be altering data */
@@ -715,6 +724,7 @@ class _Vbulletin extends \IPS\convert\Software
{
/* If the topic is missing, there isn't much we can do. */
$libraryClass->setLastKeyValue( $attachment['attachmentid'] );
$this->app->log( 'vb_attach_missing_topic', __METHOD__, \IPS\convert\App::LOG_WARNING, $attachment['attachmentid'] );
continue;
}
@@ -728,6 +738,7 @@ class _Vbulletin extends \IPS\convert\Software
{
/* If the filedata row is missing, there isn't much we can do. */
$libraryClass->setLastKeyValue( $attachment['attachmentid'] );
$this->app->log( 'vb_attach_missing_filedata', __METHOD__, \IPS\convert\App::LOG_WARNING, $attachment['attachmentid'] );
continue;
}
}
@@ -965,21 +976,29 @@ class _Vbulletin extends \IPS\convert\Software
\IPS\Request::i()->t = $matches[1];
\IPS\Request::i()->page = $matches[2];
}
/* Normal Topics */
elseif( preg_match( "/\/(\d+)\-.+?\.html/", $url->data[ \IPS\Http\Url::COMPONENT_PATH ], $matches ) )
{
$url = $url->setPath( preg_replace( "/\/(\d+)\-.+?\.html/", "/showthread.php", $url->data[ \IPS\Http\Url::COMPONENT_PATH ] ) )->setQueryString( 't', $matches[1] );
\IPS\Request::i()->t = $matches[1];
}
/* Post Links */
elseif( preg_match( "/\/(\d+)\-.+?\-post(\d+)\.html/", $url->data[ \IPS\Http\Url::COMPONENT_PATH ], $matches ) )
{
$url = $url->setPath( preg_replace( "/\/(\d+)\-.+?\-post(\d+)\.html/", "/showpost.php", $url->data[ \IPS\Http\Url::COMPONENT_PATH ] ) )->setQueryString( 'p', $matches[2] );
\IPS\Request::i()->p = $matches[2];
}
}
/* Forum URLs are the same across VB 3.8 and VB 4 */
if( mb_strpos( $url->data[ \IPS\Http\Url::COMPONENT_PATH ], 'forumdisplay.php' ) !== FALSE )
{
/* Forum URLs can be in one of 3 formats really...
* /forumdisplay.php/1-name
* /forums/1-name
* /forumdisplay.php?f=1
*/
/* Forum URLs can be in one of 4 formats really...
* /forumdisplay.php/1-name
* /forums/1-name
* /forumdisplay.php?f=1
* /forumdisplay.php?1-name
*/
$path = $url->data[ \IPS\Http\Url::COMPONENT_PATH ];
if( mb_strpos( $path, 'forumdisplay.php' ) !== FALSE )
{
@@ -987,6 +1006,10 @@ class _Vbulletin extends \IPS\convert\Software
{
$oldId = \IPS\Request::i()->f;
}
elseif( preg_match( '#^(\d+)-[^/]+#i', $url->data[ \IPS\Http\Url::COMPONENT_QUERY ], $matches ) )
{
$oldId = $matches[1];
}
else
{
$queryStringPieces = explode( '-', mb_substr( $path, mb_strpos( $path, 'forumdisplay.php/' ) + mb_strlen( 'forumdisplay.php/' ) ) );
@@ -1064,11 +1087,12 @@ class _Vbulletin extends \IPS\convert\Software
/* And then topic URLs, same idea */
elseif( mb_strpos( $url->data[ \IPS\Http\Url::COMPONENT_PATH ], 'showthread.php' ) !== FALSE )
{
/* Topic URLs can be in one of 3 formats really...
* /showthread.php/1-name
* /threads/1-name
* /showthread.php?t=1
*/
/* Topic URLs can be in one of 4 formats really...
* /showthread.php/1-name
* /threads/1-name
* /showthread.php?t=1
* /showthread.php?1-name
*/
$path = $url->data[ \IPS\Http\Url::COMPONENT_PATH ];
if( mb_strpos( $path, 'showthread.php' ) !== FALSE )
{
@@ -1076,6 +1100,10 @@ class _Vbulletin extends \IPS\convert\Software
{
$oldId = \IPS\Request::i()->t;
}
elseif( preg_match( '#^(\d+)-[^/]+#i', $url->data[ \IPS\Http\Url::COMPONENT_QUERY ], $matches ) )
{
$oldId = $matches[1];
}
else
{
$queryStringPieces = explode( '-', mb_substr( $path, mb_strpos( $path, 'showthread.php/' ) + mb_strlen( 'showthread.php/' ) ) );
@@ -1156,7 +1184,7 @@ class _Vbulletin extends \IPS\convert\Software
}
catch( \OutOfRangeException $e )
{
$data = $this->getLink( (string) $parameters[1], array( 'topics', 'forums_topics', FALSE, TRUE ) );
$data = $this->getLink( (string) $parameters[1], array( 'topics', 'forums_topics' ), FALSE, TRUE );
}
$item = \IPS\forums\Topic::load( $data );
@@ -91,7 +91,7 @@ class _Vbulletin5 extends \IPS\convert\Software
break;
case 'post':
return parent::countRows( 'node', array( \IPS\Db::i()->in( 'contenttypeid', array( $this->fetchType( 'Text' ), $this->fetchType( 'Gallery' ), $this->fetchType( 'Video' ), $this->fetchType( 'Link' ) ) ) ) );
return parent::countRows( 'node', array( \IPS\Db::i()->in( 'contenttypeid', array( $this->fetchType( 'Text' ), $this->fetchType( 'Gallery' ), $this->fetchType( 'Video' ), $this->fetchType( 'Link' ), $this->fetchType( 'Poll' ) ) ) ) );
break;
case 'attachment':
@@ -235,8 +235,19 @@ class _Vbulletin5 extends \IPS\convert\Software
$libraryClass::setKey( 'node.nodeid' );
$clubs = $this->fetchClubs();
foreach( $this->fetch( 'node', 'nodeid', array( "node.nodeid<>2 AND ( ( node.parentid=? AND node.contenttypeid=? ) OR " . $this->db->in( 'nodeid', array_keys( $clubs ) ) . " )", $this->fetchType( 'Thread' ), $this->fetchType( 'Channel' ) ) ) AS $forum )
foreach( $this->fetch( 'node', 'nodeid', array( "node.nodeid<>? AND ( ( closure.parent=? AND node.contenttypeid=? ) OR " . $this->db->in( 'nodeid', array_keys( $clubs ) ) . " )", 2, 2, $this->fetchType( 'Channel' ) ) )->join( 'closure', "closure.child=node.nodeid" ) AS $forum )
{
/* This may be a club forum that already exists */
try
{
$this->app->getLink( $forum['nodeid'], 'forums_forums' );
$libraryClass->setLastKeyValue( $forum['nodeid'] );
$this->app->log( 'vb5_skipped_forum', __METHOD__, \IPS\convert\App::LOG_WARNING );
continue;
}
catch( \OutOfRangeException $e ){}
$self = $this;
$checkpermission = function( $name, $perm ) use ( $forum, $self )
{
@@ -441,16 +452,33 @@ class _Vbulletin5 extends \IPS\convert\Software
{
/* Actually, we don't - end of the line */
$libraryClass->setLastKeyValue( $post['nodeid'] );
$this->app->log( 'vb5_post_missing_parent_post', __METHOD__, \IPS\convert\App::LOG_WARNING, $post['nodeid'] );
continue;
}
catch( \UnderflowException $e )
{
/* Loading the parent failed - move on */
$libraryClass->setLastKeyValue( $post['nodeid'] );
$this->app->log( 'vb5_post_missing_parent_post_2', __METHOD__, \IPS\convert\App::LOG_WARNING, $post['nodeid'] );
continue;
}
}
}
/* We may need to insert some data in core_soft_delete_log later if the post was hidden by a moderator. */
$softDeleted = FALSE;
$queued = 0;
if ( !$post['approved'] )
{
/* If the post is unapproved, then that's a direct translation and the post should then be marked as hidden pending approval. */
$queued = 1;
}
else if ( $post['deleteuserid'] )
{
$queued = -1;
$softDeleted = TRUE;
}
$info = array(
'pid' => $post['nodeid'],
'topic_id' => $post['parentid'],
@@ -459,12 +487,25 @@ class _Vbulletin5 extends \IPS\convert\Software
'author_name' => $post['authorname'],
'ip_address' => $post['ipaddress'],
'post_date' => $post['publishdate'],
'queued' => $post['approved'] ? 0 : -1,
'queued' => $queued,
'post_htmlstate' => ( in_array( $post['htmlstate'], array( 'on', 'on_nl2br' ) ) ) ? 1 : 0,
);
$post_id = $libraryClass->convertForumsPost( $info );
/* Need to insert into the SDL? */
if ( $softDeleted )
{
\IPS\Db::i()->insert( 'core_soft_delete_log', array(
'sdl_obj_id' => $post_id,
'sdl_obj_key' => 'post',
'sdl_obj_member_id' => $this->app->getLink( $post['deleteuserid'], 'core_members', TRUE ),
'sdl_obj_date' => $post['unpublishdate'] ?: time(),
'sdl_obj_reason' => $post['deletereason'] ?: NULL,
'sdl_locked' => 0
) );
}
/* Reputation */
foreach( $this->db->select( '*', 'reputation', array( "nodeid=?", $post['nodeid'] ) ) AS $rep )
{
@@ -511,7 +552,7 @@ class _Vbulletin5 extends \IPS\convert\Software
/* Warnings */
foreach( $this->db->select( '*', 'infraction', array( "nodeid=?", $post['nodeid'] ) ) AS $warn )
{
$libraryClass->convertWarnLog( array(
$warnId = $libraryClass->convertWarnLog( array(
'wl_id' => $warn['infractionid'],
'wl_member' => $warn['nodeid'],
'wl_moderator' => $warn['whoadded'],
@@ -520,6 +561,17 @@ class _Vbulletin5 extends \IPS\convert\Software
'wl_note_member' => $warn['note'],
'wl_note_mods' => $warn['customreason'],
) );
/* Add a member history record for this member */
$libraryClass->convertMemberHistory( array(
'log_id' => 'w' . $warn['infractionid'],
'log_member' => $warn['userid'],
'log_by' => $warn['whoadded'],
'log_type' => 'warning',
'log_data' => array( 'wid' => $warnId ),
'log_date' => $warn['dateline']
)
);
}
/* If we have a latest edit, then update the main post - this should really be in the library, as the converters should not be altering data */
@@ -566,12 +618,16 @@ class _Vbulletin5 extends \IPS\convert\Software
}
catch( \OutOfRangeException $e )
{
/* No valid converter link for the parent post */
$libraryClass->setLastKeyValue( $attachment['nodeid'] );
$this->app->log( 'vb5_attach_missing_post_link', __METHOD__, \IPS\convert\App::LOG_WARNING, $attachment['nodeid'] );
continue;
}
catch( \UnderflowException $e )
{
/* Parent post does not exist in vB database */
$libraryClass->setLastKeyValue( $attachment['nodeid'] );
$this->app->log( 'vb5_attach_missing_post', __METHOD__, \IPS\convert\App::LOG_WARNING, $attachment['nodeid'] );
continue;
}
}
@@ -644,6 +700,7 @@ class _Vbulletin5 extends \IPS\convert\Software
else
{
$oldAttachId = NULL;
$update = FALSE;
/* Try old attach ID */
try
@@ -655,8 +712,17 @@ class _Vbulletin5 extends \IPS\convert\Software
if ( $oldAttachId !== NULL and preg_match( "/\[ATTACH=(.+?)?\]" . $oldAttachId . "\[\/ATTACH\]/i", $post ) )
{
$update = TRUE;
$post = preg_replace( "/\[ATTACH(.+?)?\]" . $oldAttachId . "\[\/ATTACH\]/i", '[attachment=' . $attach_id . ':name]', $post );
}
elseif( preg_match( "/\[ATTACH=JSON](.+?)\"data-attachmentid\":\"?{$attachment['nodeid']}\"?(.+?)\[\/ATTACH\]/i", $post ) )
{
$update = TRUE;
$post = preg_replace( "/\[ATTACH=JSON](.+?)\"data-attachmentid\":\"?{$attachment['nodeid']}\"?(.+?)\[\/ATTACH\]/i", '[attachment=' . $attach_id . ':name]', $post );
}
if( $update )
{
\IPS\Db::i()->update( 'forums_posts', array( 'post' => $post ), array( "pid=?", $pid ) );
}
}
@@ -20,11 +20,53 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
class _Xenforo extends \IPS\convert\Software
{
/**
* @brief The similarities between XF1 and XF2 are close enough that we can use the same converter
*/
public static $isLegacy = NULL;
/**
* @brief Flag to indicate the post data has been fixed during conversion, and we only need to use Legacy Parser
*/
public static $contentFixed = TRUE;
/**
* Constructor
*
* @param \IPS\convert\App The application to reference for database and other information.
* @param bool Establish a DB connection
* @return void
* @throws \InvalidArgumentException
*/
public function __construct( \IPS\convert\App $app, $needDB=TRUE )
{
$return = parent::__construct( $app, $needDB );
if ( $needDB )
{
try
{
/* Is this XF1 or XF2 */
if ( static::$isLegacy === NULL )
{
$version = $this->db->select( 'MAX(version_id)', 'xf_template', array( "addon_id=?", 'XF' ) )->first();
if ( $version < 2000010 )
{
static::$isLegacy = TRUE;
}
else
{
static::$isLegacy = FALSE;
}
}
}
catch( \Exception $e ) {} # If we can't query, we won't be able to do anything anyway
}
return $return;
}
/**
* Software Name
*
@@ -33,7 +75,7 @@ class _Xenforo extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "XenForo";
return "XenForo (1.5.x/2.0.x)";
}
/**
@@ -187,17 +229,19 @@ class _Xenforo extends \IPS\convert\Software
{
return \IPS\convert\Software\Core\Xenforo::fixPostData( $post );
}
/**
* Helper to fetch a xenforo phrase
*
* @param string $title Phrase
* @param string $xfOneTitle XF1 Phrase title
* @param string $xfTwoTitle XF2 Phrase Title
* @return string|null
*/
protected function getPhrase( $title )
protected function getPhrase( $xfOneTitle, $xfTwoTitle )
{
try
{
$title = ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) ? $xfTwoTitle : $xfOneTitle;
return $this->db->select( 'phrase_text', 'xf_phrase', array( "title=?", $title ) )->first();
}
catch( \UnderflowException $e )
@@ -273,7 +317,6 @@ class _Xenforo extends \IPS\convert\Software
'follow_notify_freq' => ( $follow['send_alert'] OR $follow['send_email'] ) ? 'immediate' : 'none',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -418,14 +461,13 @@ class _Xenforo extends \IPS\convert\Software
'follow_notify_freq' => ( $follow['email_subscribe'] ) ? 'immediate' : 'none',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
/* Tag Prefix */
if ( $row['prefix_id'] )
{
$prefix = $this->getPhrase( "thread_prefix_{$row['prefix_id']}" );
$prefix = $this->getPhrase( "thread_prefix_{$row['prefix_id']}", "thread_prefix.{$row['prefix_id']}" );
if ( !is_null( $prefix ) )
{
@@ -519,12 +561,23 @@ class _Xenforo extends \IPS\convert\Software
$queued = 2;
break;
}
$editName = NULL;
try
{
if( $row['last_edit_user_id'] )
{
$editName = $this->db->select( 'username', 'xf_user', array( 'user_id=?', $row['last_edit_user_id'] ) )->first();
}
}
catch( \UnderflowException $e ) {}
$info = array(
'pid' => $row['post_id'],
'topic_id' => $row['thread_id'],
'post' => static::fixPostData( $row['message'] ),
'edit_time' => $row['last_edit_date'],
'edit_name' => $editName,
'author_id' => $row['user_id'],
'author_name' => $row['username'],
'ip_address' => $ip,
@@ -555,7 +608,7 @@ class _Xenforo extends \IPS\convert\Software
/* Warnings */
foreach( $this->db->select( '*', 'xf_warning', array( "content_type=? AND content_id=?", 'post', $row['post_id'] ) ) AS $warn )
{
$libraryClass->convertWarnLog( array(
$warnId = $libraryClass->convertWarnLog( array(
'wl_id' => $warn['warning_id'],
'wl_member' => $warn['user_id'],
'wl_moderator' => $warn['warning_user_id'],
@@ -564,6 +617,17 @@ class _Xenforo extends \IPS\convert\Software
'wl_note_member' => $warn['title'],
'wl_note_mods' => $warn['notes'],
) );
/* Add a member history record for this member */
$libraryClass->convertMemberHistory( array(
'log_id' => 'w' . $warn['warning_id'],
'log_member' => $warn['user_id'],
'log_by' => $warn['warning_user_id'],
'log_type' => 'warning',
'log_data' => array( 'wid' => $warnId ),
'log_date' => $warn['warning_date']
)
);
}
/* Edit History */
@@ -28,7 +28,7 @@ class _Coppermine extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "Coppermine";
return "Coppermine (phpBB 3.1.x/3.2.x)";
}
/**
@@ -144,6 +144,7 @@ class _Coppermine extends \IPS\convert\Software
{
/* Content Rebuilds */
\IPS\Task::queue( 'convert', 'RebuildGalleryImages', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
\IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'gallery_comments', 'class' => 'IPS\gallery\Image\Comment' ), 2, array( 'app', 'link', 'class' ) );
\IPS\Task::queue( 'core', 'RebuildItemCounts', array( 'class' => 'IPS\gallery\Image' ), 3, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\gallery\Album', 'count' => 0 ), 4, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\gallery\Category', 'count' => 0 ), 5, array( 'class' ) );
@@ -111,6 +111,7 @@ class _Photoplog extends \IPS\convert\Software
{
/* Content Rebuilds */
\IPS\Task::queue( 'convert', 'RebuildGalleryImages', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
\IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'gallery_comments', 'class' => 'IPS\gallery\Image\Comment' ), 2, array( 'app', 'link', 'class' ) );
\IPS\Task::queue( 'core', 'RebuildItemCounts', array( 'class' => 'IPS\gallery\Image' ), 3, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\gallery\Album', 'count' => 0 ), 4, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\gallery\Category', 'count' => 0 ), 5, array( 'class' ) );
@@ -29,7 +29,7 @@ class _Photopost extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "Photopost";
return "Photopost (8.x)";
}
/**
@@ -43,6 +43,26 @@ class _Photopost extends \IPS\convert\Software
return "photopost";
}
/**
* Requires Parent
*
* @return boolean
*/
public static function requiresParent()
{
return TRUE;
}
/**
* Possible Parent Conversions
*
* @return array
*/
public static function parents()
{
return array( 'core' => array( 'photopost' ) );
}
/**
* Content we can convert from this software.
*
@@ -50,7 +70,226 @@ class _Photopost extends \IPS\convert\Software
*/
public static function canConvert()
{
return NULL;
return array(
'convertGalleryCategories' => array(
'table' => 'categories',
'where' => array( 'cattype=?', 'c' )
),
'convertGalleryAlbums' => array(
'table' => 'categories',
'where' => array( 'cattype=?', 'a' )
),
'convertGalleryImages' => array(
'table' => 'photos',
'where' => NULL,
),
'convertGalleryComments' => array(
'table' => 'comments',
'where' => NULL,
)
);
}
/**
* List of conversion methods that require additional information
*
* @return array
*/
public static function checkConf()
{
return array(
'convertGalleryAlbums',
'convertGalleryImages'
);
}
/**
* Get More Information
*
* @param string $method Conversion method
* @return array
*/
public function getMoreInfo( $method )
{
$return = array();
switch( $method )
{
case 'convertGalleryAlbums':
$options = array();
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'gallery_categories' ), 'IPS\gallery\Category' ) AS $category )
{
$options[$category->_id] = $category->_title;
}
$return['convertGalleryAlbums']['members_gallery_category'] = array(
'field_class' => 'IPS\\Helpers\\Form\\Select',
'field_default' => NULL,
'field_required' => FALSE,
'field_extra' => array(
'options' => $options
),
'field_hint' => NULL,
);
break;
case 'convertGalleryImages':
$return['convertGalleryImages']['file_location'] = array(
'field_class' => 'IPS\\Helpers\\Form\\Text',
'field_default' => NULL,
'field_required' => TRUE,
'field_extra' => array(),
'field_hint' => \IPS\Member::loggedIn()->language()->addToStack('convert_photopost_image_path'),
'field_validation' => function( $value ) { if ( !@is_dir( $value ) ) { throw new \DomainException( 'path_invalid' ); } },
);
break;
}
return ( isset( $return[ $method ] ) ) ? $return[ $method ] : array();
}
/**
* Finish - Adds everything it needs to the queues and clears data store
*
* @return array Messages to display
*/
public function finish()
{
/* Content Rebuilds */
\IPS\Task::queue( 'convert', 'RebuildGalleryImages', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
\IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'gallery_comments', 'class' => 'IPS\gallery\Image\Comment' ), 2, array( 'app', 'link', 'class' ) );
\IPS\Task::queue( 'core', 'RebuildItemCounts', array( 'class' => 'IPS\gallery\Image' ), 3, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\gallery\Album', 'count' => 0 ), 4, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\gallery\Category', 'count' => 0 ), 5, array( 'class' ) );
return array( "f_gallery_images_rebuild", "f_gallery_cat_recount", "f_gallery_album_recount", "f_gallery_image_recount" );
}
/**
* Convert gallery categories
*
* @return void
*/
public function convertGalleryCategories()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'id' );
foreach( $this->fetch( 'categories', 'id', array( 'cattype=?', 'c' ) ) AS $row )
{
$libraryClass->convertGalleryCategory( array(
'category_id' => $row['id'],
'category_name' => html_entity_decode( $row['catname'] ),
'category_desc' => $row['description'],
'category_parent_id' => $row['parent'],
'category_count_imgs' => $row['photos'],
'category_position' => $row['catorder']
) );
$libraryClass->setLastKeyValue( $row['id'] );
}
}
/**
* Convert gallery albums
*
* @return void
*/
public function convertGalleryAlbums()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'id' );
foreach( $this->fetch( 'categories', 'id', array( 'cattype=?', 'a' ) ) AS $row )
{
$info = array(
'album_id' => $row['id'],
'album_owner_id' => $row['parent'],
'album_description' => $row['description'],
'album_name' => $row['catname'],
'album_type' => $row['private'] == 'yes' ? 2 : 1,
'album_count_imgs' => $row['photos'],
'album_count_comments' => $row['posts']
);
$category = $this->app->_session['more_info']['convertGalleryAlbums']['members_gallery_category'];
if ( $category == 0 )
{
$category = NULL;
}
$libraryClass->convertGalleryAlbum( $info, NULL, $category );
$libraryClass->setLastKeyValue( $row['id'] );
}
}
/**
* Convert gallery images
*
* @return void
*/
public function convertGalleryImages()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'id' );
foreach( $this->fetch( 'photos', 'id' ) AS $row )
{
$info = array(
'image_id' => $row['id'],
'image_album_id' => $row['cat'],
'image_category_id' => $row['cat'],
'image_member_id' => $row['userid'],
'image_caption' => $row['title'],
'image_description' => $row['description'],
'image_views' => $row['views'],
'image_comments' => $row['numcom'],
'image_ratings_total' => $row['rating'] * $row['votes'],
'image_ratings_count' => $row['votes'],
'image_rating' => $row['rating'],
'image_date' => $row['date'],
'image_last_comment' => $row['lastpost'],
'image_ipaddress' => $row['ipaddress'],
'image_approved' => $row['approved'],
'image_file_name' => $row['bigimage']
);
$directory = $row['storecat'] ?: $row['cat'];
$path = rtrim( $this->app->_session['more_info']['convertGalleryImages']['file_location'], '/' ) . '/' . $directory . '/' . $row['bigimage'];
$libraryClass->convertGalleryImage( $info, $path );
$libraryClass->setLastKeyValue( $row['id'] );
}
}
/**
* Convert gallery comments
*
* @return void
*/
public function convertGalleryComments()
{
$libraryClass = $this->getLibrary();
$libraryClass::setKey( 'id' );
foreach( $this->fetch( 'comments', 'id' ) AS $row )
{
$libraryClass->convertGalleryComment( array(
'comment_id' => $row['id'],
'comment_text' => $row['comment'],
'comment_img_id' => $row['photo'],
'comment_author_id' => $row['userid'],
'comment_author_name' => $row['username'],
'comment_ip_address' => $row['ipaddress'],
'comment_post_date' => $row['date'],
'comment_approved' => $row['approved'],
) );
$libraryClass->setLastKeyValue( $row['id'] );
}
}
/**
@@ -157,6 +157,7 @@ class _Vbulletin extends \IPS\convert\Software
{
/* Content Rebuilds */
\IPS\Task::queue( 'convert', 'RebuildGalleryImages', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
\IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'gallery_comments', 'class' => 'IPS\gallery\Image\Comment' ), 2, array( 'app', 'link', 'class' ) );
\IPS\Task::queue( 'core', 'RebuildItemCounts', array( 'class' => 'IPS\gallery\Image' ), 3, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\gallery\Album', 'count' => 0 ), 4, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\gallery\Category', 'count' => 0 ), 5, array( 'class' ) );
@@ -295,13 +296,24 @@ class _Vbulletin extends \IPS\convert\Software
$filepath = rtrim( $this->app->_session['more_info']['convertGalleryImages']['file_location'], '/' ) . '/' . $filepath . '/' . $image['pictureid'] . '.picture';
}
try
{
$albumAndDate = $this->db->select( 'albumid, dateline', 'albumpicture', array( "pictureid=?", $image['pictureid'] ) )->first();
}
catch( \UnderflowException $e )
{
/* Orphaned */
$libraryClass->setLastKeyValue( $image['pictureid'] );
continue;
}
$info = array(
'image_id' => $image['pictureid'],
'image_album_id' => $this->db->select( 'albumid', 'albumpicture', array( "pictureid=?", $image['pictureid'] ) )->first(),
'image_album_id' => $albumAndDate['albumid'],
'image_member_id' => $image['userid'],
'image_caption' => $image['caption'],
'image_file_name' => $image['caption'] . '.' . $image['extension'],
'image_date' => $this->db->select( 'dateline', 'albumpicture', array( "pictureid=?", $image['pictureid'] ) )->first(),
'image_date' => $albumAndDate['dateline'],
);
$libraryClass->convertGalleryImage( $info, $filepath, $filedata );
@@ -436,7 +448,7 @@ class _Vbulletin extends \IPS\convert\Software
$data = $this->app->getLink( \IPS\Request::i()->albumid, 'gallery_albums' );
$item = \IPS\gallery\Album::load( $data );
if( $item->canView() )
if( $item->can( 'view' ) )
{
return $item->url();
}
@@ -20,6 +20,54 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
class _Xenforo extends \IPS\convert\Software
{
/**
* @brief The similarities between XF1 and XF2 are close enough that we can use the same converter
*/
public static $isLegacy = NULL;
/**
* @brief XF2 Has prefixes on RM tables
*/
public static $tablePrefix = 'xengallery_';
/**
* Constructor
*
* @param \IPS\convert\App The application to reference for database and other information.
* @param bool Establish a DB connection
* @return void
* @throws \InvalidArgumentException
*/
public function __construct( \IPS\convert\App $app, $needDB=TRUE )
{
$return = parent::__construct( $app, $needDB );
if ( $needDB )
{
try
{
/* Is this XF1 or XF2 */
if ( static::$isLegacy === NULL )
{
$version = $this->db->select( 'MAX(version_id)', 'xf_template', array( "addon_id=?", 'XF' ) )->first();
if ( $version < 2000010 )
{
static::$isLegacy = TRUE;
}
else
{
static::$tablePrefix = 'xf_mg_';
static::$isLegacy = FALSE;
}
}
}
catch( \Exception $e ) {} # If we can't query, we won't be able to do anything anyway
}
return $return;
}
/**
* Software Name
*
@@ -28,7 +76,7 @@ class _Xenforo extends \IPS\convert\Software
public static function softwareName()
{
/* Child classes must override this method */
return "XenForo Media Gallery";
return "XenForo Media Gallery (1.5.x/2.0.x)";
}
/**
@@ -51,24 +99,56 @@ class _Xenforo extends \IPS\convert\Software
{
return array(
'convertGalleryCategories' => array(
'table' => 'xengallery_category',
'table' => static::$tablePrefix . 'category',
'where' => NULL
),
'convertGalleryAlbums' => array(
'table' => 'xengallery_album',
'table' => static::$tablePrefix . 'album',
'where' => NULL,
),
'convertGalleryImages' => array(
'table' => 'xengallery_media',
'table' => ( static::$isLegacy ? 'xengallery_media' : 'xf_mg_media_item' ),
'where' => NULL,
),
'convertGalleryComments' => array(
'table' => 'xengallery_comment',
'table' => static::$tablePrefix . 'comment',
'where' => NULL,
)
);
}
/**
* Count Source Rows for a specific step
*
* @param string $table The table containing the rows to count.
* @param array|NULL $where WHERE clause to only count specific rows, or NULL to count all.
* @param bool $recache Skip cache and pull directly (updating cache)
* @return integer
* @throws \IPS\convert\Exception
*/
public function countRows( $table, $where=NULL, $recache=FALSE )
{
switch( $table )
{
case 'xengallery_category':
/* We need to do this for the connection check because all tables have different names in XF2 */
if( $this->db->checkForTable( 'xengallery_category' ) )
{
return $this->db->select( 'COUNT(*)', 'xengallery_category' )->first();
}
elseif( $this->db->checkForTable( 'xf_mg_category' ) )
{
return $this->db->select( 'COUNT(*)', 'xf_mg_category' )->first();
}
break;
default:
return parent::countRows( $table, $where, $recache );
break;
}
}
/**
* Uses Prefix
*
@@ -190,6 +270,7 @@ class _Xenforo extends \IPS\convert\Software
{
/* Content Rebuilds */
\IPS\Task::queue( 'convert', 'RebuildGalleryImages', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
\IPS\Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'gallery_comments', 'class' => 'IPS\gallery\Image\Comment' ), 2, array( 'app', 'link', 'class' ) );
\IPS\Task::queue( 'core', 'RebuildItemCounts', array( 'class' => 'IPS\gallery\Image' ), 3, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\gallery\Album', 'count' => 0 ), 4, array( 'class' ) );
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\gallery\Category', 'count' => 0 ), 5, array( 'class' ) );
@@ -203,13 +284,15 @@ class _Xenforo extends \IPS\convert\Software
/**
* Helper to fetch a xenforo phrase
*
* @param string $title Phrase
* @param string $xfOneTitle XF1 Phrase title
* @param string $xfTwoTitle XF2 Phrase Title
* @return string|null
*/
protected function getPhrase( $title )
protected function getPhrase( $xfOneTitle, $xfTwoTitle )
{
try
{
$title = ( static::$isLegacy === FALSE OR is_null( static::$isLegacy ) ) ? $xfTwoTitle : $xfOneTitle;
return $this->db->select( 'phrase_text', 'xf_phrase', array( "title=?", $title ) )->first();
}
catch( \UnderflowException $e )
@@ -229,19 +312,19 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass::setKey( 'category_id' );
foreach( $this->fetch( 'xengallery_category', 'category_id' ) AS $row )
foreach( $this->fetch( static::$tablePrefix . 'category', 'category_id' ) AS $row )
{
$libraryClass->convertGalleryCategory( array(
'category_id' => $row['category_id'],
'category_name' => $row['category_title'],
'category_desc' => $row['category_description'],
'category_name' => isset( $row['category_title'] ) ? $row['category_title'] : $row['title'],
'category_desc' => isset( $row['category_description'] ) ? $row['category_description'] : $row['description'],
'category_parent_id' => $row['parent_category_id'],
'category_count_imgs' => $row['category_media_count'],
'category_count_imgs' => isset( $row['category_media_count'] ) ? $row['category_media_count'] : $row['category_media_count'],
'category_position' => $row['display_order']
) );
/* Follows */
foreach( $this->db->select( '*', 'xengallery_category_watch', array( "notify_on=? AND category_id=?", 'media', $row['category_id'] ) ) AS $follow )
foreach( $this->db->select( '*', static::$tablePrefix . 'category_watch', array( "notify_on=? AND category_id=?", 'media', $row['category_id'] ) ) AS $follow )
{
$libraryClass->convertFollow( array(
'follow_app' => 'gallery',
@@ -256,7 +339,6 @@ class _Xenforo extends \IPS\convert\Software
'follow_notify_freq' => ( $follow['send_alert'] OR $follow['send_email'] ) ? 'immediate' : 'none',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
}
@@ -273,20 +355,32 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass::setKey( 'album_id' );
foreach( $this->fetch( 'xengallery_album', 'album_id' ) AS $row )
foreach( $this->fetch( static::$tablePrefix . 'album', 'album_id' ) AS $row )
{
try
if( static::$isLegacy )
{
$perm = $this->db->select( '*', 'xengallery_album_permission', array( "album_id=? AND permission=?", $row['album_id'], 'view' ) )->first();
try
{
$perm = $this->db->select( '*', 'xengallery_album_permission', array( "album_id=? AND permission=?", $row['album_id'], 'view' ) )->first();
$perm['share_users'] = \unserialize( $perm['share_users'] );
}
catch( \UnderflowException $e )
{
/* If the permission row is missing for some reason, err on the side of caution and set this album private */
$perm = array(
'album_id' => $row['album_id'],
'permission' => 'view',
'access_type' => 'private',
'share_users' => array()
);
}
}
catch( \UnderflowException $e )
/* XF2 */
else
{
/* If the permission row is missing for some reason, err on the side of caution and set this album private */
$perm = array(
'album_id' => $row['album_id'],
'permission' => 'view',
'access_type' => 'private',
'share_users' => \serialize( array() ),
'access_type' => $row['view_privacy'],
'share_users' => json_decode( $row['view_users'], TRUE )
);
}
@@ -297,7 +391,7 @@ class _Xenforo extends \IPS\convert\Software
{
/* Specific members */
case 'shared':
$users = \unserialize( $perm['share_users'] );
$users = $perm['share_users'];
$socialgroup['members'] = array();
if ( count( $users ) )
@@ -340,14 +434,14 @@ class _Xenforo extends \IPS\convert\Software
$info = array(
'album_id' => $row['album_id'],
'album_owner_id' => $row['album_user_id'],
'album_description' => $row['album_description'],
'album_name' => $row['album_title'],
'album_description' => isset( $row['album_description'] ) ? $row['album_description'] : $row['description'],
'album_name' => isset( $row['album_title'] ) ? $row['album_title'] : $row['title'],
'album_type' => $type,
'album_count_imgs' => $row['album_media_count'],
'album_count_comments' => $row['album_comment_count'],
'album_rating_aggregate' => $row['album_rating_avg'],
'album_rating_count' => $row['album_rating_count'],
'album_rating_total' => $row['album_rating_sum'],
'album_count_imgs' => isset( $row['album_media_count'] ) ? $row['album_media_count'] : $row['media_count'],
'album_count_comments' => isset( $row['album_comment_count'] ) ? $row['album_comment_count'] : $row['comment_count'],
'album_rating_aggregate' => isset( $row['album_rating_avg'] ) ? $row['album_rating_avg'] : $row['rating_avg'],
'album_rating_count' => isset( $row['album_rating_count'] ) ? $row['album_rating_count'] : $row['rating_count'],
'album_rating_total' => isset( $row['album_rating_sum'] ) ? $row['album_rating_sum'] : $row['ating_sum'],
);
$category = $this->app->_session['more_info']['convertGalleryAlbums']['members_gallery_category'];
@@ -359,7 +453,7 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass->convertGalleryAlbum( $info, $socialgroup, $category );
/* Follows */
foreach( $this->db->select( '*', 'xengallery_album_watch', array( "notify_on=? AND album_id=?", 'media', $row['album_id'] ) ) AS $follow )
foreach( $this->db->select( '*', static::$tablePrefix . 'album_watch', array( "notify_on=? AND album_id=?", 'media', $row['album_id'] ) ) AS $follow )
{
$libraryClass->convertFollow( array(
'follow_app' => 'gallery',
@@ -374,7 +468,6 @@ class _Xenforo extends \IPS\convert\Software
'follow_notify_freq' => ( $follow['send_alert'] OR $follow['send_email'] ) ? 'immediate' : 'none',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -393,13 +486,27 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass::setKey( 'media_id' );
foreach( $this->fetch( 'xengallery_media', 'media_id' ) AS $row )
foreach( $this->fetch( static::$isLegacy ? 'xf_gallery_media' : 'xf_mg_media_item', 'media_id' ) AS $row )
{
/* Exif */
$exif = array();
foreach( $this->db->select( 'exif_name, exif_value', 'xengallery_exif', array( "media_id=?", $row['media_id'] ) )->setKeyField( 'exif_name' )->setValueField( 'exif_value' ) AS $key => $value )
if( static::$isLegacy )
{
$exif[$key] = $value;
foreach( $this->db->select( 'exif_name, exif_value', 'xengallery_exif', array( "media_id=?", $row['media_id'] ) )->setKeyField( 'exif_name' )->setValueField( 'exif_value' ) AS $key => $value )
{
$exif[ $key ] = $value;
}
}
/* XF2 */
else
{
foreach( json_decode( $row['exif_data'], TRUE ) AS $key => $value )
{
foreach( $value as $subKey => $subValue )
{
$exif[ $key . '.' . $subKey ] = $subValue;
}
}
}
/* IP Address */
@@ -417,9 +524,10 @@ class _Xenforo extends \IPS\convert\Software
{
/* Normal image upload */
case 'image_upload':
case 'image':
try
{
$file_data = $this->db->select( '*', 'xf_attachment', array( "xf_attachment.content_type=? AND xf_attachment.content_id=?", 'xengallery_media', $row['media_id'] ) )
$file_data = $this->db->select( '*', 'xf_attachment', array( "xf_attachment.content_type=? AND xf_attachment.content_id=?", ( static::$isLegacy ? 'xengallery_media' : 'xfmg_media' ), $row['media_id'] ) )
->join( 'xf_attachment_data', 'xf_attachment.data_id = xf_attachment_data.data_id' )
->first();
}
@@ -433,15 +541,17 @@ class _Xenforo extends \IPS\convert\Software
$group = floor( $file_data['data_id'] / 1000 );
$path = rtrim( $this->app->_session['more_info']['convertGalleryImages']['file_location'], '/' ) . '/' . $group . '/' . $file_data['data_id'] . '-' . $file_data['file_hash'] . '.data';
$file_name = $file_data['filename'];
$description = $row['media_description'];
$description = isset( $row['media_description'] ) ? $row['media_description'] : $row['description'];
break;
/* Video Embed */
case 'video_embed':
case 'embed':
$path = $this->app->_session['more_info']['convertGalleryImages']['media_thumb_location'];
/* We need to figure out what service we came from, and the video ID */
$matches = array();
preg_match( '/\[media=(.*?)\](.*?)\[\/media\]/i', $row['media_tag'], $matches );
$service = $matches[1];
$video_id = $matches[2];
@@ -460,7 +570,7 @@ class _Xenforo extends \IPS\convert\Software
continue 2;
}
$description = $em . "<br>" . $row['media_description'];
$description = $em . "<br>" . isset( $row['media_description'] ) ? $row['media_description'] : $row['description'];
break;
}
@@ -470,8 +580,8 @@ class _Xenforo extends \IPS\convert\Software
'image_album_id' => $row['album_id'],
'image_category_id' => $row['category_id'],
'image_member_id' => $row['user_id'],
'image_caption' => $row['media_title'],
'image_views' => $row['media_view_count'],
'image_caption' => isset( $row['media_title'] ) ? $row['media_title'] : $row['title'],
'image_views' => isset( $row['media_view_count'] ) ? $row['media_view_count'] : $row['view_count'],
'image_comments' => $row['comment_count'],
'image_ratings_total' => $row['rating_sum'],
'image_ratings_count' => $row['rating_count'],
@@ -488,9 +598,9 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass->convertGalleryImage( $info, $path );
/* Warnings */
foreach( $this->db->select( '*', 'xf_warning', array( "content_type=? AND content_id=?", 'xengallery_media', $row['media_id'] ) ) AS $warn )
foreach( $this->db->select( '*', 'xf_warning', array( "content_type=? AND content_id=?", ( static::$isLegacy ? 'xengallery_media' : 'xfmg_media' ), $row['media_id'] ) ) AS $warn )
{
$libraryClass->convertWarnLog( array(
$warnId = $libraryClass->convertWarnLog( array(
'wl_id' => $warn['warning_id'],
'wl_member' => $warn['user_id'],
'wl_moderator' => $warn['warning_user_id'],
@@ -499,6 +609,17 @@ class _Xenforo extends \IPS\convert\Software
'wl_note_member' => $warn['title'],
'wl_note_mods' => $warn['notes'],
) );
/* Add a member history record for this member */
$libraryClass->convertMemberHistory( array(
'log_id' => 'w' . $warn['warning_id'],
'log_member' => $warn['user_id'],
'log_by' => $warn['warning_user_id'],
'log_type' => 'warning',
'log_data' => array( 'wid' => $warnId ),
'log_date' => $warn['warning_date']
)
);
}
/* Reputation */
@@ -520,7 +641,7 @@ class _Xenforo extends \IPS\convert\Software
}
/* Follows */
foreach( $this->db->select( '*', 'xengallery_media_watch', array( "media_id=?", $row['media_id'] ) ) AS $follow )
foreach( $this->db->select( '*', static::$tablePrefix . 'media_watch', array( "media_id=?", $row['media_id'] ) ) AS $follow )
{
$libraryClass->convertFollow( array(
'follow_app' => 'gallery',
@@ -535,7 +656,6 @@ class _Xenforo extends \IPS\convert\Software
'follow_notify_freq' => ( $follow['send_alert'] OR $follow['send_email'] ) ? 'immediate' : 'none',
'follow_notify_sent' => 0,
'follow_visible' => 1,
'follow_index_id' => NULL
) );
}
@@ -582,7 +702,7 @@ class _Xenforo extends \IPS\convert\Software
$libraryClass::setKey( 'comment_id' );
foreach( $this->fetch( 'xengallery_comment', 'comment_id', array( "content_type=?", 'media' ) ) AS $row )
foreach( $this->fetch( static::$tablePrefix . 'comment', 'comment_id', array( "content_type=?", 'media' ) ) AS $row )
{
/* IP Address */
try
@@ -640,9 +760,9 @@ class _Xenforo extends \IPS\convert\Software
}
/* Warnings */
foreach( $this->db->select( '*', 'xf_warning', array( "content_type=? AND content_id=?", 'xengallery_comment', $row['comment_id'] ) ) AS $warn )
foreach( $this->db->select( '*', 'xf_warning', array( "content_type=? AND content_id=?", ( static::$isLegacy ? 'xengallery_comment' : 'xfmg_comment' ), $row['comment_id'] ) ) AS $warn )
{
$libraryClass->convertWarnLog( array(
$warnId = $libraryClass->convertWarnLog( array(
'wl_id' => $warn['warning_id'],
'wl_member' => $warn['user_id'],
'wl_moderator' => $warn['warning_user_id'],
@@ -651,6 +771,17 @@ class _Xenforo extends \IPS\convert\Software
'wl_note_member' => $warn['title'],
'wl_note_mods' => $warn['notes'],
) );
/* Add a member history record for this member */
$libraryClass->convertMemberHistory( array(
'log_id' => 'w' . $warn['warning_id'],
'log_member' => $warn['user_id'],
'log_by' => $warn['warning_user_id'],
'log_type' => 'warning',
'log_data' => array( 'wid' => $warnId ),
'log_date' => $warn['warning_date']
)
);
}
$libraryClass->setLastKeyValue( $row['comment_id'] );