Pwirth has uploaded a new change for review. https://gerrit.wikimedia.org/r/311707
Change subject: BSFoundation: Added BSEntity base classes ...................................................................... BSFoundation: Added BSEntity base classes * Moved Entity base classes from BlueSpiceSocial to BlueSpiceFoundation, so everyone could use them Change-Id: Ie6f706688c869ac0ccdc5fd11dbd79fcd02966c1 --- M extension.json A includes/Entity.php A includes/EntityRegistry.php A includes/content/EntityContent.php A includes/content/EntityContentHandler.php A includes/entityconfigs/EntityConfig.php 6 files changed, 771 insertions(+), 1 deletion(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/BlueSpiceFoundation refs/changes/07/311707/1 diff --git a/extension.json b/extension.json index e0a8151..27d3d1a 100644 --- a/extension.json +++ b/extension.json @@ -228,7 +228,12 @@ "BsACTION": "includes/Common.php", "BsSTYLEMEDIA": "includes/Common.php", "EXTINFO": "includes/Common.php", - "EXTTYPE": "includes/Common.php" + "EXTTYPE": "includes/Common.php", + "BSEntity": "includes/Entity.php", + "BSEntityRegistry": "includes/EntityRegistry.php", + "BSEntityConfig": "includes/entityconfigs/EntityConfig.php", + "BSEntityContent": "includes/content/EntityContent.php", + "BSEntityContentHandler": "includes/content/EntityContentHandler.php" }, "bsgConfigFiles": [], "load_composer_autoloader": true, diff --git a/includes/Entity.php b/includes/Entity.php new file mode 100644 index 0000000..c0ab4ba --- /dev/null +++ b/includes/Entity.php @@ -0,0 +1,475 @@ +<?php +/** + * BSEntity base extension for BlueSpice + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * This file is part of BlueSpice for MediaWiki + * For further information visit http://bluespice.com + * + * @author Patric Wirth <wi...@hallowelt.com> + * @version 2.27.0 + * @package BlueSpiceSocial + * @subpackage BSEntity + * @copyright Copyright (C) 2016 Hallo Welt! GmbH, All rights reserved. + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License v2 or later + * @filesource + */ +class BSEntity { + protected function initExt() {} + const NS = ''; + protected static $aEntitiesByID = array(); + protected $bUnsavedChanges = true; + + protected $oConfig = null; + + protected $iID = 0; + protected $iOwnerID = 0; + protected $sType = ''; + + protected function __construct( $o, $oConfig ) { + $this->oConfig = $oConfig; + if( !empty($o->id) ) { + $this->iID = (int) $o->id; + $this->bUnsavedChanges = false; + } + if( !empty($o->ownerid) ) { + $this->iOwnerID = $o->ownerid; + } + if( !empty($o->type) ) { + $this->sType = $o->type; + } + } + + protected static function factory( $sType, $oData ) { + $oConfig = BSEntityConfig::factory( $sType ); + if( !$oConfig instanceof BSEntityConfig ) { + return null; + } + + $sEntity = $oConfig->get('EntityClass'); + $oInstance = new $sEntity($oData, $oConfig); + //error_log(var_export($oInstance,1)); + return $oInstance; + } + + protected function generateID() { + //this is the case if the current BSEntity is new (no Title created yet) + if ( (int) $this->getID() === 0 ) { + $dbw = wfGetDB( DB_MASTER ); + $res = $dbw->selectRow( + 'page', + 'page_title', + array( 'page_namespace' => static::NS ), + __METHOD__, + array( 'ORDER BY' => 'page_id DESC' ) + ); + + if ( $res ) { + $this->iID = (int) $res->page_title + 1; + } else { + $this->iID = 1; + } + } + } + + /** + * Get BSEntity by BSEntityContent Object, wrapper for newFromObject + * @param BSEntityContent $sContent + * @return BSEntity + */ + public static function newFromContent( BSEntityContent $sContent ) { + $aContent = $sContent->getJsonData(); + if ( empty($aContent) ) { + return null; + } + + return static::newFromObject( (object) $aContent ); + } + + /** + * Get BSEntity by Json Object + * @param Object $oObject + * @return BSEntity + */ + public static function newFromObject( $oObject ) { + if( !is_object($oObject) ) { + return null; + } + + if( empty($oObject->type) ) { + //$oObject->type = BSEntityRegistry::getDefaultHandlerType(); + return null; + } + if( !BSEntityRegistry::isRegisteredType($oObject->type) ) { + return null; + } + + if( !empty($oObject->id) && (int) $oObject->id !== 0 ) { + return static::newFromID( $oObject->id ); + } + + $oInstance = static::factory( + $oObject->type, + $oObject + ); + return static::appendCache( $oInstance ); + } + + /** + * Gets the related config object + * @return BSEntityConfig + */ + public function getConfig() { + return $this->oConfig; + } + + /** + * Gets the related Title object by ID + * @return Title + */ + public static function getTitleFor( $iID ) { + return Title::newFromText( $iID, static::NS ); + } + + /** + * Gets the related Title object + * @return Title + */ + public function getTitle() { + return static::getTitleFor( $this->iID ); + } + + public function getTimestampTouched() { + return $this->getTitle()->getTouched(); + } + + public function getTimestampCreated() { + return $this->getTitle()->getEarliestRevTime(); + } + + /** + * Get BSEntity from ID, wrapper for newFromTitle + * @param int $iID + * @param boolean $bForceReload + * @return BSEntity | null + */ + public static function newFromID( $iID, $bForceReload = false ) { + if ( !is_numeric( $iID ) ) { + return null; + } + $iID = (int) $iID; + + if ( !$bForceReload && static::hasCacheEntry( $iID ) ) { + return static::getInstanceFromCacheByID( $iID ); + } + + $oTitle = static::getTitleFor( $iID ); + + if( is_null($oTitle) || !$oTitle->exists() ) { + return null; + } + + $sText = BsPageContentProvider::getInstance()->getContentFromTitle( + $oTitle + ); + + $oBSEntityContent = new JsonContent( $sText ); + $oData = (object) $oBSEntityContent->getJsonData(); + + if( empty($oData->type) ) { + return null; + //$oData->type = BSEntityRegistry::getDefaultHandlerType(); + } + if( !BSEntityRegistry::isRegisteredType($oData->type) ) { + return null; + } + + $oInstace = static::factory( + $oData->type, + $oData + ); + return static::appendCache( $oInstace ); + } + + /** + * Main method for getting a BSEntity from a Title + * @param Title $oTitle + * @param boolean $bForceReload + * @return BSEntity + */ + public static function newFromTitle( Title $oTitle, $bForceReload = false ) { + //$oBSEntity = new BSEntity(); + if ( is_null( $oTitle ) || $oTitle->getNamespace() !== static::NS ) { + return null; + } + //if( !$oTitle->exists() ) { + $iID = (int) $oTitle->getText(); + + return static::newFromID( $iID, $bForceReload ); + } + + /** + * Saves the current BSEntity + * @return Status + */ + public function save( User $oUser = null, $aOptions = array() ) { + if( !$oUser instanceof User ) { + return Status::newFatal( 'No User' ); + } + if( !$this->hasUnsavedChanges() ) { + return Status::newGood('success'); + } + if( empty($this->getID()) ) { + $this->generateID(); + } + if( empty($this->getID()) ) { + return Status::newFatal( 'No ID generated' ); + } + if( empty($this->getOwnerID()) ) { + $this->setOwnerID( (int) $oUser->getId() ); + } + $sType = $this->getType(); + if( empty($sType) ) { + return Status::newFatal('Related Type error'); + } + + $oTitle = $this->getTitle(); + if ( is_null( $oTitle ) ) { + return Status::newFatal('Related Title error'); + } + + $oWikiPage = new WikiPage( $oTitle ); + $sContentClass = $this->getConfig()->get('ContentClass'); + try { + $oStatus = $oWikiPage->doEditContent( + new $sContentClass( $this->toJson() ), + "", + 0, + 0, + $oUser, + null + ); + } catch( Exception $e ) { + //Something probalby breaks json + return Status::newFatal( $e->getMessage() ); + } + //TODO: check why this is not good + if ( !$oStatus->isOK() ) { + return $oStatus; + } + + $this->setUnsavedChanges( false ); + + wfRunHooks( 'BSEntitySaveComplete', array( $this, $oStatus, $oUser ) ); + + $this->invalidateCache(); + return $oStatus; + } + + /** + * Deletes the current BSEntity + * @return Status + */ + public function delete( User $oUser = null, $aOptions = array() ) { + $oTitle = $this->getTitle(); + + $oStatus = null; + $oWikiPage = new WikiPage( $oTitle ); + + wfRunHooks( 'BSEntityDelete', array( $this, $oStatus, $oUser ) ); + if( $oStatus instanceof Status && $oStatus->isOK() ) { + return $oStatus; + } + + try { + $sError = ''; + $b = $oWikiPage->doDeleteArticle( + '', + false, + 0, + true, + $sError, + $oUser + ); + } catch( Exception $e ) { + error_log( $e->getMessage() ); + return Status::newFatal( $e->getMessage() ); + } + + $oStatus = Status::newGood( 'success' ); + wfRunHooks( 'BSEntityDeleteComplete', array( $this, $oStatus ) ); + if( !$oStatus->isOK() ) { + return $oStatus; + } + + $this->invalidateCache(); + return $oStatus; + } + + /** + * Gets the BSEntity attributes formated for the api + * @return object + */ + public function getFullData( $a = array() ) { + return array_merge( + $a, + array( + 'id' => $this->getID(), + 'ownerid' => $this->getOwnerID(), + 'type' => $this->getType(), + ) + ); + } + + /** + * Adds a BSEntity to the cache + * @param BSEntity $oInstance + * @return BSEntity + */ + protected static function appendCache( BSEntity &$oInstance ) { + if( static::hasCacheEntry($oInstance->getID()) ) { + return $oInstance; + } + static::$aEntitiesByID[$oInstance->getID()] = $oInstance; + return $oInstance; + } + + /** + * Removes a BSEntity from the cache if it's in + * @param BSEntity $oInstance + * @return BSEntity + */ + protected static function detachCache( BSEntity &$oInstance ) { + if( !static::hasCacheEntry($oInstance->getID()) ) { + return $oInstance; + } + unset( static::$aEntitiesByID[$oInstance->getID()] ); + return $oInstance; + } + + /** + * Gets a instance of the BSEntity from the cache by ID + * @param int $iID + * @return BSEntity + */ + protected static function getInstanceFromCacheByID( $iID ) { + if( !static::hasCacheEntry( $iID ) ) { + return null; + } + return static::$aEntitiesByID[(int) $iID]; + } + + protected static function hasCacheEntry( $iID ) { + return isset( static::$aEntitiesByID[(int) $iID] ); + } + + /** + * Checks, if the current BSEntity exists in the Wiki + * @return boolean + */ + public function exists() { + $bExists = $this->getID() > 0 ? true : false; + if ( !$bExists ) { + return false; + } + $oTitle = $this->getTitle(); + if ( is_null( $oTitle ) ) { + return false; + } + return $oTitle->exists(); + } + + /** + * Checks if there are unsaved changes + * @return boolean + */ + public function hasUnsavedChanges() { + return (bool) $this->bUnsavedChanges; + } + + /** + * Returns the current id for the BSEntity + * @return int + */ + public function getID() { + return (int) $this->iID; + } + + /** + * Returns the current user id for the BSEntity + * @return int + */ + public function getOwnerID() { + return (int) $this->iOwnerID; + } + + /** + * Returns the current type for the BSSocialEntity + * @return String + */ + public function getType() { + return $this->sType; + } + + /** + * Sets the current BSEntity to an unsaved changes mode, refreshes cache + * @param String $bStatus + * @return BSEntity + */ + public function setUnsavedChanges( $bStatus = true ) { + $this->bUnsavedChanges = (bool) $bStatus; + return $this; + } + + /** + * Sets the current user ID + * @param int + * @return BSEntity + */ + public function setOwnerID( $iOwnerID ) { + return $this->setUnsavedChanges( + $this->iOwnerID = (int) $iOwnerID + ); + } + + /** + * Subclass needs to return the current BSEntity as a Json encoded + * string + * @return stdObject - Subclass needs to return encoded string! + */ + public function toJson() { + return json_encode( (object) static::getFullData() ); + } + + public function setValuesByObject( $o ) { + if( isset($o->ownerid) ) { + $this->setOwnerID( $o->ownerid ); + } + } + + public function userIsOwner( User $oUser ) { + if( $oUser->isAnon() || $this->getOwnerID() < 1) { + return false; + } + return $oUser->getId() == $this->getOwnerID(); + } + + public function invalidateCache() { + $this->getTitle()->invalidateCache(); + static::detachCache( $this ); + return $this; + } +} \ No newline at end of file diff --git a/includes/EntityRegistry.php b/includes/EntityRegistry.php new file mode 100644 index 0000000..4d4a5a5 --- /dev/null +++ b/includes/EntityRegistry.php @@ -0,0 +1,82 @@ +<?php + +/** + * BSEntityRegistry class for BSSocial + * + * add desc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * This file is part of BlueSpice for MediaWiki + * For further information visit http://bluespice.com + * + * @author Patric Wirth <wi...@hallowelt.com> + * @package BlueSpiceSocial + * @subpackage BSSocial + * @copyright Copyright (C) 2016 Hallo Welt! GmbH, All rights reserved. + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License v2 or later + * @filesource + */ + +/** + * BSEntityRegistry class for BSSocial extension + * @package BlueSpiceSocial + * @subpackage BSSocial + */ +class BSEntityRegistry { + private function __construct() {} + private static $bEntitiesRegistered = false; + private static $aEntities = array(); + + protected static function runRegister( $bForceReload = false ) { + if( static::$bEntitiesRegistered && !$bForceReload ) { + return true; + } + + $b = wfRunHooks( 'BSEntityRegister', array( + &self::$aEntities, + //&self::$sDefaultHandlerType, + )); + + return $b ? static::$bEntitiesRegistered = true : $b; + } + + public static function getRegisteredEntities() { + if( !self::runRegister() ) { + return array(); + } + return self::$aEntities; + } + + public static function isRegisteredType( $sType ) { + return in_array( + $sType, + self::getRegisterdTypeKeys() + ); + } + + public static function getRegisteredEntityByType( $sType ) { + if( !self::isRegisteredType($sType) ) { + return array(); + } + return self::$aEntities[$sType]; + } + + public static function getRegisterdTypeKeys() { + return array_keys( + self::getRegisteredEntities() + ); + } +} \ No newline at end of file diff --git a/includes/content/EntityContent.php b/includes/content/EntityContent.php new file mode 100644 index 0000000..6e317f4 --- /dev/null +++ b/includes/content/EntityContent.php @@ -0,0 +1,108 @@ +<?php + +abstract class BSEntityContent extends JsonContent { + + public function __construct( $text, $modelId = '' ) { + parent::__construct( $text, $modelId ); + } + + /** + * Decodes the JSON into a PHP associative array. + * @return array + */ + public function getJsonData() { + return FormatJson::decode( $this->getNativeData(), true ); + } + + /** + * @return bool Whether content is valid JSON. + */ + public function isValid() { + return $this->getJsonData() !== null; + } + + /** + * Pretty-print JSON + * + * @return bool|null|string + */ + public function beautifyJSON() { + $decoded = FormatJson::decode( $this->getNativeData(), true ); + if ( !is_array( $decoded ) ) { + return null; + } + return FormatJson::encode( $decoded, true ); + + } + + /** + * Beautifies JSON prior to save. + * @param Title $title Title + * @param User $user User + * @param ParserOptions $popts + * @return JsonContent + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { + return new static( $this->beautifyJSON() ); + } + + /** + * Set the HTML and add the appropriate styles + * + * + * @param Title $title + * @param int $revId + * @param ParserOptions $options + * @param bool $generateHtml + * @param ParserOutput $output + */ + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output + ) { + return parent::fillParserOutput( + $title, + $revId, + $options, + $generateHtml, + $output + ); + } + /** + * Constructs an HTML representation of a JSON object. + * @param array $mapping + * @return string HTML + */ + protected function objectTable( $mapping ) { + $rows = array(); + + foreach ( $mapping as $key => $val ) { + $rows[] = $this->objectRow( $key, $val ); + } + return Xml::tags( 'table', array( 'class' => 'mw-json' ), + Xml::tags( 'tbody', array(), join( "\n", $rows ) ) + ); + } + + /** + * Constructs HTML representation of a single key-value pair. + * @param string $key + * @param mixed $val + * @return string HTML. + */ + protected function objectRow( $key, $val ) { + $th = Xml::elementClean( 'th', array(), $key ); + if ( is_array( $val ) ) { + $td = Xml::tags( 'td', array(), self::objectTable( $val ) ); + } else { + if ( is_string( $val ) ) { + $val = '"' . $val . '"'; + } else { + $val = FormatJson::encode( $val ); + } + + $td = Xml::elementClean( 'td', array( 'class' => 'value' ), $val ); + } + + return Xml::tags( 'tr', array(), $th . $td ); + } +} \ No newline at end of file diff --git a/includes/content/EntityContentHandler.php b/includes/content/EntityContentHandler.php new file mode 100644 index 0000000..ad86f3c --- /dev/null +++ b/includes/content/EntityContentHandler.php @@ -0,0 +1,15 @@ +<?php + +abstract class BSEntityContentHandler extends JsonContentHandler { + + public function __construct( $modelId = '' ) { + parent::__construct( $modelId ); + } + + /** + * @return string + */ + protected function getContentClass() { + return 'BSEntityContent'; + } +} \ No newline at end of file diff --git a/includes/entityconfigs/EntityConfig.php b/includes/entityconfigs/EntityConfig.php new file mode 100644 index 0000000..bf73971 --- /dev/null +++ b/includes/entityconfigs/EntityConfig.php @@ -0,0 +1,85 @@ +<?php + +/** + * BSEntityConfig class for BSSocial + * + * add desc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * This file is part of BlueSpice for MediaWiki + * For further information visit http://bluespice.com + * + * @author Patric Wirth <wi...@hallowelt.com> + * @package BlueSpiceSocial + * @subpackage BSSocial + * @copyright Copyright (C) 2016 Hallo Welt! GmbH, All rights reserved. + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License v2 or later + * @filesource + */ + +/** + * BSEntityConfig class for BSSocial extension + * @package BlueSpiceSocial + * @subpackage BSSocial + */ +abstract class BSEntityConfig { + protected static $aEntityConfigs = null; + protected static $aDefaults = array(); + protected $sType = ''; + + public static function factory( $sType ) { + if( !is_null(static::$aEntityConfigs) ) { + if( !isset(static::$aEntityConfigs[$sType]) ) { + return null; + } + return static::$aEntityConfigs[$sType]; + } + //TODO: Check params and classes + $aRegisteredEntities = BSEntityRegistry::getRegisteredEntities(); + foreach( $aRegisteredEntities as $sKey => $sConfigClass ) { + static::$aEntityConfigs[$sKey] = new $sConfigClass(); + static::$aEntityConfigs[$sKey]->sType = $sKey; + array_merge( + static::$aDefaults, + static::$aEntityConfigs[$sKey]->addGetterDefaults() + ); + } + Hooks::run( 'BSEntityConfigDefaults', array( &static::$aDefaults ) ); + return static::$aEntityConfigs[$sType]; + } + + protected static function getDefault( $sMethod ) { + if( !isset(static::$aDefaults[$sMethod]) ) { + return false; + } + return static::$aDefaults[$sMethod]; + } + + public function get( $sMethod ) { + $sMethod = "get_$sMethod"; + if( !is_callable( array($this, $sMethod) ) ) { + static::getDefault( $sMethod ); + } + return $this->$sMethod(); + } + + abstract public function addGetterDefaults(); + abstract public function get_EntityClass(); + + public function get_ContentClass() { + return 'BSEntityContent'; + } +} \ No newline at end of file -- To view, visit https://gerrit.wikimedia.org/r/311707 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ie6f706688c869ac0ccdc5fd11dbd79fcd02966c1 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/BlueSpiceFoundation Gerrit-Branch: master Gerrit-Owner: Pwirth <wi...@hallowelt.biz> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits