jenkins-bot has submitted this change and it was merged.

Change subject: Merge from master
......................................................................


Merge from master

This change contains several new API base classes from the master branch.
Those are needed by some extensions like InsertFile, NSFileRepoConnector
and BookshelfUI

See also https://gerrit.wikimedia.org/r/#/c/224409/

Change-Id: I6dc475aac57bfdeba0cabe02984bf61e3d153753
---
M BlueSpiceFoundation.php
M includes/AutoLoader.php
M includes/CoreHooks.php
M includes/api/ApiFormatJsonMW.php
A includes/api/BSApiExtJSStoreBase.php
A includes/api/BSApiFileBackendStore.php
A includes/api/BSApiTasksBase.php
A includes/api/BSStandardAPIResponse.php
M includes/api/BsApiBase.php
A resources/bluespice.extjs/BS/model/File.js
10 files changed, 884 insertions(+), 181 deletions(-)

Approvals:
  Robert Vogel: Looks good to me, approved
  Pwirth: Looks good to me, but someone else must approve
  jenkins-bot: Verified



diff --git a/BlueSpiceFoundation.php b/BlueSpiceFoundation.php
index c24d5d9..2f708ca 100644
--- a/BlueSpiceFoundation.php
+++ b/BlueSpiceFoundation.php
@@ -65,6 +65,8 @@
 $wgAjaxExportList[] = 'BsCommonAJAXInterface::getFileUrl';
 $wgAjaxExportList[] = 'BsCore::ajaxBSPing';
 
+$wgAPIModules['bs-filebackend-store'] = 'BSApiFileBackendStore';
+
 //I18N MW1.23+
 $wgMessagesDirs['BlueSpice'] = __DIR__ . '/i18n/core';
 $wgMessagesDirs['BlueSpiceCredits'] = __DIR__ . '/i18n/credits';
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 03fbc59..7183f8d 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -51,7 +51,11 @@
 $GLOBALS['wgAutoloadClasses']['BsExtJSSortParam'] = 
__DIR__."/ExtJSStoreParams.php";
 
 //api
-$GLOBALS['wgAutoloadClasses']['BsApiBase'] = __DIR__."/api/BsApiBase.php";
+$GLOBALS['wgAutoloadClasses']['BSStandardAPIResponse'] = 
__DIR__."/api/BSStandardAPIResponse.php";
+$GLOBALS['wgAutoloadClasses']['BSApiBase'] = __DIR__."/api/BSApiBase.php";
+$GLOBALS['wgAutoloadClasses']['BSApiTasksBase'] = 
__DIR__."/api/BSApiTasksBase.php";
+$GLOBALS['wgAutoloadClasses']['BSApiExtJSStoreBase'] = 
__DIR__."/api/BSApiExtJSStoreBase.php";
+$GLOBALS['wgAutoloadClasses']['BSApiFileBackendStore'] = 
__DIR__."/api/BSApiFileBackendStore.php";
 
 //adapter
 $GLOBALS['wgAutoloadClasses']['BsExtensionMW'] = 
__DIR__."/ExtensionMW.class.php";
diff --git a/includes/CoreHooks.php b/includes/CoreHooks.php
index 7da6192..1c430c4 100644
--- a/includes/CoreHooks.php
+++ b/includes/CoreHooks.php
@@ -435,26 +435,26 @@
 
        /**
         * Additional chances to reject an uploaded file
-        * @param string $saveName: destination file name
-        * @param string $tempName: filesystem path to the temporary file for 
checks
-        * @param string &$error: output: message key for message to show if 
upload canceled by returning false. May also be an array, where the first 
element
-                                                                               
is the message key and the remaining elements are used as parameters to the 
message.
-        * @return bool true on success , false on failure
+        * @param String $sSaveName: Destination file name
+        * @param String $sTempName: Filesystem path to the temporary file for 
checks
+        * @param String &$sError: output: message key for message to show if 
upload canceled by returning false
+        * @return Boolean true on success , false on failure
         */
        public static function onUploadVerification( $sSaveName, $sTempName, 
&$sError ) {
-               $aParts = explode( '.', $sSaveName );
+               if( empty( $sSaveName ) || !$iFileExt = strrpos( $sSaveName, 
'.' ) ) {
+                       return true;
+               }
 
-               if ( !empty( $aParts[0] ) ) {
-                       $oUser = User::newFromName( $aParts[0] );
+               $sUser = substr( $sSaveName, 0, $iFileExt );
+               $oUser = User::newFromName( $sUser );
+               if( is_null( $oUser ) || $oUser->getId() == 0 ) {
+                       return true;
+               }
 
-                       if ( $oUser->getId() != 0 ) {
-                               $oCurrUser = 
RequestContext::getMain()->getUser();
-
-                               if ( strcasecmp( $oUser->getName(), 
$oCurrUser->getName() ) !== 0 ) {
-                                       $sError = 'bs-imageofotheruser';
-                                       return false;
-                               }
-                       }
+               $oCurrUser = RequestContext::getMain()->getUser();
+               if( $oUser->getId() !== $oCurrUser->getId() ) {
+                       $sError = 'bs-imageofotheruser';
+                       return false;
                }
 
                return true;
diff --git a/includes/api/ApiFormatJsonMW.php b/includes/api/ApiFormatJsonMW.php
index d087043..aa39b4b 100644
--- a/includes/api/ApiFormatJsonMW.php
+++ b/includes/api/ApiFormatJsonMW.php
@@ -38,14 +38,15 @@
                }
 
                if ( defined( 'ApiResult::META_CONTENT' ) ) {
-                       $data = $this->getResult()->getResultData();
-                       $data = ApiResult::transformForBC( $data );
-                       $data = ApiResult::transformForTypes( $data, array( 
'BC' => true ) );
-                       $data = ApiResult::removeMetadata( $data );
+                       $data = $this->getResult()->getResultData( null, array(
+                               'BC' => array(),
+                               'Types' => array(),
+                               'Strip' => 'all',
+                       ) );
                } else {
                        $data = $this->getResultData();
                }
- 
+
                $this->printText(
                        $prefix .
                        FormatJson::encode( $data, $this->getIsHtml() ) .
diff --git a/includes/api/BSApiExtJSStoreBase.php 
b/includes/api/BSApiExtJSStoreBase.php
new file mode 100644
index 0000000..9af6019
--- /dev/null
+++ b/includes/api/BSApiExtJSStoreBase.php
@@ -0,0 +1,433 @@
+<?php
+/**
+ *  This class serves as a backend for ExtJS stores. It allows all
+ * necessary parameters and provides convenience methods and a standard ouput
+ * format
+ *
+ * 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://www.blue-spice.org
+ *
+ * @author     Robert Vogel <[email protected]>
+ * @author     Patric Wirth <[email protected]>
+ * @package    Bluespice_Foundation
+ * @copyright  Copyright (C) 2015 Hallo Welt! - Medienwerkstatt GmbH, All 
rights reserved.
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License v2 or 
later
+ * @filesource
+ *
+ * Example request parameters of an ExtJS store
+
+       _dc:1430126252980
+       filter:[
+               {
+                       "type":"string",
+                       "comparison":"ct",
+                       "value":"some text ...",
+                       "field":"someField"
+               }
+       ]
+       group:[
+               {
+                       "property":"someOtherField",
+                       "direction":"ASC"
+               }
+       ]
+       sort:[
+               {
+                       "property":"someOtherField",
+                       "direction":"ASC"
+               }
+       ]
+       page:1
+       start:0
+       limit:25
+ */
+abstract class BSApiExtJSStoreBase extends BSApiBase {
+
+       /**
+        * The current parameters sent by the ExtJS store
+        * @var BsExtJSStoreParams
+        */
+       protected $oStoreParams = null;
+
+       /**
+        * Automatically set within 'postProcessData' method
+        * @var int
+        */
+       protected $iFinalDataSetCount = 0;
+
+       /**
+        * May be overwritten by subclass
+        * @var string
+        */
+       protected $root = 'results';
+
+       /**
+        * May be overwritten by subclass
+        * @var string
+        */
+       protected $totalProperty = 'total';
+
+       public function execute() {
+               $sQuery = $this->getParameter( 'query' );
+               $aData = $this->makeData( $sQuery );
+               $FinalData = $this->postProcessData( $aData );
+               $this->returnData( $FinalData );
+       }
+
+       /**
+        * @param string $sQuery Potential query provided by ExtJS component.
+        * This is some kind of preliminary filtering. Subclass has to decide if
+        * and how to process it
+        * @return array - Full list of of data objects. Filters, paging, 
sorting
+        * will be done by the base class
+        */
+       protected abstract function makeData( $sQuery = '' );
+
+       /**
+        * Creates a proper output format based on the classes properties
+        * @param array $aData An array of plain old data objects
+        */
+       public function returnData($aData) {
+               wfRunHooks( 'BSApiExtJSStoreBaseBeforeReturnData', array( 
$this, &$aData ) );
+               $result = $this->getResult();
+               $result->setIndexedTagName( $aData, $this->root );
+               $result->addValue( null, $this->root, $aData );
+               $result->addValue( null, $this->totalProperty, 
$this->iFinalDataSetCount );
+       }
+
+       /**
+        *
+        * @return BsExtJSStoreParams
+        */
+       protected function getStoreParams() {
+               if( $this->oStoreParams === null ) {
+                       $this->oStoreParams = 
BsExtJSStoreParams::newFromRequest();
+               }
+               return $this->oStoreParams;
+       }
+
+       public function getAllowedParams() {
+               return array(
+                       'sort' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_REQUIRED => false,
+                               ApiBase::PARAM_DFLT => '[]'
+                       ),
+                       'group' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_REQUIRED => false,
+                               ApiBase::PARAM_DFLT => '[]'
+                       ),
+                       'filter' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_REQUIRED => false,
+                               ApiBase::PARAM_DFLT => '[]'
+                       ),
+                       'page' => array(
+                               ApiBase::PARAM_TYPE => 'integer',
+                               ApiBase::PARAM_REQUIRED => false,
+                               ApiBase::PARAM_DFLT => 0
+                       ),
+                       'limit' => array(
+                               ApiBase::PARAM_TYPE => 'integer',
+                               ApiBase::PARAM_REQUIRED => false,
+                               ApiBase::PARAM_DFLT => 25
+                       ),
+                       'start' => array(
+                               ApiBase::PARAM_TYPE => 'integer',
+                               ApiBase::PARAM_REQUIRED => false,
+                               ApiBase::PARAM_DFLT => 0
+                       ),
+
+                       'callback' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_REQUIRED => false
+                       ),
+
+                       'query' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_REQUIRED => false
+                       ),
+                       '_dc' => array(
+                               ApiBase::PARAM_TYPE => 'integer',
+                               ApiBase::PARAM_REQUIRED => false
+                       ),
+                       'format' => array(
+                               ApiBase::PARAM_DFLT => 'json',
+                               ApiBase::PARAM_TYPE => array( 'json', 'jsonfm' )
+                       )
+               );
+       }
+
+       public function getParamDescription() {
+               return array(
+                       'sort' => 'JSON string with sorting info; deserializes 
to array of objects that hold filed name and direction for each sorting option',
+                       'group' => 'JSON string with grouping info; 
deserializes to array of objects that hold filed name and direction for each 
grouping option',
+                       'filter' => 'JSON string with filter info; deserializes 
to array of objects that hold filed name, filter type, and filter value for 
each sorting option',
+                       'page' => 'Allows server side calculation of 
start/limit',
+                       'limit' => 'Number of results to return',
+                       'start' => 'The offset to start the result list from',
+                       'query' => 'This is similar to "filter", but the 
provided value serves as a filter only for the "value" field of an ExtJS 
component',
+                       'callback' => 'The offset to start the result list 
from',
+                       '_dc' => '"Disable cache" flag',
+                       'format' => 'The format of the output (only JSON or 
formatted JSON)'
+               );
+       }
+
+       protected function getParameterFromSettings($paramName, $paramSettings, 
$parseLimit) {
+               $value = parent::getParameterFromSettings($paramName, 
$paramSettings, $parseLimit);
+               //Unfortunately there is no way to register custom types for 
parameters
+               if( in_array( $paramName, array( 'sort', 'group', 'filter' ) ) 
) {
+                       $value = FormatJson::decode( $value );
+                       if( empty($value) ) {
+                               return array();
+                       }
+               }
+               return $value;
+       }
+
+       public function getParameter( $paramName, $parseLimit = true ) {
+               //Make this public, so hook handler could get the params
+               return parent::getParameter( $paramName, $parseLimit );
+       }
+
+       /**
+        * Filter, sort and trim the result according to the call parameters and
+        * apply security trimming
+        * @param array $aData
+        * @return array
+        */
+       public function postProcessData( $aData ) {
+               if( !wfRunHooks( 'BSApiExtJSStoreBaseBeforePostProcessData', 
array( $this, &$aData ) ) ) {
+                       return $aData;
+               }
+
+               $aProcessedData = array();
+
+               //First, apply filter
+               $aProcessedData = array_filter($aData, array( $this, 
'filterCallback') );
+               wfRunHooks( 'BSApiExtJSStoreBaseAfterFilterData', array( $this, 
&$aProcessedData ) );
+
+               //Next, apply sort
+               //usort($aProcessedData, array( $this, 'sortCallback') ); <-- 
had some performance issues
+               $aProcessedData = $this->sortData( $aProcessedData );
+
+               //Before we trim, we save the count
+               $this->iFinalDataSetCount = count( $aProcessedData );
+
+               //Last, do trimming
+               $aProcessedData = $this->trimData( $aProcessedData );
+
+               return $aProcessedData;
+       }
+
+       /**
+        * Applies all sorters provided by the store
+        * --> has performance issues on large dataset; Code kept for 
documentation
+        * @param object $oA
+        * @param object $oB
+        * @return int
+        */
+       public function sortCallback( $oA, $oB ) {
+               $aSort = $this->getParameter('sort');
+               $iCount = count( $aSort );
+               for( $i = 0; $i < $iCount; $i++ ) {
+                       $sProperty = $aSort[$i]->property;
+                       $sDirection = strtoupper( $aSort[$i]->direction );
+
+                       if( $oA->$sProperty !== $oB->$sProperty ) {
+                               if( $sDirection === 'ASC' ) {
+                                       return $oA->$sProperty < 
$oB->$sProperty ? -1 : 1;
+                               }
+                               else { //'DESC'
+                                       return $oA->$sProperty > 
$oB->$sProperty ? -1 : 1;
+                               }
+                       }
+               }
+               return 0;
+       }
+
+       /**
+        *
+        * @param object $aDataSet
+        * @return boolean
+        */
+       public function filterCallback( $aDataSet ) {
+               $aFilter = $this->getParameter( 'filter' );
+               foreach( $aFilter as $oFilter ) {
+                       //If just one of these filters does not apply, the 
dataset needs
+                       //to be removed
+
+                       if( $oFilter->type == 'string' ) {
+                               $bFilterApplies = $this->filterString( 
$oFilter, $aDataSet );
+                               if( !$bFilterApplies ) {
+                                       return false;
+                               }
+                       }
+                       if( $oFilter->type == 'list' ) {
+                               $bFilterApplies = $this->filterList( $oFilter, 
$aDataSet );
+                               if( !$bFilterApplies ) {
+                                       return false;
+                               }
+                       }
+                       if( $oFilter->type == 'numeric' ) {
+                               $bFilterApplies = $this->filterNumeric( 
$oFilter, $aDataSet );
+                               if( !$bFilterApplies ) {
+                                       return false;
+                               }
+                       }
+                       //TODO: Implement for type 'date', 'datetime' and 
'boolean'
+               }
+
+               return true;
+       }
+
+       /**
+        * Performs string filtering based on given filter of type string on a 
dataset
+        * @param object $oFilter
+        * @param oject $aDataSet
+        * @return boolean true if filter applies, false if not
+        */
+       public function filterString( $oFilter, $aDataSet ) {
+               if( !is_string( $oFilter->value ) ) {
+                       return true; //TODO: Warning
+               }
+               $sFieldValue = $aDataSet->{$oFilter->field};
+               $sFilterValue = $oFilter->value;
+
+               //TODO: Add string functions to BsStringHelper
+               //HINT: http://stackoverflow.com/a/10473026 + Case insensitive
+               switch( $oFilter->comparison ) {
+                       case 'sw':
+                               return $sFilterValue === '' ||
+                                       strripos($sFieldValue, $sFilterValue, 
-strlen($sFieldValue)) !== false;
+                       case 'ew':
+                               return $sFilterValue === '' ||
+                                       (($temp = strlen($sFieldValue) - 
strlen($sFilterValue)) >= 0
+                                       && stripos($sFieldValue, $sFilterValue, 
$temp) !== false);
+                       case 'ct':
+                               return stripos($sFieldValue, $sFilterValue) !== 
false;
+                       case 'nct':
+                               return stripos($sFieldValue, $sFilterValue) === 
false;
+                       case 'eq':
+                               return $sFieldValue === $sFilterValue;
+                       case 'neq':
+                               return $sFieldValue !== $sFilterValue;
+               }
+       }
+
+       /**
+        * Performs numeric filtering based on given filter of type integer on a
+        * dataset
+        * @param object $oFilter
+        * @param oject $aDataSet
+        * @return boolean true if filter applies, false if not
+        */
+       public function filterNumeric( $oFilter, $aDataSet ) {
+               if( !is_numeric( $oFilter->value ) ) {
+                       return true; //TODO: Warning
+               }
+               $sFieldValue = $aDataSet->{$oFilter->field};
+               $iFilterValue = (int) $oFilter->value;
+
+               switch( $oFilter->comparison ) {
+                       case 'gt':
+                               return $sFieldValue < $iFilterValue;
+                       case 'lt':
+                               return $sFieldValue > $iFilterValue;
+                       case 'eq':
+                               return $sFieldValue === $iFilterValue;
+                       case 'neq':
+                               return $sFieldValue !== $iFilterValue;
+               }
+       }
+
+       /**
+        * Performs list filtering based on given filter of type array on a 
dataset
+        * @param object $oFilter
+        * @param oject $aDataSet
+        * @return boolean true if filter applies, false if not
+        */
+       public function filterList( $oFilter, $aDataSet ) {
+               if( !is_array( $oFilter->value ) ) {
+                       return true; //TODO: Warning
+               }
+               $aFieldValues = $aDataSet->{$oFilter->field};
+               if( empty( $aFieldValues ) ) {
+                       return false;
+               }
+               $aFilterValues = $oFilter->value;
+               $aTemp = array_intersect( $aFieldValues, $aFilterValues );
+               if( empty( $aTemp ) ) {
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * Applies pagination on the result
+        * @param array $aProcessedData The filtered result
+        * @return array a trimmed version of the result
+        */
+       public function trimData( $aProcessedData ) {
+               $iStart = $this->getParameter( 'start' );
+               $iEnd = $this->getParameter( 'limit' ) + $iStart;
+
+               if( $iEnd > $this->iFinalDataSetCount || $iEnd === 0 ) {
+                       $iEnd = $this->iFinalDataSetCount;
+               }
+
+               $aTrimmedData = array();
+               for( $i = $iStart; $i < $iEnd; $i++ ) {
+                       $aTrimmedData[] = $aProcessedData[$i];
+               }
+
+               return $aTrimmedData;
+       }
+
+       /**
+        * Performs fast sorting on the results. Thanks at user "pigpen"
+        * @param array $aProcessedData
+        * @return array The sorted results
+        */
+       public function sortData($aProcessedData) {
+               $aSort = $this->getParameter('sort');
+               $iCount = count( $aSort );
+               for( $i = 0; $i < $iCount; $i++ ) {
+                       $sProperty = $aSort[$i]->property;
+                       $sDirection = strtoupper( $aSort[$i]->direction );
+                       $a{$sProperty} = array();
+                       foreach( $aProcessedData as $iKey => $aDataSet ) {
+                               $a{$sProperty}[$iKey] = $aDataSet->{$sProperty};
+                       }
+
+                       $aParams[] = $a{$sProperty};
+                       $aParams[] = SORT_NATURAL; //We might tweak this 
depending on the data type of the field. Mabye we should make some "getSort( 
$fieldName )" method
+                       if( $sDirection === 'ASC' ) {
+                               $aParams[] = SORT_ASC;
+                       }
+                       else {
+                               $aParams[] = SORT_DESC;
+                       }
+               }
+               $aParams[] = &$aProcessedData;
+
+               call_user_func_array( 'array_multisort', $aParams );
+               return $aProcessedData;
+       }
+
+}
diff --git a/includes/api/BSApiFileBackendStore.php 
b/includes/api/BSApiFileBackendStore.php
new file mode 100644
index 0000000..e1c81f0
--- /dev/null
+++ b/includes/api/BSApiFileBackendStore.php
@@ -0,0 +1,180 @@
+<?php
+/**
+ *  This class serves as a backend for ExtJS stores. It allows all
+ * necessary parameters and provides convenience methods and a standard ouput
+ * format
+ *
+ * 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://www.blue-spice.org
+ *
+ * @author     Robert Vogel <[email protected]>
+ * @author     Patric Wirth <[email protected]>
+ * @package    Bluespice_Foundation
+ * @copyright  Copyright (C) 2015 Hallo Welt! - Medienwerkstatt GmbH, All 
rights reserved.
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License v2 or 
later
+ * @filesource
+ */
+class BSApiFileBackendStore extends BSApiExtJSStoreBase {
+
+       public function makeData( $sQuery = '' ) {
+               $res = $this->fetchCaseInsensitive( $sQuery );
+
+               //The initial query is made against the searchindex table, 
which holds
+               //lowercased and otherwise normalized titles. Unfornunately if
+               //one queries an exact title with dots (and colons) the result 
will be
+               //empty because the searchindex table data is stripped from 
those
+               //characters. We will fallback to a query without the use of
+               //searchindex, just in case...
+               if( $res->numRows() === 0 ) {
+                       $res = $this->fetchCaseSensitive( $sQuery );
+               }
+
+               $bUseSecureFileStore = BsExtensionManager::isContextActive(
+                       'MW::SecureFileStore::Active'
+               );
+
+               //First query: Get all files and their pages
+               $aReturn = array();
+               foreach( $res as $oRow ) {
+                       try {
+                               $oImg = RepoGroup::singleton()->getLocalRepo()
+                                               ->newFileFromRow( $oRow );
+                       } catch (Exception $ex) {
+                               continue;
+                       }
+
+                       $oTitle = Title::newFromRow( $oRow );
+                       //No "user can read" check here, because it may be 
expensive.
+                       //This may be done by hook handlers
+
+                       //TODO: use 'thumb.php'?
+                       //TODO: Make thumb size a parameter
+                       $sThumb = $oImg->createThumb( 48, 48 );
+                       $sUrl = $oImg->getUrl();
+                       if( $bUseSecureFileStore ) { //TODO: Remove
+                               $sThumb = html_entity_decode( 
SecureFileStore::secureStuff( $sThumb, true ) );
+                               $sUrl = html_entity_decode( 
SecureFileStore::secureStuff( $sUrl, true ) );
+                       }
+
+                       $aReturn[ $oRow->page_id ] = (object) array(
+                               'file_url' => $sUrl,
+                               'file_name' => $oImg->getName(),
+                               'file_size' => $oImg->getSize(),
+                               'file_bits' => $oImg->getBitDepth(),
+                               'file_user' => $oImg->getUser( 'id' ),
+                               'file_width' => $oImg->getWidth(),
+                               'file_height' => $oImg->getHeight(),
+                               'file_mimetype' => $oImg->getMimeType(), # 
major/minor
+                               'file_metadata' => unserialize( 
$oImg->getMetadata() ),
+                               'file_user_text' => $oImg->getUser( 'text' ),
+                               'file_extension' => $oImg->getExtension(),
+                               'file_timestamp' => $oImg->getTimestamp(),
+                               'file_mediatype' => $oImg->getMediaType(),
+                               'file_description' => $oImg->getDescription(),
+                               'file_display_text' => $oImg->getName(),
+                               'file_thumbnail_url' => $sThumb,
+                               'page_id' => $oTitle->getArticleID(),
+                               'page_title' => $oTitle->getText(),
+                               'page_latest' => $oTitle->getLatestRevID(),
+                               'page_namespace' => $oTitle->getNamespace(),
+                               'page_categories' => array(), //Filled by a 
second step below
+                               'page_is_redirect' => $oTitle->isRedirect(),
+
+                               //For some reason 'page_is_new' and 
'page_touched' are not
+                               //initialized by 'Title::newFromRow'; Instead 
when calling
+                               //'Title->isNew()' or 'Title->getTouched()' an 
extra query is
+                               //being sent to the database, wich introduced a 
performance
+                               //issue. As the resulting data is the same we 
just use the raw
+                               //form here.
+                               'page_is_new' => (bool)$oRow->page_is_new,
+                               'page_touched' => $oRow->page_touched
+                       );
+               }
+
+               //Second query: Get all categories of each file page
+               $aPageIds = array_keys( $aReturn );
+               if( !empty( $aPageIds ) ) {
+                       $oDbr = wfGetDB( DB_SLAVE );
+                       $oCatRes = $oDbr->select(
+                               'categorylinks',
+                               array( 'cl_from', 'cl_to' ),
+                               array( 'cl_from' => $aPageIds )
+                       );
+                       foreach( $oCatRes as $oCatRow ) {
+                               $aReturn[$oCatRow->cl_from]->page_categories[] 
= $oCatRow->cl_to;
+                       }
+               }
+
+               return array_values( $aReturn );
+               //TODO: Find out if or where this hook was used before
+               //wfRunHooks( 'BSInsertFileGetFilesBeforeQuery', array( 
&$aConds, &$aNameFilters ) );
+       }
+
+       public function fetchCaseInsensitive( $sQuery ) {
+               $oDbr = wfGetDB( DB_SLAVE );
+
+               $aContidions = array(
+                       'page_namespace' => NS_FILE,
+                       'page_title = img_name',
+                       'page_id = si_page' //Needed for case insensitive 
quering; Maybe
+                       //implement 'query' as a implicit filter on 'img_name' 
field?
+               );
+
+               if( !empty( $sQuery ) ) {
+                       $aContidions[] = "si_title ".$oDbr->buildLike(
+                               $oDbr->anyString(),
+                               strtolower( $sQuery ), //make case insensitive!
+                               $oDbr->anyString()
+                       );
+               }
+
+               $res = $oDbr->select(
+                       array( 'image', 'page', 'searchindex' ),
+                       '*',
+                       $aContidions,
+                       __METHOD__
+               );
+
+               return $res;
+       }
+
+       public function fetchCaseSensitive( $sQuery ) {
+               $oDbr = wfGetDB( DB_SLAVE );
+
+               $aContidions = array(
+                       'page_namespace' => NS_FILE,
+                       'page_title = img_name',
+               );
+
+               if( !empty( $sQuery ) ) {
+                       $aContidions[] = "img_name ".$oDbr->buildLike(
+                               $oDbr->anyString(),
+                               str_replace(' ', '_', $sQuery ),
+                               $oDbr->anyString()
+                       );
+               }
+
+               $res = $oDbr->select(
+                       array( 'image', 'page' ),
+                       '*',
+                       $aContidions,
+                       __METHOD__
+               );
+
+               return $res;
+       }
+}
diff --git a/includes/api/BSApiTasksBase.php b/includes/api/BSApiTasksBase.php
new file mode 100644
index 0000000..986a7d3
--- /dev/null
+++ b/includes/api/BSApiTasksBase.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Provides the base api 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://www.blue-spice.org
+ *
+ * @author     Patric Wirth <[email protected]>
+ * @package    Bluespice_Foundation
+ * @copyright  Copyright (C) 2011 Hallo Welt! - Medienwerkstatt GmbH, All 
rights reserved.
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License v2 or 
later
+ * @filesource
+ */
+
+/**
+ * Api base class for simple tasks in BlueSpice
+ * @package BlueSpice_Foundation
+ */
+abstract class BSApiTasksBase extends BSApiBase {
+
+       /**
+        * Methods that can be called by task param
+        * @var array
+        */
+       protected $aTasks = array();
+
+       /**
+        * The execute() method will be invoked directly by ApiMain immediately
+        * before the result of the module is output. Aside from the
+        * constructor, implementations should assume that no other methods
+        * will be called externally on the module before the result is
+        * processed.
+        * @return null
+        */
+       public function execute() {
+               $aParams = $this->extractRequestParams();
+
+               //Avoid API warning: register the parameter used to bust 
browser cache
+               $this->getMain()->getVal( '_' );
+
+               $sMethod = 'task_'.$aParams['task'];
+
+               if( !is_callable( array( $this, $sMethod ) ) ) {
+                       $oResult = $this->makeStandardReturn();
+                       $oResult->errors['task'] = 'Task '.$aParams['task'].' 
not implemented';
+               }
+               else {
+                       $oResult = $this->$sMethod( 
$this->getParameter('taskData'), $aParams );
+               }
+
+               foreach( $oResult as $sFieldName => $mFieldValue ) {
+                       if( $mFieldValue === null ) {
+                               continue; //MW Api doesn't like NULL values
+                       }
+                       $this->getResult()->addValue(null, $sFieldName, 
$mFieldValue);
+               }
+       }
+
+       /**
+        * Standard return object
+        * Every task should return this!
+        * @return BSStandardAPIResponse
+        */
+       protected function makeStandardReturn() {
+               return new BSStandardAPIResponse();
+       }
+
+       /**
+        * Returns an array of allowed parameters
+        * @return array
+        */
+       protected function getAllowedParams() {
+               return array(
+                       'task' => array(
+                               ApiBase::PARAM_REQUIRED => true,
+                               ApiBase::PARAM_TYPE => $this->aTasks,
+                       ),
+                       'taskData' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_REQUIRED => false,
+                               ApiBase::PARAM_DFLT => '{}'
+                       ),
+                       'format' => array(
+                               ApiBase::PARAM_DFLT => 'json',
+                               ApiBase::PARAM_TYPE => array( 'json', 'jsonfm' 
),
+                       )
+               );
+       }
+
+       protected function getParameterFromSettings($paramName, $paramSettings, 
$parseLimit) {
+               $value = parent::getParameterFromSettings($paramName, 
$paramSettings, $parseLimit);
+               //Unfortunately there is no way to register custom types for 
parameters
+               if( $paramName === 'taskData' ) {
+                       $value = FormatJson::decode($value);
+                       if( empty($value) ) {
+                               return array();
+                       }
+               }
+               return $value;
+       }
+
+       /**
+        * Returns the basic param descriptions
+        * @return array
+        */
+       public function getParamDescription() {
+               return array(
+                       'task' => 'The task you would like to execute',
+                       'taskData' => 'JSON string encoded object with 
arbitrary data for the task',
+                       'format' => 'The format of the result',
+               );
+       }
+
+       /**
+        * Returns the bsic description for this module
+        * @return type
+        */
+       public function getDescription() {
+               return array(
+                       'BSApiTasksBase: This should be implemented by subclass'
+               );
+       }
+
+       /**
+        * Returns the basic example
+        * @return type
+        */
+       public function getExamples() {
+               return array(
+                       
'api.php?action='.$this->getModuleName().'&task='.$this->aTasks[0].'&taskData={someKey:"someValue",isFalse:true}',
+               );
+       }
+}
\ No newline at end of file
diff --git a/includes/api/BSStandardAPIResponse.php 
b/includes/api/BSStandardAPIResponse.php
new file mode 100644
index 0000000..83fd84f
--- /dev/null
+++ b/includes/api/BSStandardAPIResponse.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * This response should implement the ExtJS standard format for serverside
+ * form validations:
+ * http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.form.action.Submit
+ *
+ * TODO: do a clean implemenation with gettes and setters
+ */
+class BSStandardAPIResponse {
+       public $errors = array(); //ExtJS
+       public $success = false; //ExtJS
+
+       //Custom fields
+       public $message = '';
+       public $payload = array();
+       public $payload_count = 0;
+}
\ No newline at end of file
diff --git a/includes/api/BsApiBase.php b/includes/api/BsApiBase.php
index 6548962..3646340 100644
--- a/includes/api/BsApiBase.php
+++ b/includes/api/BsApiBase.php
@@ -1,173 +1,50 @@
 <?php
-/**
- * Provides the base api 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://www.blue-spice.org
- *
- * @author     Patric Wirth <[email protected]>
- * @package    Bluespice_Foundation
- * @copyright  Copyright (C) 2011 Hallo Welt! - Medienwerkstatt GmbH, All 
rights reserved.
- * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License v2 or 
later
- * @filesource
- */
 
-/**
- * Api base class for BlueSpice
- * @package BlueSpice_Foundation
- */
-abstract class BsApiBase extends ApiBase {
-
+abstract class BSApiBase extends ApiBase {
        /**
-        * Methods that can be called by task param
-        * @var array
+        * Checks access permissions based on a list of titles and permissions. 
If
+        * one of it fails the API processing is ended with an appropriate 
message
+        * @param array $aTitles Array of Title objects to check the requires 
permissions against
+        * @param User $oUser the User object of the requesting user. Does a 
fallback to $this->getUser();
         */
-       protected static $aTasks = array();
-
-       /**
-        * Constructor
-        * @param $mainModule ApiMain object
-        * @param string $moduleName Name of this module
-        * @param string $modulePrefix Prefix to use for parameter names
-        */
-       public function __construct( $query, $moduleName, $modulePrefix = '' ) {
-               parent::__construct( $query, $moduleName, $modulePrefix );
-       }
-
-       /**
-        * The execute() method will be invoked directly by ApiMain immediately
-        * before the result of the module is output. Aside from the
-        * constructor, implementations should assume that no other methods
-        * will be called externally on the module before the result is
-        * processed.
-        * @return null
-        */
-       public function execute() {
-               $aParams = $this->extractRequestParams();
-
-               //Avoid API warning: register the parameter used to bust 
browser cache
-               $this->getMain()->getVal( '_' );
-
-               if( !isset($aParams['task']) ) return;
-
-               if( in_array($aParams['task'], static::$aTasks) ) {
-                       $oResult = call_user_func(
-                               array($this, $aParams['task']),
-                               $aParams
-                       );
+       protected function checkPermissions( $aTitles = array(), $oUser = null 
) {
+               $aRequiredPermissions = $this->getRequiredPermissions();
+               if( empty( $aRequiredPermissions ) ) {
+                       return; //No need for further checking
+               }
+               foreach( $aTitles as $oTitle ) {
+                       if( $oTitle instanceof Title === false ) {
+                               continue;
+                       }
+                       foreach( $aRequiredPermissions as $sPermission ) {
+                               if( $oTitle->userCan( $sPermission ) === false 
) {
+                                       //TODO: Reflect title and permission in 
error message
+                                       $this->dieUsageMsg( 'badaccess-groups' 
);
+                               }
+                       }
                }
 
-               $this->getResult()->addValue(null, 'bs', $oResult);
+               //Fallback if not conrete title was provided
+               if( empty( $aTitles ) ) {
+                       if( $oUser instanceof User === false ) {
+                               $oUser = $this->getUser();
+                       }
+                       foreach( $aRequiredPermissions as $sPermission ) {
+                               if( $oUser->isAllowed( $sPermission ) === false 
) {
+                                       //TODO: Reflect permission in error 
message
+                                       $this->dieUsageMsg( 'badaccess-groups' 
);
+                               }
+                       }
+               }
        }
 
-       /**
-        * Standard return object
-        * Every task should return this!
-        * @return object
-        */
-       protected static function stdReturn() {
-               return $oReturn = (object) array(
-                       'result' => array(
-                               'payload' => null,
-                               'success' => false,
-                               'message' => '',
-                               'errors' => array(),
-                               'payload_count' => 0,
-                       )
-               );
+       protected function getRequiredPermissions() {
+               return array( 'read' );
        }
 
-       /**
-        * Returns an array of allowed parameters
-        * @return array
-        */
-       protected function getAllowedParams() {
+       protected function getExamples() {
                return array(
-                       'task' => array(
-                               ApiBase::PARAM_REQUIRED => true,
-                               ApiBase::PARAM_TYPE => 'string'
-                       ),
-                       'format' => array(
-                               ApiBase::PARAM_DFLT => 'json',
-                               ApiBase::PARAM_TYPE => array( 'json', 'jsonfm' 
),
-                       )
-               );
-       }
-
-       /**
-        * Returns the basic param descriptions
-        * @return array
-        */
-       public function getParamDescription() {
-               return array(
-                       'task' => 'The task you would like to execute',
-                       'format' => 'The format of the result',
-               );
-       }
-
-       /**
-        * Default to false
-        * @return boolean
-        */
-       public function needsToken() {
-               return false;
-       }
-
-       /**
-        * Default to empty string
-        * @return string
-        */
-       public function getTokenSalt() {
-               return '';
-       }
-
-       /**
-        * Default to true
-        * @return boolean
-        */
-       public function mustBePosted() {
-               return true;
-       }
-
-       /**
-        * Default to false
-        * @return boolean
-        */
-       public function isWriteMode() {
-               return false;
-       }
-
-       /**
-        * Returns the bsic description for this module
-        * @return type
-        */
-       public function getDescription() {
-               return array(
-                       'BsApiBase: This should be implemented by subclass'
-               );
-       }
-
-       /**
-        * Returns the basic example
-        * @return type
-        */
-       public function getExamples() {
-               return array(
-                       
'api.php?action=<childapimodule>&task=<taskofchildapimodule>',
+                       'api.php?action='.$this->getModuleName(),
                );
        }
 }
\ No newline at end of file
diff --git a/resources/bluespice.extjs/BS/model/File.js 
b/resources/bluespice.extjs/BS/model/File.js
new file mode 100644
index 0000000..8ee12c1
--- /dev/null
+++ b/resources/bluespice.extjs/BS/model/File.js
@@ -0,0 +1,41 @@
+/**
+ * This model is used to have a unified representation of all MediaWiki repo
+ * files. The information is a combination of the page and the file table
+ */
+
+Ext.define('BS.model.File', {
+       extend: 'Ext.data.Model',
+
+       idProperty: 'file_name',
+
+       fields: [
+               //Those are values we can gather from the MediaWiki 'page' 
table.
+               { name: 'page_id', type: 'int', defaultValue: 0 },
+               { name: 'page_namespace', type: 'int', defaultValue: -99 },
+               { name: 'page_title', type: 'string', defaultValue: '' },
+               { name: 'page_is_new', type: 'bool', defaultValue: true },
+               { name: 'page_touched', type: 'date', defaultValue: 
'19700101000000', dateFormat: 'YmdHis' },
+               { name: 'page_is_redirect', type: 'bool', defaultValue: false },
+               { name: 'page_latest', type: 'date', defaultValue: 
'19700101000000', dateFormat: 'YmdHis' },
+
+               //Here come custom fields that are calculated on the server side
+               { name: 'page_categories', type: 'array', defaultValue: [] },
+
+               { name: 'file_name', type: 'string' },
+               { name: 'file_size', type: 'int', defaultValue: 0 },
+               { name: 'file_bits', type: 'int', defaultValue: 0 },
+               { name: 'file_user', type: 'int', defaultValue: 0 },
+               { name: 'file_width', type: 'int', defaultValue: 0 },
+               { name: 'file_height', type: 'int', defaultValue: 0 },
+               { name: 'file_mimetype', type: 'string', defaultValue: 
'unknown/unknown' },
+               { name: 'file_metadata', type: 'object', defaultValue: {} },
+               { name: 'file_extension', type: 'string', defaultValue: '' },
+               { name: 'file_timestamp', type: 'date', defaultValue: 
'19700101000000', dateFormat: 'YmdHis' },
+               { name: 'file_mediatype', type: 'string', defaultValue: '' },
+               { name: 'file_description', type: 'string', defaultValue: '' },
+               { name: 'file_display_text', type: 'string', defaultValue: '' 
}, //TODO: Maybe fallback to 'file_name'
+               { name: 'file_thumbnail_url', type: 'string', defaultValue: '' }
+       ]
+
+       //TODO: Implement getter
+});
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/224410
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I6dc475aac57bfdeba0cabe02984bf61e3d153753
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/BlueSpiceFoundation
Gerrit-Branch: REL1_23
Gerrit-Owner: Robert Vogel <[email protected]>
Gerrit-Reviewer: Mglaser <[email protected]>
Gerrit-Reviewer: Pwirth <[email protected]>
Gerrit-Reviewer: Robert Vogel <[email protected]>
Gerrit-Reviewer: Tweichart <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to