Version 5.0.6
This commit is contained in:
1 parent
a3a4a9ccbd
commit
b89858b2ff
1214 files changed
+92828
-14859
No files matched your search
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @brief Converter Library Extension
|
||||
* @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/
|
||||
{subpackage}
|
||||
* @since {date}
|
||||
*/
|
||||
namespace IPS\{app}\extensions\convert\Library;
|
||||
|
||||
use \IPS\convert\Library as CoreLibrary;
|
||||
|
||||
/* 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 {class} extends CoreLibrary
|
||||
{
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
public static string $app = '{app}';
|
||||
|
||||
/**
|
||||
* Returns an array of items that we can convert, including the amount of rows stored in the Community Suite as well as the recommend value of rows to convert per cycle
|
||||
*
|
||||
* @param bool $rowCounts enable row counts
|
||||
* @return array
|
||||
*/
|
||||
public function menuRows( bool $rowCounts=FALSE ): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of tables that need to be truncated when Empty Local Data is used
|
||||
*
|
||||
* @param string $method Method to truncate
|
||||
* @return array
|
||||
*/
|
||||
protected function truncate( string $method ) : array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This is how the insert methods will work - basically like 3.x, but we should be using the actual classes to insert the data unless there is a real world reason not too.
|
||||
* Using the actual routines to insert data will help to avoid having to resynchronize and rebuild things later on, thus resulting in less conversion time being needed overall.
|
||||
* Anything that parses content, for example, may need to simply insert directly then rebuild via a task over time, as HTML Purifier is slow when mass inserting content.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A note on logging -
|
||||
* If the data is missing and it is unlikely that any source software would be able to provide this, we do not need to log anything and can use default data (for example, group_layout in convertLeaderGroups).
|
||||
* If the data is missing and it is likely that a majority of the source software can provide this, we should log a NOTICE and use default data (for example, a_casesensitive in convertAcronyms).
|
||||
* If the data is missing and it is required to convert the item, we should log a WARNING and return FALSE.
|
||||
* If the conversion absolutely cannot proceed at all (filestorage locations not writable, for example), then we should log an ERROR and throw an \IPS\convert\Exception to completely halt the process and redirect to an error screen showing the last logged error.
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @brief Converter Software Extension
|
||||
* @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/
|
||||
{subpackage}
|
||||
* @since {date}
|
||||
*/
|
||||
namespace IPS\{app}\extensions\convert\Software;
|
||||
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software as ConverterSoftware;
|
||||
|
||||
/* 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 {class} extends ConverterSoftware
|
||||
{
|
||||
/**
|
||||
* Software Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function softwareName(): string
|
||||
{
|
||||
/* Child classes must override this method */
|
||||
return "{class}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Software Key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function softwareKey(): string
|
||||
{
|
||||
/* Child classes must override this method */
|
||||
return mb_strtolower( '{class}' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content we can convert from this software.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public static function canConvert(): ?array
|
||||
{
|
||||
return [
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Can we convert passwords from this software.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function loginEnabled(): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can we convert settings?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function canConvertSettings(): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings Map
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function settingsMap(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings Map Listing
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function settingsMapList(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a block of text, or a language string, that explains what the admin must do to start this conversion
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getPreConversionInformation(): ?string
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of conversion methods that require additional information
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function checkConf(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get More Information
|
||||
*
|
||||
* @param string $method Conversion method
|
||||
* @return array|null
|
||||
*/
|
||||
public function getMoreInfo( string $method ): ?array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish - Adds everything it needs to the queues and clears data store
|
||||
*
|
||||
* @return array Messages to display
|
||||
*/
|
||||
public function finish(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return $post;
|
||||
}
|
||||
|
||||
// Insert convertX methods here
|
||||
}
|
||||
Whitespace-only changes.
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"core": {
|
||||
"LoginHandler": {
|
||||
"Converter": "IPS\\convert\\extensions\\core\\LoginHandler\\Converter"
|
||||
},
|
||||
"Queue": {
|
||||
"DeleteEmptyTopics": "IPS\\convert\\extensions\\core\\Queue\\DeleteEmptyTopics",
|
||||
"InvisionCommunityRebuildContent": "IPS\\convert\\extensions\\core\\Queue\\InvisionCommunityRebuildContent",
|
||||
|
||||
@@ -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="5000049" javascript_position="1000100">/**
|
||||
<file javascript_app="convert" javascript_location="admin" javascript_path="controllers/convert" javascript_name="ips.convert.menu.js" javascript_type="controller" javascript_version="5000074" javascript_position="1000100">/**
|
||||
* Invision Community
|
||||
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
|
||||
*
|
||||
@@ -45,7 +45,7 @@
|
||||
}
|
||||
} );
|
||||
}(jQuery, _));</file>
|
||||
<file javascript_app="convert" javascript_location="admin" javascript_path="controllers/forms" javascript_name="ips.forms.reactionmapper.js" javascript_type="controller" javascript_version="5000049" javascript_position="1000050">/**
|
||||
<file javascript_app="convert" javascript_location="admin" javascript_path="controllers/forms" javascript_name="ips.forms.reactionmapper.js" javascript_type="controller" javascript_version="5000074" javascript_position="1000050">/**
|
||||
* Invision Community
|
||||
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
|
||||
*
|
||||
@@ -101,8 +101,7 @@ mustache
|
||||
Debug.js
|
||||
app.js</order>
|
||||
<order app="global" path="/dev/js//library//jquery">jquery.js
|
||||
jquery-migrate.js
|
||||
jquery.transform.js</order>
|
||||
jquery-migrate.js</order>
|
||||
<order app="global" path="/dev/js//library//linkify">linkify.min.js
|
||||
linkify-jquery.min.js</order>
|
||||
</javascript>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<language>
|
||||
<app key="convert" version="5000049">
|
||||
<app key="convert" version="5000074">
|
||||
<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>
|
||||
@@ -50,12 +50,8 @@
|
||||
<word key="db_charset" js="0">Database Character Set</word>
|
||||
<word key="could_not_count_rows" js="0">Failed to count rows in table '%s' - is your table prefix set correctly?</word>
|
||||
<word key="file_location" js="0">Location of files</word>
|
||||
<word key="new_software" js="0">Create New Converter</word>
|
||||
<word key="new_library" js="0">Create New Library</word>
|
||||
<word key="classname" js="0">Class Name</word>
|
||||
<word key="classname_desc" js="0">The path for the generated file must exist. Format must be in the form of IPS\myapp\Namespace\Classname.</word>
|
||||
<word key="library" js="0">Library</word>
|
||||
<word key="new_software_not_in_dev" js="0">You can only create new converters while IN_DEV.</word>
|
||||
<word key="new_library_not_in_dev" js="0">You can only create new libraries while IN_DEV.</word>
|
||||
<word key="source_path" js="0">Path to %s installation.</word>
|
||||
<word key="path_invalid" js="0">The path entered appears to be invalid.</word>
|
||||
@@ -468,5 +464,7 @@
|
||||
<word key="r__convert_manage" js="0">Can run conversions</word>
|
||||
<word key="r__create" js="0">Create Conversions</word>
|
||||
<word key="r__create_manage" js="0">Can create conversions</word>
|
||||
<word key="ext__Software" js="0">Create a new Converter to convert data from another platform.</word>
|
||||
<word key="ext__Library" js="0">Create a new Library to convert data for a non-default app.</word>
|
||||
</app>
|
||||
</language>
|
||||
@@ -268,6 +268,13 @@
|
||||
"107785": "4.7.19",
|
||||
"107786": "4.7.19",
|
||||
"107787": "4.7.19",
|
||||
"107795": "4.7.20 Beta 1",
|
||||
"107796": "4.7.20 Beta 2",
|
||||
"107797": "4.7.20 Beta 3",
|
||||
"107798": "4.7.20 Beta 4",
|
||||
"107799": "4.7.20 Beta 5",
|
||||
"107800": "4.7.20",
|
||||
"107801": "4.7.20",
|
||||
"500001": "5.0.0 Alpha 1",
|
||||
"500002": "5.0.0 Alpha 2",
|
||||
"5000003": "5.0.0 Alpha 3",
|
||||
@@ -316,5 +323,30 @@
|
||||
"5000046": "5.0.0 Beta 14",
|
||||
"5000047": "5.0.0 Beta 14.1",
|
||||
"5000048": "5.0.0 Beta 14.2",
|
||||
"5000049": "5.0.0"
|
||||
"5000049": "5.0.0",
|
||||
"5000050": "5.0.0",
|
||||
"5000051": "5.0.0",
|
||||
"5000052": "5.0.1",
|
||||
"5000053": "5.0.1",
|
||||
"5000054": "5.0.2",
|
||||
"5000055": "5.0.2",
|
||||
"5000056": "5.0.2",
|
||||
"5000057": "5.0.3",
|
||||
"5000058": "5.0.3",
|
||||
"5000059": "5.0.3",
|
||||
"5000060": "5.0.3",
|
||||
"5000061": "5.0.4 Beta 1",
|
||||
"5000062": "5.0.4 Beta 1",
|
||||
"5000063": "5.0.4 Beta 1",
|
||||
"5000064": "5.0.4 Beta 2",
|
||||
"5000065": "5.0.4 Beta 3",
|
||||
"5000066": "5.0.4",
|
||||
"5000067": "5.0.5 Beta 1",
|
||||
"5000068": "5.0.5 Beta 2",
|
||||
"5000069": "5.0.5",
|
||||
"5000070": "5.0.6 Beta 1",
|
||||
"5000071": "5.0.6 Beta 2",
|
||||
"5000072": "5.0.6 Beta 3",
|
||||
"5000073": "5.0.6 Beta 4",
|
||||
"5000074": "5.0.6"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Account Settings Extension
|
||||
* @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/
|
||||
* @subpackage Converter
|
||||
* @since 14 Mar 2025
|
||||
*/
|
||||
|
||||
namespace IPS\convert\extensions\core\LoginHandler;
|
||||
|
||||
use IPS\convert\Login as ConverterLogin;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler;
|
||||
use IPS\Login\Handler\ButtonHandler;
|
||||
use IPS\Login\Handler\UsernamePasswordHandler;
|
||||
use IPS\Member as MemberClass;
|
||||
use function defined;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* AccountSettings Extension
|
||||
*/
|
||||
class Converter extends ConverterLogin
|
||||
{
|
||||
public static bool $allowMultiple = false;
|
||||
}
|
||||
@@ -211,7 +211,7 @@ class RebuildContent extends QueueAbstract
|
||||
/* Pass off to software class */
|
||||
if ( $softwareClass::$contentFixed === FALSE )
|
||||
{
|
||||
$item->$contentColumn = $softwareClass::fixPostData( $item->$contentColumn, $data['class'], $item->$idColumn );
|
||||
$item->$contentColumn = $softwareClass::fixPostData( $item->$contentColumn, $data['class'], $item->$idColumn, $app );
|
||||
}
|
||||
|
||||
/* Then pass off to legacy parser to bring up to 4.x */
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace IPS\convert\modules\admin\manage;
|
||||
|
||||
use IPS\Application;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Application as ConverterApplication;
|
||||
use IPS\convert\Exception;
|
||||
use IPS\convert\Library;
|
||||
use IPS\convert\Software;
|
||||
@@ -99,12 +100,12 @@ class convert extends Controller
|
||||
}
|
||||
|
||||
/* Do a quick parent storage check */
|
||||
\IPS\convert\Application::checkConvParent( $app->getSource()->getLibrary()->app );
|
||||
ConverterApplication::checkConvParent( $app->getSource()->getLibrary()->getAppKey() );
|
||||
|
||||
/* Check child apps also have temporary columns */
|
||||
foreach( $app->children() as $child )
|
||||
{
|
||||
\IPS\convert\Application::checkConvParent( $child->getSource()->getLibrary()->app );
|
||||
ConverterApplication::checkConvParent( $child->getSource()->getLibrary()->getAppKey() );
|
||||
}
|
||||
|
||||
/* Are we using the legacy UI? If not, then start converting as we already have everything we need. */
|
||||
|
||||
@@ -188,12 +188,6 @@ class manage extends Controller
|
||||
|
||||
if ( \IPS\IN_DEV )
|
||||
{
|
||||
Output::i()->sidebar['actions']['software'] = array(
|
||||
'icon' => 'plus',
|
||||
'title' => 'new_software',
|
||||
'link' => Url::internal( "app=convert&module=manage&controller=manage&do=software" ),
|
||||
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('new_software') )
|
||||
);
|
||||
Output::i()->sidebar['actions']['library'] = array(
|
||||
'icon' => 'plus',
|
||||
'title' => 'new_library',
|
||||
@@ -254,67 +248,6 @@ class manage extends Controller
|
||||
Output::i()->output .= $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create New Converter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function software() : void
|
||||
{
|
||||
if ( \IPS\IN_DEV === FALSE )
|
||||
{
|
||||
Output::i()->error( 'new_sofware_not_in_dev', '1V100/1', 403 );
|
||||
}
|
||||
|
||||
$form = new Form;
|
||||
$form->add( new Text( 'classname', NULL, TRUE ) );
|
||||
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
/* Get the default code */
|
||||
$default = file_get_contents( \IPS\ROOT_PATH . '/applications/convert/data/defaults/Software.txt' );
|
||||
|
||||
/* Explode the entered class name to figure out the file path and namespace */
|
||||
$exploded = explode( '\\', ltrim( $values['classname'], '\\' ) );
|
||||
|
||||
/* Copy the exploded array to generate the path */
|
||||
$copied = $exploded;
|
||||
|
||||
/* Shift off IPS from the namespace */
|
||||
array_shift( $copied );
|
||||
|
||||
/* Get our application key */
|
||||
$application = array_shift( $copied );
|
||||
|
||||
/* Generate our path */
|
||||
$filepath = \IPS\ROOT_PATH . "/applications/{$application}/sources/" . implode( '/', $copied ) . ".php";
|
||||
|
||||
/* Now figure out our namespace, class, and default code with replacements */
|
||||
$classname = array_pop( $exploded );
|
||||
$namespace = implode( '\\', $exploded );
|
||||
$code = str_replace( array( '<#NAMESPACE#>', '<#CLASS#>', '<#CLASS_LOWER#>' ), array( $namespace, $classname, mb_strtolower( $classname ) ), $default );
|
||||
|
||||
/* Check if we need to create a folder */
|
||||
if( count( $copied ) > 1 )
|
||||
{
|
||||
$folder = \IPS\ROOT_PATH . "/applications/{$application}/sources/" . $copied[0];
|
||||
if( !file_exists( $folder ) )
|
||||
{
|
||||
@mkdir( $folder );
|
||||
@chmod( $folder, FOLDER_PERMISSION_NO_WRITE );
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate the file */
|
||||
file_put_contents( $filepath, $code );
|
||||
|
||||
Output::i()->redirect( Url::internal( "app=convert&module=manage&controller=manage" ), 'saved' );
|
||||
}
|
||||
|
||||
Output::i()->title = Member::loggedIn()->language()->addToStack( 'new_software' );
|
||||
Output::i()->output = (string) $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create New Library
|
||||
*
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": 107795,
|
||||
"name": "4.7.20 Beta 1",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeSettings": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"hooks": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": 107796,
|
||||
"name": "4.7.20 Beta 2",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeSettings": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"hooks": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": 107797,
|
||||
"name": "4.7.20 Beta 3",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeSettings": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"hooks": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": 107798,
|
||||
"name": "4.7.20 Beta 4",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeSettings": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"hooks": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": 107799,
|
||||
"name": "4.7.20 Beta 5",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeSettings": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"hooks": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": 107800,
|
||||
"name": "4.7.20",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeSettings": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"hooks": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": 107801,
|
||||
"name": "4.7.20",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeSettings": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"hooks": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000050,
|
||||
"name": "5.0.0",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000051,
|
||||
"name": "5.0.0",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000052,
|
||||
"name": "5.0.1",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000053,
|
||||
"name": "5.0.1",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000054,
|
||||
"name": "5.0.2",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000055,
|
||||
"name": "5.0.2",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000056,
|
||||
"name": "5.0.2",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000057,
|
||||
"name": "5.0.3",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000058,
|
||||
"name": "5.0.3",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000059,
|
||||
"name": "5.0.3",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000060,
|
||||
"name": "5.0.3",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000061,
|
||||
"name": "5.0.4 Beta 1",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000062,
|
||||
"name": "5.0.4 Beta 1",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000063,
|
||||
"name": "5.0.4 Beta 1",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000064,
|
||||
"name": "5.0.4 Beta 2",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000065,
|
||||
"name": "5.0.4 Beta 3",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000066,
|
||||
"name": "5.0.4",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000067,
|
||||
"name": "5.0.5 Beta 1",
|
||||
"steps": {
|
||||
"queries": true,
|
||||
"lang": true,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"normal": {
|
||||
"added": [
|
||||
"ext__Software",
|
||||
"ext__Library"
|
||||
],
|
||||
"edited": [],
|
||||
"removed": [
|
||||
"new_software",
|
||||
"classname",
|
||||
"classname_desc",
|
||||
"new_software_not_in_dev"
|
||||
]
|
||||
},
|
||||
"js": {
|
||||
"added": [],
|
||||
"edited": [],
|
||||
"removed": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"1": {
|
||||
"method": "update",
|
||||
"params": [
|
||||
"core_login_methods",
|
||||
{
|
||||
"login_classname": "IPS\\convert\\extensions\\core\\LoginHandler\\Converter"
|
||||
},
|
||||
[
|
||||
"login_classname=?",
|
||||
"IPS\\convert\\Login"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000068,
|
||||
"name": "5.0.5 Beta 2",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000069,
|
||||
"name": "5.0.5",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000070,
|
||||
"name": "5.0.6 Beta 1",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": true,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": true,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"files": {
|
||||
"added": [],
|
||||
"edited": [],
|
||||
"removed": []
|
||||
},
|
||||
"orders": {
|
||||
"added": [],
|
||||
"edited": [
|
||||
"global\/\/dev\/js\/\/library\/\/jquery"
|
||||
],
|
||||
"removed": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000071,
|
||||
"name": "5.0.6 Beta 2",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000072,
|
||||
"name": "5.0.6 Beta 3",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000073,
|
||||
"name": "5.0.6 Beta 4",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": 5000074,
|
||||
"name": "5.0.6",
|
||||
"steps": {
|
||||
"queries": false,
|
||||
"lang": false,
|
||||
"theme": false,
|
||||
"themeeditor": false,
|
||||
"javascript": false,
|
||||
"emailTemplates": false,
|
||||
"acpSearchKeywords": false,
|
||||
"settings": false,
|
||||
"tasks": false,
|
||||
"modules": false,
|
||||
"widgets": false,
|
||||
"customOptions": false,
|
||||
"customRoutines": false
|
||||
},
|
||||
"forceMainUpgrader": false,
|
||||
"forceManualDownloadNoCiC": false,
|
||||
"forceManualDownloadCiC": false
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class App extends ActiveRecord
|
||||
$this->start_date = time();
|
||||
parent::save();
|
||||
|
||||
Application::checkConvParent( $this->getSource()->getLibrary()->app );
|
||||
Application::checkConvParent( $this->getSource()->getLibrary()->getAppKey() );
|
||||
|
||||
Db::i()->insert( 'convert_app_sessions', array(
|
||||
'session_app_id' => $this->app_id,
|
||||
@@ -433,7 +433,7 @@ class App extends ActiveRecord
|
||||
{
|
||||
$table = 'convert_link';
|
||||
|
||||
if ( in_array( $type, [ 'core_message_topics', 'core_message_posts', 'core_message_topic_user_map', 'forums_topics', 'forums_posts', 'forums_topics_old', 'forums_posts_old' ] ) AND $mainTable === FALSE )
|
||||
if ( in_array( $type, [ 'core_message_topics', 'core_message_posts', 'core_message_topic_user_map', 'forums_topics', 'forums_posts', 'forums_posts_first', 'forums_topics_old', 'forums_posts_old' ] ) AND $mainTable === FALSE )
|
||||
{
|
||||
if ( in_array( $type, array( 'core_message_topics', 'core_message_posts', 'core_message_topic_user_map' ) ) )
|
||||
{
|
||||
@@ -441,7 +441,7 @@ class App extends ActiveRecord
|
||||
}
|
||||
else
|
||||
{
|
||||
$tableType = str_replace( [ 'forums_', '_old' ], '', $type );
|
||||
$tableType = str_replace( [ 'forums_', '_first', '_old' ], '', $type );
|
||||
$table = "convert_link_{$tableType}";
|
||||
}
|
||||
}
|
||||
@@ -716,7 +716,7 @@ class App extends ActiveRecord
|
||||
* @return void
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function log( string $message, string $method, int $severity=1, ?int $id=NULL ) : void
|
||||
public function log( string $message, string $method, int $severity=1, int $id=NULL ) : void
|
||||
{
|
||||
if ( ! in_array( $severity, array( static::LOG_NOTICE, static::LOG_WARNING, static::LOG_ERROR ) ) )
|
||||
{
|
||||
@@ -837,7 +837,7 @@ class App extends ActiveRecord
|
||||
|
||||
if ( $mainTable === FALSE )
|
||||
{
|
||||
if ( in_array( $type, array( 'core_message_topics', 'core_message_posts', 'core_message_topic_user_map', 'forums_topics', 'forums_posts', 'forums_topics_old', 'forums_posts_old' ) ) )
|
||||
if ( in_array( $type, array( 'core_message_topics', 'core_message_posts', 'core_message_topic_user_map', 'forums_topics', 'forums_posts', 'forums_posts_first', 'forums_topics_old', 'forums_posts_old' ) ) )
|
||||
{
|
||||
if ( in_array( $type, array( 'core_message_topics', 'core_message_posts', 'core_message_topic_user_map' ) ) )
|
||||
{
|
||||
@@ -845,7 +845,7 @@ class App extends ActiveRecord
|
||||
}
|
||||
else
|
||||
{
|
||||
$tableType = str_replace( [ 'forums_', '_old' ], '', $type );
|
||||
$tableType = str_replace( [ 'forums_', '_first', '_old' ], '', $type );
|
||||
$table = "convert_link_{$tableType}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@ namespace IPS\convert;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use BadMethodCallException;
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use IPS\Application;
|
||||
use IPS\convert\Software\Exception as SoftwareException;
|
||||
use IPS\Db;
|
||||
use IPS\Db\Select;
|
||||
@@ -99,6 +101,22 @@ abstract class Library
|
||||
{
|
||||
$this->software = $software;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Application Key
|
||||
*
|
||||
* @return string
|
||||
* @throws BadMethodCallException
|
||||
*/
|
||||
public function getAppKey(): string
|
||||
{
|
||||
if( !isset( static::$app ) )
|
||||
{
|
||||
throw new BadMethodCallException;
|
||||
}
|
||||
|
||||
return static::$app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Libraries
|
||||
@@ -107,7 +125,7 @@ abstract class Library
|
||||
*/
|
||||
public static function libraries() : array
|
||||
{
|
||||
return array(
|
||||
$libraries = [
|
||||
'core' => 'IPS\convert\Library\Core',
|
||||
'blog' => 'IPS\convert\Library\Blog',
|
||||
'calendar' => 'IPS\convert\Library\Calendar',
|
||||
@@ -116,7 +134,22 @@ abstract class Library
|
||||
'forums' => 'IPS\convert\Library\Forums',
|
||||
'gallery' => 'IPS\convert\Library\Gallery',
|
||||
'nexus' => 'IPS\convert\Library\Nexus',
|
||||
);
|
||||
];
|
||||
|
||||
/* Library Extensions */
|
||||
foreach ( Application::allExtensions( 'convert', 'Library', FALSE, construct: false ) as $extension )
|
||||
{
|
||||
try
|
||||
{
|
||||
if( !empty( $extension::$app ) AND !isset( $libraries[ $extension::$app ] ) )
|
||||
{
|
||||
$libraries[ $extension::$app ] = $extension;
|
||||
}
|
||||
}
|
||||
catch( BadMethodCallException ) {}
|
||||
}
|
||||
|
||||
return $libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +184,7 @@ abstract class Library
|
||||
* @param int|NULL $perCycle The number of results to process per cycle
|
||||
* @return array|NULL Data for the MultipleRedirect
|
||||
*/
|
||||
public function process( int $data, string $method, ?int $perCycle=NULL ) : ?array
|
||||
public function process( ?int $data, string $method, ?int $perCycle=NULL ) : ?array
|
||||
{
|
||||
if ( !is_null( $perCycle ) )
|
||||
{
|
||||
@@ -169,7 +202,7 @@ abstract class Library
|
||||
}
|
||||
|
||||
static::$action = $canConvert[ $method ]['table'];
|
||||
static::$startValue = $data;
|
||||
static::$startValue = (int) $data;
|
||||
$masterAppId = $this->software->app->getMasterConversionId();
|
||||
|
||||
if ( !isset( $_SESSION['convertCountRows'] ) )
|
||||
@@ -329,7 +362,6 @@ abstract class Library
|
||||
|
||||
/* Clear the running flag */
|
||||
$this->software->app->setRunningFlag( $method, FALSE );
|
||||
|
||||
$this->software->app->log( $e->getMessage(), __METHOD__, App::LOG_WARNING );
|
||||
throw new Exception;
|
||||
}
|
||||
@@ -497,7 +529,6 @@ abstract class Library
|
||||
catch( Exception $e )
|
||||
{
|
||||
Log::log( $e, 'converters' );
|
||||
|
||||
$this->software->app->log( $e->getMessage(), __METHOD__, App::LOG_WARNING );
|
||||
throw new Exception;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class Blog extends Core
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
public string $app = 'blog';
|
||||
public static string $app = 'blog';
|
||||
|
||||
/**
|
||||
* Returns an array of items that we can convert, including the amount of rows stored in the Community Suite as well as the recommend value of rows to convert per cycle
|
||||
|
||||
@@ -51,7 +51,7 @@ class Calendar extends Core
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
public string $app = 'calendar';
|
||||
public static string $app = 'calendar';
|
||||
|
||||
/**
|
||||
* Returns an array of items that we can convert, including the amount of rows stored in the Community Suite as well as the recommend value of rows to convert per cycle
|
||||
|
||||
@@ -61,7 +61,7 @@ class Cms extends Core
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
public string $app = 'cms';
|
||||
public static string $app = 'cms';
|
||||
|
||||
/**
|
||||
* Returns a block of text, or a language string, that explains what the admin must do to complete this conversion
|
||||
@@ -938,7 +938,7 @@ class Cms extends Core
|
||||
}
|
||||
|
||||
/* Zero Defaults */
|
||||
foreach( array( 'database_record_count', 'database_all_editable', 'database_comment_approve', 'database_record_approve', 'database_tags_enabled', 'database_tags_noprefixes', 'database_cat_index_type' ) AS $zeroDefault )
|
||||
foreach( array( 'database_record_count', 'database_all_editable', 'database_comment_approve', 'database_record_approve', 'database_tags_enabled', 'database_tags_noprefixes' ) AS $zeroDefault )
|
||||
{
|
||||
if ( !isset( $info[ $zeroDefault ] ) )
|
||||
{
|
||||
@@ -987,40 +987,21 @@ class Cms extends Core
|
||||
else
|
||||
{
|
||||
$info['database_fixed_field_perms'] = json_encode( array(
|
||||
'record' => array(
|
||||
'visible' => TRUE,
|
||||
'perm_view' => '*',
|
||||
'perm_2' => '*',
|
||||
'perm_3' => '*'
|
||||
'record' => array(
|
||||
'visible' => TRUE,
|
||||
'perm_view' => '*',
|
||||
'perm_2' => '*',
|
||||
'perm_3' => '*'
|
||||
),
|
||||
'record_image' => array(
|
||||
'visible' => TRUE,
|
||||
'perm_view' => '*',
|
||||
'perm_2' => '*',
|
||||
'perm_3' => '*'
|
||||
'record_image' => array(
|
||||
'visible' => TRUE,
|
||||
'perm_view' => '*',
|
||||
'perm_2' => '*',
|
||||
'perm_3' => '*'
|
||||
)
|
||||
) );
|
||||
}
|
||||
|
||||
if ( isset( $info['database_featured_settings'] ) )
|
||||
{
|
||||
if ( is_array( $info['database_featured_settings'] ) )
|
||||
{
|
||||
$info['database_featured_settings'] = json_encode( $info['database_featured_settings'] );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$info['database_featured_settings'] = json_encode( array(
|
||||
'featured' => FALSE,
|
||||
'perpage' => 10,
|
||||
'pagination' => FALSE,
|
||||
'sort' => 'record_publish_date',
|
||||
'direction' => 'desc',
|
||||
'categories' => 0,
|
||||
) );
|
||||
}
|
||||
|
||||
if ( isset( $info['database_fixed_field_settings'] ) )
|
||||
{
|
||||
if ( is_array( $info['database_fixed_field_settings'] ) )
|
||||
@@ -1039,13 +1020,8 @@ class Cms extends Core
|
||||
}
|
||||
|
||||
/* These are things we will not know, or cannot convert (like templates) */
|
||||
$info['database_template_listing'] = 'listing';
|
||||
$info['database_template_display'] = 'display';
|
||||
$info['database_template_categories'] = 'category_index';
|
||||
$info['database_field_title'] = 0; # we will set this later
|
||||
$info['database_field_content'] = 0; # we will set this later
|
||||
$info['database_template_form'] = 'form';
|
||||
$info['database_template_featured'] = 'category_articles';
|
||||
|
||||
/* Forum Integration */
|
||||
try
|
||||
@@ -2804,7 +2780,8 @@ class Cms extends Core
|
||||
*/
|
||||
public function convertAttachment( array $info=array(), array $map=array(), ?string $filepath=NULL, ?string $filedata=NULL, ?string $thumbnailpath = NULL ) : bool|int
|
||||
{
|
||||
$database = $this->software->app->getLink( str_replace( '-review', '', $map['id3'] ), 'cms_databases' );
|
||||
/* We always skip the link for id3, thus it's already the right database id */
|
||||
$database = str_replace( [ '-review', '-comment' ], '', $map['id3'] );
|
||||
|
||||
$map['id1_type'] = "cms_custom_database_{$database}";
|
||||
$map['id1_from_parent'] = FALSE;
|
||||
@@ -2823,8 +2800,11 @@ class Cms extends Core
|
||||
$map['id2_type'] = 'cms_database_comments';
|
||||
}
|
||||
}
|
||||
|
||||
$map['id3'] = $database;
|
||||
|
||||
if( empty( $map['id3'] ) )
|
||||
{
|
||||
$map['id3'] = $database;
|
||||
}
|
||||
|
||||
return parent::convertAttachment( $info, $map, $filepath, $filedata, $thumbnailpath );
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class Core extends Library
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
public string $app = 'core';
|
||||
public static string $app = 'core';
|
||||
|
||||
/**
|
||||
* Returns an array of items that we can convert, including the amount of rows stored in the Community Suite as well as the recommend value of rows to convert per cycle
|
||||
@@ -199,7 +199,7 @@ class Core extends Library
|
||||
$return[$k] = array(
|
||||
'step_title' => 'convert_custom_bbcode',
|
||||
'step_method' => 'convertCustomBbcode',
|
||||
'ips_rows' => Db::i()->select( 'COUNT(*)', 'custom_bbcode' ),
|
||||
'ips_rows' => Db::i()->select( 'COUNT(*)', 'convert_custom_bbcode' ),
|
||||
'source_rows' => array( 'table' => $v['table'], 'where' => $v['where'] ),
|
||||
'per_cycle' => 200,
|
||||
'dependencies' => array(),
|
||||
@@ -2219,7 +2219,7 @@ class Core extends Library
|
||||
}
|
||||
|
||||
/* Now let's do 0 defaults */
|
||||
foreach( array( 'g_delete_own_posts', 'g_is_supmod', 'g_access_cp', 'g_append_edit', 'g_access_offline', 'g_avoid_q', 'g_avoid_flood', 'g_max_messages', 'g_dohtml', 'g_bypass_badwords', 'g_attach_per_post', 'g_dname_changes', 'g_mod_preview', 'g_hide_online_list', 'g_mod_post_unit', 'g_ppd_limit', 'g_ppd_unit', 'g_displayname_unit', 'g_sig_unit' ) AS $zeroIsDefault )
|
||||
foreach( array( 'g_delete_own_posts', 'g_is_supmod', 'g_access_cp', 'g_append_edit', 'g_access_offline', 'g_avoid_q', 'g_avoid_flood', 'g_max_messages', 'g_bypass_badwords', 'g_attach_per_post', 'g_dname_changes', 'g_mod_preview', 'g_hide_online_list', 'g_mod_post_unit', 'g_ppd_limit', 'g_ppd_unit', 'g_displayname_unit', 'g_sig_unit' ) AS $zeroIsDefault )
|
||||
{
|
||||
if ( !isset( $info[$zeroIsDefault] ) )
|
||||
{
|
||||
@@ -3315,7 +3315,7 @@ class Core extends Library
|
||||
|
||||
$info['completed'] = TRUE;
|
||||
$info['member_streams'] = NULL;
|
||||
$info['create_menu'] = NULL;
|
||||
|
||||
$id = $info['member_id'];
|
||||
unset( $info['member_id'] );
|
||||
|
||||
@@ -3859,9 +3859,7 @@ class Core extends Library
|
||||
}
|
||||
|
||||
/* No Longer Used */
|
||||
$topic['mt_hasattach'] = 0;
|
||||
$topic['mt_to_member_id'] = 0;
|
||||
$topic['mt_is_draft'] = 0;
|
||||
$topic['mt_is_system'] = 0;
|
||||
|
||||
if ( isset( $topic['mt_start_time'] ) )
|
||||
|
||||
@@ -49,7 +49,7 @@ class Downloads extends Core
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
public string $app = 'downloads';
|
||||
public static string $app = 'downloads';
|
||||
|
||||
/**
|
||||
* Returns an array of items that we can convert, including the amount of rows stored in the Community Suite as well as the recommend value of rows to convert per cycle
|
||||
|
||||
@@ -51,7 +51,7 @@ class Forums extends Core
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
public string $app = 'forums';
|
||||
public static string $app = 'forums';
|
||||
|
||||
/**
|
||||
* Returns an array of items that we can convert, including the amount of rows stored in the Community Suite as well as the recommend value of rows to convert per cycle
|
||||
@@ -428,39 +428,6 @@ class Forums extends Core
|
||||
$info['posts'] = 0;
|
||||
}
|
||||
|
||||
if ( isset( $info['last_post'] ) )
|
||||
{
|
||||
if ( $info['last_post'] instanceof DateTime )
|
||||
{
|
||||
$info['last_post'] = $info['last_post']->getTimestamp();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$info['last_post'] = NULL;
|
||||
}
|
||||
|
||||
if ( isset( $info['last_poster_id'] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$info['last_poster_id'] = $this->software->app->getLink( $info['last_poster_id'], 'core_members', TRUE );
|
||||
}
|
||||
catch( OutOfRangeException $e )
|
||||
{
|
||||
$info['last_poster_id'] = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$info['last_poster_id'] = 0;
|
||||
}
|
||||
|
||||
if ( !isset( $info['last_poster_name'] ) )
|
||||
{
|
||||
$info['last_poster_name'] = '';
|
||||
}
|
||||
|
||||
/* Do parent_id now - we'll need it later */
|
||||
if ( !isset( $info['club_id'] ) )
|
||||
{
|
||||
@@ -540,14 +507,6 @@ class Forums extends Core
|
||||
$info['password_override'] = '';
|
||||
}
|
||||
|
||||
if ( !isset( $info['last_title'] ) )
|
||||
{
|
||||
$info['last_title'] = NULL;
|
||||
}
|
||||
|
||||
/* We can't know this yet */
|
||||
$info['last_id'] = 0;
|
||||
|
||||
if ( !isset( $info['sort_key'] ) )
|
||||
{
|
||||
$info['sort_key'] = 'last_post';
|
||||
@@ -637,33 +596,9 @@ class Forums extends Core
|
||||
|
||||
/* Just reset this */
|
||||
$info['name_seo'] = url::seoTitle( $name );
|
||||
|
||||
if ( !isset( $info['seo_last_title'] ) )
|
||||
{
|
||||
if ( !is_null( $info['last_title'] ) )
|
||||
{
|
||||
$info['seo_last_title'] = Url::seoTitle( $info['last_title'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$info['seo_last_title'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ( !isset( $info['seo_last_name'] ) )
|
||||
{
|
||||
if ( !is_null( $info['last_poster_name'] ) )
|
||||
{
|
||||
$info['seo_last_name'] = Url::seoTitle( $info['last_poster_name'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$info['seo_last_name'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
/* No longer used */
|
||||
$info['last_x_topic_ids'] = NULL;
|
||||
|
||||
/* This will auto rebuild on first visit, then recalculate later */
|
||||
unset( $info['last_post'], $info['last_poster_id'], $info['last_poster_name'], $info['seo_last_name'], $info['last_title'], $info['seo_last_title'], $info['last_id'] );
|
||||
|
||||
$bitoptions = 0;
|
||||
if ( isset( $info['forums_bitoptions'] ) )
|
||||
@@ -1217,7 +1152,6 @@ class Forums extends Core
|
||||
|
||||
/* Can't knew these */
|
||||
$info['topic_answered_pid'] = 0;
|
||||
$info['popular_time'] = 0;
|
||||
|
||||
if ( !isset( $info['featured'] ) )
|
||||
{
|
||||
@@ -1430,7 +1364,7 @@ class Forums extends Core
|
||||
unset( $info['pid'] );
|
||||
|
||||
$inserted_id = Db::i()->insert( 'forums_posts', $info );
|
||||
$this->software->app->addLink( $inserted_id, $id, 'forums_posts' );
|
||||
$this->software->app->addLink( $inserted_id, $id, $info['new_topic'] ? 'forums_posts_first' : 'forums_posts' );
|
||||
|
||||
$topicUpdate = array();
|
||||
|
||||
@@ -1976,6 +1910,12 @@ class Forums extends Core
|
||||
$map['id2_type'] = 'forums_posts';
|
||||
$map['id2_from_parent'] = FALSE;
|
||||
$map['location_key'] = 'forums_Forums';
|
||||
|
||||
if( isset( $map['first_post'] ) AND $map['first_post'] === TRUE )
|
||||
{
|
||||
$map['id2_type'] = 'forums_posts_first';
|
||||
unset( $map['first_post'] );
|
||||
}
|
||||
|
||||
return parent::convertAttachment( $info, $map, $filepath, $filedata, $thumbnailpath );
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class Gallery extends Core
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
public string $app = 'gallery';
|
||||
public static string $app = 'gallery';
|
||||
|
||||
/**
|
||||
* Returns an array of items that we can convert, including the amount of rows stored in the Community Suite as well as the recommend value of rows to convert per cycle
|
||||
|
||||
@@ -53,7 +53,7 @@ class Nexus extends Library
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
public string $app = 'nexus';
|
||||
public static string $app = 'nexus';
|
||||
|
||||
/**
|
||||
* Returns an array of items that we can convert, including the amount of rows stored in the Community Suite as well as the recommend value of rows to convert per cycle
|
||||
|
||||
@@ -72,12 +72,7 @@ class Login extends Handler
|
||||
/* Make sure we have the username or email */
|
||||
if( !$usernameOrEmail )
|
||||
{
|
||||
$member = NULL;
|
||||
|
||||
$member = new Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE ), Exception::NO_ACCOUNT, NULL, $member );
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE ), Exception::NO_ACCOUNT );
|
||||
}
|
||||
|
||||
/* Get member(s) */
|
||||
@@ -98,8 +93,6 @@ class Login extends Handler
|
||||
/* If we didn't match any, throw an exception */
|
||||
if ( !count( $members ) )
|
||||
{
|
||||
$member = NULL;
|
||||
|
||||
$member = new Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
|
||||
|
||||
@@ -170,8 +170,8 @@ abstract class Software
|
||||
*/
|
||||
public static function software() : array
|
||||
{
|
||||
return array(
|
||||
'core' => array(
|
||||
$software = [
|
||||
'core' => [
|
||||
'expressionengine' => 'IPS\convert\Software\Core\Expressionengine',
|
||||
'invisioncommunity' => 'IPS\convert\Software\Core\Invisioncommunity',
|
||||
'joomla' => 'IPS\convert\Software\Core\Joomla',
|
||||
@@ -189,28 +189,28 @@ abstract class Software
|
||||
'wordpress' => 'IPS\convert\Software\Core\Wordpress',
|
||||
'wpforo' => 'IPS\convert\Software\Core\Wpforo',
|
||||
'xenforo' => 'IPS\convert\Software\Core\Xenforo',
|
||||
),
|
||||
'blog' => array(
|
||||
],
|
||||
'blog' => [
|
||||
'vbulletin' => 'IPS\convert\Software\Blog\Vbulletin',
|
||||
'invisioncommunity' => 'IPS\convert\Software\Blog\Invisioncommunity',
|
||||
),
|
||||
'calendar' => array(
|
||||
],
|
||||
'calendar' => [
|
||||
'mybb' => 'IPS\convert\Software\Calendar\Mybb',
|
||||
'vbulletin' => 'IPS\convert\Software\Calendar\Vbulletin',
|
||||
'invisioncommunity' => 'IPS\convert\Software\Calendar\Invisioncommunity',
|
||||
),
|
||||
'cms' => array(
|
||||
],
|
||||
'cms' => [
|
||||
'joomla' => 'IPS\convert\Software\Cms\Joomla',
|
||||
'vbulletin' => 'IPS\convert\Software\Cms\Vbulletin',
|
||||
'wordpress' => 'IPS\convert\Software\Cms\Wordpress',
|
||||
'xenforo' => 'IPS\convert\Software\Cms\Xenforo',
|
||||
'xenfororm' => 'IPS\convert\Software\Cms\Xenfororm',
|
||||
),
|
||||
'downloads' => array(
|
||||
],
|
||||
'downloads' => [
|
||||
'invisioncommunity' => 'IPS\convert\Software\Downloads\Invisioncommunity',
|
||||
'xenforo' => 'IPS\convert\Software\Downloads\Xenforo',
|
||||
),
|
||||
'forums' => array(
|
||||
],
|
||||
'forums' => [
|
||||
'bbpress' => 'IPS\convert\Software\Forums\Bbpress',
|
||||
'expressionengine' => 'IPS\convert\Software\Forums\Expressionengine',
|
||||
'invisioncommunity' => 'IPS\convert\Software\Forums\Invisioncommunity',
|
||||
@@ -226,17 +226,46 @@ abstract class Software
|
||||
'woltlab' => 'IPS\convert\Software\Forums\Woltlab',
|
||||
'wpforo' => 'IPS\convert\Software\Forums\Wpforo',
|
||||
'xenforo' => 'IPS\convert\Software\Forums\Xenforo',
|
||||
),
|
||||
'gallery' => array(
|
||||
],
|
||||
'gallery' => [
|
||||
'coppermine' => 'IPS\convert\Software\Gallery\Coppermine',
|
||||
'invisioncommunity' => 'IPS\convert\Software\Gallery\Invisioncommunity',
|
||||
'photoplog' => 'IPS\convert\Software\Gallery\Photoplog',
|
||||
'photopost' => 'IPS\convert\Software\Gallery\Photopost',
|
||||
'vbulletin' => 'IPS\convert\Software\Gallery\Vbulletin',
|
||||
'xenforo' => 'IPS\convert\Software\Gallery\Xenforo',
|
||||
),
|
||||
'nexus' => array()
|
||||
);
|
||||
],
|
||||
'nexus' => []
|
||||
];
|
||||
|
||||
/* Library Extensions */
|
||||
foreach ( Application::allExtensions( 'convert', 'Library', FALSE, construct: false ) as $extension )
|
||||
{
|
||||
try
|
||||
{
|
||||
if( !empty( $extension::$app ) AND !isset( $software[ $extension::$app ] ) )
|
||||
{
|
||||
$software[ $extension::$app ] = [];
|
||||
}
|
||||
}
|
||||
catch( \BadMethodCallException ) {}
|
||||
}
|
||||
|
||||
/* Software Extensions */
|
||||
foreach ( Application::allExtensions( 'convert', 'Software', FALSE, construct: false ) as $extension )
|
||||
{
|
||||
try
|
||||
{
|
||||
if( !empty( $extension::$targetApp ) AND isset( $software[ $extension::$targetApp ] )
|
||||
AND !isset( $software[ $extension::$targetApp ][ $extension::softwareKey() ] ) )
|
||||
{
|
||||
$software[ $extension::$targetApp ][ $extension::softwareKey() ] = $extension;
|
||||
}
|
||||
}
|
||||
catch( \BadMethodCallException ) {}
|
||||
}
|
||||
|
||||
return $software;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -516,12 +545,13 @@ abstract class Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return $post;
|
||||
}
|
||||
|
||||
@@ -212,14 +212,15 @@ class Vbulletin extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return VbulletinClass::fixPostData( $post, $className, $contentId );
|
||||
return VbulletinClass::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace IPS\convert\Software\Calendar;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\calendar\Date;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\convert\Software\Core\Vbulletin as VbulletinClass;
|
||||
use IPS\Task;
|
||||
@@ -108,14 +109,15 @@ class Vbulletin extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return VbulletinClass::fixPostData( $post, $className, $contentId );
|
||||
return VbulletinClass::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -210,14 +210,15 @@ class Vbulletin extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return VbulletinClass::fixPostData( $post, $className, $contentId );
|
||||
return VbulletinClass::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ use DomainException;
|
||||
use Exception;
|
||||
use IPS\Application\Module;
|
||||
use IPS\Content\Search\Index;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\Data\Cache;
|
||||
use IPS\Data\Store;
|
||||
@@ -166,12 +167,13 @@ class Expressionengine extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return nl2br( $post );
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use DomainException;
|
||||
use Exception;
|
||||
use IPS\Application\Module;
|
||||
use IPS\Content\Search\Index;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\Data\Cache;
|
||||
use IPS\Data\Store;
|
||||
@@ -318,12 +319,13 @@ class Mybb extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
/* Replace align tags with left/right/center/justify */
|
||||
$post = preg_replace( '/\[align=(justify|left|center|right)\](.+?)\[\/align\]/i', '[$1]$2[/$1]', $post );
|
||||
|
||||
@@ -19,6 +19,7 @@ use Exception;
|
||||
use IPS\Application\Module;
|
||||
use IPS\Content\Search\Index;
|
||||
use IPS\convert\Login\HashCryptPrivate;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\core\Ignore;
|
||||
use IPS\Data\Cache;
|
||||
@@ -390,12 +391,13 @@ class Phpbb extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
/* Convert newlines to <br> tags */
|
||||
$post = nl2br( $post );
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace IPS\convert\Software\Core;
|
||||
use Exception;
|
||||
use IPS\Application\Module;
|
||||
use IPS\Content\Search\Index;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\Data\Cache;
|
||||
use IPS\Data\Store;
|
||||
@@ -206,12 +207,13 @@ class Punbb extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
$post = nl2br( $post );
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ use DomainException;
|
||||
use Exception;
|
||||
use IPS\Application\Module;
|
||||
use IPS\Content\Search\Index;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\core\Ignore;
|
||||
use IPS\Data\Cache;
|
||||
@@ -335,12 +336,13 @@ class Smf extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
// Replace BBCode - The table HTML is removed by our parser, but it leaves the posts cleaner than having unparsed bbcode
|
||||
$search = array( '[li]', '[/li]', '[table]', '[/table]', '[tr]', '[/tr]', '[td]', '[/td]', '[tt]', '[/tt]' );
|
||||
|
||||
@@ -16,6 +16,7 @@ use DomainException;
|
||||
use Exception;
|
||||
use IPS\Application\Module;
|
||||
use IPS\Content\Search\Index;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\convert\Software\Exception as SoftwareException;
|
||||
use IPS\core\Ignore;
|
||||
@@ -380,12 +381,13 @@ class UBBthreads extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
$post = preg_replace( "#\[quote=(.+?)\]#i", "[quote name=\"$1\"]", $post );
|
||||
$post = preg_replace( '#\[img:(left|center|right)\](.+?)\[\/img\]#i', '[$1][img]$2[/img][/$1]', $post );
|
||||
|
||||
@@ -14,7 +14,9 @@ namespace IPS\convert\Software\Core;
|
||||
|
||||
use DateTimeZone;
|
||||
use erusev\Parsedown;
|
||||
use IPS\Application;
|
||||
use IPS\Content\Search\Index;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\Data\Cache;
|
||||
use IPS\Data\Store;
|
||||
@@ -45,6 +47,81 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class Vanilla extends Software
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param App $app The application to reference for database and other information.
|
||||
* @param bool $needDB Establish a DB connection
|
||||
* @return void
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct( App $app, bool $needDB=TRUE )
|
||||
{
|
||||
parent::__construct( $app, $needDB );
|
||||
|
||||
/* Check for temp table */
|
||||
if ( $needDB )
|
||||
{
|
||||
if( !Db::i()->checkForTable( 'convert_vanilla_temp' ) )
|
||||
{
|
||||
Db::i()->createTable( array(
|
||||
'name' => 'convert_vanilla_temp',
|
||||
'columns' => array(
|
||||
'id' => array(
|
||||
'name' => 'id',
|
||||
'type' => 'int',
|
||||
'length' => 10,
|
||||
'allow_null' => false,
|
||||
'auto_increment' => true
|
||||
),
|
||||
'media_id' => array(
|
||||
'name' => 'media_id',
|
||||
'type' => 'int',
|
||||
'length' => 10,
|
||||
'allow_null' => false,
|
||||
'auto_increment' => false
|
||||
),
|
||||
'link_type' => array(
|
||||
'name' => 'link_type',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'allow_null' => true,
|
||||
'default' => null
|
||||
),
|
||||
'content_id' => array(
|
||||
'name' => 'content_id',
|
||||
'type' => 'int',
|
||||
'length' => 10,
|
||||
'allow_null' => false,
|
||||
'default' => 0
|
||||
),
|
||||
'post_id' => array(
|
||||
'name' => 'post_id',
|
||||
'type' => 'int',
|
||||
'length' => 10,
|
||||
'allow_null' => false,
|
||||
'default' => 0
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'PRIMARY' => array(
|
||||
'type' => 'primary',
|
||||
'name' => 'PRIMARY',
|
||||
'columns' => array( 'id', 'media_id' ),
|
||||
'length' => array( NULL )
|
||||
),
|
||||
'lookup' => array(
|
||||
'type' => 'index',
|
||||
'name' => 'lookup',
|
||||
'columns' => array( 'media_id', 'link_type' ),
|
||||
'length' => array( NULL, NULL )
|
||||
),
|
||||
)
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Software Name
|
||||
*
|
||||
@@ -198,6 +275,10 @@ class Vanilla extends Software
|
||||
'convertPrivateMessageReplies' => array(
|
||||
'table' => 'ConversationMessage',
|
||||
'where' => NULL,
|
||||
),
|
||||
'convertAttachments' => array(
|
||||
'table' => 'Media',
|
||||
'where' => [ "ForeignTable IN ('embed')" ]
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -316,6 +397,9 @@ class Vanilla extends Software
|
||||
/* Attachments */
|
||||
Task::queue( 'core', 'RebuildAttachmentThumbnails', array( 'app' => $this->app->app_id ), 1, array( 'app' ) );
|
||||
|
||||
/* Remove temp table */
|
||||
Db::i()->dropTable('convert_vanilla_temp');
|
||||
|
||||
return array( "f_search_index_rebuild", "f_clear_caches", "f_rebuild_pms", "f_signatures_rebuild", "f_rebuild_attachments" );
|
||||
}
|
||||
|
||||
@@ -327,12 +411,13 @@ class Vanilla extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
/* Is this Markdown? */
|
||||
if( mb_substr( $post, 0, 15 ) == '<!--Markdown-->' )
|
||||
@@ -347,67 +432,57 @@ class Vanilla extends Software
|
||||
$post = str_replace( '<!--Markdown-->', '', $post );
|
||||
$post = static::$_parseDown->text( $post );
|
||||
}
|
||||
elseif( mb_substr( $post, 0, 3 ) == '[{"' ) // Probably a delta
|
||||
/* If rich, we've already processed it, but need to skip the legacy formatting */
|
||||
elseif( mb_substr( $post, 0, 11 ) == '<!--Rich-->' )
|
||||
{
|
||||
$postData = str_replace( [ "\r\n","\n" ], '', $post );
|
||||
$postData = json_decode( $postData, TRUE );
|
||||
$post = str_replace( '<!--Rich-->', '', $post );
|
||||
}
|
||||
/* Deltas still tha haven't been previously processed */
|
||||
elseif( mb_substr( $post, 0, 3 ) == '[{"' )
|
||||
{
|
||||
$post = static::processQuill( $post, $app );
|
||||
}
|
||||
/* LEGACY CONVERSIONS BELOW FOR OLDER CONTENT */
|
||||
else
|
||||
{
|
||||
/* Mentions */
|
||||
$matches = [];
|
||||
preg_match_all( '/@("|")([^@"]+)("|")/i', $post, $matches );
|
||||
|
||||
$post = '';
|
||||
foreach( $postData as $data )
|
||||
if( count( $matches ) )
|
||||
{
|
||||
if( is_array( $data['insert'] ) )
|
||||
foreach( $matches[0] as $key => $tag )
|
||||
{
|
||||
switch ( $data['insert']['embed-external']['data']['embedType'] )
|
||||
$member = Member::load( $matches[2][ $key ], 'name' );
|
||||
if( !$member->member_id )
|
||||
{
|
||||
case 'quote':
|
||||
$post .= '[quote name="' . $data['insert']['embed-external']['data']['insertUser']['name'] . '" timestamp="' . strtotime( $data['insert']['embed-external']['data']['dateInserted'] ) . '"]' . $data['insert']['embed-external']['data']['body'] . "[/quote]";
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$post .= $data['insert'] . "<br>";
|
||||
|
||||
$post = str_replace( $tag, "[mention={$member->member_id}]{$member->name}[/mention]", $post );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vanilla has a quotes plugin that seems to have changed formats quite often. - We'll try to match as many formats as we can
|
||||
*/
|
||||
$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 );
|
||||
|
||||
$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 );
|
||||
|
||||
$post = preg_replace( '/<blockquote>(.*?)<\/blockquote>/ims',
|
||||
'[quote]$1[/quote]' ,
|
||||
$post );
|
||||
}
|
||||
|
||||
/* Mentions */
|
||||
$matches = [];
|
||||
preg_match_all( '/@("|")([^@"]+)("|")/i', $post, $matches );
|
||||
|
||||
if( count( $matches ) )
|
||||
{
|
||||
foreach( $matches[0] as $key => $tag )
|
||||
{
|
||||
$member = Member::load( $matches[2][ $key ], 'name' );
|
||||
if( !$member->member_id )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$post = str_replace( $tag, "[mention={$member->member_id}]{$member->name}[/mention]", $post );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vanilla has a quotes plugin that seems to have changed formats quite often. - We'll try to match as many formats as we can
|
||||
*/
|
||||
$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 );
|
||||
|
||||
$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 );
|
||||
|
||||
$post = preg_replace( '/<blockquote>(.*?)<\/blockquote>/ims',
|
||||
'[quote]$1[/quote]' ,
|
||||
$post );
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
@@ -458,11 +533,15 @@ class Vanilla extends Software
|
||||
|
||||
$uploadsPath = $this->app->_session['more_info']['convertMembers']['attach_location'];
|
||||
|
||||
$users = $this->fetch( array( 'User', 'u' ), 'u.UserID', array( "u.Deleted<>?", 1 ), 'u.*, ur.RoleID' );
|
||||
$users->join( array( 'UserRole', 'ur' ), 'ur.UserID=u.UserID' );
|
||||
$users = $this->fetch( array( 'User', 'u' ), 'u.UserID', array( "u.Deleted<>?", 1 ) );
|
||||
|
||||
$data = iterator_to_array( $users );
|
||||
$this->app->preCacheLinks( $data, [ 'core_groups' => 'RoleID' ] );
|
||||
$userIds = array_column( $data, 'UserID' );
|
||||
$roles = array_fill_keys( $userIds, [] );
|
||||
foreach( $this->db->select( '*', 'UserRole', [ $this->db->in( 'UserID', $userIds ) ] ) as $role )
|
||||
{
|
||||
$roles[ $role['UserID'] ][] = $role['RoleID'];
|
||||
}
|
||||
|
||||
foreach( $data AS $row )
|
||||
{
|
||||
@@ -482,22 +561,54 @@ class Vanilla extends Software
|
||||
/* Work out banned stuff */
|
||||
$tempBan = ( $row['Banned'] == 1 ) ? -1 : 0;
|
||||
|
||||
/* Get IP */
|
||||
try
|
||||
{
|
||||
$ipAddress = ( $row['InsertIPAddress'] AND !str_contains( $row['InsertIPAddress'], '.' ) ) ? long2ip( hexdec( bin2hex( $row['InsertIPAddress'] ) ) ) : $row['InsertIPAddress'];
|
||||
}
|
||||
catch( \ErrorException $e )
|
||||
{
|
||||
$ipAddress = '';
|
||||
}
|
||||
|
||||
if( !empty( $row['RoleID'] ) )
|
||||
{
|
||||
array_unshift( $roles[ $row['UserID'] ], $row['RoleID'] );
|
||||
}
|
||||
|
||||
$allRoles = array_unique( $roles[ $row['UserID'] ] );
|
||||
$primaryRole = NULL;
|
||||
$secondaryRoles = [];
|
||||
|
||||
/* Find a primary role. */
|
||||
if( count( $allRoles ) )
|
||||
{
|
||||
$primaryRole = array_shift( $allRoles );
|
||||
}
|
||||
|
||||
/* Any remaining roles will be secondary */
|
||||
if( count( $allRoles ) )
|
||||
{
|
||||
$secondaryRoles = $allRoles;
|
||||
}
|
||||
|
||||
/* Array of basic data */
|
||||
$info = array(
|
||||
'member_id' => $row['UserID'],
|
||||
'email' => $row['Email'],
|
||||
'name' => $row['Name'],
|
||||
'password' => $row['Password'],
|
||||
'member_group_id' => $row['RoleID'] ?: NULL,
|
||||
'member_group_id' => $primaryRole,
|
||||
'mgroup_others' => $secondaryRoles,
|
||||
'joined' => static::mysqlToDateTime( $row['DateInserted'] ),
|
||||
'ip_address' => $row['InsertIPAddress'],
|
||||
'ip_address' => $ipAddress,
|
||||
'temp_ban' => $tempBan,
|
||||
'bday_day' => $bdayDay,
|
||||
'bday_month' => $bdayMonth,
|
||||
'bday_year' => $bdayYear,
|
||||
'msg_count_new' => (int) $row['CountUnreadDiscussions'],
|
||||
'msg_count_total' => (int) $row['CountDiscussions'],
|
||||
'last_visit' => static::mysqlToDateTime( $row['DateLastActive'] ),
|
||||
'last_visit' => $row['DateLastActive'] ? static::mysqlToDateTime( $row['DateLastActive'] ) : NULL,
|
||||
'timezone' => !empty( $row['HourOffset'] )
|
||||
? (int) $row['HourOffset']
|
||||
: new DateTimeZone(
|
||||
@@ -570,10 +681,10 @@ class Vanilla extends Software
|
||||
{
|
||||
$maps[ $r['UserID'] ] = array(
|
||||
'map_user_id' => $r['UserID'],
|
||||
'map_read_time' => static::mysqlToDateTime( $r['DateLastViewed'] ),
|
||||
'map_read_time' => $r['DateLastViewed'] ? static::mysqlToDateTime( $r['DateLastViewed'] ) : NULL,
|
||||
//'map_folder_id' => TODO: Create a Bookmarks folder for "Bookmarked" conversations?
|
||||
'map_user_active' => $r['Deleted'] == 0 ? 1 : 0,
|
||||
'map_last_topic_reply' => static::mysqlToDateTime( $r['DateConversationUpdated'] )
|
||||
'map_last_topic_reply' => $r['DateConversationUpdated'] ? static::mysqlToDateTime( $r['DateConversationUpdated'] ) : NULL
|
||||
);
|
||||
}
|
||||
|
||||
@@ -602,6 +713,20 @@ class Vanilla extends Software
|
||||
{
|
||||
$row['Body'] = '<!--Markdown-->' . $row['Body'];
|
||||
}
|
||||
elseif( $row['Format'] == 'Rich' )
|
||||
{
|
||||
$row['Body'] = static::processQuill( $row['Body'], $this->app, 'core_message_posts', $row['ConversationID'], $row['MessageID'] );
|
||||
}
|
||||
|
||||
/* Get IP */
|
||||
try
|
||||
{
|
||||
$ipAddress = ( $row['InsertIPAddress'] AND !str_contains( $row['InsertIPAddress'], '.' ) ) ? long2ip( hexdec( bin2hex( $row['InsertIPAddress'] ) ) ) : $row['InsertIPAddress'];
|
||||
}
|
||||
catch( \ErrorException $e )
|
||||
{
|
||||
$ipAddress = '';
|
||||
}
|
||||
|
||||
$libraryClass->convertPrivateMessageReply( array(
|
||||
'msg_id' => $row['MessageID'],
|
||||
@@ -609,13 +734,106 @@ class Vanilla extends Software
|
||||
'msg_date' => static::mysqlToDateTime( $row['DateInserted'] ),
|
||||
'msg_post' => $row['Body'],
|
||||
'msg_author_id' => $row['InsertUserID'],
|
||||
'msg_ip_address' => $row['InsertIPAddress']
|
||||
'msg_ip_address' => $ipAddress
|
||||
) );
|
||||
|
||||
$libraryClass->setLastKeyValue( $row['MessageID'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief temporarily store post content
|
||||
*/
|
||||
protected array $_postContent = array();
|
||||
|
||||
/**
|
||||
* Convert attachments
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function convertAttachments(): void
|
||||
{
|
||||
$libraryClass = $this->getLibrary();
|
||||
$libraryClass::setKey( 'MediaID' );
|
||||
$this->_postContent = [];
|
||||
|
||||
foreach( $this->fetch( 'Media', 'MediaID', [ $this->db->in( 'ForeignTable', [ 'embed' ] ) ] ) AS $row )
|
||||
{
|
||||
$mediaRows = iterator_to_array(
|
||||
\IPS\Db::i()->select(
|
||||
'*',
|
||||
'convert_vanilla_temp',
|
||||
[ 'media_id=? AND ' . \IPS\Db::i()->in( 'link_type', [ 'core_message_posts' ] ), $row['MediaID'] ]
|
||||
) );
|
||||
|
||||
// Nothing to do, attachment must be in another content type
|
||||
if( !count( $mediaRows ) )
|
||||
{
|
||||
$libraryClass->setLastKeyValue( $row['MediaID'] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$map = [
|
||||
'id1' => $mediaRows[0]['content_id'],
|
||||
'id2' => $mediaRows[0]['post_id']
|
||||
];
|
||||
|
||||
$map['id1_type'] = 'core_message_topics';
|
||||
$map['id2_type'] = 'core_message_posts';
|
||||
$map['location_key'] = 'core_Messaging';
|
||||
$map['id1_from_parent'] = FALSE;
|
||||
$map['id2_from_parent'] = FALSE;
|
||||
$map['id3_skip_link'] = TRUE;
|
||||
|
||||
/* File extension */
|
||||
$ext = explode( '.', $row['Path'] );
|
||||
$ext = array_pop( $ext );
|
||||
|
||||
$info = array(
|
||||
'attach_id' => $row['MediaID'],
|
||||
'attach_file' => $row['Name'],
|
||||
'attach_date' => static::mysqlToDateTime( $row['DateInserted'] ),
|
||||
'attach_member_id' => $row['InsertUserID'],
|
||||
'attach_hits' => 0,
|
||||
'attach_ext' => $ext,
|
||||
'attach_filesize' => $row['Size'],
|
||||
);
|
||||
|
||||
$uploadPath = static::parseMediaLocation( $row['Path'], $this->app->_session['more_info']['convertMembers']['attach_location'] );
|
||||
|
||||
$attachId = $libraryClass->convertAttachment( $info, $map, $uploadPath );
|
||||
|
||||
/* Do some re-jiggery on the post itself to make sure attachment displays */
|
||||
if ( $attachId !== FALSE )
|
||||
{
|
||||
foreach( $mediaRows as $mediaRow )
|
||||
{
|
||||
try
|
||||
{
|
||||
$pid = $this->app->getLink( $map['id2'], 'core_message_posts' );
|
||||
|
||||
if( !isset( $this->_postContent[ $pid ] ) )
|
||||
{
|
||||
$this->_postContent[ $pid ] = Db::i()->select( 'msg_post', 'core_message_posts', array( "msg_id=?", $pid ) )->first();
|
||||
}
|
||||
|
||||
$this->_postContent[ $pid ] = str_replace( "[ATTACH={$row['MediaID']}]", '[attachment=' . $attachId . ':name]', $this->_postContent[ $pid ] );
|
||||
}
|
||||
catch( UnderflowException|OutOfRangeException $e ) {}
|
||||
}
|
||||
}
|
||||
|
||||
unset( $mediaRows );
|
||||
$libraryClass->setLastKeyValue( $row['MediaID'] );
|
||||
}
|
||||
|
||||
/* Do the updates */
|
||||
foreach( $this->_postContent as $pid => $content )
|
||||
{
|
||||
Db::i()->update( 'core_message_posts', array( 'msg_post' => $content ), array( 'msg_id=?', $pid ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a login
|
||||
*
|
||||
@@ -642,4 +860,38 @@ class Vanilla extends Software
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
public static function processQuill( string $postJson, ?App $app=null, ?string $type=null, ?int $contentId=null, ?int $commentId=null ): string
|
||||
{
|
||||
/* Need to wrap so it's an expected format */
|
||||
$post = '{"ops":' . $postJson . '}';
|
||||
|
||||
if ( strpos( $post, '"header":' ) !== false )
|
||||
{
|
||||
$post = preg_replace('/{"header":{"level":(\d+),"ref":"([^"]*)"}}/', '{"header":$1}', $post );
|
||||
}
|
||||
|
||||
$data = json_decode( $post, true );
|
||||
|
||||
if ( !isset( $data['ops'][ count( $data['ops'] ) - 1 ]['insert'] ) OR is_array( $data['ops'][ count( $data['ops'] ) - 1 ]['insert'] ) OR \substr( $data['ops'][ count( $data['ops'] ) - 1 ]['insert'], -1 ) !== "\n" )
|
||||
{
|
||||
$data['ops'][] = [ "insert" => "\n" ];
|
||||
}
|
||||
|
||||
/* Get the autoloader for the Quill library */
|
||||
require_once Application::load( 'convert' )->getApplicationPath() . '/sources/Tools/Quill/vendor/autoload.php';
|
||||
|
||||
$lexer = new \nadar\quill\Lexer( $data );
|
||||
$lexer->registerListener( new \IPS\convert\Tools\Vanilla\QuillListeners\Emoji() );
|
||||
$lexer->registerListener( new \IPS\convert\Tools\Vanilla\QuillListeners\EmbedExternal( $type, $contentId, $commentId ) );
|
||||
$lexer->registerListener( new \IPS\convert\Tools\Vanilla\QuillListeners\Mention( $app ) );
|
||||
try
|
||||
{
|
||||
return '<!--Rich-->' . $lexer->render();
|
||||
}
|
||||
catch( \ErrorException $e )
|
||||
{
|
||||
return $postJson;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -618,12 +618,13 @@ class Vbulletin extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
if( preg_match( "#\[sigpic]#i", $post ) )
|
||||
{
|
||||
|
||||
@@ -553,12 +553,13 @@ class Vbulletin5 extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
/* IMG2 BBcode - Needs to be ran before htmlspecialchars due to it containing JSON */
|
||||
$matches = array();
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace IPS\convert\Software\Core;
|
||||
|
||||
use DomainException;
|
||||
use IPS\Content\Search\Index;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Exception;
|
||||
use IPS\convert\Software;
|
||||
use IPS\Data\Cache;
|
||||
@@ -360,12 +361,13 @@ class Woltlab extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return $post;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace IPS\convert\Software\Core;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Content\Search\Index;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\Data\Cache;
|
||||
use IPS\Data\Store;
|
||||
@@ -110,12 +111,13 @@ class Wordpress extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return nl2br( $post );
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use DomainException;
|
||||
use Exception;
|
||||
use IPS\Application\Module;
|
||||
use IPS\Content\Search\Index;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\Data\Cache;
|
||||
use IPS\Data\Store;
|
||||
@@ -139,12 +140,13 @@ class Wpforo extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
/* Mentions */
|
||||
$matches = [];
|
||||
|
||||
@@ -563,12 +563,13 @@ class Xenforo extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
// run everything through htmlspecialchars to prevent XSS
|
||||
$post = htmlspecialchars( $post, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE );
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace IPS\convert\Software\Forums;
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use IPS\Content;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\convert\Software\Core\Expressionengine as ExpressionEngineCore;
|
||||
use IPS\Db;
|
||||
@@ -197,14 +198,15 @@ class Expressionengine extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return ExpressionEngineCore::fixPostData( $post, $className, $contentId );
|
||||
return ExpressionEngineCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,7 +217,6 @@ class Expressionengine extends Software
|
||||
public function convertForumsForums() : void
|
||||
{
|
||||
$libraryClass = $this->getLibrary();
|
||||
|
||||
$libraryClass::setKey( 'forum_id' );
|
||||
|
||||
foreach( $this->fetch( 'exp_forums', 'forum_id' ) AS $row )
|
||||
@@ -226,12 +227,8 @@ class Expressionengine extends Software
|
||||
'description' => $row['forum_description'],
|
||||
'topics' => $row['forum_total_topics'],
|
||||
'posts' => $row['forum_total_posts'],
|
||||
'last_post' => $row['forum_last_post_date'],
|
||||
'last_poster_id' => $row['forum_last_post_author_id'],
|
||||
'last_poster_name' => $row['forum_last_post_author'],
|
||||
'parent_id' => ( $row['forum_parent'] == 0 ) ? -1 : $row['forum_parent'],
|
||||
'position' => $row['forum_order'],
|
||||
'last_title' => $row['forum_last_post_title'],
|
||||
'allow_poll' => 1,
|
||||
'inc_postcount' => 1,
|
||||
'sub_can_post' => 1,
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace IPS\convert\Software\Forums;
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use IPS\Content;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\convert\Software\Core\Mybb as MybbCore;
|
||||
use IPS\Db;
|
||||
@@ -209,14 +210,15 @@ class Mybb extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return MybbCore::fixPostData( $post, $className, $contentId );
|
||||
return MybbCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,7 +229,6 @@ class Mybb extends Software
|
||||
public function convertForumsForums(): void
|
||||
{
|
||||
$libraryClass = $this->getLibrary();
|
||||
|
||||
$libraryClass::setKey( 'fid' );
|
||||
|
||||
foreach( $this->fetch( 'forums', 'fid' ) AS $row )
|
||||
@@ -238,13 +239,9 @@ class Mybb extends Software
|
||||
'description' => $row['description'],
|
||||
'topics' => $row['threads'],
|
||||
'posts' => $row['posts'],
|
||||
'last_post' => $row['lastpost'],
|
||||
'last_poster_id' => $row['lastposteruid'],
|
||||
'last_poster_name' => $row['lastposter'],
|
||||
'parent_id' => $row['pid'] ?: -1,
|
||||
'position' => $row['disporder'],
|
||||
'password' => $row['password'] ?: NULL,
|
||||
'last_title' => $row['lastpostsubject'],
|
||||
'inc_postcount' => $row['usepostcounts'],
|
||||
'redirect_url' => $row['linkto'],
|
||||
'sub_can_post' => ( $row['type'] == 'c' ) ? 0 : 1,
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace IPS\convert\Software\Forums;
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use IPS\Content;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\convert\Software\Core\Phpbb as PhpbbCore;
|
||||
use IPS\Db;
|
||||
@@ -192,14 +193,15 @@ class Phpbb extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return PhpbbCore::fixPostData( $post, $className, $contentId );
|
||||
return PhpbbCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,7 +212,6 @@ class Phpbb extends Software
|
||||
public function convertForumsForums(): void
|
||||
{
|
||||
$libraryClass = $this->getLibrary();
|
||||
|
||||
$libraryClass::setKey( 'forum_id' );
|
||||
|
||||
foreach( $this->fetch( 'forums', 'forum_id' ) AS $row )
|
||||
@@ -221,14 +222,10 @@ class Phpbb extends Software
|
||||
'description' => $row['forum_desc'],
|
||||
'topics' => $row['forum_topics_approved'] ?? $row['forum_topics'],
|
||||
'posts' => $row['forum_posts_approved'] ?? $row['forum_posts'],
|
||||
'last_post' => $row['forum_last_post_time'],
|
||||
'last_poster_id' => $row['forum_last_poster_id'],
|
||||
'last_poster_name' => $row['forum_last_poster_name'],
|
||||
'parent_id' => ( $row['parent_id'] != 0 ) ? $row['parent_id'] : -1,
|
||||
'conv_parent' => ( $row['parent_id'] != 0 ) ? $row['parent_id'] : -1,
|
||||
'position' => $row['left_id'],
|
||||
'password' => ( isset( $this->app->_session['more_info']['convertForumsForums']["forum_password_{$row['forum_id']}"] ) ) ? $this->app->_session['more_info']['convertForumsForums']["forum_password_{$row['forum_id']}"] : NULL,
|
||||
'last_title' => $row['forum_last_post_subject'],
|
||||
'queued_topics' => $row['forum_topics_unapproved'] ?? 0,
|
||||
'queued_posts' => isset( $row['forum_posts_unapproved'] ) ? $row['forum_topics_unapproved'] : 0,
|
||||
'sub_can_post' => $row['forum_type'] == 1
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace IPS\convert\Software\Forums;
|
||||
|
||||
use Exception;
|
||||
use IPS\Content;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\convert\Software\Core\Punbb as PunbbCore;
|
||||
use IPS\Http\Url;
|
||||
@@ -122,14 +123,15 @@ class Punbb extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return PunbbCore::fixPostData( $post, $className, $contentId );
|
||||
return PunbbCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,7 +142,6 @@ class Punbb extends Software
|
||||
public function convertForumsForums(): void
|
||||
{
|
||||
$libraryClass = $this->getLibrary();
|
||||
|
||||
$libraryClass::setKey( 'id' );
|
||||
|
||||
foreach( $this->fetch( 'forums', 'id' ) AS $row )
|
||||
@@ -166,24 +167,12 @@ class Punbb extends Software
|
||||
catch( UnderflowException $e ) {}
|
||||
}
|
||||
|
||||
/* They don't store the last poster ID. Makes me sad. */
|
||||
$last_poster_id = 0;
|
||||
try
|
||||
{
|
||||
/* Better hope they haven't changed their name */
|
||||
$last_poster_id = $this->db->select( 'id', 'users', array( "username=?", $row['last_poster'] ) )->first();
|
||||
}
|
||||
catch( UnderflowException $e ) {}
|
||||
|
||||
$info = array(
|
||||
'id' => $row['id'],
|
||||
'name' => $row['forum_name'],
|
||||
'description' => $row['forum_desc'],
|
||||
'topics' => $row['num_topics'],
|
||||
'posts' => $row['num_posts'],
|
||||
'last_post' => $row['last_post'],
|
||||
'last_poster_id' => $last_poster_id,
|
||||
'last_poster_name' => $row['last_poster'],
|
||||
'parent_id' => ( $row['parent_forum_id'] == 0 ) ? '1000' . $row['cat_id'] : $row['parent_forum_id'],
|
||||
'position' => $row['disp_position'],
|
||||
'redirect_url' => $row['redirect_url'],
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace IPS\convert\Software\Forums;
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use IPS\Content;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\convert\Software\Core\Smf as SmfCore;
|
||||
use IPS\Db;
|
||||
@@ -228,14 +229,15 @@ class Smf extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return SmfCore::fixPostData( $post, $className, $contentId );
|
||||
return SmfCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,39 +269,18 @@ class Smf extends Software
|
||||
public function convertForumsBoards(): void
|
||||
{
|
||||
$libraryClass = $this->getLibrary();
|
||||
|
||||
$libraryClass::setKey( 'id_board' );
|
||||
|
||||
foreach( $this->fetch( 'boards', 'id_board' ) AS $row )
|
||||
{
|
||||
$last_post_time = 0;
|
||||
$last_poster = 0;
|
||||
$last_title = NULL;
|
||||
$last_poster_name = NULL;
|
||||
|
||||
try
|
||||
{
|
||||
$last_post = $this->db->select( '*', 'messages', array( "id_msg=?", $row['id_last_msg'] ) )->first();
|
||||
|
||||
$last_post_time = $last_post['poster_time'];
|
||||
$last_poster = $last_post['id_member'];
|
||||
$last_poster_name = $last_post['poster_name'];
|
||||
$last_title = $last_post['subject']; # Yes, I know this is technically wrong, but it's better than just not showing anything
|
||||
}
|
||||
catch( UnderflowException $e ) {}
|
||||
|
||||
$info = array(
|
||||
'id' => $row['id_board'],
|
||||
'name' => $row['name'],
|
||||
'description' => $row['description'],
|
||||
'topics' => $row['num_topics'],
|
||||
'posts' => $row['num_posts'],
|
||||
'last_post' => $last_post_time,
|
||||
'last_poster_id' => $last_poster,
|
||||
'last_poster_name' => $last_poster_name,
|
||||
'parent_id' => ( $row['id_parent'] <> 0 ) ? $row['id_parent'] : 'c' . $row['id_cat'],
|
||||
'position' => $row['board_order'],
|
||||
'last_title' => $last_title,
|
||||
'redirect_url' => $row['redirect'],
|
||||
'queued_topics' => $row['unapproved_topics'],
|
||||
'queued_posts' => $row['unapproved_posts'],
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace IPS\convert\Software\Forums;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use Exception;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\convert\Software\Core\UBBthreads as UBBthreadsCore;
|
||||
use IPS\convert\Software\Exception as SoftwareException;
|
||||
@@ -246,14 +247,15 @@ class UBBthreads extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return UBBthreadsCore::fixPostData( $post, $className, $contentId );
|
||||
return UBBthreadsCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,12 +292,8 @@ class UBBthreads extends Software
|
||||
'description' => $row['FORUM_DESCRIPTION'],
|
||||
'topics' => $row['FORUM_TOPICS'],
|
||||
'posts' => $row['FORUM_POSTS'],
|
||||
'last_post' => DateTime::create()->setTimestamp( $row['FORUM_LAST_POST_TIME'] ),
|
||||
'last_poster_id' => $row['FORUM_LAST_POSTER_ID'],
|
||||
'last_poster_name' => $row['FORUM_LAST_POSTER_NAME'],
|
||||
'parent_id' => $row['FORUM_PARENT_ID'] ?: ( 10000 + $row['CATEGORY_ID'] ),
|
||||
'position' => $row['FORUM_SORT_ORDER'],
|
||||
'last_title' => $row['FORUM_LAST_POST_SUBJECT'],
|
||||
'allow_poll' => $row['FORUM_ALLOW_POLLS']
|
||||
) );
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ class Vanilla extends Software
|
||||
),
|
||||
'convertAttachments' => array(
|
||||
'table' => 'Media',
|
||||
'where' => array( 'ForeignTable=? OR ForeignTable=?', 'discussion', 'comment' )
|
||||
'where' => [ "ForeignTable IN ('discussion', 'comment', 'embed')" ]
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -295,6 +295,7 @@ class Vanilla extends Software
|
||||
{
|
||||
/* Content Rebuilds */
|
||||
Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\forums\Forum', 'count' => 0 ), 4, array( 'class' ) );
|
||||
Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'forums_posts_first', 'class' => 'IPS\forums\Topic\Post' ), 2, array( 'app', 'link', 'class' ) );
|
||||
Task::queue( 'convert', 'RebuildContent', array( 'app' => $this->app->app_id, 'link' => 'forums_posts', 'class' => 'IPS\forums\Topic\Post' ), 2, array( 'app', 'link', 'class' ) );
|
||||
Task::queue( 'core', 'RebuildItemCounts', array( 'class' => 'IPS\forums\Topic' ), 3, array( 'class' ) );
|
||||
Task::queue( 'convert', 'RebuildFirstPostIds', array( 'app' => $this->app->app_id ), 2, array( 'app' ) );
|
||||
@@ -313,16 +314,22 @@ class Vanilla extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return VanillaCore::fixPostData( $post, $className, $contentId );
|
||||
return VanillaCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief temporarily store post content
|
||||
*/
|
||||
protected array $_postContent = array();
|
||||
|
||||
/**
|
||||
* Convert attachments
|
||||
*
|
||||
@@ -331,18 +338,41 @@ class Vanilla extends Software
|
||||
public function convertAttachments(): void
|
||||
{
|
||||
$libraryClass = $this->getLibrary();
|
||||
|
||||
$libraryClass::setKey( 'MediaID' );
|
||||
$this->_postContent = [];
|
||||
|
||||
foreach( $this->fetch( 'Media', 'MediaID', array( 'ForeignTable=? OR ForeignTable=?', 'discussion', 'comment' ) ) AS $row )
|
||||
foreach( $this->fetch( 'Media', 'MediaID', [ $this->db->in( 'ForeignTable', [ 'discussion', 'comment', 'embed' ] ) ] ) AS $row )
|
||||
{
|
||||
if( $row['ForeignTable'] == 'discussion' )
|
||||
{
|
||||
$map = array(
|
||||
'id1' => 'fp-' . $row['ForeignID'],
|
||||
'id1' => $row['ForeignID'],
|
||||
'id2' => $row['ForeignID'],
|
||||
'first_post' => true
|
||||
);
|
||||
}
|
||||
elseif( $row['ForeignTable'] == 'embed' )
|
||||
{
|
||||
$mediaRows = iterator_to_array(
|
||||
\IPS\Db::i()->select(
|
||||
'*',
|
||||
'convert_vanilla_temp',
|
||||
[ 'media_id=? AND ' . \IPS\Db::i()->in( 'link_type', [ 'forums_posts', 'forums_posts_first'] ), $row['MediaID'] ]
|
||||
) );
|
||||
|
||||
// Nothing to do, attachment must be in another content type
|
||||
if( !count( $mediaRows ) )
|
||||
{
|
||||
$libraryClass->setLastKeyValue( $row['MediaID'] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$map = [
|
||||
'id1' => $mediaRows[0]['content_id'],
|
||||
'id2' => $mediaRows[0]['post_id'],
|
||||
'first_post' => ( $mediaRows[0]['link_type'] == 'forums_posts_first' ) ? true : false
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
@@ -377,9 +407,38 @@ class Vanilla extends Software
|
||||
|
||||
$uploadPath = VanillaCore::parseMediaLocation( $row['Path'], $this->app->_session['more_info']['convertAttachments']['attach_location'] );
|
||||
|
||||
$libraryClass->convertAttachment( $info, $map, $uploadPath );
|
||||
$attachId = $libraryClass->convertAttachment( $info, $map, $uploadPath );
|
||||
|
||||
/* Do some re-jiggery on the post itself to make sure attachment displays */
|
||||
if ( $attachId !== FALSE and isset( $mediaRows ) )
|
||||
{
|
||||
foreach( $mediaRows as $mediaRow )
|
||||
{
|
||||
try
|
||||
{
|
||||
$pid = $this->app->getLink( $map['id2'], ( isset( $map['first_post'] ) AND $map['first_post'] === TRUE ) ? 'forums_posts_first' : 'forums_posts' );
|
||||
|
||||
if( !isset( $this->_postContent[ $pid ] ) )
|
||||
{
|
||||
$this->_postContent[ $pid ] = Db::i()->select( 'post', 'forums_posts', array( "pid=?", $pid ) )->first();
|
||||
}
|
||||
|
||||
$this->_postContent[ $pid ] = str_replace( "[ATTACH={$row['MediaID']}]", '[attachment=' . $attachId . ':name]', $this->_postContent[ $pid ] );
|
||||
// dump($this->_postContent[ $pid ]);
|
||||
}
|
||||
catch( UnderflowException|OutOfRangeException $e ) {}
|
||||
}
|
||||
}
|
||||
|
||||
unset( $mediaRows );
|
||||
$libraryClass->setLastKeyValue( $row['MediaID'] );
|
||||
}
|
||||
|
||||
/* Do the updates */
|
||||
foreach( $this->_postContent as $pid => $content )
|
||||
{
|
||||
Db::i()->update( 'forums_posts', array( 'post' => $content ), array( 'pid=?', $pid ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,11 +453,7 @@ class Vanilla extends Software
|
||||
|
||||
$uploadsPath = $this->app->_session['more_info']['convertForumsForums']['attach_location'];
|
||||
|
||||
$forums = $this->fetch( array( 'Category', 'c' ), 'CategoryID', array( 'c.CategoryID<>?', -1 ),
|
||||
'c.*, lcu.UserID as LastCommentUserID, lcu.Name as LastCommentUserName, ld.Name as LastDiscussionName'
|
||||
);
|
||||
$forums->join( array( 'User', 'lcu' ), 'c.LastCommentID=lcu.UserID' );
|
||||
$forums->join( array( 'Discussion', 'ld' ), 'c.LastDiscussionID=ld.DiscussionID' );
|
||||
$forums = $this->fetch( array( 'Category', 'c' ), 'CategoryID', array( 'c.CategoryID<>?', -1 ) );
|
||||
|
||||
foreach( $forums AS $row )
|
||||
{
|
||||
@@ -409,12 +464,8 @@ class Vanilla extends Software
|
||||
'description' => $row['Description'],
|
||||
'topics' => $row['CountDiscussions'],
|
||||
'posts' => $row['CountComments'],
|
||||
'last_post' => VanillaCore::mysqlToDateTime( $row['LastDateInserted'] ),
|
||||
'last_poster_id' => $row['LastCommentID'],
|
||||
'last_poster_name' => $row['LastCommentUserName'],
|
||||
'parent_id' => ( (int) $row['ParentCategoryID'] > 0 ) ? $row['ParentCategoryID'] : NULL,
|
||||
'position' => $row['Sort'],
|
||||
'last_title' => $row['LastDiscussionName'],
|
||||
'icon' => $icon,
|
||||
'sub_can_post' => $row['AllowDiscussions'] ?: 0
|
||||
];
|
||||
@@ -584,17 +635,31 @@ class Vanilla extends Software
|
||||
{
|
||||
$row['Body'] = '<!--Markdown-->' . $row['Body'];
|
||||
}
|
||||
elseif( $row['Format'] == 'Rich' )
|
||||
{
|
||||
$row['Body'] = VanillaCore::processQuill( $row['Body'], $this->app, 'forums_posts_first', $row['DiscussionID'], $row['DiscussionID'] );
|
||||
}
|
||||
|
||||
/* Get IP */
|
||||
try
|
||||
{
|
||||
$ipAddress = ( $row['InsertIPAddress'] AND !str_contains( $row['InsertIPAddress'], '.' ) ) ? long2ip( hexdec( bin2hex( $row['InsertIPAddress'] ) ) ) : $row['InsertIPAddress'];
|
||||
}
|
||||
catch( \ErrorException $e )
|
||||
{
|
||||
$ipAddress = '';
|
||||
}
|
||||
|
||||
// First post
|
||||
$info = array(
|
||||
'pid' => 'fp-' . $row['DiscussionID'],
|
||||
'pid' => $row['DiscussionID'],
|
||||
'topic_id' => $row['DiscussionID'],
|
||||
'post' => $row['Body'],
|
||||
'new_topic' => 1,
|
||||
'edit_time' => ( $editName === NULL ) ? NULL : VanillaCore::mysqlToDateTime( $row['DateUpdated'] ),
|
||||
'edit_name' => $editName,
|
||||
'author_id' => $row['InsertUserID'],
|
||||
'ip_address' => $row['InsertIPAddress'],
|
||||
'ip_address' => $ipAddress,
|
||||
'post_date' => VanillaCore::mysqlToDateTime( $row['DateInserted'] ),
|
||||
);
|
||||
|
||||
@@ -675,6 +740,20 @@ class Vanilla extends Software
|
||||
{
|
||||
$row['Body'] = '<!--Markdown-->' . $row['Body'];
|
||||
}
|
||||
elseif( $row['Format'] == 'Rich' )
|
||||
{
|
||||
$row['Body'] = VanillaCore::processQuill( $row['Body'], $this->app, 'forums_posts', $row['DiscussionID'], $row['CommentID'] );
|
||||
}
|
||||
|
||||
/* Get IP */
|
||||
try
|
||||
{
|
||||
$ipAddress = ( $row['InsertIPAddress'] AND !str_contains( $row['InsertIPAddress'], '.' ) ) ? long2ip( hexdec( bin2hex( $row['InsertIPAddress'] ) ) ) : $row['InsertIPAddress'];
|
||||
}
|
||||
catch( \ErrorException $e )
|
||||
{
|
||||
$ipAddress = '';
|
||||
}
|
||||
|
||||
$info = [
|
||||
'pid' => $row['CommentID'],
|
||||
@@ -683,7 +762,7 @@ class Vanilla extends Software
|
||||
'edit_time' => ( $editName === NULL ) ? NULL : VanillaCore::mysqlToDateTime( $row['DateUpdated'] ),
|
||||
'edit_name' => $editName,
|
||||
'author_id' => $row['InsertUserID'],
|
||||
'ip_address' => $row['InsertIPAddress'],
|
||||
'ip_address' => $ipAddress,
|
||||
'post_date' => VanillaCore::mysqlToDateTime( $row['DateInserted'] ),
|
||||
];
|
||||
|
||||
|
||||
@@ -238,14 +238,15 @@ class Vbulletin extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return VbulletinCore::fixPostData( $post, $className, $contentId );
|
||||
return VbulletinCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,13 +370,9 @@ class Vbulletin extends Software
|
||||
'description' => $forum['description'],
|
||||
'topics' => $forum['threadcount'],
|
||||
'posts' => $forum['replycount'],
|
||||
'last_post' => $forum['lastpost'],
|
||||
'last_poster_id' => ( static::$isLegacy === FALSE or is_null( static::$isLegacy ) ) ? $forum['lastposterid'] : 0,
|
||||
'last_poster_name' => $forum['lastposter'],
|
||||
'parent_id' => $forum['parentid'],
|
||||
'position' => $forum['displayorder'],
|
||||
'password' => $forum['password'] ?: NULL,
|
||||
'last_title' => $forum['lastthread'],
|
||||
'preview_posts' => $checkpermission( 'forumoptions', 'moderatenewpost' ),
|
||||
'inc_postcount' => $checkpermission( 'forumoptions', 'countposts' ),
|
||||
'redirect_url' => $forum['link'],
|
||||
|
||||
@@ -208,14 +208,15 @@ class Vbulletin5 extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return Vbulletin5Core::fixPostData( $post, $className, $contentId );
|
||||
return Vbulletin5Core::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,7 +296,6 @@ class Vbulletin5 extends Software
|
||||
public function convertForumsForums() : void
|
||||
{
|
||||
$libraryClass = $this->getLibrary();
|
||||
|
||||
$libraryClass::setKey( 'node.nodeid' );
|
||||
$clubs = $this->fetchClubs();
|
||||
|
||||
@@ -334,9 +334,6 @@ class Vbulletin5 extends Software
|
||||
'description' => $forum['description'],
|
||||
'topics' => $forum['textcount'],
|
||||
'posts' => $forum['totalcount'],
|
||||
'last_post' => $forum['lastcontent'],
|
||||
'last_poster_id' => $forum['lastauthorid'],
|
||||
'last_poster_name' => $forum['lastcontentauthor'],
|
||||
'parent_id' => ( !array_key_exists( $forum['nodeid'], $clubs ) ) ? $forum['parentid'] : -1,
|
||||
'club_id' => ( array_key_exists( $forum['nodeid'], $clubs ) ) ? $forum['nodeid'] : NULL,
|
||||
'position' => $forum['displayorder'],
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace IPS\convert\Software\Forums;
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use IPS\Content;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\convert\Software\Core\Woltlab as WoltlabCore;
|
||||
use IPS\Db;
|
||||
@@ -175,14 +176,15 @@ class Woltlab extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return WoltlabCore::fixPostData( $post, $className, $contentId );
|
||||
return WoltlabCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace IPS\convert\Software\Forums;
|
||||
|
||||
use Exception;
|
||||
use IPS\Content;
|
||||
use IPS\convert\App;
|
||||
use IPS\convert\Software;
|
||||
use IPS\convert\Software\Core\Wpforo as WpforoCore;
|
||||
use IPS\Db;
|
||||
@@ -138,14 +139,15 @@ class Wpforo extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return WpforoCore::fixPostData( $post, $className, $contentId );
|
||||
return WpforoCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -273,14 +273,15 @@ class Xenforo extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post The post
|
||||
* @param string|null $className Content Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId Content ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return XenforoCore::fixPostData( $post, $className, $contentId );
|
||||
return XenforoCore::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,12 +339,8 @@ class Xenforo extends Software
|
||||
'description' => $row['description'],
|
||||
'topics' => ( isset( $data['discussion_count'] ) ) ? $data['discussion_count'] : 0,
|
||||
'posts' => ( isset( $data['message_count'] ) ) ? $data['message_count'] : 0,
|
||||
'last_post' => ( isset( $data['last_post_date'] ) ) ? $data['last_post_date'] : 0,
|
||||
'last_poster_id' => ( isset( $data['last_post_user_id'] ) ) ? $data['last_post_user_id'] : 0,
|
||||
'last_poster_name' => ( isset( $data['last_poster_name'] ) ) ? $data['last_post_username'] : '',
|
||||
'parent_id' => ( $row['parent_node_id'] == 0 ) ? -1 : $row['parent_node_id'],
|
||||
'position' => $row['display_order'],
|
||||
'last_title' => ( isset( $data['last_thread_title'] ) ) ? $data['last_thread_title'] : '',
|
||||
'allow_poll' => ( isset( $data['allow_poll'] ) ) ? $data['allow_poll'] : 0,
|
||||
'inc_postcount' => ( isset( $data['count_messages'] ) ) ? $data['count_messages'] : 0,
|
||||
'redirect_url' => ( isset( $data['link_url'] ) ) ? $data['link_url'] : NULL,
|
||||
|
||||
@@ -192,14 +192,15 @@ class Vbulletin extends Software
|
||||
/**
|
||||
* Pre-process content for the Invision Community text parser
|
||||
*
|
||||
* @param string $post post
|
||||
* @param string|null $className Classname passed by post-conversion rebuild
|
||||
* @param int|null $contentId ID passed by post-conversion rebuild
|
||||
* @param string The post
|
||||
* @param string|null Content Classname passed by post-conversion rebuild
|
||||
* @param int|null Content ID passed by post-conversion rebuild
|
||||
* @param App|null App object if available
|
||||
* @return string The converted post
|
||||
*/
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null ): string
|
||||
public static function fixPostData( string $post, ?string $className=null, ?int $contentId=null, ?App $app=null ): string
|
||||
{
|
||||
return VBulletinSoftware::fixPostData( $post, $className, $contentId );
|
||||
return VBulletinSoftware::fixPostData( $post, $className, $contentId, $app );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 3.4.2 (5. April 2024)
|
||||
|
||||
+ [#87](https://github.com/nadar/quill-delta-parser/issues/87) Fixed a bug where a line break preceding a list containing inline attributes results in improper HTML formatting for next paragraphs.
|
||||
|
||||
## 3.4.1 (13. March 2024)
|
||||
|
||||
+ [#84](https://github.com/nadar/quill-delta-parser/issues/84) Allow align `left` as possible value.
|
||||
|
||||
## 3.4.0 (14. September 2023)
|
||||
|
||||
> Please be aware that this release may impact the way Quill data is displayed in your frontend. Checkout the [upgrade document](UPGRADE.md) for more details.
|
||||
|
||||
+ [#80](https://github.com/nadar/quill-delta-parser/issues/80) Add missing background color listener (In this update, we have introduced a change where background color information from Quill JSON is now extracted and applied as the background color. This change may result in unexpected behavior if you had background color information present in your documents but it was not rendered correctly in previous versions. see [upgrade document](UPGRADE.md)
|
||||
|
||||
## 3.3.1 (24. March 2023)
|
||||
|
||||
+ [#78](https://github.com/nadar/quill-delta-parser/issues/78) Fixed a bug where lists with empty contents would break all output.
|
||||
|
||||
## 3.3.0 (10. March 2023)
|
||||
|
||||
+ [#77](https://github.com/nadar/quill-delta-parser/pull/77) Allow method chaining for `registerListener()` and `overwriteListener()`.
|
||||
+ [#76](https://github.com/nadar/quill-delta-parser/pull/76) Addded php 8.2 for unit tests
|
||||
|
||||
## 3.2.1 (1. November 2022)
|
||||
|
||||
+ [#74](https://github.com/nadar/quill-delta-parser/issues/74) Fixed missing support for nested lists
|
||||
+ [#73](https://github.com/nadar/quill-delta-parser/pull/73) More automation and testing with rector and auto commit on PR's for csfixer and rector.
|
||||
|
||||
## 3.2.0 (7. August 2022)
|
||||
|
||||
+ [#71](https://github.com/nadar/quill-delta-parser/pull/71) Added image width and height attributes to the image tag if available.
|
||||
+ [#72](https://github.com/nadar/quill-delta-parser/pull/72) Raised from phpstan level 5 to 6
|
||||
|
||||
## 3.1.0 (24. July 2022)
|
||||
|
||||
+ [#68](https://github.com/nadar/quill-delta-parser/pull/68) Deprecated the magical getter `$pick->$name` in `nadar\quill\Pick` class, use `$pick->optionValue($name)` instead.
|
||||
+ [#64](https://github.com/nadar/quill-delta-parser/pull/64) Replaced deprecated public $input property in `Line` with getter and setter methods `getLine()` and `setLine($input)`.
|
||||
+ [#63](https://github.com/nadar/quill-delta-parser/pull/63/) Removed deprecated methods `loadBuiltinListeneres()` and `renderListeneres()`.
|
||||
+ [#62](https://github.com/nadar/quill-delta-parser/pull/62/) Prettify the html output a little by adding newlines after every block level element. If you have custom listeners which call `BlockListener->wrapElement()` this will be added for your custom listeners as well, otherwise you'll need to add the newlines yourself.
|
||||
|
||||
## 3.0.0 (2. June 2022)
|
||||
|
||||
> This release contains breaks which might affect your application. Checkout the [upgrade document](UPGRADE.md) for more details.
|
||||
|
||||
+ [#60](https://github.com/nadar/quill-delta-parser/pull/60) Changed the behavior of `nadar\quill\listener\Link`. Property `$wrapper` has been removed and replaced by `$wrapperOpen`, `$wrapperMiddle` and `$wrapperClose`.
|
||||
+ [#61](https://github.com/nadar/quill-delta-parser/pull/61) Official dropped PHP 7.1 support added PHP 8.1 instead.
|
||||
|
||||
## 2.9.0 (5. April 2022)
|
||||
|
||||
+ [#58](https://github.com/nadar/quill-delta-parser/pull/58/files) Renamed misspelled method `loadBuiltinListeneres()` to `loadBuiltinListeners()` and `renderListeneres()` to `renderListeners()`. The old methods are still available for backwards compatibility, but deprecated and will be removed in 3.0.
|
||||
|
||||
## 2.8.0 (8. March 2022)
|
||||
|
||||
+ [#56](https://github.com/nadar/quill-delta-parser/pull/56) Provide new method to override existing listeners with `overwriteListener()`.
|
||||
|
||||
## 2.7.2 (27. October 2021)
|
||||
|
||||
+ [#53](https://github.com/nadar/quill-delta-parser/pull/53) Lists listener, check for type being an array for compatibility with [Vanilla Forums](https://vanillaforums.com/).
|
||||
|
||||
## 2.7.1 (5. October 2021)
|
||||
|
||||
+ [#51](https://github.com/nadar/quill-delta-parser/issues/51) Fixed issue where images wrapped in italics renders as text instead of showing the image.
|
||||
|
||||
## 2.7.0 (7. June 2021)
|
||||
|
||||
+ [#49](https://github.com/nadar/quill-delta-parser/issues/49) Added Code Block Listener, generates `<pre><code>...</code></pre>` enclosed output.
|
||||
|
||||
## 2.6.1 (1. June 2021)
|
||||
|
||||
+ [#48](https://github.com/nadar/quill-delta-parser/pull/48) PHP 8 compatibility.
|
||||
|
||||
## 2.6.0 (2. December 2020)
|
||||
|
||||
+ [#42](https://github.com/nadar/quill-delta-parser/pull/42) Added PHP 8 Support.
|
||||
+ [#43](https://github.com/nadar/quill-delta-parser/pull/42) Moved CI from Travis to GitHub Actions. Added PHP 8 version in Test Scenario.
|
||||
|
||||
## 2.5.0 (29. June 2020)
|
||||
|
||||
+ [#41](https://github.com/nadar/quill-delta-parser/pull/41) Add option to configure Embed Video `allow` option.
|
||||
|
||||
## 2.4.0 (2. November 2019)
|
||||
|
||||
+ [#32](https://github.com/nadar/quill-delta-parser/pull/32) Added new `wrapElement` method to simplify building block listeners.
|
||||
+ [#30](https://github.com/nadar/quill-delta-parser/issues/30) List opening tag process has been simplified in order to support single bullet lists.
|
||||
+ [#31](https://github.com/nadar/quill-delta-parser/pull/31) Add support for align attribute.
|
||||
|
||||
## 2.3.0 (23. October 2019)
|
||||
|
||||
+ Add override functionality to links, allowing to customise their wrapper.
|
||||
|
||||
## 2.2.0 (17. October 2019)
|
||||
|
||||
+ Add inline element to handle script attribute.
|
||||
|
||||
## 2.1.1 (2. September 2019)
|
||||
|
||||
+ [#25](https://github.com/nadar/quill-delta-parser/issues/25) Ensure that empty heading listeners won't destroy all upcoming elements.
|
||||
|
||||
## 2.1.0 (24. August 2019)
|
||||
|
||||
+ [#23](https://github.com/nadar/quill-delta-parser/issues/23) Add inline element to handle font attribute.
|
||||
|
||||
## 2.0.0 (10. July 2019)
|
||||
|
||||
> This release contains breaks which might affect your application. Checkout the [upgrade document](UPGRADE.md) for more details.
|
||||
|
||||
+ [#18](https://github.com/nadar/quill-delta-parser/issues/18) Enable `escapeInput` option by default in order to increase security.
|
||||
|
||||
## 1.3.2 (9. July 2019)
|
||||
|
||||
+ [#13](https://github.com/nadar/quill-delta-parser/pull/13) Fixed bug when lists are interrupted with block level elements (e.g. videos)
|
||||
+ [#16](https://github.com/nadar/quill-delta-parser/issues/16) Fixed bug with sorting index of inline elements when using prepend.
|
||||
+ [#15](https://github.com/nadar/quill-delta-parser/pull/15) Fixed bug when header contains (partial) formatting and line before header contains formatting
|
||||
|
||||
## 1.3.1 (6. June 2019)
|
||||
|
||||
+ [#12](https://github.com/nadar/quill-delta-parser/issues/12) Fixed bug when using quill parser on windows platforms.
|
||||
|
||||
## 1.3.0 (28. May 2019)
|
||||
|
||||
+ Added new debugInfo option for lines
|
||||
+ Added new behavior for hasNewline (which is now more consistent according to delta input)
|
||||
+ [#8](https://github.com/nadar/quill-delta-parser/issues/8) Fixed issue with UL/OL list elements if only one element is inside the list.
|
||||
|
||||
## 1.2.0 (30. April 2019)
|
||||
|
||||
+ [#7](https://github.com/nadar/quill-delta-parser/pull/7) Makes sure input and attributes from delta is escaped before mixing it with html. Listeners should use `$line->getInput()` instead of `$line->input` to read input. This will properly escape if it is not done already. Values from attributes should be passed through `$line->getLexer()->escape()`. See the [Color listener](src/listener/Color.php) for an example of both. Obviously, escaping should be skipped in case a listener is meant to output raw html.
|
||||
|
||||
## 1.1.1 (17. April 2019)
|
||||
|
||||
+ [#6](https://github.com/nadar/quill-delta-parser/pull/6) Fixed bug in exception messaged. Added unit tests and improved message.
|
||||
|
||||
## 1.1.0 (8. March 2019)
|
||||
|
||||
+ [#5](https://github.com/nadar/quill-delta-parser/issues/5) Fixed a bug where paragraphs with attributes where not rendered (color attribute). Added new parameter to disable the rendering of attributes.
|
||||
|
||||
## 1.0.2 (7. February 2019)
|
||||
|
||||
+ Added Image-Tag for image output.
|
||||
|
||||
## 1.0.1 (25. November 2018)
|
||||
|
||||
+ Improve Video-Tag output (Remove frameborder, use youtube embed code allow tag).
|
||||
|
||||
## 1.0.0 (21. October 2018)
|
||||
|
||||
+ First stable API release.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Basil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,170 @@
|
||||
# Quill Delta to HTML Parser
|
||||
|
||||
A PHP library to parse [Quill WYSIWYG](https://quilljs.com/) editor [deltas](https://github.com/quilljs/delta) into HTML - flexible and extendable for custom elements. Every element is parsed by the same mechanism, making it easy to extend and understand. It also sanitizes the output value, making it more secure, especially when using user-generated text.
|
||||
|
||||

|
||||
[](https://codeclimate.com/github/nadar/quill-delta-parser/maintainability)
|
||||
[](https://codeclimate.com/github/nadar/quill-delta-parser/test_coverage)
|
||||
[](https://packagist.org/packages/nadar/quill-delta-parser)
|
||||
[](https://packagist.org/packages/nadar/quill-delta-parser)
|
||||
[](https://packagist.org/packages/nadar/quill-delta-parser)
|
||||
|
||||
What is Quill? Quill is a free, open source WYSIWYG editor built for the modern web. With its modular architecture and expressive API, it is completely customizable to fit any need.
|
||||
|
||||
## Installation
|
||||
|
||||
The package is available only through Composer:
|
||||
|
||||
```sh
|
||||
composer require nadar/quill-delta-parser
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```php
|
||||
// Ensure to load the autoload file from Composer somewhere in your application.
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Create the lexer object with your given Quill JSON delta code (either PHP array or JSON string).
|
||||
$lexer = new \nadar\quill\Lexer($json);
|
||||
|
||||
// Echo the HTML for the given JSON ops.
|
||||
echo $lexer->render();
|
||||
```
|
||||
|
||||
Where `$json` is the ops JSON array from Quill, for example:
|
||||
|
||||
```json
|
||||
{
|
||||
"ops": [
|
||||
{
|
||||
"insert": "Hello"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"header": 1
|
||||
},
|
||||
"insert": "\n"
|
||||
},
|
||||
{
|
||||
"insert": "\nThis is the PHP Quill "
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"bold": true
|
||||
},
|
||||
"insert": "parser"
|
||||
},
|
||||
{
|
||||
"insert": "!\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This would render the following HTML:
|
||||
|
||||
```html
|
||||
<h1>Hello</h1>
|
||||
<p><br></p>
|
||||
<p>This is the PHP Quill <strong>parser</strong>!</p>
|
||||
```
|
||||
|
||||
## Extend the Parser
|
||||
|
||||
To extend the Parser by adding your own listeners (this can be the case if you are using Quill plugins which generate custom delta code), you have to decide whether it's an:
|
||||
|
||||
+ Inline element: Replaces content with new parsed content, mostly the case when working with Quill extensions.
|
||||
+ Block element: Encloses the whole input with a tag, for example, a heading.
|
||||
|
||||
An example for a mention plugin that generates the following delta `{"insert":{"mention":{"id":"1","value":"Basil","denotationChar":"@"}}}`; an inline plugin could look like this:
|
||||
|
||||
```php
|
||||
class Mention extends InlineListener
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function process(Line $line)
|
||||
{
|
||||
// Check if input is JSON, decodes to an array, and checks if the key "mention"
|
||||
// exists. If yes, return the value for this key.
|
||||
$mention = $line->insertJsonKey('mention');
|
||||
if ($mention) {
|
||||
// Apply the inline behavior, updates the content and append to the next "block" element.
|
||||
// The value in this example would be "<strong>Basil</strong>".
|
||||
$this->updateInput($line, '<strong>'.$mention['value'].'</strong>');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now register the listener:
|
||||
|
||||
```php
|
||||
$lexer = new Lexer($json);
|
||||
$lexer->registerListener(new Mention());
|
||||
echo $lexer->render();
|
||||
```
|
||||
|
||||
## Override Built-in Listeners
|
||||
|
||||
Certain listeners (image, video, color) produce an HTML output which may not suit your use case, so you have the option to override the properties of those plugins and re-register the Listener. Here's an example with the image tag:
|
||||
|
||||
```php
|
||||
$image = new Image();
|
||||
|
||||
|
||||
$image->wrapper = '<img src="{src}" class="my-image" />';
|
||||
|
||||
// Override the default listener behavior for image color:
|
||||
$lexer = new Lexer($json);
|
||||
$lexer->registerListener($image);
|
||||
echo $lexer->render();
|
||||
```
|
||||
|
||||
If you want to replace a class with your own image class, use `overwriteListener` to achieve the same result, but with your totally custom class. The reason is that listeners are registered by their class names.
|
||||
|
||||
```php
|
||||
$mySuperClass = new class() extends Image {
|
||||
// Here is the custom class code ...
|
||||
};
|
||||
|
||||
$lexer->overwriteListener(new Image(), $mySuperClass);
|
||||
```
|
||||
|
||||
Or, of course, when you have a separate file for your class:
|
||||
|
||||
```php
|
||||
class MySuperDuperImageClass extends Image
|
||||
{
|
||||
// Here is the custom class code ...
|
||||
}
|
||||
|
||||
$lexer->overwriteListener(new Image(), new MySuperDuperImageClass());
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
Sometimes, understanding how delta is handled and parsed can be challenging to debug. Therefore, you can use the debugger class, which will print a table with information about how the data is parsed.
|
||||
|
||||
```php
|
||||
$lexer = new Lexer($json);
|
||||
$lexer->render(); // Make sure to run the render before calling debugPrint().
|
||||
|
||||
$debug = new Debug($lexer);
|
||||
echo $debug->debugPrint();
|
||||
```
|
||||
|
||||
There is also a built-in Docker Compose file which provides access to the `output.php` file. The `output.php` helps to directly write content with the Quill editor while displaying what is rendered, including all debug information. To run this Docker webserver, execute the following command in the root directory of your Git repository clone:
|
||||
|
||||
```sh
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
and visit `http://localhost:5555/` in your browser.
|
||||
|
||||
#### Credits
|
||||
|
||||
+ [Lode Claassen](https://github.com/lode)
|
||||
+ [Dean Blackborough](https://github.com/deanblackborough)
|
||||
@@ -0,0 +1,26 @@
|
||||
# Quill Delta Parser Upgrade
|
||||
|
||||
This document will help you upgrading from a version into another. For more detailed informations about the breaking changes **click the issue detail link**, there you can see examples of how to change your code.
|
||||
|
||||
## from 3.3.x to 3.4
|
||||
|
||||
+ [#80](https://github.com/nadar/quill-delta-parser/issues/80) In this update, we have introduced a change where background color information from Quill JSON is now extracted and applied as the background color. This change may result in unexpected behavior if you had background color information present in your documents but it was not rendered correctly in previous versions. To restore the previous behavior and resolve any unexpected issues related to background color rendering, please follow these steps:
|
||||
|
||||
```php
|
||||
$backgroundColor = new BackgroundColor();
|
||||
$backgroundColor->ignore = true;
|
||||
|
||||
// override the default listener behavior for background color:
|
||||
$lexer = new Lexer($json);
|
||||
$lexer->registerListener($backgroundColor);
|
||||
echo $lexer->render();
|
||||
```
|
||||
|
||||
## from 2.x to 3.0
|
||||
|
||||
+ [#61](https://github.com/nadar/quill-delta-parser/pull/61) PHP 7.1 is official dropped in unit tests. The code should still work with PHP 7.1 until next major release.
|
||||
+ [#60](https://github.com/nadar/quill-delta-parser/pull/60) Changed the behavior of `nadar\quill\listener\Link`. Property `$wrapper` has been removed and replaced by `$wrapperOpen`, `$wrapperMiddle` and `$wrapperClose`. If you are not overriding or have a customized setup the `Link` listenere, upgrading is safe.
|
||||
|
||||
## from 1.x to 2.0
|
||||
|
||||
+ [#18](https://github.com/nadar/quill-delta-parser/issues/18) The input values from are now safely encoded by default. This might be a problem when using script or other raw html code inside your text(quill delta). To achieve the same output as behavior as before set `$lexer->escapeInput = false`. This will turn off input encoding but is not recommend.
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "nadar/quill-delta-parser",
|
||||
"description": "A PHP library to parse Quill WYSIWYG editor deltas into HTML - flexible and extendible for custom elements.",
|
||||
"keywords": [
|
||||
"quill",
|
||||
"delta",
|
||||
"html",
|
||||
"parser",
|
||||
"renderer",
|
||||
"quill-editor",
|
||||
"quill-delta",
|
||||
"quilljs"
|
||||
],
|
||||
"type": "library",
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"phpstan/phpstan": "^1.7",
|
||||
"rector/rector": "^0.14.2"
|
||||
},
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Basil Suter",
|
||||
"email": "git@nadar.io"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"nadar\\quill\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"nadar\\quill\\tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"phpstan": "vendor/bin/phpstan -v",
|
||||
"phpcsfixer": "vendor/bin/php-cs-fixer fix",
|
||||
"rector": "vendor/bin/rector"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.4"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"ext-json": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
$json = <<<'JSON'
|
||||
[{"insert":"It’s that time of year again, and we have some great new offers and new product reveals for "},{"attributes":{"bold":true},"insert":"Black Friday"},{"insert":"! \n\nThe Black Friday Deals will be available from November 29th at 09:00 CET until Cyber Monday (December 2nd), exclusively through our webshop.\n\n"},{"insert":{"embed-external":{"data":{"embedType":"image","url":"https:\/\/lh3.googleusercontent.com\/8IXf1zlGK8FZ4ttBuLsf7f79WTf4Uuq09o-quE5nmmXQk-PM9gXqoqEZUxRcEpeJjGmydwZ7Ew_hamwZjX5mPVru47lEJlWdDzEIbalt60wC8Dpjyfx-p2ZJb7yg9rbf0LEOMcZJ","name":"","attributes":[],"format":null,"bodyRaw":null},"loaderData":{"type":"image"}}}},{"insert":"\n"},{"attributes":{"bold":true},"insert":"CSL Elite Pro Kit - Officially Licensed for PS4"},{"insert":"™"},{"attributes":{"bold":true},"insert":" at 699.95 € (Europe only)"},{"insert":"\nThis offer is only valid in EU and is strictly limited!"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Race-ready Fanatec package for PS4™ and PS4™ Pro combines the CSL Elite Racing Wheel for PS4 and the CSL Elite Pedals LC"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Wheel base provides realistic force feedback thanks to Fanatec ClubSport technology"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Fully metal, adjustable CSL Elite pedals"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"CSL Elite Loadcell Kit included! Load cell technology greatly improves brake feeling and consistency."},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"\n\n\n"},{"insert":{"embed-external":{"data":{"url":"https:\/\/forum.fanatec.com\/uploads\/394\/R28CIXS9QPRK.jpg","name":"blackfriday_V3_4.jpg","type":"image\/jpeg","size":184063,"width":1200,"height":628,"mediaID":1904,"dateInserted":"2019-11-25T16:33:45+00:00","insertUserID":30220,"foreignType":"embed","foreignID":30220,"embedType":"image","format":null,"bodyRaw":null},"loaderData":{"type":"image"}}}},{"insert":"\n"},{"attributes":{"bold":true},"insert":"ClubSport Pedals V3 + Brake Performance Kit at 299,95 €\/$, 37,900.00¥, AU $499.90"},{"insert":"\nBuy the legendary ClubSport Pedals V3 with the Brake Performance Kit for less than the normal price of the pedals alone!\nHigh performance pedals with Hall sensors on throttle and clutch pedals for precision and durability"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Pressure sensitive load cell brake system with custom-made 90 kg sensor and tool-free stiffness and travel adjustment"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"12-bit resolution (4096 values) on all axis"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Alternative D-shape pedal plates included, with adjustable extensions"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Brake sensitivity can be adjusted through the Tuning Menu while driving"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Vibration motors on throttle and brake pedals to simulate TC, ABS, and other effects"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Exchangeable throttle springs. Two springs with different strengths are included."},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Optional damper kit for gas and brake with adjustable damping resistance."},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Full multi-platform compatibility"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Full metal construction of CNC-machined aluminium"},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Brake Performance Kit stiffens and shortens throw to behave more like a race car pedal, greatly improving on-the-limit threshold brake feel."},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"\n\n\n"},{"insert":{"embed-external":{"data":{"url":"https:\/\/forum.fanatec.com\/uploads\/880\/NLTSAR7P9O2M.jpg","name":"blackfriday_shifter_2.jpg","type":"image\/jpeg","size":186258,"width":1200,"height":628,"mediaID":1916,"dateInserted":"2019-11-26T16:56:01+00:00","insertUserID":30220,"foreignType":"embed","foreignID":30220,"embedType":"image","format":null,"bodyRaw":null},"loaderData":{"type":"image"}}}},{"insert":"\n"},{"attributes":{"bold":true},"insert":"ClubSport Shifter SQ V1.5 Limited Edition 2019 for 259,95€, US $199,95, 28,900.00¥, AU $349,90"},{"insert":"\nThe ClubSport Shifter SQ V1.5 Limited Edition 2019 comes in a unique colour scheme, featuring a matte grey body, anodised red lever, and matte carbon gear knobs."},{"attributes":{"list":{"depth":0,"type":"bullet"}},"insert":"\n"},{"insert":"Shares the same high-performance specification as the standard ClubSport Shifter SQ V1.5: Full
Line truncated
|
||||
|
||||
|
||||
|
||||
JSON;
|
||||
|
||||
$json = '{"ops":' . $json . '}';
|
||||
|
||||
// Ensure to load the autoload file from Composer somewhere in your application.
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Create the lexer object with your given Quill JSON delta code (either PHP array or JSON string).
|
||||
$lexer = new \nadar\quill\Lexer($json);
|
||||
|
||||
// Echo the HTML for the given JSON ops.
|
||||
echo $lexer->render();
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace nadar\quill;
|
||||
|
||||
/**
|
||||
* Block Listener
|
||||
*
|
||||
* Block listener writes from $line->input into the $line->output.
|
||||
*
|
||||
* @author Basil Suter <basil@nadar.io>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class BlockListener extends Listener
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function type(): int
|
||||
{
|
||||
return self::TYPE_BLOCK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a rendered output from the current picks with a custom Wrapper Template.
|
||||
*
|
||||
* The main purpose of this method is so simplify the process of working with block elements. The method
|
||||
* will take the `picks()` of the current Listener Object to generate the first and list line of the block
|
||||
* and render its content inbetween.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```php
|
||||
* public function render(Lexer $lexer)
|
||||
* {
|
||||
* $this->wrapElement('<blockquote>{__buffer__}</blockquote>');
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The above method will no take the picked element and render a `blockquote` tag with its content stored in `{__buffer__}`.
|
||||
*
|
||||
* Assuming you pass options to the picked element during the `process()` stage you might use those options as variable
|
||||
* in brackes and passed to `$options` param:
|
||||
*
|
||||
* ```php
|
||||
* $this->wrapElement('<h{heading}>{__buffer__}</h{heading}>', ['heading']);
|
||||
* ```
|
||||
*
|
||||
* The above example assumes the heading option is stored during the process stage: `$this->pick($line, ['heading' => $heading]);`
|
||||
*
|
||||
* @param string $wrapper html snippet using `{__buffer__}` for the placement of the lines
|
||||
* @param array<string> $options optional, pass the names of options from the lines you want to search & replace
|
||||
* e.g. using ['key'] will replace `{key}` in the $wrapper with Pick->$key.
|
||||
* With version 2.5.0 its allowed to provide the element as key and a callable as value which will be evaluted on request.
|
||||
* ```php
|
||||
* 'key' => function($value) {
|
||||
* return '<p>'.$value.'</p>';
|
||||
* }
|
||||
* ```
|
||||
* @since 2.4.0
|
||||
* @return void
|
||||
*/
|
||||
protected function wrapElement($wrapper, array $options = [])
|
||||
{
|
||||
$search = ['{__buffer__}'];
|
||||
foreach ($options as $key => $value) {
|
||||
$search[] = is_int($key) ? '{'.$value.'}' : '{'.$key.'}';
|
||||
}
|
||||
|
||||
foreach ($this->picks() as $pick) {
|
||||
$first = $this->getFirstLine($pick);
|
||||
|
||||
// while from first to the pick line and store content in buffer
|
||||
$buffer = null;
|
||||
$first->while(static function (&$index, Line $line) use (&$buffer, $pick, $first) {
|
||||
++$index;
|
||||
$buffer .= $line->getInput();
|
||||
$line->setDone();
|
||||
// if the index of the picked lines is reached or the first element is the picked index.
|
||||
if ($index == $pick->line->getIndex() || $first->getIndex() === $pick->line->getIndex()) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$replace = [$buffer];
|
||||
foreach ($options as $key => $value) {
|
||||
$name = is_int($key) ? $value : $key;
|
||||
$content = $pick->optionValue($name);
|
||||
$value = is_callable($value) ? call_user_func($value, $content, $pick, $name) : $content;
|
||||
$replace[] = $value;
|
||||
}
|
||||
|
||||
$pick->line->output = str_replace($search, $replace, $wrapper).PHP_EOL;
|
||||
$pick->line->setDone();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first Line from a Pick. If the Pick is the first Line, it will return it's own pick
|
||||
* This is done because blockItems can consist of multiple inline items
|
||||
*
|
||||
* @since 1.3.2
|
||||
*/
|
||||
protected function getFirstLine(Pick $pick): Line
|
||||
{
|
||||
$first = $pick->line;
|
||||
|
||||
$pick->line->while(
|
||||
static function (&$index, Line $line) use ($pick, &$first) {
|
||||
--$index;
|
||||
// its the same line as the start.. skip this one as its by default included in while operations
|
||||
if ($line === $pick->line) {
|
||||
return true;
|
||||
} elseif (($line->hasEndNewline() || $line->hasNewline() || ($line->isJsonInsert() && !$line->isInline()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// assign the line to $first
|
||||
$first = $line;
|
||||
return true;
|
||||
}
|
||||
);
|
||||
return $first;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace nadar\quill;
|
||||
|
||||
/**
|
||||
* Debug Object.
|
||||
*
|
||||
* The Debug class can return informations in a readable way from a lexer object. It will generate a html table
|
||||
* with additional infos about how each line is parsed line by line.
|
||||
*
|
||||
* ```php
|
||||
* $lexer = new Lexer($json);
|
||||
* $lexer->render(); // make sure to run the render before call debugPrint();
|
||||
*
|
||||
* $debug = new Debug($lexer);
|
||||
* echo $debug->debugPrint();
|
||||
* ```
|
||||
*
|
||||
* In order to push informations into the Debug Panel, you can use
|
||||
* the `debugInfo($text)` method which is available on all `Line` objects.
|
||||
*
|
||||
* @author Basil Suter <basil@nadar.io>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Debug
|
||||
{
|
||||
/**
|
||||
* @var Lexer
|
||||
*/
|
||||
public $lexer;
|
||||
|
||||
/**
|
||||
* Debug constructor
|
||||
*/
|
||||
public function __construct(Lexer $lexer)
|
||||
{
|
||||
$this->lexer = $lexer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of lines which does not have status done.
|
||||
*
|
||||
* @return array<Line>
|
||||
*/
|
||||
protected function getNotDoneLines()
|
||||
{
|
||||
$lines = [];
|
||||
foreach ($this->lexer->getLines() as $line) {
|
||||
if (!$line->isDone()) {
|
||||
$lines[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of lines which does not have the status picked
|
||||
*
|
||||
* @return array<Line>
|
||||
*/
|
||||
protected function getNotPickedLines()
|
||||
{
|
||||
$lines = [];
|
||||
foreach ($this->lexer->getLines() as $line) {
|
||||
if (!$line->isPicked() && !$line->isDone()) {
|
||||
$lines[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* return a string with debug informations.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function debugPrint()
|
||||
{
|
||||
$d = "<p><b>NOT PICKED LINES</b></p>";
|
||||
$d.= $this->getLinesTable($this->getNotPickedLines());
|
||||
|
||||
$d.= "<p><b>NOT DONE LINES</b></p>";
|
||||
$d.= $this->getLinesTable($this->getNotDoneLines());
|
||||
|
||||
$d.= "<p><b>LINE BY LINE</b></p>";
|
||||
$d.= $this->getLinesTable($this->lexer->getLines());
|
||||
|
||||
return nl2br($d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array with alles lines rendered a string
|
||||
*
|
||||
* @param array<Line> $lines
|
||||
* @return string
|
||||
*/
|
||||
public function getLinesTable(array $lines)
|
||||
{
|
||||
$_lines = [];
|
||||
foreach ($lines as $line) {
|
||||
$_lines[] = [
|
||||
$line->getIndex(),
|
||||
$line->getInput(),
|
||||
htmlentities($line->output, ENT_QUOTES),
|
||||
htmlentities($line->renderPrepend(), ENT_QUOTES),
|
||||
implode(", ", $line->getDebugInfo()),
|
||||
var_export($line->getAttributes(), true),
|
||||
var_export($line->isInline(), true),
|
||||
$this->lineStatus($line),
|
||||
var_export($line->hasEndNewline(), true),
|
||||
var_export($line->hasNewline(), true),
|
||||
var_export($line->isEmpty(), true),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->renderTable($_lines, ['ID', 'input', 'output', 'prepend', 'debug', 'attributes', 'is inline', 'status', 'has end newline', 'has new line', 'is empty']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status waterfall of a given line
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function lineStatus(Line $line)
|
||||
{
|
||||
if ($line->isDone()) {
|
||||
return 'Picked => Done';
|
||||
} elseif ($line->isPicked()) {
|
||||
return 'Picked';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the given table line by line
|
||||
*
|
||||
* @param array<mixed> $rows
|
||||
* @param array<string> $head
|
||||
* @return string
|
||||
*/
|
||||
protected function renderTable(array $rows, array $head = [])
|
||||
{
|
||||
$buffer = '<table class="table table-bordered table-striped table-hover table-sm small" border="1" width="100%" cellpadding="3" cellspacing="0">';
|
||||
|
||||
if (!empty($head)) {
|
||||
$buffer.= '<thead><tr>';
|
||||
foreach ($head as $col) {
|
||||
$buffer.= '<td><b>'.$col.'</b></td>';
|
||||
}
|
||||
|
||||
$buffer.= '</tr></thead>';
|
||||
}
|
||||
|
||||
foreach ($rows as $cols) {
|
||||
$buffer .= '<tr onclick="this.style.backgroundColor=(this.style.backgroundColor==\'red\')?(\'transparent\'):(\'red\');">';
|
||||
foreach ($cols as $col) {
|
||||
$buffer .= '<td>'.$col.'</td>';
|
||||
}
|
||||
|
||||
$buffer .= '</tr>';
|
||||
}
|
||||
|
||||
return $buffer . '</table>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace nadar\quill;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Inline listener.
|
||||
*
|
||||
* Inline listeners changes the $line->input value!
|
||||
*
|
||||
* @author Basil Suter <basil@nadar.io>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class InlineListener extends Listener
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function type(): int
|
||||
{
|
||||
return self::TYPE_INLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A short hand method for handling inline elements.
|
||||
*
|
||||
* 1. change input value
|
||||
* 2. set as done, inline, and escaped
|
||||
* 3. Add to pick list, in order to process in render method
|
||||
*
|
||||
* @param Line $line
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function updateInput(Line $line, $value)
|
||||
{
|
||||
// we override the current element, and mark as done and mark as inline
|
||||
$line->setInput($value);
|
||||
$line->setDone();
|
||||
$line->setAsInline();
|
||||
$line->setAsEscaped();
|
||||
$this->pick($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function render(Lexer $lexer)
|
||||
{
|
||||
foreach ($this->picks() as $pick) {
|
||||
$next = $pick->line->next(static function (Line $line) {
|
||||
return !$line->isInline();
|
||||
});
|
||||
|
||||
if (!$next) {
|
||||
throw new Exception(sprintf("Unable to find a next element. Invalid DELTA on '%s'. Maybe your delta code does not end with a newline?", $pick->line->getInput()));
|
||||
}
|
||||
|
||||
$next->addPrepend($pick->line->getInput(), $pick->line);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
<?php
|
||||
|
||||
namespace nadar\quill;
|
||||
|
||||
use nadar\quill\listener\Align;
|
||||
use nadar\quill\listener\BackgroundColor;
|
||||
use nadar\quill\listener\Blockquote;
|
||||
use nadar\quill\listener\Bold;
|
||||
use nadar\quill\listener\CodeBlock;
|
||||
use nadar\quill\listener\Color;
|
||||
use nadar\quill\listener\Font;
|
||||
use nadar\quill\listener\Heading;
|
||||
use nadar\quill\listener\Image;
|
||||
use nadar\quill\listener\Italic;
|
||||
use nadar\quill\listener\Link;
|
||||
use nadar\quill\listener\Lists;
|
||||
use nadar\quill\listener\Script;
|
||||
use nadar\quill\listener\Strike;
|
||||
use nadar\quill\listener\Text;
|
||||
use nadar\quill\listener\Underline;
|
||||
use nadar\quill\listener\Video;
|
||||
|
||||
/**
|
||||
* Lexer Delta Parser.
|
||||
*
|
||||
* The lexer class represents the main thread in how delta input is processed and rendered.
|
||||
*
|
||||
* Basically listeners can watch every line of delta and interact with the line, which means
|
||||
* reading input and writting to output and mark this line as done, so other listeners won't
|
||||
* take care of this line as well.
|
||||
*
|
||||
* ## Basic concept
|
||||
*
|
||||
* Listeners are grouped in 2 types:
|
||||
*
|
||||
* + inline: For elements which are only inline applied, like writing bold or italic
|
||||
* + block: Used when the line represents a full html element like heading or lists
|
||||
*
|
||||
* Inside this group types there are 2 prioirities:
|
||||
*
|
||||
* + early bird: This is default value, the early bird catches the worm...
|
||||
* + garbage collector: This is mainly used for the text listener which generates the paragraphs and can only be done at the very end of the process.
|
||||
*
|
||||
* Every listener has two methods a process() and render():
|
||||
*
|
||||
* + process: is triggered by every line, so the listener can choose whether he wants to pick this line, interact or not.
|
||||
* + render: after all lines are processed, every listener triggers the render() method once, so the picked line from process can be "further" processed and rendered.
|
||||
*
|
||||
* ## Lifecycle
|
||||
*
|
||||
* 1. lines will be generated
|
||||
* 2. lines foreached and inline early bird listeners run process() method.
|
||||
* 3. lines foreached and inline garbage collector listeners run process() method.
|
||||
* 4. lines foreached and block early bird listeners run process() method.
|
||||
* 5. lines foreached and block garbage collector listener run process() method.
|
||||
* 6. inline listeners foreach and run render() method.
|
||||
* 7. block listeners foreach and run render() method.
|
||||
*
|
||||
* @author Basil Suter <basil@nadar.io>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Lexer
|
||||
{
|
||||
/**
|
||||
* @var string The quill deltas newline is always \n, thefore do not use PHP_EOL as its different in win platforms.
|
||||
* @see https://quilljs.com/guides/designing-the-delta-format/
|
||||
* @see https://github.com/nadar/quill-delta-parser/issues/12
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public const DELTA_EOL = "\n";
|
||||
|
||||
/**
|
||||
* @var string An internal string for newlines, this makes it more easy to debug instead of using \n (newlines).
|
||||
*/
|
||||
public const NEWLINE_EXPRESSION = '<!-- <![CDATA[NEWLINE]]> -->';
|
||||
|
||||
/**
|
||||
* @var boolean Whether input should be escaped by listeners when mixed with html elements.
|
||||
* Note that a specific listener can decide to not escape if their output should be raw html.
|
||||
* If script and other insecure input values should be allowed this can be turned off but its not recommend!
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public $escapeInput = true;
|
||||
|
||||
/**
|
||||
* @var int These flags are used for escaping values for mixing with a html context.
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public $escapeFlags = ENT_QUOTES|ENT_HTML5;
|
||||
|
||||
/**
|
||||
* @var string The encoding is used for escaping values for mixing with a html context.
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public $escapeEncoding = 'UTF-8';
|
||||
|
||||
/**
|
||||
* @var boolean Whether debbuging is enabled or not. If enabled lines can contain debugInfo messages which can be retrieved with {{Debug}} class.
|
||||
*/
|
||||
public $debug = false;
|
||||
|
||||
/**
|
||||
* @var array<mixed>|string The delta ops json as string or as already parsed array
|
||||
*/
|
||||
protected $json;
|
||||
|
||||
/**
|
||||
* @var array<Line>
|
||||
*/
|
||||
private $_lines = [];
|
||||
|
||||
/**
|
||||
* @var array<mixed> The listeners grouped by type and priority.
|
||||
*/
|
||||
protected $listeners = [
|
||||
Listener::TYPE_INLINE => [
|
||||
Listener::PRIORITY_EARLY_BIRD => [],
|
||||
Listener::PRIORITY_GARBAGE_COLLECTOR => [],
|
||||
],
|
||||
Listener::TYPE_BLOCK => [
|
||||
Listener::PRIORITY_EARLY_BIRD => [],
|
||||
Listener::PRIORITY_GARBAGE_COLLECTOR => [],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Initializer
|
||||
*
|
||||
* @param string|array<mixed> $json The delta ops json as string or as already parsed array.
|
||||
* @param boolean $loadBuiltinListeners Whether the built in listeners should be loaded or not.
|
||||
*/
|
||||
public function __construct($json, $loadBuiltinListeners = true)
|
||||
{
|
||||
$this->json = $json;
|
||||
|
||||
if ($loadBuiltinListeners) {
|
||||
$this->loadBuiltinListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the library built in listeners.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loadBuiltinListeners()
|
||||
{
|
||||
$this->registerListener(new Image());
|
||||
$this->registerListener(new Bold());
|
||||
$this->registerListener(new Italic());
|
||||
$this->registerListener(new Color());
|
||||
$this->registerListener(new BackgroundColor());
|
||||
$this->registerListener(new Link());
|
||||
$this->registerListener(new Video());
|
||||
$this->registerListener(new Strike());
|
||||
$this->registerListener(new Underline());
|
||||
$this->registerListener(new Heading());
|
||||
$this->registerListener(new CodeBlock());
|
||||
$this->registerListener(new Text());
|
||||
$this->registerListener(new Lists());
|
||||
$this->registerListener(new Blockquote());
|
||||
$this->registerListener(new Font());
|
||||
$this->registerListener(new Script());
|
||||
$this->registerListener(new Align());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new listener.
|
||||
*
|
||||
* @return Lexer
|
||||
*/
|
||||
public function registerListener(Listener $listener)
|
||||
{
|
||||
$this->listeners[$listener->type()][$listener->priority()][get_class($listener)] = $listener;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrite an existing listener with a new object
|
||||
*
|
||||
* An example could when you like to provide more options or access other elements which are not covered by the base class
|
||||
* so you can extend from the built in listeners and overrite them. This keeps also the hyrarchical level of the elements.
|
||||
*
|
||||
* ```php
|
||||
* $lexer->overwriteListener(new Image, new MyOwnImage);
|
||||
* ```
|
||||
*
|
||||
* As the `new Image` listener is already registered, it will just replace the object with `new MyOwnImage`.
|
||||
*
|
||||
* @param Listener $listener The already registered listener object
|
||||
* @param Listener $new The new listener object
|
||||
* @see https://github.com/nadar/quill-delta-parser/issues/55
|
||||
* @since 2.8.0
|
||||
* @return Lexer
|
||||
*/
|
||||
public function overwriteListener(Listener $listener, Listener $new)
|
||||
{
|
||||
$this->listeners[$listener->type()][$listener->priority()][get_class($listener)] = $new;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input json as array.
|
||||
*
|
||||
* @return array<mixed> The json as array formated.
|
||||
*/
|
||||
public function getJsonArray(): array
|
||||
{
|
||||
return is_array($this->json) ? $this->json : self::decodeJson($this->json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ops section from the json otherwise json array.
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getOps(): array
|
||||
{
|
||||
return $this->getJsonArray()['ops'] ?? $this->getJsonArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line object for a given id/row/index.
|
||||
*
|
||||
* @param integer $index The index of the line.
|
||||
* @return Line|boolean
|
||||
*/
|
||||
public function getLine($index)
|
||||
{
|
||||
return $this->_lines[$index] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Line> Returns an array with all line objects.
|
||||
*/
|
||||
public function getLines(): array
|
||||
{
|
||||
return $this->_lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the arrray operations array into lines
|
||||
*
|
||||
* @param array<mixed> $ops An array from json with ops data in delta format.
|
||||
* @return array<Line> An array with Line objects
|
||||
*/
|
||||
protected function opsToLines(array $ops)
|
||||
{
|
||||
$lines = [];
|
||||
$i = 0;
|
||||
foreach ($ops as $delta) {
|
||||
// replace newline chars with internal expression
|
||||
$insert = $this->replaceNewlineWithExpression($delta['insert']);
|
||||
// if its an empty "newline-line"
|
||||
if ($insert == self::NEWLINE_EXPRESSION) {
|
||||
$lines[$i] = new Line($i, '', $delta['attributes'] ?? [], $this, true, true);
|
||||
++$i;
|
||||
} else {
|
||||
$insert = $this->normalizeInsert($insert);
|
||||
// remove the last newline from the line, as it will be splited into lines anyhow.
|
||||
$line = $this->removeLastNewline($insert);
|
||||
// check if the original input was changed, in order to determine whether current line had new line char.
|
||||
$hasEndNewline = ($line !== $insert);
|
||||
|
||||
// check if original insert string has a new line char in the string
|
||||
$hasNewline = $this->lineHasNewline($insert);
|
||||
// split the input string into parts / lines.
|
||||
$parts = explode(self::NEWLINE_EXPRESSION, $line);
|
||||
$count = count($parts);
|
||||
foreach ($parts as $index => $value) {
|
||||
// check if this line had the end newline
|
||||
$isLast = ($index + 1) == $count;
|
||||
$hadEndNewline = $isLast && $hasEndNewline;
|
||||
if ($hasNewline && $isLast && !$hasEndNewline) {
|
||||
$hasNewline = false;
|
||||
}
|
||||
|
||||
$lines[$i] = new Line($i, $value, $delta['attributes'] ?? [], $this, $hadEndNewline, $hasNewline);
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current line as a newline char.
|
||||
*
|
||||
* @param string $input
|
||||
* @return boolean
|
||||
*/
|
||||
protected function lineHasNewline($input)
|
||||
{
|
||||
return strpos($input, self::NEWLINE_EXPRESSION) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace new lines with an internal representation that aids debugging
|
||||
*
|
||||
* @param array<mixed>|string $input
|
||||
* @return array<mixed>|string
|
||||
*/
|
||||
protected function replaceNewlineWithExpression($input)
|
||||
{
|
||||
return is_array($input) ? $input : str_replace(self::DELTA_EOL, self::NEWLINE_EXPRESSION, $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normamlize the insert values into json
|
||||
*
|
||||
* @param array<mixed>|string $insert
|
||||
* @return string
|
||||
* @since 1.2.2
|
||||
*/
|
||||
protected function normalizeInsert($insert)
|
||||
{
|
||||
// plugins can have array values as text.
|
||||
if (is_array($insert)) {
|
||||
// convert the array into a json string
|
||||
return json_encode($insert);
|
||||
}
|
||||
|
||||
return $insert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the last newline expression from a string
|
||||
*
|
||||
* @param string $insert
|
||||
* @return string
|
||||
*/
|
||||
protected function removeLastNewline($insert)
|
||||
{
|
||||
$expLength = strlen(self::NEWLINE_EXPRESSION);
|
||||
// remove new line from the end of the string
|
||||
// as this explode split well be done anyhow or its already part of a new line
|
||||
if (substr($insert, -$expLength) == self::NEWLINE_EXPRESSION) {
|
||||
return substr($insert, 0, -$expLength);
|
||||
}
|
||||
|
||||
return $insert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all listeners for a given type
|
||||
*
|
||||
* @param int $type
|
||||
* @return void
|
||||
*/
|
||||
protected function processListeners(Line $line, $type)
|
||||
{
|
||||
foreach ($this->listeners[$type] as $prios) {
|
||||
foreach ($prios as $listener) {
|
||||
$listener->process($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render all listeners for a given type
|
||||
*
|
||||
* @param int $type
|
||||
* @return void
|
||||
*/
|
||||
protected function renderListeners($type)
|
||||
{
|
||||
foreach ($this->listeners[$type] as $prios) {
|
||||
foreach ($prios as $listener) {
|
||||
$listener->render($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the current delta into a html string.
|
||||
*
|
||||
* @return string The html code for the given delta input.
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$this->_lines = $this->opsToLines($this->getOps());
|
||||
|
||||
foreach ($this->_lines as $line) {
|
||||
$this->processListeners($line, Listener::TYPE_INLINE);
|
||||
$this->processListeners($line, Listener::TYPE_BLOCK);
|
||||
}
|
||||
|
||||
$this->renderListeners(Listener::TYPE_INLINE);
|
||||
$this->renderListeners(Listener::TYPE_BLOCK);
|
||||
|
||||
$buff = null;
|
||||
foreach ($this->_lines as $line) {
|
||||
$buff.= $line->output;
|
||||
}
|
||||
|
||||
return $buff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is a json or not.
|
||||
*
|
||||
* Example values which return true:
|
||||
*
|
||||
* ```php
|
||||
* Json::isJson('{"123":"456"}');
|
||||
* Json::isJson('{"123":456}');
|
||||
* Json::isJson('[{"123":"456"}]');
|
||||
* Json::isJson('[{"123":"456"}]');
|
||||
* ```
|
||||
*
|
||||
* @param mixed $value The value to test if its a json or not.
|
||||
* @return boolean Whether the string is a json or not.
|
||||
*/
|
||||
public static function isJson($value)
|
||||
{
|
||||
if (!is_scalar($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstChar = substr($value, 0, 1);
|
||||
|
||||
if ($firstChar !== '{' && $firstChar !== '[') {
|
||||
return false;
|
||||
}
|
||||
|
||||
json_decode($value);
|
||||
|
||||
return json_last_error() === JSON_ERROR_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a given json string into a php array.
|
||||
*
|
||||
* @param string $json Input json
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function decodeJson($json)
|
||||
{
|
||||
return json_decode($json, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape plain text output before mixing in a html context.
|
||||
*
|
||||
* This should be used on any input or attributes in a delta operation.
|
||||
* Double encoding is prevented on already encoded characters.
|
||||
* For escaping input, use Line->getInput() instead. Otherwise an inline listener would encode the tags from another nested inline listener.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @param string $value The value to escape.
|
||||
* @return string The escaped value, safe for usage in html, as long as $escapeInput is set to true.
|
||||
*/
|
||||
public function escape($value)
|
||||
{
|
||||
if (!$this->escapeInput) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return htmlspecialchars($value, $this->escapeFlags, $this->escapeEncoding, $double = false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
<?php
|
||||
|
||||
namespace nadar\quill;
|
||||
|
||||
/**
|
||||
* Line Object.
|
||||
*
|
||||
* A line object represents a line from the delta input. Lines are splited in the lexer object.
|
||||
*
|
||||
* @author Basil Suter <basil@nadar.io>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Line
|
||||
{
|
||||
/**
|
||||
* @var integer The status of a line which is not picked or done, which is default.
|
||||
*/
|
||||
public const STATUS_CLEAN = 1;
|
||||
|
||||
/**
|
||||
* @var integer The status of the line if its picked by a listener
|
||||
*/
|
||||
public const STATUS_PICKED = 2;
|
||||
|
||||
/**
|
||||
* @var integer The status of the line if some of the listener marked this line as done.
|
||||
*/
|
||||
public const STATUS_DONE = 3;
|
||||
|
||||
/**
|
||||
* @var array<string> An array with values which can be prependend to the actuall input string. This is mainly used if inline
|
||||
* elements are passed to the next "not" inline element.
|
||||
*/
|
||||
public $prepend = [];
|
||||
|
||||
/**
|
||||
* @var string The output is the value which will actually rendered by the lexer. So lines which directly write to the output
|
||||
* buffer needs to fill in this variable.
|
||||
*/
|
||||
public $output;
|
||||
|
||||
/**
|
||||
* @var string The input string which is assigned from the line parser. This is the actual content of the line itself!
|
||||
* @since 3.1.0
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* @var integer Holds the current status of the line.
|
||||
*/
|
||||
protected $status = 1;
|
||||
|
||||
/**
|
||||
* @var integer The ID/Index/Row of the line
|
||||
*/
|
||||
protected $index;
|
||||
|
||||
/**
|
||||
* @var array<mixed> An array with all attributes which are assigned to this lines. attribute can be inline markers like
|
||||
* bold, italic, links and so on.
|
||||
*/
|
||||
protected $attributes = [];
|
||||
|
||||
/**
|
||||
* @var Lexer The lexer object in order to access other lines and elements.
|
||||
*/
|
||||
protected $lexer;
|
||||
|
||||
/**
|
||||
* @var boolean Whether the current line is handled as "inline-line" or not. Inline lines have different effects when parsing the
|
||||
* end output. For example those can be skipped as they usual prepend the input value into the next line.
|
||||
*/
|
||||
protected $isInline = false;
|
||||
|
||||
/**
|
||||
* @var boolean Whether the current line is already escaped by a listener. If this is false, the next listener should preferable do so.
|
||||
* If this is true, it should not be done again by a next listener.
|
||||
* @since 1.2.0
|
||||
*/
|
||||
protected $isEscaped = false;
|
||||
|
||||
/**
|
||||
* @var boolean As certain elements has an end of newline but those are removed within the lexer opt to line methods we remember
|
||||
* this information here. If true this element has an \n element which has been original removed from input (as lines are spliited into
|
||||
* new lines).
|
||||
*/
|
||||
protected $hadEndNewline = false;
|
||||
|
||||
/**
|
||||
* @var boolean Whether this line has a newline or not, this information is already provided by the lines to ops method.
|
||||
*/
|
||||
protected $hasNewline;
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
private $_debug = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param integer $index The numberic index of the row within all the lines.
|
||||
* @param string $input The input value from the line parser for the current line.
|
||||
* @param array<mixed> $attributes
|
||||
* @param boolean $hadEndNewline Whether this element orignali had an newline at the end.
|
||||
* @param boolean $hasNewline
|
||||
*/
|
||||
public function __construct($index, $input, array $attributes, Lexer $lexer, $hadEndNewline, $hasNewline)
|
||||
{
|
||||
$this->index = $index;
|
||||
$this->input = $input;
|
||||
$this->attributes = $attributes;
|
||||
$this->lexer = $lexer;
|
||||
$this->hadEndNewline = $hadEndNewline;
|
||||
$this->hasNewline = $hasNewline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current line had a new line char or not, this is very important in terms of finding out wether its a block
|
||||
* element or inline element.
|
||||
*
|
||||
* This informations is assigned in the opsToLine() method in the lexer object.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasNewline()
|
||||
{
|
||||
return $this->hasNewline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this line as an end newline char or not.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasEndNewline()
|
||||
{
|
||||
return $this->hadEndNewline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this line is the first line or not.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isFirst()
|
||||
{
|
||||
return $this->previous() === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Lexer
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @return Lexer
|
||||
*/
|
||||
public function getLexer()
|
||||
{
|
||||
return $this->lexer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line's input in a safe way.
|
||||
*
|
||||
* Escaping for html is done if this wasn't done by a previous listener already.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @return string
|
||||
*/
|
||||
public function getInput()
|
||||
{
|
||||
if ($this->isEscaped()) {
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
return $this->lexer->escape($this->getUnsafeInput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw line's input, this might not be escaped for html context.
|
||||
*
|
||||
* > Note it could be escaped if a previous inline listener updated the input value
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @return string
|
||||
*/
|
||||
public function getUnsafeInput()
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array with attributes, if any.
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current line has attribute informations or not.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasAttributes()
|
||||
{
|
||||
return !empty($this->attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for a given attribute name, if not exists return false.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $defaultValue (@since 3.2.0)
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute($name, $defaultValue = false)
|
||||
{
|
||||
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new value to the prepend array.
|
||||
*
|
||||
* Certain elements needs to prepend values into the next element. The Line argument is required in order to
|
||||
* ensure the correct index for the prepend element.
|
||||
*
|
||||
* @param string $value The value to prepend.
|
||||
* @param Line $line The line which does the prepend, this is used to ensure the correctly order index of the elements. {@since 1.3.2}
|
||||
* @return void
|
||||
*/
|
||||
public function addPrepend($value, Line $line)
|
||||
{
|
||||
$this->prepend[$line->getIndex()] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string for the prepend values.
|
||||
*
|
||||
* @return string The prepend value for this line.
|
||||
*/
|
||||
public function renderPrepend()
|
||||
{
|
||||
ksort($this->prepend);
|
||||
return implode("", array_unique($this->prepend));
|
||||
}
|
||||
|
||||
/**
|
||||
* While trough lines forward or backwards define trough index until false is returned.
|
||||
*
|
||||
* An example how to while trough lines, increasing (down) the index until a certain condition
|
||||
* ($line->isFirst) happens writing lint input into a buffer variable.
|
||||
*
|
||||
* > Keep in mind that while() will contain the line where the function applys, so the first line will always be
|
||||
* > the line you apply the while() function.
|
||||
*
|
||||
* ```php
|
||||
* $buffer = null;
|
||||
*
|
||||
* $line->while(function (&$index, Line $line) use (&$buffer) {
|
||||
* $index++;
|
||||
* $buffer.= $line->input;
|
||||
*
|
||||
* if ($line->isFirst()) {
|
||||
* return false;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* echo $buffer;
|
||||
* ```
|
||||
*
|
||||
* > Keep in mind that `false` must be returned to stop the while process.
|
||||
*
|
||||
* @param callable $condition A callable which requires 2 params, the first is the index which is passed as reference,
|
||||
* second is the current line.
|
||||
* @return void
|
||||
*/
|
||||
public function while(callable $condition)
|
||||
{
|
||||
$iterate = true;
|
||||
$i = $this->getIndex();
|
||||
while ($iterate) {
|
||||
$line = $this->lexer->getLine($i);
|
||||
|
||||
if (!$line) {
|
||||
$iterate = false;
|
||||
break;
|
||||
}
|
||||
|
||||
$iterate = call_user_func_array($condition, [&$i, $line]);
|
||||
|
||||
if ($iterate !== false) {
|
||||
$iterate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* While loop down (to the next elements) until false is returend.
|
||||
*
|
||||
* > This method wont return the line.
|
||||
*
|
||||
* @param callable $condition The while condition until false is returned. The callable receives the Line as argument.
|
||||
* @since 1.3.0
|
||||
* @return void
|
||||
*/
|
||||
public function whileNext(callable $condition)
|
||||
{
|
||||
$next = $this->next();
|
||||
if ($next) {
|
||||
$next->while(static function (&$index, Line $line) use ($condition) {
|
||||
++$index;
|
||||
return call_user_func($condition, $line);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* While loop up (to the previous elements) until false is returend.
|
||||
*
|
||||
* > This method wont return the line.
|
||||
*
|
||||
* @param callable $condition The while condition until false is returned. The callable receives the Line as argument.
|
||||
* @since 1.3.0
|
||||
* @return void
|
||||
* @see see while() for better documentation
|
||||
*/
|
||||
public function whilePrevious(callable $condition)
|
||||
{
|
||||
$previous = $this->previous();
|
||||
if ($previous) {
|
||||
$previous->while(static function (&$index, Line $line) use ($condition) {
|
||||
--$index;
|
||||
return call_user_func($condition, $line);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iteration helper the go forward and backward in lines.
|
||||
*
|
||||
* The condition contains whether index should go up or down.
|
||||
*
|
||||
* ```php
|
||||
* return $this->iterate($line, function ($i) {
|
||||
* return $i+1;
|
||||
* }, function(Line $line) {
|
||||
* // will stop the process and return this current line
|
||||
* return true;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param Line $line
|
||||
* @param callable $condition The condition callable for the index
|
||||
* @param callable $fn The function which is returend to determine whether this line should be picked or not.
|
||||
* @return boolean|Line
|
||||
*/
|
||||
protected function iterate(Line $line, callable $condition, callable $fn)
|
||||
{
|
||||
$i = $line->getIndex();
|
||||
$iterate = true;
|
||||
$response = false;
|
||||
while ($iterate) {
|
||||
$i = call_user_func($condition, $i);
|
||||
$elmn = $this->lexer->getLine($i);
|
||||
// no next element found
|
||||
if (!$elmn) {
|
||||
$iterate = false;
|
||||
} elseif (call_user_func($fn, $elmn)) {
|
||||
// fn match (return true) return current element.
|
||||
$response = $elmn;
|
||||
$iterate = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next element.
|
||||
*
|
||||
* If a closure is provided you can define a condition of whether next element should be taken or not.
|
||||
*
|
||||
* For example you can iterate to the next element which is not empty:
|
||||
*
|
||||
* ```php
|
||||
* $nextNotEmpty = $line->next(function(Line $line) {
|
||||
* return !$line->isEmpty();
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* if true is returned this line will be assigned.
|
||||
*
|
||||
* @param callable $fn A function in order to determined whether this is the next element or not, if not provided the first next element is returned.
|
||||
* @return Line|boolean The method will return either a Line object or false, if there is no next Line.
|
||||
*/
|
||||
public function next($fn = null)
|
||||
{
|
||||
if ($fn === null) {
|
||||
return $this->lexer->getLine($this->index + 1);
|
||||
}
|
||||
|
||||
return $this->iterate($this, static function ($i) {
|
||||
return $i+1;
|
||||
}, $fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous line.
|
||||
*
|
||||
* If no previous line exists, false is returned.
|
||||
*
|
||||
* ```php
|
||||
* $nextNotEmpty = $line->previous(function(Line $line) {
|
||||
* return !$line->isEmpty();
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* if true is returned this line will be assigned.
|
||||
*
|
||||
* @param callable $fn A function in order to determined whether this is the previous element or not, if not provided the first previous element is returned.
|
||||
* @return Line|boolean The method will return either a Line object or false, if there is no next Line.
|
||||
*/
|
||||
public function previous($fn = null)
|
||||
{
|
||||
if ($fn === null) {
|
||||
return $this->lexer->getLine($this->index - 1);
|
||||
}
|
||||
|
||||
return $this->iterate($this, static function ($i) {
|
||||
return $i-1;
|
||||
}, $fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the line's input.
|
||||
*
|
||||
* The new input is assumed to be escaped.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param string $newInput
|
||||
* @return void
|
||||
*/
|
||||
public function setInput($newInput)
|
||||
{
|
||||
$this->input = $newInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method whether the current element is inline or not.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setAsInline()
|
||||
{
|
||||
$this->isInline = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether current line is an inline line or not.
|
||||
*/
|
||||
public function isInline(): bool
|
||||
{
|
||||
return $this->isInline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method whether the current line is escaped or not.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @return void
|
||||
*/
|
||||
public function setAsEscaped()
|
||||
{
|
||||
$this->isEscaped = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current line is escaped or not.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEscaped()
|
||||
{
|
||||
return $this->isEscaped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for the index of the line.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this line as picked.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPicked()
|
||||
{
|
||||
$this->status = self::STATUS_PICKED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether current line is picked or not. If the line has been picked an is marked as
|
||||
* done, is picked will return false.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isPicked()
|
||||
{
|
||||
return $this->status == self::STATUS_PICKED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this line as done.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDone()
|
||||
{
|
||||
$this->status = self::STATUS_DONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this line is done or not.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isDone()
|
||||
{
|
||||
return $this->status == self::STATUS_DONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether current line as empty content string or not.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return $this->input == '' && empty($this->prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some plugins have a json as insert value, in order to detected such values
|
||||
* you can use this method.
|
||||
*
|
||||
* This will check if the given insert string is a json. In order to deocde the
|
||||
* json into a php array afterwards, you can use getArrayInsert();
|
||||
*
|
||||
* @return boolean Whether current insert string is a json or not.
|
||||
*/
|
||||
public function isJsonInsert()
|
||||
{
|
||||
return Lexer::isJson($this->input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the insert json as array.
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getArrayInsert()
|
||||
{
|
||||
return Lexer::decodeJson($this->input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether insert is json/array input. if yes return the requres key.
|
||||
*
|
||||
* @param string $key The key from the json array
|
||||
* @return mixed
|
||||
*/
|
||||
public function insertJsonKey($key)
|
||||
{
|
||||
if (!$this->isJsonInsert()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$insert = $this->getArrayInsert();
|
||||
|
||||
return array_key_exists($key, $insert) ? $insert[$key] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add debug message for this line if {{Lexer::$debug}} is enabled.
|
||||
*
|
||||
* @param string $message The message which should be logged.
|
||||
* @since 1.3.0
|
||||
* @return void
|
||||
*/
|
||||
public function debugInfo($message)
|
||||
{
|
||||
if ($this->lexer->debug) {
|
||||
$this->_debug[] = $message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array with all debug informations
|
||||
*
|
||||
* @return array<string>
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public function getDebugInfo(): array
|
||||
{
|
||||
return $this->_debug;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace nadar\quill;
|
||||
|
||||
/**
|
||||
* Listener Object.
|
||||
*
|
||||
* Every type of element is a listener. Listeners are "listening" to every line of delta code and can
|
||||
* pick and process this line.
|
||||
*
|
||||
* @author Basil Suter <basil@nadar.io>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class Listener
|
||||
{
|
||||
/**
|
||||
* @var integer Type inline listener
|
||||
*/
|
||||
public const TYPE_INLINE = 1;
|
||||
|
||||
/**
|
||||
* @var integer Type block listener
|
||||
*/
|
||||
public const TYPE_BLOCK = 2;
|
||||
|
||||
/**
|
||||
* @var integer First priority listener within the given type
|
||||
*/
|
||||
public const PRIORITY_EARLY_BIRD = 1;
|
||||
|
||||
/**
|
||||
* @var integer Second priority listener within the given type. This is currently only used
|
||||
* for TEXT listeners - as they need to be the very last entry.
|
||||
*/
|
||||
public const PRIORITY_GARBAGE_COLLECTOR = 2;
|
||||
|
||||
/**
|
||||
* Get the listener type, either INLINE (1) or BLOCK (2)
|
||||
*/
|
||||
abstract public function type(): int;
|
||||
|
||||
/**
|
||||
* Process the line
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function process(Line $line);
|
||||
|
||||
/**
|
||||
* Get the priority either 1 or 2
|
||||
*/
|
||||
public function priority(): int
|
||||
{
|
||||
return self::PRIORITY_EARLY_BIRD;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array<mixed>
|
||||
*/
|
||||
private $_picks = [];
|
||||
|
||||
/**
|
||||
* Pick a certain line during the process() process in order to use them later in render method.
|
||||
*
|
||||
* If a line is picked, the status of the line switches to picked.
|
||||
*
|
||||
* @param Line $line
|
||||
* @param array<mixed> $options
|
||||
* @return void
|
||||
*/
|
||||
public function pick(Line $line, array $options = [])
|
||||
{
|
||||
$line->setPicked();
|
||||
$line->debugInfo('picked by ' . static::class);
|
||||
$this->_picks[] = new Pick($this, $line, $options, count($this->_picks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all picked lineds.
|
||||
*
|
||||
* @return Pick[] An array with Line objects, for IDE purposes we return the Line object as phpdoc
|
||||
*/
|
||||
public function picks(): array
|
||||
{
|
||||
return $this->_picks;
|
||||
}
|
||||
|
||||
/**
|
||||
* The render method is processed after the process() method is done.
|
||||
*
|
||||
* Its the right place to go forward and backward in lines if you need to, as at the point all lines
|
||||
* are allready processed trough process() method.
|
||||
*
|
||||
* @param Lexer $lexer
|
||||
* @return void
|
||||
*/
|
||||
public function render(Lexer $lexer)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace nadar\quill;
|
||||
|
||||
/**
|
||||
* Pick Object holds the line which is picked with optional data.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @author Basil Suter <basil@nadar.io>
|
||||
*/
|
||||
class Pick
|
||||
{
|
||||
/**
|
||||
* @var Line
|
||||
*/
|
||||
public $line;
|
||||
|
||||
/**
|
||||
* @var array<mixed> An array with options which can be read trough magical getter
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* @var integer Contains the current index number for this pick.
|
||||
*/
|
||||
protected $index;
|
||||
|
||||
/**
|
||||
* @var Listener The listener object the picke was made from.
|
||||
*/
|
||||
protected $listener;
|
||||
|
||||
/**
|
||||
* Create new line pick
|
||||
*
|
||||
* @param array<mixed> $options
|
||||
* @param integer $index Index of picks, starts with 0
|
||||
*/
|
||||
public function __construct(Listener $listener, Line $line, array $options, $index)
|
||||
{
|
||||
$this->listener = $listener;
|
||||
$this->line = $line;
|
||||
$this->options = $options;
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $name
|
||||
* @deprecated Deprecated in 3.1 will be removed in 4.0 use `optionValue($name)` instead.
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
trigger_error(sprintf("Deprecated in 3.1 will be removed in 4.0 use `optionValue('%s')` instead.", $name), E_USER_NOTICE);
|
||||
return $this->options[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of an option if available
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $defaultValue
|
||||
* @return mixed
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public function optionValue($name, $defaultValue = null)
|
||||
{
|
||||
return array_key_exists($name, $this->options) ? $this->options[$name] : $defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether current pick is the first pick inside the list of picks.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isFirst()
|
||||
{
|
||||
return $this->index == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether current pick is the last pick inside the list of picks.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isLast()
|
||||
{
|
||||
return (count($this->listener->picks()) - 1) == $this->index;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1214 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user