From efa9ff9b08c7eb7f0dca61ac78c24c908fe3b8ef Mon Sep 17 00:00:00 2001 From: stojg Date: Mon, 10 Dec 2012 18:11:07 +1300 Subject: [PATCH 01/49] API: Queries added by DataList::addInnerJoin() and DataList::leftJoin() come after the base joins, not before. This bug will surface when using the ORM and adding an join to DataList where a DataObject inherits another DataObject. If you for example want to restrict the number of pages that only have a related Staff object: $list = DataList::create('Page') ->InnerJoin('Staff', '"Staff"."ID" = "Page"."StaffID"); This will create a SQL query where the INNER JOIN is before the LEFT JOIN of Page and SiteTree in the resulting SQL string. In MySQL and PostgreSQL this will create an invalid query. This patch solves the problem by sorting the joins. --- model/DataQuery.php | 2 +- model/SQLQuery.php | 125 +++++++++++++++++++++++++++++----- tests/model/DataQueryTest.php | 21 ++++++ tests/model/SQLQueryTest.php | 14 ++++ 4 files changed, 145 insertions(+), 17 deletions(-) diff --git a/model/DataQuery.php b/model/DataQuery.php index e1aade7f5..39277f8a4 100644 --- a/model/DataQuery.php +++ b/model/DataQuery.php @@ -190,7 +190,7 @@ class DataQuery { } if ($joinTable) { - $query->addLeftJoin($tableClass, "\"$tableClass\".\"ID\" = \"$baseClass\".\"ID\"") ; + $query->addLeftJoin($tableClass, "\"$tableClass\".\"ID\" = \"$baseClass\".\"ID\"", $tableClass, 10) ; } } diff --git a/model/SQLQuery.php b/model/SQLQuery.php index 020473101..3e866a8a4 100644 --- a/model/SQLQuery.php +++ b/model/SQLQuery.php @@ -276,18 +276,28 @@ class SQLQuery { * Add a LEFT JOIN criteria to the FROM clause. * * @param string $table Unquoted table name - * @param string $onPredicate The "ON" SQL fragment in a "LEFT JOIN ... AS ... ON ..." statement, - * Needs to be valid (quoted) SQL. + * @param string $onPredicate The "ON" SQL fragment in a "LEFT JOIN ... AS ... ON ..." statement, Needs to be valid + * (quoted) SQL. * @param string $tableAlias Optional alias which makes it easier to identify and replace joins later on + * @param int $order A numerical index to control the order that joins are added to the query; lower order values + * will cause the query to appear first. The default is 20, and joins created automatically by the + * ORM have a value of 10. * @return SQLQuery */ - public function addLeftJoin($table, $onPredicate, $tableAlias = null) { - if(!$tableAlias) $tableAlias = $table; - $this->from[$tableAlias] = array('type' => 'LEFT', 'table' => $table, 'filter' => array($onPredicate)); + public function addLeftJoin($table, $onPredicate, $tableAlias = '', $order = 20) { + if(!$tableAlias) { + $tableAlias = $table; + } + $this->from[$tableAlias] = array( + 'type' => 'LEFT', + 'table' => $table, + 'filter' => array($onPredicate), + 'order' => $order + ); return $this; } - public function leftjoin($table, $onPredicate, $tableAlias = null) { + public function leftjoin($table, $onPredicate, $tableAlias = null, $order = 20) { Deprecation::notice('3.0', 'Please use addLeftJoin() instead!'); $this->addLeftJoin($table, $onPredicate, $tableAlias); } @@ -296,20 +306,28 @@ class SQLQuery { * Add an INNER JOIN criteria to the FROM clause. * * @param string $table Unquoted table name - * @param string $onPredicate The "ON" SQL fragment in an "INNER JOIN ... AS ... ON ..." statement. - * Needs to be valid (quoted) SQL. + * @param string $onPredicate The "ON" SQL fragment in an "INNER JOIN ... AS ... ON ..." statement. Needs to be + * valid (quoted) SQL. * @param string $tableAlias Optional alias which makes it easier to identify and replace joins later on + * @param int $order A numerical index to control the order that joins are added to the query; lower order values + * will cause the query to appear first. The default is 20, and joins created automatically by the + * ORM have a value of 10. * @return SQLQuery */ - public function addInnerJoin($table, $onPredicate, $tableAlias = null) { + public function addInnerJoin($table, $onPredicate, $tableAlias = null, $order = 20) { if(!$tableAlias) $tableAlias = $table; - $this->from[$tableAlias] = array('type' => 'INNER', 'table' => $table, 'filter' => array($onPredicate)); + $this->from[$tableAlias] = array( + 'type' => 'INNER', + 'table' => $table, + 'filter' => array($onPredicate), + 'order' => $order + ); return $this; } - public function innerjoin($table, $onPredicate, $tableAlias = null) { + public function innerjoin($table, $onPredicate, $tableAlias = null, $order = 20) { Deprecation::notice('3.0', 'Please use addInnerJoin() instead!'); - return $this->addInnerJoin($table, $onPredicate, $tableAlias); + return $this->addInnerJoin($table, $onPredicate, $tableAlias, $order); } /** @@ -876,12 +894,13 @@ class SQLQuery { // TODO: Don't require this internal-state manipulate-and-preserve - let sqlQueryToString() handle the new // syntax $origFrom = $this->from; - + // Sort the joins + $this->from = $this->getOrderedJoins($this->from); // Build from clauses foreach($this->from as $alias => $join) { // $join can be something like this array structure // array('type' => 'inner', 'table' => 'SiteTree', 'filter' => array("SiteTree.ID = 1", - // "Status = 'approved'")) + // "Status = 'approved'", 'order' => 20)) if(is_array($join)) { if(is_string($join['filter'])) $filter = $join['filter']; else if(sizeof($join['filter']) == 1) $filter = $join['filter'][0]; @@ -902,10 +921,9 @@ class SQLQuery { $this->from = $origFrom; // The query was most likely just created and then exectued. - if($sql === 'SELECT *') { + if(trim($sql) === 'SELECT * FROM') { return ''; } - return $sql; } @@ -1086,6 +1104,81 @@ class SQLQuery { $query->setLimit(1, $index); return $query; } + + /** + * Ensure that framework "auto-generated" table JOINs are first in the finalised SQL query. + * This prevents issues where developer-initiated JOINs attempt to JOIN using relations that haven't actually + * yet been scaffolded by the framework. Demonstrated by PostGres in errors like: + *"...ERROR: missing FROM-clause..." + * + * @param $from array - in the format of $this->select + * @return array - and reorderded list of selects + */ + protected function getOrderedJoins($from) { + // shift the first FROM table out from so we only deal with the JOINs + $baseFrom = array_shift($from); + $this->mergesort($from, function($firstJoin, $secondJoin) { + if($firstJoin['order'] == $secondJoin['order']) { + return 0; + } + return ($firstJoin['order'] < $secondJoin['order']) ? -1 : 1; + }); + + // Put the first FROM table back into the results + array_unshift($from, $baseFrom); + return $from; + } + /** + * Since uasort don't preserve the order of an array if the comparison is equal + * we have to resort to a merge sort. It's quick and stable: O(n*log(n)). + * + * @see http://stackoverflow.com/q/4353739/139301 + * + * @param array &$array - the array to sort + * @param callable $cmpFunction - the function to use for comparison + */ + protected function mergesort(&$array, $cmpFunction = 'strcmp') { + // Arrays of size < 2 require no action. + if (count($array) < 2) { + return; + } + // Split the array in half + $halfway = count($array) / 2; + $array1 = array_slice($array, 0, $halfway); + $array2 = array_slice($array, $halfway); + // Recurse to sort the two halves + $this->mergesort($array1, $cmpFunction); + $this->mergesort($array2, $cmpFunction); + // If all of $array1 is <= all of $array2, just append them. + if(call_user_func($cmpFunction, end($array1), reset($array2)) < 1) { + $array = array_merge($array1, $array2); + return; + } + // Merge the two sorted arrays into a single sorted array + $array = array(); + $val1 = reset($array1); + $val2 = reset($array2); + do { + if (call_user_func($cmpFunction, $val1, $val2) < 1) { + $array[key($array1)] = $val1; + $val1 = next($array1); + } else { + $array[key($array2)] = $val2; + $val2 = next($array2); + } + } while($val1 && $val2); + + // Merge the remainder + while($val1) { + $array[key($array1)] = $val1; + $val1 = next($array1); + } + while($val2) { + $array[key($array2)] = $val2; + $val2 = next($array2); + } + return; + } } diff --git a/tests/model/DataQueryTest.php b/tests/model/DataQueryTest.php index 5c074a549..1eb643462 100644 --- a/tests/model/DataQueryTest.php +++ b/tests/model/DataQueryTest.php @@ -1,6 +1,13 @@ assertEquals('DataQueryTest_B', $dq->applyRelation('ManyTestBs'), 'DataQuery::applyRelation should return the name of the related object.'); } + + public function testRelationOrderWithCustomJoin() { + $dataQuery = new DataQuery('DataQueryTest_B'); + $dataQuery->innerJoin('DataQueryTest_D', '"DataQueryTest_D"."RelationID" = "DataQueryTest_B"."ID"'); + $dataQuery->execute(); + } } @@ -56,6 +69,7 @@ class DataQueryTest_B extends DataQueryTest_A { } class DataQueryTest_C extends DataObject implements TestOnly { + public static $has_one = array( 'TestA' => 'DataQueryTest_A', 'TestB' => 'DataQueryTest_B', @@ -71,3 +85,10 @@ class DataQueryTest_C extends DataObject implements TestOnly { 'ManyTestBs' => 'DataQueryTest_B', ); } + +class DataQueryTest_D extends DataObject implements TestOnly { + + public static $has_one = array( + 'Relation' => 'DataQueryTest_B', + ); +} diff --git a/tests/model/SQLQueryTest.php b/tests/model/SQLQueryTest.php index 8e2b25984..f59c2d519 100755 --- a/tests/model/SQLQueryTest.php +++ b/tests/model/SQLQueryTest.php @@ -292,6 +292,7 @@ class SQLQueryTest extends SapphireTest { $query->sql() ); } + public function testSetWhereAny() { $query = new SQLQuery(); @@ -376,4 +377,17 @@ class SQLQueryTest_DO extends DataObject implements TestOnly { ); } +class SQLQueryTestBase extends DataObject implements TestOnly { + static $db = array( + "Title" => "Varchar", + ); +} +class SQLQueryTestChild extends SQLQueryTestBase { + static $db = array( + "Name" => "Varchar", + ); + + static $has_one = array( + ); +} From fc5dd2994caac9b6c476911de616ea0782911408 Mon Sep 17 00:00:00 2001 From: Simon Welsh Date: Sun, 9 Dec 2012 00:20:20 +1300 Subject: [PATCH 02/49] Add codesniffer that ensures indentation is with tabs. --- .travis.yml | 3 +- admin/code/ModelAdmin.php | 2 +- admin/javascript/LeftAndMain.AddForm.js | 10 +- admin/javascript/LeftAndMain.Tree.js | 4 +- admin/javascript/LeftAndMain.js | 2 +- admin/javascript/lib.js | 6 +- api/FormEncodedDataFormatter.php | 6 +- api/RSSFeed.php | 8 +- api/RestfulService.php | 6 +- cli-script.php | 28 +- conf/ConfigureFromEnv.php | 4 +- control/ContentNegotiator.php | 4 +- control/Controller.php | 4 +- control/Director.php | 56 +- control/HTTP.php | 4 +- control/HTTPRequest.php | 48 +- control/HTTPResponse.php | 46 +- control/RequestHandler.php | 2 +- control/injector/AopProxyService.php | 4 +- core/ArrayLib.php | 36 +- core/Diff.php | 872 +++++----- dev/BulkLoader.php | 9 +- dev/DevelopmentAdmin.php | 20 +- dev/FunctionalTest.php | 24 +- dev/Profiler.php | 392 ++--- dev/SapphireTestReporter.php | 34 +- dev/SilverStripeListener.php | 4 +- dev/TeamCityListener.php | 4 +- dev/TestListener.php | 12 +- dev/phpunit/PhpUnitWrapper.php | 16 +- dev/phpunit/PhpUnitWrapper_3_5.php | 6 +- email/Email.php | 12 +- email/Mailer.php | 30 +- filesystem/GD.php | 94 +- forms/AjaxUniqueTextField.php | 2 +- forms/CheckboxSetField.php | 2 +- forms/ComplexTableField.php | 64 +- forms/CompositeField.php | 4 +- forms/CreditCardField.php | 2 +- forms/DateField.php | 96 +- forms/FieldGroup.php | 14 +- forms/Form.php | 8 +- forms/FormField.php | 18 +- forms/HtmlEditorField.php | 10 +- forms/ManyManyComplexTableField.php | 2 +- forms/NumericField.php | 4 +- forms/PhoneNumberField.php | 12 +- forms/RequiredFields.php | 14 +- forms/SimpleImageField.php | 42 +- forms/TableField.php | 15 +- forms/TableListField.php | 164 +- forms/gridfield/GridField.php | 2 +- javascript/ComplexTableField.js | 6 +- javascript/GridField.js | 1 - javascript/HtmlEditorField.js | 10 +- javascript/ImageFormAction.js | 6 +- javascript/TableField.js | 2 +- javascript/TableListField.js | 12 +- javascript/i18n.js | 4 +- javascript/jquery-ondemand/jquery.ondemand.js | 4 +- javascript/tree/tree.js | 46 +- model/DataList.php | 32 +- model/DataObject.php | 156 +- model/DataQuery.php | 140 +- model/Database.php | 6 +- model/DatabaseAdmin.php | 4 +- model/HasManyList.php | 16 +- model/Hierarchy.php | 26 +- model/ManyManyList.php | 22 +- model/MySQLDatabase.php | 44 +- model/SQLQuery.php | 10 +- model/Transliterator.php | 2 +- model/Versioned.php | 4 +- model/fieldtypes/Date.php | 10 +- model/fieldtypes/Text.php | 2 +- model/fieldtypes/Varchar.php | 8 +- parsers/BBCodeParser.php | 4 +- parsers/HTML/HTMLBBCodeParser.php | 1517 ++++++++--------- search/SearchContext.php | 8 +- security/Authenticator.php | 276 +-- security/Group.php | 6 +- security/Member.php | 50 +- security/MemberAuthenticator.php | 216 +-- security/MemberLoginForm.php | 16 +- security/Permission.php | 4 +- security/PermissionCheckboxSetField.php | 4 +- security/Security.php | 62 +- tests/api/RestfulServiceTest.php | 6 +- .../features/bootstrap/FeatureContext.php | 36 +- .../Test/Behaviour/CmsFormsContext.php | 144 +- .../Framework/Test/Behaviour/CmsUiContext.php | 496 +++--- tests/bootstrap.php | 26 +- tests/control/HTTPTest.php | 12 +- tests/core/ArrayDataTest.php | 2 +- .../module/classes/ClassB.php | 2 +- tests/dev/CsvBulkLoaderTest.php | 2 +- tests/forms/FileFieldTest.php | 20 +- tests/forms/RequirementsTest.php | 4 +- tests/forms/TableListFieldTest.php | 34 +- tests/forms/uploadfield/UploadFieldTest.php | 2 +- tests/injector/testservices/SampleService.php | 4 +- .../TreeDropDownField/TreeDropdownField.js | 10 +- tests/model/ComponentSetTest.php | 1 - tests/model/DataListTest.php | 2 +- tests/model/DataObjectTest.php | 1 - tests/model/MoneyTest.php | 16 +- tests/model/PaginatedListTest.php | 8 +- tests/model/VersionedTest.php | 10 +- tests/phpcs/tabs.xml | 18 + tests/search/SearchContextTest.php | 44 +- tests/security/GroupTest.php | 128 +- tests/security/MemberTest.php | 4 +- tests/view/ViewableDataTest.php | 6 +- view/ArrayData.php | 2 +- view/Requirements.php | 16 +- view/SSTemplateParser.php.inc | 4 +- view/SSViewer.php | 10 +- 117 files changed, 3053 insertions(+), 3040 deletions(-) create mode 100644 tests/phpcs/tabs.xml diff --git a/.travis.yml b/.travis.yml index d2d94d077..1f49fe992 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,8 @@ before_script: script: - phpunit -c phpunit.xml.dist - - phpcs --encoding=utf-8 --tab-width=4 --standard=framework/tests/phpcs -np framework + - phpcs --encoding=utf-8 --tab-width=4 --standard=framework/tests/phpcs/ruleset.xml -np framework + - phpcs --encoding=utf-8 --standard=framework/tests/phpcs/tabs.xml -np framework branches: except: diff --git a/admin/code/ModelAdmin.php b/admin/code/ModelAdmin.php index 15accc263..353e2f9c4 100644 --- a/admin/code/ModelAdmin.php +++ b/admin/code/ModelAdmin.php @@ -296,7 +296,7 @@ abstract class ModelAdmin extends LeftAndMain { * * @return array Map of model class names to importer instances */ - public function getModelImporters() { + public function getModelImporters() { $importerClasses = $this->stat('model_importers'); // fallback to all defined models if not explicitly defined diff --git a/admin/javascript/LeftAndMain.AddForm.js b/admin/javascript/LeftAndMain.AddForm.js index 8f7c20c26..48d749095 100644 --- a/admin/javascript/LeftAndMain.AddForm.js +++ b/admin/javascript/LeftAndMain.AddForm.js @@ -123,7 +123,7 @@ refresh: function(selectedNode) { var tree = this.getTree(), - selectedNode = selectedNode || $(tree).jstree('get_selected') + selectedNode = selectedNode || $(tree).jstree('get_selected') origOptions = this.getOrigOptions(), dropdown = this.find('select[name=PageType]'), disallowed = [], @@ -142,10 +142,10 @@ } $.each(origOptions, function(i, optProps) { - if ($.inArray(i, disallowed) === -1 && optProps) { - dropdown.append($('')); - disableDropDown = false; - } + if ($.inArray(i, disallowed) === -1 && optProps) { + dropdown.append($('')); + disableDropDown = false; + } }); // Disable dropdown if no elements are selectable diff --git a/admin/javascript/LeftAndMain.Tree.js b/admin/javascript/LeftAndMain.Tree.js index 97fa80bee..67780c50b 100644 --- a/admin/javascript/LeftAndMain.Tree.js +++ b/admin/javascript/LeftAndMain.Tree.js @@ -153,8 +153,8 @@ "select_limit" : 1, 'initially_select': [this.find('.current').attr('id')] }, - "crrm": { - 'move': { + "crrm": { + 'move': { // Check if a node is allowed to be moved. // Caution: Runs on every drag over a new node 'check_move': function(data) { diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index e87f0f627..d5b9fe708 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -230,7 +230,7 @@ jQuery.noConflict(); */ submitForm: function(form, button, callback, ajaxOptions) { var self = this; - + // look for save button if(!button) button = this.find('.Actions :submit[name=action_save]'); // default to first button if none given - simulates browser behaviour diff --git a/admin/javascript/lib.js b/admin/javascript/lib.js index c6437cc7e..d4b989610 100644 --- a/admin/javascript/lib.js +++ b/admin/javascript/lib.js @@ -170,8 +170,8 @@ convertUrlToDataUrl: function( absUrl ) { var u = path.parseUrl( absUrl ); if ( path.isEmbeddedPage( u ) ) { - // For embedded pages, remove the dialog hash key as in getFilePath(), - // otherwise the Data Url won't match the id of the embedded Page. + // For embedded pages, remove the dialog hash key as in getFilePath(), + // otherwise the Data Url won't match the id of the embedded Page. return u.hash.split( dialogHashKey )[0].replace( /^#/, "" ); } else if ( path.isSameDomain( u, documentBase ) ) { return u.hrefNoHash.replace( documentBase.domain, "" ); @@ -232,4 +232,4 @@ }; $.path = path; -}(jQuery)); \ No newline at end of file +}(jQuery)); diff --git a/api/FormEncodedDataFormatter.php b/api/FormEncodedDataFormatter.php index 4db470227..f59d22f23 100644 --- a/api/FormEncodedDataFormatter.php +++ b/api/FormEncodedDataFormatter.php @@ -30,9 +30,9 @@ class FormEncodedDataFormatter extends XMLDataFormatter { } public function convertStringToArray($strData) { - $postArray = array(); - parse_str($strData, $postArray); - return $postArray; + $postArray = array(); + parse_str($strData, $postArray); + return $postArray; //TODO: It would be nice to implement this function in Convert.php //return Convert::querystr2array($strData); } diff --git a/api/RSSFeed.php b/api/RSSFeed.php index c565310ec..163a93e2f 100644 --- a/api/RSSFeed.php +++ b/api/RSSFeed.php @@ -106,9 +106,9 @@ class RSSFeed extends ViewableData { * every time the representation does */ public function __construct(SS_List $entries, $link, $title, - $description = null, $titleField = "Title", - $descriptionField = "Content", $authorField = null, - $lastModified = null, $etag = null) { + $description = null, $titleField = "Title", + $descriptionField = "Content", $authorField = null, + $lastModified = null, $etag = null) { $this->entries = $entries; $this->link = $link; $this->description = $description; @@ -269,7 +269,7 @@ class RSSFeed_Entry extends ViewableData { * Create a new RSSFeed entry. */ public function __construct($entry, $titleField, $descriptionField, - $authorField) { + $authorField) { $this->failover = $entry; $this->titleField = $titleField; $this->descriptionField = $descriptionField; diff --git a/api/RestfulService.php b/api/RestfulService.php index 569026ec6..8a333afec 100644 --- a/api/RestfulService.php +++ b/api/RestfulService.php @@ -65,7 +65,7 @@ class RestfulService extends ViewableData { * @param string $password The proxy auth password * @param boolean $socks Set true to use socks5 proxy instead of http */ - public function setProxy($proxy, $port = 80, $user = "", $password = "", $socks = false) { + public function setProxy($proxy, $port = 80, $user = "", $password = "", $socks = false) { $this->proxy = array( CURLOPT_PROXY => $proxy, CURLOPT_PROXYUSERPWD => "{$user}:{$password}", @@ -337,14 +337,14 @@ class RestfulService extends ViewableData { $child_count++; $k = ($parent == "") ? (string)$key : $parent . "_" . (string)$key; if($this->getRecurseValues($value,$data,$k) == 0){ // no childern, aka "leaf node" - $conv_value = Convert::raw2xml($value); + $conv_value = Convert::raw2xml($value); } //Review the fix for similar node names overriding it's predecessor if(array_key_exists($k, $data) == true) { $data[$k] = $data[$k] . ",". $conv_value; } else { - $data[$k] = $conv_value; + $data[$k] = $conv_value; } diff --git a/cli-script.php b/cli-script.php index 93e009c49..131060785 100755 --- a/cli-script.php +++ b/cli-script.php @@ -37,19 +37,19 @@ chdir(dirname($_SERVER['SCRIPT_FILENAME'])); * fourth => val */ if(isset($_SERVER['argv'][2])) { - $args = array_slice($_SERVER['argv'],2); - if(!isset($_GET)) $_GET = array(); - if(!isset($_REQUEST)) $_REQUEST = array(); - foreach($args as $arg) { - if(strpos($arg,'=') == false) { - $_GET['args'][] = $arg; - } else { - $newItems = array(); - parse_str( (substr($arg,0,2) == '--') ? substr($arg,2) : $arg, $newItems ); - $_GET = array_merge($_GET, $newItems); - } - } - $_REQUEST = array_merge($_REQUEST, $_GET); + $args = array_slice($_SERVER['argv'],2); + if(!isset($_GET)) $_GET = array(); + if(!isset($_REQUEST)) $_REQUEST = array(); + foreach($args as $arg) { + if(strpos($arg,'=') == false) { + $_GET['args'][] = $arg; + } else { + $newItems = array(); + parse_str( (substr($arg,0,2) == '--') ? substr($arg,2) : $arg, $newItems ); + $_GET = array_merge($_GET, $newItems); + } + } + $_REQUEST = array_merge($_REQUEST, $_GET); } // Set 'url' GET parameter @@ -76,7 +76,7 @@ DB::connect($databaseConfig); $url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : null; if(!$url) { echo 'Please specify an argument to cli-script.php/sake. For more information, visit' - . ' http://doc.silverstripe.org/framework/en/topics/commandline'; + . ' http://doc.silverstripe.org/framework/en/topics/commandline'; die(); } diff --git a/conf/ConfigureFromEnv.php b/conf/ConfigureFromEnv.php index f07e39fa7..a994f2880 100644 --- a/conf/ConfigureFromEnv.php +++ b/conf/ConfigureFromEnv.php @@ -100,8 +100,8 @@ if(defined('SS_DATABASE_USERNAME') && defined('SS_DATABASE_PASSWORD')) { } // For schema enabled drivers: - if(defined('SS_DATABASE_SCHEMA')) - $databaseConfig["schema"] = SS_DATABASE_SCHEMA; + if(defined('SS_DATABASE_SCHEMA')) + $databaseConfig["schema"] = SS_DATABASE_SCHEMA; } if(defined('SS_SEND_ALL_EMAILS_TO')) { diff --git a/control/ContentNegotiator.php b/control/ContentNegotiator.php index 515489777..f877b9845 100644 --- a/control/ContentNegotiator.php +++ b/control/ContentNegotiator.php @@ -45,7 +45,7 @@ class ContentNegotiator { * that need to specify the character set make use of this function. */ public static function get_encoding() { - return self::$encoding; + return self::$encoding; } /** @@ -96,7 +96,7 @@ class ContentNegotiator { } else { // The W3C validator doesn't send an HTTP_ACCEPT header, but it can support xhtml. We put this special // case in here so that designers don't get worried that their templates are HTML4. - if(isset($_SERVER['HTTP_USER_AGENT']) && substr($_SERVER['HTTP_USER_AGENT'], 0, 14) == 'W3C_Validator/') { + if(isset($_SERVER['HTTP_USER_AGENT']) && substr($_SERVER['HTTP_USER_AGENT'], 0, 14) == 'W3C_Validator/') { $chosenFormat = "xhtml"; } else { diff --git a/control/Controller.php b/control/Controller.php index 9ffea9ff0..b0e8727e7 100644 --- a/control/Controller.php +++ b/control/Controller.php @@ -164,7 +164,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider { Debug::message("Request handler $body->class object to $this->class controller;" . "rendering with template returned by $body->class::getViewer()"); } - $body = $body->getViewer($request->latestParam('Action'))->process($body); + $body = $body->getViewer($request->latestParam('Action'))->process($body); } $this->response->setBody($body); @@ -367,7 +367,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider { return $template->process($obj); } - + /** * Call this to disable site-wide basic authentication for a specific contoller. * This must be called before Controller::init(). That is, you must call it in your controller's diff --git a/control/Director.php b/control/Director.php index 023433c17..86684bc67 100644 --- a/control/Director.php +++ b/control/Director.php @@ -376,8 +376,8 @@ class Director implements TemplateGlobalProvider { $url = dirname($_SERVER['REQUEST_URI'] . 'x') . '/' . $url; } - if(substr($url,0,4) != "http") { - if($url[0] != "/") $url = Director::baseURL() . $url; + if(substr($url,0,4) != "http") { + if($url[0] != "/") $url = Director::baseURL() . $url; // Sometimes baseURL() can return a full URL instead of just a path if(substr($url,0,4) != "http") $url = self::protocolAndHost() . $url; } @@ -583,23 +583,23 @@ class Director implements TemplateGlobalProvider { */ public static function is_absolute_url($url) { $colonPosition = strpos($url, ':'); - return ( - // Base check for existence of a host on a compliant URL - parse_url($url, PHP_URL_HOST) - // Check for more than one leading slash without a protocol. - // While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers, - // and hence a potential security risk. Single leading slashes are not an issue though. - || preg_match('/\s*[\/]{2,}/', $url) - || ( - // If a colon is found, check if it's part of a valid scheme definition - // (meaning its not preceded by a slash, hash or questionmark). - // URLs in query parameters are assumed to be correctly urlencoded based on RFC3986, - // in which case no colon should be present in the parameters. - $colonPosition !== FALSE - && !preg_match('![/?#]!', substr($url, 0, $colonPosition)) - ) - - ); + return ( + // Base check for existence of a host on a compliant URL + parse_url($url, PHP_URL_HOST) + // Check for more than one leading slash without a protocol. + // While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers, + // and hence a potential security risk. Single leading slashes are not an issue though. + || preg_match('/\s*[\/]{2,}/', $url) + || ( + // If a colon is found, check if it's part of a valid scheme definition + // (meaning its not preceded by a slash, hash or questionmark). + // URLs in query parameters are assumed to be correctly urlencoded based on RFC3986, + // in which case no colon should be present in the parameters. + $colonPosition !== FALSE + && !preg_match('![/?#]!', substr($url, 0, $colonPosition)) + ) + + ); } /** @@ -681,21 +681,21 @@ class Director implements TemplateGlobalProvider { /** * Returns the Absolute URL of the site root. */ - public static function absoluteBaseURL() { - return Director::absoluteURL(Director::baseURL()); - } - + public static function absoluteBaseURL() { + return Director::absoluteURL(Director::baseURL()); + } + /** * Returns the Absolute URL of the site root, embedding the current basic-auth credentials into the URL. */ - public static function absoluteBaseURLWithAuth() { + public static function absoluteBaseURLWithAuth() { $s = ""; $login = ""; - if(isset($_SERVER['PHP_AUTH_USER'])) $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@"; + if(isset($_SERVER['PHP_AUTH_USER'])) $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@"; - return Director::protocol() . $login . $_SERVER['HTTP_HOST'] . Director::baseURL(); - } + return Director::protocol() . $login . $_SERVER['HTTP_HOST'] . Director::baseURL(); + } /** * Force the site to run on SSL. @@ -926,7 +926,7 @@ class Director implements TemplateGlobalProvider { $result = $_GET['isDev']; } else { if($firstTimeCheckingGetVar && DB::connection_attempted()) { - echo "

Sorry, you can't use ?isDev=1 until your Member and Group tables database are available. Perhaps your database connection is failing?

"; diff --git a/control/HTTP.php b/control/HTTP.php index e85daa8bd..03e776efa 100644 --- a/control/HTTP.php +++ b/control/HTTP.php @@ -171,8 +171,8 @@ class HTTP { if($regexes) foreach($regexes as $regex) { if(preg_match_all($regex, $content, $matches)) { $result = array_merge_recursive($result, (isset($matches[2]) ? $matches[2] : $matches[1])); - } - } + } + } return count($result) ? $result : null; } diff --git a/control/HTTPRequest.php b/control/HTTPRequest.php index 210c4af9b..753594588 100644 --- a/control/HTTPRequest.php +++ b/control/HTTPRequest.php @@ -252,21 +252,21 @@ class SS_HTTPRequest implements ArrayAccess { public function getURL($includeGetVars = false) { $url = ($this->getExtension()) ? $this->url . '.' . $this->getExtension() : $this->url; - if ($includeGetVars) { - // if we don't unset $vars['url'] we end up with /my/url?url=my/url&foo=bar etc - - $vars = $this->getVars(); - unset($vars['url']); + if ($includeGetVars) { + // if we don't unset $vars['url'] we end up with /my/url?url=my/url&foo=bar etc + + $vars = $this->getVars(); + unset($vars['url']); - if (count($vars)) { - $url .= '?' . http_build_query($vars); - } - } - else if(strpos($url, "?") !== false) { - $url = substr($url, 0, strpos($url, "?")); - } + if (count($vars)) { + $url .= '?' . http_build_query($vars); + } + } + else if(strpos($url, "?") !== false) { + $url = substr($url, 0, strpos($url, "?")); + } - return $url; + return $url; } /** @@ -444,9 +444,9 @@ class SS_HTTPRequest implements ArrayAccess { * @return string */ public function shiftAllParams() { - $keys = array_keys($this->allParams); - $values = array_values($this->allParams); - $value = array_shift($values); + $keys = array_keys($this->allParams); + $values = array_values($this->allParams); + $value = array_shift($values); // push additional unparsed URL parts onto the parameter stack if(array_key_exists($this->unshiftedButParsedParts, $this->dirParts)) { @@ -558,10 +558,10 @@ class SS_HTTPRequest implements ArrayAccess { */ public function getIP() { if (!empty($_SERVER['HTTP_CLIENT_IP'])) { - //check ip from share internet + //check ip from share internet return $_SERVER['HTTP_CLIENT_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - //to check ip is pass from proxy + //to check ip is pass from proxy return $_SERVER['HTTP_X_FORWARDED_FOR']; } elseif(isset($_SERVER['REMOTE_ADDR'])) { return $_SERVER['REMOTE_ADDR']; @@ -577,12 +577,12 @@ class SS_HTTPRequest implements ArrayAccess { * @return array */ public function getAcceptMimetypes($includeQuality = false) { - $mimetypes = array(); - $mimetypesWithQuality = explode(',',$this->getHeader('Accept')); - foreach($mimetypesWithQuality as $mimetypeWithQuality) { - $mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality); - } - return $mimetypes; + $mimetypes = array(); + $mimetypesWithQuality = explode(',',$this->getHeader('Accept')); + foreach($mimetypesWithQuality as $mimetypeWithQuality) { + $mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality); + } + return $mimetypes; } /** diff --git a/control/HTTPResponse.php b/control/HTTPResponse.php index 24cf76b7b..16a801838 100644 --- a/control/HTTPResponse.php +++ b/control/HTTPResponse.php @@ -277,28 +277,28 @@ class SS_HTTPResponse_Exception extends Exception { /** * @see SS_HTTPResponse::__construct(); */ - public function __construct($body = null, $statusCode = null, $statusDescription = null) { - if($body instanceof SS_HTTPResponse) { - $this->setResponse($body); - } else { - $this->setResponse(new SS_HTTPResponse($body, $statusCode, $statusDescription)); - } - - parent::__construct($this->getResponse()->getBody(), $this->getResponse()->getStatusCode()); - } - - /** - * @return SS_HTTPResponse - */ - public function getResponse() { - return $this->response; - } - - /** - * @param SS_HTTPResponse $response - */ - public function setResponse(SS_HTTPResponse $response) { - $this->response = $response; - } + public function __construct($body = null, $statusCode = null, $statusDescription = null) { + if($body instanceof SS_HTTPResponse) { + $this->setResponse($body); + } else { + $this->setResponse(new SS_HTTPResponse($body, $statusCode, $statusDescription)); + } + + parent::__construct($this->getResponse()->getBody(), $this->getResponse()->getStatusCode()); + } + + /** + * @return SS_HTTPResponse + */ + public function getResponse() { + return $this->response; + } + + /** + * @param SS_HTTPResponse $response + */ + public function setResponse(SS_HTTPResponse $response) { + $this->response = $response; + } } diff --git a/control/RequestHandler.php b/control/RequestHandler.php index aecabf90b..c67793762 100644 --- a/control/RequestHandler.php +++ b/control/RequestHandler.php @@ -94,7 +94,7 @@ class RequestHandler extends ViewableData { * or by defining $allowed_actions in your {@link Form} class. */ static $allowed_actions = null; - + public function __construct() { $this->brokenOnConstruct = false; diff --git a/control/injector/AopProxyService.php b/control/injector/AopProxyService.php index 2bff1f5a4..aba7fa8a5 100644 --- a/control/injector/AopProxyService.php +++ b/control/injector/AopProxyService.php @@ -36,6 +36,6 @@ class AopProxyService { return $result; } - } + } } -} \ No newline at end of file +} diff --git a/core/ArrayLib.php b/core/ArrayLib.php index 72d59b857..039f0eb70 100644 --- a/core/ArrayLib.php +++ b/core/ArrayLib.php @@ -69,18 +69,18 @@ class ArrayLib { * @todo Improve documentation */ public static function array_values_recursive($arr) { - $lst = array(); - foreach(array_keys($arr) as $k){ - $v = $arr[$k]; - if (is_scalar($v)) { - $lst[] = $v; - } elseif (is_array($v)) { - $lst = array_merge( $lst, - self::array_values_recursive($v) - ); - } - } - return $lst; + $lst = array(); + foreach(array_keys($arr) as $k){ + $v = $arr[$k]; + if (is_scalar($v)) { + $lst[] = $v; + } elseif (is_array($v)) { + $lst = array_merge( $lst, + self::array_values_recursive($v) + ); + } + } + return $lst; } /** @@ -110,12 +110,12 @@ class ArrayLib { */ public static function is_associative($arr) { if(is_array($arr) && ! empty($arr)) { - for($iterator = count($arr) - 1; $iterator; $iterator--) { - if (!array_key_exists($iterator, $arr)) return true; - } - return !array_key_exists(0, $arr); - } - return false; + for($iterator = count($arr) - 1; $iterator; $iterator--) { + if (!array_key_exists($iterator, $arr)) return true; + } + return !array_key_exists(0, $arr); + } + return false; } /** diff --git a/core/Diff.php b/core/Diff.php index 1bfcd07f0..7fd94771a 100644 --- a/core/Diff.php +++ b/core/Diff.php @@ -27,21 +27,21 @@ define('USE_ASSERTS', function_exists('assert')); * @access private */ class _DiffOp { - var $type; - var $orig; - var $final; + var $type; + var $orig; + var $final; - public function reverse() { - trigger_error("pure virtual", E_USER_ERROR); - } + public function reverse() { + trigger_error("pure virtual", E_USER_ERROR); + } - public function norig() { - return $this->orig ? sizeof($this->orig) : 0; - } + public function norig() { + return $this->orig ? sizeof($this->orig) : 0; + } - public function nfinal() { - return $this->final ? sizeof($this->final) : 0; - } + public function nfinal() { + return $this->final ? sizeof($this->final) : 0; + } } /** @@ -50,18 +50,18 @@ class _DiffOp { * @access private */ class _DiffOp_Copy extends _DiffOp { - var $type = 'copy'; + var $type = 'copy'; - public function _DiffOp_Copy ($orig, $final = false) { - if (!is_array($final)) - $final = $orig; - $this->orig = $orig; - $this->final = $final; - } + public function _DiffOp_Copy ($orig, $final = false) { + if (!is_array($final)) + $final = $orig; + $this->orig = $orig; + $this->final = $final; + } - public function reverse() { - return new _DiffOp_Copy($this->final, $this->orig); - } + public function reverse() { + return new _DiffOp_Copy($this->final, $this->orig); + } } /** @@ -70,16 +70,16 @@ class _DiffOp_Copy extends _DiffOp { * @access private */ class _DiffOp_Delete extends _DiffOp { - var $type = 'delete'; + var $type = 'delete'; - public function _DiffOp_Delete ($lines) { - $this->orig = $lines; - $this->final = false; - } + public function _DiffOp_Delete ($lines) { + $this->orig = $lines; + $this->final = false; + } - public function reverse() { - return new _DiffOp_Add($this->orig); - } + public function reverse() { + return new _DiffOp_Add($this->orig); + } } /** @@ -88,16 +88,16 @@ class _DiffOp_Delete extends _DiffOp { * @access private */ class _DiffOp_Add extends _DiffOp { - var $type = 'add'; + var $type = 'add'; - public function _DiffOp_Add ($lines) { - $this->final = $lines; - $this->orig = false; - } + public function _DiffOp_Add ($lines) { + $this->final = $lines; + $this->orig = false; + } - public function reverse() { - return new _DiffOp_Delete($this->final); - } + public function reverse() { + return new _DiffOp_Delete($this->final); + } } /** @@ -106,16 +106,16 @@ class _DiffOp_Add extends _DiffOp { * @access private */ class _DiffOp_Change extends _DiffOp { - var $type = 'change'; + var $type = 'change'; - public function _DiffOp_Change ($orig, $final) { - $this->orig = $orig; - $this->final = $final; - } + public function _DiffOp_Change ($orig, $final) { + $this->orig = $orig; + $this->final = $final; + } - public function reverse() { - return new _DiffOp_Change($this->final, $this->orig); - } + public function reverse() { + return new _DiffOp_Change($this->final, $this->orig); + } } @@ -143,126 +143,126 @@ class _DiffOp_Change extends _DiffOp { */ class _DiffEngine { - public function diff ($from_lines, $to_lines) { - $n_from = sizeof($from_lines); - $n_to = sizeof($to_lines); + public function diff ($from_lines, $to_lines) { + $n_from = sizeof($from_lines); + $n_to = sizeof($to_lines); - $this->xchanged = $this->ychanged = array(); - $this->xv = $this->yv = array(); - $this->xind = $this->yind = array(); - unset($this->seq); - unset($this->in_seq); - unset($this->lcs); + $this->xchanged = $this->ychanged = array(); + $this->xv = $this->yv = array(); + $this->xind = $this->yind = array(); + unset($this->seq); + unset($this->in_seq); + unset($this->lcs); - // Skip leading common lines. - for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { - if ($from_lines[$skip] != $to_lines[$skip]) - break; - $this->xchanged[$skip] = $this->ychanged[$skip] = false; - } - // Skip trailing common lines. - $xi = $n_from; $yi = $n_to; - for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { - if ($from_lines[$xi] != $to_lines[$yi]) - break; - $this->xchanged[$xi] = $this->ychanged[$yi] = false; - } + // Skip leading common lines. + for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { + if ($from_lines[$skip] != $to_lines[$skip]) + break; + $this->xchanged[$skip] = $this->ychanged[$skip] = false; + } + // Skip trailing common lines. + $xi = $n_from; $yi = $n_to; + for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { + if ($from_lines[$xi] != $to_lines[$yi]) + break; + $this->xchanged[$xi] = $this->ychanged[$yi] = false; + } - // Ignore lines which do not exist in both files. - for ($xi = $skip; $xi < $n_from - $endskip; $xi++) - $xhash[$from_lines[$xi]] = 1; - for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { - $line = $to_lines[$yi]; - if ( ($this->ychanged[$yi] = empty($xhash[$line])) ) - continue; - $yhash[$line] = 1; - $this->yv[] = $line; - $this->yind[] = $yi; - } - for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { - $line = $from_lines[$xi]; - if ( ($this->xchanged[$xi] = empty($yhash[$line])) ) - continue; - $this->xv[] = $line; - $this->xind[] = $xi; - } + // Ignore lines which do not exist in both files. + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) + $xhash[$from_lines[$xi]] = 1; + for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { + $line = $to_lines[$yi]; + if ( ($this->ychanged[$yi] = empty($xhash[$line])) ) + continue; + $yhash[$line] = 1; + $this->yv[] = $line; + $this->yind[] = $yi; + } + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { + $line = $from_lines[$xi]; + if ( ($this->xchanged[$xi] = empty($yhash[$line])) ) + continue; + $this->xv[] = $line; + $this->xind[] = $xi; + } - // Find the LCS. - $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv)); + // Find the LCS. + $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv)); - // Merge edits when possible - $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); - $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); + // Merge edits when possible + $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); + $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); - // Compute the edit operations. - $edits = array(); - $xi = $yi = 0; - while ($xi < $n_from || $yi < $n_to) { - USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]); - USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]); + // Compute the edit operations. + $edits = array(); + $xi = $yi = 0; + while ($xi < $n_from || $yi < $n_to) { + USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]); + USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]); - // Skip matching "snake". - $copy = array(); - while ( $xi < $n_from && $yi < $n_to - && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { - $copy[] = $from_lines[$xi++]; - ++$yi; - } - if ($copy) - $edits[] = new _DiffOp_Copy($copy); + // Skip matching "snake". + $copy = array(); + while ( $xi < $n_from && $yi < $n_to + && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { + $copy[] = $from_lines[$xi++]; + ++$yi; + } + if ($copy) + $edits[] = new _DiffOp_Copy($copy); - // Find deletes & adds. - $delete = array(); - while ($xi < $n_from && $this->xchanged[$xi]) - $delete[] = $from_lines[$xi++]; + // Find deletes & adds. + $delete = array(); + while ($xi < $n_from && $this->xchanged[$xi]) + $delete[] = $from_lines[$xi++]; - $add = array(); - while ($yi < $n_to && $this->ychanged[$yi]) - $add[] = $to_lines[$yi++]; + $add = array(); + while ($yi < $n_to && $this->ychanged[$yi]) + $add[] = $to_lines[$yi++]; - if ($delete && $add) - $edits[] = new _DiffOp_Change($delete, $add); - elseif ($delete) - $edits[] = new _DiffOp_Delete($delete); - elseif ($add) - $edits[] = new _DiffOp_Add($add); - } - return $edits; - } + if ($delete && $add) + $edits[] = new _DiffOp_Change($delete, $add); + elseif ($delete) + $edits[] = new _DiffOp_Delete($delete); + elseif ($add) + $edits[] = new _DiffOp_Add($add); + } + return $edits; + } - /* Divide the Largest Common Subsequence (LCS) of the sequences - * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally - * sized segments. - * - * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an - * array of NCHUNKS+1 (X, Y) indexes giving the diving points between - * sub sequences. The first sub-sequence is contained in [X0, X1), - * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note - * that (X0, Y0) == (XOFF, YOFF) and - * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). - * - * This function assumes that the first lines of the specified portions - * of the two files do not match, and likewise that the last lines do not - * match. The caller must trim matching lines from the beginning and end - * of the portions it is going to specify. - */ - public function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) { + /* Divide the Largest Common Subsequence (LCS) of the sequences + * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally + * sized segments. + * + * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an + * array of NCHUNKS+1 (X, Y) indexes giving the diving points between + * sub sequences. The first sub-sequence is contained in [X0, X1), + * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note + * that (X0, Y0) == (XOFF, YOFF) and + * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). + * + * This function assumes that the first lines of the specified portions + * of the two files do not match, and likewise that the last lines do not + * match. The caller must trim matching lines from the beginning and end + * of the portions it is going to specify. + */ + public function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) { $flip = false; if ($xlim - $xoff > $ylim - $yoff) { - // Things seems faster (I'm not sure I understand why) - // when the shortest sequence in X. - $flip = true; - list ($xoff, $xlim, $yoff, $ylim) + // Things seems faster (I'm not sure I understand why) + // when the shortest sequence in X. + $flip = true; + list ($xoff, $xlim, $yoff, $ylim) = array( $yoff, $ylim, $xoff, $xlim); - } + } if ($flip) - for ($i = $ylim - 1; $i >= $yoff; $i--) + for ($i = $ylim - 1; $i >= $yoff; $i--) $ymatches[$this->xv[$i]][] = $i; else - for ($i = $ylim - 1; $i >= $yoff; $i--) + for ($i = $ylim - 1; $i >= $yoff; $i--) $ymatches[$this->yv[$i]][] = $i; $this->lcs = 0; @@ -273,70 +273,70 @@ class _DiffEngine $numer = $xlim - $xoff + $nchunks - 1; $x = $xoff; for ($chunk = 0; $chunk < $nchunks; $chunk++) { - if ($chunk > 0) + if ($chunk > 0) for ($i = 0; $i <= $this->lcs; $i++) - $ymids[$i][$chunk-1] = $this->seq[$i]; + $ymids[$i][$chunk-1] = $this->seq[$i]; - $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); - for ( ; $x < $x1; $x++) { - $line = $flip ? $this->yv[$x] : $this->xv[$x]; - if (empty($ymatches[$line])) - continue; + $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); + for ( ; $x < $x1; $x++) { + $line = $flip ? $this->yv[$x] : $this->xv[$x]; + if (empty($ymatches[$line])) + continue; $matches = $ymatches[$line]; - reset($matches); + reset($matches); while (list ($junk, $y) = each($matches)) - if (empty($this->in_seq[$y])) { + if (empty($this->in_seq[$y])) { $k = $this->_lcs_pos($y); USE_ASSERTS && assert($k > 0); $ymids[$k] = $ymids[$k-1]; break; - } + } while (list ($junk, $y) = each($matches)) { - if ($y > $this->seq[$k-1]) { + if ($y > $this->seq[$k-1]) { USE_ASSERTS && assert($y < $this->seq[$k]); // Optimization: this is a common case: // next match is just replacing previous match. $this->in_seq[$this->seq[$k]] = false; $this->seq[$k] = $y; $this->in_seq[$y] = 1; - } - else if (empty($this->in_seq[$y])) { + } + else if (empty($this->in_seq[$y])) { $k = $this->_lcs_pos($y); USE_ASSERTS && assert($k > 0); $ymids[$k] = $ymids[$k-1]; - } - } - } - } + } + } + } + } $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); $ymid = $ymids[$this->lcs]; for ($n = 0; $n < $nchunks - 1; $n++) { - $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); - $y1 = $ymid[$n] + 1; - $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); - } + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); + $y1 = $ymid[$n] + 1; + $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); + } $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); return array($this->lcs, $seps); - } + } - public function _lcs_pos ($ypos) { + public function _lcs_pos ($ypos) { $end = $this->lcs; if ($end == 0 || $ypos > $this->seq[$end]) { - $this->seq[++$this->lcs] = $ypos; - $this->in_seq[$ypos] = 1; - return $this->lcs; - } + $this->seq[++$this->lcs] = $ypos; + $this->in_seq[$ypos] = 1; + return $this->lcs; + } $beg = 1; while ($beg < $end) { - $mid = (int)(($beg + $end) / 2); - if ( $ypos > $this->seq[$mid] ) + $mid = (int)(($beg + $end) / 2); + if ( $ypos > $this->seq[$mid] ) $beg = $mid + 1; - else + else $end = $mid; - } + } USE_ASSERTS && assert($ypos != $this->seq[$end]); @@ -344,77 +344,77 @@ class _DiffEngine $this->seq[$end] = $ypos; $this->in_seq[$ypos] = 1; return $end; - } + } - /* Find LCS of two sequences. - * - * The results are recorded in the vectors $this->{x,y}changed[], by - * storing a 1 in the element for each line that is an insertion - * or deletion (ie. is not in the LCS). - * - * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. - * - * Note that XLIM, YLIM are exclusive bounds. - * All line numbers are origin-0 and discarded lines are not counted. - */ - public function _compareseq ($xoff, $xlim, $yoff, $ylim) { + /* Find LCS of two sequences. + * + * The results are recorded in the vectors $this->{x,y}changed[], by + * storing a 1 in the element for each line that is an insertion + * or deletion (ie. is not in the LCS). + * + * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. + * + * Note that XLIM, YLIM are exclusive bounds. + * All line numbers are origin-0 and discarded lines are not counted. + */ + public function _compareseq ($xoff, $xlim, $yoff, $ylim) { // Slide down the bottom initial diagonal. while ($xoff < $xlim && $yoff < $ylim - && $this->xv[$xoff] == $this->yv[$yoff]) { - ++$xoff; - ++$yoff; - } + && $this->xv[$xoff] == $this->yv[$yoff]) { + ++$xoff; + ++$yoff; + } // Slide up the top initial diagonal. while ($xlim > $xoff && $ylim > $yoff - && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { - --$xlim; - --$ylim; - } + && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { + --$xlim; + --$ylim; + } if ($xoff == $xlim || $yoff == $ylim) - $lcs = 0; + $lcs = 0; else { - // This is ad hoc but seems to work well. - //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); - //$nchunks = max(2,min(8,(int)$nchunks)); - $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; - list ($lcs, $seps) + // This is ad hoc but seems to work well. + //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); + //$nchunks = max(2,min(8,(int)$nchunks)); + $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; + list ($lcs, $seps) = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks); - } + } if ($lcs == 0) { - // X and Y sequences have no common subsequence: - // mark all changed. - while ($yoff < $ylim) + // X and Y sequences have no common subsequence: + // mark all changed. + while ($yoff < $ylim) $this->ychanged[$this->yind[$yoff++]] = 1; - while ($xoff < $xlim) + while ($xoff < $xlim) $this->xchanged[$this->xind[$xoff++]] = 1; - } + } else { - // Use the partitions to split this problem into subproblems. - reset($seps); - $pt1 = $seps[0]; - while ($pt2 = next($seps)) { + // Use the partitions to split this problem into subproblems. + reset($seps); + $pt1 = $seps[0]; + while ($pt2 = next($seps)) { $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); $pt1 = $pt2; - } - } - } + } + } + } - /* Adjust inserts/deletes of identical lines to join changes - * as much as possible. - * - * We do something when a run of changed lines include a - * line at one end and has an excluded, identical line at the other. - * We are free to choose which identical line is included. - * 'compareseq' usually chooses the one at the beginning, - * but usually it is cleaner to consider the following identical line - * to be the "change". - * - * This is extracted verbatim from analyze.c (GNU diffutils-2.7). - */ - public function _shift_boundaries ($lines, &$changed, $other_changed) { + /* Adjust inserts/deletes of identical lines to join changes + * as much as possible. + * + * We do something when a run of changed lines include a + * line at one end and has an excluded, identical line at the other. + * We are free to choose which identical line is included. + * 'compareseq' usually chooses the one at the beginning, + * but usually it is cleaner to consider the following identical line + * to be the "change". + * + * This is extracted verbatim from analyze.c (GNU diffutils-2.7). + */ + public function _shift_boundaries ($lines, &$changed, $other_changed) { $i = 0; $j = 0; @@ -423,37 +423,37 @@ class _DiffEngine $other_len = sizeof($other_changed); while (1) { - /* - * Scan forwards to find beginning of another run of changes. - * Also keep track of the corresponding point in the other file. - * - * Throughout this code, $i and $j are adjusted together so that - * the first $i elements of $changed and the first $j elements - * of $other_changed both contain the same number of zeros - * (unchanged lines). - * Furthermore, $j is always kept so that $j == $other_len or - * $other_changed[$j] == false. - */ - while ($j < $other_len && $other_changed[$j]) + /* + * Scan forwards to find beginning of another run of changes. + * Also keep track of the corresponding point in the other file. + * + * Throughout this code, $i and $j are adjusted together so that + * the first $i elements of $changed and the first $j elements + * of $other_changed both contain the same number of zeros + * (unchanged lines). + * Furthermore, $j is always kept so that $j == $other_len or + * $other_changed[$j] == false. + */ + while ($j < $other_len && $other_changed[$j]) $j++; - while ($i < $len && ! $changed[$i]) { + while ($i < $len && ! $changed[$i]) { USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); $i++; $j++; while ($j < $other_len && $other_changed[$j]) - $j++; - } + $j++; + } - if ($i == $len) + if ($i == $len) break; - $start = $i; + $start = $i; - // Find the end of this run of changes. - while (++$i < $len && $changed[$i]) + // Find the end of this run of changes. + while (++$i < $len && $changed[$i]) continue; - do { + do { /* * Record the length of this run of changes, so that * we can later determine whether the run has grown. @@ -466,15 +466,15 @@ class _DiffEngine * This merges with previous changed regions. */ while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { - $changed[--$start] = 1; - $changed[--$i] = false; - while ($start > 0 && $changed[$start - 1]) + $changed[--$start] = 1; + $changed[--$i] = false; + while ($start > 0 && $changed[$start - 1]) $start--; - USE_ASSERTS && assert('$j > 0'); - while ($other_changed[--$j]) + USE_ASSERTS && assert('$j > 0'); + while ($other_changed[--$j]) continue; - USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); - } + USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); + } /* * Set CORRESPONDING to the end of the changed run, at the last @@ -491,35 +491,35 @@ class _DiffEngine * the changed region is moved forward as far as possible. */ while ($i < $len && $lines[$start] == $lines[$i]) { - $changed[$start++] = false; - $changed[$i++] = 1; - while ($i < $len && $changed[$i]) + $changed[$start++] = false; + $changed[$i++] = 1; + while ($i < $len && $changed[$i]) $i++; - USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); - $j++; - if ($j < $other_len && $other_changed[$j]) { + USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); + $j++; + if ($j < $other_len && $other_changed[$j]) { $corresponding = $i; while ($j < $other_len && $other_changed[$j]) - $j++; - } - } - } while ($runlength != $i - $start); + $j++; + } + } + } while ($runlength != $i - $start); - /* - * If possible, move the fully-merged run of changes - * back to a corresponding run in the other file. - */ - while ($corresponding < $i) { + /* + * If possible, move the fully-merged run of changes + * back to a corresponding run in the other file. + */ + while ($corresponding < $i) { $changed[--$start] = 1; $changed[--$i] = 0; USE_ASSERTS && assert('$j > 0'); while ($other_changed[--$j]) - continue; + continue; USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); - } - } - } + } + } + } } /** @@ -531,138 +531,138 @@ class Diff { public static $html_cleaner_class = null; - var $edits; + var $edits; - /** - * Constructor. - * Computes diff between sequences of strings. - * - * @param $from_lines array An array of strings. - * (Typically these are lines from a file.) - * @param $to_lines array An array of strings. - */ - public function Diff($from_lines, $to_lines) { - $eng = new _DiffEngine; - $this->edits = $eng->diff($from_lines, $to_lines); - //$this->_check($from_lines, $to_lines); - } + /** + * Constructor. + * Computes diff between sequences of strings. + * + * @param $from_lines array An array of strings. + * (Typically these are lines from a file.) + * @param $to_lines array An array of strings. + */ + public function Diff($from_lines, $to_lines) { + $eng = new _DiffEngine; + $this->edits = $eng->diff($from_lines, $to_lines); + //$this->_check($from_lines, $to_lines); + } - /** - * Compute reversed Diff. - * - * SYNOPSIS: - * - * $diff = new Diff($lines1, $lines2); - * $rev = $diff->reverse(); - * @return object A Diff object representing the inverse of the - * original diff. - */ - public function reverse () { + /** + * Compute reversed Diff. + * + * SYNOPSIS: + * + * $diff = new Diff($lines1, $lines2); + * $rev = $diff->reverse(); + * @return object A Diff object representing the inverse of the + * original diff. + */ + public function reverse () { $rev = $this; - $rev->edits = array(); - foreach ($this->edits as $edit) { - $rev->edits[] = $edit->reverse(); - } + $rev->edits = array(); + foreach ($this->edits as $edit) { + $rev->edits[] = $edit->reverse(); + } return $rev; - } + } - /** - * Check for empty diff. - * - * @return bool True iff two sequences were identical. - */ - public function isEmpty () { - foreach ($this->edits as $edit) { - if ($edit->type != 'copy') - return false; - } - return true; - } + /** + * Check for empty diff. + * + * @return bool True iff two sequences were identical. + */ + public function isEmpty () { + foreach ($this->edits as $edit) { + if ($edit->type != 'copy') + return false; + } + return true; + } - /** - * Compute the length of the Longest Common Subsequence (LCS). - * - * This is mostly for diagnostic purposed. - * - * @return int The length of the LCS. - */ - public function lcs () { + /** + * Compute the length of the Longest Common Subsequence (LCS). + * + * This is mostly for diagnostic purposed. + * + * @return int The length of the LCS. + */ + public function lcs () { $lcs = 0; - foreach ($this->edits as $edit) { - if ($edit->type == 'copy') - $lcs += sizeof($edit->orig); - } + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $lcs += sizeof($edit->orig); + } return $lcs; - } + } - /** - * Get the original set of lines. - * - * This reconstructs the $from_lines parameter passed to the - * constructor. - * - * @return array The original sequence of strings. - */ - public function orig() { - $lines = array(); + /** + * Get the original set of lines. + * + * This reconstructs the $from_lines parameter passed to the + * constructor. + * + * @return array The original sequence of strings. + */ + public function orig() { + $lines = array(); - foreach ($this->edits as $edit) { - if ($edit->orig) - array_splice($lines, sizeof($lines), 0, $edit->orig); - } - return $lines; - } + foreach ($this->edits as $edit) { + if ($edit->orig) + array_splice($lines, sizeof($lines), 0, $edit->orig); + } + return $lines; + } - /** - * Get the final set of lines. - * - * This reconstructs the $to_lines parameter passed to the - * constructor. - * - * @return array The sequence of strings. - */ - public function finaltext() { - $lines = array(); + /** + * Get the final set of lines. + * + * This reconstructs the $to_lines parameter passed to the + * constructor. + * + * @return array The sequence of strings. + */ + public function finaltext() { + $lines = array(); - foreach ($this->edits as $edit) { - if ($edit->final) - array_splice($lines, sizeof($lines), 0, $edit->final); - } - return $lines; - } + foreach ($this->edits as $edit) { + if ($edit->final) + array_splice($lines, sizeof($lines), 0, $edit->final); + } + return $lines; + } - /** - * Check a Diff for validity. - * - * This is here only for debugging purposes. - */ - public function _check ($from_lines, $to_lines) { - if (serialize($from_lines) != serialize($this->orig())) - trigger_error("Reconstructed original doesn't match", E_USER_ERROR); - if (serialize($to_lines) != serialize($this->finaltext())) - trigger_error("Reconstructed final doesn't match", E_USER_ERROR); + /** + * Check a Diff for validity. + * + * This is here only for debugging purposes. + */ + public function _check ($from_lines, $to_lines) { + if (serialize($from_lines) != serialize($this->orig())) + trigger_error("Reconstructed original doesn't match", E_USER_ERROR); + if (serialize($to_lines) != serialize($this->finaltext())) + trigger_error("Reconstructed final doesn't match", E_USER_ERROR); - $rev = $this->reverse(); - if (serialize($to_lines) != serialize($rev->orig())) - trigger_error("Reversed original doesn't match", E_USER_ERROR); - if (serialize($from_lines) != serialize($rev->finaltext())) - trigger_error("Reversed final doesn't match", E_USER_ERROR); + $rev = $this->reverse(); + if (serialize($to_lines) != serialize($rev->orig())) + trigger_error("Reversed original doesn't match", E_USER_ERROR); + if (serialize($from_lines) != serialize($rev->finaltext())) + trigger_error("Reversed final doesn't match", E_USER_ERROR); - $prevtype = 'none'; - foreach ($this->edits as $edit) { - if ( $prevtype == $edit->type ) - trigger_error("Edit sequence is non-optimal", E_USER_ERROR); - $prevtype = $edit->type; - } + $prevtype = 'none'; + foreach ($this->edits as $edit) { + if ( $prevtype == $edit->type ) + trigger_error("Edit sequence is non-optimal", E_USER_ERROR); + $prevtype = $edit->type; + } - $lcs = $this->lcs(); - trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE); - } - - - - /** + $lcs = $this->lcs(); + trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE); + } + + + + /** * Attempt to clean invalid HTML, which messes up diffs. * This cleans code if possible, using an instance of HTMLCleaner * @@ -750,7 +750,7 @@ class Diff else $rechunked[$listName][] = $item; if($lookForTag && !$tagStack[$listName] && isset($item[0]) && $item[0] == "<" - && substr($item,0,2) != "Diff($mapped_from_lines, $mapped_to_lines); + $this->Diff($mapped_from_lines, $mapped_to_lines); - $xi = $yi = 0; - // Optimizing loop invariants: - // http://phplens.com/lens/php-book/optimizing-debugging-php.php - for ($i = 0, $max = sizeof($this->edits); $i < $max; $i++) { - $orig = &$this->edits[$i]->orig; - if (is_array($orig)) { - $orig = array_slice($from_lines, $xi, sizeof($orig)); - $xi += sizeof($orig); - } + $xi = $yi = 0; + // Optimizing loop invariants: + // http://phplens.com/lens/php-book/optimizing-debugging-php.php + for ($i = 0, $max = sizeof($this->edits); $i < $max; $i++) { + $orig = &$this->edits[$i]->orig; + if (is_array($orig)) { + $orig = array_slice($from_lines, $xi, sizeof($orig)); + $xi += sizeof($orig); + } - $final = &$this->edits[$i]->final; - if (is_array($final)) { - $final = array_slice($to_lines, $yi, sizeof($final)); - $yi += sizeof($final); - } - } - } + $final = &$this->edits[$i]->final; + if (is_array($final)) { + $final = array_slice($to_lines, $yi, sizeof($final)); + $yi += sizeof($final); + } + } + } } diff --git a/dev/BulkLoader.php b/dev/BulkLoader.php index bcd0652e0..7db06dfc6 100644 --- a/dev/BulkLoader.php +++ b/dev/BulkLoader.php @@ -140,7 +140,7 @@ abstract class BulkLoader extends ViewableData { //get all instances of the to be imported data object if($this->deleteExistingRecords) { - DataObject::get($this->objectClass)->removeAll(); + DataObject::get($this->objectClass)->removeAll(); } return $this->processAll($filepath); @@ -273,12 +273,12 @@ class BulkLoader_Result extends Object { * */ protected $created = array(); - + /** * @var array (see {@link $created}) */ protected $updated = array(); - + /** * @var array (see {@link $created}) */ @@ -290,7 +290,7 @@ class BulkLoader_Result extends Object { * one of 3 strings: "created", "updated", or "deleted" */ protected $lastChange = array(); - + /** * Returns the count of all objects which were * created or updated. @@ -408,5 +408,4 @@ class BulkLoader_Result extends Object { return $set; } - } diff --git a/dev/DevelopmentAdmin.php b/dev/DevelopmentAdmin.php index 531150708..07d0874e7 100644 --- a/dev/DevelopmentAdmin.php +++ b/dev/DevelopmentAdmin.php @@ -18,15 +18,15 @@ class DevelopmentAdmin extends Controller { ); static $allowed_actions = array( - 'index', - 'tests', - 'jstests', - 'tasks', - 'viewmodel', - 'build', - 'reset', - 'viewcode' - ); + 'index', + 'tests', + 'jstests', + 'tasks', + 'viewmodel', + 'build', + 'reset', + 'viewcode' + ); public function init() { parent::init(); @@ -56,7 +56,7 @@ class DevelopmentAdmin extends Controller { $matched = false; if(isset($_FILE_TO_URL_MAPPING[$testPath])) { $matched = true; - break; + break; } $testPath = dirname($testPath); } diff --git a/dev/FunctionalTest.php b/dev/FunctionalTest.php index c95f4919b..351d95e35 100644 --- a/dev/FunctionalTest.php +++ b/dev/FunctionalTest.php @@ -8,14 +8,14 @@ * * * public function testMyForm() { - * // Visit a URL - * $this->get("your/url"); + * // Visit a URL + * $this->get("your/url"); * - * // Submit a form on the page that you get in response - * $this->submitForm("MyForm_ID", array("Email" => "invalid email ^&*&^")); + * // Submit a form on the page that you get in response + * $this->submitForm("MyForm_ID", array("Email" => "invalid email ^&*&^")); * - * // Validate the content that is returned - * $this->assertExactMatchBySelector("#MyForm_ID p.error", array("That email address is invalid.")); + * // Validate the content that is returned + * $this->assertExactMatchBySelector("#MyForm_ID p.error", array("That email address is invalid.")); * } * * @@ -70,10 +70,10 @@ class FunctionalTest extends SapphireTest { if($this->stat('use_draft_site')) { $this->useDraftSite(); } - - // Unprotect the site, tests are running with the assumption it's off. They will enable it on a case-by-case - // basis. - BasicAuth::protect_entire_site(false); + + // Unprotect the site, tests are running with the assumption it's off. They will enable it on a case-by-case + // basis. + BasicAuth::protect_entire_site(false); SecurityToken::disable(); } @@ -193,8 +193,8 @@ class FunctionalTest extends SapphireTest { foreach($expectedMatches as $match) { $this->assertTrue( isset($actuals[$match]), - "Failed asserting the CSS selector '$selector' has a partial match to the expected elements:\n'" - . implode("'\n'", $expectedMatches) . "'\n\n" + "Failed asserting the CSS selector '$selector' has a partial match to the expected elements:\n'" + . implode("'\n'", $expectedMatches) . "'\n\n" . "Instead the following elements were found:\n'" . implode("'\n'", array_keys($actuals)) . "'" ); return false; diff --git a/dev/Profiler.php b/dev/Profiler.php index 6353f0a87..ad805b4b4 100644 --- a/dev/Profiler.php +++ b/dev/Profiler.php @@ -14,223 +14,223 @@ * @subpackage misc */ class Profiler { - var $description; - var $startTime; - var $endTime; - var $initTime; - var $cur_timer; - var $stack; - var $trail; - var $trace; - var $count; - var $running; - - protected static $inst; + var $description; + var $startTime; + var $endTime; + var $initTime; + var $cur_timer; + var $stack; + var $trail; + var $trace; + var $count; + var $running; + + protected static $inst; - /** - * Initialise the timer. with the current micro time - */ - public function Profiler( $output_enabled=false, $trace_enabled=false) - { - $this->description = array(); - $this->startTime = array(); - $this->endTime = array(); - $this->initTime = 0; - $this->cur_timer = ""; - $this->stack = array(); - $this->trail = ""; - $this->trace = ""; - $this->count = array(); - $this->running = array(); - $this->initTime = $this->getMicroTime(); - $this->output_enabled = $output_enabled; - $this->trace_enabled = $trace_enabled; - $this->startTimer('unprofiled'); - } + /** + * Initialise the timer. with the current micro time + */ + public function Profiler( $output_enabled=false, $trace_enabled=false) + { + $this->description = array(); + $this->startTime = array(); + $this->endTime = array(); + $this->initTime = 0; + $this->cur_timer = ""; + $this->stack = array(); + $this->trail = ""; + $this->trace = ""; + $this->count = array(); + $this->running = array(); + $this->initTime = $this->getMicroTime(); + $this->output_enabled = $output_enabled; + $this->trace_enabled = $trace_enabled; + $this->startTimer('unprofiled'); + } - // Public Methods - - public static function init() { - if(!self::$inst) self::$inst = new Profiler(true,true); - } - - public static function mark($name, $level2 = "", $desc = "") { - if($level2 && $_GET['debug_profile'] > 1) $name .= " $level2"; - - if(!self::$inst) self::$inst = new Profiler(true,true); - - self::$inst->startTimer($name, $desc); - } - public static function unmark($name, $level2 = "", $desc = "") { - if($level2 && $_GET['debug_profile'] > 1) $name .= " $level2"; - - if(!self::$inst) self::$inst = new Profiler(true,true); - - self::$inst->stopTimer($name, $desc); - } - public static function show($showTrace = false) { - if(!self::$inst) self::$inst = new Profiler(true,true); - - echo "
"; - echo "

" - . "(Click to close)

"; - self::$inst->printTimers(); - if($showTrace) self::$inst->printTrace(); - echo "
"; - } + // Public Methods + + public static function init() { + if(!self::$inst) self::$inst = new Profiler(true,true); + } + + public static function mark($name, $level2 = "", $desc = "") { + if($level2 && $_GET['debug_profile'] > 1) $name .= " $level2"; + + if(!self::$inst) self::$inst = new Profiler(true,true); + + self::$inst->startTimer($name, $desc); + } + public static function unmark($name, $level2 = "", $desc = "") { + if($level2 && $_GET['debug_profile'] > 1) $name .= " $level2"; + + if(!self::$inst) self::$inst = new Profiler(true,true); + + self::$inst->stopTimer($name, $desc); + } + public static function show($showTrace = false) { + if(!self::$inst) self::$inst = new Profiler(true,true); + + echo "
"; + echo "

" + . "(Click to close)

"; + self::$inst->printTimers(); + if($showTrace) self::$inst->printTrace(); + echo "
"; + } - /** - * Start an individual timer - * This will pause the running timer and place it on a stack. - * @param string $name name of the timer - * @param string optional $desc description of the timer - */ - public function startTimer($name, $desc="" ){ - $this->trace.="start $name\n"; - $n=array_push( $this->stack, $this->cur_timer ); - $this->__suspendTimer( $this->stack[$n-1] ); - $this->startTime[$name] = $this->getMicroTime(); - $this->cur_timer=$name; - $this->description[$name] = $desc; - if (!array_key_exists($name,$this->count)) - $this->count[$name] = 1; - else - $this->count[$name]++; - } + /** + * Start an individual timer + * This will pause the running timer and place it on a stack. + * @param string $name name of the timer + * @param string optional $desc description of the timer + */ + public function startTimer($name, $desc="" ){ + $this->trace.="start $name\n"; + $n=array_push( $this->stack, $this->cur_timer ); + $this->__suspendTimer( $this->stack[$n-1] ); + $this->startTime[$name] = $this->getMicroTime(); + $this->cur_timer=$name; + $this->description[$name] = $desc; + if (!array_key_exists($name,$this->count)) + $this->count[$name] = 1; + else + $this->count[$name]++; + } - /** - * Stop an individual timer - * Restart the timer that was running before this one - * @param string $name name of the timer - */ - public function stopTimer($name){ - $this->trace.="stop $name\n"; - $this->endTime[$name] = $this->getMicroTime(); - if (!array_key_exists($name, $this->running)) - $this->running[$name] = $this->elapsedTime($name); - else - $this->running[$name] += $this->elapsedTime($name); - $this->cur_timer=array_pop($this->stack); - $this->__resumeTimer($this->cur_timer); - } + /** + * Stop an individual timer + * Restart the timer that was running before this one + * @param string $name name of the timer + */ + public function stopTimer($name){ + $this->trace.="stop $name\n"; + $this->endTime[$name] = $this->getMicroTime(); + if (!array_key_exists($name, $this->running)) + $this->running[$name] = $this->elapsedTime($name); + else + $this->running[$name] += $this->elapsedTime($name); + $this->cur_timer=array_pop($this->stack); + $this->__resumeTimer($this->cur_timer); + } - /** - * measure the elapsed time of a timer without stoping the timer if - * it is still running - */ - public function elapsedTime($name){ - // This shouldn't happen, but it does once. - if (!array_key_exists($name,$this->startTime)) - return 0; + /** + * measure the elapsed time of a timer without stoping the timer if + * it is still running + */ + public function elapsedTime($name){ + // This shouldn't happen, but it does once. + if (!array_key_exists($name,$this->startTime)) + return 0; - if(array_key_exists($name,$this->endTime)){ - return ($this->endTime[$name] - $this->startTime[$name]); - } else { - $now=$this->getMicroTime(); - return ($now - $this->startTime[$name]); - } - }//end start_time + if(array_key_exists($name,$this->endTime)){ + return ($this->endTime[$name] - $this->startTime[$name]); + } else { + $now=$this->getMicroTime(); + return ($now - $this->startTime[$name]); + } + }//end start_time - /** - * Measure the elapsed time since the profile class was initialised - * - */ - public function elapsedOverall(){ - $oaTime = $this->getMicroTime() - $this->initTime; - return($oaTime); - }//end start_time + /** + * Measure the elapsed time since the profile class was initialised + * + */ + public function elapsedOverall(){ + $oaTime = $this->getMicroTime() - $this->initTime; + return($oaTime); + }//end start_time - /** - * print out a log of all the timers that were registered - * - */ - public function printTimers($enabled=false) - { - if($this->output_enabled||$enabled){ - $TimedTotal = 0; - $tot_perc = 0; - ksort($this->description); - print("
\n");
-            $oaTime = $this->getMicroTime() - $this->initTime;
-            echo"============================================================================\n";
-            echo "                              PROFILER OUTPUT\n";
-            echo"============================================================================\n";
-            print( "Calls                    Time  Routine\n");
-            echo"-----------------------------------------------------------------------------\n";
-            while (list ($key, $val) = each ($this->description)) {
-                $t = $this->elapsedTime($key);
-                $total = $this->running[$key];
-                $count = $this->count[$key];
-                $TimedTotal += $total;
-                $perc = ($total/$oaTime)*100;
-                $tot_perc+=$perc;
-                // $perc=sprintf("%3.2f", $perc );
-                $lines[ sprintf( "%3d    %3.4f ms (%3.2f %%)  %s\n", $count, $total*1000, $perc, $key) ] = $total;
-            }
+	/**
+	*   print out a log of all the timers that were registered
+	*
+	*/
+	public function printTimers($enabled=false)
+	{
+		if($this->output_enabled||$enabled){
+			$TimedTotal = 0;
+			$tot_perc = 0;
+			ksort($this->description);
+			print("
\n");
+			$oaTime = $this->getMicroTime() - $this->initTime;
+			echo"============================================================================\n";
+			echo "                              PROFILER OUTPUT\n";
+			echo"============================================================================\n";
+			print( "Calls                    Time  Routine\n");
+			echo"-----------------------------------------------------------------------------\n";
+			while (list ($key, $val) = each ($this->description)) {
+				$t = $this->elapsedTime($key);
+				$total = $this->running[$key];
+				$count = $this->count[$key];
+				$TimedTotal += $total;
+				$perc = ($total/$oaTime)*100;
+				$tot_perc+=$perc;
+				// $perc=sprintf("%3.2f", $perc );
+				$lines[ sprintf( "%3d    %3.4f ms (%3.2f %%)  %s\n", $count, $total*1000, $perc, $key) ] = $total;
+			}
 			arsort($lines);
 			foreach($lines as $line => $total) {
 				echo $line;
 			}
 
-            echo "\n";
+			echo "\n";
 
-            $missed=$oaTime-$TimedTotal;
-            $perc = ($missed/$oaTime)*100;
-            $tot_perc+=$perc;
-            // $perc=sprintf("%3.2f", $perc );
-            printf( "       %3.4f ms (%3.2f %%)  %s\n", $missed*1000,$perc, "Missed");
+			$missed=$oaTime-$TimedTotal;
+			$perc = ($missed/$oaTime)*100;
+			$tot_perc+=$perc;
+			// $perc=sprintf("%3.2f", $perc );
+			printf( "       %3.4f ms (%3.2f %%)  %s\n", $missed*1000,$perc, "Missed");
 
-            echo"============================================================================\n";
+			echo"============================================================================\n";
 
-            printf( "       %3.4f ms (%3.2f %%)  %s\n", $oaTime*1000,$tot_perc, "OVERALL TIME");
+			printf( "       %3.4f ms (%3.2f %%)  %s\n", $oaTime*1000,$tot_perc, "OVERALL TIME");
 
-            echo"============================================================================\n";
+			echo"============================================================================\n";
 
-            print("
"); - } - } + print("
"); + } + } - public function printTrace( $enabled=false ) - { - if($this->trace_enabled||$enabled){ - print("
");
-            print("Trace\n$this->trace\n\n");
-            print("
"); - } - } + public function printTrace( $enabled=false ) + { + if($this->trace_enabled||$enabled){ + print("
");
+			print("Trace\n$this->trace\n\n");
+			print("
"); + } + } - /// Internal Use Only Functions + /// Internal Use Only Functions - /** - * Get the current time as accuratly as possible - * - */ - public function getMicroTime(){ - $tmp=explode(' ', microtime()); - $rt=$tmp[0]+$tmp[1]; - return $rt; - } + /** + * Get the current time as accuratly as possible + * + */ + public function getMicroTime(){ + $tmp=explode(' ', microtime()); + $rt=$tmp[0]+$tmp[1]; + return $rt; + } - /** - * resume an individual timer - * - */ - public function __resumeTimer($name){ - $this->trace.="resume $name\n"; - $this->startTime[$name] = $this->getMicroTime(); - } + /** + * resume an individual timer + * + */ + public function __resumeTimer($name){ + $this->trace.="resume $name\n"; + $this->startTime[$name] = $this->getMicroTime(); + } - /** - * suspend an individual timer - * - */ - public function __suspendTimer($name){ - $this->trace.="suspend $name\n"; - $this->endTime[$name] = $this->getMicroTime(); - if (!array_key_exists($name, $this->running)) - $this->running[$name] = $this->elapsedTime($name); - else - $this->running[$name] += $this->elapsedTime($name); - } + /** + * suspend an individual timer + * + */ + public function __suspendTimer($name){ + $this->trace.="suspend $name\n"; + $this->endTime[$name] = $this->getMicroTime(); + if (!array_key_exists($name, $this->running)) + $this->running[$name] = $this->elapsedTime($name); + else + $this->running[$name] += $this->elapsedTime($name); + } } diff --git a/dev/SapphireTestReporter.php b/dev/SapphireTestReporter.php index 36af8b18f..84da76c72 100644 --- a/dev/SapphireTestReporter.php +++ b/dev/SapphireTestReporter.php @@ -83,17 +83,17 @@ class SapphireTestReporter implements PHPUnit_Framework_TestListener { public function __construct() { @include_once 'Benchmark/Timer.php'; if(class_exists('Benchmark_Timer')) { - $this->timer = new Benchmark_Timer(); - $this->hasTimer = true; + $this->timer = new Benchmark_Timer(); + $this->hasTimer = true; } else { - $this->hasTimer = false; + $this->hasTimer = false; } - $this->suiteResults = array( - 'suites' => array(), // array of suites run - 'hasTimer' => $this->hasTimer, // availability of PEAR Benchmark_Timer - 'totalTests' => 0 // total number of tests run - ); + $this->suiteResults = array( + 'suites' => array(), // array of suites run + 'hasTimer' => $this->hasTimer, // availability of PEAR Benchmark_Timer + 'totalTests' => 0 // total number of tests run + ); } /** @@ -117,14 +117,14 @@ class SapphireTestReporter implements PHPUnit_Framework_TestListener { public function startTestSuite( PHPUnit_Framework_TestSuite $suite) { if(strlen($suite->getName())) { $this->endCurrentTestSuite(); - $this->currentSuite = array( - 'suite' => $suite, // the test suite - 'tests' => array(), // the tests in the suite - 'errors' => 0, // number of tests with errors (including setup errors) - 'failures' => 0, // number of tests which failed - 'incomplete' => 0, // number of tests that were not completed correctly + $this->currentSuite = array( + 'suite' => $suite, // the test suite + 'tests' => array(), // the tests in the suite + 'errors' => 0, // number of tests with errors (including setup errors) + 'failures' => 0, // number of tests which failed + 'incomplete' => 0, // number of tests that were not completed correctly 'error' => null); // Any error encountered during setup of the test suite - } + } } /** @@ -226,7 +226,7 @@ class SapphireTestReporter implements PHPUnit_Framework_TestListener { $this->currentSuite['incomplete']++; $this->addStatus(TEST_INCOMPLETE, $e->toString(), $this->getTestException($test, $e), $e->getTrace()); } - + /** * Not used * @@ -257,7 +257,7 @@ class SapphireTestReporter implements PHPUnit_Framework_TestListener { $this->currentTest = null; } - /** + /** * Upon completion of a test, records the execution time (if available) and adds the test to * the tests performed in the current suite. * diff --git a/dev/SilverStripeListener.php b/dev/SilverStripeListener.php index c6c5c01a1..009591f82 100644 --- a/dev/SilverStripeListener.php +++ b/dev/SilverStripeListener.php @@ -27,13 +27,13 @@ class SilverStripeListener implements PHPUnit_Framework_TestListener { public function startTest(PHPUnit_Framework_Test $test) { } - + public function endTest(PHPUnit_Framework_Test $test, $time) { } public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { } - + public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { } diff --git a/dev/TeamCityListener.php b/dev/TeamCityListener.php index e924ed8a4..7af8a6c52 100644 --- a/dev/TeamCityListener.php +++ b/dev/TeamCityListener.php @@ -27,7 +27,7 @@ class TeamCityListener implements PHPUnit_Framework_TestListener { $class = get_class($test); echo "##teamcity[testStarted name='{$class}.{$test->getName()}']\n"; } - + public function endTest(PHPUnit_Framework_Test $test, $time) { $class = get_class($test); echo "##teamcity[testFinished name='{$class}.{$test->getName()}' duration='$time']\n"; @@ -40,7 +40,7 @@ class TeamCityListener implements PHPUnit_Framework_TestListener { echo "##teamcity[testFailed type='exception' name='{$class}.{$test->getName()}' message='$message'" . " details='$trace']\n"; } - + public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { $class = get_class($test); $message = $this->escape($e->getMessage()); diff --git a/dev/TestListener.php b/dev/TestListener.php index 5fcd93cfe..5494dfd89 100644 --- a/dev/TestListener.php +++ b/dev/TestListener.php @@ -13,15 +13,15 @@ class SS_TestListener implements PHPUnit_Framework_TestListener { public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) {} - public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) {} + public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) {} - public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) {} + public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) {} - public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) {} + public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) {} - public function startTest(PHPUnit_Framework_Test $test) {} + public function startTest(PHPUnit_Framework_Test $test) {} - public function endTest(PHPUnit_Framework_Test $test, $time) {} + public function endTest(PHPUnit_Framework_Test $test, $time) {} public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { $name = $suite->getName(); @@ -29,7 +29,7 @@ class SS_TestListener implements PHPUnit_Framework_TestListener { $this->class = new $name(); $this->class->setUpOnce(); - } + } public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { $name = $suite->getName(); diff --git a/dev/phpunit/PhpUnitWrapper.php b/dev/phpunit/PhpUnitWrapper.php index 0af2fefea..194339ea7 100644 --- a/dev/phpunit/PhpUnitWrapper.php +++ b/dev/phpunit/PhpUnitWrapper.php @@ -138,14 +138,14 @@ class PhpUnitWrapper implements IPhpUnitWrapper { public static function inst() { if (self::$phpunit_wrapper == null) { - if (fileExistsInIncludePath("/PHPUnit/Autoload.php")) { - self::$phpunit_wrapper = new PhpUnitWrapper_3_5(); - } else - if (fileExistsInIncludePath("/PHPUnit/Framework.php")) { - self::$phpunit_wrapper = new PhpUnitWrapper_3_4(); - } else { - self::$phpunit_wrapper = new PhpUnitWrapper(); - } + if (fileExistsInIncludePath("/PHPUnit/Autoload.php")) { + self::$phpunit_wrapper = new PhpUnitWrapper_3_5(); + } else + if (fileExistsInIncludePath("/PHPUnit/Framework.php")) { + self::$phpunit_wrapper = new PhpUnitWrapper_3_4(); + } else { + self::$phpunit_wrapper = new PhpUnitWrapper(); + } self::$phpunit_wrapper->init(); } diff --git a/dev/phpunit/PhpUnitWrapper_3_5.php b/dev/phpunit/PhpUnitWrapper_3_5.php index fdfe61dac..3085ad572 100644 --- a/dev/phpunit/PhpUnitWrapper_3_5.php +++ b/dev/phpunit/PhpUnitWrapper_3_5.php @@ -35,11 +35,11 @@ class PhpUnitWrapper_3_5 extends PhpUnitWrapper { protected function beforeRunTests() { if($this->getCoverageStatus()) { - $this->coverage = new PHP_CodeCoverage(); + $this->coverage = new PHP_CodeCoverage(); $coverage = $this->coverage; - $filter = $coverage->filter(); - $modules = $this->moduleDirectories(); + $filter = $coverage->filter(); + $modules = $this->moduleDirectories(); foreach(TestRunner::$coverage_filter_dirs as $dir) { if($dir[0] == '*') { diff --git a/email/Email.php b/email/Email.php index 59466b565..0a95099be 100644 --- a/email/Email.php +++ b/email/Email.php @@ -363,12 +363,12 @@ class Email extends ViewableData { * @desc Validates the email address. Returns true of false */ public static function validEmailAddress($address) { - if (function_exists('filter_var')) { - return filter_var($address, FILTER_VALIDATE_EMAIL); - } else { - return preg_match('#^([a-zA-Z0-9_+\.\-]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)' - . '|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$#', $address); - } + if (function_exists('filter_var')) { + return filter_var($address, FILTER_VALIDATE_EMAIL); + } else { + return preg_match('#^([a-zA-Z0-9_+\.\-]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)' + . '|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$#', $address); + } } /** diff --git a/email/Mailer.php b/email/Mailer.php index 6f055ff42..734771916 100644 --- a/email/Mailer.php +++ b/email/Mailer.php @@ -41,7 +41,7 @@ class Mailer extends Object { /* * Sends an email as a both HTML and plaintext * $attachedFiles should be an array of file names - * - if you pass the entire $_FILES entry, the user-uploaded filename will be preserved + * - if you pass the entire $_FILES entry, the user-uploaded filename will be preserved * use $plainContent to override default plain-content generation * * @return bool @@ -54,9 +54,9 @@ function htmlEmail($to, $from, $subject, $htmlContent, $attachedFiles = false, $ dieprintr($customheaders); } - + $bodyIsUnicode = (strpos($htmlContent,"&#") !== false); - $plainEncoding = ""; + $plainEncoding = ""; // We generate plaintext content by default, but you can pass custom stuff $plainEncoding = ''; @@ -80,7 +80,7 @@ function htmlEmail($to, $from, $subject, $htmlContent, $attachedFiles = false, $ // Make the HTML part $headers["Content-Type"] = "text/html; charset=utf-8"; - + // Add basic wrapper tags if the body tag hasn't been given if(stripos($htmlContent, ']*)<([^<>]+)> *$/', $bounceAddress, $parts)) $bounceAddress = $parts[2]; + // Strip the human name from the bounce address + if(preg_match('/^([^<>]*)<([^<>]+)> *$/', $bounceAddress, $parts)) $bounceAddress = $parts[2]; // $headers["Sender"] = $from; $headers["X-Mailer"] = X_MAILER; @@ -406,9 +406,9 @@ function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $ $file['contents'] = QuotedPrintable_encode($file['contents']); } - $headers = "Content-type: $mimeType;\n\tname=\"$base\"\n". - "Content-Transfer-Encoding: $encoding\n". - "Content-Disposition: $disposition;\n\tfilename=\"$base\"\n" ; + $headers = "Content-type: $mimeType;\n\tname=\"$base\"\n". + "Content-Transfer-Encoding: $encoding\n". + "Content-Disposition: $disposition;\n\tfilename=\"$base\"\n"; if ( isset($file['contentLocation']) ) $headers .= 'Content-Location: ' . $file['contentLocation'] . "\n" ; @@ -443,4 +443,4 @@ function validEmailAddr($emailAddress) { } return $emailAddress; -} \ No newline at end of file +} diff --git a/filesystem/GD.php b/filesystem/GD.php index abb1d0a92..655bae201 100644 --- a/filesystem/GD.php +++ b/filesystem/GD.php @@ -116,9 +116,9 @@ class GD extends Object { * @todo This method isn't very efficent */ public function fittedResize($width, $height) { - $gd = $this->resizeByHeight($height); - if($gd->width > $width) $gd = $gd->resizeByWidth($width); - return $gd; + $gd = $this->resizeByHeight($height); + if($gd->width > $width) $gd = $gd->resizeByWidth($width); + return $gd; } public function hasGD() { @@ -169,9 +169,9 @@ class GD extends Object { if(!$this->gd) return; if(function_exists("imagerotate")) { - $newGD = imagerotate($this->gd, $angle,0); + $newGD = imagerotate($this->gd, $angle,0); } else { - //imagerotate is not included in PHP included in Ubuntu + //imagerotate is not included in PHP included in Ubuntu $newGD = $this->rotatePixelByPixel($angle); } $output = clone $this; @@ -180,45 +180,45 @@ class GD extends Object { } /** - * Rotates image by given angle. It's slow because makes it pixel by pixel rather than - * using built-in function. Used when imagerotate function is not available(i.e. Ubuntu) - * - * @param angle - * - * @return GD - */ + * Rotates image by given angle. It's slow because makes it pixel by pixel rather than + * using built-in function. Used when imagerotate function is not available(i.e. Ubuntu) + * + * @param angle + * + * @return GD + */ - public function rotatePixelByPixel($angle) { - $sourceWidth = imagesx($this->gd); - $sourceHeight = imagesy($this->gd); - if ($angle == 180) { - $destWidth = $sourceWidth; - $destHeight = $sourceHeight; - } else { - $destWidth = $sourceHeight; - $destHeight = $sourceWidth; - } - $rotate=imagecreatetruecolor($destWidth,$destHeight); - imagealphablending($rotate, false); - for ($x = 0; $x < ($sourceWidth); $x++) { - for ($y = 0; $y < ($sourceHeight); $y++) { - $color = imagecolorat($this->gd, $x, $y); - switch ($angle) { - case 90: - imagesetpixel($rotate, $y, $destHeight - $x - 1, $color); - break; - case 180: - imagesetpixel($rotate, $destWidth - $x - 1, $destHeight - $y - 1, $color); - break; - case 270: - imagesetpixel($rotate, $destWidth - $y - 1, $x, $color); - break; - default: $rotate = $this->gd; - }; - } - } - return $rotate; - } + public function rotatePixelByPixel($angle) { + $sourceWidth = imagesx($this->gd); + $sourceHeight = imagesy($this->gd); + if ($angle == 180) { + $destWidth = $sourceWidth; + $destHeight = $sourceHeight; + } else { + $destWidth = $sourceHeight; + $destHeight = $sourceWidth; + } + $rotate=imagecreatetruecolor($destWidth,$destHeight); + imagealphablending($rotate, false); + for ($x = 0; $x < ($sourceWidth); $x++) { + for ($y = 0; $y < ($sourceHeight); $y++) { + $color = imagecolorat($this->gd, $x, $y); + switch ($angle) { + case 90: + imagesetpixel($rotate, $y, $destHeight - $x - 1, $color); + break; + case 180: + imagesetpixel($rotate, $destWidth - $x - 1, $destHeight - $y - 1, $color); + break; + case 270: + imagesetpixel($rotate, $destWidth - $y - 1, $x, $color); + break; + default: $rotate = $this->gd; + }; + } + } + return $rotate; + } /** @@ -241,7 +241,7 @@ class GD extends Object { return $output; } - /** + /** * Method return width of image. * * @return integer width. @@ -304,9 +304,9 @@ class GD extends Object { /** * Resize to fit fully within the given box, without resizing. Extra space left around * the image will be padded with the background color. - * @param width - * @param height - * @param backgroundColour + * @param width + * @param height + * @param backgroundColour */ public function paddedResize($width, $height, $backgroundColor = "FFFFFF") { if(!$this->gd) return; diff --git a/forms/AjaxUniqueTextField.php b/forms/AjaxUniqueTextField.php index 952f507f5..4d76a3239 100644 --- a/forms/AjaxUniqueTextField.php +++ b/forms/AjaxUniqueTextField.php @@ -29,7 +29,7 @@ class AjaxUniqueTextField extends TextField { parent::__construct($name, $title, $value); } - + public function Field($properties = array()) { Requirements::javascript(THIRDPARTY_DIR . "/prototype/prototype.js"); Requirements::javascript(THIRDPARTY_DIR . "/behaviour/behaviour.js"); diff --git a/forms/CheckboxSetField.php b/forms/CheckboxSetField.php index 9db5c51d6..9d174706c 100644 --- a/forms/CheckboxSetField.php +++ b/forms/CheckboxSetField.php @@ -79,7 +79,7 @@ class CheckboxSetField extends OptionsetField { if(is_a($object, 'DataObject')) { $items[] = $object->ID; } - } + } } elseif($values && is_string($values)) { $items = explode(',', $values); $items = str_replace('{comma}', ',', $items); diff --git a/forms/ComplexTableField.php b/forms/ComplexTableField.php index 3ec2deb1a..147674aa5 100644 --- a/forms/ComplexTableField.php +++ b/forms/ComplexTableField.php @@ -35,7 +35,7 @@ class ComplexTableField extends TableListField { * - A method name, eg, 'getCMSFields': Call that method on the child object to get the fields. */ protected $addTitle; - + protected $detailFormFields; protected $viewAction; @@ -176,7 +176,7 @@ class ComplexTableField extends TableListField { public function PopupWidth() { return $this->popupWidth; } - + public function PopupHeight() { return $this->popupHeight; } @@ -286,13 +286,13 @@ JS; public function setDetailFormValidator( Validator $validator ) { $this->detailFormValidator = $validator; } - - public function setAddTitle($addTitle) { + + public function setAddTitle($addTitle) { if(is_string($addTitle)) $this->addTitle = $addTitle; } - - public function Title() { + + public function Title() { return $this->addTitle ? $this->addTitle : parent::Title(); } @@ -326,8 +326,8 @@ JS; // this is a workaround for a bug where each subsequent popup-call didn't have ID // of the parent set, and so didn't properly save the relation return ($idField) ? $idField->Value() : (isset($_REQUEST['ctf']['ID']) ? $_REQUEST['ctf']['ID'] : null); - } - + } + public function AddLink() { @@ -397,7 +397,7 @@ JS; $detailFields->push(new HiddenField('ctf[childID]', '', $childData->ID)); } - /* TODO: Figure out how to implement this + /* TODO: Figure out how to implement this if($this->getParentClass()) { $detailFields->push(new HiddenField('ctf[parentClass]', '', $this->getParentClass())); @@ -591,9 +591,9 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest { public function dataObj() { // used to discover fields if requested and for population of field if(is_numeric($this->itemID)) { - // we have to use the basedataclass, otherwise we might exclude other subclasses - return DataObject::get_by_id( - ClassInfo::baseDataClass(Object::getCustomClass($this->ctf->sourceClass())), $this->itemID); + // we have to use the basedataclass, otherwise we might exclude other subclasses + return DataObject::get_by_id( + ClassInfo::baseDataClass(Object::getCustomClass($this->ctf->sourceClass())), $tis->itemID); } } @@ -723,31 +723,31 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest { } /** - * Method handles pagination in asset popup. - * - * @return Object SS_List - */ + * Method handles pagination in asset popup. + * + * @return Object SS_List + */ public function Pagination() { $this->pageSize = 9; $currentItem = $this->PopupCurrentItem(); $result = new ArrayList(); - if($currentItem < 6) { - $offset = 1; - } elseif($this->TotalCount() - $currentItem <= 4) { - $offset = $currentItem - (10 - ($this->TotalCount() - $currentItem)); - $offset = $offset <= 0 ? 1 : $offset; - } else { - $offset = $currentItem - 5; - } - for($i = $offset;$i <= $offset + $this->pageSize && $i <= $this->TotalCount();$i++) { - $start = $i - 1; - $links['link'] = Controller::join_links($this->Link() . "$this->methodName?ctf[start]={$start}"); - $links['number'] = $i; - $links['active'] = $i == $currentItem ? false : true; - $result->push(new ArrayData($links)); + if($currentItem < 6) { + $offset = 1; + } elseif($this->TotalCount() - $currentItem <= 4) { + $offset = $currentItem - (10 - ($this->TotalCount() - $currentItem)); + $offset = $offset <= 0 ? 1 : $offset; + } else { + $offset = $currentItem - 5; } - return $result; + for($i = $offset;$i <= $offset + $this->pageSize && $i <= $this->TotalCount();$i++) { + $start = $i - 1; + $links['link'] = Controller::join_links($this->Link() . "$this->methodName?ctf[start]={$start}"); + $links['number'] = $i; + $links['active'] = $i == $currentItem ? false : true; + $result->push(new ArrayData($links)); + } + return $result; } public function ShowPagination() { @@ -768,7 +768,7 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest { * @param String $str Example: FamilyID (when one Individual has_one Family) */ public function setParentIdName($str) { - throw new Exception("setParentIdName is no longer necessary"); + throw new Exception("setParentIdName is no longer necessary"); } public function setTemplatePopup($template) { diff --git a/forms/CompositeField.php b/forms/CompositeField.php index 37923a7f8..5a9c933e9 100644 --- a/forms/CompositeField.php +++ b/forms/CompositeField.php @@ -171,8 +171,8 @@ class CompositeField extends FormField { $formName = (isset($this->form)) ? $this->form->FormName() : '(unknown form)'; if(isset($list[$name])) { user_error("collateDataFields() I noticed that a field called '$name' appears twice in" - . " your form: '{$formName}'. One is a '{$field->class}' and the other is a" - . " '{$list[$name]->class}'", E_USER_ERROR); + . " your form: '{$formName}'. One is a '{$field->class}' and the other is a" + . " '{$list[$name]->class}'", E_USER_ERROR); } $list[$name] = $field; } diff --git a/forms/CreditCardField.php b/forms/CreditCardField.php index 9f1f1f886..7e157cc2e 100644 --- a/forms/CreditCardField.php +++ b/forms/CreditCardField.php @@ -52,7 +52,7 @@ class CreditCardField extends TextField { if($this->value) foreach($this->value as $part){ if(!$part || !(strlen($part) == 4) || !preg_match("/([0-9]{4})/", $part)){ switch($i){ - case 0: $number = _t('CreditCardField.FIRST', 'first'); break; + case 0: $number = _t('CreditCardField.FIRST', 'first'); break; case 1: $number = _t('CreditCardField.SECOND', 'second'); break; case 2: $number = _t('CreditCardField.THIRD', 'third'); break; case 3: $number = _t('CreditCardField.FOURTH', 'fourth'); break; diff --git a/forms/DateField.php b/forms/DateField.php index 0b6d5c11f..753c08f6b 100644 --- a/forms/DateField.php +++ b/forms/DateField.php @@ -43,9 +43,9 @@ require_once 'Zend/Date.php'; * * ## Example: German dates with separate fields for day, month, year * - * $f = new DateField('MyDate'); - * $f->setLocale('de_DE'); - * $f->setConfig('dmyfields'); + * $f = new DateField('MyDate'); + * $f->setLocale('de_DE'); + * $f->setConfig('dmyfields'); * * # Validation * @@ -306,11 +306,11 @@ class DateField extends TextField { * @return boolean */ public static function set_default_config($k, $v) { - if (array_key_exists($k,self::$default_config)) { - self::$default_config[$k]=$v; - return true; - } - return false; + if (array_key_exists($k,self::$default_config)) { + self::$default_config[$k]=$v; + return true; + } + return false; } /** @@ -559,7 +559,7 @@ class DateField_View_JQuery extends Object { // Include language files (if required) if ($this->jqueryLocaleFile){ Requirements::javascript($this->jqueryLocaleFile); - } + } Requirements::javascript(FRAMEWORK_DIR . "/javascript/DateField.js"); } @@ -601,51 +601,51 @@ class DateField_View_JQuery extends Object { public static function convert_iso_to_jquery_format($format) { $convert = array( '/([^d])d([^d])/' => '$1d$2', - '/^d([^d])/' => 'd$1', - '/([^d])d$/' => '$1d', - '/dd/' => 'dd', - '/SS/' => '', - '/eee/' => 'd', - '/e/' => 'N', - '/D/' => '', - '/EEEE/' => 'DD', - '/EEE/' => 'D', - '/w/' => '', + '/^d([^d])/' => 'd$1', + '/([^d])d$/' => '$1d', + '/dd/' => 'dd', + '/SS/' => '', + '/eee/' => 'd', + '/e/' => 'N', + '/D/' => '', + '/EEEE/' => 'DD', + '/EEE/' => 'D', + '/w/' => '', // make single "M" lowercase - '/([^M])M([^M])/' => '$1m$2', + '/([^M])M([^M])/' => '$1m$2', // make single "M" at start of line lowercase - '/^M([^M])/' => 'm$1', + '/^M([^M])/' => 'm$1', // make single "M" at end of line lowercase - '/([^M])M$/' => '$1m', + '/([^M])M$/' => '$1m', // match exactly three capital Ms not preceeded or followed by an M - '/(? 'M', + '/(? 'M', // match exactly two capital Ms not preceeded or followed by an M - '/(? 'mm', + '/(? 'mm', // match four capital Ms (maximum allowed) - '/MMMM/' => 'MM', - '/l/' => '', - '/YYYY/' => 'yy', - '/yyyy/' => 'yy', - // See http://open.silverstripe.org/ticket/7669 - '/y{1,3}/' => 'yy', - '/a/' => '', - '/B/' => '', - '/hh/' => '', - '/h/' => '', - '/([^H])H([^H])/' => '', - '/^H([^H])/' => '', - '/([^H])H$/' => '', - '/HH/' => '', - // '/mm/' => '', - '/ss/' => '', - '/zzzz/' => '', - '/I/' => '', - '/ZZZZ/' => '', - '/Z/' => '', - '/z/' => '', - '/X/' => '', - '/r/' => '', - '/U/' => '', + '/MMMM/' => 'MM', + '/l/' => '', + '/YYYY/' => 'yy', + '/yyyy/' => 'yy', + // See http://open.silverstripe.org/ticket/7669 + '/y{1,3}/' => 'yy', + '/a/' => '', + '/B/' => '', + '/hh/' => '', + '/h/' => '', + '/([^H])H([^H])/' => '', + '/^H([^H])/' => '', + '/([^H])H$/' => '', + '/HH/' => '', + // '/mm/' => '', + '/ss/' => '', + '/zzzz/' => '', + '/I/' => '', + '/ZZZZ/' => '', + '/Z/' => '', + '/z/' => '', + '/X/' => '', + '/r/' => '', + '/U/' => '', ); $patterns = array_keys($convert); $replacements = array_values($convert); diff --git a/forms/FieldGroup.php b/forms/FieldGroup.php index 3ce94e97d..f3da6571f 100644 --- a/forms/FieldGroup.php +++ b/forms/FieldGroup.php @@ -90,12 +90,12 @@ class FieldGroup extends CompositeField { * * @param string $zebra one of odd or even. */ - public function setZebra($zebra) { - if($zebra == 'odd' || $zebra == 'even') $this->zebra = $zebra; - else user_error("setZebra passed '$zebra'. It should be passed 'odd' or 'even'", E_USER_WARNING); - return $this; - } - + public function setZebra($zebra) { + if($zebra == 'odd' || $zebra == 'even') $this->zebra = $zebra; + else user_error("setZebra passed '$zebra'. It should be passed 'odd' or 'even'", E_USER_WARNING); + return $this; + } + /** * @return string */ @@ -132,4 +132,4 @@ class FieldGroup extends CompositeField { public function php($data) { return; } -} \ No newline at end of file +} diff --git a/forms/Form.php b/forms/Form.php index d1cf61b03..258b82ba3 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -1065,7 +1065,7 @@ class Form extends RequestHandler { * * @return boolean */ - public function validate(){ + public function validate(){ if($this->validator){ $errors = $this->validator->validate(); @@ -1533,7 +1533,7 @@ class Form extends RequestHandler { $result .= ""; if( $this->validator ) - $result .= '

'._t('Form.VALIDATOR', 'Validator').'

' . $this->validator->debug(); + $result .= '

'._t('Form.VALIDATOR', 'Validator').'

' . $this->validator->debug(); return $result; } @@ -1550,8 +1550,8 @@ class Form extends RequestHandler { */ public function testSubmission($action, $data) { $data['action_' . $action] = true; - - return Director::test($this->FormAction(), $data, Controller::curr()->getSession()); + + return Director::test($this->FormAction(), $data, Controller::curr()->getSession()); //$response = $this->controller->run($data); //return $response; diff --git a/forms/FormField.php b/forms/FormField.php index 7b86b1fd5..1e1111e20 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -85,7 +85,7 @@ class FormField extends RequestHandler { protected $template, $fieldHolderTemplate, - $smallFieldHolderTemplate; + $smallFieldHolderTemplate; /** * @var array All attributes on the form field (not the field holder). @@ -597,14 +597,14 @@ class FormField extends RequestHandler { return $obj->renderWith($this->getFieldHolderTemplates()); } - /** - * Returns a restricted field holder used within things like FieldGroups. - * - * @param array $properties - * - * @return string - */ - public function SmallFieldHolder($properties = array()) { + /** + * Returns a restricted field holder used within things like FieldGroups. + * + * @param array $properties + * + * @return string + */ + public function SmallFieldHolder($properties = array()) { $obj = ($properties) ? $this->customise($properties) : $this; return $obj->renderWith($this->getSmallFieldHolderTemplates()); diff --git a/forms/HtmlEditorField.php b/forms/HtmlEditorField.php index e577e4cef..1d906697b 100644 --- a/forms/HtmlEditorField.php +++ b/forms/HtmlEditorField.php @@ -181,8 +181,8 @@ class HtmlEditorField extends TextareaField { // Save file & link tracking data. if(class_exists('SiteTree')) { if($record->ID && $record->many_many('LinkTracking') && $tracker = $record->LinkTracking()) { - $tracker->removeByFilter(sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', - $this->name, $record->ID)); + $tracker->removeByFilter(sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', + $this->name, $record->ID)); if($linkedPages) foreach($linkedPages as $item) { $SQL_fieldName = Convert::raw2sql($this->name); @@ -192,9 +192,9 @@ class HtmlEditorField extends TextareaField { } if($record->ID && $record->many_many('ImageTracking') && $tracker = $record->ImageTracking()) { - $tracker->where( - sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', $this->name, $record->ID) - )->removeAll(); + $tracker->where( + sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', $this->name, $record->ID) + )->removeAll(); $fieldName = $this->name; if($linkedFiles) foreach($linkedFiles as $item) { diff --git a/forms/ManyManyComplexTableField.php b/forms/ManyManyComplexTableField.php index fbd1e505d..12dde229e 100644 --- a/forms/ManyManyComplexTableField.php +++ b/forms/ManyManyComplexTableField.php @@ -59,7 +59,7 @@ class ManyManyComplexTableField extends HasManyComplexTableField { break; } $belongsManyManyRelations = $singleton->uninherited( 'belongs_many_many', true ); - if( isset( $belongsManyManyRelations ) && array_key_exists( $this->name, $belongsManyManyRelations ) ) { + if( isset( $belongsManyManyRelations ) && array_key_exists( $this->name, $belongsManyManyRelations ) ) { $this->manyManyParentClass = $class; $manyManyTable = $belongsManyManyRelations[$this->name] . '_' . $this->name; break; diff --git a/forms/NumericField.php b/forms/NumericField.php index a506960d6..ba519dae6 100644 --- a/forms/NumericField.php +++ b/forms/NumericField.php @@ -14,8 +14,8 @@ class NumericField extends TextField{ /** PHP Validation **/ public function validate($validator){ if($this->value && !is_numeric(trim($this->value))){ - $validator->validationError( - $this->name, + $validator->validationError( + $this->name, _t( 'NumericField.VALIDATION', "'{value}' is not a number, only numbers can be accepted for this field", array('value' => $this->value) diff --git a/forms/PhoneNumberField.php b/forms/PhoneNumberField.php index b2f0e599d..93c138bfb 100644 --- a/forms/PhoneNumberField.php +++ b/forms/PhoneNumberField.php @@ -33,11 +33,11 @@ class PhoneNumberField extends FormField { list($countryCode, $areaCode, $phoneNumber, $extension) = $this->parseValue(); $hasTitle = false; - if ($this->value=="") { - $countryCode=$this->countryCode; - $areaCode=$this->areaCode; - $extension=$this->ext; - } + if ($this->value=="") { + $countryCode=$this->countryCode; + $areaCode=$this->areaCode; + $extension=$this->ext; + } if($this->countryCode !== null) { $fields->push(new NumericField($this->name.'[Country]', '+', $countryCode, 4)); @@ -94,7 +94,7 @@ class PhoneNumberField extends FormField { preg_match( '/^(?:(?:\+(\d+))?\s*\((\d+)\))?\s*([0-9A-Za-z]*)\s*(?:[#]\s*(\d+))?$/', $this->value, $parts); else return array( '', '', $this->value, '' ); - + if(is_array($parts)) array_shift( $parts ); for ($x=0;$x<=3;$x++) { diff --git a/forms/RequiredFields.php b/forms/RequiredFields.php index abcaebe94..536140d9c 100644 --- a/forms/RequiredFields.php +++ b/forms/RequiredFields.php @@ -42,15 +42,15 @@ class RequiredFields extends Validator { * Debug helper */ public function debug() { - if(!is_array($this->required)) return false; + if(!is_array($this->required)) return false; - $result = "
    "; - foreach( $this->required as $name ){ - $result .= "
  • $name
  • "; - } + $result = "
      "; + foreach( $this->required as $name ){ + $result .= "
    • $name
    • "; + } - $result .= "
    "; - return $result; + $result .= "
"; + return $result; } /** diff --git a/forms/SimpleImageField.php b/forms/SimpleImageField.php index 56c4fce38..5ecda60a2 100644 --- a/forms/SimpleImageField.php +++ b/forms/SimpleImageField.php @@ -51,7 +51,7 @@ * function doform($data, $form) { * $file = new File(); * $file->loadUploaded($_FILES['FileTypeID']); - * + * * // Redirect to a page thanking people for registering * $this->redirect('thanks-for-your-submission/'); * } @@ -85,19 +85,19 @@ class SimpleImageField extends FileField { } public function Field($properties = array()) { - if($this->form) $record = $this->form->getRecord(); - $fieldName = $this->name; - if(isset($record)&&$record) { - $imageField = $record->$fieldName(); - } else { - $imageField = ""; - } - + if($this->form) $record = $this->form->getRecord(); + $fieldName = $this->name; + if(isset($record)&&$record) { + $imageField = $record->$fieldName(); + } else { + $imageField = ""; + } + $html = "
"; if($imageField && $imageField->exists()) { $html .= '
'; if($imageField->hasMethod('Thumbnail') && $imageField->Thumbnail()) { - $html .= "Thumbnail()->getURL()."\" />"; + $html .= "Thumbnail()->getURL()."\" />"; } else if($imageField->CMSThumbnail()) { $html .= "CMSThumbnail()->getURL()."\" />"; } @@ -124,7 +124,7 @@ class SimpleImageField extends FileField { return $html; } - + /** * Returns a readonly version of this field */ @@ -149,25 +149,25 @@ class SimpleImageField_Disabled extends FormField { public function Field($properties = array()) { $record = $this->form->getRecord(); - $fieldName = $this->name; + $fieldName = $this->name; - $field = "
"; + $field = "
"; if($this->value) { // Only the case for DataDifferencer $field .= $this->value; } else { if($record) $imageField = $record->$fieldName(); if($imageField && $imageField->exists()) { - if($imageField->hasMethod('Thumbnail')) $field .= "Thumbnail()->URL."\" />"; - elseif($imageField->CMSThumbnail()) $field .= "CMSThumbnail()->URL."\" />"; - else {} // This shouldn't be called but it sometimes is for some reason, so we don't do anything - }else{ - $field .= ""; - } + if($imageField->hasMethod('Thumbnail')) $field .= "Thumbnail()->URL."\" />"; + elseif($imageField->CMSThumbnail()) $field .= "CMSThumbnail()->URL."\" />"; + else {} // This shouldn't be called but it sometimes is for some reason, so we don't do anything + }else{ + $field .= ""; } - $field .= "
"; + } + $field .= "
"; - return $field; + return $field; } } diff --git a/forms/TableField.php b/forms/TableField.php index c017bed36..9f58deced 100644 --- a/forms/TableField.php +++ b/forms/TableField.php @@ -16,7 +16,7 @@ * @package forms * @subpackage fields-relational */ - + class TableField extends TableListField { protected $fieldList; @@ -244,9 +244,9 @@ class TableField extends TableListField { $dataObjects = $this->sortData($value, $record->ID); // New fields are nested in their own sub-array, and need to be sorted separately - if(isset($dataObjects['new']) && $dataObjects['new']) { - $newFields = $this->sortData($dataObjects['new'], $record->ID); - } + if(isset($dataObjects['new']) && $dataObjects['new']) { + $newFields = $this->sortData($dataObjects['new'], $record->ID); + } // Update existing fields // @todo Should this be in an else{} statement? @@ -255,7 +255,7 @@ class TableField extends TableListField { // Save newly added record if($savedObjIds || $newFields) { $savedObjIds = $this->saveData($newFields,false); - } + } // Add the new records to the DataList if($savedObjIds) foreach($savedObjIds as $id => $status) { @@ -416,7 +416,7 @@ class TableField extends TableListField { } return $savedObjIds; - } + } /** * Organises the data in the appropriate manner for saving @@ -444,8 +444,7 @@ class TableField extends TableListField { // TODO ADD stuff for removing rows with incomplete data } - - return $sortedData; + return $sortedData; } /** diff --git a/forms/TableListField.php b/forms/TableListField.php index 82196c021..112bc2cba 100644 --- a/forms/TableListField.php +++ b/forms/TableListField.php @@ -102,12 +102,12 @@ class TableListField extends FormField { * Actions can be disabled through $permissions. * Format (key is used for the methodname and CSS-class): * array( - * 'delete' => array( - * 'label' => 'Delete', - * 'icon' => 'framework/images/delete.gif', - * 'icon_disabled' => 'framework/images/delete_disabled.gif', - * 'class' => 'deletelink', - * ) + * 'delete' => array( + * 'label' => 'Delete', + * 'icon' => 'framework/images/delete.gif', + * 'icon_disabled' => 'framework/images/delete_disabled.gif', + * 'class' => 'deletelink', + * ) * ) */ public $actions = array( @@ -178,14 +178,14 @@ class TableListField extends FormField { * * Example: * array( - * array( - * "rule" => '$Flag == "red"', - * "class" => "red" - * ), - * array( - * "rule" => '$Flag == "orange"', - * "class" => "orange" - * ) + * array( + * "rule" => '$Flag == "red"', + * "class" => "red" + * ), + * array( + * "rule" => '$Flag == "orange"', + * "class" => "orange" + * ) * ) */ public $highlightConditions = array(); @@ -269,10 +269,10 @@ class TableListField extends FormField { ); public function sourceClass() { - $list = $this->getDataList(); - if(method_exists($list, 'dataClass')) return $list->dataClass(); - // Failover for SS_List - else return get_class($list->First()); + $list = $this->getDataList(); + if(method_exists($list, 'dataClass')) return $list->dataClass(); + // Failover for SS_List + else return get_class($list->First()); } public function handleItem($request) { @@ -423,7 +423,7 @@ JS // The type-hinting above doesn't seem to work consistently if($items instanceof SS_List) { - $this->dataList = $items; + $this->dataList = $items; } else { user_error('TableList::setCustomSourceItems() should be passed a SS_List', E_USER_WARNING); } @@ -440,29 +440,29 @@ JS // TODO: Sorting could be implemented on regular SS_Lists. if(method_exists($items,'canSortBy') && isset($_REQUEST['ctf'][$this->getName()]['sort'])) { - $sort = $_REQUEST['ctf'][$this->getName()]['sort']; - // TODO: sort direction + $sort = $_REQUEST['ctf'][$this->getName()]['sort']; + // TODO: sort direction if($items->canSortBy($sort)) $items = $items->sort($sort); } // Determine pagination limit, offset // To disable pagination, set $this->showPagination to false. if($this->showPagination && $this->pageSize) { - $SQL_limit = (int)$this->pageSize; - if(isset($_REQUEST['ctf'][$this->getName()]['start']) - && is_numeric($_REQUEST['ctf'][$this->getName()]['start'])) { + $SQL_limit = (int)$this->pageSize; + if(isset($_REQUEST['ctf'][$this->getName()]['start']) + && is_numeric($_REQUEST['ctf'][$this->getName()]['start'])) { - if(isset($_REQUEST['ctf'][$this->getName()]['start'])) { + if(isset($_REQUEST['ctf'][$this->getName()]['start'])) { $SQL_start = intval($_REQUEST['ctf'][$this->getName()]['start']); } else { $SQL_start = "0"; } - } else { - $SQL_start = 0; - } + } else { + $SQL_start = 0; + } - $items = $items->limit($SQL_limit, $SQL_start); - } + $items = $items->limit($SQL_limit, $SQL_start); + } return $items; } @@ -484,7 +484,7 @@ JS public function getDataList() { // If we weren't passed in a DataList to begin with, try and get the datalist from the form if($this->form && $this->getDataListFromForm) { - $this->getDataListFromForm = false; + $this->getDataListFromForm = false; $relation = $this->name; if($record = $this->form->getRecord()) { if($record->hasMethod($relation)) $this->dataList = $record->$relation(); @@ -508,10 +508,10 @@ JS */ public function getQuery() { Deprecation::notice('3.0', 'Use getDataList() instead.'); - $list = $this->getDataList(); - if(method_exists($list,'dataQuery')) { - return $this->getDataList()->dataQuery()->query(); - } + $list = $this->getDataList(); + if(method_exists($list,'dataQuery')) { + return $this->getDataList()->dataQuery()->query(); + } } /** @@ -519,10 +519,10 @@ JS */ public function getCsvQuery() { Deprecation::notice('3.0', 'Use getCsvDataList() instead.'); - $list = $this->getCsvDataList(); - if(method_exists($list,'dataQuery')) { - return $list->dataQuery()->query(); - } + $list = $this->getCsvDataList(); + if(method_exists($list,'dataQuery')) { + return $list->dataQuery()->query(); + } } public function FieldList() { @@ -581,20 +581,20 @@ JS $childId = Convert::raw2sql($_REQUEST['ctf']['childID']); if (is_numeric($childId)) { - $this->getDataList()->removeById($childId); + $this->getDataList()->removeById($childId); } // TODO return status in JSON etc. //return $this->renderWith($this->template); } - - + + /** * ################################# * Summary-Row * ################################# */ - + /** * Can utilize some built-in summary-functions, with optional casting. * Currently supported: @@ -710,7 +710,7 @@ JS return array_sum($values)/count($values); } - + /** * ################################# * Permissions @@ -770,14 +770,14 @@ JS } public function setPageSize($pageSize) { - $this->pageSize = $pageSize; - return $this; + $this->pageSize = $pageSize; + return $this; } - - public function PageSize() { + + public function PageSize() { return $this->pageSize; } - + public function ListStart() { return $_REQUEST['ctf'][$this->getName()]['start']; } @@ -937,9 +937,9 @@ JS * Return the total number of items in the source DataList */ public function TotalCount() { - if($this->_cache_TotalCount === null) { - $this->_cache_TotalCount = $this->getDataList()->Count(); - } + if($this->_cache_TotalCount === null) { + $this->_cache_TotalCount = $this->getDataList()->Count(); + } return $this->_cache_TotalCount; } @@ -951,13 +951,13 @@ JS * * @todo Not fully implemented at the moment */ - - /** - * Compile all request-parameters for search and pagination - * (except the actual list-positions) as a query-string. - * - * @return String URL-parameters - */ + + /** + * Compile all request-parameters for search and pagination + * (except the actual list-positions) as a query-string. + * + * @return String URL-parameters + */ public function filterString() { } @@ -969,10 +969,10 @@ JS * CSV Export * ################################# */ - public function setFieldListCsv($fields) { - $this->fieldListCsv = $fields; - return $this; - } + public function setFieldListCsv($fields) { + $this->fieldListCsv = $fields; + return $this; + } /** * Set the CSV separator character. Defaults to , @@ -996,7 +996,7 @@ JS $this->csvHasHeader = false; return $this; } - + /** * Exports a given set of comma-separated IDs (from a previous search-query, stored in a HiddenField). * Uses {$csv_columns} if present, and falls back to {$result_columns}. @@ -1181,24 +1181,24 @@ JS } public function Title() { - // adding translating functionality - // this is a bit complicated, because this parameter is passed to this class - // and should come here translated already - // adding this to TODO probably add a method to the classes - // to return they're translated string - // added by ruibarreiros @ 27/11/2007 + // adding translating functionality + // this is a bit complicated, because this parameter is passed to this class + // and should come here translated already + // adding this to TODO probably add a method to the classes + // to return they're translated string + // added by ruibarreiros @ 27/11/2007 return $this->sourceClass() ? singleton($this->sourceClass())->singular_name() : $this->getName(); } public function NameSingular() { - // same as Title() - // added by ruibarreiros @ 27/11/2007 - return $this->sourceClass() ? singleton($this->sourceClass())->singular_name() : $this->getName(); + // same as Title() + // added by ruibarreiros @ 27/11/2007 + return $this->sourceClass() ? singleton($this->sourceClass())->singular_name() : $this->getName(); } public function NamePlural() { - // same as Title() - // added by ruibarreiros @ 27/11/2007 + // same as Title() + // added by ruibarreiros @ 27/11/2007 return $this->sourceClass() ? singleton($this->sourceClass())->plural_name() : $this->getName(); } @@ -1232,7 +1232,7 @@ JS */ public function Link($action = null) { $form = $this->getForm(); - if($form) { + if($form) { $token = $form->getSecurityToken(); $parentUrlParts = parse_url(parent::Link($action)); $queryPart = (isset($parentUrlParts['query'])) ? '?' . $parentUrlParts['query'] : null; @@ -1302,7 +1302,7 @@ JS return $value; } - + public function setHighlightConditions($conditions) { $this->highlightConditions = $conditions; return $this; @@ -1476,7 +1476,7 @@ class TableListField_Item extends ViewableData { public function Link($action = null) { $form = $this->parent->getForm(); - if($form) { + if($form) { $token = $form->getSecurityToken(); $parentUrlParts = parse_url($this->parent->Link()); $queryPart = (isset($parentUrlParts['query'])) ? '?' . $parentUrlParts['query'] : null; @@ -1516,7 +1516,7 @@ class TableListField_Item extends ViewableData { return $allowedActions; } - + public function BaseLink() { user_error("TableListField_Item::BaseLink() deprecated, use Link() instead", E_USER_NOTICE); return $this->Link(); @@ -1567,7 +1567,7 @@ class TableListField_Item extends ViewableData { if(isset($condition['exclusive']) && $condition['exclusive']) { return $condition['class']; } else { - $classes[] = $condition['class']; + $classes[] = $condition['class']; } } } @@ -1628,8 +1628,8 @@ class TableListField_ItemRequest extends RequestHandler { public function dataObj() { // used to discover fields if requested and for population of field if(is_numeric($this->itemID)) { - // we have to use the basedataclass, otherwise we might exclude other subclasses - return $this->ctf->getDataList()->byId($this->itemID); + // we have to use the basedataclass, otherwise we might exclude other subclasses + return $this->ctf->getDataList()->byId($this->itemID); } } diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index 16e1215af..33d23100d 100644 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -211,7 +211,7 @@ class GridField extends FormField { public function getManipulatedList() { $list = $this->getList(); foreach($this->getComponents() as $item) { - if($item instanceof GridField_DataManipulator) { + if($item instanceof GridField_DataManipulator) { $list = $item->getManipulatedData($this, $list); } } diff --git a/javascript/ComplexTableField.js b/javascript/ComplexTableField.js index 1ffc8f7d6..2735e21f6 100644 --- a/javascript/ComplexTableField.js +++ b/javascript/ComplexTableField.js @@ -151,6 +151,6 @@ ComplexTableField.applyTo('div.ComplexTableField'); * Get first letter as uppercase */ String.prototype.ucfirst = function () { - var firstLetter = this.substr(0,1).toUpperCase() - return this.substr(0,1).toUpperCase() + this.substr(1,this.length); -} \ No newline at end of file + var firstLetter = this.substr(0,1).toUpperCase() + return this.substr(0,1).toUpperCase() + this.substr(1,this.length); +} diff --git a/javascript/GridField.js b/javascript/GridField.js index bbe020550..a458a2773 100644 --- a/javascript/GridField.js +++ b/javascript/GridField.js @@ -244,7 +244,6 @@ this._super(); this.selectable('destroy'); } - }); /** diff --git a/javascript/HtmlEditorField.js b/javascript/HtmlEditorField.js index 5b380b2bc..d33afd4c5 100644 --- a/javascript/HtmlEditorField.js +++ b/javascript/HtmlEditorField.js @@ -6,15 +6,15 @@ * ajax / iframe submissions */ - var ss = ss || {}; +var ss = ss || {}; /** * Wrapper for HTML WYSIWYG libraries, which abstracts library internals * from interface concerns like inserting and editing links. * Caution: Incomplete and unstable API. */ - ss.editorWrappers = {}; - ss.editorWrappers.initial - ss.editorWrappers.tinyMCE = (function() { +ss.editorWrappers = {}; +ss.editorWrappers.initial +ss.editorWrappers.tinyMCE = (function() { return { init: function(config) { if(!ss.editorWrappers.tinyMCE.initialized) { @@ -798,7 +798,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; if(header) header[(hasItems) ? 'show' : 'hide'](); // Disable "insert" button if no files are selected - this.find('.Actions :submit') + this.find('.Actions :submit') .button(hasItems ? 'enable' : 'disable') .toggleClass('ui-state-disabled', !hasItems); diff --git a/javascript/ImageFormAction.js b/javascript/ImageFormAction.js index 5e1b85128..e379bf1d1 100644 --- a/javascript/ImageFormAction.js +++ b/javascript/ImageFormAction.js @@ -1,6 +1,6 @@ (function($) { $(document).ready(function() { - $("input.rollover").live('mouseover', function(){ + $("input.rollover").live('mouseover', function(){ if(!this.overSrc) { var srcParts = $(this).attr('src').match( /(.*)\.([a-zA-Z]+)$/ ); var fileName = srcParts[1]; @@ -11,8 +11,8 @@ $(this).attr('src', this.overSrc); }); - $("input.rollover").live('mouseout', function(){ + $("input.rollover").live('mouseout', function(){ $(this).attr('src', this.outSrc); }); }); -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/javascript/TableField.js b/javascript/TableField.js index d78bf440f..3ca030cf1 100644 --- a/javascript/TableField.js +++ b/javascript/TableField.js @@ -126,7 +126,7 @@ TableField.prototype = { var tbody = table.tBodies[0]; var numRows = tbody.rows.length; var newRow = tbody.rows[0].cloneNode(true); - + // Get the input elements in this new row var inputs = newRow.getElementsByTagName('input'); // For every input, set it's value to blank if it is not hidden diff --git a/javascript/TableListField.js b/javascript/TableListField.js index e2fcbe88d..6b15f0f47 100644 --- a/javascript/TableListField.js +++ b/javascript/TableListField.js @@ -36,7 +36,7 @@ TableListField.prototype = { rules['#'+this.id+' table.data tr td.markingcheckbox'] = { onclick : function(e) { - // do nothing for clicks in marking box cells (e.g. if checkbox is missed) + // do nothing for clicks in marking box cells (e.g. if checkbox is missed) } }; @@ -176,15 +176,15 @@ TableListField.prototype = { } if(el.getAttribute('href')) { - jQuery.ajax({ + jQuery.ajax({ 'url': el.getAttribute('href'), 'data': {'update': 1}, - 'success': function(response) { + 'success': function(response) { jQuery('#' + self.id).replaceWith(response) // reapply behaviour and reattach methods to TF container node // e.g.
- Behaviour.apply(jQuery('#' + self.id)[0], true); - } + Behaviour.apply(jQuery('#' + self.id)[0], true); + } }); } @@ -371,4 +371,4 @@ Number.prototype.toCurrency = function(iso) { if(!iso) iso = SS_DEFAULT_ISO; // TODO stub, please implement properly return "$" + this.toFixed(2); -} \ No newline at end of file +} diff --git a/javascript/i18n.js b/javascript/i18n.js index cb99aa58b..ab8be9003 100644 --- a/javascript/i18n.js +++ b/javascript/i18n.js @@ -108,7 +108,7 @@ ss.i18n = { * @return string result : Stripped string * */ - stripStr: function(str) { + stripStr: function(str) { return str.replace(/^\s*/, "").replace(/\s*$/, ""); }, @@ -228,4 +228,4 @@ ss.i18n = { ss.i18n.addEvent(window, "load", function() { ss.i18n.init(); -}); \ No newline at end of file +}); diff --git a/javascript/jquery-ondemand/jquery.ondemand.js b/javascript/jquery-ondemand/jquery.ondemand.js index 6653cdce3..cbe6529df 100644 --- a/javascript/jquery-ondemand/jquery.ondemand.js +++ b/javascript/jquery-ondemand/jquery.ondemand.js @@ -23,7 +23,7 @@ // loaded files list - to protect against loading existed file again (by PGA) _ondemand_loaded_list : null, - + /** * Returns true if the given CSS or JS script has already been loaded */ @@ -160,4 +160,4 @@ }); -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/javascript/tree/tree.js b/javascript/tree/tree.js index c945bd29b..909fd95d1 100644 --- a/javascript/tree/tree.js +++ b/javascript/tree/tree.js @@ -212,7 +212,7 @@ Tree.prototype = { } // Update the helper classes accordingly - if(!hasChildren) this.removeNodeClass('children'); + if(!hasChildren) this.removeNodeClass('children'); else this.lastTreeNode().addNodeClass('last'); // Update the helper variables @@ -303,8 +303,8 @@ TreeNode.prototype = { // Move all the nodes up until that point into spanC for(j=startingPoint;jdataQuery()->query()->canSortBy($fieldName); + return $this->dataQuery()->query()->canSortBy($fieldName); } /** @@ -673,7 +673,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return mixed */ public function max($fieldName) { - return $this->dataQuery->max($fieldName); + return $this->dataQuery->max($fieldName); } /** @@ -683,7 +683,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return mixed */ public function min($fieldName) { - return $this->dataQuery->min($fieldName); + return $this->dataQuery->min($fieldName); } /** @@ -693,7 +693,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return mixed */ public function avg($fieldName) { - return $this->dataQuery->avg($fieldName); + return $this->dataQuery->avg($fieldName); } /** @@ -703,7 +703,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return mixed */ public function sum($fieldName) { - return $this->dataQuery->sum($fieldName); + return $this->dataQuery->sum($fieldName); } @@ -833,7 +833,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab // Index current data foreach($this->column() as $id) { - $has[$id] = true; + $has[$id] = true; } // Keep track of items to delete @@ -951,7 +951,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab */ public function newObject($initialFields = null) { $class = $this->dataClass; - return Injector::inst()->create($class, $initialFields, false, $this->model); + return Injector::inst()->create($class, $initialFields, false, $this->model); } /** @@ -967,14 +967,14 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab } - /** - * Remove an item from this DataList by ID + /** + * Remove an item from this DataList by ID * * @param int $itemID - The primary ID - */ + */ public function removeByID($itemID) { - $item = $this->byID($itemID); - if($item) return $item->delete(); + $item = $this->byID($itemID); + if($item) return $item->delete(); } /** @@ -1046,7 +1046,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return bool */ public function offsetExists($key) { - return ($this->limit(1,$key)->First() != null); + return ($this->limit(1,$key)->First() != null); } /** @@ -1056,7 +1056,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return DataObject */ public function offsetGet($key) { - return $this->limit(1, $key)->First(); + return $this->limit(1, $key)->First(); } /** @@ -1066,7 +1066,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @param mixed $value */ public function offsetSet($key, $value) { - user_error("Can't alter items in a DataList using array-access", E_USER_ERROR); + user_error("Can't alter items in a DataList using array-access", E_USER_ERROR); } /** @@ -1075,7 +1075,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @param mixed $key */ public function offsetUnset($key) { - user_error("Can't alter items in a DataList using array-access", E_USER_ERROR); + user_error("Can't alter items in a DataList using array-access", E_USER_ERROR); } } diff --git a/model/DataObject.php b/model/DataObject.php index b4c73f639..27ae22fe0 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -13,46 +13,46 @@ * * * class Article extends DataObject implements PermissionProvider { - * static $api_access = true; - * - * function canView($member = false) { - * return Permission::check('ARTICLE_VIEW'); - * } - * function canEdit($member = false) { - * return Permission::check('ARTICLE_EDIT'); - * } - * function canDelete() { - * return Permission::check('ARTICLE_DELETE'); - * } - * function canCreate() { - * return Permission::check('ARTICLE_CREATE'); - * } - * function providePermissions() { - * return array( - * 'ARTICLE_VIEW' => 'Read an article object', - * 'ARTICLE_EDIT' => 'Edit an article object', - * 'ARTICLE_DELETE' => 'Delete an article object', - * 'ARTICLE_CREATE' => 'Create an article object', - * ); - * } + * static $api_access = true; + * + * function canView($member = false) { + * return Permission::check('ARTICLE_VIEW'); + * } + * function canEdit($member = false) { + * return Permission::check('ARTICLE_EDIT'); + * } + * function canDelete() { + * return Permission::check('ARTICLE_DELETE'); + * } + * function canCreate() { + * return Permission::check('ARTICLE_CREATE'); + * } + * function providePermissions() { + * return array( + * 'ARTICLE_VIEW' => 'Read an article object', + * 'ARTICLE_EDIT' => 'Edit an article object', + * 'ARTICLE_DELETE' => 'Delete an article object', + * 'ARTICLE_CREATE' => 'Create an article object', + * ); + * } * } * * * Object-level access control by {@link Group} membership: * * class Article extends DataObject { - * static $api_access = true; - * - * function canView($member = false) { - * if(!$member) $member = Member::currentUser(); - * return $member->inGroup('Subscribers'); - * } - * function canEdit($member = false) { - * if(!$member) $member = Member::currentUser(); - * return $member->inGroup('Editors'); - * } - * - * // ... + * static $api_access = true; + * + * function canView($member = false) { + * if(!$member) $member = Member::currentUser(); + * return $member->inGroup('Subscribers'); + * } + * function canEdit($member = false) { + * if(!$member) $member = Member::currentUser(); + * return $member->inGroup('Editors'); + * } + * + * // ... * } * * @@ -813,7 +813,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * @param $priority String left|right Determines who wins in case of a conflict (optional) * @param $includeRelations Boolean Merge any existing relations (optional) * @param $overwriteWithEmpty Boolean Overwrite existing left values with empty right values. - * Only applicable with $priority='right'. (optional) + * Only applicable with $priority='right'. (optional) * @return Boolean */ public function merge($rightObj, $priority = 'right', $includeRelations = true, $overwriteWithEmpty = false) { @@ -992,7 +992,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity if($defaults && !is_array($defaults)) { user_error("Bad '$this->class' defaults given: " . var_export($defaults, true), - E_USER_WARNING); + E_USER_WARNING); $defaults = null; } @@ -1238,8 +1238,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity . " Make sure that you call parent::onBeforeDelete().", E_USER_ERROR); } - // Deleting a record without an ID shouldn't do anything - if(!$this->ID) throw new LogicException("DataObject::delete() called on a DataObject without an ID"); + // Deleting a record without an ID shouldn't do anything + if(!$this->ID) throw new LogicException("DataObject::delete() called on a DataObject without an ID"); // TODO: This is quite ugly. To improve: // - move the details of the delete code in the DataQuery system @@ -1844,8 +1844,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * Used by {@link SearchContext}. * * @param array $_params - * 'fieldClasses': Associative array of field names as keys and FormField classes as values - * 'restrictFields': Numeric array of a field name whitelist + * 'fieldClasses': Associative array of field names as keys and FormField classes as values + * 'restrictFields': Numeric array of a field name whitelist * @return FieldList */ public function scaffoldSearchFields($_params = null) { @@ -1936,14 +1936,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * or extended onto it by using {@link DataExtension->updateCMSFields()}. * * - * klass MyCustomClass extends DataObject { - * static $db = array('CustomProperty'=>'Boolean'); + * class MyCustomClass extends DataObject { + * static $db = array('CustomProperty'=>'Boolean'); * - * function getCMSFields() { - * $fields = parent::getCMSFields(); - * $fields->addFieldToTab('Root.Content',new CheckboxField('CustomProperty')); - * return $fields; - * } + * function getCMSFields() { + * $fields = parent::getCMSFields(); + * $fields->addFieldToTab('Root.Content',new CheckboxField('CustomProperty')); + * return $fields; + * } * } * * @@ -3227,8 +3227,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity // var_dump("{$ancestorClass}.{$type}_{$name}"); $autoLabels[$name] = _t("{$ancestorClass}.{$type}_{$name}",FormField::name_to_label($name)); } - } - } + } + } $labels = array_merge((array)$autoLabels, (array)$customLabels); $this->extend('updateFieldLabels', $labels); @@ -3366,7 +3366,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * * * array( - * 'MySQLDatabase' => 'ENGINE=MyISAM' + * 'MySQLDatabase' => 'ENGINE=MyISAM' * ) * * @@ -3409,8 +3409,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * * Example: * array( - * array('Title' => "DefaultPage1", 'PageTitle' => 'page1'), - * array('Title' => "DefaultPage2") + * array('Title' => "DefaultPage1", 'PageTitle' => 'page1'), + * array('Title' => "DefaultPage2") * ). * * @var array @@ -3466,7 +3466,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * Example code: * * public static $many_many_extraFields = array( - * 'Members' => array( + * 'Members' => array( * 'Role' => 'Varchar(100)' * ) * ); @@ -3496,8 +3496,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * * Overriding the default filter, with a custom defined filter: * - * static $searchable_fields = array( - * "Name" => "PartialMatchFilter" + * static $searchable_fields = array( + * "Name" => "PartialMatchFilter" * ); * * @@ -3505,21 +3505,21 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * The 'filter' parameter will be generated from {@link DBField::$default_search_filter_class}. * The 'title' parameter will be generated from {@link DataObject->fieldLabels()}. * - * static $searchable_fields = array( - * "Name" => array( - * "field" => "TextField" - * ) + * static $searchable_fields = array( + * "Name" => array( + * "field" => "TextField" + * ) * ); * * * Overriding the default form field, filter and title: * - * static $searchable_fields = array( - * "Organisation.ZipCode" => array( - * "field" => "TextField", - * "filter" => "PartialMatchFilter", - * "title" => 'Organisation ZIP' - * ) + * static $searchable_fields = array( + * "Organisation.ZipCode" => array( + * "field" => "TextField", + * "filter" => "PartialMatchFilter", + * "title" => 'Organisation ZIP' + * ) * ); * */ @@ -3571,21 +3571,21 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity } /** - * Returns true if the given method/parameter has a value - * (Uses the DBField::hasValue if the parameter is a database field) - * + * Returns true if the given method/parameter has a value + * (Uses the DBField::hasValue if the parameter is a database field) + * * @param string $field The field name * @param array $arguments * @param bool $cache - * @return boolean - */ - public function hasValue($field, $arguments = null, $cache = true) { - $obj = $this->dbObject($field); - if($obj) { - return $obj->exists(); - } else { - return parent::hasValue($field, $arguments, $cache); - } - } + * @return boolean + */ + public function hasValue($field, $arguments = null, $cache = true) { + $obj = $this->dbObject($field); + if($obj) { + return $obj->exists(); + } else { + return parent::hasValue($field, $arguments, $cache); + } + } } diff --git a/model/DataQuery.php b/model/DataQuery.php index e1aade7f5..61c738a3d 100644 --- a/model/DataQuery.php +++ b/model/DataQuery.php @@ -334,8 +334,8 @@ class DataQuery { * * @param String $field Unquoted database column name (will be escaped automatically) */ -public function max($field) { - return $this->aggregate(sprintf('MAX("%s")', Convert::raw2sql($field))); + public function max($field) { + return $this->aggregate(sprintf('MAX("%s")', Convert::raw2sql($field))); } /** @@ -344,7 +344,7 @@ public function max($field) { * @param String $field Unquoted database column name (will be escaped automatically) */ public function min($field) { - return $this->aggregate(sprintf('MIN("%s")', Convert::raw2sql($field))); + return $this->aggregate(sprintf('MIN("%s")', Convert::raw2sql($field))); } /** @@ -353,7 +353,7 @@ public function max($field) { * @param String $field Unquoted database column name (will be escaped automatically) */ public function avg($field) { - return $this->aggregate(sprintf('AVG("%s")', Convert::raw2sql($field))); + return $this->aggregate(sprintf('AVG("%s")', Convert::raw2sql($field))); } /** @@ -362,14 +362,14 @@ public function max($field) { * @param String $field Unquoted database column name (will be escaped automatically) */ public function sum($field) { - return $this->aggregate(sprintf('SUM("%s")', Convert::raw2sql($field))); + return $this->aggregate(sprintf('SUM("%s")', Convert::raw2sql($field))); } /** * Runs a raw aggregate expression. Please handle escaping yourself */ public function aggregate($expression) { - return $this->getFinalisedQuery()->aggregate($expression)->execute()->value(); + return $this->getFinalisedQuery()->aggregate($expression)->execute()->value(); } /** @@ -582,74 +582,74 @@ public function max($field) { * @return The model class of the related item */ public function applyRelation($relation) { - // NO-OP - if(!$relation) return $this->dataClass; - - if(is_string($relation)) $relation = explode(".", $relation); - - $modelClass = $this->dataClass; - - foreach($relation as $rel) { - $model = singleton($modelClass); - if ($component = $model->has_one($rel)) { - if(!$this->query->isJoinedTo($component)) { - $foreignKey = $model->getReverseAssociation($component); - $this->query->addLeftJoin($component, - "\"$component\".\"ID\" = \"{$modelClass}\".\"{$foreignKey}ID\""); + // NO-OP + if(!$relation) return $this->dataClass; + + if(is_string($relation)) $relation = explode(".", $relation); + + $modelClass = $this->dataClass; + + foreach($relation as $rel) { + $model = singleton($modelClass); + if ($component = $model->has_one($rel)) { + if(!$this->query->isJoinedTo($component)) { + $foreignKey = $model->getReverseAssociation($component); + $this->query->addLeftJoin($component, + "\"$component\".\"ID\" = \"{$modelClass}\".\"{$foreignKey}ID\""); - /** - * add join clause to the component's ancestry classes so that the search filter could search on - * its ancestor fields. - */ - $ancestry = ClassInfo::ancestry($component, true); - if(!empty($ancestry)){ - $ancestry = array_reverse($ancestry); - foreach($ancestry as $ancestor){ - if($ancestor != $component){ - $this->query->addInnerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\""); - } - } - } - } - $modelClass = $component; + /** + * add join clause to the component's ancestry classes so that the search filter could search on + * its ancestor fields. + */ + $ancestry = ClassInfo::ancestry($component, true); + if(!empty($ancestry)){ + $ancestry = array_reverse($ancestry); + foreach($ancestry as $ancestor){ + if($ancestor != $component){ + $this->query->addInnerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\""); + } + } + } + } + $modelClass = $component; - } elseif ($component = $model->has_many($rel)) { - if(!$this->query->isJoinedTo($component)) { - $ancestry = $model->getClassAncestry(); - $foreignKey = $model->getRemoteJoinField($rel); - $this->query->addLeftJoin($component, - "\"$component\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\""); - /** - * add join clause to the component's ancestry classes so that the search filter could search on - * its ancestor fields. - */ - $ancestry = ClassInfo::ancestry($component, true); - if(!empty($ancestry)){ - $ancestry = array_reverse($ancestry); - foreach($ancestry as $ancestor){ - if($ancestor != $component){ - $this->query->addInnerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\""); - } - } - } - } - $modelClass = $component; + } elseif ($component = $model->has_many($rel)) { + if(!$this->query->isJoinedTo($component)) { + $ancestry = $model->getClassAncestry(); + $foreignKey = $model->getRemoteJoinField($rel); + $this->query->addLeftJoin($component, + "\"$component\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\""); + /** + * add join clause to the component's ancestry classes so that the search filter could search on + * its ancestor fields. + */ + $ancestry = ClassInfo::ancestry($component, true); + if(!empty($ancestry)){ + $ancestry = array_reverse($ancestry); + foreach($ancestry as $ancestor){ + if($ancestor != $component){ + $this->query->addInnerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\""); + } + } + } + } + $modelClass = $component; - } elseif ($component = $model->many_many($rel)) { - list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component; - $parentBaseClass = ClassInfo::baseDataClass($parentClass); - $componentBaseClass = ClassInfo::baseDataClass($componentClass); - $this->query->addInnerJoin($relationTable, - "\"$relationTable\".\"$parentField\" = \"$parentBaseClass\".\"ID\""); - $this->query->addLeftJoin($componentBaseClass, - "\"$relationTable\".\"$componentField\" = \"$componentBaseClass\".\"ID\""); - if(ClassInfo::hasTable($componentClass)) { - $this->query->addLeftJoin($componentClass, - "\"$relationTable\".\"$componentField\" = \"$componentClass\".\"ID\""); - } - $modelClass = $componentClass; + } elseif ($component = $model->many_many($rel)) { + list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component; + $parentBaseClass = ClassInfo::baseDataClass($parentClass); + $componentBaseClass = ClassInfo::baseDataClass($componentClass); + $this->query->addInnerJoin($relationTable, + "\"$relationTable\".\"$parentField\" = \"$parentBaseClass\".\"ID\""); + $this->query->addLeftJoin($componentBaseClass, + "\"$relationTable\".\"$componentField\" = \"$componentBaseClass\".\"ID\""); + if(ClassInfo::hasTable($componentClass)) { + $this->query->addLeftJoin($componentClass, + "\"$relationTable\".\"$componentField\" = \"$componentClass\".\"ID\""); + } + $modelClass = $componentClass; - } + } } return $modelClass; diff --git a/model/Database.php b/model/Database.php index 788c751c9..30c36e49d 100644 --- a/model/Database.php +++ b/model/Database.php @@ -422,7 +422,7 @@ abstract class SS_Database { //Indexes specified as arrays cannot be checked with this line: (it flattens out the array) if(!is_array($spec)) { $spec = preg_replace('/\s*,\s*/', ',', $spec); - } + } if(!isset($this->tableList[strtolower($table)])) $newTable = true; @@ -1136,7 +1136,7 @@ abstract class SS_Query implements Iterator { $result .= ""; foreach($record as $k => $v) { $result .= "" . Convert::raw2xml($k) . " "; - } + } $result .= " \n"; } @@ -1213,7 +1213,7 @@ abstract class SS_Query implements Iterator { */ public function valid() { if(!$this->queryHasBegun) $this->next(); - return $this->currentRecord !== false; + return $this->currentRecord !== false; } /** diff --git a/model/DatabaseAdmin.php b/model/DatabaseAdmin.php index a62bb94d0..090b77ad3 100644 --- a/model/DatabaseAdmin.php +++ b/model/DatabaseAdmin.php @@ -308,8 +308,8 @@ class DatabaseAdmin extends Controller { foreach($subclasses as $subclass) { $id = $record['ID']; if(($record['ClassName'] != $subclass) && - (!is_subclass_of($record['ClassName'], $subclass)) && - (isset($recordExists[$subclass][$id]))) { + (!is_subclass_of($record['ClassName'], $subclass)) && + (isset($recordExists[$subclass][$id]))) { $sql = "DELETE FROM \"$subclass\" WHERE \"ID\" = $record[ID]"; echo "
  • $sql"; DB::query($sql); diff --git a/model/HasManyList.php b/model/HasManyList.php index 290f8955a..c39efceb4 100644 --- a/model/HasManyList.php +++ b/model/HasManyList.php @@ -66,10 +66,10 @@ class HasManyList extends RelationList { * @param $itemID The ID of the item to be removed */ public function removeByID($itemID) { - $item = $this->byID($itemID); - return $this->remove($item); - } - + $item = $this->byID($itemID); + return $this->remove($item); + } + /** * Remove an item from this relation. * Doesn't actually remove the item, it just clears the foreign key value. @@ -77,10 +77,10 @@ class HasManyList extends RelationList { * @todo Maybe we should delete the object instead? */ public function remove($item) { - if(!($item instanceof $this->dataClass)) { - throw new InvalidArgumentException("HasManyList::remove() expecting a $this->dataClass object, or ID", - E_USER_ERROR); - } + if(!($item instanceof $this->dataClass)) { + throw new InvalidArgumentException("HasManyList::remove() expecting a $this->dataClass object, or ID", + E_USER_ERROR); + } $fk = $this->foreignKey; $item->$fk = null; diff --git a/model/Hierarchy.php b/model/Hierarchy.php index cd9d990f7..4fa8f98d9 100644 --- a/model/Hierarchy.php +++ b/model/Hierarchy.php @@ -434,14 +434,14 @@ class Hierarchy extends DataExtension { public function Children() { if(!(isset($this->_cache_children) && $this->_cache_children)) { $result = $this->owner->stageChildren(false); - if(isset($result)) { - $this->_cache_children = new ArrayList(); - foreach($result as $child) { - if($child->canView()) { - $this->_cache_children->push($child); - } - } - } + if(isset($result)) { + $this->_cache_children = new ArrayList(); + foreach($result as $child) { + if($child->canView()) { + $this->_cache_children->push($child); + } + } + } } return $this->_cache_children; } @@ -487,10 +487,10 @@ class Hierarchy extends DataExtension { // Next, go through the live children. Only some of these will be listed $liveChildren = $this->owner->liveChildren(true, true); if($liveChildren) { - $merged = new ArrayList(); - $merged->merge($stageChildren); - $merged->merge($liveChildren); - $stageChildren = $merged; + $merged = new ArrayList(); + $merged->merge($stageChildren); + $merged->merge($liveChildren); + $stageChildren = $merged; } } @@ -526,7 +526,7 @@ class Hierarchy extends DataExtension { throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied'); } - return Versioned::get_including_deleted(ClassInfo::baseDataClass($this->owner->class), + return Versioned::get_including_deleted(ClassInfo::baseDataClass($this->owner->class), "\"ParentID\" = " . (int)$this->owner->ID)->count(); } diff --git a/model/ManyManyList.php b/model/ManyManyList.php index e3e1a1b27..1cfb5069c 100644 --- a/model/ManyManyList.php +++ b/model/ManyManyList.php @@ -117,11 +117,11 @@ class ManyManyList extends RelationList { * @param $itemID The ID of the item to remove. */ public function remove($item) { - if(!($item instanceof $this->dataClass)) { - throw new InvalidArgumentException("ManyManyList::remove() expecting a $this->dataClass object"); - } - - return $this->removeByID($item->ID); + if(!($item instanceof $this->dataClass)) { + throw new InvalidArgumentException("ManyManyList::remove() expecting a $this->dataClass object"); + } + + return $this->removeByID($item->ID); } /** @@ -130,7 +130,7 @@ class ManyManyList extends RelationList { * @param $itemID The item it */ public function removeByID($itemID) { - if(!is_numeric($itemID)) throw new InvalidArgumentException("ManyManyList::removeById() expecting an ID"); + if(!is_numeric($itemID)) throw new InvalidArgumentException("ManyManyList::removeById() expecting an ID"); $query = new SQLQuery("*", array("\"$this->joinTable\"")); $query->setDelete(true); @@ -145,16 +145,16 @@ class ManyManyList extends RelationList { $query->execute(); } - /** - * Remove all items from this many-many join. To remove a subset of items, filter it first. - */ - public function removeAll() { + /** + * Remove all items from this many-many join. To remove a subset of items, filter it first. + */ + public function removeAll() { $query = $this->dataQuery()->query(); $query->setDelete(true); $query->setSelect(array('*')); $query->setFrom("\"$this->joinTable\""); $query->execute(); - } + } /** * Find the extra field data for a single row of the relationship diff --git a/model/MySQLDatabase.php b/model/MySQLDatabase.php index a0881104f..3ab1846e0 100644 --- a/model/MySQLDatabase.php +++ b/model/MySQLDatabase.php @@ -269,8 +269,8 @@ class MySQLDatabase extends SS_Database { if($alteredIndexes) foreach($alteredIndexes as $k => $v) { $alterList[] .= "DROP INDEX \"$k\""; $alterList[] .= "ADD ". $this->getIndexSqlDefinition($k, $v); - } - + } + if($alteredOptions && isset($alteredOptions[get_class($this)])) { if(!isset($this->indexList[$tableName])) { $this->indexList[$tableName] = $this->indexList($tableName); @@ -298,7 +298,7 @@ class MySQLDatabase extends SS_Database { } } - $alterations = implode(",\n", $alterList); + $alterations = implode(",\n", $alterList); $this->query("ALTER TABLE \"$tableName\" $alterations"); } @@ -467,9 +467,9 @@ class MySQLDatabase extends SS_Database { $indexSpec = trim($indexSpec); if($indexSpec[0] != '(') list($indexType, $indexFields) = explode(' ',$indexSpec,2); - else $indexFields = $indexSpec; + else $indexFields = $indexSpec; - if(!isset($indexType)) + if(!isset($indexType)) $indexType = "index"; if($indexType=='using') @@ -499,15 +499,15 @@ class MySQLDatabase extends SS_Database { $indexSpec=$this->convertIndexSpec($indexSpec); $indexSpec = trim($indexSpec); - if($indexSpec[0] != '(') { - list($indexType, $indexFields) = explode(' ',$indexSpec,2); - } else { - $indexFields = $indexSpec; - } + if($indexSpec[0] != '(') { + list($indexType, $indexFields) = explode(' ',$indexSpec,2); + } else { + $indexFields = $indexSpec; + } - if(!$indexType) { - $indexType = "index"; - } + if(!$indexType) { + $indexType = "index"; + } $this->query("ALTER TABLE \"$tableName\" DROP INDEX \"$indexName\""); $this->query("ALTER TABLE \"$tableName\" ADD $indexType \"$indexName\" $indexFields"); @@ -822,19 +822,19 @@ class MySQLDatabase extends SS_Database { if(!class_exists('File')) throw new Exception('MySQLDatabase->searchEngine() requires "File" class'); $fileFilter = ''; - $keywords = Convert::raw2sql($keywords); + $keywords = Convert::raw2sql($keywords); $htmlEntityKeywords = htmlentities($keywords, ENT_NOQUOTES, 'UTF-8'); $extraFilters = array('SiteTree' => '', 'File' => ''); - if($booleanSearch) $boolean = "IN BOOLEAN MODE"; + if($booleanSearch) $boolean = "IN BOOLEAN MODE"; - if($extraFilter) { - $extraFilters['SiteTree'] = " AND $extraFilter"; + if($extraFilter) { + $extraFilters['SiteTree'] = " AND $extraFilter"; - if($alternativeFileFilter) $extraFilters['File'] = " AND $alternativeFileFilter"; - else $extraFilters['File'] = $extraFilters['SiteTree']; - } + if($alternativeFileFilter) $extraFilters['File'] = " AND $alternativeFileFilter"; + else $extraFilters['File'] = $extraFilters['SiteTree']; + } // Always ensure that only pages with ShowInSearch = 1 can be searched $extraFilters['SiteTree'] .= " AND ShowInSearch <> 0"; @@ -984,7 +984,7 @@ class MySQLDatabase extends SS_Database { $boolean = $booleanSearch ? "IN BOOLEAN MODE" : ""; $fieldNames = '"' . implode('", "', $fields) . '"'; - $SQL_keywords = Convert::raw2sql($keywords); + $SQL_keywords = Convert::raw2sql($keywords); $SQL_htmlEntityKeywords = Convert::raw2sql(htmlentities($keywords, ENT_NOQUOTES, 'UTF-8')); return "(MATCH ($fieldNames) AGAINST ('$SQL_keywords' $boolean) + MATCH ($fieldNames)" @@ -1131,7 +1131,7 @@ class MySQLDatabase extends SS_Database { * @param string $date2 to be substracted of $date1, can be either 'now', literal datetime like * '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"' * @return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which - * is the result of the substraction + * is the result of the substraction */ public function datetimeDifferenceClause($date1, $date2) { diff --git a/model/SQLQuery.php b/model/SQLQuery.php index 020473101..33e81f43d 100644 --- a/model/SQLQuery.php +++ b/model/SQLQuery.php @@ -915,11 +915,11 @@ class SQLQuery { * @return string */ public function __toString() { - try { - return $this->sql(); - } catch(Exception $e) { - return ""; - } + try { + return $this->sql(); + } catch(Exception $e) { + return ""; + } } /** diff --git a/model/Transliterator.php b/model/Transliterator.php index f8753bc3e..4a8a5018f 100644 --- a/model/Transliterator.php +++ b/model/Transliterator.php @@ -53,6 +53,6 @@ class SS_Transliterator extends Object { * Transliteration using iconv() */ protected function useIconv($source) { - return iconv("utf-8", "us-ascii//IGNORE//TRANSLIT", $source); + return iconv("utf-8", "us-ascii//IGNORE//TRANSLIT", $source); } } diff --git a/model/Versioned.php b/model/Versioned.php index 11409f209..8148f0956 100644 --- a/model/Versioned.php +++ b/model/Versioned.php @@ -136,8 +136,8 @@ class Versioned extends DataExtension { * @todo Should this all go into VersionedDataQuery? */ public function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null) { - $baseTable = ClassInfo::baseDataClass($dataQuery->dataClass()); - + $baseTable = ClassInfo::baseDataClass($dataQuery->dataClass()); + switch($dataQuery->getQueryParam('Versioned.mode')) { // Noop case '': diff --git a/model/fieldtypes/Date.php b/model/fieldtypes/Date.php index 5e3851cd4..f789dd82c 100644 --- a/model/fieldtypes/Date.php +++ b/model/fieldtypes/Date.php @@ -330,23 +330,23 @@ class Date extends DBField { public function days_between($fyear, $fmonth, $fday, $tyear, $tmonth, $tday){ - return abs((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, $tmonth, $tday, $tyear))/(60*60*24)); + return abs((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, $tmonth, $tday, $tyear))/(60*60*24)); } public function day_before($fyear, $fmonth, $fday){ - return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-1,$fyear)); + return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-1,$fyear)); } public function next_day($fyear, $fmonth, $fday){ - return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday+1,$fyear)); + return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday+1,$fyear)); } public function weekday($fyear, $fmonth, $fday){ // 0 is a Monday - return (((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, 7, 17, 2006))/(60*60*24))+700000) % 7; + return (((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, 7, 17, 2006))/(60*60*24))+700000) % 7; } public function prior_monday($fyear, $fmonth, $fday){ - return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-$this->weekday($fyear, $fmonth, $fday),$fyear)); + return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-$this->weekday($fyear, $fmonth, $fday),$fyear)); } /** diff --git a/model/fieldtypes/Text.php b/model/fieldtypes/Text.php index 392bafa02..771d8536a 100644 --- a/model/fieldtypes/Text.php +++ b/model/fieldtypes/Text.php @@ -33,7 +33,7 @@ class Text extends StringField { 'NoHTML' => 'Text', ); - /** + /** * (non-PHPdoc) * @see DBField::requireField() */ diff --git a/model/fieldtypes/Varchar.php b/model/fieldtypes/Varchar.php index 6cf02aec8..98f8eba0a 100644 --- a/model/fieldtypes/Varchar.php +++ b/model/fieldtypes/Varchar.php @@ -17,8 +17,8 @@ class Varchar extends StringField { ); protected $size; - - /** + + /** * Construct a new short text field * * @param $name string The name of the field @@ -27,12 +27,12 @@ class Varchar extends StringField { * See {@link StringField::setOptions()} for information on the available options * @return unknown_type */ - public function __construct($name = null, $size = 50, $options = array()) { + public function __construct($name = null, $size = 50, $options = array()) { $this->size = $size ? $size : 50; parent::__construct($name, $options); } - /** + /** * (non-PHPdoc) * @see DBField::requireField() */ diff --git a/parsers/BBCodeParser.php b/parsers/BBCodeParser.php index e3352c3ef..72fce426c 100644 --- a/parsers/BBCodeParser.php +++ b/parsers/BBCodeParser.php @@ -27,7 +27,7 @@ class BBCodeParser extends TextParser { * @var Boolean */ protected static $allowSimilies = false; - + /** * Set the location of the smiles folder. By default use the ones in framework * but this can be overridden by setting BBCodeParser::set_icon_folder('themes/yourtheme/images/'); @@ -165,7 +165,7 @@ class BBCodeParser extends TextParser { '#(? " ", // :( '#(? " ", // :-( '#(? " ", // :p - '#(? " ", // 8-) + '#(? " ", // 8-) '#(? " " // :^) ); $this->content = preg_replace(array_keys($smilies), array_values($smilies), $this->content); diff --git a/parsers/HTML/HTMLBBCodeParser.php b/parsers/HTML/HTMLBBCodeParser.php index b09ce64e2..e9cfdacf6 100644 --- a/parsers/HTML/HTMLBBCodeParser.php +++ b/parsers/HTML/HTMLBBCodeParser.php @@ -55,819 +55,818 @@ */ class SSHTMLBBCodeParser { - /** - * An array of tags parsed by the engine, should be overwritten by filters - * - * @access private - * @var array - */ - var $_definedTags = array(); + /** + * An array of tags parsed by the engine, should be overwritten by filters + * + * @access private + * @var array + */ + var $_definedTags = array(); - /** - * A string containing the input - * - * @access private - * @var string - */ - var $_text = ''; + /** + * A string containing the input + * + * @access private + * @var string + */ + var $_text = ''; - /** - * A string containing the preparsed input - * - * @access private - * @var string - */ - var $_preparsed = ''; + /** + * A string containing the preparsed input + * + * @access private + * @var string + */ + var $_preparsed = ''; - /** - * An array tags and texts build from the input text - * - * @access private - * @var array - */ - var $_tagArray = array(); + /** + * An array tags and texts build from the input text + * + * @access private + * @var array + */ + var $_tagArray = array(); - /** - * A string containing the parsed version of the text - * - * @access private - * @var string - */ - var $_parsed = ''; + /** + * A string containing the parsed version of the text + * + * @access private + * @var string + */ + var $_parsed = ''; - /** - * An array of options, filled by an ini file or through the contructor - * - * @access private - * @var array - */ - var $_options = array( - 'quotestyle' => 'double', - 'quotewhat' => 'all', - 'open' => '[', - 'close' => ']', - 'xmlclose' => true, - 'filters' => 'Basic' - ); + /** + * An array of options, filled by an ini file or through the contructor + * + * @access private + * @var array + */ + var $_options = array( + 'quotestyle' => 'double', + 'quotewhat' => 'all', + 'open' => '[', + 'close' => ']', + 'xmlclose' => true, + 'filters' => 'Basic' + ); - /** - * An array of filters used for parsing - * - * @access private - * @var array - */ - var $_filters = array(); + /** + * An array of filters used for parsing + * + * @access private + * @var array + */ + var $_filters = array(); - /** - * Constructor, initialises the options and filters - * - * Sets the private variable _options with base options defined with - * &PEAR::getStaticProperty(), overwriting them with (if present) - * the argument to this method. - * Then it sets the extra options to properly escape the tag - * characters in preg_replace() etc. The set options are - * then stored back with &PEAR::getStaticProperty(), so that the filter - * classes can use them. - * All the filters in the options are initialised and their defined tags - * are copied into the private variable _definedTags. - * - * @param array options to use, can be left out - * @return none - * @access public - * @author Stijn de Reede - */ - public function SSHTMLBBCodeParser($options = array()) - { - // set the already set options - $baseoptions = &SSHTMLBBCodeParser::getStaticProperty('SSHTMLBBCodeParser', '_options'); - if (is_array($baseoptions)) { - foreach ($baseoptions as $k => $v) { - $this->_options[$k] = $v; - } - } + /** + * Constructor, initialises the options and filters + * + * Sets the private variable _options with base options defined with + * &PEAR::getStaticProperty(), overwriting them with (if present) + * the argument to this method. + * Then it sets the extra options to properly escape the tag + * characters in preg_replace() etc. The set options are + * then stored back with &PEAR::getStaticProperty(), so that the filter + * classes can use them. + * All the filters in the options are initialised and their defined tags + * are copied into the private variable _definedTags. + * + * @param array options to use, can be left out + * @return none + * @access public + * @author Stijn de Reede + */ + public function SSHTMLBBCodeParser($options = array()) + { + // set the already set options + $baseoptions = &SSHTMLBBCodeParser::getStaticProperty('SSHTMLBBCodeParser', '_options'); + if (is_array($baseoptions)) { + foreach ($baseoptions as $k => $v) { + $this->_options[$k] = $v; + } + } - // set the options passed as an argument - foreach ($options as $k => $v ) { - $this->_options[$k] = $v; - } + // set the options passed as an argument + foreach ($options as $k => $v ) { + $this->_options[$k] = $v; + } - // add escape open and close chars to the options for preg escaping - $preg_escape = '\^$.[]|()?*+{}'; - if ($this->_options['open'] != '' && strpos($preg_escape, $this->_options['open'])) { - $this->_options['open_esc'] = "\\".$this->_options['open']; - } else { - $this->_options['open_esc'] = $this->_options['open']; - } - if ($this->_options['close'] != '' && strpos($preg_escape, $this->_options['close'])) { - $this->_options['close_esc'] = "\\".$this->_options['close']; - } else { - $this->_options['close_esc'] = $this->_options['close']; - } + // add escape open and close chars to the options for preg escaping + $preg_escape = '\^$.[]|()?*+{}'; + if ($this->_options['open'] != '' && strpos($preg_escape, $this->_options['open'])) { + $this->_options['open_esc'] = "\\".$this->_options['open']; + } else { + $this->_options['open_esc'] = $this->_options['open']; + } + if ($this->_options['close'] != '' && strpos($preg_escape, $this->_options['close'])) { + $this->_options['close_esc'] = "\\".$this->_options['close']; + } else { + $this->_options['close_esc'] = $this->_options['close']; + } - // set the options back so that child classes can use them */ - $baseoptions = $this->_options; - unset($baseoptions); + // set the options back so that child classes can use them */ + $baseoptions = $this->_options; + unset($baseoptions); - // return if this is a subclass - if (is_subclass_of($this, 'SSHTMLBBCodeParser_Filter')) { - return; - } + // return if this is a subclass + if (is_subclass_of($this, 'SSHTMLBBCodeParser_Filter')) { + return; + } - // extract the definedTags from subclasses */ - $this->addFilters($this->_options['filters']); - } - - static function &getStaticProperty($class, $var) - { - static $properties; - if (!isset($properties[$class])) { - $properties[$class] = array(); - } - if (!array_key_exists($var, $properties[$class])) { - $properties[$class][$var] = null; - } - return $properties[$class][$var]; - } + // extract the definedTags from subclasses */ + $this->addFilters($this->_options['filters']); + } + + static function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + return $properties[$class][$var]; + } - /** - * Option setter - * - * @param string option name - * @param mixed option value - * @author Lorenzo Alberton - */ - public function setOption($name, $value) - { - $this->_options[$name] = $value; - } + /** + * Option setter + * + * @param string option name + * @param mixed option value + * @author Lorenzo Alberton + */ + public function setOption($name, $value) + { + $this->_options[$name] = $value; + } - /** - * Add a new filter - * - * @param string filter - * @author Lorenzo Alberton - */ - public function addFilter($filter) - { - - $filter = ucfirst($filter); - if (!array_key_exists($filter, $this->_filters)) { - $class = 'SSHTMLBBCodeParser_Filter_'.$filter; - if (fopen('BBCodeParser/Filter/'.$filter.'.php','r',true)) { - include_once 'BBCodeParser/Filter/'.$filter.'.php'; - } - if (!class_exists($class)) { + /** + * Add a new filter + * + * @param string filter + * @author Lorenzo Alberton + */ + public function addFilter($filter) + { + $filter = ucfirst($filter); + if (!array_key_exists($filter, $this->_filters)) { + $class = 'SSHTMLBBCodeParser_Filter_'.$filter; + if (fopen('BBCodeParser/Filter/'.$filter.'.php','r',true)) { + include_once 'BBCodeParser/Filter/'.$filter.'.php'; + } + if (!class_exists($class)) { - //PEAR::raiseError("Failed to load filter $filter", null, PEAR_ERROR_DIE); - } + //PEAR::raiseError("Failed to load filter $filter", null, PEAR_ERROR_DIE); + } else { $this->_filters[$filter] = new $class; - $this->_definedTags = array_merge( - $this->_definedTags, - $this->_filters[$filter]->_definedTags - ); + $this->_definedTags = array_merge( + $this->_definedTags, + $this->_filters[$filter]->_definedTags + ); } - } + } - } + } - /** - * Remove an existing filter - * - * @param string $filter - * @author Lorenzo Alberton - */ - public function removeFilter($filter) - { - $filter = ucfirst(trim($filter)); - if (!empty($filter) && array_key_exists($filter, $this->_filters)) { - unset($this->_filters[$filter]); - } - // also remove the related $this->_definedTags for this filter, - // preserving the others - $this->_definedTags = array(); - foreach (array_keys($this->_filters) as $filter) { - $this->_definedTags = array_merge( - $this->_definedTags, - $this->_filters[$filter]->_definedTags - ); - } - } + /** + * Remove an existing filter + * + * @param string $filter + * @author Lorenzo Alberton + */ + public function removeFilter($filter) + { + $filter = ucfirst(trim($filter)); + if (!empty($filter) && array_key_exists($filter, $this->_filters)) { + unset($this->_filters[$filter]); + } + // also remove the related $this->_definedTags for this filter, + // preserving the others + $this->_definedTags = array(); + foreach (array_keys($this->_filters) as $filter) { + $this->_definedTags = array_merge( + $this->_definedTags, + $this->_filters[$filter]->_definedTags + ); + } + } - /** - * Add new filters - * - * @param mixed (array or string) - * @return boolean true if all ok, false if not. - * @author Lorenzo Alberton - */ - public function addFilters($filters) - { - if (is_string($filters)) { - //comma-separated list - if (strpos($filters, ',') !== false) { - $filters = explode(',', $filters); - } else { - $filters = array($filters); - } - } - if (!is_array($filters)) { - //invalid format - return false; - } - foreach ($filters as $filter) { - if (trim($filter)){ - $this->addFilter($filter); - } - } - return true; - } + /** + * Add new filters + * + * @param mixed (array or string) + * @return boolean true if all ok, false if not. + * @author Lorenzo Alberton + */ + public function addFilters($filters) + { + if (is_string($filters)) { + //comma-separated list + if (strpos($filters, ',') !== false) { + $filters = explode(',', $filters); + } else { + $filters = array($filters); + } + } + if (!is_array($filters)) { + //invalid format + return false; + } + foreach ($filters as $filter) { + if (trim($filter)){ + $this->addFilter($filter); + } + } + return true; + } - /** - * Executes statements before the actual array building starts - * - * This method should be overwritten in a filter if you want to do - * something before the parsing process starts. This can be useful to - * allow certain short alternative tags which then can be converted into - * proper tags with preg_replace() calls. - * The main class walks through all the filters and and calls this - * method. The filters should modify their private $_preparsed - * variable, with input from $_text. - * - * @return none - * @access private - * @see $_text - * @author Stijn de Reede - */ - public function _preparse() - { - // default: assign _text to _preparsed, to be overwritten by filters - $this->_preparsed = $this->_text; + /** + * Executes statements before the actual array building starts + * + * This method should be overwritten in a filter if you want to do + * something before the parsing process starts. This can be useful to + * allow certain short alternative tags which then can be converted into + * proper tags with preg_replace() calls. + * The main class walks through all the filters and and calls this + * method. The filters should modify their private $_preparsed + * variable, with input from $_text. + * + * @return none + * @access private + * @see $_text + * @author Stijn de Reede + */ + public function _preparse() + { + // default: assign _text to _preparsed, to be overwritten by filters + $this->_preparsed = $this->_text; - // return if this is a subclass - if (is_subclass_of($this, 'SSHTMLBBCodeParser')) { - return; - } + // return if this is a subclass + if (is_subclass_of($this, 'SSHTMLBBCodeParser')) { + return; + } - // walk through the filters and execute _preparse - foreach ($this->_filters as $filter) { - $filter->setText($this->_preparsed); - $filter->_preparse(); - $this->_preparsed = $filter->getPreparsed(); - } - } + // walk through the filters and execute _preparse + foreach ($this->_filters as $filter) { + $filter->setText($this->_preparsed); + $filter->_preparse(); + $this->_preparsed = $filter->getPreparsed(); + } + } - /** - * Builds the tag array from the input string $_text - * - * An array consisting of tag and text elements is contructed from the - * $_preparsed variable. The method uses _buildTag() to check if a tag is - * valid and to build the actual tag to be added to the tag array. - * - * @todo - rewrite whole method, as this one is old and probably slow - * - see if a recursive method would be better than an iterative one - * - * @return none - * @access private - * @see _buildTag() - * @see $_text - * @see $_tagArray - * @author Stijn de Reede - */ - public function _buildTagArray() - { - $this->_tagArray = array(); - $str = $this->_preparsed; - $strPos = 0; - $strLength = strlen($str); + /** + * Builds the tag array from the input string $_text + * + * An array consisting of tag and text elements is contructed from the + * $_preparsed variable. The method uses _buildTag() to check if a tag is + * valid and to build the actual tag to be added to the tag array. + * + * @todo - rewrite whole method, as this one is old and probably slow + * - see if a recursive method would be better than an iterative one + * + * @return none + * @access private + * @see _buildTag() + * @see $_text + * @see $_tagArray + * @author Stijn de Reede + */ + public function _buildTagArray() + { + $this->_tagArray = array(); + $str = $this->_preparsed; + $strPos = 0; + $strLength = strlen($str); - while (($strPos < $strLength)) { - $tag = array(); - $openPos = strpos($str, $this->_options['open'], $strPos); - if ($openPos === false) { - $openPos = $strLength; - $nextOpenPos = $strLength; - } - if ($openPos + 1 > $strLength) { - $nextOpenPos = $strLength; - } else { - $nextOpenPos = strpos($str, $this->_options['open'], $openPos + 1); - if ($nextOpenPos === false) { - $nextOpenPos = $strLength; - } - } - $closePos = strpos($str, $this->_options['close'], $strPos); - if ($closePos === false) { - $closePos = $strLength + 1; - } + while (($strPos < $strLength)) { + $tag = array(); + $openPos = strpos($str, $this->_options['open'], $strPos); + if ($openPos === false) { + $openPos = $strLength; + $nextOpenPos = $strLength; + } + if ($openPos + 1 > $strLength) { + $nextOpenPos = $strLength; + } else { + $nextOpenPos = strpos($str, $this->_options['open'], $openPos + 1); + if ($nextOpenPos === false) { + $nextOpenPos = $strLength; + } + } + $closePos = strpos($str, $this->_options['close'], $strPos); + if ($closePos === false) { + $closePos = $strLength + 1; + } - if ($openPos == $strPos) { - if (($nextOpenPos < $closePos)) { - // new open tag before closing tag: treat as text - $newPos = $nextOpenPos; - $tag['text'] = substr($str, $strPos, $nextOpenPos - $strPos); - $tag['type'] = 0; - } else { - // possible valid tag - $newPos = $closePos + 1; - $newTag = $this->_buildTag(substr($str, $strPos, $closePos - $strPos + 1)); - if (($newTag !== false)) { - $tag = $newTag; - } else { - // no valid tag after all - $tag['text'] = substr($str, $strPos, $closePos - $strPos + 1); - $tag['type'] = 0; - } - } - } else { - // just text - $newPos = $openPos; - $tag['text'] = substr($str, $strPos, $openPos - $strPos); - $tag['type'] = 0; - } + if ($openPos == $strPos) { + if (($nextOpenPos < $closePos)) { + // new open tag before closing tag: treat as text + $newPos = $nextOpenPos; + $tag['text'] = substr($str, $strPos, $nextOpenPos - $strPos); + $tag['type'] = 0; + } else { + // possible valid tag + $newPos = $closePos + 1; + $newTag = $this->_buildTag(substr($str, $strPos, $closePos - $strPos + 1)); + if (($newTag !== false)) { + $tag = $newTag; + } else { + // no valid tag after all + $tag['text'] = substr($str, $strPos, $closePos - $strPos + 1); + $tag['type'] = 0; + } + } + } else { + // just text + $newPos = $openPos; + $tag['text'] = substr($str, $strPos, $openPos - $strPos); + $tag['type'] = 0; + } - // join 2 following text elements - if ($tag['type'] === 0 && isset($prev) && $prev['type'] === 0) { - $tag['text'] = $prev['text'].$tag['text']; - array_pop($this->_tagArray); - } + // join 2 following text elements + if ($tag['type'] === 0 && isset($prev) && $prev['type'] === 0) { + $tag['text'] = $prev['text'].$tag['text']; + array_pop($this->_tagArray); + } - $this->_tagArray[] = $tag; - $prev = $tag; - $strPos = $newPos; - } - } + $this->_tagArray[] = $tag; + $prev = $tag; + $strPos = $newPos; + } + } - /** - * Builds a tag from the input string - * - * This method builds a tag array based on the string it got as an - * argument. If the tag is invalid, is returned. The tag - * attributes are extracted from the string and stored in the tag - * array as an associative array. - * - * @param string string to build tag from - * @return array tag in array format - * @access private - * @see _buildTagArray() - * @author Stijn de Reede - */ - public function _buildTag($str) - { - $tag = array('text' => $str, 'attributes' => array()); + /** + * Builds a tag from the input string + * + * This method builds a tag array based on the string it got as an + * argument. If the tag is invalid, is returned. The tag + * attributes are extracted from the string and stored in the tag + * array as an associative array. + * + * @param string string to build tag from + * @return array tag in array format + * @access private + * @see _buildTagArray() + * @author Stijn de Reede + */ + public function _buildTag($str) + { + $tag = array('text' => $str, 'attributes' => array()); - if (substr($str, 1, 1) == '/') { // closing tag + if (substr($str, 1, 1) == '/') { // closing tag - $tag['tag'] = strtolower(substr($str, 2, strlen($str) - 3)); - if (!in_array($tag['tag'], array_keys($this->_definedTags))) { - return false; // nope, it's not valid - } else { - $tag['type'] = 2; - return $tag; - } - } else { // opening tag + $tag['tag'] = strtolower(substr($str, 2, strlen($str) - 3)); + if (!in_array($tag['tag'], array_keys($this->_definedTags))) { + return false; // nope, it's not valid + } else { + $tag['type'] = 2; + return $tag; + } + } else { // opening tag - $tag['type'] = 1; - if (strpos($str, ' ') && (strpos($str, '=') === false)) { - return false; // nope, it's not valid - } + $tag['type'] = 1; + if (strpos($str, ' ') && (strpos($str, '=') === false)) { + return false; // nope, it's not valid + } - // tnx to Onno for the regex - // split the tag with arguments and all - $oe = $this->_options['open_esc']; - $ce = $this->_options['close_esc']; - $tagArray = array(); - if (preg_match("!$oe([a-z0-9]+)[^$ce]*$ce!i", $str, $tagArray) == 0) { - return false; - } - $tag['tag'] = strtolower($tagArray[1]); - if (!in_array($tag['tag'], array_keys($this->_definedTags))) { - return false; // nope, it's not valid - } + // tnx to Onno for the regex + // split the tag with arguments and all + $oe = $this->_options['open_esc']; + $ce = $this->_options['close_esc']; + $tagArray = array(); + if (preg_match("!$oe([a-z0-9]+)[^$ce]*$ce!i", $str, $tagArray) == 0) { + return false; + } + $tag['tag'] = strtolower($tagArray[1]); + if (!in_array($tag['tag'], array_keys($this->_definedTags))) { + return false; // nope, it's not valid + } - // tnx to Onno for the regex - // validate the arguments - $attributeArray = array(); - $regex = "![\s$oe]([a-z0-9]+)=(\"[^\s$ce]+\"|[^\s$ce]"; - if ($tag['tag'] != 'url') { - $regex .= "[^=]"; - } - $regex .= "+)(?=[\s$ce])!i"; - preg_match_all($regex, $str, $attributeArray, PREG_SET_ORDER); - foreach ($attributeArray as $attribute) { - $attNam = strtolower($attribute[1]); - if (in_array($attNam, array_keys($this->_definedTags[$tag['tag']]['attributes']))) { - if ($attribute[2][0] == '"' && $attribute[2][strlen($attribute[2])-1] == '"') { - $tag['attributes'][$attNam] = substr($attribute[2], 1, -1); - } else { - $tag['attributes'][$attNam] = $attribute[2]; - } - } - } - return $tag; - } - } + // tnx to Onno for the regex + // validate the arguments + $attributeArray = array(); + $regex = "![\s$oe]([a-z0-9]+)=(\"[^\s$ce]+\"|[^\s$ce]"; + if ($tag['tag'] != 'url') { + $regex .= "[^=]"; + } + $regex .= "+)(?=[\s$ce])!i"; + preg_match_all($regex, $str, $attributeArray, PREG_SET_ORDER); + foreach ($attributeArray as $attribute) { + $attNam = strtolower($attribute[1]); + if (in_array($attNam, array_keys($this->_definedTags[$tag['tag']]['attributes']))) { + if ($attribute[2][0] == '"' && $attribute[2][strlen($attribute[2])-1] == '"') { + $tag['attributes'][$attNam] = substr($attribute[2], 1, -1); + } else { + $tag['attributes'][$attNam] = $attribute[2]; + } + } + } + return $tag; + } + } - /** - * Validates the tag array, regarding the allowed tags - * - * While looping through the tag array, two following text tags are - * joined, and it is checked that the tag is allowed inside the - * last opened tag. - * By remembering what tags have been opened it is checked that - * there is correct (xml compliant) nesting. - * In the end all still opened tags are closed. - * - * @return none - * @access private - * @see _isAllowed() - * @see $_tagArray - * @author Stijn de Reede , Seth Price - */ - public function _validateTagArray() - { - $newTagArray = array(); - $openTags = array(); - foreach ($this->_tagArray as $tag) { - $prevTag = end($newTagArray); - switch ($tag['type']) { - case 0: - if (($child = $this->_childNeeded(end($openTags), 'text')) && - $child !== false && - /* - * No idea what to do in this case: A child is needed, but - * no valid one is returned. We'll ignore it here and live - * with it until someone reports a valid bug. - */ - $child !== true ) - { - if (trim($tag['text']) == '') { - //just an empty indentation or newline without value? - continue; - } - $newTagArray[] = $child; - $openTags[] = $child['tag']; - } - if ($prevTag['type'] === 0) { - $tag['text'] = $prevTag['text'].$tag['text']; - array_pop($newTagArray); - } - $newTagArray[] = $tag; - break; + /** + * Validates the tag array, regarding the allowed tags + * + * While looping through the tag array, two following text tags are + * joined, and it is checked that the tag is allowed inside the + * last opened tag. + * By remembering what tags have been opened it is checked that + * there is correct (xml compliant) nesting. + * In the end all still opened tags are closed. + * + * @return none + * @access private + * @see _isAllowed() + * @see $_tagArray + * @author Stijn de Reede , Seth Price + */ + public function _validateTagArray() + { + $newTagArray = array(); + $openTags = array(); + foreach ($this->_tagArray as $tag) { + $prevTag = end($newTagArray); + switch ($tag['type']) { + case 0: + if (($child = $this->_childNeeded(end($openTags), 'text')) && + $child !== false && + /* + * No idea what to do in this case: A child is needed, but + * no valid one is returned. We'll ignore it here and live + * with it until someone reports a valid bug. + */ + $child !== true ) + { + if (trim($tag['text']) == '') { + //just an empty indentation or newline without value? + continue; + } + $newTagArray[] = $child; + $openTags[] = $child['tag']; + } + if ($prevTag['type'] === 0) { + $tag['text'] = $prevTag['text'].$tag['text']; + array_pop($newTagArray); + } + $newTagArray[] = $tag; + break; - case 1: - if (!$this->_isAllowed(end($openTags), $tag['tag']) || - ($parent = $this->_parentNeeded(end($openTags), $tag['tag'])) === true || - ($child = $this->_childNeeded(end($openTags), $tag['tag'])) === true) { - $tag['type'] = 0; - if ($prevTag['type'] === 0) { - $tag['text'] = $prevTag['text'].$tag['text']; - array_pop($newTagArray); - } - } else { - if ($parent) { - /* - * Avoid use of parent if we can help it. If we are - * trying to insert a new parent, but the current tag is - * the same as the previous tag, then assume that the - * previous tag structure is valid, and add this tag as - * a sibling. To add as a sibling, we need to close the - * current tag. - */ - if ($tag['tag'] == end($openTags)){ - $newTagArray[] = $this->_buildTag('[/'.$tag['tag'].']'); - array_pop($openTags); - } else { - $newTagArray[] = $parent; - $openTags[] = $parent['tag']; - } - } - if ($child) { - $newTagArray[] = $child; - $openTags[] = $child['tag']; - } - $openTags[] = $tag['tag']; - } - $newTagArray[] = $tag; - break; + case 1: + if (!$this->_isAllowed(end($openTags), $tag['tag']) || + ($parent = $this->_parentNeeded(end($openTags), $tag['tag'])) === true || + ($child = $this->_childNeeded(end($openTags), $tag['tag'])) === true) { + $tag['type'] = 0; + if ($prevTag['type'] === 0) { + $tag['text'] = $prevTag['text'].$tag['text']; + array_pop($newTagArray); + } + } else { + if ($parent) { + /* + * Avoid use of parent if we can help it. If we are + * trying to insert a new parent, but the current tag is + * the same as the previous tag, then assume that the + * previous tag structure is valid, and add this tag as + * a sibling. To add as a sibling, we need to close the + * current tag. + */ + if ($tag['tag'] == end($openTags)){ + $newTagArray[] = $this->_buildTag('[/'.$tag['tag'].']'); + array_pop($openTags); + } else { + $newTagArray[] = $parent; + $openTags[] = $parent['tag']; + } + } + if ($child) { + $newTagArray[] = $child; + $openTags[] = $child['tag']; + } + $openTags[] = $tag['tag']; + } + $newTagArray[] = $tag; + break; - case 2: - if (($tag['tag'] == end($openTags) || $this->_isAllowed(end($openTags), $tag['tag']))) { - if (in_array($tag['tag'], $openTags)) { - $tmpOpenTags = array(); - while (end($openTags) != $tag['tag']) { - $newTagArray[] = $this->_buildTag('[/'.end($openTags).']'); - $tmpOpenTags[] = end($openTags); - array_pop($openTags); - } - $newTagArray[] = $tag; - array_pop($openTags); - /* why is this here? it just seems to break things - * (nested lists where closing tags need to be - * generated) - while (end($tmpOpenTags)) { - $tmpTag = $this->_buildTag('['.end($tmpOpenTags).']'); - $newTagArray[] = $tmpTag; - $openTags[] = $tmpTag['tag']; - array_pop($tmpOpenTags); - }*/ - } - } else { - $tag['type'] = 0; - if ($prevTag['type'] === 0) { - $tag['text'] = $prevTag['text'].$tag['text']; - array_pop($newTagArray); - } - $newTagArray[] = $tag; - } - break; - } - } - while (end($openTags)) { - $newTagArray[] = $this->_buildTag('[/'.end($openTags).']'); - array_pop($openTags); - } - $this->_tagArray = $newTagArray; - } + case 2: + if (($tag['tag'] == end($openTags) || $this->_isAllowed(end($openTags), $tag['tag']))) { + if (in_array($tag['tag'], $openTags)) { + $tmpOpenTags = array(); + while (end($openTags) != $tag['tag']) { + $newTagArray[] = $this->_buildTag('[/'.end($openTags).']'); + $tmpOpenTags[] = end($openTags); + array_pop($openTags); + } + $newTagArray[] = $tag; + array_pop($openTags); + /* why is this here? it just seems to break things + * (nested lists where closing tags need to be + * generated) + while (end($tmpOpenTags)) { + $tmpTag = $this->_buildTag('['.end($tmpOpenTags).']'); + $newTagArray[] = $tmpTag; + $openTags[] = $tmpTag['tag']; + array_pop($tmpOpenTags); + }*/ + } + } else { + $tag['type'] = 0; + if ($prevTag['type'] === 0) { + $tag['text'] = $prevTag['text'].$tag['text']; + array_pop($newTagArray); + } + $newTagArray[] = $tag; + } + break; + } + } + while (end($openTags)) { + $newTagArray[] = $this->_buildTag('[/'.end($openTags).']'); + array_pop($openTags); + } + $this->_tagArray = $newTagArray; + } - /** - * Checks to see if a parent is needed - * - * Checks to see if the current $in tag has an appropriate parent. If it - * does, then it returns false. If a parent is needed, then it returns the - * first tag in the list to add to the stack. - * - * @param array tag that is on the outside - * @param array tag that is on the inside - * @return boolean false if not needed, tag if needed, true if out - * of our minds - * @access private - * @see _validateTagArray() - * @author Seth Price - */ - public function _parentNeeded($out, $in) - { - if (!isset($this->_definedTags[$in]['parent']) || - ($this->_definedTags[$in]['parent'] == 'all') - ) { - return false; - } + /** + * Checks to see if a parent is needed + * + * Checks to see if the current $in tag has an appropriate parent. If it + * does, then it returns false. If a parent is needed, then it returns the + * first tag in the list to add to the stack. + * + * @param array tag that is on the outside + * @param array tag that is on the inside + * @return boolean false if not needed, tag if needed, true if out + * of our minds + * @access private + * @see _validateTagArray() + * @author Seth Price + */ + public function _parentNeeded($out, $in) + { + if (!isset($this->_definedTags[$in]['parent']) || + ($this->_definedTags[$in]['parent'] == 'all') + ) { + return false; + } - $ar = explode('^', $this->_definedTags[$in]['parent']); - $tags = explode(',', $ar[1]); - if ($ar[0] == 'none'){ - if ($out && in_array($out, $tags)) { - return false; - } - //Create a tag from the first one on the list - return $this->_buildTag('['.$tags[0].']'); - } - if ($ar[0] == 'all' && $out && !in_array($out, $tags)) { - return false; - } - // Tag is needed, we don't know which one. We could make something up, - // but it would be so random, I think that it would be worthless. - return true; - } + $ar = explode('^', $this->_definedTags[$in]['parent']); + $tags = explode(',', $ar[1]); + if ($ar[0] == 'none'){ + if ($out && in_array($out, $tags)) { + return false; + } + //Create a tag from the first one on the list + return $this->_buildTag('['.$tags[0].']'); + } + if ($ar[0] == 'all' && $out && !in_array($out, $tags)) { + return false; + } + // Tag is needed, we don't know which one. We could make something up, + // but it would be so random, I think that it would be worthless. + return true; + } - /** - * Checks to see if a child is needed - * - * Checks to see if the current $out tag has an appropriate child. If it - * does, then it returns false. If a child is needed, then it returns the - * first tag in the list to add to the stack. - * - * @param array tag that is on the outside - * @param array tag that is on the inside - * @return boolean false if not needed, tag if needed, true if out - * of our minds - * @access private - * @see _validateTagArray() - * @author Seth Price - */ - public function _childNeeded($out, $in) - { - if (!isset($this->_definedTags[$out]['child']) || - ($this->_definedTags[$out]['child'] == 'all') - ) { - return false; - } + /** + * Checks to see if a child is needed + * + * Checks to see if the current $out tag has an appropriate child. If it + * does, then it returns false. If a child is needed, then it returns the + * first tag in the list to add to the stack. + * + * @param array tag that is on the outside + * @param array tag that is on the inside + * @return boolean false if not needed, tag if needed, true if out + * of our minds + * @access private + * @see _validateTagArray() + * @author Seth Price + */ + public function _childNeeded($out, $in) + { + if (!isset($this->_definedTags[$out]['child']) || + ($this->_definedTags[$out]['child'] == 'all') + ) { + return false; + } - $ar = explode('^', $this->_definedTags[$out]['child']); - $tags = explode(',', $ar[1]); - if ($ar[0] == 'none'){ - if ($in && in_array($in, $tags)) { - return false; - } - //Create a tag from the first one on the list - return $this->_buildTag('['.$tags[0].']'); - } - if ($ar[0] == 'all' && $in && !in_array($in, $tags)) { - return false; - } - // Tag is needed, we don't know which one. We could make something up, - // but it would be so random, I think that it would be worthless. - return true; - } + $ar = explode('^', $this->_definedTags[$out]['child']); + $tags = explode(',', $ar[1]); + if ($ar[0] == 'none'){ + if ($in && in_array($in, $tags)) { + return false; + } + //Create a tag from the first one on the list + return $this->_buildTag('['.$tags[0].']'); + } + if ($ar[0] == 'all' && $in && !in_array($in, $tags)) { + return false; + } + // Tag is needed, we don't know which one. We could make something up, + // but it would be so random, I think that it would be worthless. + return true; + } - /** - * Checks to see if a tag is allowed inside another tag - * - * The allowed tags are extracted from the private _definedTags array. - * - * @param array tag that is on the outside - * @param array tag that is on the inside - * @return boolean return true if the tag is allowed, false - * otherwise - * @access private - * @see _validateTagArray() - * @author Stijn de Reede - */ - public function _isAllowed($out, $in) - { - if (!$out || ($this->_definedTags[$out]['allowed'] == 'all')) { - return true; - } - if ($this->_definedTags[$out]['allowed'] == 'none') { - return false; - } + /** + * Checks to see if a tag is allowed inside another tag + * + * The allowed tags are extracted from the private _definedTags array. + * + * @param array tag that is on the outside + * @param array tag that is on the inside + * @return boolean return true if the tag is allowed, false + * otherwise + * @access private + * @see _validateTagArray() + * @author Stijn de Reede + */ + public function _isAllowed($out, $in) + { + if (!$out || ($this->_definedTags[$out]['allowed'] == 'all')) { + return true; + } + if ($this->_definedTags[$out]['allowed'] == 'none') { + return false; + } - $ar = explode('^', $this->_definedTags[$out]['allowed']); - $tags = explode(',', $ar[1]); - if ($ar[0] == 'none' && in_array($in, $tags)) { - return true; - } - if ($ar[0] == 'all' && in_array($in, $tags)) { - return false; - } - return false; - } + $ar = explode('^', $this->_definedTags[$out]['allowed']); + $tags = explode(',', $ar[1]); + if ($ar[0] == 'none' && in_array($in, $tags)) { + return true; + } + if ($ar[0] == 'all' && in_array($in, $tags)) { + return false; + } + return false; + } - /** - * Builds a parsed string based on the tag array - * - * The correct html and attribute values are extracted from the private - * _definedTags array. - * - * @return none - * @access private - * @see $_tagArray - * @see $_parsed - * @author Stijn de Reede - */ - public function _buildParsedString() - { - $this->_parsed = ''; - foreach ($this->_tagArray as $tag) { - switch ($tag['type']) { + /** + * Builds a parsed string based on the tag array + * + * The correct html and attribute values are extracted from the private + * _definedTags array. + * + * @return none + * @access private + * @see $_tagArray + * @see $_parsed + * @author Stijn de Reede + */ + public function _buildParsedString() + { + $this->_parsed = ''; + foreach ($this->_tagArray as $tag) { + switch ($tag['type']) { - // just text - case 0: - $this->_parsed .= $tag['text']; - break; + // just text + case 0: + $this->_parsed .= $tag['text']; + break; - // opening tag - case 1: - $this->_parsed .= '<'.$this->_definedTags[$tag['tag']]['htmlopen']; - if ($this->_options['quotestyle'] == 'single') $q = "'"; - if ($this->_options['quotestyle'] == 'double') $q = '"'; - foreach ($tag['attributes'] as $a => $v) { - //prevent XSS attacks. IMHO this is not enough, though... - //@see http://pear.php.net/bugs/bug.php?id=5609 - $v = preg_replace('#(script|about|applet|activex|chrome):#is', "\\1:", $v); - $v = htmlspecialchars($v); - $v = str_replace('&amp;', '&', $v); + // opening tag + case 1: + $this->_parsed .= '<'.$this->_definedTags[$tag['tag']]['htmlopen']; + if ($this->_options['quotestyle'] == 'single') $q = "'"; + if ($this->_options['quotestyle'] == 'double') $q = '"'; + foreach ($tag['attributes'] as $a => $v) { + //prevent XSS attacks. IMHO this is not enough, though... + //@see http://pear.php.net/bugs/bug.php?id=5609 + $v = preg_replace('#(script|about|applet|activex|chrome):#is', "\\1:", $v); + $v = htmlspecialchars($v); + $v = str_replace('&amp;', '&', $v); - if (($this->_options['quotewhat'] == 'nothing') || - (($this->_options['quotewhat'] == 'strings') && is_numeric($v)) - ) { - $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, ''); - } else { - $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, $q); - } - } - if ($this->_definedTags[$tag['tag']]['htmlclose'] == '' && $this->_options['xmlclose']) { - $this->_parsed .= ' /'; - } - $this->_parsed .= '>'; - break; + if (($this->_options['quotewhat'] == 'nothing') || + (($this->_options['quotewhat'] == 'strings') && is_numeric($v)) + ) { + $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, ''); + } else { + $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, $q); + } + } + if ($this->_definedTags[$tag['tag']]['htmlclose'] == '' && $this->_options['xmlclose']) { + $this->_parsed .= ' /'; + } + $this->_parsed .= '>'; + break; - // closing tag - case 2: - if ($this->_definedTags[$tag['tag']]['htmlclose'] != '') { - $this->_parsed .= '_definedTags[$tag['tag']]['htmlclose'].'>'; - } - break; - } - } - } + // closing tag + case 2: + if ($this->_definedTags[$tag['tag']]['htmlclose'] != '') { + $this->_parsed .= '_definedTags[$tag['tag']]['htmlclose'].'>'; + } + break; + } + } + } - /** - * Sets text in the object to be parsed - * - * @param string the text to set in the object - * @return none - * @access public - * @see getText() - * @see $_text - * @author Stijn de Reede - */ - public function setText($str) - { - $this->_text = $str; - } + /** + * Sets text in the object to be parsed + * + * @param string the text to set in the object + * @return none + * @access public + * @see getText() + * @see $_text + * @author Stijn de Reede + */ + public function setText($str) + { + $this->_text = $str; + } - /** - * Gets the unparsed text from the object - * - * @return string the text set in the object - * @access public - * @see setText() - * @see $_text - * @author Stijn de Reede - */ - public function getText() - { - return $this->_text; - } + /** + * Gets the unparsed text from the object + * + * @return string the text set in the object + * @access public + * @see setText() + * @see $_text + * @author Stijn de Reede + */ + public function getText() + { + return $this->_text; + } - /** - * Gets the preparsed text from the object - * - * @return string the text set in the object - * @access public - * @see _preparse() - * @see $_preparsed - * @author Stijn de Reede - */ - public function getPreparsed() - { - return $this->_preparsed; - } + /** + * Gets the preparsed text from the object + * + * @return string the text set in the object + * @access public + * @see _preparse() + * @see $_preparsed + * @author Stijn de Reede + */ + public function getPreparsed() + { + return $this->_preparsed; + } - /** - * Gets the parsed text from the object - * - * @return string the parsed text set in the object - * @access public - * @see parse() - * @see $_parsed - * @author Stijn de Reede - */ - public function getParsed() - { - return $this->_parsed; - } + /** + * Gets the parsed text from the object + * + * @return string the parsed text set in the object + * @access public + * @see parse() + * @see $_parsed + * @author Stijn de Reede + */ + public function getParsed() + { + return $this->_parsed; + } - /** - * Parses the text set in the object - * - * @return none - * @access public - * @see _preparse() - * @see _buildTagArray() - * @see _validateTagArray() - * @see _buildParsedString() - * @author Stijn de Reede - */ - public function parse() - { - $this->_preparse(); - $this->_buildTagArray(); - $this->_validateTagArray(); - $this->_buildParsedString(); - } + /** + * Parses the text set in the object + * + * @return none + * @access public + * @see _preparse() + * @see _buildTagArray() + * @see _validateTagArray() + * @see _buildParsedString() + * @author Stijn de Reede + */ + public function parse() + { + $this->_preparse(); + $this->_buildTagArray(); + $this->_validateTagArray(); + $this->_buildParsedString(); + } - /** - * Quick method to do setText(), parse() and getParsed at once - * - * @return none - * @access public - * @see parse() - * @see $_text - * @author Stijn de Reede - */ - public function qparse($str) - { - $this->_text = $str; - $this->parse(); - return $this->_parsed; - } + /** + * Quick method to do setText(), parse() and getParsed at once + * + * @return none + * @access public + * @see parse() + * @see $_text + * @author Stijn de Reede + */ + public function qparse($str) + { + $this->_text = $str; + $this->parse(); + return $this->_parsed; + } - /** - * Quick static method to do setText(), parse() and getParsed at once - * - * @return none - * @access public - * @see parse() - * @see $_text - * @author Stijn de Reede - */ - public function staticQparse($str) - { - $p = new SSHTMLBBCodeParser(); - $str = $p->qparse($str); - unset($p); - return $str; - } + /** + * Quick static method to do setText(), parse() and getParsed at once + * + * @return none + * @access public + * @see parse() + * @see $_text + * @author Stijn de Reede + */ + public function staticQparse($str) + { + $p = new SSHTMLBBCodeParser(); + $str = $p->qparse($str); + unset($p); + return $str; + } } diff --git a/search/SearchContext.php b/search/SearchContext.php index 610974f2b..de34b4086 100644 --- a/search/SearchContext.php +++ b/search/SearchContext.php @@ -147,7 +147,7 @@ class SearchContext extends Object { $searchParamArray = $searchParams; } - foreach($searchParamArray as $key => $value) { + foreach($searchParamArray as $key => $value) { $key = str_replace('__', '.', $key); if($filter = $this->getFilter($key)) { $filter->setModel($this->modelClass); @@ -158,9 +158,9 @@ class SearchContext extends Object { } } - if($this->connective != "AND") { - throw new Exception("SearchContext connective '$this->connective' not supported after ORM-rewrite."); - } + if($this->connective != "AND") { + throw new Exception("SearchContext connective '$this->connective' not supported after ORM-rewrite."); + } return $query; } diff --git a/security/Authenticator.php b/security/Authenticator.php index 9d61924a9..75c05ef4d 100644 --- a/security/Authenticator.php +++ b/security/Authenticator.php @@ -11,95 +11,95 @@ */ abstract class Authenticator extends Object { - /** - * This variable holds all authenticators that should be used - * - * @var array - */ - private static $authenticators = array('MemberAuthenticator'); - - /** - * Used to influence the order of authenticators on the login-screen - * (default shows first). - * - * @var string - */ - private static $default_authenticator = 'MemberAuthenticator'; + /** + * This variable holds all authenticators that should be used + * + * @var array + */ + private static $authenticators = array('MemberAuthenticator'); + + /** + * Used to influence the order of authenticators on the login-screen + * (default shows first). + * + * @var string + */ + private static $default_authenticator = 'MemberAuthenticator'; - /** - * Method to authenticate an user - * - * @param array $RAW_data Raw data to authenticate the user - * @param Form $form Optional: If passed, better error messages can be - * produced by using - * {@link Form::sessionMessage()} - * @return bool|Member Returns FALSE if authentication fails, otherwise - * the member object - */ + /** + * Method to authenticate an user + * + * @param array $RAW_data Raw data to authenticate the user + * @param Form $form Optional: If passed, better error messages can be + * produced by using + * {@link Form::sessionMessage()} + * @return bool|Member Returns FALSE if authentication fails, otherwise + * the member object + */ public static function authenticate($RAW_data, Form $form = null) { } - /** - * Method that creates the login form for this authentication method - * - * @param Controller The parent controller, necessary to create the - * appropriate form action tag - * @return Form Returns the login form to use with this authentication - * method - */ + /** + * Method that creates the login form for this authentication method + * + * @param Controller The parent controller, necessary to create the + * appropriate form action tag + * @return Form Returns the login form to use with this authentication + * method + */ public static function get_login_form(Controller $controller) { } - /** - * Get the name of the authentication method - * - * @return string Returns the name of the authentication method. - */ + /** + * Get the name of the authentication method + * + * @return string Returns the name of the authentication method. + */ public static function get_name() { } - public static function register($authenticator) { + public static function register($authenticator) { self::register_authenticator($authenticator); - } + } - /** - * Register a new authenticator - * - * The new authenticator has to exist and to be derived from the - * {@link Authenticator}. - * Every authenticator can be registered only once. - * - * @param string $authenticator Name of the authenticator class to - * register - * @return bool Returns TRUE on success, FALSE otherwise. - */ - public static function register_authenticator($authenticator) { - $authenticator = trim($authenticator); + /** + * Register a new authenticator + * + * The new authenticator has to exist and to be derived from the + * {@link Authenticator}. + * Every authenticator can be registered only once. + * + * @param string $authenticator Name of the authenticator class to + * register + * @return bool Returns TRUE on success, FALSE otherwise. + */ + public static function register_authenticator($authenticator) { + $authenticator = trim($authenticator); - if(class_exists($authenticator) == false) - return false; + if(class_exists($authenticator) == false) + return false; - if(is_subclass_of($authenticator, 'Authenticator') == false) - return false; + if(is_subclass_of($authenticator, 'Authenticator') == false) + return false; - if(in_array($authenticator, self::$authenticators) == false) { - if(call_user_func(array($authenticator, 'on_register')) === true) { - array_push(self::$authenticators, $authenticator); - } else { - return false; - } - } + if(in_array($authenticator, self::$authenticators) == false) { + if(call_user_func(array($authenticator, 'on_register')) === true) { + array_push(self::$authenticators, $authenticator); + } else { + return false; + } + } - return true; - } - - public static function unregister($authenticator) { - self::unregister_authenticator($authenticator); - } - + return true; + } + + public static function unregister($authenticator) { + self::unregister_authenticator($authenticator); + } + /** * Remove a previously registered authenticator * @@ -108,82 +108,82 @@ abstract class Authenticator extends Object { */ public static function unregister_authenticator($authenticator) { if(call_user_func(array($authenticator, 'on_unregister')) === true) { - if(in_array($authenticator, self::$authenticators)) { - unset(self::$authenticators[array_search($authenticator, self::$authenticators)]); - } + if(in_array($authenticator, self::$authenticators)) { + unset(self::$authenticators[array_search($authenticator, self::$authenticators)]); + } }; } - /** - * Check if a given authenticator is registered - * - * @param string $authenticator Name of the authenticator class to check - * @return bool Returns TRUE if the authenticator is registered, FALSE - * otherwise. - */ - public static function is_registered($authenticator) { - return in_array($authenticator, self::$authenticators); - } + /** + * Check if a given authenticator is registered + * + * @param string $authenticator Name of the authenticator class to check + * @return bool Returns TRUE if the authenticator is registered, FALSE + * otherwise. + */ + public static function is_registered($authenticator) { + return in_array($authenticator, self::$authenticators); + } - /** - * Get all registered authenticators - * - * @return array Returns an array with the class names of all registered - * authenticators. - */ - public static function get_authenticators() { - // put default authenticator first (mainly for tab-order on loginform) - if($key = array_search(self::$default_authenticator,self::$authenticators)) { - unset(self::$authenticators[$key]); - array_unshift(self::$authenticators, self::$default_authenticator); - } + /** + * Get all registered authenticators + * + * @return array Returns an array with the class names of all registered + * authenticators. + */ + public static function get_authenticators() { + // put default authenticator first (mainly for tab-order on loginform) + if($key = array_search(self::$default_authenticator,self::$authenticators)) { + unset(self::$authenticators[$key]); + array_unshift(self::$authenticators, self::$default_authenticator); + } - return self::$authenticators; - } - - /** - * Set a default authenticator (shows first in tabs) - * - * @param string - */ - public static function set_default_authenticator($authenticator) { - self::$default_authenticator = $authenticator; - - - } - - /** - * @return string - */ - public static function get_default_authenticator() { - return self::$default_authenticator; - } + return self::$authenticators; + } + + /** + * Set a default authenticator (shows first in tabs) + * + * @param string + */ + public static function set_default_authenticator($authenticator) { + self::$default_authenticator = $authenticator; + + + } + + /** + * @return string + */ + public static function get_default_authenticator() { + return self::$default_authenticator; + } - /** - * Callback function that is called when the authenticator is registered - * - * Use this method for initialization of a newly registered authenticator. - * Just overload this method and it will be called when the authenticator - * is registered. - * If the method returns FALSE, the authenticator won't be - * registered! - * - * @return bool Returns TRUE on success, FALSE otherwise. - */ - protected static function on_register() { - return true; - } - - /** - * Callback function that is called when an authenticator is removed. - * - * @return bool - */ - protected static function on_unregister() { - return true; - } + /** + * Callback function that is called when the authenticator is registered + * + * Use this method for initialization of a newly registered authenticator. + * Just overload this method and it will be called when the authenticator + * is registered. + * If the method returns FALSE, the authenticator won't be + * registered! + * + * @return bool Returns TRUE on success, FALSE otherwise. + */ + protected static function on_register() { + return true; + } + + /** + * Callback function that is called when an authenticator is removed. + * + * @return bool + */ + protected static function on_unregister() { + return true; + } } diff --git a/security/Group.php b/security/Group.php index 518011e6a..be07d5066 100755 --- a/security/Group.php +++ b/security/Group.php @@ -154,7 +154,7 @@ class Group extends DataObject { // but tabstrip.js doesn't display tabs when directly adressed through a URL pragma _t('Group.RolesAddEditLink', 'Manage roles') ) . - "

    " + "

    " ) ); @@ -332,7 +332,7 @@ class Group extends DataObject { } public function getTreeTitle() { - if($this->hasMethod('alternateTreeTitle')) return $this->alternateTreeTitle(); + if($this->hasMethod('alternateTreeTitle')) return $this->alternateTreeTitle(); else return htmlspecialchars($this->Title, ENT_QUOTES); } @@ -384,7 +384,7 @@ class Group extends DataObject { $results = $this->extend('canEdit', $member); if($results && is_array($results)) if(!min($results)) return false; - if( + if( // either we have an ADMIN (bool)Permission::checkMember($member, "ADMIN") || ( diff --git a/security/Member.php b/security/Member.php index 30794ebf5..a28d24ec6 100644 --- a/security/Member.php +++ b/security/Member.php @@ -561,7 +561,7 @@ class Member extends DataObject implements TemplateGlobalProvider { * It should return fields that are editable by the admin and the logged-in user. * * @return FieldList Returns a {@link FieldList} containing the fields for - * the member form. + * the member form. */ public function getMemberFormFields() { $fields = parent::getFrontendFields(); @@ -658,10 +658,10 @@ class Member extends DataObject implements TemplateGlobalProvider { return $word . $number; } else { - $random = rand(); - $string = md5($random); - $output = substr($string, 0, 6); - return $output; + $random = rand(); + $string = md5($random); + $output = substr($string, 0, 6); + return $output; } } @@ -1149,7 +1149,7 @@ class Member extends DataObject implements TemplateGlobalProvider { * * @param array $groupList An array of group code names. * @param array $memberGroups A component set of groups (if set to NULL, - * $this->groups() will be used) + * $this->groups() will be used) * @return array Groups in which the member is NOT in. */ public function memberNotInGroups($groupList, $memberGroups = null){ @@ -1171,7 +1171,7 @@ class Member extends DataObject implements TemplateGlobalProvider { * this member. * * @return FieldList Return a FieldList of fields that would appropriate for - * editing this member. + * editing this member. */ public function getCMSFields() { require_once('Zend/Date.php'); @@ -1542,10 +1542,10 @@ class Member_ProfileForm extends Form { $fields->push(new HiddenField('ID','ID',$member->ID)); $actions = new FieldList( - FormAction::create('dosave',_t('CMSMain.SAVE', 'Save')) - ->addExtraClass('ss-ui-button ss-ui-action-constructive') - ->setAttribute('data-icon', 'accept') - ->setUseButtonTag(true) + FormAction::create('dosave',_t('CMSMain.SAVE', 'Save')) + ->addExtraClass('ss-ui-button ss-ui-action-constructive') + ->setAttribute('data-icon', 'accept') + ->setUseButtonTag(true) ); $validator = new Member_Validator(); @@ -1592,14 +1592,14 @@ class Member_ProfileForm extends Form { * @subpackage security */ class Member_ChangePasswordEmail extends Email { - protected $from = ''; // setting a blank from address uses the site's default administrator email - protected $subject = ''; - protected $ss_template = 'ChangePasswordEmail'; - - public function __construct() { + protected $from = ''; // setting a blank from address uses the site's default administrator email + protected $subject = ''; + protected $ss_template = 'ChangePasswordEmail'; + + public function __construct() { parent::__construct(); - $this->subject = _t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject'); - } + $this->subject = _t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject'); + } } @@ -1610,14 +1610,14 @@ class Member_ChangePasswordEmail extends Email { * @subpackage security */ class Member_ForgotPasswordEmail extends Email { - protected $from = ''; // setting a blank from address uses the site's default administrator email - protected $subject = ''; - protected $ss_template = 'ForgotPasswordEmail'; - - public function __construct() { + protected $from = ''; // setting a blank from address uses the site's default administrator email + protected $subject = ''; + protected $ss_template = 'ForgotPasswordEmail'; + + public function __construct() { parent::__construct(); - $this->subject = _t('Member.SUBJECTPASSWORDRESET', "Your password reset link", 'Email subject'); - } + $this->subject = _t('Member.SUBJECTPASSWORDRESET', "Your password reset link", 'Email subject'); + } } /** diff --git a/security/MemberAuthenticator.php b/security/MemberAuthenticator.php index 91d7a16fc..7654cca37 100644 --- a/security/MemberAuthenticator.php +++ b/security/MemberAuthenticator.php @@ -18,103 +18,103 @@ class MemberAuthenticator extends Authenticator { 'sha1' => 'sha1_v2.4' ); - /** - * Method to authenticate an user - * - * @param array $RAW_data Raw data to authenticate the user - * @param Form $form Optional: If passed, better error messages can be - * produced by using - * {@link Form::sessionMessage()} - * @return bool|Member Returns FALSE if authentication fails, otherwise - * the member object - * @see Security::setDefaultAdmin() - */ - public static function authenticate($RAW_data, Form $form = null) { - if(array_key_exists('Email', $RAW_data) && $RAW_data['Email']){ - $SQL_user = Convert::raw2sql($RAW_data['Email']); - } else { - return false; - } - - $isLockedOut = false; - $result = null; - - // Default login (see Security::setDefaultAdmin()) - if(Security::check_default_admin($RAW_data['Email'], $RAW_data['Password'])) { - $member = Security::findAnAdministrator(); - } else { - $member = DataObject::get_one( - "Member", - "\"" . Member::get_unique_identifier_field() . "\" = '$SQL_user' AND \"Password\" IS NOT NULL" - ); - - if($member) { - $result = $member->checkPassword($RAW_data['Password']); - } else { - $result = new ValidationResult(false, _t('Member.ERRORWRONGCRED')); - } - - if($member && !$result->valid()) { - $member->registerFailedLogin(); - $member = false; - } - } - - // Optionally record every login attempt as a {@link LoginAttempt} object /** - * TODO We could handle this with an extension + * Method to authenticate an user + * + * @param array $RAW_data Raw data to authenticate the user + * @param Form $form Optional: If passed, better error messages can be + * produced by using + * {@link Form::sessionMessage()} + * @return bool|Member Returns FALSE if authentication fails, otherwise + * the member object + * @see Security::setDefaultAdmin() */ - if(Security::login_recording()) { - $attempt = new LoginAttempt(); - if($member) { - // successful login (member is existing with matching password) - $attempt->MemberID = $member->ID; - $attempt->Status = 'Success'; - - // Audit logging hook - $member->extend('authenticated'); + public static function authenticate($RAW_data, Form $form = null) { + if(array_key_exists('Email', $RAW_data) && $RAW_data['Email']){ + $SQL_user = Convert::raw2sql($RAW_data['Email']); } else { - // failed login - we're trying to see if a user exists with this email (disregarding wrong passwords) - $existingMember = DataObject::get_one( - "Member", - "\"" . Member::get_unique_identifier_field() . "\" = '$SQL_user'" - ); - if($existingMember) { - $attempt->MemberID = $existingMember->ID; - - // Audit logging hook - $existingMember->extend('authenticationFailed'); - } else { - - // Audit logging hook - singleton('Member')->extend('authenticationFailedUnknownUser', $RAW_data); - } - $attempt->Status = 'Failure'; - } - if(is_array($RAW_data['Email'])) { - user_error("Bad email passed to MemberAuthenticator::authenticate(): $RAW_data[Email]", E_USER_WARNING); return false; } + + $isLockedOut = false; + $result = null; + + // Default login (see Security::setDefaultAdmin()) + if(Security::check_default_admin($RAW_data['Email'], $RAW_data['Password'])) { + $member = Security::findAnAdministrator(); + } else { + $member = DataObject::get_one( + "Member", + "\"" . Member::get_unique_identifier_field() . "\" = '$SQL_user' AND \"Password\" IS NOT NULL" + ); + + if($member) { + $result = $member->checkPassword($RAW_data['Password']); + } else { + $result = new ValidationResult(false, _t('Member.ERRORWRONGCRED')); + } + + if($member && !$result->valid()) { + $member->registerFailedLogin(); + $member = false; + } + } - $attempt->Email = $RAW_data['Email']; - $attempt->IP = Controller::curr()->getRequest()->getIP(); - $attempt->write(); - } - - // Legacy migration to precision-safe password hashes. - // A login-event with cleartext passwords is the only time - // when we can rehash passwords to a different hashing algorithm, - // bulk-migration doesn't work due to the nature of hashing. - // See PasswordEncryptor_LegacyPHPHash class. - if( - $member // only migrate after successful login - && self::$migrate_legacy_hashes - && array_key_exists($member->PasswordEncryption, self::$migrate_legacy_hashes) - ) { - $member->Password = $RAW_data['Password']; - $member->PasswordEncryption = self::$migrate_legacy_hashes[$member->PasswordEncryption]; - $member->write(); - } + // Optionally record every login attempt as a {@link LoginAttempt} object + /** + * TODO We could handle this with an extension + */ + if(Security::login_recording()) { + $attempt = new LoginAttempt(); + if($member) { + // successful login (member is existing with matching password) + $attempt->MemberID = $member->ID; + $attempt->Status = 'Success'; + + // Audit logging hook + $member->extend('authenticated'); + } else { + // failed login - we're trying to see if a user exists with this email (disregarding wrong passwords) + $existingMember = DataObject::get_one( + "Member", + "\"" . Member::get_unique_identifier_field() . "\" = '$SQL_user'" + ); + if($existingMember) { + $attempt->MemberID = $existingMember->ID; + + // Audit logging hook + $existingMember->extend('authenticationFailed'); + } else { + + // Audit logging hook + singleton('Member')->extend('authenticationFailedUnknownUser', $RAW_data); + } + $attempt->Status = 'Failure'; + } + if(is_array($RAW_data['Email'])) { + user_error("Bad email passed to MemberAuthenticator::authenticate(): $RAW_data[Email]", E_USER_WARNING); + return false; + } + + $attempt->Email = $RAW_data['Email']; + $attempt->IP = Controller::curr()->getRequest()->getIP(); + $attempt->write(); + } + + // Legacy migration to precision-safe password hashes. + // A login-event with cleartext passwords is the only time + // when we can rehash passwords to a different hashing algorithm, + // bulk-migration doesn't work due to the nature of hashing. + // See PasswordEncryptor_LegacyPHPHash class. + if( + $member // only migrate after successful login + && self::$migrate_legacy_hashes + && array_key_exists($member->PasswordEncryption, self::$migrate_legacy_hashes) + ) { + $member->Password = $RAW_data['Password']; + $member->PasswordEncryption = self::$migrate_legacy_hashes[$member->PasswordEncryption]; + $member->write(); + } if($member) { Session::clear('BackURL'); @@ -126,25 +126,25 @@ class MemberAuthenticator extends Authenticator { } - /** - * Method that creates the login form for this authentication method - * - * @param Controller The parent controller, necessary to create the - * appropriate form action tag - * @return Form Returns the login form to use with this authentication - * method - */ - public static function get_login_form(Controller $controller) { - return Object::create("MemberLoginForm", $controller, "LoginForm"); - } + /** + * Method that creates the login form for this authentication method + * + * @param Controller The parent controller, necessary to create the + * appropriate form action tag + * @return Form Returns the login form to use with this authentication + * method + */ + public static function get_login_form(Controller $controller) { + return Object::create("MemberLoginForm", $controller, "LoginForm"); + } - /** - * Get the name of the authentication method - * - * @return string Returns the name of the authentication method. - */ - public static function get_name() { + /** + * Get the name of the authentication method + * + * @return string Returns the name of the authentication method. + */ + public static function get_name() { return _t('MemberAuthenticator.TITLE', "E-mail & Password"); } } diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php index 2e04763f4..5ac7b7d86 100644 --- a/security/MemberLoginForm.php +++ b/security/MemberLoginForm.php @@ -33,7 +33,7 @@ class MemberLoginForm extends LoginForm { * @param string $authenticatorClassName Name of the authenticator class that this form uses. */ public function __construct($controller, $name, $fields = null, $actions = null, - $checkCurrentUser = true) { + $checkCurrentUser = true) { // This is now set on the class directly to make it easier to create subclasses // $this->authenticator_class = $authenticatorClassName; @@ -226,13 +226,13 @@ JS } - /** - * Try to authenticate the user - * - * @param array Submitted data - * @return Member Returns the member object on successful authentication - * or NULL on failure. - */ + /** + * Try to authenticate the user + * + * @param array Submitted data + * @return Member Returns the member object on successful authentication + * or NULL on failure. + */ public function performLogin($data) { $member = call_user_func_array(array($this->authenticator_class, 'authenticate'), array($data, $this)); if($member) { diff --git a/security/Permission.php b/security/Permission.php index a77b8280c..771fef203 100644 --- a/security/Permission.php +++ b/security/Permission.php @@ -6,7 +6,7 @@ */ class Permission extends DataObject implements TemplateGlobalProvider { - // the (1) after Type specifies the DB default value which is needed for + // the (1) after Type specifies the DB default value which is needed for // upgrades from older SilverStripe versions static $db = array( "Code" => "Varchar", @@ -55,7 +55,7 @@ class Permission extends DataObject implements TemplateGlobalProvider { */ static $declared_permissions = null; - /** + /** * Linear list of declared permissions in the system. * * @var array diff --git a/security/PermissionCheckboxSetField.php b/security/PermissionCheckboxSetField.php index c2a98f3b7..059735371 100644 --- a/security/PermissionCheckboxSetField.php +++ b/security/PermissionCheckboxSetField.php @@ -157,7 +157,7 @@ class PermissionCheckboxSetField extends FormField { } } } - + $odd = 0; $options = ''; if($this->source) { @@ -256,7 +256,7 @@ class PermissionCheckboxSetField extends FormField { $idList = array(); if($this->value) foreach($this->value as $id => $bool) { - if($bool) { + if($bool) { $perm = new $managedClass(); $perm->{$this->filterField} = $record->ID; $perm->Code = $id; diff --git a/security/Security.php b/security/Security.php index 908cb13a0..5b3daf07c 100644 --- a/security/Security.php +++ b/security/Security.php @@ -7,18 +7,18 @@ class Security extends Controller { static $allowed_actions = array( - 'index', - 'login', - 'logout', - 'basicauthlogin', - 'lostpassword', - 'passwordsent', - 'changepassword', + 'index', + 'login', + 'logout', + 'basicauthlogin', + 'lostpassword', + 'passwordsent', + 'changepassword', 'ping', 'LoginForm', 'ChangePasswordForm', 'LostPasswordForm', - ); + ); /** * Default user name. Only used in dev-mode by {@link setDefaultAdmin()} @@ -138,22 +138,22 @@ class Security extends Controller { * If you don't provide a messageSet, a default will be used. * * @param Controller $controller The controller that you were on to cause the permission - * failure. + * failure. * @param string|array $messageSet The message to show to the user. This - * can be a string, or a map of different - * messages for different contexts. - * If you pass an array, you can use the - * following keys: - * - default: The default message - * - logInAgain: The message to show - * if the user has just - * logged out and the - * - alreadyLoggedIn: The message to - * show if the user - * is already logged - * in and lacks the - * permission to - * access the item. + * can be a string, or a map of different + * messages for different contexts. + * If you pass an array, you can use the + * following keys: + * - default: The default message + * - logInAgain: The message to show + * if the user has just + * logged out and the + * - alreadyLoggedIn: The message to + * show if the user + * is already logged + * in and lacks the + * permission to + * access the item. * * The alreadyLoggedIn value can contain a '%s' placeholder that will be replaced with a link * to log in. @@ -240,7 +240,7 @@ class Security extends Controller { } - /** + /** * Get the login form to process according to the submitted data */ protected function LoginForm() { @@ -262,7 +262,7 @@ class Security extends Controller { } - /** + /** * Get the login forms for all available authentication methods * * @return array Returns an array of available login forms (array of Form @@ -276,8 +276,8 @@ class Security extends Controller { $authenticators = Authenticator::get_authenticators(); foreach($authenticators as $authenticator) { - array_push($forms, - call_user_func(array($authenticator, 'get_login_form'), + array_push($forms, + call_user_func(array($authenticator, 'get_login_form'), $this)); } @@ -307,9 +307,9 @@ class Security extends Controller { * Log the currently logged in user out * * @param bool $redirect Redirect the user back to where they came. - * - If it's false, the code calling logout() is - * responsible for sending the user where-ever - * they should go. + * - If it's false, the code calling logout() is + * responsible for sending the user where-ever + * they should go. */ public function logout($redirect = true) { $member = Member::currentUser(); @@ -675,7 +675,7 @@ class Security extends Controller { } if ($adminGroup) { - $member = $adminGroup->Members()->First(); + $member = $adminGroup->Members()->First(); } if(!$adminGroup) { diff --git a/tests/api/RestfulServiceTest.php b/tests/api/RestfulServiceTest.php index 9cc817bc5..201ea6f3a 100644 --- a/tests/api/RestfulServiceTest.php +++ b/tests/api/RestfulServiceTest.php @@ -37,7 +37,7 @@ class RestfulServiceTest extends SapphireTest { $service->setQueryString($params); $responseBody = $service->request($url)->getBody(); foreach ($params as $key => $value) { - $this->assertContains("$value", $responseBody); + $this->assertContains("$value", $responseBody); $this->assertContains("$value", $responseBody); } } @@ -52,7 +52,7 @@ class RestfulServiceTest extends SapphireTest { $service->setQueryString($params); $responseBody = $service->request($url)->getBody(); foreach ($params as $key => $value) { - $this->assertContains("$value", $responseBody); + $this->assertContains("$value", $responseBody); $this->assertContains("$value", $responseBody); } } @@ -67,7 +67,7 @@ class RestfulServiceTest extends SapphireTest { $url .= '?' . http_build_query($params); $responseBody = $service->request($url)->getBody(); foreach ($params as $key => $value) { - $this->assertContains("$value", $responseBody); + $this->assertContains("$value", $responseBody); $this->assertContains("$value", $responseBody); } } diff --git a/tests/behat/features/bootstrap/FeatureContext.php b/tests/behat/features/bootstrap/FeatureContext.php index ea6f10054..a522e8a7c 100644 --- a/tests/behat/features/bootstrap/FeatureContext.php +++ b/tests/behat/features/bootstrap/FeatureContext.php @@ -3,10 +3,10 @@ namespace SilverStripe\Framework\Test\Behaviour; use SilverStripe\BehatExtension\Context\SilverStripeContext, - SilverStripe\BehatExtension\Context\BasicContext, - SilverStripe\BehatExtension\Context\LoginContext, - SilverStripe\Framework\Test\Behaviour\CmsFormsContext, - SilverStripe\Framework\Test\Behaviour\CmsUiContext; + SilverStripe\BehatExtension\Context\BasicContext, + SilverStripe\BehatExtension\Context\LoginContext, + SilverStripe\Framework\Test\Behaviour\CmsFormsContext, + SilverStripe\Framework\Test\Behaviour\CmsUiContext; // PHPUnit require_once 'PHPUnit/Autoload.php'; @@ -20,19 +20,19 @@ require_once 'PHPUnit/Framework/Assert/Functions.php'; */ class FeatureContext extends SilverStripeContext { - /** - * Initializes context. - * Every scenario gets it's own context object. - * - * @param array $parameters context parameters (set them up through behat.yml) - */ - public function __construct(array $parameters) - { - $this->useContext('BasicContext', new BasicContext($parameters)); - $this->useContext('LoginContext', new LoginContext($parameters)); - $this->useContext('CmsFormsContext', new CmsFormsContext($parameters)); - $this->useContext('CmsUiContext', new CmsUiContext($parameters)); + /** + * Initializes context. + * Every scenario gets it's own context object. + * + * @param array $parameters context parameters (set them up through behat.yml) + */ + public function __construct(array $parameters) + { + $this->useContext('BasicContext', new BasicContext($parameters)); + $this->useContext('LoginContext', new LoginContext($parameters)); + $this->useContext('CmsFormsContext', new CmsFormsContext($parameters)); + $this->useContext('CmsUiContext', new CmsUiContext($parameters)); - parent::__construct($parameters); - } + parent::__construct($parameters); + } } diff --git a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php index 7e3591913..ca7b7baf9 100644 --- a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php +++ b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php @@ -3,12 +3,12 @@ namespace SilverStripe\Framework\Test\Behaviour; use Behat\Behat\Context\ClosuredContextInterface, - Behat\Behat\Context\TranslatedContextInterface, - Behat\Behat\Context\BehatContext, - Behat\Behat\Context\Step, - Behat\Behat\Exception\PendingException; + Behat\Behat\Context\TranslatedContextInterface, + Behat\Behat\Context\BehatContext, + Behat\Behat\Context\Step, + Behat\Behat\Exception\PendingException; use Behat\Gherkin\Node\PyStringNode, - Behat\Gherkin\Node\TableNode; + Behat\Gherkin\Node\TableNode; // PHPUnit require_once 'PHPUnit/Autoload.php'; @@ -21,81 +21,81 @@ require_once 'PHPUnit/Framework/Assert/Functions.php'; */ class CmsFormsContext extends BehatContext { - protected $context; + protected $context; - /** - * Initializes context. - * Every scenario gets it's own context object. - * - * @param array $parameters context parameters (set them up through behat.yml) - */ - public function __construct(array $parameters) - { - // Initialize your context here - $this->context = $parameters; - } + /** + * Initializes context. + * Every scenario gets it's own context object. + * + * @param array $parameters context parameters (set them up through behat.yml) + */ + public function __construct(array $parameters) + { + // Initialize your context here + $this->context = $parameters; + } - /** - * Get Mink session from MinkContext - */ - public function getSession($name = null) - { - return $this->getMainContext()->getSession($name); - } + /** + * Get Mink session from MinkContext + */ + public function getSession($name = null) + { + return $this->getMainContext()->getSession($name); + } - /** - * @Then /^I should see an edit page form$/ - */ - public function stepIShouldSeeAnEditPageForm() - { - $page = $this->getSession()->getPage(); + /** + * @Then /^I should see an edit page form$/ + */ + public function stepIShouldSeeAnEditPageForm() + { + $page = $this->getSession()->getPage(); - $form = $page->find('css', '#Form_EditForm'); - assertNotNull($form, 'I should see an edit page form'); - } + $form = $page->find('css', '#Form_EditForm'); + assertNotNull($form, 'I should see an edit page form'); + } - /** - * @When /^I fill in the "(?P([^"]*))" HTML field with "(?P([^"]*))"$/ - * @When /^I fill in "(?P([^"]*))" for the "(?P([^"]*))" HTML field$/ - */ - public function stepIFillInTheHtmlFieldWith($field, $value) - { - $page = $this->getSession()->getPage(); - $inputField = $page->findField($field); - assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); + /** + * @When /^I fill in the "(?P([^"]*))" HTML field with "(?P([^"]*))"$/ + * @When /^I fill in "(?P([^"]*))" for the "(?P([^"]*))" HTML field$/ + */ + public function stepIFillInTheHtmlFieldWith($field, $value) + { + $page = $this->getSession()->getPage(); + $inputField = $page->findField($field); + assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); - $this->getSession()->evaluateScript(sprintf( - "jQuery('#%s').entwine('ss').getEditor().setContent('%s')", - $inputField->getAttribute('id'), - addcslashes($value, "'") - )); - } + $this->getSession()->evaluateScript(sprintf( + "jQuery('#%s').entwine('ss').getEditor().setContent('%s')", + $inputField->getAttribute('id'), + addcslashes($value, "'") + )); + } - /** - * @When /^I append "(?P([^"]*))" to the "(?P([^"]*))" HTML field$/ - */ - public function stepIAppendTotheHtmlField($field, $value) - { - $page = $this->getSession()->getPage(); - $inputField = $page->findField($field); - assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); + /** + * @When /^I append "(?P([^"]*))" to the "(?P([^"]*))" HTML field$/ + */ + public function stepIAppendTotheHtmlField($field, $value) + { + $page = $this->getSession()->getPage(); + $inputField = $page->findField($field); + assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); - $this->getSession()->evaluateScript(sprintf( - "jQuery('#%s').entwine('ss').getEditor().insertContent('%s')", - $inputField->getAttribute('id'), - addcslashes($value, "'") - )); - } + $this->getSession()->evaluateScript(sprintf( + "jQuery('#%s').entwine('ss').getEditor().insertContent('%s')", + $inputField->getAttribute('id'), + addcslashes($value, "'") + )); + } - /** - * @Then /^the "(?P([^"]*))" HTML field should contain "(?P([^"]*))"$/ - */ - public function theHtmlFieldShouldContain($field, $value) - { - $page = $this->getSession()->getPage(); - $inputField = $page->findField($field); - assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); + /** + * @Then /^the "(?P([^"]*))" HTML field should contain "(?P([^"]*))"$/ + */ + public function theHtmlFieldShouldContain($field, $value) + { + $page = $this->getSession()->getPage(); + $inputField = $page->findField($field); + assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); - $this->getMainContext()->assertElementContains('#' . $inputField->getAttribute('id'), $value); - } + $this->getMainContext()->assertElementContains('#' . $inputField->getAttribute('id'), $value); + } } diff --git a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php index 85200c835..1eaba63b4 100644 --- a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php +++ b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php @@ -3,12 +3,12 @@ namespace SilverStripe\Framework\Test\Behaviour; use Behat\Behat\Context\ClosuredContextInterface, - Behat\Behat\Context\TranslatedContextInterface, - Behat\Behat\Context\BehatContext, - Behat\Behat\Context\Step, - Behat\Behat\Exception\PendingException; + Behat\Behat\Context\TranslatedContextInterface, + Behat\Behat\Context\BehatContext, + Behat\Behat\Context\Step, + Behat\Behat\Exception\PendingException; use Behat\Gherkin\Node\PyStringNode, - Behat\Gherkin\Node\TableNode; + Behat\Gherkin\Node\TableNode; // PHPUnit @@ -22,293 +22,293 @@ require_once 'PHPUnit/Framework/Assert/Functions.php'; */ class CmsUiContext extends BehatContext { - protected $context; + protected $context; - /** - * Initializes context. - * Every scenario gets it's own context object. - * - * @param array $parameters context parameters (set them up through behat.yml) - */ - public function __construct(array $parameters) - { - // Initialize your context here - $this->context = $parameters; - } + /** + * Initializes context. + * Every scenario gets it's own context object. + * + * @param array $parameters context parameters (set them up through behat.yml) + */ + public function __construct(array $parameters) + { + // Initialize your context here + $this->context = $parameters; + } - /** - * Get Mink session from MinkContext - */ - public function getSession($name = null) - { - return $this->getMainContext()->getSession($name); - } + /** + * Get Mink session from MinkContext + */ + public function getSession($name = null) + { + return $this->getMainContext()->getSession($name); + } - /** - * @Then /^I should see the CMS$/ - */ - public function iShouldSeeTheCms() - { - $page = $this->getSession()->getPage(); - $cms_element = $page->find('css', '.cms'); - assertNotNull($cms_element, 'CMS not found'); - } + /** + * @Then /^I should see the CMS$/ + */ + public function iShouldSeeTheCms() + { + $page = $this->getSession()->getPage(); + $cms_element = $page->find('css', '.cms'); + assertNotNull($cms_element, 'CMS not found'); + } - /** - * @Then /^I should see a "([^"]*)" notice$/ - */ - public function iShouldSeeANotice($notice) - { - $this->getMainContext()->assertElementContains('.notice-wrap', $notice); - } + /** + * @Then /^I should see a "([^"]*)" notice$/ + */ + public function iShouldSeeANotice($notice) + { + $this->getMainContext()->assertElementContains('.notice-wrap', $notice); + } - /** - * @Then /^I should see a "([^"]*)" message$/ - */ - public function iShouldSeeAMessage($message) - { - $this->getMainContext()->assertElementContains('.message', $message); - } + /** + * @Then /^I should see a "([^"]*)" message$/ + */ + public function iShouldSeeAMessage($message) + { + $this->getMainContext()->assertElementContains('.message', $message); + } - protected function getCmsTabsElement() - { - $this->getSession()->wait(5000, "window.jQuery('.cms-content-header-tabs').size() > 0"); + protected function getCmsTabsElement() + { + $this->getSession()->wait(5000, "window.jQuery('.cms-content-header-tabs').size() > 0"); - $page = $this->getSession()->getPage(); - $cms_content_header_tabs = $page->find('css', '.cms-content-header-tabs'); - assertNotNull($cms_content_header_tabs, 'CMS tabs not found'); + $page = $this->getSession()->getPage(); + $cms_content_header_tabs = $page->find('css', '.cms-content-header-tabs'); + assertNotNull($cms_content_header_tabs, 'CMS tabs not found'); - return $cms_content_header_tabs; - } + return $cms_content_header_tabs; + } - protected function getCmsContentToolbarElement() - { - $this->getSession()->wait( - 5000, - "window.jQuery('.cms-content-toolbar').size() > 0 " - . "&& window.jQuery('.cms-content-toolbar').children().size() > 0" - ); + protected function getCmsContentToolbarElement() + { + $this->getSession()->wait( + 5000, + "window.jQuery('.cms-content-toolbar').size() > 0 " + . "&& window.jQuery('.cms-content-toolbar').children().size() > 0" + ); - $page = $this->getSession()->getPage(); - $cms_content_toolbar_element = $page->find('css', '.cms-content-toolbar'); - assertNotNull($cms_content_toolbar_element, 'CMS content toolbar not found'); + $page = $this->getSession()->getPage(); + $cms_content_toolbar_element = $page->find('css', '.cms-content-toolbar'); + assertNotNull($cms_content_toolbar_element, 'CMS content toolbar not found'); - return $cms_content_toolbar_element; - } + return $cms_content_toolbar_element; + } - protected function getCmsTreeElement() - { - $this->getSession()->wait(5000, "window.jQuery('.cms-tree').size() > 0"); + protected function getCmsTreeElement() + { + $this->getSession()->wait(5000, "window.jQuery('.cms-tree').size() > 0"); - $page = $this->getSession()->getPage(); - $cms_tree_element = $page->find('css', '.cms-tree'); - assertNotNull($cms_tree_element, 'CMS tree not found'); + $page = $this->getSession()->getPage(); + $cms_tree_element = $page->find('css', '.cms-tree'); + assertNotNull($cms_tree_element, 'CMS tree not found'); - return $cms_tree_element; - } + return $cms_tree_element; + } - protected function getGridfieldTable($title) - { - $page = $this->getSession()->getPage(); - $table_elements = $page->findAll('css', '.ss-gridfield-table'); - assertNotNull($table_elements, 'Table elements not found'); + protected function getGridfieldTable($title) + { + $page = $this->getSession()->getPage(); + $table_elements = $page->findAll('css', '.ss-gridfield-table'); + assertNotNull($table_elements, 'Table elements not found'); - $table_element = null; - foreach ($table_elements as $table) { - $table_title_element = $table->find('css', '.title'); - if ($table_title_element->getText() === $title) { - $table_element = $table; - break; - } - } - assertNotNull($table_element, sprintf('Table `%s` not found', $title)); + $table_element = null; + foreach ($table_elements as $table) { + $table_title_element = $table->find('css', '.title'); + if ($table_title_element->getText() === $title) { + $table_element = $table; + break; + } + } + assertNotNull($table_element, sprintf('Table `%s` not found', $title)); - return $table_element; - } + return $table_element; + } - /** - * @Given /^I should see a "([^"]*)" button in CMS Content Toolbar$/ - */ - public function iShouldSeeAButtonInCmsContentToolbar($text) - { - $cms_content_toolbar_element = $this->getCmsContentToolbarElement(); + /** + * @Given /^I should see a "([^"]*)" button in CMS Content Toolbar$/ + */ + public function iShouldSeeAButtonInCmsContentToolbar($text) + { + $cms_content_toolbar_element = $this->getCmsContentToolbarElement(); - $element = $cms_content_toolbar_element->find('named', array('link_or_button', "'$text'")); - assertNotNull($element, sprintf('%s button not found', $text)); - } + $element = $cms_content_toolbar_element->find('named', array('link_or_button', "'$text'")); + assertNotNull($element, sprintf('%s button not found', $text)); + } - /** - * @When /^I should see "([^"]*)" in CMS Tree$/ - */ - public function stepIShouldSeeInCmsTree($text) - { - $cms_tree_element = $this->getCmsTreeElement(); + /** + * @When /^I should see "([^"]*)" in CMS Tree$/ + */ + public function stepIShouldSeeInCmsTree($text) + { + $cms_tree_element = $this->getCmsTreeElement(); - $element = $cms_tree_element->find('named', array('content', "'$text'")); - assertNotNull($element, sprintf('%s not found', $text)); - } + $element = $cms_tree_element->find('named', array('content', "'$text'")); + assertNotNull($element, sprintf('%s not found', $text)); + } - /** - * @When /^I should not see "([^"]*)" in CMS Tree$/ - */ - public function stepIShouldNotSeeInCmsTree($text) - { - $cms_tree_element = $this->getCmsTreeElement(); + /** + * @When /^I should not see "([^"]*)" in CMS Tree$/ + */ + public function stepIShouldNotSeeInCmsTree($text) + { + $cms_tree_element = $this->getCmsTreeElement(); - $element = $cms_tree_element->find('named', array('content', "'$text'")); - assertNull($element, sprintf('%s found', $text)); - } + $element = $cms_tree_element->find('named', array('content', "'$text'")); + assertNull($element, sprintf('%s found', $text)); + } - /** - * @When /^I expand the "([^"]*)" CMS Panel$/ - */ - public function iExpandTheCmsPanel() - { - // TODO Make dynamic, currently hardcoded to first panel - $page = $this->getSession()->getPage(); + /** + * @When /^I expand the "([^"]*)" CMS Panel$/ + */ + public function iExpandTheCmsPanel() + { + // TODO Make dynamic, currently hardcoded to first panel + $page = $this->getSession()->getPage(); - $panel_toggle_element = $page->find('css', '.cms-content > .cms-panel > .cms-panel-toggle > .toggle-expand'); - assertNotNull($panel_toggle_element, 'Panel toggle not found'); + $panel_toggle_element = $page->find('css', '.cms-content > .cms-panel > .cms-panel-toggle > .toggle-expand'); + assertNotNull($panel_toggle_element, 'Panel toggle not found'); - if ($panel_toggle_element->isVisible()) { - $panel_toggle_element->click(); - } - } + if ($panel_toggle_element->isVisible()) { + $panel_toggle_element->click(); + } + } - /** - * @When /^I click the "([^"]*)" CMS tab$/ - */ - public function iClickTheCmsTab($tab) - { - $this->getSession()->wait(5000, "window.jQuery('.ui-tabs-nav').size() > 0"); + /** + * @When /^I click the "([^"]*)" CMS tab$/ + */ + public function iClickTheCmsTab($tab) + { + $this->getSession()->wait(5000, "window.jQuery('.ui-tabs-nav').size() > 0"); - $page = $this->getSession()->getPage(); - $tabsets = $page->findAll('css', '.ui-tabs-nav'); - assertNotNull($tabsets, 'CMS tabs not found'); + $page = $this->getSession()->getPage(); + $tabsets = $page->findAll('css', '.ui-tabs-nav'); + assertNotNull($tabsets, 'CMS tabs not found'); - $tab_element = null; - foreach($tabsets as $tabset) { - if($tab_element) continue; - $tab_element = $tabset->find('named', array('link_or_button', "'$tab'")); - } - assertNotNull($tab_element, sprintf('%s tab not found', $tab)); + $tab_element = null; + foreach($tabsets as $tabset) { + if($tab_element) continue; + $tab_element = $tabset->find('named', array('link_or_button', "'$tab'")); + } + assertNotNull($tab_element, sprintf('%s tab not found', $tab)); - $tab_element->click(); - } + $tab_element->click(); + } - /** - * @Then /^the "([^"]*)" table should contain "([^"]*)"$/ - */ - public function theTableShouldContain($table, $text) - { - $table_element = $this->getGridfieldTable($table); + /** + * @Then /^the "([^"]*)" table should contain "([^"]*)"$/ + */ + public function theTableShouldContain($table, $text) + { + $table_element = $this->getGridfieldTable($table); - $element = $table_element->find('named', array('content', "'$text'")); - assertNotNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $table)); - } + $element = $table_element->find('named', array('content', "'$text'")); + assertNotNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $table)); + } - /** - * @Then /^the "([^"]*)" table should not contain "([^"]*)"$/ - */ - public function theTableShouldNotContain($table, $text) - { - $table_element = $this->getGridfieldTable($table); + /** + * @Then /^the "([^"]*)" table should not contain "([^"]*)"$/ + */ + public function theTableShouldNotContain($table, $text) + { + $table_element = $this->getGridfieldTable($table); - $element = $table_element->find('named', array('content', "'$text'")); - assertNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $table)); - } + $element = $table_element->find('named', array('content', "'$text'")); + assertNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $table)); + } - /** - * @Given /^I click on "([^"]*)" in the "([^"]*)" table$/ - */ - public function iClickOnInTheTable($text, $table) - { - $table_element = $this->getGridfieldTable($table); + /** + * @Given /^I click on "([^"]*)" in the "([^"]*)" table$/ + */ + public function iClickOnInTheTable($text, $table) + { + $table_element = $this->getGridfieldTable($table); - $element = $table_element->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text)); - assertNotNull($element, sprintf('Element containing `%s` not found', $text)); - $element->click(); - } + $element = $table_element->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text)); + assertNotNull($element, sprintf('Element containing `%s` not found', $text)); + $element->click(); + } - /** - * @Then /^I can see the preview panel$/ - */ - public function iCanSeeThePreviewPanel() - { - $this->getMainContext()->assertElementOnPage('.cms-preview'); - } + /** + * @Then /^I can see the preview panel$/ + */ + public function iCanSeeThePreviewPanel() + { + $this->getMainContext()->assertElementOnPage('.cms-preview'); + } - /** - * @Given /^the preview contains "([^"]*)"$/ - */ - public function thePreviewContains($content) - { - $driver = $this->getSession()->getDriver(); - $driver->switchToIFrame('cms-preview-iframe'); + /** + * @Given /^the preview contains "([^"]*)"$/ + */ + public function thePreviewContains($content) + { + $driver = $this->getSession()->getDriver(); + $driver->switchToIFrame('cms-preview-iframe'); - $this->getMainContext()->assertPageContainsText($content); - $driver->switchToWindow(); - } + $this->getMainContext()->assertPageContainsText($content); + $driver->switchToWindow(); + } - /** - * @Given /^the preview does not contain "([^"]*)"$/ - */ - public function thePreviewDoesNotContain($content) - { - $driver = $this->getSession()->getDriver(); - $driver->switchToIFrame('cms-preview-iframe'); + /** + * @Given /^the preview does not contain "([^"]*)"$/ + */ + public function thePreviewDoesNotContain($content) + { + $driver = $this->getSession()->getDriver(); + $driver->switchToIFrame('cms-preview-iframe'); - $this->getMainContext()->assertPageNotContainsText($content); - $driver->switchToWindow(); - } + $this->getMainContext()->assertPageNotContainsText($content); + $driver->switchToWindow(); + } - /** - * Workaround for chosen.js dropdowns which hide the original dropdown field. - * - * @When /^(?:|I )fill in "(?P(?:[^"]|\\")*)" dropdown with "(?P(?:[^"]|\\")*)"$/ - * @When /^(?:|I )fill in "(?P(?:[^"]|\\")*)" for "(?P(?:[^"]|\\")*)" dropdown$/ - */ - public function theIFillInTheDropdownWith($field, $value) - { - $field = $this->fixStepArgument($field); - $value = $this->fixStepArgument($value); + /** + * Workaround for chosen.js dropdowns which hide the original dropdown field. + * + * @When /^(?:|I )fill in "(?P(?:[^"]|\\")*)" dropdown with "(?P(?:[^"]|\\")*)"$/ + * @When /^(?:|I )fill in "(?P(?:[^"]|\\")*)" for "(?P(?:[^"]|\\")*)" dropdown$/ + */ + public function theIFillInTheDropdownWith($field, $value) + { + $field = $this->fixStepArgument($field); + $value = $this->fixStepArgument($value); - $inputField = $this->getSession()->getPage()->findField($field); - if(null === $inputField) { - throw new ElementNotFoundException(sprintf( - 'Chosen.js dropdown named "%s" not found', - $field - )); - } + $inputField = $this->getSession()->getPage()->findField($field); + if(null === $inputField) { + throw new ElementNotFoundException(sprintf( + 'Chosen.js dropdown named "%s" not found', + $field + )); + } - $container = $inputField->getParent()->getParent(); - if(null === $container) throw new ElementNotFoundException('Chosen.js field container not found'); - - $linkEl = $container->find('xpath', './/a'); - if(null === $linkEl) throw new ElementNotFoundException('Chosen.js link element not found'); - $linkEl->click(); - $this->getSession()->wait(100); // wait for dropdown overlay to appear + $container = $inputField->getParent()->getParent(); + if(null === $container) throw new ElementNotFoundException('Chosen.js field container not found'); + + $linkEl = $container->find('xpath', './/a'); + if(null === $linkEl) throw new ElementNotFoundException('Chosen.js link element not found'); + $linkEl->click(); + $this->getSession()->wait(100); // wait for dropdown overlay to appear - $listEl = $container->find('xpath', sprintf('.//li[contains(normalize-space(string(.)), \'%s\')]', $value)); - if(null === $listEl) - { - throw new ElementNotFoundException(sprintf( - 'Chosen.js list element with title "%s" not found', - $value - )); - } - $listEl->click(); - } + $listEl = $container->find('xpath', sprintf('.//li[contains(normalize-space(string(.)), \'%s\')]', $value)); + if(null === $listEl) + { + throw new ElementNotFoundException(sprintf( + 'Chosen.js list element with title "%s" not found', + $value + )); + } + $listEl->click(); + } - /** - * Returns fixed step argument (with \\" replaced back to "). - * - * @param string $argument - * - * @return string - */ - protected function fixStepArgument($argument) - { - return str_replace('\\"', '"', $argument); - } + /** + * Returns fixed step argument (with \\" replaced back to "). + * + * @param string $argument + * + * @return string + */ + protected function fixStepArgument($argument) + { + return str_replace('\\"', '"', $argument); + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1904a068f..8e4da445d 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -27,18 +27,18 @@ if(!defined('BASE_PATH')) define('BASE_PATH', dirname($frameworkPath)); // Copied from cli-script.php, to enable same behaviour through phpunit runner. if(isset($_SERVER['argv'][2])) { - $args = array_slice($_SERVER['argv'],2); - if(!isset($_GET)) $_GET = array(); - if(!isset($_REQUEST)) $_REQUEST = array(); - foreach($args as $arg) { - if(strpos($arg,'=') == false) { - $_GET['args'][] = $arg; - } else { - $newItems = array(); - parse_str( (substr($arg,0,2) == '--') ? substr($arg,2) : $arg, $newItems ); - $_GET = array_merge($_GET, $newItems); - } - } + $args = array_slice($_SERVER['argv'],2); + if(!isset($_GET)) $_GET = array(); + if(!isset($_REQUEST)) $_REQUEST = array(); + foreach($args as $arg) { + if(strpos($arg,'=') == false) { + $_GET['args'][] = $arg; + } else { + $newItems = array(); + parse_str( (substr($arg,0,2) == '--') ? substr($arg,2) : $arg, $newItems ); + $_GET = array_merge($_GET, $newItems); + } + } $_REQUEST = array_merge($_REQUEST, $_GET); } @@ -70,4 +70,4 @@ if(!isset($_GET['flush']) || !$_GET['flush']) { "Add flush=1 as an argument to discover new classes or files.\n", false ); -} \ No newline at end of file +} diff --git a/tests/control/HTTPTest.php b/tests/control/HTTPTest.php index 654b824cb..5379c7ae9 100644 --- a/tests/control/HTTPTest.php +++ b/tests/control/HTTPTest.php @@ -10,8 +10,8 @@ class HTTPTest extends SapphireTest { /** * Tests {@link HTTP::getLinksIn()} */ - public function testGetLinksIn() { - $content = ' + public function testGetLinksIn() { + $content = '

    My Cool Site

    @@ -26,13 +26,13 @@ class HTTPTest extends SapphireTest { played a part in his journey. HE ALSO DISCOVERED THE KEY. Later he got his mixed up.

    - '; - + '; + $expected = array ( '/', 'home/', 'mother/', '$Journey', 'space travel', 'unquoted', 'single quote', '/father', 'attributes', 'journey', 'CAPS LOCK', 'quotes \'mixed\' up' ); - + $result = HTTP::getLinksIn($content); // Results don't neccesarily come out in the order they are in the $content param. @@ -41,7 +41,7 @@ class HTTPTest extends SapphireTest { $this->assertTrue(is_array($result)); $this->assertEquals($expected, $result, 'Test that all links within the content are found.'); - } + } /** * Tests {@link HTTP::setGetVar()} diff --git a/tests/core/ArrayDataTest.php b/tests/core/ArrayDataTest.php index 6869246f6..88835b630 100644 --- a/tests/core/ArrayDataTest.php +++ b/tests/core/ArrayDataTest.php @@ -11,7 +11,7 @@ class ArrayDataTest extends SapphireTest { $this->assertEquals("Varchar", get_class($arrayData->A)); $this->assertEquals("ArrayData", get_class($arrayData->B)); } - + public function testWrappingANonEmptyObjectWorks() { $object = new ArrayDataTest_NonEmptyObject(); $this->assertTrue(is_object($object)); diff --git a/tests/core/manifest/fixtures/namespaced_classmanifest/module/classes/ClassB.php b/tests/core/manifest/fixtures/namespaced_classmanifest/module/classes/ClassB.php index 1cd5a0dc4..59ea70846 100644 --- a/tests/core/manifest/fixtures/namespaced_classmanifest/module/classes/ClassB.php +++ b/tests/core/manifest/fixtures/namespaced_classmanifest/module/classes/ClassB.php @@ -4,5 +4,5 @@ */ namespace silverstripe\test; - + class ClassB extends ClassA { } diff --git a/tests/dev/CsvBulkLoaderTest.php b/tests/dev/CsvBulkLoaderTest.php index 2df55a45c..435972547 100644 --- a/tests/dev/CsvBulkLoaderTest.php +++ b/tests/dev/CsvBulkLoaderTest.php @@ -55,7 +55,7 @@ class CsvBulkLoaderTest extends SapphireTest { $this->assertEquals(4, $resultDataObject->Count(), 'Test if existing data is deleted before new data is added'); - } + } /** * Test import with manual column mapping diff --git a/tests/forms/FileFieldTest.php b/tests/forms/FileFieldTest.php index 83bf22d57..39017d1e1 100644 --- a/tests/forms/FileFieldTest.php +++ b/tests/forms/FileFieldTest.php @@ -18,11 +18,11 @@ class FileFieldTest extends FunctionalTest { new FieldList() ); $fileFieldValue = array( - 'name' => 'aCV.txt', - 'type' => 'application/octet-stream', - 'tmp_name' => '/private/var/tmp/phpzTQbqP', - 'error' => 0, - 'size' => 3471 + 'name' => 'aCV.txt', + 'type' => 'application/octet-stream', + 'tmp_name' => '/private/var/tmp/phpzTQbqP', + 'error' => 0, + 'size' => 3471 ); $fileField->setValue($fileFieldValue); @@ -46,11 +46,11 @@ class FileFieldTest extends FunctionalTest { ); // All fields are filled but for some reason an error occured when uploading the file => fails $fileFieldValue = array( - 'name' => 'aCV.txt', - 'type' => 'application/octet-stream', - 'tmp_name' => '/private/var/tmp/phpzTQbqP', - 'error' => 1, - 'size' => 3471 + 'name' => 'aCV.txt', + 'type' => 'application/octet-stream', + 'tmp_name' => '/private/var/tmp/phpzTQbqP', + 'error' => 1, + 'size' => 3471 ); $fileField->setValue($fileFieldValue); diff --git a/tests/forms/RequirementsTest.php b/tests/forms/RequirementsTest.php index 597bbff23..9c53e0ec9 100644 --- a/tests/forms/RequirementsTest.php +++ b/tests/forms/RequirementsTest.php @@ -368,7 +368,7 @@ class RequirementsTest extends SapphireTest { ); } } - + public function assertFileNotIncluded($backend, $type, $files) { $type = strtolower($type); switch ($type) { @@ -412,4 +412,4 @@ class RequirementsTest extends SapphireTest { ); } } -} \ No newline at end of file +} diff --git a/tests/forms/TableListFieldTest.php b/tests/forms/TableListFieldTest.php index 161d73e99..740cbd9a5 100644 --- a/tests/forms/TableListFieldTest.php +++ b/tests/forms/TableListFieldTest.php @@ -169,11 +169,11 @@ class TableListFieldTest extends SapphireTest { $ajaxResponse = Director::test($table->Link())->getBody(); // Check that the column headings have been rendered - $this->assertRegExp('/]*>.*Col A.*<\/th>/si', $ajaxResponse); - $this->assertRegExp('/]*>.*Col B.*<\/th>/si', $ajaxResponse); - $this->assertRegExp('/]*>.*Col C.*<\/th>/si', $ajaxResponse); - $this->assertRegExp('/]*>.*Col D.*<\/th>/si', $ajaxResponse); - $this->assertRegExp('/]*>.*Col E.*<\/th>/si', $ajaxResponse); + $this->assertRegExp('/]*>.*Col A.*<\/th>/si', $ajaxResponse); + $this->assertRegExp('/]*>.*Col B.*<\/th>/si', $ajaxResponse); + $this->assertRegExp('/]*>.*Col C.*<\/th>/si', $ajaxResponse); + $this->assertRegExp('/]*>.*Col D.*<\/th>/si', $ajaxResponse); + $this->assertRegExp('/]*>.*Col E.*<\/th>/si', $ajaxResponse); } public function testCsvExport() { @@ -294,19 +294,19 @@ class TableListFieldTest extends SapphireTest { unset($_REQUEST['ctf']); } - /** - * Check that a SS_List can be passed to TableListField - */ + /** + * Check that a SS_List can be passed to TableListField + */ public function testDataObjectSet() { - $one = new TableListFieldTest_Obj; - $one->A = "A-one"; - $two = new TableListFieldTest_Obj; - $two->A = "A-two"; - $three = new TableListFieldTest_Obj; - $three->A = "A-three"; - - $list = new ArrayList(array($one, $two, $three)); - + $one = new TableListFieldTest_Obj; + $one->A = "A-one"; + $two = new TableListFieldTest_Obj; + $two->A = "A-two"; + $three = new TableListFieldTest_Obj; + $three->A = "A-three"; + + $list = new ArrayList(array($one, $two, $three)); + // A TableListField must be inside a form for its links to be generated $form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldList( new TableListField("Tester", $list, array( diff --git a/tests/forms/uploadfield/UploadFieldTest.php b/tests/forms/uploadfield/UploadFieldTest.php index aebb9feb3..2ceb16b17 100644 --- a/tests/forms/uploadfield/UploadFieldTest.php +++ b/tests/forms/uploadfield/UploadFieldTest.php @@ -4,7 +4,7 @@ * @subpackage tests */ - class UploadFieldTest extends FunctionalTest { +class UploadFieldTest extends FunctionalTest { static $fixture_file = 'UploadFieldTest.yml'; diff --git a/tests/injector/testservices/SampleService.php b/tests/injector/testservices/SampleService.php index 738b6afe4..c43ff3110 100644 --- a/tests/injector/testservices/SampleService.php +++ b/tests/injector/testservices/SampleService.php @@ -2,11 +2,11 @@ class SampleService { - public $constructorVarOne; + public $constructorVarOne; public $constructorVarTwo; public function __construct($v1 = null, $v2 = null) { $this->constructorVarOne = $v1; $this->constructorVarTwo = $v2; } -} \ No newline at end of file +} diff --git a/tests/javascript/TreeDropDownField/TreeDropdownField.js b/tests/javascript/TreeDropDownField/TreeDropdownField.js index 05ebd1849..07afd1997 100644 --- a/tests/javascript/TreeDropDownField/TreeDropdownField.js +++ b/tests/javascript/TreeDropDownField/TreeDropdownField.js @@ -49,7 +49,7 @@ '' + '
  • ' ); - }); + }); afterEach(function() { $('#testfield').remove(); @@ -114,7 +114,7 @@ '' + '
    ' ); - }); + }); afterEach(function() { $('#testfield').remove(); @@ -146,7 +146,7 @@ '' + '
    ' ); - }); + }); afterEach(function() { $('#testfield').remove(); @@ -222,7 +222,7 @@ '' + '' ); - }); + }); afterEach(function() { $('#testfield').remove(); @@ -236,4 +236,4 @@ }); }); }); -}(jQuery)); \ No newline at end of file +}(jQuery)); diff --git a/tests/model/ComponentSetTest.php b/tests/model/ComponentSetTest.php index 8a067fec4..074fe2503 100644 --- a/tests/model/ComponentSetTest.php +++ b/tests/model/ComponentSetTest.php @@ -60,7 +60,6 @@ class ComponentSetTest_Player extends Member implements TestOnly { static $belongs_many_many = array( 'Teams' => 'ComponentSetTest_Team' ); - } class ComponentSetTest_Team extends DataObject implements TestOnly { diff --git a/tests/model/DataListTest.php b/tests/model/DataListTest.php index 7f3335f37..f2904be78 100644 --- a/tests/model/DataListTest.php +++ b/tests/model/DataListTest.php @@ -492,7 +492,7 @@ class DataListTest extends SapphireTest { */ public function testExcludeOnFilter() { $list = DataObjectTest_TeamComment::get(); - $list = $list->filter('Comment', 'Phil is a unique guy, and comments on team2'); + $list = $list->filter('Comment', 'Phil is a unique guy, and comments on team2'); $list = $list->exclude('Name', 'Bob'); $this->assertContains( diff --git a/tests/model/DataObjectTest.php b/tests/model/DataObjectTest.php index df4d59e0a..aedfee7e4 100644 --- a/tests/model/DataObjectTest.php +++ b/tests/model/DataObjectTest.php @@ -1143,7 +1143,6 @@ class DataObjectTest_Player extends Member implements TestOnly { static $belongs_many_many = array( 'Teams' => 'DataObjectTest_Team' ); - } class DataObjectTest_Team extends DataObject implements TestOnly { diff --git a/tests/model/MoneyTest.php b/tests/model/MoneyTest.php index 8b479493d..9b8bfc92d 100644 --- a/tests/model/MoneyTest.php +++ b/tests/model/MoneyTest.php @@ -71,12 +71,12 @@ class MoneyTest extends SapphireTest { } /** - * Write a Money object to the database, then re-read it to ensure it - * is re-read properly. - */ - public function testGettingWrittenDataObject() { - $local = i18n::get_locale(); - //make sure that the $ amount is not prefixed by US$, as it would be in non-US locale + * Write a Money object to the database, then re-read it to ensure it + * is re-read properly. + */ + public function testGettingWrittenDataObject() { + $local = i18n::get_locale(); + //make sure that the $ amount is not prefixed by US$, as it would be in non-US locale i18n::set_locale('en_US'); $obj = new MoneyTest_DataObject(); @@ -99,8 +99,8 @@ class MoneyTest extends SapphireTest { "Money field not added to data object properly when read." ); - i18n::set_locale($local); - } + i18n::set_locale($local); + } public function testToCurrency() { $USD = new Money(); diff --git a/tests/model/PaginatedListTest.php b/tests/model/PaginatedListTest.php index d0f7f8540..257212bcb 100644 --- a/tests/model/PaginatedListTest.php +++ b/tests/model/PaginatedListTest.php @@ -43,11 +43,11 @@ class PaginatedListTest extends SapphireTest { public function testSetPaginationFromQuery() { $query = $this->getMock('SQLQuery'); $query->expects($this->once()) - ->method('getLimit') - ->will($this->returnValue(array('limit' => 15, 'start' => 30))); + ->method('getLimit') + ->will($this->returnValue(array('limit' => 15, 'start' => 30))); $query->expects($this->once()) - ->method('unlimitedRowCount') - ->will($this->returnValue(100)); + ->method('unlimitedRowCount') + ->will($this->returnValue(100)); $list = new PaginatedList(new ArrayList()); $list->setPaginationFromQuery($query); diff --git a/tests/model/VersionedTest.php b/tests/model/VersionedTest.php index 34888d53c..6c1aec3b9 100644 --- a/tests/model/VersionedTest.php +++ b/tests/model/VersionedTest.php @@ -254,12 +254,12 @@ class VersionedTest extends SapphireTest { * Test that SQLQuery::queriedTables() applies the version-suffixes properly. */ public function testQueriedTables() { - Versioned::reading_stage('Live'); + Versioned::reading_stage('Live'); - $this->assertEquals(array( - 'VersionedTest_DataObject_Live', - 'VersionedTest_Subclass_Live', - ), DataObject::get('VersionedTest_Subclass')->dataQuery()->query()->queriedTables()); + $this->assertEquals(array( + 'VersionedTest_DataObject_Live', + 'VersionedTest_Subclass_Live', + ), DataObject::get('VersionedTest_Subclass')->dataQuery()->query()->queriedTables()); } public function testGetVersionWhenClassnameChanged() { diff --git a/tests/phpcs/tabs.xml b/tests/phpcs/tabs.xml new file mode 100644 index 000000000..93dc08cb6 --- /dev/null +++ b/tests/phpcs/tabs.xml @@ -0,0 +1,18 @@ + + + CodeSniffer ruleset for SilverStripe indentation conventions. + + + */css/* + css/* + + + thirdparty/* + */jquery-changetracker/* + parsers/HTML/BBCodeParser/* + + + */SSTemplateParser.php$ + + + diff --git a/tests/search/SearchContextTest.php b/tests/search/SearchContextTest.php index f9e9b41e4..f5a9b7ca2 100644 --- a/tests/search/SearchContextTest.php +++ b/tests/search/SearchContextTest.php @@ -41,40 +41,40 @@ class SearchContextTest extends SapphireTest { } public function testPartialMatchUsedByDefaultWhenNotExplicitlySet() { - $person = singleton('SearchContextTest_Person'); - $context = $person->getDefaultSearchContext(); - - $this->assertEquals( - array( - "Name" => new PartialMatchFilter("Name"), - "HairColor" => new PartialMatchFilter("HairColor"), - "EyeColor" => new PartialMatchFilter("EyeColor") - ), - $context->getFilters() - ); + $person = singleton('SearchContextTest_Person'); + $context = $person->getDefaultSearchContext(); + + $this->assertEquals( + array( + "Name" => new PartialMatchFilter("Name"), + "HairColor" => new PartialMatchFilter("HairColor"), + "EyeColor" => new PartialMatchFilter("EyeColor") + ), + $context->getFilters() + ); } public function testDefaultFiltersDefinedWhenNotSetInDataObject() { $book = singleton('SearchContextTest_Book'); $context = $book->getDefaultSearchContext(); - $this->assertEquals( - array( - "Title" => new PartialMatchFilter("Title") - ), - $context->getFilters() - ); + $this->assertEquals( + array( + "Title" => new PartialMatchFilter("Title") + ), + $context->getFilters() + ); } public function testUserDefinedFiltersAppearInSearchContext() { $company = singleton('SearchContextTest_Company'); $context = $company->getDefaultSearchContext(); - + $this->assertEquals( array( "Name" => new PartialMatchFilter("Name"), - "Industry" => new PartialMatchFilter("Industry"), - "AnnualProfit" => new PartialMatchFilter("AnnualProfit") + "Industry" => new PartialMatchFilter("Industry"), + "AnnualProfit" => new PartialMatchFilter("AnnualProfit") ), $context->getFilters() ); @@ -87,8 +87,8 @@ class SearchContextTest extends SapphireTest { $this->assertEquals( new FieldList( new TextField("Name", 'Name'), - new TextareaField("Industry", 'Industry'), - new NumericField("AnnualProfit", 'The Almighty Annual Profit') + new TextareaField("Industry", 'Industry'), + new NumericField("AnnualProfit", 'The Almighty Annual Profit') ), $context->getFields() ); diff --git a/tests/security/GroupTest.php b/tests/security/GroupTest.php index 0299d8d64..e923dad41 100644 --- a/tests/security/GroupTest.php +++ b/tests/security/GroupTest.php @@ -52,47 +52,47 @@ class GroupTest extends FunctionalTest { public function testMemberGroupRelationForm() { Session::set('loggedInAs', $this->idFromFixture('GroupTest_Member', 'admin')); - $adminGroup = $this->objFromFixture('Group', 'admingroup'); - $parentGroup = $this->objFromFixture('Group', 'parentgroup'); - $childGroup = $this->objFromFixture('Group', 'childgroup'); + $adminGroup = $this->objFromFixture('Group', 'admingroup'); + $parentGroup = $this->objFromFixture('Group', 'parentgroup'); + $childGroup = $this->objFromFixture('Group', 'childgroup'); - // Test single group relation through checkboxsetfield - $form = new GroupTest_MemberForm($this, 'Form'); - $member = $this->objFromFixture('GroupTest_Member', 'admin'); - $form->loadDataFrom($member); - $checkboxSetField = $form->Fields()->fieldByName('Groups'); - $checkboxSetField->setValue(array( - $adminGroup->ID => $adminGroup->ID, // keep existing relation - $parentGroup->ID => $parentGroup->ID, // add new relation - )); - $form->saveInto($member); - $updatedGroups = $member->Groups(); + // Test single group relation through checkboxsetfield + $form = new GroupTest_MemberForm($this, 'Form'); + $member = $this->objFromFixture('GroupTest_Member', 'admin'); + $form->loadDataFrom($member); + $checkboxSetField = $form->Fields()->fieldByName('Groups'); + $checkboxSetField->setValue(array( + $adminGroup->ID => $adminGroup->ID, // keep existing relation + $parentGroup->ID => $parentGroup->ID, // add new relation + )); + $form->saveInto($member); + $updatedGroups = $member->Groups(); - $this->assertEquals( + $this->assertEquals( array($adminGroup->ID, $parentGroup->ID), - $updatedGroups->column(), - "Adding a toplevel group works" - ); + $updatedGroups->column(), + "Adding a toplevel group works" + ); - // Test unsetting relationship - $form->loadDataFrom($member); - $checkboxSetField = $form->Fields()->fieldByName('Groups'); - $checkboxSetField->setValue(array( - $adminGroup->ID => $adminGroup->ID, // keep existing relation - //$parentGroup->ID => $parentGroup->ID, // remove previously set relation - )); - $form->saveInto($member); - $member->flushCache(); - $updatedGroups = $member->Groups(); - $this->assertEquals( + // Test unsetting relationship + $form->loadDataFrom($member); + $checkboxSetField = $form->Fields()->fieldByName('Groups'); + $checkboxSetField->setValue(array( + $adminGroup->ID => $adminGroup->ID, // keep existing relation + //$parentGroup->ID => $parentGroup->ID, // remove previously set relation + )); + $form->saveInto($member); + $member->flushCache(); + $updatedGroups = $member->Groups(); + $this->assertEquals( array($adminGroup->ID), - $updatedGroups->column(), - "Removing a previously added toplevel group works" - ); + $updatedGroups->column(), + "Removing a previously added toplevel group works" + ); - // Test adding child group + // Test adding child group - } + } public function testCollateAncestorIDs() { $parentGroup = $this->objFromFixture('Group', 'parentgroup'); @@ -139,37 +139,37 @@ class GroupTest extends FunctionalTest { } class GroupTest_Member extends Member implements TestOnly { - - public function getCMSFields() { - $groups = DataObject::get('Group'); - $groupsMap = ($groups) ? $groups->map() : false; - $fields = new FieldList( - new HiddenField('ID', 'ID'), - new CheckboxSetField( - 'Groups', - 'Groups', - $groupsMap - ) - ); - - return $fields; - } - + + public function getCMSFields() { + $groups = DataObject::get('Group'); + $groupsMap = ($groups) ? $groups->map() : false; + $fields = new FieldList( + new HiddenField('ID', 'ID'), + new CheckboxSetField( + 'Groups', + 'Groups', + $groupsMap + ) + ); + + return $fields; + } + } class GroupTest_MemberForm extends Form { - - public function __construct($controller, $name) { - $fields = singleton('GroupTest_Member')->getCMSFields(); - $actions = new FieldList( - new FormAction('doSave','save') - ); - - parent::__construct($controller, $name, $fields, $actions); - } - - public function doSave($data, $form) { - // done in testing methods - } - + + public function __construct($controller, $name) { + $fields = singleton('GroupTest_Member')->getCMSFields(); + $actions = new FieldList( + new FormAction('doSave','save') + ); + + parent::__construct($controller, $name, $fields, $actions); + } + + public function doSave($data, $form) { + // done in testing methods + } + } diff --git a/tests/security/MemberTest.php b/tests/security/MemberTest.php index 94e94d3bf..4879fa764 100644 --- a/tests/security/MemberTest.php +++ b/tests/security/MemberTest.php @@ -29,8 +29,8 @@ class MemberTest extends FunctionalTest { } public function __destruct() { - i18n::set_default_locale($this->local); - } + i18n::set_default_locale($this->local); + } public function setUp() { parent::setUp(); diff --git a/tests/view/ViewableDataTest.php b/tests/view/ViewableDataTest.php index 93dcf45c0..40d770e02 100644 --- a/tests/view/ViewableDataTest.php +++ b/tests/view/ViewableDataTest.php @@ -155,9 +155,9 @@ class ViewableDataTest_Castable extends ViewableData { return $this->unsafeXML(); } - public function forTemplate() { - return 'castable'; - } + public function forTemplate() { + return 'castable'; + } } class ViewableDataTest_RequiresCasting extends ViewableData { diff --git a/view/ArrayData.php b/view/ArrayData.php index 549a953b5..09b691b02 100644 --- a/view/ArrayData.php +++ b/view/ArrayData.php @@ -65,7 +65,7 @@ class ArrayData extends ViewableData { return new ArrayData($value); } elseif (ArrayLib::is_associative($value)) { return new ArrayData($value); - } else { + } else { return $value; } } diff --git a/view/Requirements.php b/view/Requirements.php index 84cdbfd76..1d10336c9 100644 --- a/view/Requirements.php +++ b/view/Requirements.php @@ -22,7 +22,7 @@ class Requirements { * @return boolean */ public static function get_combined_files_enabled() { - return self::backend()->get_combined_files_enabled(); + return self::backend()->get_combined_files_enabled(); } /** @@ -656,7 +656,7 @@ class Requirements_Backend { $jsRequirements = ''; // Combine files - updates $this->javascript and $this->css - $this->process_combined_files(); + $this->process_combined_files(); foreach(array_diff_key($this->javascript,$this->blocked) as $file => $dummy) { $path = $this->path_for_file($file); @@ -1023,9 +1023,9 @@ class Requirements_Backend { // file exists, check modification date of every contained file $srcLastMod = 0; foreach($fileList as $file) { - if(file_exists($base . $file)) { - $srcLastMod = max(filemtime($base . $file), $srcLastMod); - } + if(file_exists($base . $file)) { + $srcLastMod = max(filemtime($base . $file), $srcLastMod); + } } $refresh = $srcLastMod > filemtime($combinedFilePath); } else { @@ -1071,9 +1071,9 @@ class Requirements_Backend { // method repeatedly - it will behave different on the second call! $this->javascript = $newJSRequirements; $this->css = $newCSSRequirements; - } - - public function get_custom_scripts() { + } + + public function get_custom_scripts() { $requirements = ""; if($this->customScript) { diff --git a/view/SSTemplateParser.php.inc b/view/SSTemplateParser.php.inc index 77635863a..7652ed105 100644 --- a/view/SSTemplateParser.php.inc +++ b/view/SSTemplateParser.php.inc @@ -453,7 +453,7 @@ class SSTemplateParser extends Parser { function Require_Call(&$res, $sub) { $res['php'] = "Requirements::".$sub['Method']['text'].'('.$sub['CallArguments']['php'].');'; } - + /*!* @@ -617,7 +617,7 @@ class SSTemplateParser extends Parser { function OldTTag_OldTPart(&$res, $sub) { $res['php'] = $sub['php']; } - + /*!* # This is the old <% sprintf(_t()) %> tag diff --git a/view/SSViewer.php b/view/SSViewer.php index d50e08947..b3a315122 100644 --- a/view/SSViewer.php +++ b/view/SSViewer.php @@ -664,10 +664,10 @@ class SSViewer { } if(!$this->chosenTemplates) { - $templateList = (is_array($templateList)) ? $templateList : array($templateList); - - user_error("None of these templates can be found in theme '" - . self::current_theme() . "': ". implode(".ss, ", $templateList) . ".ss", E_USER_WARNING); + $templateList = (is_array($templateList)) ? $templateList : array($templateList); + + user_error("None of these templates can be found in theme '" + . self::current_theme() . "': ". implode(".ss, ", $templateList) . ".ss", E_USER_WARNING); } } @@ -717,7 +717,7 @@ class SSViewer { protected static $options = array( 'rewriteHashlinks' => true, ); - + protected static $topLevel = array(); public static function topLevel() { From 4f63f91cc8168affdccfb64051eac6fcd2078a3b Mon Sep 17 00:00:00 2001 From: Marcus Nyeholt Date: Fri, 7 Dec 2012 13:16:33 +1100 Subject: [PATCH 03/49] BUG Fixed issue with convertServiceProperty Fixed issue where convertServiceProperty is called when creating objects with user-supplied constructor arguments, so that it's only called when creating objects using injector configuration. This reduces the overhead of unnecessary calls to convertServiceProperty. Updated test cases to validate behaviour --- control/injector/Injector.php | 35 ++++++++++--- tests/injector/InjectorTest.php | 92 +++++++++++++++++++++++++++------ 2 files changed, 106 insertions(+), 21 deletions(-) diff --git a/control/injector/Injector.php b/control/injector/Injector.php index e5a776fcc..cfe06843e 100644 --- a/control/injector/Injector.php +++ b/control/injector/Injector.php @@ -362,6 +362,7 @@ class Injector { // EXCEPT when there's already an existing instance at this id. // if so, we need to instantiate and replace immediately if (isset($this->serviceCache[$id])) { + $this->updateSpecConstructor($spec); $this->instantiate($spec, $id); } } @@ -403,6 +404,20 @@ class Injector { } } } + + /** + * Update a class specification to convert constructor configuration information if needed + * + * We do this as a separate process to avoid unneeded calls to convertServiceProperty + * + * @param array $spec + * The class specification to update + */ + protected function updateSpecConstructor(&$spec) { + if (isset($spec['constructor'])) { + $spec['constructor'] = $this->convertServiceProperty($spec['constructor']); + } + } /** * Recursively convert a value into its proper representation with service references @@ -468,7 +483,7 @@ class Injector { $constructorParams = $spec['constructor']; } - $object = $this->objectCreator->create($this, $class, $constructorParams); + $object = $this->objectCreator->create($class, $constructorParams); // figure out if we have a specific id set or not. In some cases, we might be instantiating objects // that we don't manage directly; we don't want to store these in the service cache below @@ -730,15 +745,22 @@ class Injector { // we don't want to return the singleton version of it. $spec = $this->specs[$serviceName]; $type = isset($spec['type']) ? $spec['type'] : null; - + // if we're explicitly a prototype OR we're not wanting a singleton if (($type && $type == 'prototype') || !$asSingleton) { if ($spec && $constructorArgs) { $spec['constructor'] = $constructorArgs; + } else { + // convert any _configured_ constructor args. + // we don't call this for get() calls where someone passes in + // constructor args, otherwise we end up calling convertServiceParams + // way too often + $this->updateSpecConstructor($spec); } return $this->instantiate($spec, $serviceName, !$type ? 'prototype' : $type); } else { if (!isset($this->serviceCache[$serviceName])) { + $this->updateSpecConstructor($spec); $this->instantiate($spec, $serviceName); } return $this->serviceCache[$serviceName]; @@ -750,6 +772,7 @@ class Injector { $this->load(array($name => $config)); if (isset($this->specs[$name])) { $spec = $this->specs[$name]; + $this->updateSpecConstructor($spec); return $this->instantiate($spec, $name); } } @@ -816,10 +839,10 @@ class InjectionCreator { * @param array $params * An array of parameters to be passed to the constructor */ - public function create(Injector $injector, $class, $params = array()) { + public function create($class, $params = array()) { $reflector = new ReflectionClass($class); if (count($params)) { - return $reflector->newInstanceArgs($injector->convertServiceProperty($params)); + return $reflector->newInstanceArgs($params); } return $reflector->newInstance(); } @@ -833,10 +856,10 @@ class SilverStripeInjectionCreator { * @param array $params * An array of parameters to be passed to the constructor */ - public function create(Injector $injector, $class, $params = array()) { + public function create($class, $params = array()) { $class = Object::getCustomClass($class); $reflector = new ReflectionClass($class); - return $reflector->newInstanceArgs($injector->convertServiceProperty($params)); + return $reflector->newInstanceArgs($params); } } diff --git a/tests/injector/InjectorTest.php b/tests/injector/InjectorTest.php index d5cd5cfe6..297ba3af2 100644 --- a/tests/injector/InjectorTest.php +++ b/tests/injector/InjectorTest.php @@ -442,7 +442,7 @@ class InjectorTest extends SapphireTest { public function testCustomObjectCreator() { $injector = new Injector(); - $injector->setObjectCreator(new SSObjectCreator()); + $injector->setObjectCreator(new SSObjectCreator($injector)); $config = array( 'OriginalRequirementsBackend', 'DummyRequirements' => array( @@ -485,9 +485,65 @@ class InjectorTest extends SapphireTest { $again = $injector->get('NeedsBothCirculars'); $this->assertEquals($again->var, 'One'); } + + public function testConvertServicePropertyOnCreate() { + // make sure convert service property is not called on direct calls to create, only on configured + // declarations to avoid un-needed function calls + $injector = new Injector(); + $item = $injector->create('ConstructableObject', '%$TestObject'); + $this->assertEquals('%$TestObject', $item->property); + + // do it again but have test object configured as a constructor dependency + $injector = new Injector(); + $config = array( + 'ConstructableObject' => array( + 'constructor' => array( + '%$TestObject' + ) + ) + ); + + $injector->load($config); + $item = $injector->get('ConstructableObject'); + $this->assertTrue($item->property instanceof TestObject); + + // and with a configured object defining TestObject to be something else! + $injector = new Injector(array('locator' => 'InjectorTestConfigLocator')); + $config = array( + 'ConstructableObject' => array( + 'constructor' => array( + '%$TestObject' + ) + ), + ); + + $injector->load($config); + $item = $injector->get('ConstructableObject'); + $this->assertTrue($item->property instanceof ConstructableObject); + + $this->assertInstanceOf('OtherTestObject', $item->property->property); + } } -class TestObject { +class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator implements TestOnly { + public function locateConfigFor($name) { + if ($name == 'TestObject') { + return array('class' => 'ConstructableObject', 'constructor' => array('%$OtherTestObject')); + } + + return parent::locateConfigFor($name); + } +} + +class ConstructableObject implements TestOnly { + public $property; + + public function __construct($prop) { + $this->property = $prop; + } +} + +class TestObject implements TestOnly { public $sampleService; @@ -497,7 +553,7 @@ class TestObject { } -class OtherTestObject { +class OtherTestObject implements TestOnly { private $sampleService; @@ -511,13 +567,13 @@ class OtherTestObject { } -class CircularOne { +class CircularOne implements TestOnly { public $circularTwo; } -class CircularTwo { +class CircularTwo implements TestOnly { public $circularOne; @@ -528,7 +584,7 @@ class CircularTwo { } } -class NeedsBothCirculars { +class NeedsBothCirculars implements TestOnly{ public $circularOne; public $circularTwo; @@ -536,15 +592,15 @@ class NeedsBothCirculars { } -class MyParentClass { +class MyParentClass implements TestOnly { public $one; } -class MyChildClass extends MyParentClass { +class MyChildClass extends MyParentClass implements TestOnly { } -class DummyRequirements { +class DummyRequirements implements TestOnly { public $backend; @@ -558,15 +614,15 @@ class DummyRequirements { } -class OriginalRequirementsBackend { +class OriginalRequirementsBackend implements TestOnly { } -class NewRequirementsBackend { +class NewRequirementsBackend implements TestOnly { } -class TestStaticInjections { +class TestStaticInjections implements TestOnly { public $backend; static $dependencies = array( @@ -582,13 +638,19 @@ class TestStaticInjections { * @see https://github.com/silverstripe/sapphire */ class SSObjectCreator extends InjectionCreator { + private $injector; + + public function __construct($injector) { + $this->injector = $injector; + } - public function create(Injector $injector, $class, $params = array()) { + public function create($class, $params = array()) { if (strpos($class, '(') === false) { - return parent::create($injector, $class, $params); + return parent::create($class, $params); } else { list($class, $params) = self::parse_class_spec($class); - return parent::create($injector, $class, $params); + $params = $this->injector->convertServiceProperty($params); + return parent::create($class, $params); } } From b0121b541c0d2595e8b1c05bdfe3333ad4251b07 Mon Sep 17 00:00:00 2001 From: Simon Welsh Date: Sun, 9 Dec 2012 00:20:20 +1300 Subject: [PATCH 04/49] Add codesniffer that ensures indentation is with tabs. --- .travis.yml | 3 +- admin/code/CMSProfileController.php | 18 +- admin/code/ModelAdmin.php | 2 +- admin/javascript/LeftAndMain.Tree.js | 4 +- admin/javascript/LeftAndMain.js | 2 +- admin/javascript/lib.js | 6 +- api/FormEncodedDataFormatter.php | 6 +- api/RSSFeed.php | 8 +- api/RestfulService.php | 6 +- cli-script.php | 28 +- conf/ConfigureFromEnv.php | 4 +- control/ContentNegotiator.php | 4 +- control/Controller.php | 4 +- control/Director.php | 22 +- control/HTTP.php | 4 +- control/HTTPRequest.php | 48 +- control/HTTPResponse.php | 48 +- control/RequestHandler.php | 2 +- control/injector/AopProxyService.php | 4 +- core/ArrayLib.php | 36 +- core/Diff.php | 872 +++++----- dev/BulkLoader.php | 9 +- dev/DevelopmentAdmin.php | 20 +- dev/FunctionalTest.php | 24 +- dev/Profiler.php | 394 ++--- dev/SapphireTestReporter.php | 34 +- dev/SilverStripeListener.php | 4 +- dev/TeamCityListener.php | 4 +- dev/TestListener.php | 12 +- dev/phpunit/PhpUnitWrapper.php | 16 +- dev/phpunit/PhpUnitWrapper_3_5.php | 6 +- email/Email.php | 12 +- email/Mailer.php | 28 +- filesystem/GD.php | 96 +- forms/AjaxUniqueTextField.php | 2 +- forms/CheckboxSetField.php | 2 +- forms/CompositeField.php | 4 +- forms/CreditCardField.php | 2 +- forms/DateField.php | 96 +- forms/FieldGroup.php | 14 +- forms/Form.php | 8 +- forms/FormField.php | 18 +- forms/HtmlEditorField.php | 10 +- forms/NumericField.php | 4 +- forms/PhoneNumberField.php | 12 +- forms/RequiredFields.php | 14 +- forms/gridfield/GridField.php | 2 +- javascript/GridField.js | 1 - javascript/HtmlEditorField.js | 10 +- javascript/i18n.js | 4 +- javascript/jquery-ondemand/jquery.ondemand.js | 4 +- javascript/tree/tree.js | 46 +- model/DataList.php | 32 +- model/DataObject.php | 156 +- model/DataQuery.php | 138 +- model/Database.php | 6 +- model/DatabaseAdmin.php | 4 +- model/HasManyList.php | 16 +- model/Hierarchy.php | 26 +- model/ManyManyList.php | 22 +- model/MySQLDatabase.php | 44 +- model/SQLQuery.php | 10 +- model/Transliterator.php | 2 +- model/Versioned.php | 8 +- model/fieldtypes/Date.php | 10 +- model/fieldtypes/Text.php | 2 +- model/fieldtypes/Varchar.php | 8 +- parsers/BBCodeParser.php | 4 +- parsers/HTML/HTMLBBCodeParser.php | 1517 ++++++++--------- search/SearchContext.php | 8 +- security/Authenticator.php | 276 +-- security/Group.php | 6 +- security/Member.php | 42 +- security/MemberAuthenticator.php | 216 +-- security/MemberLoginForm.php | 16 +- security/Permission.php | 4 +- security/PermissionCheckboxSetField.php | 4 +- security/Security.php | 62 +- tests/api/RestfulServiceTest.php | 6 +- .../features/bootstrap/FeatureContext.php | 36 +- .../Test/Behaviour/CmsFormsContext.php | 144 +- .../Framework/Test/Behaviour/CmsUiContext.php | 636 +++---- tests/bootstrap.php | 26 +- tests/control/HTTPTest.php | 12 +- tests/core/ArrayDataTest.php | 2 +- .../module/classes/ClassB.php | 2 +- tests/dev/CsvBulkLoaderTest.php | 2 +- tests/forms/FileFieldTest.php | 20 +- tests/forms/RequirementsTest.php | 4 +- tests/forms/uploadfield/UploadFieldTest.php | 2 +- tests/injector/testservices/SampleService.php | 4 +- .../TreeDropDownField/TreeDropdownField.js | 10 +- tests/model/ComponentSetTest.php | 1 - tests/model/DataListTest.php | 4 +- tests/model/DataObjectTest.php | 1 - tests/model/MoneyTest.php | 16 +- tests/model/PaginatedListTest.php | 8 +- tests/model/VersionedTest.php | 10 +- tests/phpcs/tabs.xml | 18 + tests/search/SearchContextTest.php | 44 +- tests/security/GroupTest.php | 128 +- tests/security/MemberTest.php | 4 +- tests/view/ViewableDataTest.php | 6 +- view/ArrayData.php | 2 +- view/Requirements.php | 16 +- view/SSTemplateParser.php.inc | 4 +- view/SSViewer.php | 10 +- 107 files changed, 2937 insertions(+), 2923 deletions(-) create mode 100644 tests/phpcs/tabs.xml diff --git a/.travis.yml b/.travis.yml index d2d94d077..1f49fe992 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,8 @@ before_script: script: - phpunit -c phpunit.xml.dist - - phpcs --encoding=utf-8 --tab-width=4 --standard=framework/tests/phpcs -np framework + - phpcs --encoding=utf-8 --tab-width=4 --standard=framework/tests/phpcs/ruleset.xml -np framework + - phpcs --encoding=utf-8 --standard=framework/tests/phpcs/tabs.xml -np framework branches: except: diff --git a/admin/code/CMSProfileController.php b/admin/code/CMSProfileController.php index 734624780..7eb7ee9df 100644 --- a/admin/code/CMSProfileController.php +++ b/admin/code/CMSProfileController.php @@ -26,15 +26,15 @@ class CMSProfileController extends LeftAndMain { $form->Fields()->push(new HiddenField('ID', null, Member::currentUserID())); $form->Actions()->push( FormAction::create('save',_t('CMSMain.SAVE', 'Save')) - ->addExtraClass('ss-ui-button ss-ui-action-constructive') - ->setAttribute('data-icon', 'accept') - ->setUseButtonTag(true) - ); - $form->Actions()->removeByName('delete'); - $form->setValidator(new Member_Validator()); - $form->setTemplate('Form'); - $form->setAttribute('data-pjax-fragment', null); - if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet'); + ->addExtraClass('ss-ui-button ss-ui-action-constructive') + ->setAttribute('data-icon', 'accept') + ->setUseButtonTag(true) + ); + $form->Actions()->removeByName('delete'); + $form->setValidator(new Member_Validator()); + $form->setTemplate('Form'); + $form->setAttribute('data-pjax-fragment', null); + if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet'); $form->addExtraClass('member-profile-form root-form cms-edit-form cms-panel-padded center'); return $form; diff --git a/admin/code/ModelAdmin.php b/admin/code/ModelAdmin.php index 15accc263..353e2f9c4 100644 --- a/admin/code/ModelAdmin.php +++ b/admin/code/ModelAdmin.php @@ -296,7 +296,7 @@ abstract class ModelAdmin extends LeftAndMain { * * @return array Map of model class names to importer instances */ - public function getModelImporters() { + public function getModelImporters() { $importerClasses = $this->stat('model_importers'); // fallback to all defined models if not explicitly defined diff --git a/admin/javascript/LeftAndMain.Tree.js b/admin/javascript/LeftAndMain.Tree.js index 97fa80bee..67780c50b 100644 --- a/admin/javascript/LeftAndMain.Tree.js +++ b/admin/javascript/LeftAndMain.Tree.js @@ -153,8 +153,8 @@ "select_limit" : 1, 'initially_select': [this.find('.current').attr('id')] }, - "crrm": { - 'move': { + "crrm": { + 'move': { // Check if a node is allowed to be moved. // Caution: Runs on every drag over a new node 'check_move': function(data) { diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index 5c0e885b1..dbfe0270c 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -301,7 +301,7 @@ jQuery.noConflict(); */ submitForm: function(form, button, callback, ajaxOptions) { var self = this; - + // look for save button if(!button) button = this.find('.Actions :submit[name=action_save]'); // default to first button if none given - simulates browser behaviour diff --git a/admin/javascript/lib.js b/admin/javascript/lib.js index c6437cc7e..d4b989610 100644 --- a/admin/javascript/lib.js +++ b/admin/javascript/lib.js @@ -170,8 +170,8 @@ convertUrlToDataUrl: function( absUrl ) { var u = path.parseUrl( absUrl ); if ( path.isEmbeddedPage( u ) ) { - // For embedded pages, remove the dialog hash key as in getFilePath(), - // otherwise the Data Url won't match the id of the embedded Page. + // For embedded pages, remove the dialog hash key as in getFilePath(), + // otherwise the Data Url won't match the id of the embedded Page. return u.hash.split( dialogHashKey )[0].replace( /^#/, "" ); } else if ( path.isSameDomain( u, documentBase ) ) { return u.hrefNoHash.replace( documentBase.domain, "" ); @@ -232,4 +232,4 @@ }; $.path = path; -}(jQuery)); \ No newline at end of file +}(jQuery)); diff --git a/api/FormEncodedDataFormatter.php b/api/FormEncodedDataFormatter.php index 4db470227..f59d22f23 100644 --- a/api/FormEncodedDataFormatter.php +++ b/api/FormEncodedDataFormatter.php @@ -30,9 +30,9 @@ class FormEncodedDataFormatter extends XMLDataFormatter { } public function convertStringToArray($strData) { - $postArray = array(); - parse_str($strData, $postArray); - return $postArray; + $postArray = array(); + parse_str($strData, $postArray); + return $postArray; //TODO: It would be nice to implement this function in Convert.php //return Convert::querystr2array($strData); } diff --git a/api/RSSFeed.php b/api/RSSFeed.php index c565310ec..163a93e2f 100644 --- a/api/RSSFeed.php +++ b/api/RSSFeed.php @@ -106,9 +106,9 @@ class RSSFeed extends ViewableData { * every time the representation does */ public function __construct(SS_List $entries, $link, $title, - $description = null, $titleField = "Title", - $descriptionField = "Content", $authorField = null, - $lastModified = null, $etag = null) { + $description = null, $titleField = "Title", + $descriptionField = "Content", $authorField = null, + $lastModified = null, $etag = null) { $this->entries = $entries; $this->link = $link; $this->description = $description; @@ -269,7 +269,7 @@ class RSSFeed_Entry extends ViewableData { * Create a new RSSFeed entry. */ public function __construct($entry, $titleField, $descriptionField, - $authorField) { + $authorField) { $this->failover = $entry; $this->titleField = $titleField; $this->descriptionField = $descriptionField; diff --git a/api/RestfulService.php b/api/RestfulService.php index 569026ec6..8a333afec 100644 --- a/api/RestfulService.php +++ b/api/RestfulService.php @@ -65,7 +65,7 @@ class RestfulService extends ViewableData { * @param string $password The proxy auth password * @param boolean $socks Set true to use socks5 proxy instead of http */ - public function setProxy($proxy, $port = 80, $user = "", $password = "", $socks = false) { + public function setProxy($proxy, $port = 80, $user = "", $password = "", $socks = false) { $this->proxy = array( CURLOPT_PROXY => $proxy, CURLOPT_PROXYUSERPWD => "{$user}:{$password}", @@ -337,14 +337,14 @@ class RestfulService extends ViewableData { $child_count++; $k = ($parent == "") ? (string)$key : $parent . "_" . (string)$key; if($this->getRecurseValues($value,$data,$k) == 0){ // no childern, aka "leaf node" - $conv_value = Convert::raw2xml($value); + $conv_value = Convert::raw2xml($value); } //Review the fix for similar node names overriding it's predecessor if(array_key_exists($k, $data) == true) { $data[$k] = $data[$k] . ",". $conv_value; } else { - $data[$k] = $conv_value; + $data[$k] = $conv_value; } diff --git a/cli-script.php b/cli-script.php index 93e009c49..131060785 100755 --- a/cli-script.php +++ b/cli-script.php @@ -37,19 +37,19 @@ chdir(dirname($_SERVER['SCRIPT_FILENAME'])); * fourth => val */ if(isset($_SERVER['argv'][2])) { - $args = array_slice($_SERVER['argv'],2); - if(!isset($_GET)) $_GET = array(); - if(!isset($_REQUEST)) $_REQUEST = array(); - foreach($args as $arg) { - if(strpos($arg,'=') == false) { - $_GET['args'][] = $arg; - } else { - $newItems = array(); - parse_str( (substr($arg,0,2) == '--') ? substr($arg,2) : $arg, $newItems ); - $_GET = array_merge($_GET, $newItems); - } - } - $_REQUEST = array_merge($_REQUEST, $_GET); + $args = array_slice($_SERVER['argv'],2); + if(!isset($_GET)) $_GET = array(); + if(!isset($_REQUEST)) $_REQUEST = array(); + foreach($args as $arg) { + if(strpos($arg,'=') == false) { + $_GET['args'][] = $arg; + } else { + $newItems = array(); + parse_str( (substr($arg,0,2) == '--') ? substr($arg,2) : $arg, $newItems ); + $_GET = array_merge($_GET, $newItems); + } + } + $_REQUEST = array_merge($_REQUEST, $_GET); } // Set 'url' GET parameter @@ -76,7 +76,7 @@ DB::connect($databaseConfig); $url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : null; if(!$url) { echo 'Please specify an argument to cli-script.php/sake. For more information, visit' - . ' http://doc.silverstripe.org/framework/en/topics/commandline'; + . ' http://doc.silverstripe.org/framework/en/topics/commandline'; die(); } diff --git a/conf/ConfigureFromEnv.php b/conf/ConfigureFromEnv.php index f07e39fa7..a994f2880 100644 --- a/conf/ConfigureFromEnv.php +++ b/conf/ConfigureFromEnv.php @@ -100,8 +100,8 @@ if(defined('SS_DATABASE_USERNAME') && defined('SS_DATABASE_PASSWORD')) { } // For schema enabled drivers: - if(defined('SS_DATABASE_SCHEMA')) - $databaseConfig["schema"] = SS_DATABASE_SCHEMA; + if(defined('SS_DATABASE_SCHEMA')) + $databaseConfig["schema"] = SS_DATABASE_SCHEMA; } if(defined('SS_SEND_ALL_EMAILS_TO')) { diff --git a/control/ContentNegotiator.php b/control/ContentNegotiator.php index 515489777..f877b9845 100644 --- a/control/ContentNegotiator.php +++ b/control/ContentNegotiator.php @@ -45,7 +45,7 @@ class ContentNegotiator { * that need to specify the character set make use of this function. */ public static function get_encoding() { - return self::$encoding; + return self::$encoding; } /** @@ -96,7 +96,7 @@ class ContentNegotiator { } else { // The W3C validator doesn't send an HTTP_ACCEPT header, but it can support xhtml. We put this special // case in here so that designers don't get worried that their templates are HTML4. - if(isset($_SERVER['HTTP_USER_AGENT']) && substr($_SERVER['HTTP_USER_AGENT'], 0, 14) == 'W3C_Validator/') { + if(isset($_SERVER['HTTP_USER_AGENT']) && substr($_SERVER['HTTP_USER_AGENT'], 0, 14) == 'W3C_Validator/') { $chosenFormat = "xhtml"; } else { diff --git a/control/Controller.php b/control/Controller.php index 9ffea9ff0..b0e8727e7 100644 --- a/control/Controller.php +++ b/control/Controller.php @@ -164,7 +164,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider { Debug::message("Request handler $body->class object to $this->class controller;" . "rendering with template returned by $body->class::getViewer()"); } - $body = $body->getViewer($request->latestParam('Action'))->process($body); + $body = $body->getViewer($request->latestParam('Action'))->process($body); } $this->response->setBody($body); @@ -367,7 +367,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider { return $template->process($obj); } - + /** * Call this to disable site-wide basic authentication for a specific contoller. * This must be called before Controller::init(). That is, you must call it in your controller's diff --git a/control/Director.php b/control/Director.php index da566fdac..ea1703a26 100644 --- a/control/Director.php +++ b/control/Director.php @@ -348,8 +348,8 @@ class Director implements TemplateGlobalProvider { $url = dirname($_SERVER['REQUEST_URI'] . 'x') . '/' . $url; } - if(substr($url,0,4) != "http") { - if($url[0] != "/") $url = Director::baseURL() . $url; + if(substr($url,0,4) != "http") { + if($url[0] != "/") $url = Director::baseURL() . $url; // Sometimes baseURL() can return a full URL instead of just a path if(substr($url,0,4) != "http") $url = self::protocolAndHost() . $url; } @@ -630,21 +630,21 @@ class Director implements TemplateGlobalProvider { /** * Returns the Absolute URL of the site root. */ - public static function absoluteBaseURL() { - return Director::absoluteURL(Director::baseURL()); - } - + public static function absoluteBaseURL() { + return Director::absoluteURL(Director::baseURL()); + } + /** * Returns the Absolute URL of the site root, embedding the current basic-auth credentials into the URL. */ - public static function absoluteBaseURLWithAuth() { + public static function absoluteBaseURLWithAuth() { $s = ""; $login = ""; - if(isset($_SERVER['PHP_AUTH_USER'])) $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@"; + if(isset($_SERVER['PHP_AUTH_USER'])) $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@"; - return Director::protocol() . $login . $_SERVER['HTTP_HOST'] . Director::baseURL(); - } + return Director::protocol() . $login . $_SERVER['HTTP_HOST'] . Director::baseURL(); + } /** * Force the site to run on SSL. @@ -843,7 +843,7 @@ class Director implements TemplateGlobalProvider { $result = $_GET['isDev']; } else { if($firstTimeCheckingGetVar && DB::connection_attempted()) { - echo "

    Sorry, you can't use ?isDev=1 until your Member and Group tables database are available. Perhaps your database connection is failing?

    "; diff --git a/control/HTTP.php b/control/HTTP.php index 242e31a5c..71a4d6362 100644 --- a/control/HTTP.php +++ b/control/HTTP.php @@ -171,8 +171,8 @@ class HTTP { if($regexes) foreach($regexes as $regex) { if(preg_match_all($regex, $content, $matches)) { $result = array_merge_recursive($result, (isset($matches[2]) ? $matches[2] : $matches[1])); - } - } + } + } return count($result) ? $result : null; } diff --git a/control/HTTPRequest.php b/control/HTTPRequest.php index f6de9a251..89a883058 100644 --- a/control/HTTPRequest.php +++ b/control/HTTPRequest.php @@ -296,21 +296,21 @@ class SS_HTTPRequest implements ArrayAccess { public function getURL($includeGetVars = false) { $url = ($this->getExtension()) ? $this->url . '.' . $this->getExtension() : $this->url; - if ($includeGetVars) { - // if we don't unset $vars['url'] we end up with /my/url?url=my/url&foo=bar etc - - $vars = $this->getVars(); - unset($vars['url']); + if ($includeGetVars) { + // if we don't unset $vars['url'] we end up with /my/url?url=my/url&foo=bar etc + + $vars = $this->getVars(); + unset($vars['url']); - if (count($vars)) { - $url .= '?' . http_build_query($vars); - } - } - else if(strpos($url, "?") !== false) { - $url = substr($url, 0, strpos($url, "?")); - } + if (count($vars)) { + $url .= '?' . http_build_query($vars); + } + } + else if(strpos($url, "?") !== false) { + $url = substr($url, 0, strpos($url, "?")); + } - return $url; + return $url; } /** @@ -501,9 +501,9 @@ class SS_HTTPRequest implements ArrayAccess { * @return string */ public function shiftAllParams() { - $keys = array_keys($this->allParams); - $values = array_values($this->allParams); - $value = array_shift($values); + $keys = array_keys($this->allParams); + $values = array_values($this->allParams); + $value = array_shift($values); // push additional unparsed URL parts onto the parameter stack if(array_key_exists($this->unshiftedButParsedParts, $this->dirParts)) { @@ -636,10 +636,10 @@ class SS_HTTPRequest implements ArrayAccess { */ public function getIP() { if (!empty($_SERVER['HTTP_CLIENT_IP'])) { - //check ip from share internet + //check ip from share internet return $_SERVER['HTTP_CLIENT_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - //to check ip is pass from proxy + //to check ip is pass from proxy return $_SERVER['HTTP_X_FORWARDED_FOR']; } elseif(isset($_SERVER['REMOTE_ADDR'])) { return $_SERVER['REMOTE_ADDR']; @@ -655,12 +655,12 @@ class SS_HTTPRequest implements ArrayAccess { * @return array */ public function getAcceptMimetypes($includeQuality = false) { - $mimetypes = array(); - $mimetypesWithQuality = explode(',',$this->getHeader('Accept')); - foreach($mimetypesWithQuality as $mimetypeWithQuality) { - $mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality); - } - return $mimetypes; + $mimetypes = array(); + $mimetypesWithQuality = explode(',',$this->getHeader('Accept')); + foreach($mimetypesWithQuality as $mimetypeWithQuality) { + $mimetypes[] = ($includeQuality) ? $mimetypeWithQuality : preg_replace('/;.*/', '', $mimetypeWithQuality); + } + return $mimetypes; } /** diff --git a/control/HTTPResponse.php b/control/HTTPResponse.php index c6bfad172..2dec5b583 100644 --- a/control/HTTPResponse.php +++ b/control/HTTPResponse.php @@ -303,37 +303,37 @@ class SS_HTTPResponse_Exception extends Exception { * $statusDescription will be the HTTP status of the resulting response. * @see SS_HTTPResponse::__construct(); */ - public function __construct($body = null, $statusCode = null, $statusDescription = null) { - if($body instanceof SS_HTTPResponse) { - // statusCode and statusDescription should override whatever is passed in the body - if($statusCode) $body->setStatusCode($statusCode); + public function __construct($body = null, $statusCode = null, $statusDescription = null) { + if($body instanceof SS_HTTPResponse) { + // statusCode and statusDescription should override whatever is passed in the body + if($statusCode) $body->setStatusCode($statusCode); if($statusDescription) $body->setStatusDescription($statusDescription); $this->setResponse($body); - } else { + } else { $response = new SS_HTTPResponse($body, $statusCode, $statusDescription); // Error responses should always be considered plaintext, for security reasons $response->addHeader('Content-Type', 'text/plain'); - $this->setResponse($response); - } - - parent::__construct($this->getResponse()->getBody(), $this->getResponse()->getStatusCode()); - } - - /** - * @return SS_HTTPResponse - */ - public function getResponse() { - return $this->response; - } - - /** - * @param SS_HTTPResponse $response - */ - public function setResponse(SS_HTTPResponse $response) { - $this->response = $response; - } + $this->setResponse($response); + } + + parent::__construct($this->getResponse()->getBody(), $this->getResponse()->getStatusCode()); + } + + /** + * @return SS_HTTPResponse + */ + public function getResponse() { + return $this->response; + } + + /** + * @param SS_HTTPResponse $response + */ + public function setResponse(SS_HTTPResponse $response) { + $this->response = $response; + } } diff --git a/control/RequestHandler.php b/control/RequestHandler.php index e36612f7c..acd9e7ad5 100644 --- a/control/RequestHandler.php +++ b/control/RequestHandler.php @@ -94,7 +94,7 @@ class RequestHandler extends ViewableData { * or by defining $allowed_actions in your {@link Form} class. */ static $allowed_actions = null; - + public function __construct() { $this->brokenOnConstruct = false; diff --git a/control/injector/AopProxyService.php b/control/injector/AopProxyService.php index 2bff1f5a4..aba7fa8a5 100644 --- a/control/injector/AopProxyService.php +++ b/control/injector/AopProxyService.php @@ -36,6 +36,6 @@ class AopProxyService { return $result; } - } + } } -} \ No newline at end of file +} diff --git a/core/ArrayLib.php b/core/ArrayLib.php index 72d59b857..039f0eb70 100644 --- a/core/ArrayLib.php +++ b/core/ArrayLib.php @@ -69,18 +69,18 @@ class ArrayLib { * @todo Improve documentation */ public static function array_values_recursive($arr) { - $lst = array(); - foreach(array_keys($arr) as $k){ - $v = $arr[$k]; - if (is_scalar($v)) { - $lst[] = $v; - } elseif (is_array($v)) { - $lst = array_merge( $lst, - self::array_values_recursive($v) - ); - } - } - return $lst; + $lst = array(); + foreach(array_keys($arr) as $k){ + $v = $arr[$k]; + if (is_scalar($v)) { + $lst[] = $v; + } elseif (is_array($v)) { + $lst = array_merge( $lst, + self::array_values_recursive($v) + ); + } + } + return $lst; } /** @@ -110,12 +110,12 @@ class ArrayLib { */ public static function is_associative($arr) { if(is_array($arr) && ! empty($arr)) { - for($iterator = count($arr) - 1; $iterator; $iterator--) { - if (!array_key_exists($iterator, $arr)) return true; - } - return !array_key_exists(0, $arr); - } - return false; + for($iterator = count($arr) - 1; $iterator; $iterator--) { + if (!array_key_exists($iterator, $arr)) return true; + } + return !array_key_exists(0, $arr); + } + return false; } /** diff --git a/core/Diff.php b/core/Diff.php index 1bfcd07f0..7fd94771a 100644 --- a/core/Diff.php +++ b/core/Diff.php @@ -27,21 +27,21 @@ define('USE_ASSERTS', function_exists('assert')); * @access private */ class _DiffOp { - var $type; - var $orig; - var $final; + var $type; + var $orig; + var $final; - public function reverse() { - trigger_error("pure virtual", E_USER_ERROR); - } + public function reverse() { + trigger_error("pure virtual", E_USER_ERROR); + } - public function norig() { - return $this->orig ? sizeof($this->orig) : 0; - } + public function norig() { + return $this->orig ? sizeof($this->orig) : 0; + } - public function nfinal() { - return $this->final ? sizeof($this->final) : 0; - } + public function nfinal() { + return $this->final ? sizeof($this->final) : 0; + } } /** @@ -50,18 +50,18 @@ class _DiffOp { * @access private */ class _DiffOp_Copy extends _DiffOp { - var $type = 'copy'; + var $type = 'copy'; - public function _DiffOp_Copy ($orig, $final = false) { - if (!is_array($final)) - $final = $orig; - $this->orig = $orig; - $this->final = $final; - } + public function _DiffOp_Copy ($orig, $final = false) { + if (!is_array($final)) + $final = $orig; + $this->orig = $orig; + $this->final = $final; + } - public function reverse() { - return new _DiffOp_Copy($this->final, $this->orig); - } + public function reverse() { + return new _DiffOp_Copy($this->final, $this->orig); + } } /** @@ -70,16 +70,16 @@ class _DiffOp_Copy extends _DiffOp { * @access private */ class _DiffOp_Delete extends _DiffOp { - var $type = 'delete'; + var $type = 'delete'; - public function _DiffOp_Delete ($lines) { - $this->orig = $lines; - $this->final = false; - } + public function _DiffOp_Delete ($lines) { + $this->orig = $lines; + $this->final = false; + } - public function reverse() { - return new _DiffOp_Add($this->orig); - } + public function reverse() { + return new _DiffOp_Add($this->orig); + } } /** @@ -88,16 +88,16 @@ class _DiffOp_Delete extends _DiffOp { * @access private */ class _DiffOp_Add extends _DiffOp { - var $type = 'add'; + var $type = 'add'; - public function _DiffOp_Add ($lines) { - $this->final = $lines; - $this->orig = false; - } + public function _DiffOp_Add ($lines) { + $this->final = $lines; + $this->orig = false; + } - public function reverse() { - return new _DiffOp_Delete($this->final); - } + public function reverse() { + return new _DiffOp_Delete($this->final); + } } /** @@ -106,16 +106,16 @@ class _DiffOp_Add extends _DiffOp { * @access private */ class _DiffOp_Change extends _DiffOp { - var $type = 'change'; + var $type = 'change'; - public function _DiffOp_Change ($orig, $final) { - $this->orig = $orig; - $this->final = $final; - } + public function _DiffOp_Change ($orig, $final) { + $this->orig = $orig; + $this->final = $final; + } - public function reverse() { - return new _DiffOp_Change($this->final, $this->orig); - } + public function reverse() { + return new _DiffOp_Change($this->final, $this->orig); + } } @@ -143,126 +143,126 @@ class _DiffOp_Change extends _DiffOp { */ class _DiffEngine { - public function diff ($from_lines, $to_lines) { - $n_from = sizeof($from_lines); - $n_to = sizeof($to_lines); + public function diff ($from_lines, $to_lines) { + $n_from = sizeof($from_lines); + $n_to = sizeof($to_lines); - $this->xchanged = $this->ychanged = array(); - $this->xv = $this->yv = array(); - $this->xind = $this->yind = array(); - unset($this->seq); - unset($this->in_seq); - unset($this->lcs); + $this->xchanged = $this->ychanged = array(); + $this->xv = $this->yv = array(); + $this->xind = $this->yind = array(); + unset($this->seq); + unset($this->in_seq); + unset($this->lcs); - // Skip leading common lines. - for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { - if ($from_lines[$skip] != $to_lines[$skip]) - break; - $this->xchanged[$skip] = $this->ychanged[$skip] = false; - } - // Skip trailing common lines. - $xi = $n_from; $yi = $n_to; - for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { - if ($from_lines[$xi] != $to_lines[$yi]) - break; - $this->xchanged[$xi] = $this->ychanged[$yi] = false; - } + // Skip leading common lines. + for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { + if ($from_lines[$skip] != $to_lines[$skip]) + break; + $this->xchanged[$skip] = $this->ychanged[$skip] = false; + } + // Skip trailing common lines. + $xi = $n_from; $yi = $n_to; + for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { + if ($from_lines[$xi] != $to_lines[$yi]) + break; + $this->xchanged[$xi] = $this->ychanged[$yi] = false; + } - // Ignore lines which do not exist in both files. - for ($xi = $skip; $xi < $n_from - $endskip; $xi++) - $xhash[$from_lines[$xi]] = 1; - for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { - $line = $to_lines[$yi]; - if ( ($this->ychanged[$yi] = empty($xhash[$line])) ) - continue; - $yhash[$line] = 1; - $this->yv[] = $line; - $this->yind[] = $yi; - } - for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { - $line = $from_lines[$xi]; - if ( ($this->xchanged[$xi] = empty($yhash[$line])) ) - continue; - $this->xv[] = $line; - $this->xind[] = $xi; - } + // Ignore lines which do not exist in both files. + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) + $xhash[$from_lines[$xi]] = 1; + for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { + $line = $to_lines[$yi]; + if ( ($this->ychanged[$yi] = empty($xhash[$line])) ) + continue; + $yhash[$line] = 1; + $this->yv[] = $line; + $this->yind[] = $yi; + } + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { + $line = $from_lines[$xi]; + if ( ($this->xchanged[$xi] = empty($yhash[$line])) ) + continue; + $this->xv[] = $line; + $this->xind[] = $xi; + } - // Find the LCS. - $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv)); + // Find the LCS. + $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv)); - // Merge edits when possible - $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); - $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); + // Merge edits when possible + $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); + $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); - // Compute the edit operations. - $edits = array(); - $xi = $yi = 0; - while ($xi < $n_from || $yi < $n_to) { - USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]); - USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]); + // Compute the edit operations. + $edits = array(); + $xi = $yi = 0; + while ($xi < $n_from || $yi < $n_to) { + USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]); + USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]); - // Skip matching "snake". - $copy = array(); - while ( $xi < $n_from && $yi < $n_to - && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { - $copy[] = $from_lines[$xi++]; - ++$yi; - } - if ($copy) - $edits[] = new _DiffOp_Copy($copy); + // Skip matching "snake". + $copy = array(); + while ( $xi < $n_from && $yi < $n_to + && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { + $copy[] = $from_lines[$xi++]; + ++$yi; + } + if ($copy) + $edits[] = new _DiffOp_Copy($copy); - // Find deletes & adds. - $delete = array(); - while ($xi < $n_from && $this->xchanged[$xi]) - $delete[] = $from_lines[$xi++]; + // Find deletes & adds. + $delete = array(); + while ($xi < $n_from && $this->xchanged[$xi]) + $delete[] = $from_lines[$xi++]; - $add = array(); - while ($yi < $n_to && $this->ychanged[$yi]) - $add[] = $to_lines[$yi++]; + $add = array(); + while ($yi < $n_to && $this->ychanged[$yi]) + $add[] = $to_lines[$yi++]; - if ($delete && $add) - $edits[] = new _DiffOp_Change($delete, $add); - elseif ($delete) - $edits[] = new _DiffOp_Delete($delete); - elseif ($add) - $edits[] = new _DiffOp_Add($add); - } - return $edits; - } + if ($delete && $add) + $edits[] = new _DiffOp_Change($delete, $add); + elseif ($delete) + $edits[] = new _DiffOp_Delete($delete); + elseif ($add) + $edits[] = new _DiffOp_Add($add); + } + return $edits; + } - /* Divide the Largest Common Subsequence (LCS) of the sequences - * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally - * sized segments. - * - * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an - * array of NCHUNKS+1 (X, Y) indexes giving the diving points between - * sub sequences. The first sub-sequence is contained in [X0, X1), - * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note - * that (X0, Y0) == (XOFF, YOFF) and - * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). - * - * This function assumes that the first lines of the specified portions - * of the two files do not match, and likewise that the last lines do not - * match. The caller must trim matching lines from the beginning and end - * of the portions it is going to specify. - */ - public function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) { + /* Divide the Largest Common Subsequence (LCS) of the sequences + * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally + * sized segments. + * + * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an + * array of NCHUNKS+1 (X, Y) indexes giving the diving points between + * sub sequences. The first sub-sequence is contained in [X0, X1), + * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note + * that (X0, Y0) == (XOFF, YOFF) and + * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). + * + * This function assumes that the first lines of the specified portions + * of the two files do not match, and likewise that the last lines do not + * match. The caller must trim matching lines from the beginning and end + * of the portions it is going to specify. + */ + public function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) { $flip = false; if ($xlim - $xoff > $ylim - $yoff) { - // Things seems faster (I'm not sure I understand why) - // when the shortest sequence in X. - $flip = true; - list ($xoff, $xlim, $yoff, $ylim) + // Things seems faster (I'm not sure I understand why) + // when the shortest sequence in X. + $flip = true; + list ($xoff, $xlim, $yoff, $ylim) = array( $yoff, $ylim, $xoff, $xlim); - } + } if ($flip) - for ($i = $ylim - 1; $i >= $yoff; $i--) + for ($i = $ylim - 1; $i >= $yoff; $i--) $ymatches[$this->xv[$i]][] = $i; else - for ($i = $ylim - 1; $i >= $yoff; $i--) + for ($i = $ylim - 1; $i >= $yoff; $i--) $ymatches[$this->yv[$i]][] = $i; $this->lcs = 0; @@ -273,70 +273,70 @@ class _DiffEngine $numer = $xlim - $xoff + $nchunks - 1; $x = $xoff; for ($chunk = 0; $chunk < $nchunks; $chunk++) { - if ($chunk > 0) + if ($chunk > 0) for ($i = 0; $i <= $this->lcs; $i++) - $ymids[$i][$chunk-1] = $this->seq[$i]; + $ymids[$i][$chunk-1] = $this->seq[$i]; - $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); - for ( ; $x < $x1; $x++) { - $line = $flip ? $this->yv[$x] : $this->xv[$x]; - if (empty($ymatches[$line])) - continue; + $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); + for ( ; $x < $x1; $x++) { + $line = $flip ? $this->yv[$x] : $this->xv[$x]; + if (empty($ymatches[$line])) + continue; $matches = $ymatches[$line]; - reset($matches); + reset($matches); while (list ($junk, $y) = each($matches)) - if (empty($this->in_seq[$y])) { + if (empty($this->in_seq[$y])) { $k = $this->_lcs_pos($y); USE_ASSERTS && assert($k > 0); $ymids[$k] = $ymids[$k-1]; break; - } + } while (list ($junk, $y) = each($matches)) { - if ($y > $this->seq[$k-1]) { + if ($y > $this->seq[$k-1]) { USE_ASSERTS && assert($y < $this->seq[$k]); // Optimization: this is a common case: // next match is just replacing previous match. $this->in_seq[$this->seq[$k]] = false; $this->seq[$k] = $y; $this->in_seq[$y] = 1; - } - else if (empty($this->in_seq[$y])) { + } + else if (empty($this->in_seq[$y])) { $k = $this->_lcs_pos($y); USE_ASSERTS && assert($k > 0); $ymids[$k] = $ymids[$k-1]; - } - } - } - } + } + } + } + } $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); $ymid = $ymids[$this->lcs]; for ($n = 0; $n < $nchunks - 1; $n++) { - $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); - $y1 = $ymid[$n] + 1; - $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); - } + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); + $y1 = $ymid[$n] + 1; + $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); + } $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); return array($this->lcs, $seps); - } + } - public function _lcs_pos ($ypos) { + public function _lcs_pos ($ypos) { $end = $this->lcs; if ($end == 0 || $ypos > $this->seq[$end]) { - $this->seq[++$this->lcs] = $ypos; - $this->in_seq[$ypos] = 1; - return $this->lcs; - } + $this->seq[++$this->lcs] = $ypos; + $this->in_seq[$ypos] = 1; + return $this->lcs; + } $beg = 1; while ($beg < $end) { - $mid = (int)(($beg + $end) / 2); - if ( $ypos > $this->seq[$mid] ) + $mid = (int)(($beg + $end) / 2); + if ( $ypos > $this->seq[$mid] ) $beg = $mid + 1; - else + else $end = $mid; - } + } USE_ASSERTS && assert($ypos != $this->seq[$end]); @@ -344,77 +344,77 @@ class _DiffEngine $this->seq[$end] = $ypos; $this->in_seq[$ypos] = 1; return $end; - } + } - /* Find LCS of two sequences. - * - * The results are recorded in the vectors $this->{x,y}changed[], by - * storing a 1 in the element for each line that is an insertion - * or deletion (ie. is not in the LCS). - * - * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. - * - * Note that XLIM, YLIM are exclusive bounds. - * All line numbers are origin-0 and discarded lines are not counted. - */ - public function _compareseq ($xoff, $xlim, $yoff, $ylim) { + /* Find LCS of two sequences. + * + * The results are recorded in the vectors $this->{x,y}changed[], by + * storing a 1 in the element for each line that is an insertion + * or deletion (ie. is not in the LCS). + * + * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. + * + * Note that XLIM, YLIM are exclusive bounds. + * All line numbers are origin-0 and discarded lines are not counted. + */ + public function _compareseq ($xoff, $xlim, $yoff, $ylim) { // Slide down the bottom initial diagonal. while ($xoff < $xlim && $yoff < $ylim - && $this->xv[$xoff] == $this->yv[$yoff]) { - ++$xoff; - ++$yoff; - } + && $this->xv[$xoff] == $this->yv[$yoff]) { + ++$xoff; + ++$yoff; + } // Slide up the top initial diagonal. while ($xlim > $xoff && $ylim > $yoff - && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { - --$xlim; - --$ylim; - } + && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { + --$xlim; + --$ylim; + } if ($xoff == $xlim || $yoff == $ylim) - $lcs = 0; + $lcs = 0; else { - // This is ad hoc but seems to work well. - //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); - //$nchunks = max(2,min(8,(int)$nchunks)); - $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; - list ($lcs, $seps) + // This is ad hoc but seems to work well. + //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); + //$nchunks = max(2,min(8,(int)$nchunks)); + $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; + list ($lcs, $seps) = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks); - } + } if ($lcs == 0) { - // X and Y sequences have no common subsequence: - // mark all changed. - while ($yoff < $ylim) + // X and Y sequences have no common subsequence: + // mark all changed. + while ($yoff < $ylim) $this->ychanged[$this->yind[$yoff++]] = 1; - while ($xoff < $xlim) + while ($xoff < $xlim) $this->xchanged[$this->xind[$xoff++]] = 1; - } + } else { - // Use the partitions to split this problem into subproblems. - reset($seps); - $pt1 = $seps[0]; - while ($pt2 = next($seps)) { + // Use the partitions to split this problem into subproblems. + reset($seps); + $pt1 = $seps[0]; + while ($pt2 = next($seps)) { $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); $pt1 = $pt2; - } - } - } + } + } + } - /* Adjust inserts/deletes of identical lines to join changes - * as much as possible. - * - * We do something when a run of changed lines include a - * line at one end and has an excluded, identical line at the other. - * We are free to choose which identical line is included. - * 'compareseq' usually chooses the one at the beginning, - * but usually it is cleaner to consider the following identical line - * to be the "change". - * - * This is extracted verbatim from analyze.c (GNU diffutils-2.7). - */ - public function _shift_boundaries ($lines, &$changed, $other_changed) { + /* Adjust inserts/deletes of identical lines to join changes + * as much as possible. + * + * We do something when a run of changed lines include a + * line at one end and has an excluded, identical line at the other. + * We are free to choose which identical line is included. + * 'compareseq' usually chooses the one at the beginning, + * but usually it is cleaner to consider the following identical line + * to be the "change". + * + * This is extracted verbatim from analyze.c (GNU diffutils-2.7). + */ + public function _shift_boundaries ($lines, &$changed, $other_changed) { $i = 0; $j = 0; @@ -423,37 +423,37 @@ class _DiffEngine $other_len = sizeof($other_changed); while (1) { - /* - * Scan forwards to find beginning of another run of changes. - * Also keep track of the corresponding point in the other file. - * - * Throughout this code, $i and $j are adjusted together so that - * the first $i elements of $changed and the first $j elements - * of $other_changed both contain the same number of zeros - * (unchanged lines). - * Furthermore, $j is always kept so that $j == $other_len or - * $other_changed[$j] == false. - */ - while ($j < $other_len && $other_changed[$j]) + /* + * Scan forwards to find beginning of another run of changes. + * Also keep track of the corresponding point in the other file. + * + * Throughout this code, $i and $j are adjusted together so that + * the first $i elements of $changed and the first $j elements + * of $other_changed both contain the same number of zeros + * (unchanged lines). + * Furthermore, $j is always kept so that $j == $other_len or + * $other_changed[$j] == false. + */ + while ($j < $other_len && $other_changed[$j]) $j++; - while ($i < $len && ! $changed[$i]) { + while ($i < $len && ! $changed[$i]) { USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); $i++; $j++; while ($j < $other_len && $other_changed[$j]) - $j++; - } + $j++; + } - if ($i == $len) + if ($i == $len) break; - $start = $i; + $start = $i; - // Find the end of this run of changes. - while (++$i < $len && $changed[$i]) + // Find the end of this run of changes. + while (++$i < $len && $changed[$i]) continue; - do { + do { /* * Record the length of this run of changes, so that * we can later determine whether the run has grown. @@ -466,15 +466,15 @@ class _DiffEngine * This merges with previous changed regions. */ while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { - $changed[--$start] = 1; - $changed[--$i] = false; - while ($start > 0 && $changed[$start - 1]) + $changed[--$start] = 1; + $changed[--$i] = false; + while ($start > 0 && $changed[$start - 1]) $start--; - USE_ASSERTS && assert('$j > 0'); - while ($other_changed[--$j]) + USE_ASSERTS && assert('$j > 0'); + while ($other_changed[--$j]) continue; - USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); - } + USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); + } /* * Set CORRESPONDING to the end of the changed run, at the last @@ -491,35 +491,35 @@ class _DiffEngine * the changed region is moved forward as far as possible. */ while ($i < $len && $lines[$start] == $lines[$i]) { - $changed[$start++] = false; - $changed[$i++] = 1; - while ($i < $len && $changed[$i]) + $changed[$start++] = false; + $changed[$i++] = 1; + while ($i < $len && $changed[$i]) $i++; - USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); - $j++; - if ($j < $other_len && $other_changed[$j]) { + USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); + $j++; + if ($j < $other_len && $other_changed[$j]) { $corresponding = $i; while ($j < $other_len && $other_changed[$j]) - $j++; - } - } - } while ($runlength != $i - $start); + $j++; + } + } + } while ($runlength != $i - $start); - /* - * If possible, move the fully-merged run of changes - * back to a corresponding run in the other file. - */ - while ($corresponding < $i) { + /* + * If possible, move the fully-merged run of changes + * back to a corresponding run in the other file. + */ + while ($corresponding < $i) { $changed[--$start] = 1; $changed[--$i] = 0; USE_ASSERTS && assert('$j > 0'); while ($other_changed[--$j]) - continue; + continue; USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); - } - } - } + } + } + } } /** @@ -531,138 +531,138 @@ class Diff { public static $html_cleaner_class = null; - var $edits; + var $edits; - /** - * Constructor. - * Computes diff between sequences of strings. - * - * @param $from_lines array An array of strings. - * (Typically these are lines from a file.) - * @param $to_lines array An array of strings. - */ - public function Diff($from_lines, $to_lines) { - $eng = new _DiffEngine; - $this->edits = $eng->diff($from_lines, $to_lines); - //$this->_check($from_lines, $to_lines); - } + /** + * Constructor. + * Computes diff between sequences of strings. + * + * @param $from_lines array An array of strings. + * (Typically these are lines from a file.) + * @param $to_lines array An array of strings. + */ + public function Diff($from_lines, $to_lines) { + $eng = new _DiffEngine; + $this->edits = $eng->diff($from_lines, $to_lines); + //$this->_check($from_lines, $to_lines); + } - /** - * Compute reversed Diff. - * - * SYNOPSIS: - * - * $diff = new Diff($lines1, $lines2); - * $rev = $diff->reverse(); - * @return object A Diff object representing the inverse of the - * original diff. - */ - public function reverse () { + /** + * Compute reversed Diff. + * + * SYNOPSIS: + * + * $diff = new Diff($lines1, $lines2); + * $rev = $diff->reverse(); + * @return object A Diff object representing the inverse of the + * original diff. + */ + public function reverse () { $rev = $this; - $rev->edits = array(); - foreach ($this->edits as $edit) { - $rev->edits[] = $edit->reverse(); - } + $rev->edits = array(); + foreach ($this->edits as $edit) { + $rev->edits[] = $edit->reverse(); + } return $rev; - } + } - /** - * Check for empty diff. - * - * @return bool True iff two sequences were identical. - */ - public function isEmpty () { - foreach ($this->edits as $edit) { - if ($edit->type != 'copy') - return false; - } - return true; - } + /** + * Check for empty diff. + * + * @return bool True iff two sequences were identical. + */ + public function isEmpty () { + foreach ($this->edits as $edit) { + if ($edit->type != 'copy') + return false; + } + return true; + } - /** - * Compute the length of the Longest Common Subsequence (LCS). - * - * This is mostly for diagnostic purposed. - * - * @return int The length of the LCS. - */ - public function lcs () { + /** + * Compute the length of the Longest Common Subsequence (LCS). + * + * This is mostly for diagnostic purposed. + * + * @return int The length of the LCS. + */ + public function lcs () { $lcs = 0; - foreach ($this->edits as $edit) { - if ($edit->type == 'copy') - $lcs += sizeof($edit->orig); - } + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $lcs += sizeof($edit->orig); + } return $lcs; - } + } - /** - * Get the original set of lines. - * - * This reconstructs the $from_lines parameter passed to the - * constructor. - * - * @return array The original sequence of strings. - */ - public function orig() { - $lines = array(); + /** + * Get the original set of lines. + * + * This reconstructs the $from_lines parameter passed to the + * constructor. + * + * @return array The original sequence of strings. + */ + public function orig() { + $lines = array(); - foreach ($this->edits as $edit) { - if ($edit->orig) - array_splice($lines, sizeof($lines), 0, $edit->orig); - } - return $lines; - } + foreach ($this->edits as $edit) { + if ($edit->orig) + array_splice($lines, sizeof($lines), 0, $edit->orig); + } + return $lines; + } - /** - * Get the final set of lines. - * - * This reconstructs the $to_lines parameter passed to the - * constructor. - * - * @return array The sequence of strings. - */ - public function finaltext() { - $lines = array(); + /** + * Get the final set of lines. + * + * This reconstructs the $to_lines parameter passed to the + * constructor. + * + * @return array The sequence of strings. + */ + public function finaltext() { + $lines = array(); - foreach ($this->edits as $edit) { - if ($edit->final) - array_splice($lines, sizeof($lines), 0, $edit->final); - } - return $lines; - } + foreach ($this->edits as $edit) { + if ($edit->final) + array_splice($lines, sizeof($lines), 0, $edit->final); + } + return $lines; + } - /** - * Check a Diff for validity. - * - * This is here only for debugging purposes. - */ - public function _check ($from_lines, $to_lines) { - if (serialize($from_lines) != serialize($this->orig())) - trigger_error("Reconstructed original doesn't match", E_USER_ERROR); - if (serialize($to_lines) != serialize($this->finaltext())) - trigger_error("Reconstructed final doesn't match", E_USER_ERROR); + /** + * Check a Diff for validity. + * + * This is here only for debugging purposes. + */ + public function _check ($from_lines, $to_lines) { + if (serialize($from_lines) != serialize($this->orig())) + trigger_error("Reconstructed original doesn't match", E_USER_ERROR); + if (serialize($to_lines) != serialize($this->finaltext())) + trigger_error("Reconstructed final doesn't match", E_USER_ERROR); - $rev = $this->reverse(); - if (serialize($to_lines) != serialize($rev->orig())) - trigger_error("Reversed original doesn't match", E_USER_ERROR); - if (serialize($from_lines) != serialize($rev->finaltext())) - trigger_error("Reversed final doesn't match", E_USER_ERROR); + $rev = $this->reverse(); + if (serialize($to_lines) != serialize($rev->orig())) + trigger_error("Reversed original doesn't match", E_USER_ERROR); + if (serialize($from_lines) != serialize($rev->finaltext())) + trigger_error("Reversed final doesn't match", E_USER_ERROR); - $prevtype = 'none'; - foreach ($this->edits as $edit) { - if ( $prevtype == $edit->type ) - trigger_error("Edit sequence is non-optimal", E_USER_ERROR); - $prevtype = $edit->type; - } + $prevtype = 'none'; + foreach ($this->edits as $edit) { + if ( $prevtype == $edit->type ) + trigger_error("Edit sequence is non-optimal", E_USER_ERROR); + $prevtype = $edit->type; + } - $lcs = $this->lcs(); - trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE); - } - - - - /** + $lcs = $this->lcs(); + trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE); + } + + + + /** * Attempt to clean invalid HTML, which messes up diffs. * This cleans code if possible, using an instance of HTMLCleaner * @@ -750,7 +750,7 @@ class Diff else $rechunked[$listName][] = $item; if($lookForTag && !$tagStack[$listName] && isset($item[0]) && $item[0] == "<" - && substr($item,0,2) != "Diff($mapped_from_lines, $mapped_to_lines); + $this->Diff($mapped_from_lines, $mapped_to_lines); - $xi = $yi = 0; - // Optimizing loop invariants: - // http://phplens.com/lens/php-book/optimizing-debugging-php.php - for ($i = 0, $max = sizeof($this->edits); $i < $max; $i++) { - $orig = &$this->edits[$i]->orig; - if (is_array($orig)) { - $orig = array_slice($from_lines, $xi, sizeof($orig)); - $xi += sizeof($orig); - } + $xi = $yi = 0; + // Optimizing loop invariants: + // http://phplens.com/lens/php-book/optimizing-debugging-php.php + for ($i = 0, $max = sizeof($this->edits); $i < $max; $i++) { + $orig = &$this->edits[$i]->orig; + if (is_array($orig)) { + $orig = array_slice($from_lines, $xi, sizeof($orig)); + $xi += sizeof($orig); + } - $final = &$this->edits[$i]->final; - if (is_array($final)) { - $final = array_slice($to_lines, $yi, sizeof($final)); - $yi += sizeof($final); - } - } - } + $final = &$this->edits[$i]->final; + if (is_array($final)) { + $final = array_slice($to_lines, $yi, sizeof($final)); + $yi += sizeof($final); + } + } + } } diff --git a/dev/BulkLoader.php b/dev/BulkLoader.php index bcd0652e0..7db06dfc6 100644 --- a/dev/BulkLoader.php +++ b/dev/BulkLoader.php @@ -140,7 +140,7 @@ abstract class BulkLoader extends ViewableData { //get all instances of the to be imported data object if($this->deleteExistingRecords) { - DataObject::get($this->objectClass)->removeAll(); + DataObject::get($this->objectClass)->removeAll(); } return $this->processAll($filepath); @@ -273,12 +273,12 @@ class BulkLoader_Result extends Object { *
    */ protected $created = array(); - + /** * @var array (see {@link $created}) */ protected $updated = array(); - + /** * @var array (see {@link $created}) */ @@ -290,7 +290,7 @@ class BulkLoader_Result extends Object { * one of 3 strings: "created", "updated", or "deleted" */ protected $lastChange = array(); - + /** * Returns the count of all objects which were * created or updated. @@ -408,5 +408,4 @@ class BulkLoader_Result extends Object { return $set; } - } diff --git a/dev/DevelopmentAdmin.php b/dev/DevelopmentAdmin.php index bd8fb202a..8dd45e743 100644 --- a/dev/DevelopmentAdmin.php +++ b/dev/DevelopmentAdmin.php @@ -18,15 +18,15 @@ class DevelopmentAdmin extends Controller { ); static $allowed_actions = array( - 'index', - 'tests', - 'jstests', - 'tasks', - 'viewmodel', - 'build', - 'reset', - 'viewcode' - ); + 'index', + 'tests', + 'jstests', + 'tasks', + 'viewmodel', + 'build', + 'reset', + 'viewcode' + ); public function init() { parent::init(); @@ -56,7 +56,7 @@ class DevelopmentAdmin extends Controller { $matched = false; if(isset($_FILE_TO_URL_MAPPING[$testPath])) { $matched = true; - break; + break; } $testPath = dirname($testPath); } diff --git a/dev/FunctionalTest.php b/dev/FunctionalTest.php index c95f4919b..351d95e35 100644 --- a/dev/FunctionalTest.php +++ b/dev/FunctionalTest.php @@ -8,14 +8,14 @@ * * * public function testMyForm() { - * // Visit a URL - * $this->get("your/url"); + * // Visit a URL + * $this->get("your/url"); * - * // Submit a form on the page that you get in response - * $this->submitForm("MyForm_ID", array("Email" => "invalid email ^&*&^")); + * // Submit a form on the page that you get in response + * $this->submitForm("MyForm_ID", array("Email" => "invalid email ^&*&^")); * - * // Validate the content that is returned - * $this->assertExactMatchBySelector("#MyForm_ID p.error", array("That email address is invalid.")); + * // Validate the content that is returned + * $this->assertExactMatchBySelector("#MyForm_ID p.error", array("That email address is invalid.")); * } * * @@ -70,10 +70,10 @@ class FunctionalTest extends SapphireTest { if($this->stat('use_draft_site')) { $this->useDraftSite(); } - - // Unprotect the site, tests are running with the assumption it's off. They will enable it on a case-by-case - // basis. - BasicAuth::protect_entire_site(false); + + // Unprotect the site, tests are running with the assumption it's off. They will enable it on a case-by-case + // basis. + BasicAuth::protect_entire_site(false); SecurityToken::disable(); } @@ -193,8 +193,8 @@ class FunctionalTest extends SapphireTest { foreach($expectedMatches as $match) { $this->assertTrue( isset($actuals[$match]), - "Failed asserting the CSS selector '$selector' has a partial match to the expected elements:\n'" - . implode("'\n'", $expectedMatches) . "'\n\n" + "Failed asserting the CSS selector '$selector' has a partial match to the expected elements:\n'" + . implode("'\n'", $expectedMatches) . "'\n\n" . "Instead the following elements were found:\n'" . implode("'\n'", array_keys($actuals)) . "'" ); return false; diff --git a/dev/Profiler.php b/dev/Profiler.php index 60a2b29e9..437476481 100644 --- a/dev/Profiler.php +++ b/dev/Profiler.php @@ -16,224 +16,224 @@ * @subpackage misc */ class Profiler { - var $description; - var $startTime; - var $endTime; - var $initTime; - var $cur_timer; - var $stack; - var $trail; - var $trace; - var $count; - var $running; - - protected static $inst; + var $description; + var $startTime; + var $endTime; + var $initTime; + var $cur_timer; + var $stack; + var $trail; + var $trace; + var $count; + var $running; + + protected static $inst; - /** - * Initialise the timer. with the current micro time - */ - public function Profiler( $output_enabled=false, $trace_enabled=false) - { - $this->description = array(); - $this->startTime = array(); - $this->endTime = array(); - $this->initTime = 0; - $this->cur_timer = ""; - $this->stack = array(); - $this->trail = ""; - $this->trace = ""; - $this->count = array(); - $this->running = array(); - $this->initTime = $this->getMicroTime(); - $this->output_enabled = $output_enabled; - $this->trace_enabled = $trace_enabled; - $this->startTimer('unprofiled'); - } + /** + * Initialise the timer. with the current micro time + */ + public function Profiler( $output_enabled=false, $trace_enabled=false) + { + $this->description = array(); + $this->startTime = array(); + $this->endTime = array(); + $this->initTime = 0; + $this->cur_timer = ""; + $this->stack = array(); + $this->trail = ""; + $this->trace = ""; + $this->count = array(); + $this->running = array(); + $this->initTime = $this->getMicroTime(); + $this->output_enabled = $output_enabled; + $this->trace_enabled = $trace_enabled; + $this->startTimer('unprofiled'); + } - // Public Methods - - public static function init() { - Deprecation::notice('3.1', 'The Profiler class is deprecated, use third party tools like XHProf instead'); - if(!self::$inst) self::$inst = new Profiler(true,true); - } - - public static function mark($name, $level2 = "", $desc = "") { - if($level2 && $_GET['debug_profile'] > 1) $name .= " $level2"; - - if(!self::$inst) self::$inst = new Profiler(true,true); - - self::$inst->startTimer($name, $desc); - } - public static function unmark($name, $level2 = "", $desc = "") { - if($level2 && $_GET['debug_profile'] > 1) $name .= " $level2"; - - if(!self::$inst) self::$inst = new Profiler(true,true); - - self::$inst->stopTimer($name, $desc); - } - public static function show($showTrace = false) { - if(!self::$inst) self::$inst = new Profiler(true,true); - - echo "
    "; - echo "

    " - . "(Click to close)

    "; - self::$inst->printTimers(); - if($showTrace) self::$inst->printTrace(); - echo "
    "; - } + // Public Methods + + public static function init() { + Deprecation::notice('3.1', 'The Profiler class is deprecated, use third party tools like XHProf instead'); + if(!self::$inst) self::$inst = new Profiler(true,true); + } + + public static function mark($name, $level2 = "", $desc = "") { + if($level2 && $_GET['debug_profile'] > 1) $name .= " $level2"; + + if(!self::$inst) self::$inst = new Profiler(true,true); + + self::$inst->startTimer($name, $desc); + } + public static function unmark($name, $level2 = "", $desc = "") { + if($level2 && $_GET['debug_profile'] > 1) $name .= " $level2"; + + if(!self::$inst) self::$inst = new Profiler(true,true); + + self::$inst->stopTimer($name, $desc); + } + public static function show($showTrace = false) { + if(!self::$inst) self::$inst = new Profiler(true,true); + + echo "
    "; + echo "

    " + . "(Click to close)

    "; + self::$inst->printTimers(); + if($showTrace) self::$inst->printTrace(); + echo "
    "; + } - /** - * Start an individual timer - * This will pause the running timer and place it on a stack. - * @param string $name name of the timer - * @param string optional $desc description of the timer - */ - public function startTimer($name, $desc="" ){ - $this->trace.="start $name\n"; - $n=array_push( $this->stack, $this->cur_timer ); - $this->__suspendTimer( $this->stack[$n-1] ); - $this->startTime[$name] = $this->getMicroTime(); - $this->cur_timer=$name; - $this->description[$name] = $desc; - if (!array_key_exists($name,$this->count)) - $this->count[$name] = 1; - else - $this->count[$name]++; - } + /** + * Start an individual timer + * This will pause the running timer and place it on a stack. + * @param string $name name of the timer + * @param string optional $desc description of the timer + */ + public function startTimer($name, $desc="" ){ + $this->trace.="start $name\n"; + $n=array_push( $this->stack, $this->cur_timer ); + $this->__suspendTimer( $this->stack[$n-1] ); + $this->startTime[$name] = $this->getMicroTime(); + $this->cur_timer=$name; + $this->description[$name] = $desc; + if (!array_key_exists($name,$this->count)) + $this->count[$name] = 1; + else + $this->count[$name]++; + } - /** - * Stop an individual timer - * Restart the timer that was running before this one - * @param string $name name of the timer - */ - public function stopTimer($name){ - $this->trace.="stop $name\n"; - $this->endTime[$name] = $this->getMicroTime(); - if (!array_key_exists($name, $this->running)) - $this->running[$name] = $this->elapsedTime($name); - else - $this->running[$name] += $this->elapsedTime($name); - $this->cur_timer=array_pop($this->stack); - $this->__resumeTimer($this->cur_timer); - } + /** + * Stop an individual timer + * Restart the timer that was running before this one + * @param string $name name of the timer + */ + public function stopTimer($name){ + $this->trace.="stop $name\n"; + $this->endTime[$name] = $this->getMicroTime(); + if (!array_key_exists($name, $this->running)) + $this->running[$name] = $this->elapsedTime($name); + else + $this->running[$name] += $this->elapsedTime($name); + $this->cur_timer=array_pop($this->stack); + $this->__resumeTimer($this->cur_timer); + } - /** - * measure the elapsed time of a timer without stoping the timer if - * it is still running - */ - public function elapsedTime($name){ - // This shouldn't happen, but it does once. - if (!array_key_exists($name,$this->startTime)) - return 0; + /** + * measure the elapsed time of a timer without stoping the timer if + * it is still running + */ + public function elapsedTime($name){ + // This shouldn't happen, but it does once. + if (!array_key_exists($name,$this->startTime)) + return 0; - if(array_key_exists($name,$this->endTime)){ - return ($this->endTime[$name] - $this->startTime[$name]); - } else { - $now=$this->getMicroTime(); - return ($now - $this->startTime[$name]); - } - }//end start_time + if(array_key_exists($name,$this->endTime)){ + return ($this->endTime[$name] - $this->startTime[$name]); + } else { + $now=$this->getMicroTime(); + return ($now - $this->startTime[$name]); + } + }//end start_time - /** - * Measure the elapsed time since the profile class was initialised - * - */ - public function elapsedOverall(){ - $oaTime = $this->getMicroTime() - $this->initTime; - return($oaTime); - }//end start_time + /** + * Measure the elapsed time since the profile class was initialised + * + */ + public function elapsedOverall(){ + $oaTime = $this->getMicroTime() - $this->initTime; + return($oaTime); + }//end start_time - /** - * print out a log of all the timers that were registered - * - */ - public function printTimers($enabled=false) - { - if($this->output_enabled||$enabled){ - $TimedTotal = 0; - $tot_perc = 0; - ksort($this->description); - print("
    \n");
    -            $oaTime = $this->getMicroTime() - $this->initTime;
    -            echo"============================================================================\n";
    -            echo "                              PROFILER OUTPUT\n";
    -            echo"============================================================================\n";
    -            print( "Calls                    Time  Routine\n");
    -            echo"-----------------------------------------------------------------------------\n";
    -            while (list ($key, $val) = each ($this->description)) {
    -                $t = $this->elapsedTime($key);
    -                $total = $this->running[$key];
    -                $count = $this->count[$key];
    -                $TimedTotal += $total;
    -                $perc = ($total/$oaTime)*100;
    -                $tot_perc+=$perc;
    -                // $perc=sprintf("%3.2f", $perc );
    -                $lines[ sprintf( "%3d    %3.4f ms (%3.2f %%)  %s\n", $count, $total*1000, $perc, $key) ] = $total;
    -            }
    +	/**
    +	*   print out a log of all the timers that were registered
    +	*
    +	*/
    +	public function printTimers($enabled=false)
    +	{
    +		if($this->output_enabled||$enabled){
    +			$TimedTotal = 0;
    +			$tot_perc = 0;
    +			ksort($this->description);
    +			print("
    \n");
    +			$oaTime = $this->getMicroTime() - $this->initTime;
    +			echo"============================================================================\n";
    +			echo "                              PROFILER OUTPUT\n";
    +			echo"============================================================================\n";
    +			print( "Calls                    Time  Routine\n");
    +			echo"-----------------------------------------------------------------------------\n";
    +			while (list ($key, $val) = each ($this->description)) {
    +				$t = $this->elapsedTime($key);
    +				$total = $this->running[$key];
    +				$count = $this->count[$key];
    +				$TimedTotal += $total;
    +				$perc = ($total/$oaTime)*100;
    +				$tot_perc+=$perc;
    +				// $perc=sprintf("%3.2f", $perc );
    +				$lines[ sprintf( "%3d    %3.4f ms (%3.2f %%)  %s\n", $count, $total*1000, $perc, $key) ] = $total;
    +			}
     			arsort($lines);
     			foreach($lines as $line => $total) {
     				echo $line;
     			}
     
    -            echo "\n";
    +			echo "\n";
     
    -            $missed=$oaTime-$TimedTotal;
    -            $perc = ($missed/$oaTime)*100;
    -            $tot_perc+=$perc;
    -            // $perc=sprintf("%3.2f", $perc );
    -            printf( "       %3.4f ms (%3.2f %%)  %s\n", $missed*1000,$perc, "Missed");
    +			$missed=$oaTime-$TimedTotal;
    +			$perc = ($missed/$oaTime)*100;
    +			$tot_perc+=$perc;
    +			// $perc=sprintf("%3.2f", $perc );
    +			printf( "       %3.4f ms (%3.2f %%)  %s\n", $missed*1000,$perc, "Missed");
     
    -            echo"============================================================================\n";
    +			echo"============================================================================\n";
     
    -            printf( "       %3.4f ms (%3.2f %%)  %s\n", $oaTime*1000,$tot_perc, "OVERALL TIME");
    +			printf( "       %3.4f ms (%3.2f %%)  %s\n", $oaTime*1000,$tot_perc, "OVERALL TIME");
     
    -            echo"============================================================================\n";
    +			echo"============================================================================\n";
     
    -            print("
    "); - } - } + print("
    "); + } + } - public function printTrace( $enabled=false ) - { - if($this->trace_enabled||$enabled){ - print("
    ");
    -            print("Trace\n$this->trace\n\n");
    -            print("
    "); - } - } + public function printTrace( $enabled=false ) + { + if($this->trace_enabled||$enabled){ + print("
    ");
    +			print("Trace\n$this->trace\n\n");
    +			print("
    "); + } + } - /// Internal Use Only Functions + /// Internal Use Only Functions - /** - * Get the current time as accuratly as possible - * - */ - public function getMicroTime(){ - $tmp=explode(' ', microtime()); - $rt=$tmp[0]+$tmp[1]; - return $rt; - } + /** + * Get the current time as accuratly as possible + * + */ + public function getMicroTime(){ + $tmp=explode(' ', microtime()); + $rt=$tmp[0]+$tmp[1]; + return $rt; + } - /** - * resume an individual timer - * - */ - public function __resumeTimer($name){ - $this->trace.="resume $name\n"; - $this->startTime[$name] = $this->getMicroTime(); - } + /** + * resume an individual timer + * + */ + public function __resumeTimer($name){ + $this->trace.="resume $name\n"; + $this->startTime[$name] = $this->getMicroTime(); + } - /** - * suspend an individual timer - * - */ - public function __suspendTimer($name){ - $this->trace.="suspend $name\n"; - $this->endTime[$name] = $this->getMicroTime(); - if (!array_key_exists($name, $this->running)) - $this->running[$name] = $this->elapsedTime($name); - else - $this->running[$name] += $this->elapsedTime($name); - } + /** + * suspend an individual timer + * + */ + public function __suspendTimer($name){ + $this->trace.="suspend $name\n"; + $this->endTime[$name] = $this->getMicroTime(); + if (!array_key_exists($name, $this->running)) + $this->running[$name] = $this->elapsedTime($name); + else + $this->running[$name] += $this->elapsedTime($name); + } } diff --git a/dev/SapphireTestReporter.php b/dev/SapphireTestReporter.php index 36af8b18f..84da76c72 100644 --- a/dev/SapphireTestReporter.php +++ b/dev/SapphireTestReporter.php @@ -83,17 +83,17 @@ class SapphireTestReporter implements PHPUnit_Framework_TestListener { public function __construct() { @include_once 'Benchmark/Timer.php'; if(class_exists('Benchmark_Timer')) { - $this->timer = new Benchmark_Timer(); - $this->hasTimer = true; + $this->timer = new Benchmark_Timer(); + $this->hasTimer = true; } else { - $this->hasTimer = false; + $this->hasTimer = false; } - $this->suiteResults = array( - 'suites' => array(), // array of suites run - 'hasTimer' => $this->hasTimer, // availability of PEAR Benchmark_Timer - 'totalTests' => 0 // total number of tests run - ); + $this->suiteResults = array( + 'suites' => array(), // array of suites run + 'hasTimer' => $this->hasTimer, // availability of PEAR Benchmark_Timer + 'totalTests' => 0 // total number of tests run + ); } /** @@ -117,14 +117,14 @@ class SapphireTestReporter implements PHPUnit_Framework_TestListener { public function startTestSuite( PHPUnit_Framework_TestSuite $suite) { if(strlen($suite->getName())) { $this->endCurrentTestSuite(); - $this->currentSuite = array( - 'suite' => $suite, // the test suite - 'tests' => array(), // the tests in the suite - 'errors' => 0, // number of tests with errors (including setup errors) - 'failures' => 0, // number of tests which failed - 'incomplete' => 0, // number of tests that were not completed correctly + $this->currentSuite = array( + 'suite' => $suite, // the test suite + 'tests' => array(), // the tests in the suite + 'errors' => 0, // number of tests with errors (including setup errors) + 'failures' => 0, // number of tests which failed + 'incomplete' => 0, // number of tests that were not completed correctly 'error' => null); // Any error encountered during setup of the test suite - } + } } /** @@ -226,7 +226,7 @@ class SapphireTestReporter implements PHPUnit_Framework_TestListener { $this->currentSuite['incomplete']++; $this->addStatus(TEST_INCOMPLETE, $e->toString(), $this->getTestException($test, $e), $e->getTrace()); } - + /** * Not used * @@ -257,7 +257,7 @@ class SapphireTestReporter implements PHPUnit_Framework_TestListener { $this->currentTest = null; } - /** + /** * Upon completion of a test, records the execution time (if available) and adds the test to * the tests performed in the current suite. * diff --git a/dev/SilverStripeListener.php b/dev/SilverStripeListener.php index c6c5c01a1..009591f82 100644 --- a/dev/SilverStripeListener.php +++ b/dev/SilverStripeListener.php @@ -27,13 +27,13 @@ class SilverStripeListener implements PHPUnit_Framework_TestListener { public function startTest(PHPUnit_Framework_Test $test) { } - + public function endTest(PHPUnit_Framework_Test $test, $time) { } public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { } - + public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { } diff --git a/dev/TeamCityListener.php b/dev/TeamCityListener.php index e924ed8a4..7af8a6c52 100644 --- a/dev/TeamCityListener.php +++ b/dev/TeamCityListener.php @@ -27,7 +27,7 @@ class TeamCityListener implements PHPUnit_Framework_TestListener { $class = get_class($test); echo "##teamcity[testStarted name='{$class}.{$test->getName()}']\n"; } - + public function endTest(PHPUnit_Framework_Test $test, $time) { $class = get_class($test); echo "##teamcity[testFinished name='{$class}.{$test->getName()}' duration='$time']\n"; @@ -40,7 +40,7 @@ class TeamCityListener implements PHPUnit_Framework_TestListener { echo "##teamcity[testFailed type='exception' name='{$class}.{$test->getName()}' message='$message'" . " details='$trace']\n"; } - + public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { $class = get_class($test); $message = $this->escape($e->getMessage()); diff --git a/dev/TestListener.php b/dev/TestListener.php index 5fcd93cfe..5494dfd89 100644 --- a/dev/TestListener.php +++ b/dev/TestListener.php @@ -13,15 +13,15 @@ class SS_TestListener implements PHPUnit_Framework_TestListener { public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) {} - public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) {} + public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) {} - public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) {} + public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) {} - public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) {} + public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) {} - public function startTest(PHPUnit_Framework_Test $test) {} + public function startTest(PHPUnit_Framework_Test $test) {} - public function endTest(PHPUnit_Framework_Test $test, $time) {} + public function endTest(PHPUnit_Framework_Test $test, $time) {} public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { $name = $suite->getName(); @@ -29,7 +29,7 @@ class SS_TestListener implements PHPUnit_Framework_TestListener { $this->class = new $name(); $this->class->setUpOnce(); - } + } public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { $name = $suite->getName(); diff --git a/dev/phpunit/PhpUnitWrapper.php b/dev/phpunit/PhpUnitWrapper.php index 0af2fefea..194339ea7 100644 --- a/dev/phpunit/PhpUnitWrapper.php +++ b/dev/phpunit/PhpUnitWrapper.php @@ -138,14 +138,14 @@ class PhpUnitWrapper implements IPhpUnitWrapper { public static function inst() { if (self::$phpunit_wrapper == null) { - if (fileExistsInIncludePath("/PHPUnit/Autoload.php")) { - self::$phpunit_wrapper = new PhpUnitWrapper_3_5(); - } else - if (fileExistsInIncludePath("/PHPUnit/Framework.php")) { - self::$phpunit_wrapper = new PhpUnitWrapper_3_4(); - } else { - self::$phpunit_wrapper = new PhpUnitWrapper(); - } + if (fileExistsInIncludePath("/PHPUnit/Autoload.php")) { + self::$phpunit_wrapper = new PhpUnitWrapper_3_5(); + } else + if (fileExistsInIncludePath("/PHPUnit/Framework.php")) { + self::$phpunit_wrapper = new PhpUnitWrapper_3_4(); + } else { + self::$phpunit_wrapper = new PhpUnitWrapper(); + } self::$phpunit_wrapper->init(); } diff --git a/dev/phpunit/PhpUnitWrapper_3_5.php b/dev/phpunit/PhpUnitWrapper_3_5.php index fdfe61dac..3085ad572 100644 --- a/dev/phpunit/PhpUnitWrapper_3_5.php +++ b/dev/phpunit/PhpUnitWrapper_3_5.php @@ -35,11 +35,11 @@ class PhpUnitWrapper_3_5 extends PhpUnitWrapper { protected function beforeRunTests() { if($this->getCoverageStatus()) { - $this->coverage = new PHP_CodeCoverage(); + $this->coverage = new PHP_CodeCoverage(); $coverage = $this->coverage; - $filter = $coverage->filter(); - $modules = $this->moduleDirectories(); + $filter = $coverage->filter(); + $modules = $this->moduleDirectories(); foreach(TestRunner::$coverage_filter_dirs as $dir) { if($dir[0] == '*') { diff --git a/email/Email.php b/email/Email.php index c88bcd1c4..337849071 100644 --- a/email/Email.php +++ b/email/Email.php @@ -363,12 +363,12 @@ class Email extends ViewableData { * Validates the email address. Returns true of false */ public static function validEmailAddress($address) { - if (function_exists('filter_var')) { - return filter_var($address, FILTER_VALIDATE_EMAIL); - } else { - return preg_match('#^([a-zA-Z0-9_+\.\-]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)' - . '|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$#', $address); - } + if (function_exists('filter_var')) { + return filter_var($address, FILTER_VALIDATE_EMAIL); + } else { + return preg_match('#^([a-zA-Z0-9_+\.\-]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)' + . '|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$#', $address); + } } /** diff --git a/email/Mailer.php b/email/Mailer.php index c70675bed..072b01ac7 100644 --- a/email/Mailer.php +++ b/email/Mailer.php @@ -120,9 +120,9 @@ class Mailer { dieprintr($customheaders); } - + $bodyIsUnicode = (strpos($htmlContent,"&#") !== false); - $plainEncoding = ""; + $plainEncoding = ""; // We generate plaintext content by default, but you can pass custom stuff $plainEncoding = ''; @@ -146,7 +146,7 @@ class Mailer { // Make the HTML part $headers["Content-Type"] = "text/html; charset=utf-8"; - + // Add basic wrapper tags if the body tag hasn't been given if(stripos($htmlContent, 'validEmailAddr($from); // Messages with the X-SilverStripeMessageID header can be tracked - if(isset($customheaders["X-SilverStripeMessageID"]) && defined('BOUNCE_EMAIL')) { - $bounceAddress = BOUNCE_EMAIL; - } else { - $bounceAddress = $from; - } + if(isset($customheaders["X-SilverStripeMessageID"]) && defined('BOUNCE_EMAIL')) { + $bounceAddress = BOUNCE_EMAIL; + } else { + $bounceAddress = $from; + } - // Strip the human name from the bounce address - if(preg_match('/^([^<>]*)<([^<>]+)> *$/', $bounceAddress, $parts)) $bounceAddress = $parts[2]; + // Strip the human name from the bounce address + if(preg_match('/^([^<>]*)<([^<>]+)> *$/', $bounceAddress, $parts)) $bounceAddress = $parts[2]; // $headers["Sender"] = $from; $headers["X-Mailer"] = X_MAILER; @@ -350,9 +350,9 @@ class Mailer { $file['contents'] = $this->QuotedPrintable_encode($file['contents']); } - $headers = "Content-type: $mimeType;\n\tname=\"$base\"\n". - "Content-Transfer-Encoding: $encoding\n". - "Content-Disposition: $disposition;\n\tfilename=\"$base\"\n" ; + $headers = "Content-type: $mimeType;\n\tname=\"$base\"\n". + "Content-Transfer-Encoding: $encoding\n". + "Content-Disposition: $disposition;\n\tfilename=\"$base\"\n" ; if ( isset($file['contentLocation']) ) $headers .= 'Content-Location: ' . $file['contentLocation'] . "\n" ; @@ -487,4 +487,4 @@ function validEmailAddr($emailAddress) { $mailer = Injector::inst()->create('Mailer'); return $mailer->validEmailAddr($emailAddress); -} \ No newline at end of file +} diff --git a/filesystem/GD.php b/filesystem/GD.php index fc63476f8..d05de83d6 100644 --- a/filesystem/GD.php +++ b/filesystem/GD.php @@ -135,9 +135,9 @@ class GDBackend extends Object implements Image_Backend { * @todo This method isn't very efficent */ public function fittedResize($width, $height) { - $gd = $this->resizeByHeight($height); - if($gd->width > $width) $gd = $gd->resizeByWidth($width); - return $gd; + $gd = $this->resizeByHeight($height); + if($gd->width > $width) $gd = $gd->resizeByWidth($width); + return $gd; } /** @@ -199,9 +199,9 @@ class GDBackend extends Object implements Image_Backend { if(!$this->gd) return; if(function_exists("imagerotate")) { - $newGD = imagerotate($this->gd, $angle,0); + $newGD = imagerotate($this->gd, $angle,0); } else { - //imagerotate is not included in PHP included in Ubuntu + //imagerotate is not included in PHP included in Ubuntu $newGD = $this->rotatePixelByPixel($angle); } $output = clone $this; @@ -210,45 +210,45 @@ class GDBackend extends Object implements Image_Backend { } /** - * Rotates image by given angle. It's slow because makes it pixel by pixel rather than - * using built-in function. Used when imagerotate function is not available(i.e. Ubuntu) - * - * @param angle - * - * @return GD - */ + * Rotates image by given angle. It's slow because makes it pixel by pixel rather than + * using built-in function. Used when imagerotate function is not available(i.e. Ubuntu) + * + * @param angle + * + * @return GD + */ - public function rotatePixelByPixel($angle) { - $sourceWidth = imagesx($this->gd); - $sourceHeight = imagesy($this->gd); - if ($angle == 180) { - $destWidth = $sourceWidth; - $destHeight = $sourceHeight; - } else { - $destWidth = $sourceHeight; - $destHeight = $sourceWidth; - } - $rotate=imagecreatetruecolor($destWidth,$destHeight); - imagealphablending($rotate, false); - for ($x = 0; $x < ($sourceWidth); $x++) { - for ($y = 0; $y < ($sourceHeight); $y++) { - $color = imagecolorat($this->gd, $x, $y); - switch ($angle) { - case 90: - imagesetpixel($rotate, $y, $destHeight - $x - 1, $color); - break; - case 180: - imagesetpixel($rotate, $destWidth - $x - 1, $destHeight - $y - 1, $color); - break; - case 270: - imagesetpixel($rotate, $destWidth - $y - 1, $x, $color); - break; - default: $rotate = $this->gd; - }; - } - } - return $rotate; - } + public function rotatePixelByPixel($angle) { + $sourceWidth = imagesx($this->gd); + $sourceHeight = imagesy($this->gd); + if ($angle == 180) { + $destWidth = $sourceWidth; + $destHeight = $sourceHeight; + } else { + $destWidth = $sourceHeight; + $destHeight = $sourceWidth; + } + $rotate=imagecreatetruecolor($destWidth,$destHeight); + imagealphablending($rotate, false); + for ($x = 0; $x < ($sourceWidth); $x++) { + for ($y = 0; $y < ($sourceHeight); $y++) { + $color = imagecolorat($this->gd, $x, $y); + switch ($angle) { + case 90: + imagesetpixel($rotate, $y, $destHeight - $x - 1, $color); + break; + case 180: + imagesetpixel($rotate, $destWidth - $x - 1, $destHeight - $y - 1, $color); + break; + case 270: + imagesetpixel($rotate, $destWidth - $y - 1, $x, $color); + break; + default: $rotate = $this->gd; + }; + } + } + return $rotate; + } /** @@ -271,7 +271,7 @@ class GDBackend extends Object implements Image_Backend { return $output; } - /** + /** * Method return width of image. * * @return integer width. @@ -334,9 +334,9 @@ class GDBackend extends Object implements Image_Backend { /** * Resize to fit fully within the given box, without resizing. Extra space left around * the image will be padded with the background color. - * @param width - * @param height - * @param backgroundColour + * @param width + * @param height + * @param backgroundColour */ public function paddedResize($width, $height, $backgroundColor = "FFFFFF") { if(!$this->gd) return; @@ -476,4 +476,4 @@ class GD extends GDBackend { ); GDBackend::set_default_quality($quality); } -} \ No newline at end of file +} diff --git a/forms/AjaxUniqueTextField.php b/forms/AjaxUniqueTextField.php index 3133eb362..9b368f2b3 100644 --- a/forms/AjaxUniqueTextField.php +++ b/forms/AjaxUniqueTextField.php @@ -29,7 +29,7 @@ class AjaxUniqueTextField extends TextField { parent::__construct($name, $title, $value); } - + public function Field($properties = array()) { $url = Convert::raw2att( $this->validateURL ); diff --git a/forms/CheckboxSetField.php b/forms/CheckboxSetField.php index 9db5c51d6..9d174706c 100644 --- a/forms/CheckboxSetField.php +++ b/forms/CheckboxSetField.php @@ -79,7 +79,7 @@ class CheckboxSetField extends OptionsetField { if(is_a($object, 'DataObject')) { $items[] = $object->ID; } - } + } } elseif($values && is_string($values)) { $items = explode(',', $values); $items = str_replace('{comma}', ',', $items); diff --git a/forms/CompositeField.php b/forms/CompositeField.php index 1887b5b53..a41c4555c 100644 --- a/forms/CompositeField.php +++ b/forms/CompositeField.php @@ -163,8 +163,8 @@ class CompositeField extends FormField { $formName = (isset($this->form)) ? $this->form->FormName() : '(unknown form)'; if(isset($list[$name])) { user_error("collateDataFields() I noticed that a field called '$name' appears twice in" - . " your form: '{$formName}'. One is a '{$field->class}' and the other is a" - . " '{$list[$name]->class}'", E_USER_ERROR); + . " your form: '{$formName}'. One is a '{$field->class}' and the other is a" + . " '{$list[$name]->class}'", E_USER_ERROR); } $list[$name] = $field; } diff --git a/forms/CreditCardField.php b/forms/CreditCardField.php index 9f1f1f886..7e157cc2e 100644 --- a/forms/CreditCardField.php +++ b/forms/CreditCardField.php @@ -52,7 +52,7 @@ class CreditCardField extends TextField { if($this->value) foreach($this->value as $part){ if(!$part || !(strlen($part) == 4) || !preg_match("/([0-9]{4})/", $part)){ switch($i){ - case 0: $number = _t('CreditCardField.FIRST', 'first'); break; + case 0: $number = _t('CreditCardField.FIRST', 'first'); break; case 1: $number = _t('CreditCardField.SECOND', 'second'); break; case 2: $number = _t('CreditCardField.THIRD', 'third'); break; case 3: $number = _t('CreditCardField.FOURTH', 'fourth'); break; diff --git a/forms/DateField.php b/forms/DateField.php index 9d39b6044..0a8c4e191 100644 --- a/forms/DateField.php +++ b/forms/DateField.php @@ -43,9 +43,9 @@ require_once 'Zend/Date.php'; * * ## Example: German dates with separate fields for day, month, year * - * $f = new DateField('MyDate'); - * $f->setLocale('de_DE'); - * $f->setConfig('dmyfields'); + * $f = new DateField('MyDate'); + * $f->setLocale('de_DE'); + * $f->setConfig('dmyfields'); * * # Validation * @@ -314,11 +314,11 @@ class DateField extends TextField { * @return boolean */ public static function set_default_config($k, $v) { - if (array_key_exists($k,self::$default_config)) { - self::$default_config[$k]=$v; - return true; - } - return false; + if (array_key_exists($k,self::$default_config)) { + self::$default_config[$k]=$v; + return true; + } + return false; } /** @@ -567,7 +567,7 @@ class DateField_View_JQuery extends Object { // Include language files (if required) if ($this->jqueryLocaleFile){ Requirements::javascript($this->jqueryLocaleFile); - } + } Requirements::javascript(FRAMEWORK_DIR . "/javascript/DateField.js"); } @@ -609,51 +609,51 @@ class DateField_View_JQuery extends Object { public static function convert_iso_to_jquery_format($format) { $convert = array( '/([^d])d([^d])/' => '$1d$2', - '/^d([^d])/' => 'd$1', - '/([^d])d$/' => '$1d', - '/dd/' => 'dd', - '/SS/' => '', - '/eee/' => 'd', - '/e/' => 'N', - '/D/' => '', - '/EEEE/' => 'DD', - '/EEE/' => 'D', - '/w/' => '', + '/^d([^d])/' => 'd$1', + '/([^d])d$/' => '$1d', + '/dd/' => 'dd', + '/SS/' => '', + '/eee/' => 'd', + '/e/' => 'N', + '/D/' => '', + '/EEEE/' => 'DD', + '/EEE/' => 'D', + '/w/' => '', // make single "M" lowercase - '/([^M])M([^M])/' => '$1m$2', + '/([^M])M([^M])/' => '$1m$2', // make single "M" at start of line lowercase - '/^M([^M])/' => 'm$1', + '/^M([^M])/' => 'm$1', // make single "M" at end of line lowercase - '/([^M])M$/' => '$1m', + '/([^M])M$/' => '$1m', // match exactly three capital Ms not preceeded or followed by an M - '/(? 'M', + '/(? 'M', // match exactly two capital Ms not preceeded or followed by an M - '/(? 'mm', + '/(? 'mm', // match four capital Ms (maximum allowed) - '/MMMM/' => 'MM', - '/l/' => '', - '/YYYY/' => 'yy', - '/yyyy/' => 'yy', - // See http://open.silverstripe.org/ticket/7669 - '/y{1,3}/' => 'yy', - '/a/' => '', - '/B/' => '', - '/hh/' => '', - '/h/' => '', - '/([^H])H([^H])/' => '', - '/^H([^H])/' => '', - '/([^H])H$/' => '', - '/HH/' => '', - // '/mm/' => '', - '/ss/' => '', - '/zzzz/' => '', - '/I/' => '', - '/ZZZZ/' => '', - '/Z/' => '', - '/z/' => '', - '/X/' => '', - '/r/' => '', - '/U/' => '', + '/MMMM/' => 'MM', + '/l/' => '', + '/YYYY/' => 'yy', + '/yyyy/' => 'yy', + // See http://open.silverstripe.org/ticket/7669 + '/y{1,3}/' => 'yy', + '/a/' => '', + '/B/' => '', + '/hh/' => '', + '/h/' => '', + '/([^H])H([^H])/' => '', + '/^H([^H])/' => '', + '/([^H])H$/' => '', + '/HH/' => '', + // '/mm/' => '', + '/ss/' => '', + '/zzzz/' => '', + '/I/' => '', + '/ZZZZ/' => '', + '/Z/' => '', + '/z/' => '', + '/X/' => '', + '/r/' => '', + '/U/' => '', ); $patterns = array_keys($convert); $replacements = array_values($convert); diff --git a/forms/FieldGroup.php b/forms/FieldGroup.php index 3ce94e97d..f3da6571f 100644 --- a/forms/FieldGroup.php +++ b/forms/FieldGroup.php @@ -90,12 +90,12 @@ class FieldGroup extends CompositeField { * * @param string $zebra one of odd or even. */ - public function setZebra($zebra) { - if($zebra == 'odd' || $zebra == 'even') $this->zebra = $zebra; - else user_error("setZebra passed '$zebra'. It should be passed 'odd' or 'even'", E_USER_WARNING); - return $this; - } - + public function setZebra($zebra) { + if($zebra == 'odd' || $zebra == 'even') $this->zebra = $zebra; + else user_error("setZebra passed '$zebra'. It should be passed 'odd' or 'even'", E_USER_WARNING); + return $this; + } + /** * @return string */ @@ -132,4 +132,4 @@ class FieldGroup extends CompositeField { public function php($data) { return; } -} \ No newline at end of file +} diff --git a/forms/Form.php b/forms/Form.php index f137587a6..0600c8b60 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -999,7 +999,7 @@ class Form extends RequestHandler { * * @return boolean */ - public function validate(){ + public function validate(){ if($this->validator){ $errors = $this->validator->validate(); @@ -1420,7 +1420,7 @@ class Form extends RequestHandler { $result .= ""; if( $this->validator ) - $result .= '

    '._t('Form.VALIDATOR', 'Validator').'

    ' . $this->validator->debug(); + $result .= '

    '._t('Form.VALIDATOR', 'Validator').'

    ' . $this->validator->debug(); return $result; } @@ -1437,8 +1437,8 @@ class Form extends RequestHandler { */ public function testSubmission($action, $data) { $data['action_' . $action] = true; - - return Director::test($this->FormAction(), $data, Controller::curr()->getSession()); + + return Director::test($this->FormAction(), $data, Controller::curr()->getSession()); //$response = $this->controller->run($data); //return $response; diff --git a/forms/FormField.php b/forms/FormField.php index 0ec3dd06a..7e1fb80d0 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -85,7 +85,7 @@ class FormField extends RequestHandler { protected $template, $fieldHolderTemplate, - $smallFieldHolderTemplate; + $smallFieldHolderTemplate; /** * @var array All attributes on the form field (not the field holder). @@ -582,14 +582,14 @@ class FormField extends RequestHandler { return $obj->renderWith($this->getFieldHolderTemplates()); } - /** - * Returns a restricted field holder used within things like FieldGroups. - * - * @param array $properties - * - * @return string - */ - public function SmallFieldHolder($properties = array()) { + /** + * Returns a restricted field holder used within things like FieldGroups. + * + * @param array $properties + * + * @return string + */ + public function SmallFieldHolder($properties = array()) { $obj = ($properties) ? $this->customise($properties) : $this; return $obj->renderWith($this->getSmallFieldHolderTemplates()); diff --git a/forms/HtmlEditorField.php b/forms/HtmlEditorField.php index 791f01d0a..e4c0a7fa6 100644 --- a/forms/HtmlEditorField.php +++ b/forms/HtmlEditorField.php @@ -175,8 +175,8 @@ class HtmlEditorField extends TextareaField { // Save file & link tracking data. if(class_exists('SiteTree')) { if($record->ID && $record->many_many('LinkTracking') && $tracker = $record->LinkTracking()) { - $tracker->removeByFilter(sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', - $this->name, $record->ID)); + $tracker->removeByFilter(sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', + $this->name, $record->ID)); if($linkedPages) foreach($linkedPages as $item) { $SQL_fieldName = Convert::raw2sql($this->name); @@ -186,9 +186,9 @@ class HtmlEditorField extends TextareaField { } if($record->ID && $record->many_many('ImageTracking') && $tracker = $record->ImageTracking()) { - $tracker->where( - sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', $this->name, $record->ID) - )->removeAll(); + $tracker->where( + sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', $this->name, $record->ID) + )->removeAll(); $fieldName = $this->name; if($linkedFiles) foreach($linkedFiles as $item) { diff --git a/forms/NumericField.php b/forms/NumericField.php index a506960d6..ba519dae6 100644 --- a/forms/NumericField.php +++ b/forms/NumericField.php @@ -14,8 +14,8 @@ class NumericField extends TextField{ /** PHP Validation **/ public function validate($validator){ if($this->value && !is_numeric(trim($this->value))){ - $validator->validationError( - $this->name, + $validator->validationError( + $this->name, _t( 'NumericField.VALIDATION', "'{value}' is not a number, only numbers can be accepted for this field", array('value' => $this->value) diff --git a/forms/PhoneNumberField.php b/forms/PhoneNumberField.php index b2f0e599d..93c138bfb 100644 --- a/forms/PhoneNumberField.php +++ b/forms/PhoneNumberField.php @@ -33,11 +33,11 @@ class PhoneNumberField extends FormField { list($countryCode, $areaCode, $phoneNumber, $extension) = $this->parseValue(); $hasTitle = false; - if ($this->value=="") { - $countryCode=$this->countryCode; - $areaCode=$this->areaCode; - $extension=$this->ext; - } + if ($this->value=="") { + $countryCode=$this->countryCode; + $areaCode=$this->areaCode; + $extension=$this->ext; + } if($this->countryCode !== null) { $fields->push(new NumericField($this->name.'[Country]', '+', $countryCode, 4)); @@ -94,7 +94,7 @@ class PhoneNumberField extends FormField { preg_match( '/^(?:(?:\+(\d+))?\s*\((\d+)\))?\s*([0-9A-Za-z]*)\s*(?:[#]\s*(\d+))?$/', $this->value, $parts); else return array( '', '', $this->value, '' ); - + if(is_array($parts)) array_shift( $parts ); for ($x=0;$x<=3;$x++) { diff --git a/forms/RequiredFields.php b/forms/RequiredFields.php index abcaebe94..536140d9c 100644 --- a/forms/RequiredFields.php +++ b/forms/RequiredFields.php @@ -42,15 +42,15 @@ class RequiredFields extends Validator { * Debug helper */ public function debug() { - if(!is_array($this->required)) return false; + if(!is_array($this->required)) return false; - $result = "
      "; - foreach( $this->required as $name ){ - $result .= "
    • $name
    • "; - } + $result = "
        "; + foreach( $this->required as $name ){ + $result .= "
      • $name
      • "; + } - $result .= "
      "; - return $result; + $result .= "
    "; + return $result; } /** diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index 4cbaec58e..21116a7b1 100644 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -211,7 +211,7 @@ class GridField extends FormField { public function getManipulatedList() { $list = $this->getList(); foreach($this->getComponents() as $item) { - if($item instanceof GridField_DataManipulator) { + if($item instanceof GridField_DataManipulator) { $list = $item->getManipulatedData($this, $list); } } diff --git a/javascript/GridField.js b/javascript/GridField.js index 47663e17e..0eed43a79 100644 --- a/javascript/GridField.js +++ b/javascript/GridField.js @@ -244,7 +244,6 @@ this._super(); this.selectable('destroy'); } - }); /** diff --git a/javascript/HtmlEditorField.js b/javascript/HtmlEditorField.js index 4cb948625..5f7724eb5 100644 --- a/javascript/HtmlEditorField.js +++ b/javascript/HtmlEditorField.js @@ -6,15 +6,15 @@ * ajax / iframe submissions */ - var ss = ss || {}; +var ss = ss || {}; /** * Wrapper for HTML WYSIWYG libraries, which abstracts library internals * from interface concerns like inserting and editing links. * Caution: Incomplete and unstable API. */ - ss.editorWrappers = {}; - ss.editorWrappers.initial - ss.editorWrappers.tinyMCE = (function() { +ss.editorWrappers = {}; +ss.editorWrappers.initial +ss.editorWrappers.tinyMCE = (function() { return { init: function(config) { if(!ss.editorWrappers.tinyMCE.initialized) { @@ -807,7 +807,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; if(header) header[(hasItems) ? 'show' : 'hide'](); // Disable "insert" button if no files are selected - this.find('.Actions :submit') + this.find('.Actions :submit') .button(hasItems ? 'enable' : 'disable') .toggleClass('ui-state-disabled', !hasItems); diff --git a/javascript/i18n.js b/javascript/i18n.js index cb99aa58b..ab8be9003 100644 --- a/javascript/i18n.js +++ b/javascript/i18n.js @@ -108,7 +108,7 @@ ss.i18n = { * @return string result : Stripped string * */ - stripStr: function(str) { + stripStr: function(str) { return str.replace(/^\s*/, "").replace(/\s*$/, ""); }, @@ -228,4 +228,4 @@ ss.i18n = { ss.i18n.addEvent(window, "load", function() { ss.i18n.init(); -}); \ No newline at end of file +}); diff --git a/javascript/jquery-ondemand/jquery.ondemand.js b/javascript/jquery-ondemand/jquery.ondemand.js index 6653cdce3..cbe6529df 100644 --- a/javascript/jquery-ondemand/jquery.ondemand.js +++ b/javascript/jquery-ondemand/jquery.ondemand.js @@ -23,7 +23,7 @@ // loaded files list - to protect against loading existed file again (by PGA) _ondemand_loaded_list : null, - + /** * Returns true if the given CSS or JS script has already been loaded */ @@ -160,4 +160,4 @@ }); -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/javascript/tree/tree.js b/javascript/tree/tree.js index c945bd29b..909fd95d1 100644 --- a/javascript/tree/tree.js +++ b/javascript/tree/tree.js @@ -212,7 +212,7 @@ Tree.prototype = { } // Update the helper classes accordingly - if(!hasChildren) this.removeNodeClass('children'); + if(!hasChildren) this.removeNodeClass('children'); else this.lastTreeNode().addNodeClass('last'); // Update the helper variables @@ -303,8 +303,8 @@ TreeNode.prototype = { // Move all the nodes up until that point into spanC for(j=startingPoint;jdataQuery()->query()->canSortBy($fieldName); + return $this->dataQuery()->query()->canSortBy($fieldName); } /** @@ -718,7 +718,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return mixed */ public function max($fieldName) { - return $this->dataQuery->max($fieldName); + return $this->dataQuery->max($fieldName); } /** @@ -728,7 +728,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return mixed */ public function min($fieldName) { - return $this->dataQuery->min($fieldName); + return $this->dataQuery->min($fieldName); } /** @@ -738,7 +738,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return mixed */ public function avg($fieldName) { - return $this->dataQuery->avg($fieldName); + return $this->dataQuery->avg($fieldName); } /** @@ -748,7 +748,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return mixed */ public function sum($fieldName) { - return $this->dataQuery->sum($fieldName); + return $this->dataQuery->sum($fieldName); } @@ -878,7 +878,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab // Index current data foreach($this->column() as $id) { - $has[$id] = true; + $has[$id] = true; } // Keep track of items to delete @@ -996,7 +996,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab */ public function newObject($initialFields = null) { $class = $this->dataClass; - return Injector::inst()->create($class, $initialFields, false, $this->model); + return Injector::inst()->create($class, $initialFields, false, $this->model); } /** @@ -1012,14 +1012,14 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab } - /** - * Remove an item from this DataList by ID + /** + * Remove an item from this DataList by ID * * @param int $itemID - The primary ID - */ + */ public function removeByID($itemID) { - $item = $this->byID($itemID); - if($item) return $item->delete(); + $item = $this->byID($itemID); + if($item) return $item->delete(); } /** @@ -1091,7 +1091,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return bool */ public function offsetExists($key) { - return ($this->limit(1,$key)->First() != null); + return ($this->limit(1,$key)->First() != null); } /** @@ -1101,7 +1101,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return DataObject */ public function offsetGet($key) { - return $this->limit(1, $key)->First(); + return $this->limit(1, $key)->First(); } /** @@ -1111,7 +1111,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @param mixed $value */ public function offsetSet($key, $value) { - user_error("Can't alter items in a DataList using array-access", E_USER_ERROR); + user_error("Can't alter items in a DataList using array-access", E_USER_ERROR); } /** @@ -1120,7 +1120,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @param mixed $key */ public function offsetUnset($key) { - user_error("Can't alter items in a DataList using array-access", E_USER_ERROR); + user_error("Can't alter items in a DataList using array-access", E_USER_ERROR); } } diff --git a/model/DataObject.php b/model/DataObject.php index a5f37b79c..6c271d81c 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -13,46 +13,46 @@ * * * class Article extends DataObject implements PermissionProvider { - * static $api_access = true; - * - * function canView($member = false) { - * return Permission::check('ARTICLE_VIEW'); - * } - * function canEdit($member = false) { - * return Permission::check('ARTICLE_EDIT'); - * } - * function canDelete() { - * return Permission::check('ARTICLE_DELETE'); - * } - * function canCreate() { - * return Permission::check('ARTICLE_CREATE'); - * } - * function providePermissions() { - * return array( - * 'ARTICLE_VIEW' => 'Read an article object', - * 'ARTICLE_EDIT' => 'Edit an article object', - * 'ARTICLE_DELETE' => 'Delete an article object', - * 'ARTICLE_CREATE' => 'Create an article object', - * ); - * } + * static $api_access = true; + * + * function canView($member = false) { + * return Permission::check('ARTICLE_VIEW'); + * } + * function canEdit($member = false) { + * return Permission::check('ARTICLE_EDIT'); + * } + * function canDelete() { + * return Permission::check('ARTICLE_DELETE'); + * } + * function canCreate() { + * return Permission::check('ARTICLE_CREATE'); + * } + * function providePermissions() { + * return array( + * 'ARTICLE_VIEW' => 'Read an article object', + * 'ARTICLE_EDIT' => 'Edit an article object', + * 'ARTICLE_DELETE' => 'Delete an article object', + * 'ARTICLE_CREATE' => 'Create an article object', + * ); + * } * } * * * Object-level access control by {@link Group} membership: * * class Article extends DataObject { - * static $api_access = true; - * - * function canView($member = false) { - * if(!$member) $member = Member::currentUser(); - * return $member->inGroup('Subscribers'); - * } - * function canEdit($member = false) { - * if(!$member) $member = Member::currentUser(); - * return $member->inGroup('Editors'); - * } - * - * // ... + * static $api_access = true; + * + * function canView($member = false) { + * if(!$member) $member = Member::currentUser(); + * return $member->inGroup('Subscribers'); + * } + * function canEdit($member = false) { + * if(!$member) $member = Member::currentUser(); + * return $member->inGroup('Editors'); + * } + * + * // ... * } * * @@ -818,7 +818,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * @param $priority String left|right Determines who wins in case of a conflict (optional) * @param $includeRelations Boolean Merge any existing relations (optional) * @param $overwriteWithEmpty Boolean Overwrite existing left values with empty right values. - * Only applicable with $priority='right'. (optional) + * Only applicable with $priority='right'. (optional) * @return Boolean */ public function merge($rightObj, $priority = 'right', $includeRelations = true, $overwriteWithEmpty = false) { @@ -997,7 +997,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity if($defaults && !is_array($defaults)) { user_error("Bad '$this->class' defaults given: " . var_export($defaults, true), - E_USER_WARNING); + E_USER_WARNING); $defaults = null; } @@ -1258,8 +1258,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity . " Make sure that you call parent::onBeforeDelete().", E_USER_ERROR); } - // Deleting a record without an ID shouldn't do anything - if(!$this->ID) throw new LogicException("DataObject::delete() called on a DataObject without an ID"); + // Deleting a record without an ID shouldn't do anything + if(!$this->ID) throw new LogicException("DataObject::delete() called on a DataObject without an ID"); // TODO: This is quite ugly. To improve: // - move the details of the delete code in the DataQuery system @@ -1868,8 +1868,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * Used by {@link SearchContext}. * * @param array $_params - * 'fieldClasses': Associative array of field names as keys and FormField classes as values - * 'restrictFields': Numeric array of a field name whitelist + * 'fieldClasses': Associative array of field names as keys and FormField classes as values + * 'restrictFields': Numeric array of a field name whitelist * @return FieldList */ public function scaffoldSearchFields($_params = null) { @@ -1960,14 +1960,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * or extended onto it by using {@link DataExtension->updateCMSFields()}. * * - * klass MyCustomClass extends DataObject { - * static $db = array('CustomProperty'=>'Boolean'); + * class MyCustomClass extends DataObject { + * static $db = array('CustomProperty'=>'Boolean'); * - * function getCMSFields() { - * $fields = parent::getCMSFields(); - * $fields->addFieldToTab('Root.Content',new CheckboxField('CustomProperty')); - * return $fields; - * } + * function getCMSFields() { + * $fields = parent::getCMSFields(); + * $fields->addFieldToTab('Root.Content',new CheckboxField('CustomProperty')); + * return $fields; + * } * } * * @@ -3130,8 +3130,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity // var_dump("{$ancestorClass}.{$type}_{$name}"); $autoLabels[$name] = _t("{$ancestorClass}.{$type}_{$name}",FormField::name_to_label($name)); } - } - } + } + } $labels = array_merge((array)$autoLabels, (array)$customLabels); $this->extend('updateFieldLabels', $labels); @@ -3278,7 +3278,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * * * array( - * 'MySQLDatabase' => 'ENGINE=MyISAM' + * 'MySQLDatabase' => 'ENGINE=MyISAM' * ) * * @@ -3321,8 +3321,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * * Example: * array( - * array('Title' => "DefaultPage1", 'PageTitle' => 'page1'), - * array('Title' => "DefaultPage2") + * array('Title' => "DefaultPage1", 'PageTitle' => 'page1'), + * array('Title' => "DefaultPage2") * ). * * @var array @@ -3378,7 +3378,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * Example code: * * public static $many_many_extraFields = array( - * 'Members' => array( + * 'Members' => array( * 'Role' => 'Varchar(100)' * ) * ); @@ -3408,8 +3408,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * * Overriding the default filter, with a custom defined filter: * - * static $searchable_fields = array( - * "Name" => "PartialMatchFilter" + * static $searchable_fields = array( + * "Name" => "PartialMatchFilter" * ); * * @@ -3417,21 +3417,21 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * The 'filter' parameter will be generated from {@link DBField::$default_search_filter_class}. * The 'title' parameter will be generated from {@link DataObject->fieldLabels()}. * - * static $searchable_fields = array( - * "Name" => array( - * "field" => "TextField" - * ) + * static $searchable_fields = array( + * "Name" => array( + * "field" => "TextField" + * ) * ); * * * Overriding the default form field, filter and title: * - * static $searchable_fields = array( - * "Organisation.ZipCode" => array( - * "field" => "TextField", - * "filter" => "PartialMatchFilter", - * "title" => 'Organisation ZIP' - * ) + * static $searchable_fields = array( + * "Organisation.ZipCode" => array( + * "field" => "TextField", + * "filter" => "PartialMatchFilter", + * "title" => 'Organisation ZIP' + * ) * ); * */ @@ -3483,21 +3483,21 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity } /** - * Returns true if the given method/parameter has a value - * (Uses the DBField::hasValue if the parameter is a database field) - * + * Returns true if the given method/parameter has a value + * (Uses the DBField::hasValue if the parameter is a database field) + * * @param string $field The field name * @param array $arguments * @param bool $cache - * @return boolean - */ - public function hasValue($field, $arguments = null, $cache = true) { - $obj = $this->dbObject($field); - if($obj) { - return $obj->exists(); - } else { - return parent::hasValue($field, $arguments, $cache); - } - } + * @return boolean + */ + public function hasValue($field, $arguments = null, $cache = true) { + $obj = $this->dbObject($field); + if($obj) { + return $obj->exists(); + } else { + return parent::hasValue($field, $arguments, $cache); + } + } } diff --git a/model/DataQuery.php b/model/DataQuery.php index d9b195972..bd1e968f3 100644 --- a/model/DataQuery.php +++ b/model/DataQuery.php @@ -338,7 +338,7 @@ class DataQuery { * @param String $field Unquoted database column name (will be escaped automatically) */ public function max($field) { - return $this->aggregate(sprintf('MAX("%s")', Convert::raw2sql($field))); + return $this->aggregate(sprintf('MAX("%s")', Convert::raw2sql($field))); } /** @@ -347,7 +347,7 @@ class DataQuery { * @param String $field Unquoted database column name (will be escaped automatically) */ public function min($field) { - return $this->aggregate(sprintf('MIN("%s")', Convert::raw2sql($field))); + return $this->aggregate(sprintf('MIN("%s")', Convert::raw2sql($field))); } /** @@ -356,7 +356,7 @@ class DataQuery { * @param String $field Unquoted database column name (will be escaped automatically) */ public function avg($field) { - return $this->aggregate(sprintf('AVG("%s")', Convert::raw2sql($field))); + return $this->aggregate(sprintf('AVG("%s")', Convert::raw2sql($field))); } /** @@ -365,14 +365,14 @@ class DataQuery { * @param String $field Unquoted database column name (will be escaped automatically) */ public function sum($field) { - return $this->aggregate(sprintf('SUM("%s")', Convert::raw2sql($field))); + return $this->aggregate(sprintf('SUM("%s")', Convert::raw2sql($field))); } /** * Runs a raw aggregate expression. Please handle escaping yourself */ public function aggregate($expression) { - return $this->getFinalisedQuery()->aggregate($expression)->execute()->value(); + return $this->getFinalisedQuery()->aggregate($expression)->execute()->value(); } /** @@ -567,74 +567,74 @@ class DataQuery { * @return The model class of the related item */ public function applyRelation($relation) { - // NO-OP - if(!$relation) return $this->dataClass; - - if(is_string($relation)) $relation = explode(".", $relation); - - $modelClass = $this->dataClass; - - foreach($relation as $rel) { - $model = singleton($modelClass); - if ($component = $model->has_one($rel)) { - if(!$this->query->isJoinedTo($component)) { - $foreignKey = $model->getReverseAssociation($component); - $this->query->addLeftJoin($component, - "\"$component\".\"ID\" = \"{$modelClass}\".\"{$foreignKey}ID\""); + // NO-OP + if(!$relation) return $this->dataClass; + + if(is_string($relation)) $relation = explode(".", $relation); + + $modelClass = $this->dataClass; + + foreach($relation as $rel) { + $model = singleton($modelClass); + if ($component = $model->has_one($rel)) { + if(!$this->query->isJoinedTo($component)) { + $foreignKey = $model->getReverseAssociation($component); + $this->query->addLeftJoin($component, + "\"$component\".\"ID\" = \"{$modelClass}\".\"{$foreignKey}ID\""); - /** - * add join clause to the component's ancestry classes so that the search filter could search on - * its ancestor fields. - */ - $ancestry = ClassInfo::ancestry($component, true); - if(!empty($ancestry)){ - $ancestry = array_reverse($ancestry); - foreach($ancestry as $ancestor){ - if($ancestor != $component){ - $this->query->addInnerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\""); - } - } - } - } - $modelClass = $component; + /** + * add join clause to the component's ancestry classes so that the search filter could search on + * its ancestor fields. + */ + $ancestry = ClassInfo::ancestry($component, true); + if(!empty($ancestry)){ + $ancestry = array_reverse($ancestry); + foreach($ancestry as $ancestor){ + if($ancestor != $component){ + $this->query->addInnerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\""); + } + } + } + } + $modelClass = $component; - } elseif ($component = $model->has_many($rel)) { - if(!$this->query->isJoinedTo($component)) { - $ancestry = $model->getClassAncestry(); - $foreignKey = $model->getRemoteJoinField($rel); - $this->query->addLeftJoin($component, - "\"$component\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\""); - /** - * add join clause to the component's ancestry classes so that the search filter could search on - * its ancestor fields. - */ - $ancestry = ClassInfo::ancestry($component, true); - if(!empty($ancestry)){ - $ancestry = array_reverse($ancestry); - foreach($ancestry as $ancestor){ - if($ancestor != $component){ - $this->query->addInnerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\""); - } - } - } - } - $modelClass = $component; + } elseif ($component = $model->has_many($rel)) { + if(!$this->query->isJoinedTo($component)) { + $ancestry = $model->getClassAncestry(); + $foreignKey = $model->getRemoteJoinField($rel); + $this->query->addLeftJoin($component, + "\"$component\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\""); + /** + * add join clause to the component's ancestry classes so that the search filter could search on + * its ancestor fields. + */ + $ancestry = ClassInfo::ancestry($component, true); + if(!empty($ancestry)){ + $ancestry = array_reverse($ancestry); + foreach($ancestry as $ancestor){ + if($ancestor != $component){ + $this->query->addInnerJoin($ancestor, "\"$component\".\"ID\" = \"$ancestor\".\"ID\""); + } + } + } + } + $modelClass = $component; - } elseif ($component = $model->many_many($rel)) { - list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component; - $parentBaseClass = ClassInfo::baseDataClass($parentClass); - $componentBaseClass = ClassInfo::baseDataClass($componentClass); - $this->query->addInnerJoin($relationTable, - "\"$relationTable\".\"$parentField\" = \"$parentBaseClass\".\"ID\""); - $this->query->addLeftJoin($componentBaseClass, - "\"$relationTable\".\"$componentField\" = \"$componentBaseClass\".\"ID\""); - if(ClassInfo::hasTable($componentClass)) { - $this->query->addLeftJoin($componentClass, - "\"$relationTable\".\"$componentField\" = \"$componentClass\".\"ID\""); - } - $modelClass = $componentClass; + } elseif ($component = $model->many_many($rel)) { + list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component; + $parentBaseClass = ClassInfo::baseDataClass($parentClass); + $componentBaseClass = ClassInfo::baseDataClass($componentClass); + $this->query->addInnerJoin($relationTable, + "\"$relationTable\".\"$parentField\" = \"$parentBaseClass\".\"ID\""); + $this->query->addLeftJoin($componentBaseClass, + "\"$relationTable\".\"$componentField\" = \"$componentBaseClass\".\"ID\""); + if(ClassInfo::hasTable($componentClass)) { + $this->query->addLeftJoin($componentClass, + "\"$relationTable\".\"$componentField\" = \"$componentClass\".\"ID\""); + } + $modelClass = $componentClass; - } + } } return $modelClass; diff --git a/model/Database.php b/model/Database.php index 7ede4e631..44093e881 100644 --- a/model/Database.php +++ b/model/Database.php @@ -422,7 +422,7 @@ abstract class SS_Database { //Indexes specified as arrays cannot be checked with this line: (it flattens out the array) if(!is_array($spec)) { $spec = preg_replace('/\s*,\s*/', ',', $spec); - } + } if(!isset($this->tableList[strtolower($table)])) $newTable = true; @@ -1142,7 +1142,7 @@ abstract class SS_Query implements Iterator { $result .= ""; foreach($record as $k => $v) { $result .= "" . Convert::raw2xml($k) . " "; - } + } $result .= " \n"; } @@ -1219,7 +1219,7 @@ abstract class SS_Query implements Iterator { */ public function valid() { if(!$this->queryHasBegun) $this->next(); - return $this->currentRecord !== false; + return $this->currentRecord !== false; } /** diff --git a/model/DatabaseAdmin.php b/model/DatabaseAdmin.php index 62d39154e..150045818 100644 --- a/model/DatabaseAdmin.php +++ b/model/DatabaseAdmin.php @@ -298,8 +298,8 @@ class DatabaseAdmin extends Controller { foreach($subclasses as $subclass) { $id = $record['ID']; if(($record['ClassName'] != $subclass) && - (!is_subclass_of($record['ClassName'], $subclass)) && - (isset($recordExists[$subclass][$id]))) { + (!is_subclass_of($record['ClassName'], $subclass)) && + (isset($recordExists[$subclass][$id]))) { $sql = "DELETE FROM \"$subclass\" WHERE \"ID\" = $record[ID]"; echo "
  • $sql"; DB::query($sql); diff --git a/model/HasManyList.php b/model/HasManyList.php index 290f8955a..c39efceb4 100644 --- a/model/HasManyList.php +++ b/model/HasManyList.php @@ -66,10 +66,10 @@ class HasManyList extends RelationList { * @param $itemID The ID of the item to be removed */ public function removeByID($itemID) { - $item = $this->byID($itemID); - return $this->remove($item); - } - + $item = $this->byID($itemID); + return $this->remove($item); + } + /** * Remove an item from this relation. * Doesn't actually remove the item, it just clears the foreign key value. @@ -77,10 +77,10 @@ class HasManyList extends RelationList { * @todo Maybe we should delete the object instead? */ public function remove($item) { - if(!($item instanceof $this->dataClass)) { - throw new InvalidArgumentException("HasManyList::remove() expecting a $this->dataClass object, or ID", - E_USER_ERROR); - } + if(!($item instanceof $this->dataClass)) { + throw new InvalidArgumentException("HasManyList::remove() expecting a $this->dataClass object, or ID", + E_USER_ERROR); + } $fk = $this->foreignKey; $item->$fk = null; diff --git a/model/Hierarchy.php b/model/Hierarchy.php index cd9d990f7..4fa8f98d9 100644 --- a/model/Hierarchy.php +++ b/model/Hierarchy.php @@ -434,14 +434,14 @@ class Hierarchy extends DataExtension { public function Children() { if(!(isset($this->_cache_children) && $this->_cache_children)) { $result = $this->owner->stageChildren(false); - if(isset($result)) { - $this->_cache_children = new ArrayList(); - foreach($result as $child) { - if($child->canView()) { - $this->_cache_children->push($child); - } - } - } + if(isset($result)) { + $this->_cache_children = new ArrayList(); + foreach($result as $child) { + if($child->canView()) { + $this->_cache_children->push($child); + } + } + } } return $this->_cache_children; } @@ -487,10 +487,10 @@ class Hierarchy extends DataExtension { // Next, go through the live children. Only some of these will be listed $liveChildren = $this->owner->liveChildren(true, true); if($liveChildren) { - $merged = new ArrayList(); - $merged->merge($stageChildren); - $merged->merge($liveChildren); - $stageChildren = $merged; + $merged = new ArrayList(); + $merged->merge($stageChildren); + $merged->merge($liveChildren); + $stageChildren = $merged; } } @@ -526,7 +526,7 @@ class Hierarchy extends DataExtension { throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied'); } - return Versioned::get_including_deleted(ClassInfo::baseDataClass($this->owner->class), + return Versioned::get_including_deleted(ClassInfo::baseDataClass($this->owner->class), "\"ParentID\" = " . (int)$this->owner->ID)->count(); } diff --git a/model/ManyManyList.php b/model/ManyManyList.php index e3e1a1b27..1cfb5069c 100644 --- a/model/ManyManyList.php +++ b/model/ManyManyList.php @@ -117,11 +117,11 @@ class ManyManyList extends RelationList { * @param $itemID The ID of the item to remove. */ public function remove($item) { - if(!($item instanceof $this->dataClass)) { - throw new InvalidArgumentException("ManyManyList::remove() expecting a $this->dataClass object"); - } - - return $this->removeByID($item->ID); + if(!($item instanceof $this->dataClass)) { + throw new InvalidArgumentException("ManyManyList::remove() expecting a $this->dataClass object"); + } + + return $this->removeByID($item->ID); } /** @@ -130,7 +130,7 @@ class ManyManyList extends RelationList { * @param $itemID The item it */ public function removeByID($itemID) { - if(!is_numeric($itemID)) throw new InvalidArgumentException("ManyManyList::removeById() expecting an ID"); + if(!is_numeric($itemID)) throw new InvalidArgumentException("ManyManyList::removeById() expecting an ID"); $query = new SQLQuery("*", array("\"$this->joinTable\"")); $query->setDelete(true); @@ -145,16 +145,16 @@ class ManyManyList extends RelationList { $query->execute(); } - /** - * Remove all items from this many-many join. To remove a subset of items, filter it first. - */ - public function removeAll() { + /** + * Remove all items from this many-many join. To remove a subset of items, filter it first. + */ + public function removeAll() { $query = $this->dataQuery()->query(); $query->setDelete(true); $query->setSelect(array('*')); $query->setFrom("\"$this->joinTable\""); $query->execute(); - } + } /** * Find the extra field data for a single row of the relationship diff --git a/model/MySQLDatabase.php b/model/MySQLDatabase.php index c0069bead..e85f26123 100644 --- a/model/MySQLDatabase.php +++ b/model/MySQLDatabase.php @@ -269,8 +269,8 @@ class MySQLDatabase extends SS_Database { if($alteredIndexes) foreach($alteredIndexes as $k => $v) { $alterList[] .= "DROP INDEX \"$k\""; $alterList[] .= "ADD ". $this->getIndexSqlDefinition($k, $v); - } - + } + if($alteredOptions && isset($alteredOptions[get_class($this)])) { if(!isset($this->indexList[$tableName])) { $this->indexList[$tableName] = $this->indexList($tableName); @@ -298,7 +298,7 @@ class MySQLDatabase extends SS_Database { } } - $alterations = implode(",\n", $alterList); + $alterations = implode(",\n", $alterList); $this->query("ALTER TABLE \"$tableName\" $alterations"); } @@ -467,9 +467,9 @@ class MySQLDatabase extends SS_Database { $indexSpec = trim($indexSpec); if($indexSpec[0] != '(') list($indexType, $indexFields) = explode(' ',$indexSpec,2); - else $indexFields = $indexSpec; + else $indexFields = $indexSpec; - if(!isset($indexType)) + if(!isset($indexType)) $indexType = "index"; if($indexType=='using') @@ -499,15 +499,15 @@ class MySQLDatabase extends SS_Database { $indexSpec=$this->convertIndexSpec($indexSpec); $indexSpec = trim($indexSpec); - if($indexSpec[0] != '(') { - list($indexType, $indexFields) = explode(' ',$indexSpec,2); - } else { - $indexFields = $indexSpec; - } + if($indexSpec[0] != '(') { + list($indexType, $indexFields) = explode(' ',$indexSpec,2); + } else { + $indexFields = $indexSpec; + } - if(!$indexType) { - $indexType = "index"; - } + if(!$indexType) { + $indexType = "index"; + } $this->query("ALTER TABLE \"$tableName\" DROP INDEX \"$indexName\""); $this->query("ALTER TABLE \"$tableName\" ADD $indexType \"$indexName\" $indexFields"); @@ -822,19 +822,19 @@ class MySQLDatabase extends SS_Database { if(!class_exists('File')) throw new Exception('MySQLDatabase->searchEngine() requires "File" class'); $fileFilter = ''; - $keywords = Convert::raw2sql($keywords); + $keywords = Convert::raw2sql($keywords); $htmlEntityKeywords = htmlentities($keywords, ENT_NOQUOTES, 'UTF-8'); $extraFilters = array('SiteTree' => '', 'File' => ''); - if($booleanSearch) $boolean = "IN BOOLEAN MODE"; + if($booleanSearch) $boolean = "IN BOOLEAN MODE"; - if($extraFilter) { - $extraFilters['SiteTree'] = " AND $extraFilter"; + if($extraFilter) { + $extraFilters['SiteTree'] = " AND $extraFilter"; - if($alternativeFileFilter) $extraFilters['File'] = " AND $alternativeFileFilter"; - else $extraFilters['File'] = $extraFilters['SiteTree']; - } + if($alternativeFileFilter) $extraFilters['File'] = " AND $alternativeFileFilter"; + else $extraFilters['File'] = $extraFilters['SiteTree']; + } // Always ensure that only pages with ShowInSearch = 1 can be searched $extraFilters['SiteTree'] .= " AND ShowInSearch <> 0"; @@ -982,7 +982,7 @@ class MySQLDatabase extends SS_Database { $boolean = $booleanSearch ? "IN BOOLEAN MODE" : ""; $fieldNames = '"' . implode('", "', $fields) . '"'; - $SQL_keywords = Convert::raw2sql($keywords); + $SQL_keywords = Convert::raw2sql($keywords); $SQL_htmlEntityKeywords = Convert::raw2sql(htmlentities($keywords, ENT_NOQUOTES, 'UTF-8')); return "(MATCH ($fieldNames) AGAINST ('$SQL_keywords' $boolean) + MATCH ($fieldNames)" @@ -1151,7 +1151,7 @@ class MySQLDatabase extends SS_Database { * @param string $date2 to be substracted of $date1, can be either 'now', literal datetime like * '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"' * @return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which - * is the result of the substraction + * is the result of the substraction */ public function datetimeDifferenceClause($date1, $date2) { diff --git a/model/SQLQuery.php b/model/SQLQuery.php index 5a2a633e3..7981c61d4 100644 --- a/model/SQLQuery.php +++ b/model/SQLQuery.php @@ -908,11 +908,11 @@ class SQLQuery { * @return string */ public function __toString() { - try { - return $this->sql(); - } catch(Exception $e) { - return ""; - } + try { + return $this->sql(); + } catch(Exception $e) { + return ""; + } } /** diff --git a/model/Transliterator.php b/model/Transliterator.php index f8753bc3e..4a8a5018f 100644 --- a/model/Transliterator.php +++ b/model/Transliterator.php @@ -53,6 +53,6 @@ class SS_Transliterator extends Object { * Transliteration using iconv() */ protected function useIconv($source) { - return iconv("utf-8", "us-ascii//IGNORE//TRANSLIT", $source); + return iconv("utf-8", "us-ascii//IGNORE//TRANSLIT", $source); } } diff --git a/model/Versioned.php b/model/Versioned.php index ac7140c5b..a74928408 100644 --- a/model/Versioned.php +++ b/model/Versioned.php @@ -136,8 +136,8 @@ class Versioned extends DataExtension { * @todo Should this all go into VersionedDataQuery? */ public function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null) { - $baseTable = ClassInfo::baseDataClass($dataQuery->dataClass()); - + $baseTable = ClassInfo::baseDataClass($dataQuery->dataClass()); + switch($dataQuery->getQueryParam('Versioned.mode')) { // Noop case '': @@ -266,8 +266,8 @@ class Versioned extends DataExtension { */ function augmentLoadLazyFields(SQLQuery &$query, DataQuery &$dataQuery = null, $record) { $dataClass = $dataQuery->dataClass(); - if (isset($record['Version'])){ - $dataQuery->where("\"$dataClass\".\"RecordID\" = " . $record['ID']); + if (isset($record['Version'])){ + $dataQuery->where("\"$dataClass\".\"RecordID\" = " . $record['ID']); $dataQuery->where("\"$dataClass\".\"Version\" = " . $record['Version']); $dataQuery->setQueryParam('Versioned.mode', 'all_versions'); } diff --git a/model/fieldtypes/Date.php b/model/fieldtypes/Date.php index 5e3851cd4..f789dd82c 100644 --- a/model/fieldtypes/Date.php +++ b/model/fieldtypes/Date.php @@ -330,23 +330,23 @@ class Date extends DBField { public function days_between($fyear, $fmonth, $fday, $tyear, $tmonth, $tday){ - return abs((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, $tmonth, $tday, $tyear))/(60*60*24)); + return abs((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, $tmonth, $tday, $tyear))/(60*60*24)); } public function day_before($fyear, $fmonth, $fday){ - return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-1,$fyear)); + return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-1,$fyear)); } public function next_day($fyear, $fmonth, $fday){ - return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday+1,$fyear)); + return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday+1,$fyear)); } public function weekday($fyear, $fmonth, $fday){ // 0 is a Monday - return (((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, 7, 17, 2006))/(60*60*24))+700000) % 7; + return (((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, 7, 17, 2006))/(60*60*24))+700000) % 7; } public function prior_monday($fyear, $fmonth, $fday){ - return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-$this->weekday($fyear, $fmonth, $fday),$fyear)); + return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-$this->weekday($fyear, $fmonth, $fday),$fyear)); } /** diff --git a/model/fieldtypes/Text.php b/model/fieldtypes/Text.php index 822d4a535..f9e778513 100644 --- a/model/fieldtypes/Text.php +++ b/model/fieldtypes/Text.php @@ -33,7 +33,7 @@ class Text extends StringField { 'NoHTML' => 'Text', ); - /** + /** * (non-PHPdoc) * @see DBField::requireField() */ diff --git a/model/fieldtypes/Varchar.php b/model/fieldtypes/Varchar.php index 6cf02aec8..98f8eba0a 100644 --- a/model/fieldtypes/Varchar.php +++ b/model/fieldtypes/Varchar.php @@ -17,8 +17,8 @@ class Varchar extends StringField { ); protected $size; - - /** + + /** * Construct a new short text field * * @param $name string The name of the field @@ -27,12 +27,12 @@ class Varchar extends StringField { * See {@link StringField::setOptions()} for information on the available options * @return unknown_type */ - public function __construct($name = null, $size = 50, $options = array()) { + public function __construct($name = null, $size = 50, $options = array()) { $this->size = $size ? $size : 50; parent::__construct($name, $options); } - /** + /** * (non-PHPdoc) * @see DBField::requireField() */ diff --git a/parsers/BBCodeParser.php b/parsers/BBCodeParser.php index e3352c3ef..72fce426c 100644 --- a/parsers/BBCodeParser.php +++ b/parsers/BBCodeParser.php @@ -27,7 +27,7 @@ class BBCodeParser extends TextParser { * @var Boolean */ protected static $allowSimilies = false; - + /** * Set the location of the smiles folder. By default use the ones in framework * but this can be overridden by setting BBCodeParser::set_icon_folder('themes/yourtheme/images/'); @@ -165,7 +165,7 @@ class BBCodeParser extends TextParser { '#(? " ", // :( '#(? " ", // :-( '#(? " ", // :p - '#(? " ", // 8-) + '#(? " ", // 8-) '#(? " " // :^) ); $this->content = preg_replace(array_keys($smilies), array_values($smilies), $this->content); diff --git a/parsers/HTML/HTMLBBCodeParser.php b/parsers/HTML/HTMLBBCodeParser.php index b09ce64e2..e9cfdacf6 100644 --- a/parsers/HTML/HTMLBBCodeParser.php +++ b/parsers/HTML/HTMLBBCodeParser.php @@ -55,819 +55,818 @@ */ class SSHTMLBBCodeParser { - /** - * An array of tags parsed by the engine, should be overwritten by filters - * - * @access private - * @var array - */ - var $_definedTags = array(); + /** + * An array of tags parsed by the engine, should be overwritten by filters + * + * @access private + * @var array + */ + var $_definedTags = array(); - /** - * A string containing the input - * - * @access private - * @var string - */ - var $_text = ''; + /** + * A string containing the input + * + * @access private + * @var string + */ + var $_text = ''; - /** - * A string containing the preparsed input - * - * @access private - * @var string - */ - var $_preparsed = ''; + /** + * A string containing the preparsed input + * + * @access private + * @var string + */ + var $_preparsed = ''; - /** - * An array tags and texts build from the input text - * - * @access private - * @var array - */ - var $_tagArray = array(); + /** + * An array tags and texts build from the input text + * + * @access private + * @var array + */ + var $_tagArray = array(); - /** - * A string containing the parsed version of the text - * - * @access private - * @var string - */ - var $_parsed = ''; + /** + * A string containing the parsed version of the text + * + * @access private + * @var string + */ + var $_parsed = ''; - /** - * An array of options, filled by an ini file or through the contructor - * - * @access private - * @var array - */ - var $_options = array( - 'quotestyle' => 'double', - 'quotewhat' => 'all', - 'open' => '[', - 'close' => ']', - 'xmlclose' => true, - 'filters' => 'Basic' - ); + /** + * An array of options, filled by an ini file or through the contructor + * + * @access private + * @var array + */ + var $_options = array( + 'quotestyle' => 'double', + 'quotewhat' => 'all', + 'open' => '[', + 'close' => ']', + 'xmlclose' => true, + 'filters' => 'Basic' + ); - /** - * An array of filters used for parsing - * - * @access private - * @var array - */ - var $_filters = array(); + /** + * An array of filters used for parsing + * + * @access private + * @var array + */ + var $_filters = array(); - /** - * Constructor, initialises the options and filters - * - * Sets the private variable _options with base options defined with - * &PEAR::getStaticProperty(), overwriting them with (if present) - * the argument to this method. - * Then it sets the extra options to properly escape the tag - * characters in preg_replace() etc. The set options are - * then stored back with &PEAR::getStaticProperty(), so that the filter - * classes can use them. - * All the filters in the options are initialised and their defined tags - * are copied into the private variable _definedTags. - * - * @param array options to use, can be left out - * @return none - * @access public - * @author Stijn de Reede - */ - public function SSHTMLBBCodeParser($options = array()) - { - // set the already set options - $baseoptions = &SSHTMLBBCodeParser::getStaticProperty('SSHTMLBBCodeParser', '_options'); - if (is_array($baseoptions)) { - foreach ($baseoptions as $k => $v) { - $this->_options[$k] = $v; - } - } + /** + * Constructor, initialises the options and filters + * + * Sets the private variable _options with base options defined with + * &PEAR::getStaticProperty(), overwriting them with (if present) + * the argument to this method. + * Then it sets the extra options to properly escape the tag + * characters in preg_replace() etc. The set options are + * then stored back with &PEAR::getStaticProperty(), so that the filter + * classes can use them. + * All the filters in the options are initialised and their defined tags + * are copied into the private variable _definedTags. + * + * @param array options to use, can be left out + * @return none + * @access public + * @author Stijn de Reede + */ + public function SSHTMLBBCodeParser($options = array()) + { + // set the already set options + $baseoptions = &SSHTMLBBCodeParser::getStaticProperty('SSHTMLBBCodeParser', '_options'); + if (is_array($baseoptions)) { + foreach ($baseoptions as $k => $v) { + $this->_options[$k] = $v; + } + } - // set the options passed as an argument - foreach ($options as $k => $v ) { - $this->_options[$k] = $v; - } + // set the options passed as an argument + foreach ($options as $k => $v ) { + $this->_options[$k] = $v; + } - // add escape open and close chars to the options for preg escaping - $preg_escape = '\^$.[]|()?*+{}'; - if ($this->_options['open'] != '' && strpos($preg_escape, $this->_options['open'])) { - $this->_options['open_esc'] = "\\".$this->_options['open']; - } else { - $this->_options['open_esc'] = $this->_options['open']; - } - if ($this->_options['close'] != '' && strpos($preg_escape, $this->_options['close'])) { - $this->_options['close_esc'] = "\\".$this->_options['close']; - } else { - $this->_options['close_esc'] = $this->_options['close']; - } + // add escape open and close chars to the options for preg escaping + $preg_escape = '\^$.[]|()?*+{}'; + if ($this->_options['open'] != '' && strpos($preg_escape, $this->_options['open'])) { + $this->_options['open_esc'] = "\\".$this->_options['open']; + } else { + $this->_options['open_esc'] = $this->_options['open']; + } + if ($this->_options['close'] != '' && strpos($preg_escape, $this->_options['close'])) { + $this->_options['close_esc'] = "\\".$this->_options['close']; + } else { + $this->_options['close_esc'] = $this->_options['close']; + } - // set the options back so that child classes can use them */ - $baseoptions = $this->_options; - unset($baseoptions); + // set the options back so that child classes can use them */ + $baseoptions = $this->_options; + unset($baseoptions); - // return if this is a subclass - if (is_subclass_of($this, 'SSHTMLBBCodeParser_Filter')) { - return; - } + // return if this is a subclass + if (is_subclass_of($this, 'SSHTMLBBCodeParser_Filter')) { + return; + } - // extract the definedTags from subclasses */ - $this->addFilters($this->_options['filters']); - } - - static function &getStaticProperty($class, $var) - { - static $properties; - if (!isset($properties[$class])) { - $properties[$class] = array(); - } - if (!array_key_exists($var, $properties[$class])) { - $properties[$class][$var] = null; - } - return $properties[$class][$var]; - } + // extract the definedTags from subclasses */ + $this->addFilters($this->_options['filters']); + } + + static function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + return $properties[$class][$var]; + } - /** - * Option setter - * - * @param string option name - * @param mixed option value - * @author Lorenzo Alberton - */ - public function setOption($name, $value) - { - $this->_options[$name] = $value; - } + /** + * Option setter + * + * @param string option name + * @param mixed option value + * @author Lorenzo Alberton + */ + public function setOption($name, $value) + { + $this->_options[$name] = $value; + } - /** - * Add a new filter - * - * @param string filter - * @author Lorenzo Alberton - */ - public function addFilter($filter) - { - - $filter = ucfirst($filter); - if (!array_key_exists($filter, $this->_filters)) { - $class = 'SSHTMLBBCodeParser_Filter_'.$filter; - if (fopen('BBCodeParser/Filter/'.$filter.'.php','r',true)) { - include_once 'BBCodeParser/Filter/'.$filter.'.php'; - } - if (!class_exists($class)) { + /** + * Add a new filter + * + * @param string filter + * @author Lorenzo Alberton + */ + public function addFilter($filter) + { + $filter = ucfirst($filter); + if (!array_key_exists($filter, $this->_filters)) { + $class = 'SSHTMLBBCodeParser_Filter_'.$filter; + if (fopen('BBCodeParser/Filter/'.$filter.'.php','r',true)) { + include_once 'BBCodeParser/Filter/'.$filter.'.php'; + } + if (!class_exists($class)) { - //PEAR::raiseError("Failed to load filter $filter", null, PEAR_ERROR_DIE); - } + //PEAR::raiseError("Failed to load filter $filter", null, PEAR_ERROR_DIE); + } else { $this->_filters[$filter] = new $class; - $this->_definedTags = array_merge( - $this->_definedTags, - $this->_filters[$filter]->_definedTags - ); + $this->_definedTags = array_merge( + $this->_definedTags, + $this->_filters[$filter]->_definedTags + ); } - } + } - } + } - /** - * Remove an existing filter - * - * @param string $filter - * @author Lorenzo Alberton - */ - public function removeFilter($filter) - { - $filter = ucfirst(trim($filter)); - if (!empty($filter) && array_key_exists($filter, $this->_filters)) { - unset($this->_filters[$filter]); - } - // also remove the related $this->_definedTags for this filter, - // preserving the others - $this->_definedTags = array(); - foreach (array_keys($this->_filters) as $filter) { - $this->_definedTags = array_merge( - $this->_definedTags, - $this->_filters[$filter]->_definedTags - ); - } - } + /** + * Remove an existing filter + * + * @param string $filter + * @author Lorenzo Alberton + */ + public function removeFilter($filter) + { + $filter = ucfirst(trim($filter)); + if (!empty($filter) && array_key_exists($filter, $this->_filters)) { + unset($this->_filters[$filter]); + } + // also remove the related $this->_definedTags for this filter, + // preserving the others + $this->_definedTags = array(); + foreach (array_keys($this->_filters) as $filter) { + $this->_definedTags = array_merge( + $this->_definedTags, + $this->_filters[$filter]->_definedTags + ); + } + } - /** - * Add new filters - * - * @param mixed (array or string) - * @return boolean true if all ok, false if not. - * @author Lorenzo Alberton - */ - public function addFilters($filters) - { - if (is_string($filters)) { - //comma-separated list - if (strpos($filters, ',') !== false) { - $filters = explode(',', $filters); - } else { - $filters = array($filters); - } - } - if (!is_array($filters)) { - //invalid format - return false; - } - foreach ($filters as $filter) { - if (trim($filter)){ - $this->addFilter($filter); - } - } - return true; - } + /** + * Add new filters + * + * @param mixed (array or string) + * @return boolean true if all ok, false if not. + * @author Lorenzo Alberton + */ + public function addFilters($filters) + { + if (is_string($filters)) { + //comma-separated list + if (strpos($filters, ',') !== false) { + $filters = explode(',', $filters); + } else { + $filters = array($filters); + } + } + if (!is_array($filters)) { + //invalid format + return false; + } + foreach ($filters as $filter) { + if (trim($filter)){ + $this->addFilter($filter); + } + } + return true; + } - /** - * Executes statements before the actual array building starts - * - * This method should be overwritten in a filter if you want to do - * something before the parsing process starts. This can be useful to - * allow certain short alternative tags which then can be converted into - * proper tags with preg_replace() calls. - * The main class walks through all the filters and and calls this - * method. The filters should modify their private $_preparsed - * variable, with input from $_text. - * - * @return none - * @access private - * @see $_text - * @author Stijn de Reede - */ - public function _preparse() - { - // default: assign _text to _preparsed, to be overwritten by filters - $this->_preparsed = $this->_text; + /** + * Executes statements before the actual array building starts + * + * This method should be overwritten in a filter if you want to do + * something before the parsing process starts. This can be useful to + * allow certain short alternative tags which then can be converted into + * proper tags with preg_replace() calls. + * The main class walks through all the filters and and calls this + * method. The filters should modify their private $_preparsed + * variable, with input from $_text. + * + * @return none + * @access private + * @see $_text + * @author Stijn de Reede + */ + public function _preparse() + { + // default: assign _text to _preparsed, to be overwritten by filters + $this->_preparsed = $this->_text; - // return if this is a subclass - if (is_subclass_of($this, 'SSHTMLBBCodeParser')) { - return; - } + // return if this is a subclass + if (is_subclass_of($this, 'SSHTMLBBCodeParser')) { + return; + } - // walk through the filters and execute _preparse - foreach ($this->_filters as $filter) { - $filter->setText($this->_preparsed); - $filter->_preparse(); - $this->_preparsed = $filter->getPreparsed(); - } - } + // walk through the filters and execute _preparse + foreach ($this->_filters as $filter) { + $filter->setText($this->_preparsed); + $filter->_preparse(); + $this->_preparsed = $filter->getPreparsed(); + } + } - /** - * Builds the tag array from the input string $_text - * - * An array consisting of tag and text elements is contructed from the - * $_preparsed variable. The method uses _buildTag() to check if a tag is - * valid and to build the actual tag to be added to the tag array. - * - * @todo - rewrite whole method, as this one is old and probably slow - * - see if a recursive method would be better than an iterative one - * - * @return none - * @access private - * @see _buildTag() - * @see $_text - * @see $_tagArray - * @author Stijn de Reede - */ - public function _buildTagArray() - { - $this->_tagArray = array(); - $str = $this->_preparsed; - $strPos = 0; - $strLength = strlen($str); + /** + * Builds the tag array from the input string $_text + * + * An array consisting of tag and text elements is contructed from the + * $_preparsed variable. The method uses _buildTag() to check if a tag is + * valid and to build the actual tag to be added to the tag array. + * + * @todo - rewrite whole method, as this one is old and probably slow + * - see if a recursive method would be better than an iterative one + * + * @return none + * @access private + * @see _buildTag() + * @see $_text + * @see $_tagArray + * @author Stijn de Reede + */ + public function _buildTagArray() + { + $this->_tagArray = array(); + $str = $this->_preparsed; + $strPos = 0; + $strLength = strlen($str); - while (($strPos < $strLength)) { - $tag = array(); - $openPos = strpos($str, $this->_options['open'], $strPos); - if ($openPos === false) { - $openPos = $strLength; - $nextOpenPos = $strLength; - } - if ($openPos + 1 > $strLength) { - $nextOpenPos = $strLength; - } else { - $nextOpenPos = strpos($str, $this->_options['open'], $openPos + 1); - if ($nextOpenPos === false) { - $nextOpenPos = $strLength; - } - } - $closePos = strpos($str, $this->_options['close'], $strPos); - if ($closePos === false) { - $closePos = $strLength + 1; - } + while (($strPos < $strLength)) { + $tag = array(); + $openPos = strpos($str, $this->_options['open'], $strPos); + if ($openPos === false) { + $openPos = $strLength; + $nextOpenPos = $strLength; + } + if ($openPos + 1 > $strLength) { + $nextOpenPos = $strLength; + } else { + $nextOpenPos = strpos($str, $this->_options['open'], $openPos + 1); + if ($nextOpenPos === false) { + $nextOpenPos = $strLength; + } + } + $closePos = strpos($str, $this->_options['close'], $strPos); + if ($closePos === false) { + $closePos = $strLength + 1; + } - if ($openPos == $strPos) { - if (($nextOpenPos < $closePos)) { - // new open tag before closing tag: treat as text - $newPos = $nextOpenPos; - $tag['text'] = substr($str, $strPos, $nextOpenPos - $strPos); - $tag['type'] = 0; - } else { - // possible valid tag - $newPos = $closePos + 1; - $newTag = $this->_buildTag(substr($str, $strPos, $closePos - $strPos + 1)); - if (($newTag !== false)) { - $tag = $newTag; - } else { - // no valid tag after all - $tag['text'] = substr($str, $strPos, $closePos - $strPos + 1); - $tag['type'] = 0; - } - } - } else { - // just text - $newPos = $openPos; - $tag['text'] = substr($str, $strPos, $openPos - $strPos); - $tag['type'] = 0; - } + if ($openPos == $strPos) { + if (($nextOpenPos < $closePos)) { + // new open tag before closing tag: treat as text + $newPos = $nextOpenPos; + $tag['text'] = substr($str, $strPos, $nextOpenPos - $strPos); + $tag['type'] = 0; + } else { + // possible valid tag + $newPos = $closePos + 1; + $newTag = $this->_buildTag(substr($str, $strPos, $closePos - $strPos + 1)); + if (($newTag !== false)) { + $tag = $newTag; + } else { + // no valid tag after all + $tag['text'] = substr($str, $strPos, $closePos - $strPos + 1); + $tag['type'] = 0; + } + } + } else { + // just text + $newPos = $openPos; + $tag['text'] = substr($str, $strPos, $openPos - $strPos); + $tag['type'] = 0; + } - // join 2 following text elements - if ($tag['type'] === 0 && isset($prev) && $prev['type'] === 0) { - $tag['text'] = $prev['text'].$tag['text']; - array_pop($this->_tagArray); - } + // join 2 following text elements + if ($tag['type'] === 0 && isset($prev) && $prev['type'] === 0) { + $tag['text'] = $prev['text'].$tag['text']; + array_pop($this->_tagArray); + } - $this->_tagArray[] = $tag; - $prev = $tag; - $strPos = $newPos; - } - } + $this->_tagArray[] = $tag; + $prev = $tag; + $strPos = $newPos; + } + } - /** - * Builds a tag from the input string - * - * This method builds a tag array based on the string it got as an - * argument. If the tag is invalid, is returned. The tag - * attributes are extracted from the string and stored in the tag - * array as an associative array. - * - * @param string string to build tag from - * @return array tag in array format - * @access private - * @see _buildTagArray() - * @author Stijn de Reede - */ - public function _buildTag($str) - { - $tag = array('text' => $str, 'attributes' => array()); + /** + * Builds a tag from the input string + * + * This method builds a tag array based on the string it got as an + * argument. If the tag is invalid, is returned. The tag + * attributes are extracted from the string and stored in the tag + * array as an associative array. + * + * @param string string to build tag from + * @return array tag in array format + * @access private + * @see _buildTagArray() + * @author Stijn de Reede + */ + public function _buildTag($str) + { + $tag = array('text' => $str, 'attributes' => array()); - if (substr($str, 1, 1) == '/') { // closing tag + if (substr($str, 1, 1) == '/') { // closing tag - $tag['tag'] = strtolower(substr($str, 2, strlen($str) - 3)); - if (!in_array($tag['tag'], array_keys($this->_definedTags))) { - return false; // nope, it's not valid - } else { - $tag['type'] = 2; - return $tag; - } - } else { // opening tag + $tag['tag'] = strtolower(substr($str, 2, strlen($str) - 3)); + if (!in_array($tag['tag'], array_keys($this->_definedTags))) { + return false; // nope, it's not valid + } else { + $tag['type'] = 2; + return $tag; + } + } else { // opening tag - $tag['type'] = 1; - if (strpos($str, ' ') && (strpos($str, '=') === false)) { - return false; // nope, it's not valid - } + $tag['type'] = 1; + if (strpos($str, ' ') && (strpos($str, '=') === false)) { + return false; // nope, it's not valid + } - // tnx to Onno for the regex - // split the tag with arguments and all - $oe = $this->_options['open_esc']; - $ce = $this->_options['close_esc']; - $tagArray = array(); - if (preg_match("!$oe([a-z0-9]+)[^$ce]*$ce!i", $str, $tagArray) == 0) { - return false; - } - $tag['tag'] = strtolower($tagArray[1]); - if (!in_array($tag['tag'], array_keys($this->_definedTags))) { - return false; // nope, it's not valid - } + // tnx to Onno for the regex + // split the tag with arguments and all + $oe = $this->_options['open_esc']; + $ce = $this->_options['close_esc']; + $tagArray = array(); + if (preg_match("!$oe([a-z0-9]+)[^$ce]*$ce!i", $str, $tagArray) == 0) { + return false; + } + $tag['tag'] = strtolower($tagArray[1]); + if (!in_array($tag['tag'], array_keys($this->_definedTags))) { + return false; // nope, it's not valid + } - // tnx to Onno for the regex - // validate the arguments - $attributeArray = array(); - $regex = "![\s$oe]([a-z0-9]+)=(\"[^\s$ce]+\"|[^\s$ce]"; - if ($tag['tag'] != 'url') { - $regex .= "[^=]"; - } - $regex .= "+)(?=[\s$ce])!i"; - preg_match_all($regex, $str, $attributeArray, PREG_SET_ORDER); - foreach ($attributeArray as $attribute) { - $attNam = strtolower($attribute[1]); - if (in_array($attNam, array_keys($this->_definedTags[$tag['tag']]['attributes']))) { - if ($attribute[2][0] == '"' && $attribute[2][strlen($attribute[2])-1] == '"') { - $tag['attributes'][$attNam] = substr($attribute[2], 1, -1); - } else { - $tag['attributes'][$attNam] = $attribute[2]; - } - } - } - return $tag; - } - } + // tnx to Onno for the regex + // validate the arguments + $attributeArray = array(); + $regex = "![\s$oe]([a-z0-9]+)=(\"[^\s$ce]+\"|[^\s$ce]"; + if ($tag['tag'] != 'url') { + $regex .= "[^=]"; + } + $regex .= "+)(?=[\s$ce])!i"; + preg_match_all($regex, $str, $attributeArray, PREG_SET_ORDER); + foreach ($attributeArray as $attribute) { + $attNam = strtolower($attribute[1]); + if (in_array($attNam, array_keys($this->_definedTags[$tag['tag']]['attributes']))) { + if ($attribute[2][0] == '"' && $attribute[2][strlen($attribute[2])-1] == '"') { + $tag['attributes'][$attNam] = substr($attribute[2], 1, -1); + } else { + $tag['attributes'][$attNam] = $attribute[2]; + } + } + } + return $tag; + } + } - /** - * Validates the tag array, regarding the allowed tags - * - * While looping through the tag array, two following text tags are - * joined, and it is checked that the tag is allowed inside the - * last opened tag. - * By remembering what tags have been opened it is checked that - * there is correct (xml compliant) nesting. - * In the end all still opened tags are closed. - * - * @return none - * @access private - * @see _isAllowed() - * @see $_tagArray - * @author Stijn de Reede , Seth Price - */ - public function _validateTagArray() - { - $newTagArray = array(); - $openTags = array(); - foreach ($this->_tagArray as $tag) { - $prevTag = end($newTagArray); - switch ($tag['type']) { - case 0: - if (($child = $this->_childNeeded(end($openTags), 'text')) && - $child !== false && - /* - * No idea what to do in this case: A child is needed, but - * no valid one is returned. We'll ignore it here and live - * with it until someone reports a valid bug. - */ - $child !== true ) - { - if (trim($tag['text']) == '') { - //just an empty indentation or newline without value? - continue; - } - $newTagArray[] = $child; - $openTags[] = $child['tag']; - } - if ($prevTag['type'] === 0) { - $tag['text'] = $prevTag['text'].$tag['text']; - array_pop($newTagArray); - } - $newTagArray[] = $tag; - break; + /** + * Validates the tag array, regarding the allowed tags + * + * While looping through the tag array, two following text tags are + * joined, and it is checked that the tag is allowed inside the + * last opened tag. + * By remembering what tags have been opened it is checked that + * there is correct (xml compliant) nesting. + * In the end all still opened tags are closed. + * + * @return none + * @access private + * @see _isAllowed() + * @see $_tagArray + * @author Stijn de Reede , Seth Price + */ + public function _validateTagArray() + { + $newTagArray = array(); + $openTags = array(); + foreach ($this->_tagArray as $tag) { + $prevTag = end($newTagArray); + switch ($tag['type']) { + case 0: + if (($child = $this->_childNeeded(end($openTags), 'text')) && + $child !== false && + /* + * No idea what to do in this case: A child is needed, but + * no valid one is returned. We'll ignore it here and live + * with it until someone reports a valid bug. + */ + $child !== true ) + { + if (trim($tag['text']) == '') { + //just an empty indentation or newline without value? + continue; + } + $newTagArray[] = $child; + $openTags[] = $child['tag']; + } + if ($prevTag['type'] === 0) { + $tag['text'] = $prevTag['text'].$tag['text']; + array_pop($newTagArray); + } + $newTagArray[] = $tag; + break; - case 1: - if (!$this->_isAllowed(end($openTags), $tag['tag']) || - ($parent = $this->_parentNeeded(end($openTags), $tag['tag'])) === true || - ($child = $this->_childNeeded(end($openTags), $tag['tag'])) === true) { - $tag['type'] = 0; - if ($prevTag['type'] === 0) { - $tag['text'] = $prevTag['text'].$tag['text']; - array_pop($newTagArray); - } - } else { - if ($parent) { - /* - * Avoid use of parent if we can help it. If we are - * trying to insert a new parent, but the current tag is - * the same as the previous tag, then assume that the - * previous tag structure is valid, and add this tag as - * a sibling. To add as a sibling, we need to close the - * current tag. - */ - if ($tag['tag'] == end($openTags)){ - $newTagArray[] = $this->_buildTag('[/'.$tag['tag'].']'); - array_pop($openTags); - } else { - $newTagArray[] = $parent; - $openTags[] = $parent['tag']; - } - } - if ($child) { - $newTagArray[] = $child; - $openTags[] = $child['tag']; - } - $openTags[] = $tag['tag']; - } - $newTagArray[] = $tag; - break; + case 1: + if (!$this->_isAllowed(end($openTags), $tag['tag']) || + ($parent = $this->_parentNeeded(end($openTags), $tag['tag'])) === true || + ($child = $this->_childNeeded(end($openTags), $tag['tag'])) === true) { + $tag['type'] = 0; + if ($prevTag['type'] === 0) { + $tag['text'] = $prevTag['text'].$tag['text']; + array_pop($newTagArray); + } + } else { + if ($parent) { + /* + * Avoid use of parent if we can help it. If we are + * trying to insert a new parent, but the current tag is + * the same as the previous tag, then assume that the + * previous tag structure is valid, and add this tag as + * a sibling. To add as a sibling, we need to close the + * current tag. + */ + if ($tag['tag'] == end($openTags)){ + $newTagArray[] = $this->_buildTag('[/'.$tag['tag'].']'); + array_pop($openTags); + } else { + $newTagArray[] = $parent; + $openTags[] = $parent['tag']; + } + } + if ($child) { + $newTagArray[] = $child; + $openTags[] = $child['tag']; + } + $openTags[] = $tag['tag']; + } + $newTagArray[] = $tag; + break; - case 2: - if (($tag['tag'] == end($openTags) || $this->_isAllowed(end($openTags), $tag['tag']))) { - if (in_array($tag['tag'], $openTags)) { - $tmpOpenTags = array(); - while (end($openTags) != $tag['tag']) { - $newTagArray[] = $this->_buildTag('[/'.end($openTags).']'); - $tmpOpenTags[] = end($openTags); - array_pop($openTags); - } - $newTagArray[] = $tag; - array_pop($openTags); - /* why is this here? it just seems to break things - * (nested lists where closing tags need to be - * generated) - while (end($tmpOpenTags)) { - $tmpTag = $this->_buildTag('['.end($tmpOpenTags).']'); - $newTagArray[] = $tmpTag; - $openTags[] = $tmpTag['tag']; - array_pop($tmpOpenTags); - }*/ - } - } else { - $tag['type'] = 0; - if ($prevTag['type'] === 0) { - $tag['text'] = $prevTag['text'].$tag['text']; - array_pop($newTagArray); - } - $newTagArray[] = $tag; - } - break; - } - } - while (end($openTags)) { - $newTagArray[] = $this->_buildTag('[/'.end($openTags).']'); - array_pop($openTags); - } - $this->_tagArray = $newTagArray; - } + case 2: + if (($tag['tag'] == end($openTags) || $this->_isAllowed(end($openTags), $tag['tag']))) { + if (in_array($tag['tag'], $openTags)) { + $tmpOpenTags = array(); + while (end($openTags) != $tag['tag']) { + $newTagArray[] = $this->_buildTag('[/'.end($openTags).']'); + $tmpOpenTags[] = end($openTags); + array_pop($openTags); + } + $newTagArray[] = $tag; + array_pop($openTags); + /* why is this here? it just seems to break things + * (nested lists where closing tags need to be + * generated) + while (end($tmpOpenTags)) { + $tmpTag = $this->_buildTag('['.end($tmpOpenTags).']'); + $newTagArray[] = $tmpTag; + $openTags[] = $tmpTag['tag']; + array_pop($tmpOpenTags); + }*/ + } + } else { + $tag['type'] = 0; + if ($prevTag['type'] === 0) { + $tag['text'] = $prevTag['text'].$tag['text']; + array_pop($newTagArray); + } + $newTagArray[] = $tag; + } + break; + } + } + while (end($openTags)) { + $newTagArray[] = $this->_buildTag('[/'.end($openTags).']'); + array_pop($openTags); + } + $this->_tagArray = $newTagArray; + } - /** - * Checks to see if a parent is needed - * - * Checks to see if the current $in tag has an appropriate parent. If it - * does, then it returns false. If a parent is needed, then it returns the - * first tag in the list to add to the stack. - * - * @param array tag that is on the outside - * @param array tag that is on the inside - * @return boolean false if not needed, tag if needed, true if out - * of our minds - * @access private - * @see _validateTagArray() - * @author Seth Price - */ - public function _parentNeeded($out, $in) - { - if (!isset($this->_definedTags[$in]['parent']) || - ($this->_definedTags[$in]['parent'] == 'all') - ) { - return false; - } + /** + * Checks to see if a parent is needed + * + * Checks to see if the current $in tag has an appropriate parent. If it + * does, then it returns false. If a parent is needed, then it returns the + * first tag in the list to add to the stack. + * + * @param array tag that is on the outside + * @param array tag that is on the inside + * @return boolean false if not needed, tag if needed, true if out + * of our minds + * @access private + * @see _validateTagArray() + * @author Seth Price + */ + public function _parentNeeded($out, $in) + { + if (!isset($this->_definedTags[$in]['parent']) || + ($this->_definedTags[$in]['parent'] == 'all') + ) { + return false; + } - $ar = explode('^', $this->_definedTags[$in]['parent']); - $tags = explode(',', $ar[1]); - if ($ar[0] == 'none'){ - if ($out && in_array($out, $tags)) { - return false; - } - //Create a tag from the first one on the list - return $this->_buildTag('['.$tags[0].']'); - } - if ($ar[0] == 'all' && $out && !in_array($out, $tags)) { - return false; - } - // Tag is needed, we don't know which one. We could make something up, - // but it would be so random, I think that it would be worthless. - return true; - } + $ar = explode('^', $this->_definedTags[$in]['parent']); + $tags = explode(',', $ar[1]); + if ($ar[0] == 'none'){ + if ($out && in_array($out, $tags)) { + return false; + } + //Create a tag from the first one on the list + return $this->_buildTag('['.$tags[0].']'); + } + if ($ar[0] == 'all' && $out && !in_array($out, $tags)) { + return false; + } + // Tag is needed, we don't know which one. We could make something up, + // but it would be so random, I think that it would be worthless. + return true; + } - /** - * Checks to see if a child is needed - * - * Checks to see if the current $out tag has an appropriate child. If it - * does, then it returns false. If a child is needed, then it returns the - * first tag in the list to add to the stack. - * - * @param array tag that is on the outside - * @param array tag that is on the inside - * @return boolean false if not needed, tag if needed, true if out - * of our minds - * @access private - * @see _validateTagArray() - * @author Seth Price - */ - public function _childNeeded($out, $in) - { - if (!isset($this->_definedTags[$out]['child']) || - ($this->_definedTags[$out]['child'] == 'all') - ) { - return false; - } + /** + * Checks to see if a child is needed + * + * Checks to see if the current $out tag has an appropriate child. If it + * does, then it returns false. If a child is needed, then it returns the + * first tag in the list to add to the stack. + * + * @param array tag that is on the outside + * @param array tag that is on the inside + * @return boolean false if not needed, tag if needed, true if out + * of our minds + * @access private + * @see _validateTagArray() + * @author Seth Price + */ + public function _childNeeded($out, $in) + { + if (!isset($this->_definedTags[$out]['child']) || + ($this->_definedTags[$out]['child'] == 'all') + ) { + return false; + } - $ar = explode('^', $this->_definedTags[$out]['child']); - $tags = explode(',', $ar[1]); - if ($ar[0] == 'none'){ - if ($in && in_array($in, $tags)) { - return false; - } - //Create a tag from the first one on the list - return $this->_buildTag('['.$tags[0].']'); - } - if ($ar[0] == 'all' && $in && !in_array($in, $tags)) { - return false; - } - // Tag is needed, we don't know which one. We could make something up, - // but it would be so random, I think that it would be worthless. - return true; - } + $ar = explode('^', $this->_definedTags[$out]['child']); + $tags = explode(',', $ar[1]); + if ($ar[0] == 'none'){ + if ($in && in_array($in, $tags)) { + return false; + } + //Create a tag from the first one on the list + return $this->_buildTag('['.$tags[0].']'); + } + if ($ar[0] == 'all' && $in && !in_array($in, $tags)) { + return false; + } + // Tag is needed, we don't know which one. We could make something up, + // but it would be so random, I think that it would be worthless. + return true; + } - /** - * Checks to see if a tag is allowed inside another tag - * - * The allowed tags are extracted from the private _definedTags array. - * - * @param array tag that is on the outside - * @param array tag that is on the inside - * @return boolean return true if the tag is allowed, false - * otherwise - * @access private - * @see _validateTagArray() - * @author Stijn de Reede - */ - public function _isAllowed($out, $in) - { - if (!$out || ($this->_definedTags[$out]['allowed'] == 'all')) { - return true; - } - if ($this->_definedTags[$out]['allowed'] == 'none') { - return false; - } + /** + * Checks to see if a tag is allowed inside another tag + * + * The allowed tags are extracted from the private _definedTags array. + * + * @param array tag that is on the outside + * @param array tag that is on the inside + * @return boolean return true if the tag is allowed, false + * otherwise + * @access private + * @see _validateTagArray() + * @author Stijn de Reede + */ + public function _isAllowed($out, $in) + { + if (!$out || ($this->_definedTags[$out]['allowed'] == 'all')) { + return true; + } + if ($this->_definedTags[$out]['allowed'] == 'none') { + return false; + } - $ar = explode('^', $this->_definedTags[$out]['allowed']); - $tags = explode(',', $ar[1]); - if ($ar[0] == 'none' && in_array($in, $tags)) { - return true; - } - if ($ar[0] == 'all' && in_array($in, $tags)) { - return false; - } - return false; - } + $ar = explode('^', $this->_definedTags[$out]['allowed']); + $tags = explode(',', $ar[1]); + if ($ar[0] == 'none' && in_array($in, $tags)) { + return true; + } + if ($ar[0] == 'all' && in_array($in, $tags)) { + return false; + } + return false; + } - /** - * Builds a parsed string based on the tag array - * - * The correct html and attribute values are extracted from the private - * _definedTags array. - * - * @return none - * @access private - * @see $_tagArray - * @see $_parsed - * @author Stijn de Reede - */ - public function _buildParsedString() - { - $this->_parsed = ''; - foreach ($this->_tagArray as $tag) { - switch ($tag['type']) { + /** + * Builds a parsed string based on the tag array + * + * The correct html and attribute values are extracted from the private + * _definedTags array. + * + * @return none + * @access private + * @see $_tagArray + * @see $_parsed + * @author Stijn de Reede + */ + public function _buildParsedString() + { + $this->_parsed = ''; + foreach ($this->_tagArray as $tag) { + switch ($tag['type']) { - // just text - case 0: - $this->_parsed .= $tag['text']; - break; + // just text + case 0: + $this->_parsed .= $tag['text']; + break; - // opening tag - case 1: - $this->_parsed .= '<'.$this->_definedTags[$tag['tag']]['htmlopen']; - if ($this->_options['quotestyle'] == 'single') $q = "'"; - if ($this->_options['quotestyle'] == 'double') $q = '"'; - foreach ($tag['attributes'] as $a => $v) { - //prevent XSS attacks. IMHO this is not enough, though... - //@see http://pear.php.net/bugs/bug.php?id=5609 - $v = preg_replace('#(script|about|applet|activex|chrome):#is', "\\1:", $v); - $v = htmlspecialchars($v); - $v = str_replace('&amp;', '&', $v); + // opening tag + case 1: + $this->_parsed .= '<'.$this->_definedTags[$tag['tag']]['htmlopen']; + if ($this->_options['quotestyle'] == 'single') $q = "'"; + if ($this->_options['quotestyle'] == 'double') $q = '"'; + foreach ($tag['attributes'] as $a => $v) { + //prevent XSS attacks. IMHO this is not enough, though... + //@see http://pear.php.net/bugs/bug.php?id=5609 + $v = preg_replace('#(script|about|applet|activex|chrome):#is', "\\1:", $v); + $v = htmlspecialchars($v); + $v = str_replace('&amp;', '&', $v); - if (($this->_options['quotewhat'] == 'nothing') || - (($this->_options['quotewhat'] == 'strings') && is_numeric($v)) - ) { - $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, ''); - } else { - $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, $q); - } - } - if ($this->_definedTags[$tag['tag']]['htmlclose'] == '' && $this->_options['xmlclose']) { - $this->_parsed .= ' /'; - } - $this->_parsed .= '>'; - break; + if (($this->_options['quotewhat'] == 'nothing') || + (($this->_options['quotewhat'] == 'strings') && is_numeric($v)) + ) { + $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, ''); + } else { + $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, $q); + } + } + if ($this->_definedTags[$tag['tag']]['htmlclose'] == '' && $this->_options['xmlclose']) { + $this->_parsed .= ' /'; + } + $this->_parsed .= '>'; + break; - // closing tag - case 2: - if ($this->_definedTags[$tag['tag']]['htmlclose'] != '') { - $this->_parsed .= '_definedTags[$tag['tag']]['htmlclose'].'>'; - } - break; - } - } - } + // closing tag + case 2: + if ($this->_definedTags[$tag['tag']]['htmlclose'] != '') { + $this->_parsed .= '_definedTags[$tag['tag']]['htmlclose'].'>'; + } + break; + } + } + } - /** - * Sets text in the object to be parsed - * - * @param string the text to set in the object - * @return none - * @access public - * @see getText() - * @see $_text - * @author Stijn de Reede - */ - public function setText($str) - { - $this->_text = $str; - } + /** + * Sets text in the object to be parsed + * + * @param string the text to set in the object + * @return none + * @access public + * @see getText() + * @see $_text + * @author Stijn de Reede + */ + public function setText($str) + { + $this->_text = $str; + } - /** - * Gets the unparsed text from the object - * - * @return string the text set in the object - * @access public - * @see setText() - * @see $_text - * @author Stijn de Reede - */ - public function getText() - { - return $this->_text; - } + /** + * Gets the unparsed text from the object + * + * @return string the text set in the object + * @access public + * @see setText() + * @see $_text + * @author Stijn de Reede + */ + public function getText() + { + return $this->_text; + } - /** - * Gets the preparsed text from the object - * - * @return string the text set in the object - * @access public - * @see _preparse() - * @see $_preparsed - * @author Stijn de Reede - */ - public function getPreparsed() - { - return $this->_preparsed; - } + /** + * Gets the preparsed text from the object + * + * @return string the text set in the object + * @access public + * @see _preparse() + * @see $_preparsed + * @author Stijn de Reede + */ + public function getPreparsed() + { + return $this->_preparsed; + } - /** - * Gets the parsed text from the object - * - * @return string the parsed text set in the object - * @access public - * @see parse() - * @see $_parsed - * @author Stijn de Reede - */ - public function getParsed() - { - return $this->_parsed; - } + /** + * Gets the parsed text from the object + * + * @return string the parsed text set in the object + * @access public + * @see parse() + * @see $_parsed + * @author Stijn de Reede + */ + public function getParsed() + { + return $this->_parsed; + } - /** - * Parses the text set in the object - * - * @return none - * @access public - * @see _preparse() - * @see _buildTagArray() - * @see _validateTagArray() - * @see _buildParsedString() - * @author Stijn de Reede - */ - public function parse() - { - $this->_preparse(); - $this->_buildTagArray(); - $this->_validateTagArray(); - $this->_buildParsedString(); - } + /** + * Parses the text set in the object + * + * @return none + * @access public + * @see _preparse() + * @see _buildTagArray() + * @see _validateTagArray() + * @see _buildParsedString() + * @author Stijn de Reede + */ + public function parse() + { + $this->_preparse(); + $this->_buildTagArray(); + $this->_validateTagArray(); + $this->_buildParsedString(); + } - /** - * Quick method to do setText(), parse() and getParsed at once - * - * @return none - * @access public - * @see parse() - * @see $_text - * @author Stijn de Reede - */ - public function qparse($str) - { - $this->_text = $str; - $this->parse(); - return $this->_parsed; - } + /** + * Quick method to do setText(), parse() and getParsed at once + * + * @return none + * @access public + * @see parse() + * @see $_text + * @author Stijn de Reede + */ + public function qparse($str) + { + $this->_text = $str; + $this->parse(); + return $this->_parsed; + } - /** - * Quick static method to do setText(), parse() and getParsed at once - * - * @return none - * @access public - * @see parse() - * @see $_text - * @author Stijn de Reede - */ - public function staticQparse($str) - { - $p = new SSHTMLBBCodeParser(); - $str = $p->qparse($str); - unset($p); - return $str; - } + /** + * Quick static method to do setText(), parse() and getParsed at once + * + * @return none + * @access public + * @see parse() + * @see $_text + * @author Stijn de Reede + */ + public function staticQparse($str) + { + $p = new SSHTMLBBCodeParser(); + $str = $p->qparse($str); + unset($p); + return $str; + } } diff --git a/search/SearchContext.php b/search/SearchContext.php index 610974f2b..de34b4086 100644 --- a/search/SearchContext.php +++ b/search/SearchContext.php @@ -147,7 +147,7 @@ class SearchContext extends Object { $searchParamArray = $searchParams; } - foreach($searchParamArray as $key => $value) { + foreach($searchParamArray as $key => $value) { $key = str_replace('__', '.', $key); if($filter = $this->getFilter($key)) { $filter->setModel($this->modelClass); @@ -158,9 +158,9 @@ class SearchContext extends Object { } } - if($this->connective != "AND") { - throw new Exception("SearchContext connective '$this->connective' not supported after ORM-rewrite."); - } + if($this->connective != "AND") { + throw new Exception("SearchContext connective '$this->connective' not supported after ORM-rewrite."); + } return $query; } diff --git a/security/Authenticator.php b/security/Authenticator.php index 9d61924a9..75c05ef4d 100644 --- a/security/Authenticator.php +++ b/security/Authenticator.php @@ -11,95 +11,95 @@ */ abstract class Authenticator extends Object { - /** - * This variable holds all authenticators that should be used - * - * @var array - */ - private static $authenticators = array('MemberAuthenticator'); - - /** - * Used to influence the order of authenticators on the login-screen - * (default shows first). - * - * @var string - */ - private static $default_authenticator = 'MemberAuthenticator'; + /** + * This variable holds all authenticators that should be used + * + * @var array + */ + private static $authenticators = array('MemberAuthenticator'); + + /** + * Used to influence the order of authenticators on the login-screen + * (default shows first). + * + * @var string + */ + private static $default_authenticator = 'MemberAuthenticator'; - /** - * Method to authenticate an user - * - * @param array $RAW_data Raw data to authenticate the user - * @param Form $form Optional: If passed, better error messages can be - * produced by using - * {@link Form::sessionMessage()} - * @return bool|Member Returns FALSE if authentication fails, otherwise - * the member object - */ + /** + * Method to authenticate an user + * + * @param array $RAW_data Raw data to authenticate the user + * @param Form $form Optional: If passed, better error messages can be + * produced by using + * {@link Form::sessionMessage()} + * @return bool|Member Returns FALSE if authentication fails, otherwise + * the member object + */ public static function authenticate($RAW_data, Form $form = null) { } - /** - * Method that creates the login form for this authentication method - * - * @param Controller The parent controller, necessary to create the - * appropriate form action tag - * @return Form Returns the login form to use with this authentication - * method - */ + /** + * Method that creates the login form for this authentication method + * + * @param Controller The parent controller, necessary to create the + * appropriate form action tag + * @return Form Returns the login form to use with this authentication + * method + */ public static function get_login_form(Controller $controller) { } - /** - * Get the name of the authentication method - * - * @return string Returns the name of the authentication method. - */ + /** + * Get the name of the authentication method + * + * @return string Returns the name of the authentication method. + */ public static function get_name() { } - public static function register($authenticator) { + public static function register($authenticator) { self::register_authenticator($authenticator); - } + } - /** - * Register a new authenticator - * - * The new authenticator has to exist and to be derived from the - * {@link Authenticator}. - * Every authenticator can be registered only once. - * - * @param string $authenticator Name of the authenticator class to - * register - * @return bool Returns TRUE on success, FALSE otherwise. - */ - public static function register_authenticator($authenticator) { - $authenticator = trim($authenticator); + /** + * Register a new authenticator + * + * The new authenticator has to exist and to be derived from the + * {@link Authenticator}. + * Every authenticator can be registered only once. + * + * @param string $authenticator Name of the authenticator class to + * register + * @return bool Returns TRUE on success, FALSE otherwise. + */ + public static function register_authenticator($authenticator) { + $authenticator = trim($authenticator); - if(class_exists($authenticator) == false) - return false; + if(class_exists($authenticator) == false) + return false; - if(is_subclass_of($authenticator, 'Authenticator') == false) - return false; + if(is_subclass_of($authenticator, 'Authenticator') == false) + return false; - if(in_array($authenticator, self::$authenticators) == false) { - if(call_user_func(array($authenticator, 'on_register')) === true) { - array_push(self::$authenticators, $authenticator); - } else { - return false; - } - } + if(in_array($authenticator, self::$authenticators) == false) { + if(call_user_func(array($authenticator, 'on_register')) === true) { + array_push(self::$authenticators, $authenticator); + } else { + return false; + } + } - return true; - } - - public static function unregister($authenticator) { - self::unregister_authenticator($authenticator); - } - + return true; + } + + public static function unregister($authenticator) { + self::unregister_authenticator($authenticator); + } + /** * Remove a previously registered authenticator * @@ -108,82 +108,82 @@ abstract class Authenticator extends Object { */ public static function unregister_authenticator($authenticator) { if(call_user_func(array($authenticator, 'on_unregister')) === true) { - if(in_array($authenticator, self::$authenticators)) { - unset(self::$authenticators[array_search($authenticator, self::$authenticators)]); - } + if(in_array($authenticator, self::$authenticators)) { + unset(self::$authenticators[array_search($authenticator, self::$authenticators)]); + } }; } - /** - * Check if a given authenticator is registered - * - * @param string $authenticator Name of the authenticator class to check - * @return bool Returns TRUE if the authenticator is registered, FALSE - * otherwise. - */ - public static function is_registered($authenticator) { - return in_array($authenticator, self::$authenticators); - } + /** + * Check if a given authenticator is registered + * + * @param string $authenticator Name of the authenticator class to check + * @return bool Returns TRUE if the authenticator is registered, FALSE + * otherwise. + */ + public static function is_registered($authenticator) { + return in_array($authenticator, self::$authenticators); + } - /** - * Get all registered authenticators - * - * @return array Returns an array with the class names of all registered - * authenticators. - */ - public static function get_authenticators() { - // put default authenticator first (mainly for tab-order on loginform) - if($key = array_search(self::$default_authenticator,self::$authenticators)) { - unset(self::$authenticators[$key]); - array_unshift(self::$authenticators, self::$default_authenticator); - } + /** + * Get all registered authenticators + * + * @return array Returns an array with the class names of all registered + * authenticators. + */ + public static function get_authenticators() { + // put default authenticator first (mainly for tab-order on loginform) + if($key = array_search(self::$default_authenticator,self::$authenticators)) { + unset(self::$authenticators[$key]); + array_unshift(self::$authenticators, self::$default_authenticator); + } - return self::$authenticators; - } - - /** - * Set a default authenticator (shows first in tabs) - * - * @param string - */ - public static function set_default_authenticator($authenticator) { - self::$default_authenticator = $authenticator; - - - } - - /** - * @return string - */ - public static function get_default_authenticator() { - return self::$default_authenticator; - } + return self::$authenticators; + } + + /** + * Set a default authenticator (shows first in tabs) + * + * @param string + */ + public static function set_default_authenticator($authenticator) { + self::$default_authenticator = $authenticator; + + + } + + /** + * @return string + */ + public static function get_default_authenticator() { + return self::$default_authenticator; + } - /** - * Callback function that is called when the authenticator is registered - * - * Use this method for initialization of a newly registered authenticator. - * Just overload this method and it will be called when the authenticator - * is registered. - * If the method returns FALSE, the authenticator won't be - * registered! - * - * @return bool Returns TRUE on success, FALSE otherwise. - */ - protected static function on_register() { - return true; - } - - /** - * Callback function that is called when an authenticator is removed. - * - * @return bool - */ - protected static function on_unregister() { - return true; - } + /** + * Callback function that is called when the authenticator is registered + * + * Use this method for initialization of a newly registered authenticator. + * Just overload this method and it will be called when the authenticator + * is registered. + * If the method returns FALSE, the authenticator won't be + * registered! + * + * @return bool Returns TRUE on success, FALSE otherwise. + */ + protected static function on_register() { + return true; + } + + /** + * Callback function that is called when an authenticator is removed. + * + * @return bool + */ + protected static function on_unregister() { + return true; + } } diff --git a/security/Group.php b/security/Group.php index 772712a21..e70851b87 100755 --- a/security/Group.php +++ b/security/Group.php @@ -154,7 +154,7 @@ class Group extends DataObject { // but tabstrip.js doesn't display tabs when directly adressed through a URL pragma _t('Group.RolesAddEditLink', 'Manage roles') ) . - "

    " + "

    " ) ); @@ -315,7 +315,7 @@ class Group extends DataObject { } public function getTreeTitle() { - if($this->hasMethod('alternateTreeTitle')) return $this->alternateTreeTitle(); + if($this->hasMethod('alternateTreeTitle')) return $this->alternateTreeTitle(); else return htmlspecialchars($this->Title, ENT_QUOTES); } @@ -367,7 +367,7 @@ class Group extends DataObject { $results = $this->extend('canEdit', $member); if($results && is_array($results)) if(!min($results)) return false; - if( + if( // either we have an ADMIN (bool)Permission::checkMember($member, "ADMIN") || ( diff --git a/security/Member.php b/security/Member.php index c6e4fb12f..137a7e2de 100644 --- a/security/Member.php +++ b/security/Member.php @@ -561,7 +561,7 @@ class Member extends DataObject implements TemplateGlobalProvider { * It should return fields that are editable by the admin and the logged-in user. * * @return FieldList Returns a {@link FieldList} containing the fields for - * the member form. + * the member form. */ public function getMemberFormFields() { $fields = parent::getFrontendFields(); @@ -658,10 +658,10 @@ class Member extends DataObject implements TemplateGlobalProvider { return $word . $number; } else { - $random = rand(); - $string = md5($random); - $output = substr($string, 0, 6); - return $output; + $random = rand(); + $string = md5($random); + $output = substr($string, 0, 6); + return $output; } } @@ -1149,7 +1149,7 @@ class Member extends DataObject implements TemplateGlobalProvider { * * @param array $groupList An array of group code names. * @param array $memberGroups A component set of groups (if set to NULL, - * $this->groups() will be used) + * $this->groups() will be used) * @return array Groups in which the member is NOT in. */ public function memberNotInGroups($groupList, $memberGroups = null){ @@ -1171,7 +1171,7 @@ class Member extends DataObject implements TemplateGlobalProvider { * this member. * * @return FieldList Return a FieldList of fields that would appropriate for - * editing this member. + * editing this member. */ public function getCMSFields() { require_once('Zend/Date.php'); @@ -1535,14 +1535,14 @@ class Member_GroupSet extends ManyManyList { * @subpackage security */ class Member_ChangePasswordEmail extends Email { - protected $from = ''; // setting a blank from address uses the site's default administrator email - protected $subject = ''; - protected $ss_template = 'ChangePasswordEmail'; - - public function __construct() { + protected $from = ''; // setting a blank from address uses the site's default administrator email + protected $subject = ''; + protected $ss_template = 'ChangePasswordEmail'; + + public function __construct() { parent::__construct(); - $this->subject = _t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject'); - } + $this->subject = _t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject'); + } } @@ -1553,14 +1553,14 @@ class Member_ChangePasswordEmail extends Email { * @subpackage security */ class Member_ForgotPasswordEmail extends Email { - protected $from = ''; // setting a blank from address uses the site's default administrator email - protected $subject = ''; - protected $ss_template = 'ForgotPasswordEmail'; - - public function __construct() { + protected $from = ''; // setting a blank from address uses the site's default administrator email + protected $subject = ''; + protected $ss_template = 'ForgotPasswordEmail'; + + public function __construct() { parent::__construct(); - $this->subject = _t('Member.SUBJECTPASSWORDRESET', "Your password reset link", 'Email subject'); - } + $this->subject = _t('Member.SUBJECTPASSWORDRESET', "Your password reset link", 'Email subject'); + } } /** diff --git a/security/MemberAuthenticator.php b/security/MemberAuthenticator.php index 91d7a16fc..7654cca37 100644 --- a/security/MemberAuthenticator.php +++ b/security/MemberAuthenticator.php @@ -18,103 +18,103 @@ class MemberAuthenticator extends Authenticator { 'sha1' => 'sha1_v2.4' ); - /** - * Method to authenticate an user - * - * @param array $RAW_data Raw data to authenticate the user - * @param Form $form Optional: If passed, better error messages can be - * produced by using - * {@link Form::sessionMessage()} - * @return bool|Member Returns FALSE if authentication fails, otherwise - * the member object - * @see Security::setDefaultAdmin() - */ - public static function authenticate($RAW_data, Form $form = null) { - if(array_key_exists('Email', $RAW_data) && $RAW_data['Email']){ - $SQL_user = Convert::raw2sql($RAW_data['Email']); - } else { - return false; - } - - $isLockedOut = false; - $result = null; - - // Default login (see Security::setDefaultAdmin()) - if(Security::check_default_admin($RAW_data['Email'], $RAW_data['Password'])) { - $member = Security::findAnAdministrator(); - } else { - $member = DataObject::get_one( - "Member", - "\"" . Member::get_unique_identifier_field() . "\" = '$SQL_user' AND \"Password\" IS NOT NULL" - ); - - if($member) { - $result = $member->checkPassword($RAW_data['Password']); - } else { - $result = new ValidationResult(false, _t('Member.ERRORWRONGCRED')); - } - - if($member && !$result->valid()) { - $member->registerFailedLogin(); - $member = false; - } - } - - // Optionally record every login attempt as a {@link LoginAttempt} object /** - * TODO We could handle this with an extension + * Method to authenticate an user + * + * @param array $RAW_data Raw data to authenticate the user + * @param Form $form Optional: If passed, better error messages can be + * produced by using + * {@link Form::sessionMessage()} + * @return bool|Member Returns FALSE if authentication fails, otherwise + * the member object + * @see Security::setDefaultAdmin() */ - if(Security::login_recording()) { - $attempt = new LoginAttempt(); - if($member) { - // successful login (member is existing with matching password) - $attempt->MemberID = $member->ID; - $attempt->Status = 'Success'; - - // Audit logging hook - $member->extend('authenticated'); + public static function authenticate($RAW_data, Form $form = null) { + if(array_key_exists('Email', $RAW_data) && $RAW_data['Email']){ + $SQL_user = Convert::raw2sql($RAW_data['Email']); } else { - // failed login - we're trying to see if a user exists with this email (disregarding wrong passwords) - $existingMember = DataObject::get_one( - "Member", - "\"" . Member::get_unique_identifier_field() . "\" = '$SQL_user'" - ); - if($existingMember) { - $attempt->MemberID = $existingMember->ID; - - // Audit logging hook - $existingMember->extend('authenticationFailed'); - } else { - - // Audit logging hook - singleton('Member')->extend('authenticationFailedUnknownUser', $RAW_data); - } - $attempt->Status = 'Failure'; - } - if(is_array($RAW_data['Email'])) { - user_error("Bad email passed to MemberAuthenticator::authenticate(): $RAW_data[Email]", E_USER_WARNING); return false; } + + $isLockedOut = false; + $result = null; + + // Default login (see Security::setDefaultAdmin()) + if(Security::check_default_admin($RAW_data['Email'], $RAW_data['Password'])) { + $member = Security::findAnAdministrator(); + } else { + $member = DataObject::get_one( + "Member", + "\"" . Member::get_unique_identifier_field() . "\" = '$SQL_user' AND \"Password\" IS NOT NULL" + ); + + if($member) { + $result = $member->checkPassword($RAW_data['Password']); + } else { + $result = new ValidationResult(false, _t('Member.ERRORWRONGCRED')); + } + + if($member && !$result->valid()) { + $member->registerFailedLogin(); + $member = false; + } + } - $attempt->Email = $RAW_data['Email']; - $attempt->IP = Controller::curr()->getRequest()->getIP(); - $attempt->write(); - } - - // Legacy migration to precision-safe password hashes. - // A login-event with cleartext passwords is the only time - // when we can rehash passwords to a different hashing algorithm, - // bulk-migration doesn't work due to the nature of hashing. - // See PasswordEncryptor_LegacyPHPHash class. - if( - $member // only migrate after successful login - && self::$migrate_legacy_hashes - && array_key_exists($member->PasswordEncryption, self::$migrate_legacy_hashes) - ) { - $member->Password = $RAW_data['Password']; - $member->PasswordEncryption = self::$migrate_legacy_hashes[$member->PasswordEncryption]; - $member->write(); - } + // Optionally record every login attempt as a {@link LoginAttempt} object + /** + * TODO We could handle this with an extension + */ + if(Security::login_recording()) { + $attempt = new LoginAttempt(); + if($member) { + // successful login (member is existing with matching password) + $attempt->MemberID = $member->ID; + $attempt->Status = 'Success'; + + // Audit logging hook + $member->extend('authenticated'); + } else { + // failed login - we're trying to see if a user exists with this email (disregarding wrong passwords) + $existingMember = DataObject::get_one( + "Member", + "\"" . Member::get_unique_identifier_field() . "\" = '$SQL_user'" + ); + if($existingMember) { + $attempt->MemberID = $existingMember->ID; + + // Audit logging hook + $existingMember->extend('authenticationFailed'); + } else { + + // Audit logging hook + singleton('Member')->extend('authenticationFailedUnknownUser', $RAW_data); + } + $attempt->Status = 'Failure'; + } + if(is_array($RAW_data['Email'])) { + user_error("Bad email passed to MemberAuthenticator::authenticate(): $RAW_data[Email]", E_USER_WARNING); + return false; + } + + $attempt->Email = $RAW_data['Email']; + $attempt->IP = Controller::curr()->getRequest()->getIP(); + $attempt->write(); + } + + // Legacy migration to precision-safe password hashes. + // A login-event with cleartext passwords is the only time + // when we can rehash passwords to a different hashing algorithm, + // bulk-migration doesn't work due to the nature of hashing. + // See PasswordEncryptor_LegacyPHPHash class. + if( + $member // only migrate after successful login + && self::$migrate_legacy_hashes + && array_key_exists($member->PasswordEncryption, self::$migrate_legacy_hashes) + ) { + $member->Password = $RAW_data['Password']; + $member->PasswordEncryption = self::$migrate_legacy_hashes[$member->PasswordEncryption]; + $member->write(); + } if($member) { Session::clear('BackURL'); @@ -126,25 +126,25 @@ class MemberAuthenticator extends Authenticator { } - /** - * Method that creates the login form for this authentication method - * - * @param Controller The parent controller, necessary to create the - * appropriate form action tag - * @return Form Returns the login form to use with this authentication - * method - */ - public static function get_login_form(Controller $controller) { - return Object::create("MemberLoginForm", $controller, "LoginForm"); - } + /** + * Method that creates the login form for this authentication method + * + * @param Controller The parent controller, necessary to create the + * appropriate form action tag + * @return Form Returns the login form to use with this authentication + * method + */ + public static function get_login_form(Controller $controller) { + return Object::create("MemberLoginForm", $controller, "LoginForm"); + } - /** - * Get the name of the authentication method - * - * @return string Returns the name of the authentication method. - */ - public static function get_name() { + /** + * Get the name of the authentication method + * + * @return string Returns the name of the authentication method. + */ + public static function get_name() { return _t('MemberAuthenticator.TITLE', "E-mail & Password"); } } diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php index 2e04763f4..5ac7b7d86 100644 --- a/security/MemberLoginForm.php +++ b/security/MemberLoginForm.php @@ -33,7 +33,7 @@ class MemberLoginForm extends LoginForm { * @param string $authenticatorClassName Name of the authenticator class that this form uses. */ public function __construct($controller, $name, $fields = null, $actions = null, - $checkCurrentUser = true) { + $checkCurrentUser = true) { // This is now set on the class directly to make it easier to create subclasses // $this->authenticator_class = $authenticatorClassName; @@ -226,13 +226,13 @@ JS } - /** - * Try to authenticate the user - * - * @param array Submitted data - * @return Member Returns the member object on successful authentication - * or NULL on failure. - */ + /** + * Try to authenticate the user + * + * @param array Submitted data + * @return Member Returns the member object on successful authentication + * or NULL on failure. + */ public function performLogin($data) { $member = call_user_func_array(array($this->authenticator_class, 'authenticate'), array($data, $this)); if($member) { diff --git a/security/Permission.php b/security/Permission.php index ea7e194ed..3e7bb12fb 100644 --- a/security/Permission.php +++ b/security/Permission.php @@ -6,7 +6,7 @@ */ class Permission extends DataObject implements TemplateGlobalProvider { - // the (1) after Type specifies the DB default value which is needed for + // the (1) after Type specifies the DB default value which is needed for // upgrades from older SilverStripe versions static $db = array( "Code" => "Varchar", @@ -55,7 +55,7 @@ class Permission extends DataObject implements TemplateGlobalProvider { */ static $declared_permissions = null; - /** + /** * Linear list of declared permissions in the system. * * @var array diff --git a/security/PermissionCheckboxSetField.php b/security/PermissionCheckboxSetField.php index c2a98f3b7..059735371 100644 --- a/security/PermissionCheckboxSetField.php +++ b/security/PermissionCheckboxSetField.php @@ -157,7 +157,7 @@ class PermissionCheckboxSetField extends FormField { } } } - + $odd = 0; $options = ''; if($this->source) { @@ -256,7 +256,7 @@ class PermissionCheckboxSetField extends FormField { $idList = array(); if($this->value) foreach($this->value as $id => $bool) { - if($bool) { + if($bool) { $perm = new $managedClass(); $perm->{$this->filterField} = $record->ID; $perm->Code = $id; diff --git a/security/Security.php b/security/Security.php index 908cb13a0..5b3daf07c 100644 --- a/security/Security.php +++ b/security/Security.php @@ -7,18 +7,18 @@ class Security extends Controller { static $allowed_actions = array( - 'index', - 'login', - 'logout', - 'basicauthlogin', - 'lostpassword', - 'passwordsent', - 'changepassword', + 'index', + 'login', + 'logout', + 'basicauthlogin', + 'lostpassword', + 'passwordsent', + 'changepassword', 'ping', 'LoginForm', 'ChangePasswordForm', 'LostPasswordForm', - ); + ); /** * Default user name. Only used in dev-mode by {@link setDefaultAdmin()} @@ -138,22 +138,22 @@ class Security extends Controller { * If you don't provide a messageSet, a default will be used. * * @param Controller $controller The controller that you were on to cause the permission - * failure. + * failure. * @param string|array $messageSet The message to show to the user. This - * can be a string, or a map of different - * messages for different contexts. - * If you pass an array, you can use the - * following keys: - * - default: The default message - * - logInAgain: The message to show - * if the user has just - * logged out and the - * - alreadyLoggedIn: The message to - * show if the user - * is already logged - * in and lacks the - * permission to - * access the item. + * can be a string, or a map of different + * messages for different contexts. + * If you pass an array, you can use the + * following keys: + * - default: The default message + * - logInAgain: The message to show + * if the user has just + * logged out and the + * - alreadyLoggedIn: The message to + * show if the user + * is already logged + * in and lacks the + * permission to + * access the item. * * The alreadyLoggedIn value can contain a '%s' placeholder that will be replaced with a link * to log in. @@ -240,7 +240,7 @@ class Security extends Controller { } - /** + /** * Get the login form to process according to the submitted data */ protected function LoginForm() { @@ -262,7 +262,7 @@ class Security extends Controller { } - /** + /** * Get the login forms for all available authentication methods * * @return array Returns an array of available login forms (array of Form @@ -276,8 +276,8 @@ class Security extends Controller { $authenticators = Authenticator::get_authenticators(); foreach($authenticators as $authenticator) { - array_push($forms, - call_user_func(array($authenticator, 'get_login_form'), + array_push($forms, + call_user_func(array($authenticator, 'get_login_form'), $this)); } @@ -307,9 +307,9 @@ class Security extends Controller { * Log the currently logged in user out * * @param bool $redirect Redirect the user back to where they came. - * - If it's false, the code calling logout() is - * responsible for sending the user where-ever - * they should go. + * - If it's false, the code calling logout() is + * responsible for sending the user where-ever + * they should go. */ public function logout($redirect = true) { $member = Member::currentUser(); @@ -675,7 +675,7 @@ class Security extends Controller { } if ($adminGroup) { - $member = $adminGroup->Members()->First(); + $member = $adminGroup->Members()->First(); } if(!$adminGroup) { diff --git a/tests/api/RestfulServiceTest.php b/tests/api/RestfulServiceTest.php index 9cc817bc5..201ea6f3a 100644 --- a/tests/api/RestfulServiceTest.php +++ b/tests/api/RestfulServiceTest.php @@ -37,7 +37,7 @@ class RestfulServiceTest extends SapphireTest { $service->setQueryString($params); $responseBody = $service->request($url)->getBody(); foreach ($params as $key => $value) { - $this->assertContains("$value", $responseBody); + $this->assertContains("$value", $responseBody); $this->assertContains("$value", $responseBody); } } @@ -52,7 +52,7 @@ class RestfulServiceTest extends SapphireTest { $service->setQueryString($params); $responseBody = $service->request($url)->getBody(); foreach ($params as $key => $value) { - $this->assertContains("$value", $responseBody); + $this->assertContains("$value", $responseBody); $this->assertContains("$value", $responseBody); } } @@ -67,7 +67,7 @@ class RestfulServiceTest extends SapphireTest { $url .= '?' . http_build_query($params); $responseBody = $service->request($url)->getBody(); foreach ($params as $key => $value) { - $this->assertContains("$value", $responseBody); + $this->assertContains("$value", $responseBody); $this->assertContains("$value", $responseBody); } } diff --git a/tests/behat/features/bootstrap/FeatureContext.php b/tests/behat/features/bootstrap/FeatureContext.php index ea6f10054..a522e8a7c 100644 --- a/tests/behat/features/bootstrap/FeatureContext.php +++ b/tests/behat/features/bootstrap/FeatureContext.php @@ -3,10 +3,10 @@ namespace SilverStripe\Framework\Test\Behaviour; use SilverStripe\BehatExtension\Context\SilverStripeContext, - SilverStripe\BehatExtension\Context\BasicContext, - SilverStripe\BehatExtension\Context\LoginContext, - SilverStripe\Framework\Test\Behaviour\CmsFormsContext, - SilverStripe\Framework\Test\Behaviour\CmsUiContext; + SilverStripe\BehatExtension\Context\BasicContext, + SilverStripe\BehatExtension\Context\LoginContext, + SilverStripe\Framework\Test\Behaviour\CmsFormsContext, + SilverStripe\Framework\Test\Behaviour\CmsUiContext; // PHPUnit require_once 'PHPUnit/Autoload.php'; @@ -20,19 +20,19 @@ require_once 'PHPUnit/Framework/Assert/Functions.php'; */ class FeatureContext extends SilverStripeContext { - /** - * Initializes context. - * Every scenario gets it's own context object. - * - * @param array $parameters context parameters (set them up through behat.yml) - */ - public function __construct(array $parameters) - { - $this->useContext('BasicContext', new BasicContext($parameters)); - $this->useContext('LoginContext', new LoginContext($parameters)); - $this->useContext('CmsFormsContext', new CmsFormsContext($parameters)); - $this->useContext('CmsUiContext', new CmsUiContext($parameters)); + /** + * Initializes context. + * Every scenario gets it's own context object. + * + * @param array $parameters context parameters (set them up through behat.yml) + */ + public function __construct(array $parameters) + { + $this->useContext('BasicContext', new BasicContext($parameters)); + $this->useContext('LoginContext', new LoginContext($parameters)); + $this->useContext('CmsFormsContext', new CmsFormsContext($parameters)); + $this->useContext('CmsUiContext', new CmsUiContext($parameters)); - parent::__construct($parameters); - } + parent::__construct($parameters); + } } diff --git a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php index 7e3591913..ca7b7baf9 100644 --- a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php +++ b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsFormsContext.php @@ -3,12 +3,12 @@ namespace SilverStripe\Framework\Test\Behaviour; use Behat\Behat\Context\ClosuredContextInterface, - Behat\Behat\Context\TranslatedContextInterface, - Behat\Behat\Context\BehatContext, - Behat\Behat\Context\Step, - Behat\Behat\Exception\PendingException; + Behat\Behat\Context\TranslatedContextInterface, + Behat\Behat\Context\BehatContext, + Behat\Behat\Context\Step, + Behat\Behat\Exception\PendingException; use Behat\Gherkin\Node\PyStringNode, - Behat\Gherkin\Node\TableNode; + Behat\Gherkin\Node\TableNode; // PHPUnit require_once 'PHPUnit/Autoload.php'; @@ -21,81 +21,81 @@ require_once 'PHPUnit/Framework/Assert/Functions.php'; */ class CmsFormsContext extends BehatContext { - protected $context; + protected $context; - /** - * Initializes context. - * Every scenario gets it's own context object. - * - * @param array $parameters context parameters (set them up through behat.yml) - */ - public function __construct(array $parameters) - { - // Initialize your context here - $this->context = $parameters; - } + /** + * Initializes context. + * Every scenario gets it's own context object. + * + * @param array $parameters context parameters (set them up through behat.yml) + */ + public function __construct(array $parameters) + { + // Initialize your context here + $this->context = $parameters; + } - /** - * Get Mink session from MinkContext - */ - public function getSession($name = null) - { - return $this->getMainContext()->getSession($name); - } + /** + * Get Mink session from MinkContext + */ + public function getSession($name = null) + { + return $this->getMainContext()->getSession($name); + } - /** - * @Then /^I should see an edit page form$/ - */ - public function stepIShouldSeeAnEditPageForm() - { - $page = $this->getSession()->getPage(); + /** + * @Then /^I should see an edit page form$/ + */ + public function stepIShouldSeeAnEditPageForm() + { + $page = $this->getSession()->getPage(); - $form = $page->find('css', '#Form_EditForm'); - assertNotNull($form, 'I should see an edit page form'); - } + $form = $page->find('css', '#Form_EditForm'); + assertNotNull($form, 'I should see an edit page form'); + } - /** - * @When /^I fill in the "(?P([^"]*))" HTML field with "(?P([^"]*))"$/ - * @When /^I fill in "(?P([^"]*))" for the "(?P([^"]*))" HTML field$/ - */ - public function stepIFillInTheHtmlFieldWith($field, $value) - { - $page = $this->getSession()->getPage(); - $inputField = $page->findField($field); - assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); + /** + * @When /^I fill in the "(?P([^"]*))" HTML field with "(?P([^"]*))"$/ + * @When /^I fill in "(?P([^"]*))" for the "(?P([^"]*))" HTML field$/ + */ + public function stepIFillInTheHtmlFieldWith($field, $value) + { + $page = $this->getSession()->getPage(); + $inputField = $page->findField($field); + assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); - $this->getSession()->evaluateScript(sprintf( - "jQuery('#%s').entwine('ss').getEditor().setContent('%s')", - $inputField->getAttribute('id'), - addcslashes($value, "'") - )); - } + $this->getSession()->evaluateScript(sprintf( + "jQuery('#%s').entwine('ss').getEditor().setContent('%s')", + $inputField->getAttribute('id'), + addcslashes($value, "'") + )); + } - /** - * @When /^I append "(?P([^"]*))" to the "(?P([^"]*))" HTML field$/ - */ - public function stepIAppendTotheHtmlField($field, $value) - { - $page = $this->getSession()->getPage(); - $inputField = $page->findField($field); - assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); + /** + * @When /^I append "(?P([^"]*))" to the "(?P([^"]*))" HTML field$/ + */ + public function stepIAppendTotheHtmlField($field, $value) + { + $page = $this->getSession()->getPage(); + $inputField = $page->findField($field); + assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); - $this->getSession()->evaluateScript(sprintf( - "jQuery('#%s').entwine('ss').getEditor().insertContent('%s')", - $inputField->getAttribute('id'), - addcslashes($value, "'") - )); - } + $this->getSession()->evaluateScript(sprintf( + "jQuery('#%s').entwine('ss').getEditor().insertContent('%s')", + $inputField->getAttribute('id'), + addcslashes($value, "'") + )); + } - /** - * @Then /^the "(?P([^"]*))" HTML field should contain "(?P([^"]*))"$/ - */ - public function theHtmlFieldShouldContain($field, $value) - { - $page = $this->getSession()->getPage(); - $inputField = $page->findField($field); - assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); + /** + * @Then /^the "(?P([^"]*))" HTML field should contain "(?P([^"]*))"$/ + */ + public function theHtmlFieldShouldContain($field, $value) + { + $page = $this->getSession()->getPage(); + $inputField = $page->findField($field); + assertNotNull($inputField, sprintf('HTML field "%s" not found', $field)); - $this->getMainContext()->assertElementContains('#' . $inputField->getAttribute('id'), $value); - } + $this->getMainContext()->assertElementContains('#' . $inputField->getAttribute('id'), $value); + } } diff --git a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php index 42d2b2e73..0cbcb4ed2 100644 --- a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php +++ b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php @@ -3,13 +3,13 @@ namespace SilverStripe\Framework\Test\Behaviour; use Behat\Behat\Context\ClosuredContextInterface, - Behat\Behat\Context\TranslatedContextInterface, - Behat\Behat\Context\BehatContext, - Behat\Behat\Context\Step, - Behat\Behat\Exception\PendingException, - Behat\Mink\Exception\ElementNotFoundException; + Behat\Behat\Context\TranslatedContextInterface, + Behat\Behat\Context\BehatContext, + Behat\Behat\Context\Step, + Behat\Behat\Exception\PendingException, + Behat\Mink\Exception\ElementNotFoundException; use Behat\Gherkin\Node\PyStringNode, - Behat\Gherkin\Node\TableNode; + Behat\Gherkin\Node\TableNode; // PHPUnit @@ -23,376 +23,376 @@ require_once 'PHPUnit/Framework/Assert/Functions.php'; */ class CmsUiContext extends BehatContext { - protected $context; + protected $context; - /** - * Initializes context. - * Every scenario gets it's own context object. - * - * @param array $parameters context parameters (set them up through behat.yml) - */ - public function __construct(array $parameters) - { - // Initialize your context here - $this->context = $parameters; - } + /** + * Initializes context. + * Every scenario gets it's own context object. + * + * @param array $parameters context parameters (set them up through behat.yml) + */ + public function __construct(array $parameters) + { + // Initialize your context here + $this->context = $parameters; + } - /** - * Get Mink session from MinkContext - */ - public function getSession($name = null) - { - return $this->getMainContext()->getSession($name); - } + /** + * Get Mink session from MinkContext + */ + public function getSession($name = null) + { + return $this->getMainContext()->getSession($name); + } - /** - * @Then /^I should see the CMS$/ - */ - public function iShouldSeeTheCms() - { - $page = $this->getSession()->getPage(); - $cms_element = $page->find('css', '.cms'); - assertNotNull($cms_element, 'CMS not found'); - } + /** + * @Then /^I should see the CMS$/ + */ + public function iShouldSeeTheCms() + { + $page = $this->getSession()->getPage(); + $cms_element = $page->find('css', '.cms'); + assertNotNull($cms_element, 'CMS not found'); + } - /** - * @Then /^I should see a "([^"]*)" notice$/ - */ - public function iShouldSeeANotice($notice) - { - $this->getMainContext()->assertElementContains('.notice-wrap', $notice); - } + /** + * @Then /^I should see a "([^"]*)" notice$/ + */ + public function iShouldSeeANotice($notice) + { + $this->getMainContext()->assertElementContains('.notice-wrap', $notice); + } - /** - * @Then /^I should see a "([^"]*)" message$/ - */ - public function iShouldSeeAMessage($message) - { - $this->getMainContext()->assertElementContains('.message', $message); - } + /** + * @Then /^I should see a "([^"]*)" message$/ + */ + public function iShouldSeeAMessage($message) + { + $this->getMainContext()->assertElementContains('.message', $message); + } - protected function getCmsTabsElement() - { - $this->getSession()->wait(5000, "window.jQuery('.cms-content-header-tabs').size() > 0"); + protected function getCmsTabsElement() + { + $this->getSession()->wait(5000, "window.jQuery('.cms-content-header-tabs').size() > 0"); - $page = $this->getSession()->getPage(); - $cms_content_header_tabs = $page->find('css', '.cms-content-header-tabs'); - assertNotNull($cms_content_header_tabs, 'CMS tabs not found'); + $page = $this->getSession()->getPage(); + $cms_content_header_tabs = $page->find('css', '.cms-content-header-tabs'); + assertNotNull($cms_content_header_tabs, 'CMS tabs not found'); - return $cms_content_header_tabs; - } + return $cms_content_header_tabs; + } - protected function getCmsContentToolbarElement() - { - $this->getSession()->wait( - 5000, - "window.jQuery('.cms-content-toolbar').size() > 0 " - . "&& window.jQuery('.cms-content-toolbar').children().size() > 0" - ); + protected function getCmsContentToolbarElement() + { + $this->getSession()->wait( + 5000, + "window.jQuery('.cms-content-toolbar').size() > 0 " + . "&& window.jQuery('.cms-content-toolbar').children().size() > 0" + ); - $page = $this->getSession()->getPage(); - $cms_content_toolbar_element = $page->find('css', '.cms-content-toolbar'); - assertNotNull($cms_content_toolbar_element, 'CMS content toolbar not found'); + $page = $this->getSession()->getPage(); + $cms_content_toolbar_element = $page->find('css', '.cms-content-toolbar'); + assertNotNull($cms_content_toolbar_element, 'CMS content toolbar not found'); - return $cms_content_toolbar_element; - } + return $cms_content_toolbar_element; + } - protected function getCmsTreeElement() - { - $this->getSession()->wait(5000, "window.jQuery('.cms-tree').size() > 0"); + protected function getCmsTreeElement() + { + $this->getSession()->wait(5000, "window.jQuery('.cms-tree').size() > 0"); - $page = $this->getSession()->getPage(); - $cms_tree_element = $page->find('css', '.cms-tree'); - assertNotNull($cms_tree_element, 'CMS tree not found'); + $page = $this->getSession()->getPage(); + $cms_tree_element = $page->find('css', '.cms-tree'); + assertNotNull($cms_tree_element, 'CMS tree not found'); - return $cms_tree_element; - } + return $cms_tree_element; + } - protected function getGridfieldTable($title) - { - $page = $this->getSession()->getPage(); - $table_elements = $page->findAll('css', '.ss-gridfield-table'); - assertNotNull($table_elements, 'Table elements not found'); + protected function getGridfieldTable($title) + { + $page = $this->getSession()->getPage(); + $table_elements = $page->findAll('css', '.ss-gridfield-table'); + assertNotNull($table_elements, 'Table elements not found'); - $table_element = null; - foreach ($table_elements as $table) { - $table_title_element = $table->find('css', '.title'); - if ($table_title_element->getText() === $title) { - $table_element = $table; - break; - } - } - assertNotNull($table_element, sprintf('Table `%s` not found', $title)); + $table_element = null; + foreach ($table_elements as $table) { + $table_title_element = $table->find('css', '.title'); + if ($table_title_element->getText() === $title) { + $table_element = $table; + break; + } + } + assertNotNull($table_element, sprintf('Table `%s` not found', $title)); - return $table_element; - } + return $table_element; + } - /** - * @Given /^I should see a "([^"]*)" button in CMS Content Toolbar$/ - */ - public function iShouldSeeAButtonInCmsContentToolbar($text) - { - $cms_content_toolbar_element = $this->getCmsContentToolbarElement(); + /** + * @Given /^I should see a "([^"]*)" button in CMS Content Toolbar$/ + */ + public function iShouldSeeAButtonInCmsContentToolbar($text) + { + $cms_content_toolbar_element = $this->getCmsContentToolbarElement(); - $element = $cms_content_toolbar_element->find('named', array('link_or_button', "'$text'")); - assertNotNull($element, sprintf('%s button not found', $text)); - } + $element = $cms_content_toolbar_element->find('named', array('link_or_button', "'$text'")); + assertNotNull($element, sprintf('%s button not found', $text)); + } - /** - * @When /^I should see "([^"]*)" in CMS Tree$/ - */ - public function stepIShouldSeeInCmsTree($text) - { - $cms_tree_element = $this->getCmsTreeElement(); + /** + * @When /^I should see "([^"]*)" in CMS Tree$/ + */ + public function stepIShouldSeeInCmsTree($text) + { + $cms_tree_element = $this->getCmsTreeElement(); - $element = $cms_tree_element->find('named', array('content', "'$text'")); - assertNotNull($element, sprintf('%s not found', $text)); - } + $element = $cms_tree_element->find('named', array('content', "'$text'")); + assertNotNull($element, sprintf('%s not found', $text)); + } - /** - * @When /^I should not see "([^"]*)" in CMS Tree$/ - */ - public function stepIShouldNotSeeInCmsTree($text) - { - $cms_tree_element = $this->getCmsTreeElement(); + /** + * @When /^I should not see "([^"]*)" in CMS Tree$/ + */ + public function stepIShouldNotSeeInCmsTree($text) + { + $cms_tree_element = $this->getCmsTreeElement(); - $element = $cms_tree_element->find('named', array('content', "'$text'")); - assertNull($element, sprintf('%s found', $text)); - } + $element = $cms_tree_element->find('named', array('content', "'$text'")); + assertNull($element, sprintf('%s found', $text)); + } - /** - * @When /^I expand the "([^"]*)" CMS Panel$/ - */ - public function iExpandTheCmsPanel() - { - // TODO Make dynamic, currently hardcoded to first panel - $page = $this->getSession()->getPage(); + /** + * @When /^I expand the "([^"]*)" CMS Panel$/ + */ + public function iExpandTheCmsPanel() + { + // TODO Make dynamic, currently hardcoded to first panel + $page = $this->getSession()->getPage(); - $panel_toggle_element = $page->find('css', '.cms-content > .cms-panel > .cms-panel-toggle > .toggle-expand'); - assertNotNull($panel_toggle_element, 'Panel toggle not found'); + $panel_toggle_element = $page->find('css', '.cms-content > .cms-panel > .cms-panel-toggle > .toggle-expand'); + assertNotNull($panel_toggle_element, 'Panel toggle not found'); - if ($panel_toggle_element->isVisible()) { - $panel_toggle_element->click(); - } - } + if ($panel_toggle_element->isVisible()) { + $panel_toggle_element->click(); + } + } - /** - * @When /^I click the "([^"]*)" CMS tab$/ - */ - public function iClickTheCmsTab($tab) - { - $this->getSession()->wait(5000, "window.jQuery('.ui-tabs-nav').size() > 0"); + /** + * @When /^I click the "([^"]*)" CMS tab$/ + */ + public function iClickTheCmsTab($tab) + { + $this->getSession()->wait(5000, "window.jQuery('.ui-tabs-nav').size() > 0"); - $page = $this->getSession()->getPage(); - $tabsets = $page->findAll('css', '.ui-tabs-nav'); - assertNotNull($tabsets, 'CMS tabs not found'); + $page = $this->getSession()->getPage(); + $tabsets = $page->findAll('css', '.ui-tabs-nav'); + assertNotNull($tabsets, 'CMS tabs not found'); - $tab_element = null; - foreach($tabsets as $tabset) { - if($tab_element) continue; - $tab_element = $tabset->find('named', array('link_or_button', "'$tab'")); - } - assertNotNull($tab_element, sprintf('%s tab not found', $tab)); + $tab_element = null; + foreach($tabsets as $tabset) { + if($tab_element) continue; + $tab_element = $tabset->find('named', array('link_or_button', "'$tab'")); + } + assertNotNull($tab_element, sprintf('%s tab not found', $tab)); - $tab_element->click(); - } + $tab_element->click(); + } - /** - * @Then /^the "([^"]*)" table should contain "([^"]*)"$/ - */ - public function theTableShouldContain($table, $text) - { - $table_element = $this->getGridfieldTable($table); + /** + * @Then /^the "([^"]*)" table should contain "([^"]*)"$/ + */ + public function theTableShouldContain($table, $text) + { + $table_element = $this->getGridfieldTable($table); - $element = $table_element->find('named', array('content', "'$text'")); - assertNotNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $table)); - } + $element = $table_element->find('named', array('content', "'$text'")); + assertNotNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $table)); + } - /** - * @Then /^the "([^"]*)" table should not contain "([^"]*)"$/ - */ - public function theTableShouldNotContain($table, $text) - { - $table_element = $this->getGridfieldTable($table); + /** + * @Then /^the "([^"]*)" table should not contain "([^"]*)"$/ + */ + public function theTableShouldNotContain($table, $text) + { + $table_element = $this->getGridfieldTable($table); - $element = $table_element->find('named', array('content', "'$text'")); - assertNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $table)); - } + $element = $table_element->find('named', array('content', "'$text'")); + assertNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $table)); + } - /** - * @Given /^I click on "([^"]*)" in the "([^"]*)" table$/ - */ - public function iClickOnInTheTable($text, $table) - { - $table_element = $this->getGridfieldTable($table); + /** + * @Given /^I click on "([^"]*)" in the "([^"]*)" table$/ + */ + public function iClickOnInTheTable($text, $table) + { + $table_element = $this->getGridfieldTable($table); - $element = $table_element->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text)); - assertNotNull($element, sprintf('Element containing `%s` not found', $text)); - $element->click(); - } + $element = $table_element->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text)); + assertNotNull($element, sprintf('Element containing `%s` not found', $text)); + $element->click(); + } - /** - * @Then /^I can see the preview panel$/ - */ - public function iCanSeeThePreviewPanel() - { - $this->getMainContext()->assertElementOnPage('.cms-preview'); - } + /** + * @Then /^I can see the preview panel$/ + */ + public function iCanSeeThePreviewPanel() + { + $this->getMainContext()->assertElementOnPage('.cms-preview'); + } - /** - * @Given /^the preview contains "([^"]*)"$/ - */ - public function thePreviewContains($content) - { - $driver = $this->getSession()->getDriver(); - $driver->switchToIFrame('cms-preview-iframe'); + /** + * @Given /^the preview contains "([^"]*)"$/ + */ + public function thePreviewContains($content) + { + $driver = $this->getSession()->getDriver(); + $driver->switchToIFrame('cms-preview-iframe'); - $this->getMainContext()->assertPageContainsText($content); - $driver->switchToWindow(); - } + $this->getMainContext()->assertPageContainsText($content); + $driver->switchToWindow(); + } - /** - * @Given /^I set the CMS mode to "([^"]*)"$/ - */ - public function iSetTheCmsToMode($mode) - { - return array( - new Step\When(sprintf('I fill in the "Change view mode" dropdown with "%s"', $mode)), - new Step\When('I wait for 1 second') // wait for CMS layout to redraw - ); - } + /** + * @Given /^I set the CMS mode to "([^"]*)"$/ + */ + public function iSetTheCmsToMode($mode) + { + return array( + new Step\When(sprintf('I fill in the "Change view mode" dropdown with "%s"', $mode)), + new Step\When('I wait for 1 second') // wait for CMS layout to redraw + ); + } - /** - * @Given /^I wait for the preview to load$/ - */ - public function iWaitForThePreviewToLoad() - { - $driver = $this->getSession()->getDriver(); - $driver->switchToIFrame('cms-preview-iframe'); - - $this->getSession()->wait( - 5000, - "!jQuery('iframe[name=cms-preview-iframe]').hasClass('loading')" - ); + /** + * @Given /^I wait for the preview to load$/ + */ + public function iWaitForThePreviewToLoad() + { + $driver = $this->getSession()->getDriver(); + $driver->switchToIFrame('cms-preview-iframe'); + + $this->getSession()->wait( + 5000, + "!jQuery('iframe[name=cms-preview-iframe]').hasClass('loading')" + ); - $driver->switchToWindow(); - } + $driver->switchToWindow(); + } - /** - * @Given /^I switch the preview to "([^"]*)"$/ - */ - public function iSwitchThePreviewToMode($mode) - { - $controls = $this->getSession()->getPage()->find('css', '.cms-preview-controls'); - assertNotNull($controls, 'Preview controls not found'); + /** + * @Given /^I switch the preview to "([^"]*)"$/ + */ + public function iSwitchThePreviewToMode($mode) + { + $controls = $this->getSession()->getPage()->find('css', '.cms-preview-controls'); + assertNotNull($controls, 'Preview controls not found'); - $label = $controls->find('xpath', sprintf( - './/label[(@for="%s")]', - $mode - )); - assertNotNull($label, 'Preview mode switch not found'); + $label = $controls->find('xpath', sprintf( + './/label[(@for="%s")]', + $mode + )); + assertNotNull($label, 'Preview mode switch not found'); - $label->click(); + $label->click(); - return new Step\When('I wait for the preview to load'); - } + return new Step\When('I wait for the preview to load'); + } - /** - * @Given /^the preview does not contain "([^"]*)"$/ - */ - public function thePreviewDoesNotContain($content) - { - $driver = $this->getSession()->getDriver(); - $driver->switchToIFrame('cms-preview-iframe'); + /** + * @Given /^the preview does not contain "([^"]*)"$/ + */ + public function thePreviewDoesNotContain($content) + { + $driver = $this->getSession()->getDriver(); + $driver->switchToIFrame('cms-preview-iframe'); - $this->getMainContext()->assertPageNotContainsText($content); - $driver->switchToWindow(); - } + $this->getMainContext()->assertPageNotContainsText($content); + $driver->switchToWindow(); + } - /** - * Workaround for chosen.js dropdowns which hide the original dropdown field. - * - * @When /^(?:|I )fill in the "(?P(?:[^"]|\\")*)" dropdown with "(?P(?:[^"]|\\")*)"$/ - * @When /^(?:|I )fill in "(?P(?:[^"]|\\")*)" for the "(?P(?:[^"]|\\")*)" dropdown$/ - */ - public function theIFillInTheDropdownWith($field, $value) - { - $field = $this->fixStepArgument($field); - $value = $this->fixStepArgument($value); + /** + * Workaround for chosen.js dropdowns which hide the original dropdown field. + * + * @When /^(?:|I )fill in the "(?P(?:[^"]|\\")*)" dropdown with "(?P(?:[^"]|\\")*)"$/ + * @When /^(?:|I )fill in "(?P(?:[^"]|\\")*)" for the "(?P(?:[^"]|\\")*)" dropdown$/ + */ + public function theIFillInTheDropdownWith($field, $value) + { + $field = $this->fixStepArgument($field); + $value = $this->fixStepArgument($value); - // Given the fuzzy matching, we might get more than one matching field. - $formFields = array(); + // Given the fuzzy matching, we might get more than one matching field. + $formFields = array(); - // Find by label - $formField = $this->getSession()->getPage()->findField($field); - if($formField) $formFields[] = $formField; + // Find by label + $formField = $this->getSession()->getPage()->findField($field); + if($formField) $formFields[] = $formField; - // Fall back to finding by title (for dropdowns without a label) - if(!$formFields) { - $formFields = $this->getSession()->getPage()->findAll( - 'xpath', - sprintf( - '//*[self::select][(./@title="%s")]', - $field - ) - ); - } + // Fall back to finding by title (for dropdowns without a label) + if(!$formFields) { + $formFields = $this->getSession()->getPage()->findAll( + 'xpath', + sprintf( + '//*[self::select][(./@title="%s")]', + $field + ) + ); + } - assertGreaterThan(0, count($formFields), sprintf( - 'Chosen.js dropdown named "%s" not found', - $field - )); + assertGreaterThan(0, count($formFields), sprintf( + 'Chosen.js dropdown named "%s" not found', + $field + )); - $containers = array(); - foreach($formFields as $formField) { - // Traverse up to field holder - $containerCandidate = $formField; - do { - $containerCandidate = $containerCandidate->getParent(); - } while($containerCandidate && !preg_match('/field/', $containerCandidate->getAttribute('class'))); + $containers = array(); + foreach($formFields as $formField) { + // Traverse up to field holder + $containerCandidate = $formField; + do { + $containerCandidate = $containerCandidate->getParent(); + } while($containerCandidate && !preg_match('/field/', $containerCandidate->getAttribute('class'))); - if( - $containerCandidate - && $containerCandidate->isVisible() - && preg_match('/field/', $containerCandidate->getAttribute('class')) - ) { - $containers[] = $containerCandidate; - } - } - - assertGreaterThan(0, count($containers), 'Chosen.js field container not found'); + if( + $containerCandidate + && $containerCandidate->isVisible() + && preg_match('/field/', $containerCandidate->getAttribute('class')) + ) { + $containers[] = $containerCandidate; + } + } + + assertGreaterThan(0, count($containers), 'Chosen.js field container not found'); - // Default to first visible container - $container = $containers[0]; - - // Click on newly expanded list element, indirectly setting the dropdown value - $linkEl = $container->find('xpath', './/a[./@href]'); - assertNotNull($linkEl, 'Chosen.js link element not found'); - $this->getSession()->wait(100); // wait for dropdown overlay to appear - $linkEl->click(); + // Default to first visible container + $container = $containers[0]; + + // Click on newly expanded list element, indirectly setting the dropdown value + $linkEl = $container->find('xpath', './/a[./@href]'); + assertNotNull($linkEl, 'Chosen.js link element not found'); + $this->getSession()->wait(100); // wait for dropdown overlay to appear + $linkEl->click(); - $listEl = $container->find('xpath', sprintf('.//li[contains(normalize-space(string(.)), \'%s\')]', $value)); - assertNotNull($listEl, sprintf( - 'Chosen.js list element with title "%s" not found', - $value - )); + $listEl = $container->find('xpath', sprintf('.//li[contains(normalize-space(string(.)), \'%s\')]', $value)); + assertNotNull($listEl, sprintf( + 'Chosen.js list element with title "%s" not found', + $value + )); - // Dropdown flyout might be animated - // $this->getSession()->wait(1000, 'jQuery(":animated").length == 0'); - $this->getSession()->wait(300); + // Dropdown flyout might be animated + // $this->getSession()->wait(1000, 'jQuery(":animated").length == 0'); + $this->getSession()->wait(300); - $listEl->click(); - } + $listEl->click(); + } - /** - * Returns fixed step argument (with \\" replaced back to "). - * - * @param string $argument - * - * @return string - */ - protected function fixStepArgument($argument) - { - return str_replace('\\"', '"', $argument); - } + /** + * Returns fixed step argument (with \\" replaced back to "). + * + * @param string $argument + * + * @return string + */ + protected function fixStepArgument($argument) + { + return str_replace('\\"', '"', $argument); + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1904a068f..8e4da445d 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -27,18 +27,18 @@ if(!defined('BASE_PATH')) define('BASE_PATH', dirname($frameworkPath)); // Copied from cli-script.php, to enable same behaviour through phpunit runner. if(isset($_SERVER['argv'][2])) { - $args = array_slice($_SERVER['argv'],2); - if(!isset($_GET)) $_GET = array(); - if(!isset($_REQUEST)) $_REQUEST = array(); - foreach($args as $arg) { - if(strpos($arg,'=') == false) { - $_GET['args'][] = $arg; - } else { - $newItems = array(); - parse_str( (substr($arg,0,2) == '--') ? substr($arg,2) : $arg, $newItems ); - $_GET = array_merge($_GET, $newItems); - } - } + $args = array_slice($_SERVER['argv'],2); + if(!isset($_GET)) $_GET = array(); + if(!isset($_REQUEST)) $_REQUEST = array(); + foreach($args as $arg) { + if(strpos($arg,'=') == false) { + $_GET['args'][] = $arg; + } else { + $newItems = array(); + parse_str( (substr($arg,0,2) == '--') ? substr($arg,2) : $arg, $newItems ); + $_GET = array_merge($_GET, $newItems); + } + } $_REQUEST = array_merge($_REQUEST, $_GET); } @@ -70,4 +70,4 @@ if(!isset($_GET['flush']) || !$_GET['flush']) { "Add flush=1 as an argument to discover new classes or files.\n", false ); -} \ No newline at end of file +} diff --git a/tests/control/HTTPTest.php b/tests/control/HTTPTest.php index 654b824cb..5379c7ae9 100644 --- a/tests/control/HTTPTest.php +++ b/tests/control/HTTPTest.php @@ -10,8 +10,8 @@ class HTTPTest extends SapphireTest { /** * Tests {@link HTTP::getLinksIn()} */ - public function testGetLinksIn() { - $content = ' + public function testGetLinksIn() { + $content = '

    My Cool Site

    @@ -26,13 +26,13 @@ class HTTPTest extends SapphireTest { played a part in his journey. HE ALSO DISCOVERED THE KEY. Later he got his mixed up.

    - '; - + '; + $expected = array ( '/', 'home/', 'mother/', '$Journey', 'space travel', 'unquoted', 'single quote', '/father', 'attributes', 'journey', 'CAPS LOCK', 'quotes \'mixed\' up' ); - + $result = HTTP::getLinksIn($content); // Results don't neccesarily come out in the order they are in the $content param. @@ -41,7 +41,7 @@ class HTTPTest extends SapphireTest { $this->assertTrue(is_array($result)); $this->assertEquals($expected, $result, 'Test that all links within the content are found.'); - } + } /** * Tests {@link HTTP::setGetVar()} diff --git a/tests/core/ArrayDataTest.php b/tests/core/ArrayDataTest.php index 6869246f6..88835b630 100644 --- a/tests/core/ArrayDataTest.php +++ b/tests/core/ArrayDataTest.php @@ -11,7 +11,7 @@ class ArrayDataTest extends SapphireTest { $this->assertEquals("Varchar", get_class($arrayData->A)); $this->assertEquals("ArrayData", get_class($arrayData->B)); } - + public function testWrappingANonEmptyObjectWorks() { $object = new ArrayDataTest_NonEmptyObject(); $this->assertTrue(is_object($object)); diff --git a/tests/core/manifest/fixtures/namespaced_classmanifest/module/classes/ClassB.php b/tests/core/manifest/fixtures/namespaced_classmanifest/module/classes/ClassB.php index 1cd5a0dc4..59ea70846 100644 --- a/tests/core/manifest/fixtures/namespaced_classmanifest/module/classes/ClassB.php +++ b/tests/core/manifest/fixtures/namespaced_classmanifest/module/classes/ClassB.php @@ -4,5 +4,5 @@ */ namespace silverstripe\test; - + class ClassB extends ClassA { } diff --git a/tests/dev/CsvBulkLoaderTest.php b/tests/dev/CsvBulkLoaderTest.php index 2df55a45c..435972547 100644 --- a/tests/dev/CsvBulkLoaderTest.php +++ b/tests/dev/CsvBulkLoaderTest.php @@ -55,7 +55,7 @@ class CsvBulkLoaderTest extends SapphireTest { $this->assertEquals(4, $resultDataObject->Count(), 'Test if existing data is deleted before new data is added'); - } + } /** * Test import with manual column mapping diff --git a/tests/forms/FileFieldTest.php b/tests/forms/FileFieldTest.php index 83bf22d57..39017d1e1 100644 --- a/tests/forms/FileFieldTest.php +++ b/tests/forms/FileFieldTest.php @@ -18,11 +18,11 @@ class FileFieldTest extends FunctionalTest { new FieldList() ); $fileFieldValue = array( - 'name' => 'aCV.txt', - 'type' => 'application/octet-stream', - 'tmp_name' => '/private/var/tmp/phpzTQbqP', - 'error' => 0, - 'size' => 3471 + 'name' => 'aCV.txt', + 'type' => 'application/octet-stream', + 'tmp_name' => '/private/var/tmp/phpzTQbqP', + 'error' => 0, + 'size' => 3471 ); $fileField->setValue($fileFieldValue); @@ -46,11 +46,11 @@ class FileFieldTest extends FunctionalTest { ); // All fields are filled but for some reason an error occured when uploading the file => fails $fileFieldValue = array( - 'name' => 'aCV.txt', - 'type' => 'application/octet-stream', - 'tmp_name' => '/private/var/tmp/phpzTQbqP', - 'error' => 1, - 'size' => 3471 + 'name' => 'aCV.txt', + 'type' => 'application/octet-stream', + 'tmp_name' => '/private/var/tmp/phpzTQbqP', + 'error' => 1, + 'size' => 3471 ); $fileField->setValue($fileFieldValue); diff --git a/tests/forms/RequirementsTest.php b/tests/forms/RequirementsTest.php index 597bbff23..9c53e0ec9 100644 --- a/tests/forms/RequirementsTest.php +++ b/tests/forms/RequirementsTest.php @@ -368,7 +368,7 @@ class RequirementsTest extends SapphireTest { ); } } - + public function assertFileNotIncluded($backend, $type, $files) { $type = strtolower($type); switch ($type) { @@ -412,4 +412,4 @@ class RequirementsTest extends SapphireTest { ); } } -} \ No newline at end of file +} diff --git a/tests/forms/uploadfield/UploadFieldTest.php b/tests/forms/uploadfield/UploadFieldTest.php index aebb9feb3..2ceb16b17 100644 --- a/tests/forms/uploadfield/UploadFieldTest.php +++ b/tests/forms/uploadfield/UploadFieldTest.php @@ -4,7 +4,7 @@ * @subpackage tests */ - class UploadFieldTest extends FunctionalTest { +class UploadFieldTest extends FunctionalTest { static $fixture_file = 'UploadFieldTest.yml'; diff --git a/tests/injector/testservices/SampleService.php b/tests/injector/testservices/SampleService.php index 738b6afe4..c43ff3110 100644 --- a/tests/injector/testservices/SampleService.php +++ b/tests/injector/testservices/SampleService.php @@ -2,11 +2,11 @@ class SampleService { - public $constructorVarOne; + public $constructorVarOne; public $constructorVarTwo; public function __construct($v1 = null, $v2 = null) { $this->constructorVarOne = $v1; $this->constructorVarTwo = $v2; } -} \ No newline at end of file +} diff --git a/tests/javascript/TreeDropDownField/TreeDropdownField.js b/tests/javascript/TreeDropDownField/TreeDropdownField.js index 05ebd1849..07afd1997 100644 --- a/tests/javascript/TreeDropDownField/TreeDropdownField.js +++ b/tests/javascript/TreeDropDownField/TreeDropdownField.js @@ -49,7 +49,7 @@ '' + '' ); - }); + }); afterEach(function() { $('#testfield').remove(); @@ -114,7 +114,7 @@ '' + '' ); - }); + }); afterEach(function() { $('#testfield').remove(); @@ -146,7 +146,7 @@ '' + '' ); - }); + }); afterEach(function() { $('#testfield').remove(); @@ -222,7 +222,7 @@ '' + '' ); - }); + }); afterEach(function() { $('#testfield').remove(); @@ -236,4 +236,4 @@ }); }); }); -}(jQuery)); \ No newline at end of file +}(jQuery)); diff --git a/tests/model/ComponentSetTest.php b/tests/model/ComponentSetTest.php index 8a067fec4..074fe2503 100644 --- a/tests/model/ComponentSetTest.php +++ b/tests/model/ComponentSetTest.php @@ -60,7 +60,6 @@ class ComponentSetTest_Player extends Member implements TestOnly { static $belongs_many_many = array( 'Teams' => 'ComponentSetTest_Team' ); - } class ComponentSetTest_Team extends DataObject implements TestOnly { diff --git a/tests/model/DataListTest.php b/tests/model/DataListTest.php index 96ff41258..0ebc0c68d 100644 --- a/tests/model/DataListTest.php +++ b/tests/model/DataListTest.php @@ -624,11 +624,11 @@ class DataListTest extends SapphireTest { */ public function testExcludeOnFilter() { $list = DataObjectTest_TeamComment::get(); - $list = $list->filter('Comment', 'Phil is a unique guy, and comments on team2'); + $list = $list->filter('Comment', 'Phil is a unique guy, and comments on team2'); $list = $list->exclude('Name', 'Bob'); $this->assertContains( - 'WHERE ("DataObjectTest_TeamComment"."Comment" = ' + 'WHERE ("DataObjectTest_TeamComment"."Comment" = ' . '\'Phil is a unique guy, and comments on team2\') ' . 'AND (("DataObjectTest_TeamComment"."Name" != \'Bob\'))', $list->sql()); diff --git a/tests/model/DataObjectTest.php b/tests/model/DataObjectTest.php index 270c07bb2..e57f407f8 100644 --- a/tests/model/DataObjectTest.php +++ b/tests/model/DataObjectTest.php @@ -1125,7 +1125,6 @@ class DataObjectTest_Player extends Member implements TestOnly { static $belongs_many_many = array( 'Teams' => 'DataObjectTest_Team' ); - } class DataObjectTest_Team extends DataObject implements TestOnly { diff --git a/tests/model/MoneyTest.php b/tests/model/MoneyTest.php index 8b479493d..9b8bfc92d 100644 --- a/tests/model/MoneyTest.php +++ b/tests/model/MoneyTest.php @@ -71,12 +71,12 @@ class MoneyTest extends SapphireTest { } /** - * Write a Money object to the database, then re-read it to ensure it - * is re-read properly. - */ - public function testGettingWrittenDataObject() { - $local = i18n::get_locale(); - //make sure that the $ amount is not prefixed by US$, as it would be in non-US locale + * Write a Money object to the database, then re-read it to ensure it + * is re-read properly. + */ + public function testGettingWrittenDataObject() { + $local = i18n::get_locale(); + //make sure that the $ amount is not prefixed by US$, as it would be in non-US locale i18n::set_locale('en_US'); $obj = new MoneyTest_DataObject(); @@ -99,8 +99,8 @@ class MoneyTest extends SapphireTest { "Money field not added to data object properly when read." ); - i18n::set_locale($local); - } + i18n::set_locale($local); + } public function testToCurrency() { $USD = new Money(); diff --git a/tests/model/PaginatedListTest.php b/tests/model/PaginatedListTest.php index d0f7f8540..257212bcb 100644 --- a/tests/model/PaginatedListTest.php +++ b/tests/model/PaginatedListTest.php @@ -43,11 +43,11 @@ class PaginatedListTest extends SapphireTest { public function testSetPaginationFromQuery() { $query = $this->getMock('SQLQuery'); $query->expects($this->once()) - ->method('getLimit') - ->will($this->returnValue(array('limit' => 15, 'start' => 30))); + ->method('getLimit') + ->will($this->returnValue(array('limit' => 15, 'start' => 30))); $query->expects($this->once()) - ->method('unlimitedRowCount') - ->will($this->returnValue(100)); + ->method('unlimitedRowCount') + ->will($this->returnValue(100)); $list = new PaginatedList(new ArrayList()); $list->setPaginationFromQuery($query); diff --git a/tests/model/VersionedTest.php b/tests/model/VersionedTest.php index 34888d53c..6c1aec3b9 100644 --- a/tests/model/VersionedTest.php +++ b/tests/model/VersionedTest.php @@ -254,12 +254,12 @@ class VersionedTest extends SapphireTest { * Test that SQLQuery::queriedTables() applies the version-suffixes properly. */ public function testQueriedTables() { - Versioned::reading_stage('Live'); + Versioned::reading_stage('Live'); - $this->assertEquals(array( - 'VersionedTest_DataObject_Live', - 'VersionedTest_Subclass_Live', - ), DataObject::get('VersionedTest_Subclass')->dataQuery()->query()->queriedTables()); + $this->assertEquals(array( + 'VersionedTest_DataObject_Live', + 'VersionedTest_Subclass_Live', + ), DataObject::get('VersionedTest_Subclass')->dataQuery()->query()->queriedTables()); } public function testGetVersionWhenClassnameChanged() { diff --git a/tests/phpcs/tabs.xml b/tests/phpcs/tabs.xml new file mode 100644 index 000000000..93dc08cb6 --- /dev/null +++ b/tests/phpcs/tabs.xml @@ -0,0 +1,18 @@ + + + CodeSniffer ruleset for SilverStripe indentation conventions. + + + */css/* + css/* + + + thirdparty/* + */jquery-changetracker/* + parsers/HTML/BBCodeParser/* + + + */SSTemplateParser.php$ + + + diff --git a/tests/search/SearchContextTest.php b/tests/search/SearchContextTest.php index f9e9b41e4..f5a9b7ca2 100644 --- a/tests/search/SearchContextTest.php +++ b/tests/search/SearchContextTest.php @@ -41,40 +41,40 @@ class SearchContextTest extends SapphireTest { } public function testPartialMatchUsedByDefaultWhenNotExplicitlySet() { - $person = singleton('SearchContextTest_Person'); - $context = $person->getDefaultSearchContext(); - - $this->assertEquals( - array( - "Name" => new PartialMatchFilter("Name"), - "HairColor" => new PartialMatchFilter("HairColor"), - "EyeColor" => new PartialMatchFilter("EyeColor") - ), - $context->getFilters() - ); + $person = singleton('SearchContextTest_Person'); + $context = $person->getDefaultSearchContext(); + + $this->assertEquals( + array( + "Name" => new PartialMatchFilter("Name"), + "HairColor" => new PartialMatchFilter("HairColor"), + "EyeColor" => new PartialMatchFilter("EyeColor") + ), + $context->getFilters() + ); } public function testDefaultFiltersDefinedWhenNotSetInDataObject() { $book = singleton('SearchContextTest_Book'); $context = $book->getDefaultSearchContext(); - $this->assertEquals( - array( - "Title" => new PartialMatchFilter("Title") - ), - $context->getFilters() - ); + $this->assertEquals( + array( + "Title" => new PartialMatchFilter("Title") + ), + $context->getFilters() + ); } public function testUserDefinedFiltersAppearInSearchContext() { $company = singleton('SearchContextTest_Company'); $context = $company->getDefaultSearchContext(); - + $this->assertEquals( array( "Name" => new PartialMatchFilter("Name"), - "Industry" => new PartialMatchFilter("Industry"), - "AnnualProfit" => new PartialMatchFilter("AnnualProfit") + "Industry" => new PartialMatchFilter("Industry"), + "AnnualProfit" => new PartialMatchFilter("AnnualProfit") ), $context->getFilters() ); @@ -87,8 +87,8 @@ class SearchContextTest extends SapphireTest { $this->assertEquals( new FieldList( new TextField("Name", 'Name'), - new TextareaField("Industry", 'Industry'), - new NumericField("AnnualProfit", 'The Almighty Annual Profit') + new TextareaField("Industry", 'Industry'), + new NumericField("AnnualProfit", 'The Almighty Annual Profit') ), $context->getFields() ); diff --git a/tests/security/GroupTest.php b/tests/security/GroupTest.php index 0299d8d64..e923dad41 100644 --- a/tests/security/GroupTest.php +++ b/tests/security/GroupTest.php @@ -52,47 +52,47 @@ class GroupTest extends FunctionalTest { public function testMemberGroupRelationForm() { Session::set('loggedInAs', $this->idFromFixture('GroupTest_Member', 'admin')); - $adminGroup = $this->objFromFixture('Group', 'admingroup'); - $parentGroup = $this->objFromFixture('Group', 'parentgroup'); - $childGroup = $this->objFromFixture('Group', 'childgroup'); + $adminGroup = $this->objFromFixture('Group', 'admingroup'); + $parentGroup = $this->objFromFixture('Group', 'parentgroup'); + $childGroup = $this->objFromFixture('Group', 'childgroup'); - // Test single group relation through checkboxsetfield - $form = new GroupTest_MemberForm($this, 'Form'); - $member = $this->objFromFixture('GroupTest_Member', 'admin'); - $form->loadDataFrom($member); - $checkboxSetField = $form->Fields()->fieldByName('Groups'); - $checkboxSetField->setValue(array( - $adminGroup->ID => $adminGroup->ID, // keep existing relation - $parentGroup->ID => $parentGroup->ID, // add new relation - )); - $form->saveInto($member); - $updatedGroups = $member->Groups(); + // Test single group relation through checkboxsetfield + $form = new GroupTest_MemberForm($this, 'Form'); + $member = $this->objFromFixture('GroupTest_Member', 'admin'); + $form->loadDataFrom($member); + $checkboxSetField = $form->Fields()->fieldByName('Groups'); + $checkboxSetField->setValue(array( + $adminGroup->ID => $adminGroup->ID, // keep existing relation + $parentGroup->ID => $parentGroup->ID, // add new relation + )); + $form->saveInto($member); + $updatedGroups = $member->Groups(); - $this->assertEquals( + $this->assertEquals( array($adminGroup->ID, $parentGroup->ID), - $updatedGroups->column(), - "Adding a toplevel group works" - ); + $updatedGroups->column(), + "Adding a toplevel group works" + ); - // Test unsetting relationship - $form->loadDataFrom($member); - $checkboxSetField = $form->Fields()->fieldByName('Groups'); - $checkboxSetField->setValue(array( - $adminGroup->ID => $adminGroup->ID, // keep existing relation - //$parentGroup->ID => $parentGroup->ID, // remove previously set relation - )); - $form->saveInto($member); - $member->flushCache(); - $updatedGroups = $member->Groups(); - $this->assertEquals( + // Test unsetting relationship + $form->loadDataFrom($member); + $checkboxSetField = $form->Fields()->fieldByName('Groups'); + $checkboxSetField->setValue(array( + $adminGroup->ID => $adminGroup->ID, // keep existing relation + //$parentGroup->ID => $parentGroup->ID, // remove previously set relation + )); + $form->saveInto($member); + $member->flushCache(); + $updatedGroups = $member->Groups(); + $this->assertEquals( array($adminGroup->ID), - $updatedGroups->column(), - "Removing a previously added toplevel group works" - ); + $updatedGroups->column(), + "Removing a previously added toplevel group works" + ); - // Test adding child group + // Test adding child group - } + } public function testCollateAncestorIDs() { $parentGroup = $this->objFromFixture('Group', 'parentgroup'); @@ -139,37 +139,37 @@ class GroupTest extends FunctionalTest { } class GroupTest_Member extends Member implements TestOnly { - - public function getCMSFields() { - $groups = DataObject::get('Group'); - $groupsMap = ($groups) ? $groups->map() : false; - $fields = new FieldList( - new HiddenField('ID', 'ID'), - new CheckboxSetField( - 'Groups', - 'Groups', - $groupsMap - ) - ); - - return $fields; - } - + + public function getCMSFields() { + $groups = DataObject::get('Group'); + $groupsMap = ($groups) ? $groups->map() : false; + $fields = new FieldList( + new HiddenField('ID', 'ID'), + new CheckboxSetField( + 'Groups', + 'Groups', + $groupsMap + ) + ); + + return $fields; + } + } class GroupTest_MemberForm extends Form { - - public function __construct($controller, $name) { - $fields = singleton('GroupTest_Member')->getCMSFields(); - $actions = new FieldList( - new FormAction('doSave','save') - ); - - parent::__construct($controller, $name, $fields, $actions); - } - - public function doSave($data, $form) { - // done in testing methods - } - + + public function __construct($controller, $name) { + $fields = singleton('GroupTest_Member')->getCMSFields(); + $actions = new FieldList( + new FormAction('doSave','save') + ); + + parent::__construct($controller, $name, $fields, $actions); + } + + public function doSave($data, $form) { + // done in testing methods + } + } diff --git a/tests/security/MemberTest.php b/tests/security/MemberTest.php index 477db200d..099c8dcfb 100644 --- a/tests/security/MemberTest.php +++ b/tests/security/MemberTest.php @@ -29,8 +29,8 @@ class MemberTest extends FunctionalTest { } public function __destruct() { - i18n::set_default_locale($this->local); - } + i18n::set_default_locale($this->local); + } public function setUp() { parent::setUp(); diff --git a/tests/view/ViewableDataTest.php b/tests/view/ViewableDataTest.php index 93dcf45c0..40d770e02 100644 --- a/tests/view/ViewableDataTest.php +++ b/tests/view/ViewableDataTest.php @@ -155,9 +155,9 @@ class ViewableDataTest_Castable extends ViewableData { return $this->unsafeXML(); } - public function forTemplate() { - return 'castable'; - } + public function forTemplate() { + return 'castable'; + } } class ViewableDataTest_RequiresCasting extends ViewableData { diff --git a/view/ArrayData.php b/view/ArrayData.php index dd3575c9b..8c70201e0 100644 --- a/view/ArrayData.php +++ b/view/ArrayData.php @@ -65,7 +65,7 @@ class ArrayData extends ViewableData { return new ArrayData($value); } elseif (ArrayLib::is_associative($value)) { return new ArrayData($value); - } else { + } else { return $value; } } diff --git a/view/Requirements.php b/view/Requirements.php index b0ac446c3..8cbc5a322 100644 --- a/view/Requirements.php +++ b/view/Requirements.php @@ -22,7 +22,7 @@ class Requirements { * @return boolean */ public static function get_combined_files_enabled() { - return self::backend()->get_combined_files_enabled(); + return self::backend()->get_combined_files_enabled(); } /** @@ -656,7 +656,7 @@ class Requirements_Backend { $jsRequirements = ''; // Combine files - updates $this->javascript and $this->css - $this->process_combined_files(); + $this->process_combined_files(); foreach(array_diff_key($this->javascript,$this->blocked) as $file => $dummy) { $path = $this->path_for_file($file); @@ -1022,9 +1022,9 @@ class Requirements_Backend { // file exists, check modification date of every contained file $srcLastMod = 0; foreach($fileList as $file) { - if(file_exists($base . $file)) { - $srcLastMod = max(filemtime($base . $file), $srcLastMod); - } + if(file_exists($base . $file)) { + $srcLastMod = max(filemtime($base . $file), $srcLastMod); + } } $refresh = $srcLastMod > filemtime($combinedFilePath); } else { @@ -1070,9 +1070,9 @@ class Requirements_Backend { // method repeatedly - it will behave different on the second call! $this->javascript = $newJSRequirements; $this->css = $newCSSRequirements; - } - - public function get_custom_scripts() { + } + + public function get_custom_scripts() { $requirements = ""; if($this->customScript) { diff --git a/view/SSTemplateParser.php.inc b/view/SSTemplateParser.php.inc index 77635863a..7652ed105 100644 --- a/view/SSTemplateParser.php.inc +++ b/view/SSTemplateParser.php.inc @@ -453,7 +453,7 @@ class SSTemplateParser extends Parser { function Require_Call(&$res, $sub) { $res['php'] = "Requirements::".$sub['Method']['text'].'('.$sub['CallArguments']['php'].');'; } - + /*!* @@ -617,7 +617,7 @@ class SSTemplateParser extends Parser { function OldTTag_OldTPart(&$res, $sub) { $res['php'] = $sub['php']; } - + /*!* # This is the old <% sprintf(_t()) %> tag diff --git a/view/SSViewer.php b/view/SSViewer.php index 226d981fe..5c1317d55 100644 --- a/view/SSViewer.php +++ b/view/SSViewer.php @@ -664,10 +664,10 @@ class SSViewer { } if(!$this->chosenTemplates) { - $templateList = (is_array($templateList)) ? $templateList : array($templateList); - - user_error("None of these templates can be found in theme '" - . self::current_theme() . "': ". implode(".ss, ", $templateList) . ".ss", E_USER_WARNING); + $templateList = (is_array($templateList)) ? $templateList : array($templateList); + + user_error("None of these templates can be found in theme '" + . self::current_theme() . "': ". implode(".ss, ", $templateList) . ".ss", E_USER_WARNING); } } @@ -717,7 +717,7 @@ class SSViewer { protected static $options = array( 'rewriteHashlinks' => true, ); - + protected static $topLevel = array(); public static function topLevel() { From 0f60ca72555892cad61fc82dc3dcda64b57e2c17 Mon Sep 17 00:00:00 2001 From: Justin Martin Date: Tue, 11 Dec 2012 13:57:45 -0800 Subject: [PATCH 05/49] BUG: Confirmed Password Field now copies attributes to child fields. --- forms/ConfirmedPasswordField.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/forms/ConfirmedPasswordField.php b/forms/ConfirmedPasswordField.php index 7f8e9b82c..844e98367 100644 --- a/forms/ConfirmedPasswordField.php +++ b/forms/ConfirmedPasswordField.php @@ -122,6 +122,11 @@ class ConfirmedPasswordField extends FormField { foreach($this->children as $field) { $field->setDisabled($this->isDisabled()); $field->setReadonly($this->isReadonly()); + if(count($this->attributes)) { + foreach($this->attributes as $name => $value) { + $field->setAttribute($name, $value); + } + } $content .= $field->FieldHolder(); } From 441bb5f74c9a59d9a161bd69c5de7e933d3e2628 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 12 Dec 2012 15:12:23 +0100 Subject: [PATCH 06/49] Added travis environment info output --- tests/travis/before_script | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/travis/before_script b/tests/travis/before_script index d537d174b..adcdc0465 100755 --- a/tests/travis/before_script +++ b/tests/travis/before_script @@ -4,6 +4,14 @@ BUILD_DIR=$1 +# Environment info +echo "# Environment info" +echo " - `php --version`" +echo " - `mysql --version`" +echo " - `pg_config --version`" +echo " - SQLite3 `sqlite3 -version`" +echo "" + # Fetch all dependencies # TODO Replace with different composer.json variations From 639cc0222cf670df2355fb10332313e618738b7a Mon Sep 17 00:00:00 2001 From: Loz Calver Date: Wed, 12 Dec 2012 15:55:33 +0000 Subject: [PATCH 07/49] BUG Fix insert media form inserting images from other UploadFields (fixes #8051) The insert media form would pick up unwanted images from other UploadFields. Limiting where it is looking to only the closest form fixes this. --- javascript/HtmlEditorField.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/HtmlEditorField.js b/javascript/HtmlEditorField.js index d33afd4c5..656527fe0 100644 --- a/javascript/HtmlEditorField.js +++ b/javascript/HtmlEditorField.js @@ -874,7 +874,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; //get the uploaded file ID when this event triggers, signaling the upload has compeleted successfully editFieldIDs.push($(this).data('id')); }); - var uploadedFiles = $('.ss-uploadfield-files').children('.ss-uploadfield-item'); + var uploadedFiles = form.find('.ss-uploadfield-files').children('.ss-uploadfield-item'); uploadedFiles.each(function(){ var uploadedID = $(this).data('fileid'); if ($.inArray(uploadedID, editFieldIDs) == -1) { From 92e4b4fc5bf203ce523f355108d1caed9f1538eb Mon Sep 17 00:00:00 2001 From: Naomi Guyer Date: Tue, 23 Oct 2012 09:55:08 +1300 Subject: [PATCH 08/49] Remove sub navigation for "Files" (fixes 7956) Backport from master. Fixes display issues with expanded, unselected submenus ... by removing them. They're strictly not necessary since both "list" and "add" modes can be reached through the default AssetAdmin UI. --- admin/templates/Includes/LeftAndMain_Menu.ss | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/admin/templates/Includes/LeftAndMain_Menu.ss b/admin/templates/Includes/LeftAndMain_Menu.ss index 469bb40a9..3fa8cef9b 100644 --- a/admin/templates/Includes/LeftAndMain_Menu.ss +++ b/admin/templates/Includes/LeftAndMain_Menu.ss @@ -27,22 +27,7 @@ target="_blank"<% end_if%>>   $Title - - - <% if Code == 'AssetAdmin' %> - - <% end_if %> +
  • <% end_loop %> From 5fed5b91c996514d44297db45b58474e379c9b7b Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 3 Dec 2012 01:03:18 +0100 Subject: [PATCH 09/49] API Moved email bounce handling to new 'emailbouncehandler' module --- _config.php | 14 +-- docs/en/changelogs/3.1.0.md | 6 +- email/Email.php | 193 ++---------------------------------- security/Member.php | 3 - 4 files changed, 13 insertions(+), 203 deletions(-) diff --git a/_config.php b/_config.php index fea04185e..243d2f9bc 100644 --- a/_config.php +++ b/_config.php @@ -34,18 +34,6 @@ define('MCE_ROOT', FRAMEWORK_DIR . '/thirdparty/tinymce/'); ShortcodeParser::get('default')->register('file_link', array('File', 'link_shortcode_handler')); ShortcodeParser::get('default')->register('embed', array('Oembed', 'handle_shortcode')); -/** - * The secret key that needs to be sent along with pings to /Email_BounceHandler - * - * Change this to something different for increase security (you can - * override it in mysite/_config.php to ease upgrades). - * For more information see: - * {@link http://doc.silverstripe.org/doku.php?id=email_bouncehandler} - */ -if(!defined('EMAIL_BOUNCEHANDLER_KEY')) { - define('EMAIL_BOUNCEHANDLER_KEY', '1aaaf8fb60ea253dbf6efa71baaacbb3'); -} - // Zend_Cache temp directory setting $_ENV['TMPDIR'] = TEMP_FOLDER; // for *nix $_ENV['TMP'] = TEMP_FOLDER; // for Windows @@ -60,4 +48,4 @@ SS_Cache::pick_backend('aggregatestore', 'aggregate', 1000); Deprecation::notification_version('3.0.0'); // TODO Remove once new ManifestBuilder with submodule support is in place -require_once('admin/_config.php'); +require_once('admin/_config.php'); \ No newline at end of file diff --git a/docs/en/changelogs/3.1.0.md b/docs/en/changelogs/3.1.0.md index 08a883616..78a6149c4 100644 --- a/docs/en/changelogs/3.1.0.md +++ b/docs/en/changelogs/3.1.0.md @@ -14,6 +14,10 @@ * Removed `Member_ProfileForm`, use `CMSProfileController` instead * `SiteTree::$nested_urls` enabled by default. To disable, call `SiteTree::disable_nested_urls()`. * Removed CMS permission checks from `File->canEdit()` and `File->canDelete()`. If you have unsecured controllers relying on these permissions, please override them through a `DataExtension`. + * Moved email bounce handling to new ["emailbouncehandler" module](https://github.com/silverstripe-labs/silverstripe-emailbouncehandler), + including `Email_BounceHandler` and `Email_BounceRecord` classes, + as well as the `Member->Bounced` property. * Deprecated global email methods `htmlEmail()` and `plaintextEmail`, as well as various email helper methods like `encodeMultipart()`. Use the `Email` API, or the `Mailer` class where applicable. - * Removed non-functional `$inlineImages` option for sending emails * Removed support for keyed arrays in `SelectionGroup`, use new `SelectionGroup_Item` object + * Removed non-functional `$inlineImages` option for sending emails + * Removed support for keyed arrays in `SelectionGroup`, use new `SelectionGroup_Item` object to populate the list instead (see [API docs](api:SelectionGroup)). \ No newline at end of file diff --git a/email/Email.php b/email/Email.php index 337849071..376617545 100644 --- a/email/Email.php +++ b/email/Email.php @@ -16,8 +16,6 @@ if(isset($_SERVER['SERVER_NAME'])) { */ define('X_MAILER', 'SilverStripe Mailer - version 2006.06.21'); } -// Note: The constant 'BOUNCE_EMAIL' should be defined as a valid email address for where bounces should be returned -// to. /** * Class to support sending emails. @@ -113,11 +111,6 @@ class Email extends ViewableData { */ protected $template_data = null; - /** - * @param string $bounceHandlerURL - */ - protected $bounceHandlerURL = null; - /** * @param sring $admin_email_address The default administrator email address. * This will be set in the config on a site-by-site basis @@ -151,7 +144,11 @@ class Email extends ViewableData { if($body != null) $this->body = $body; if($cc != null) $this->cc = $cc; if($bcc != null) $this->bcc = $bcc; - if($bounceHandlerURL != null) $this->setBounceHandlerURL($bounceHandlerURL); + + if($bounceHandlerURL != null) { + Deprecation::notice('3.1', 'Use "emailbouncehandler" module'); + } + parent::__construct(); } @@ -163,12 +160,8 @@ class Email extends ViewableData { ); } - public function setBounceHandlerURL( $bounceHandlerURL ) { - if($bounceHandlerURL) { - $this->bounceHandlerURL = $bounceHandlerURL; - } else { - $this->bounceHandlerURL = $_SERVER['HTTP_HOST'] . Director::baseURL() . 'Email_BounceHandler'; - } + public function setBounceHandlerURL($bounceHandlerURL) { + Deprecation::notice('3.1', 'Use "emailbouncehandler" module'); } public function attachFile($filename, $attachedFilename = null, $mimetype = null) { @@ -388,12 +381,8 @@ class Email extends ViewableData { if(empty($this->from)) $this->from = Email::getAdminEmail(); - $this->setBounceHandlerURL($this->bounceHandlerURL); - $headers = $this->customHeaders; - $headers['X-SilverStripeBounceURL'] = $this->bounceHandlerURL; - if($messageID) $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID; if(project()) $headers['X-SilverStripeSite'] = project(); @@ -449,12 +438,8 @@ class Email extends ViewableData { if(empty($this->from)) $this->from = Email::getAdminEmail(); - $this->setBounceHandlerURL( $this->bounceHandlerURL ); - $headers = $this->customHeaders; - $headers['X-SilverStripeBounceURL'] = $this->bounceHandlerURL; - if($messageID) $headers['X-SilverStripeMessageID'] = project() . '.' . $messageID; if(project()) $headers['X-SilverStripeSite'] = project(); @@ -620,167 +605,3 @@ class Email extends ViewableData { } } } - -/** - * Base class that email bounce handlers extend - * @package framework - * @subpackage email - */ -class Email_BounceHandler extends Controller { - - static $allowed_actions = array( - 'index' - ); - - public function init() { - BasicAuth::protect_entire_site(false); - parent::init(); - } - - public function index() { - $subclasses = ClassInfo::subclassesFor( $this->class ); - unset($subclasses[$this->class]); - - if( $subclasses ) { - $subclass = array_pop( $subclasses ); - $task = new $subclass(); - $task->index(); - return; - } - - // Check if access key exists - if( !isset($_REQUEST['Key']) ) { - echo 'Error: Access validation failed. No "Key" specified.'; - return; - } - - // Check against access key defined in framework/_config.php - if( $_REQUEST['Key'] != EMAIL_BOUNCEHANDLER_KEY) { - echo 'Error: Access validation failed. Invalid "Key" specified.'; - return; - } - - if( !$_REQUEST['Email'] ) { - echo "No email address"; - return; - } - - $this->recordBounce( $_REQUEST['Email'], $_REQUEST['Date'], $_REQUEST['Time'], $_REQUEST['Message'] ); - } - - private function recordBounce( $email, $date = null, $time = null, $error = null ) { - if(preg_match('/<(.*)>/', $email, $parts)) $email = $parts[1]; - - $SQL_email = Convert::raw2sql($email); - $SQL_bounceTime = Convert::raw2sql("$date $time"); - - $duplicateBounce = DataObject::get_one("Email_BounceRecord", - "\"BounceEmail\" = '$SQL_email' AND (\"BounceTime\"+INTERVAL 1 MINUTE) > '$SQL_bounceTime'"); - - if(!$duplicateBounce) { - $record = new Email_BounceRecord(); - - $member = DataObject::get_one( 'Member', "\"Email\"='$SQL_email'" ); - - if( $member ) { - $record->MemberID = $member->ID; - - // If the SilverStripeMessageID (taken from the X-SilverStripeMessageID header embedded in the email) - // is sent, then log this bounce in a Newsletter_SentRecipient record so it will show up on the 'Sent - // Status Report' tab of the Newsletter - if( isset($_REQUEST['SilverStripeMessageID'])) { - // Note: was sent out with: $project . '.' . $messageID; - $message_id_parts = explode('.', $_REQUEST['SilverStripeMessageID']); - // Note: was encoded with: base64_encode( $newsletter->ID . '_' . date( 'd-m-Y H:i:s' ) ); - $newsletter_id_date_parts = explode ('_', base64_decode($message_id_parts[1]) ); - - // Escape just in case - $SQL_memberID = Convert::raw2sql($member->ID); - $SQL_newsletterID = Convert::raw2sql($newsletter_id_date_parts[0]); - - // Log the bounce - $oldNewsletterSentRecipient = DataObject::get_one("Newsletter_SentRecipient", - "\"MemberID\" = '$SQL_memberID' AND \"ParentID\" = '$SQL_newsletterID'" - . " AND \"Email\" = '$SQL_email'"); - - // Update the Newsletter_SentRecipient record if it exists - if($oldNewsletterSentRecipient) { - $oldNewsletterSentRecipient->Result = 'Bounced'; - $oldNewsletterSentRecipient->write(); - } else { - // For some reason it didn't exist, create a new record - $newNewsletterSentRecipient = new Newsletter_SentRecipient(); - $newNewsletterSentRecipient->Email = $SQL_email; - $newNewsletterSentRecipient->MemberID = $member->ID; - $newNewsletterSentRecipient->Result = 'Bounced'; - $newNewsletterSentRecipient->ParentID = $newsletter_id_date_parts[0]; - $newNewsletterSentRecipient->write(); - } - - // Now we are going to Blacklist this member so that email will not be sent to them in the future. - // Note: Sending can be re-enabled by going to 'Mailing List' 'Bounced' tab and unchecking the box - // under 'Blacklisted' - $member->setBlacklistedEmail(TRUE); - echo '

    Member: '.$member->FirstName.' '.$member->Surname - .' <'.$member->Email.'> was added to the Email Blacklist!

    '; - } - } - - if( !$date ) - $date = date( 'd-m-Y' ); - /*else - $date = date( 'd-m-Y', strtotime( $date ) );*/ - - if( !$time ) - $time = date( 'H:i:s' ); - /*else - $time = date( 'H:i:s', strtotime( $time ) );*/ - - $record->BounceEmail = $email; - $record->BounceTime = $date . ' ' . $time; - $record->BounceMessage = $error; - $record->write(); - - echo "Handled bounced email to address: $email"; - } else { - echo 'Sorry, this bounce report has already been logged, not logging this duplicate bounce.'; - } - } - -} - -/** - * Database record for recording a bounced email - * @package framework - * @subpackage email - */ -class Email_BounceRecord extends DataObject { - static $db = array( - 'BounceEmail' => 'Varchar', - 'BounceTime' => 'SS_Datetime', - 'BounceMessage' => 'Varchar' - ); - - static $has_one = array( - 'Member' => 'Member' - ); - - static $has_many = array(); - - static $many_many = array(); - - static $defaults = array(); - - static $singular_name = 'Email Bounce Record'; - - - /** - * a record of Email_BounceRecord can't be created manually. Instead, it should be - * created though system. - */ - public function canCreate($member = null) { - return false; - } -} - - diff --git a/security/Member.php b/security/Member.php index 137a7e2de..5313b9781 100644 --- a/security/Member.php +++ b/security/Member.php @@ -15,7 +15,6 @@ class Member extends DataObject implements TemplateGlobalProvider { 'RememberLoginToken' => 'Varchar(160)', // Note: this currently holds a hash, not a token. 'NumVisit' => 'Int', 'LastVisited' => 'SS_Datetime', - 'Bounced' => 'Boolean', // Note: This does not seem to be used anywhere. 'AutoLoginHash' => 'Varchar(160)', 'AutoLoginExpired' => 'SS_Datetime', // This is an arbitrary code pointing to a PasswordEncryptor instance, @@ -584,7 +583,6 @@ class Member extends DataObject implements TemplateGlobalProvider { $fields->removeByName('RememberLoginToken'); $fields->removeByName('NumVisit'); $fields->removeByName('LastVisited'); - $fields->removeByName('Bounced'); $fields->removeByName('AutoLoginHash'); $fields->removeByName('AutoLoginExpired'); $fields->removeByName('PasswordEncryption'); @@ -1197,7 +1195,6 @@ class Member extends DataObject implements TemplateGlobalProvider { i18n::get_existing_translations() )); - $mainFields->removeByName('Bounced'); $mainFields->removeByName('RememberLoginToken'); $mainFields->removeByName('AutoLoginHash'); $mainFields->removeByName('AutoLoginExpired'); From bdc3e91e018f593c0b5a45736f451b33130fa458 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Dec 2012 09:47:30 +0100 Subject: [PATCH 10/49] Fixed wrong floating on GridField certain buttons Regression from 9152387c92, alternative solution to https://github.com/silverstripe/sapphire/pull/1028 --- css/GridField.css | 4 ++-- scss/GridField.scss | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/css/GridField.css b/css/GridField.css index 5fad514b9..ec1280ebc 100644 --- a/css/GridField.css +++ b/css/GridField.css @@ -41,7 +41,7 @@ .cms table.ss-gridfield-table tbody td.col-buttons { width: auto; padding: 0 8px; text-align: right; white-space: nowrap; } .cms table.ss-gridfield-table tbody td.col-listChildrenLink { width: 16px; border-right: none; text-indent: -9999em; padding: 0; } .cms table.ss-gridfield-table tbody td.col-listChildrenLink .list-children-link { background: transparent url(../images/sitetree_ss_default_icons.png) no-repeat 3px -4px; display: block; } -.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.item { color: #1556b2; } +.cms table.ss-gridfield-table tbody td.col-getTreeTitle span.item { color: #0073c1; } .cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge { clear: both; text-transform: uppercase; display: inline-block; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 10px; margin-right: 6px; margin-top: -1px; -webkit-border-radius: 2px 2px; -moz-border-radius: 2px / 2px; border-radius: 2px / 2px; } .cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.modified { color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; } .cms table.ss-gridfield-table tbody td.col-getTreeTitle span.badge.addedtodraft { color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; } @@ -53,7 +53,7 @@ .cms table.ss-gridfield-table tbody td button.ui-state-active { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } .cms table.ss-gridfield-table tbody td button.gridfield-button-delete { width: 20px; margin: 0; } .cms table.ss-gridfield-table tbody td button.gridfield-button-delete span.btn-icon-decline { left: 2px; } -.cms table.ss-gridfield-table tbody td a.view-link, .cms table.ss-gridfield-table tbody td a.edit-link { display: inline-block; float: left; width: 20px; height: 20px; text-indent: 9999em; overflow: hidden; vertical-align: middle; } +.cms table.ss-gridfield-table tbody td a.view-link, .cms table.ss-gridfield-table tbody td a.edit-link { display: inline-block; width: 20px; height: 20px; text-indent: 9999em; overflow: hidden; vertical-align: middle; } .cms table.ss-gridfield-table tbody td a.view-link { background: url(../admin/images/btn-icon/magnifier.png) no-repeat 0 1px; } .cms table.ss-gridfield-table tbody td a.edit-link { background: url(../admin/images/btn-icon/document--pencil.png) no-repeat 2px 0px; } .cms table.ss-gridfield-table tfoot { color: #323e46; } diff --git a/scss/GridField.scss b/scss/GridField.scss index b23b74576..57cab751d 100644 --- a/scss/GridField.scss +++ b/scss/GridField.scss @@ -252,7 +252,6 @@ $gf_grid_x: 16px; } a.view-link, a.edit-link { display:inline-block; - float: left; width:20px; height:20px; //min height to fit the edit icon text-indent:9999em; From 0ba51c1ebfed2674bed64e5aaea8bf84d3384be1 Mon Sep 17 00:00:00 2001 From: Paul Clarke Date: Thu, 13 Dec 2012 10:09:30 +0100 Subject: [PATCH 11/49] Fix to allow buttons to align inline (fixes #8099) --- admin/css/screen.css | 1 - admin/scss/_style.scss | 1 - css/GridField.css | 13 +++++-------- scss/GridField.scss | 24 ++++++++++++------------ 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/admin/css/screen.css b/admin/css/screen.css index 874636bad..9e562767b 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -669,7 +669,6 @@ form.small .cms-file-info-data .field .middleColumn { margin-left: 120px; } /** -------------------------------------------- Users Members Admin -------------------------------------------- */ .members_grid span button#action_gridfield_relationfind { display: none; } -.members_grid p button#action_export { margin-top: 16px; } .members_grid p button#action_export span.btn-icon-download-csv { height: 17px; } .members_grid p button#action_export .ui-button-text { padding-left: 26px; } diff --git a/admin/scss/_style.scss b/admin/scss/_style.scss index a062121fb..fbdc704b6 100644 --- a/admin/scss/_style.scss +++ b/admin/scss/_style.scss @@ -1575,7 +1575,6 @@ form.small { display:none; //hides find button - redundant functionality } p button#action_export { - margin-top:$grid-y*2; span.btn-icon-download-csv { height:17px; //exact height of icon } diff --git a/css/GridField.css b/css/GridField.css index ec1280ebc..eaeffbdd4 100644 --- a/css/GridField.css +++ b/css/GridField.css @@ -9,13 +9,11 @@ /** ----------------------------------------------- Grid Units (px) We have a vertical rhythm that the grid is based off both x (=horizontal) and y (=vertical). All internal padding and margins are scaled to this and accounting for paragraphs ------------------------------------------------ */ /** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */ .cms .ss-gridfield > div { margin-bottom: 36px; } -.cms .ss-gridfield > div.addNewGridFieldButton { margin-bottom: 12px; } -.cms .ss-gridfield > div.addNewGridFieldButton:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } -*:first-child .cms .ss-gridfield > div.addNewGridFieldButton { zoom: 1; } +.cms .ss-gridfield > div.addNewGridFieldButton { margin-bottom: 0; } +.cms .ss-gridfield > div.addNewGridFieldButton .action { margin-bottom: 12px; } .cms .ss-gridfield[data-selectable] tr.ui-selected, .cms .ss-gridfield[data-selectable] tr.ui-selecting { background: #FFFAD6 !important; } .cms .ss-gridfield[data-selectable] td { cursor: pointer; } .cms .ss-gridfield span button#action_gridfield_relationfind { display: none; } -.cms .ss-gridfield p button#action_export { margin-top: 12px; } .cms .ss-gridfield p button#action_export span.btn-icon-download-csv { height: 17px; } .cms .ss-gridfield p button#action_export .ui-button-text { padding-left: 26px; } .cms .ss-gridfield .right { float: right; } @@ -25,10 +23,9 @@ .cms .ss-gridfield .left > * { margin-right: 5px; float: left; font-size: 14.4px; } .cms .ss-gridfield .grid-levelup { text-indent: -9999em; margin-bottom: 6px; } .cms .ss-gridfield .grid-levelup a.list-parent-link { background: transparent url(../images/gridfield-level-up.png) no-repeat 0 0; display: block; } -.cms .ss-gridfield .add-existing-autocompleter { width: 500px; } -.cms .ss-gridfield .add-existing-autocompleter input.relation-search { width: 380px; } -.cms .ss-gridfield .grid-print-button { display: inline-block; } -.cms .ss-gridfield .grid-csv-button { display: inline-block; } +.cms .ss-gridfield .add-existing-autocompleter span { display: inline-block; } +.cms .ss-gridfield .add-existing-autocompleter input.relation-search { width: 270px; } +.cms .ss-gridfield .grid-csv-button, .cms .ss-gridfield .grid-print-button { margin-bottom: 12px; display: inline-block; } .cms table.ss-gridfield-table { display: table; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; padding: 0; border-collapse: separate; border-bottom: 0 none; width: 100%; margin-bottom: 12px; } .cms table.ss-gridfield-table thead { color: #323e46; background: transparent; } .cms table.ss-gridfield-table thead tr.filter-header .fieldgroup { max-width: 512px; } diff --git a/scss/GridField.scss b/scss/GridField.scss index 57cab751d..7e7770162 100644 --- a/scss/GridField.scss +++ b/scss/GridField.scss @@ -41,8 +41,10 @@ $gf_grid_x: 16px; & > div { margin-bottom: $gf_grid_y*3; &.addNewGridFieldButton{ - margin-bottom:$gf_grid_y; - @include clearfix; + margin-bottom: 0; + .action { + margin-bottom: $gf_grid_y; + } } } @@ -61,7 +63,6 @@ $gf_grid_x: 16px; } p button#action_export { - margin-top:$gf_grid_y; span.btn-icon-download-csv { height:17px; //exact height of icon } @@ -77,8 +78,7 @@ $gf_grid_x: 16px; font-size: $gf_grid_y*1.2; } - .pagination-records-number - { + .pagination-records-number { font-size: 1.0em; padding: 6px 3px 6px 0; color: $color-text-light; @@ -106,15 +106,15 @@ $gf_grid_x: 16px; margin-bottom: 6px; } .add-existing-autocompleter { - input.relation-search { - width: 380px; + span { + display: inline-block; + } + input.relation-search { + width: 270px; } - width: 500px; } - .grid-print-button{ - display: inline-block; - } - .grid-csv-button{ + .grid-csv-button, .grid-print-button { + margin-bottom: $gf_grid_y; display: inline-block; } } From 2369cc4f42c7c2b55ca58cfe917a82aa530700ff Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Dec 2012 10:15:03 +0100 Subject: [PATCH 12/49] Moved group member listing utility buttons after field Stay consistent with main member listing in admin/security, and de-emphasize their importantce. Having the "link existing" closer to the actual table is a much stronger UI coupling. --- security/Group.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/Group.php b/security/Group.php index e70851b87..c4484ac2f 100755 --- a/security/Group.php +++ b/security/Group.php @@ -95,8 +95,8 @@ class Group extends DataObject { if($this->ID) { $group = $this; $config = new GridFieldConfig_RelationEditor(); - $config->addComponents(new GridFieldExportButton('before')); - $config->addComponents(new GridFieldPrintButton('before')); + $config->addComponents(new GridFieldExportButton('after')); + $config->addComponents(new GridFieldPrintButton('after')); $config->getComponentByType('GridFieldAddExistingAutocompleter') ->setResultsFormat('$Title ($Email)')->setSearchFields(array('FirstName', 'Surname', 'Email')); $config->getComponentByType('GridFieldDetailForm') From abf1ee9b52c7d88a3239f8bc050ff023b267efe9 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Dec 2012 10:21:56 +0100 Subject: [PATCH 13/49] Suppress jQuery UI's borders around tabs in the CMS Noticed this on the "Groups" tab in admin/security, but likely a problem elsewhere as well. --- admin/css/screen.css | 2 +- admin/scss/_style.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/admin/css/screen.css b/admin/css/screen.css index 9e562767b..8074e56d1 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -363,7 +363,7 @@ body.cms { overflow: hidden; } /** -------------------------------------------- Tabs -------------------------------------------- */ .ui-tabs { padding: 0; background: none; } .ui-tabs .ui-tabs { position: static; } -.ui-tabs .ui-tabs-panel { padding: 8px 0; background: transparent; } +.ui-tabs .ui-tabs-panel { padding: 8px 0; background: transparent; border: 0; } .ui-tabs .ui-tabs-panel.cms-edit-form { padding: 0; } .ui-tabs .ui-widget-header { border: 0; background: none; } .ui-tabs .ui-tabs-nav { float: right; margin: 0 0 -1px 0; padding: 0 12px 0 0; border-bottom: none; } diff --git a/admin/scss/_style.scss b/admin/scss/_style.scss index fbdc704b6..2219c522b 100644 --- a/admin/scss/_style.scss +++ b/admin/scss/_style.scss @@ -159,6 +159,7 @@ body.cms { .ui-tabs-panel { padding: $grid-x 0; background: transparent; // default it's white + border: 0; // suppress default borders &.cms-edit-form { padding: 0; } From 7dd224d2a43da5249ad947dfeb0304bc3801a3d2 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Dec 2012 10:34:13 +0100 Subject: [PATCH 14/49] Made GridField font size settings less cryptic Instead of applying it to generic alignment classes like "left" and "right", make it clear that those are targeted at all contents of the button row. --- css/GridField.css | 5 +++-- scss/GridField.scss | 10 ++++++---- templates/Includes/GridFieldButtonRow.ss | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/css/GridField.css b/css/GridField.css index eaeffbdd4..45f6ce957 100644 --- a/css/GridField.css +++ b/css/GridField.css @@ -17,10 +17,11 @@ .cms .ss-gridfield p button#action_export span.btn-icon-download-csv { height: 17px; } .cms .ss-gridfield p button#action_export .ui-button-text { padding-left: 26px; } .cms .ss-gridfield .right { float: right; } -.cms .ss-gridfield .right > * { float: right; margin-left: 5px; font-size: 14.4px; } +.cms .ss-gridfield .right > * { float: right; margin-left: 8px; } .cms .ss-gridfield .right .pagination-records-number { font-size: 1.0em; padding: 6px 3px 6px 0; color: white; text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.2); font-weight: normal; } .cms .ss-gridfield .left { float: left; } -.cms .ss-gridfield .left > * { margin-right: 5px; float: left; font-size: 14.4px; } +.cms .ss-gridfield .left > * { margin-right: 8px; float: left; } +.cms .ss-gridfield .ss-gridfield-buttonrow { font-size: 14.4px; } .cms .ss-gridfield .grid-levelup { text-indent: -9999em; margin-bottom: 6px; } .cms .ss-gridfield .grid-levelup a.list-parent-link { background: transparent url(../images/gridfield-level-up.png) no-repeat 0 0; display: block; } .cms .ss-gridfield .add-existing-autocompleter span { display: inline-block; } diff --git a/scss/GridField.scss b/scss/GridField.scss index 7e7770162..72bd01e72 100644 --- a/scss/GridField.scss +++ b/scss/GridField.scss @@ -74,8 +74,7 @@ $gf_grid_x: 16px; float:right; & > * { float: right; - margin-left:5px; - font-size: $gf_grid_y*1.2; + margin-left:$gf_grid_x/2; } .pagination-records-number { @@ -89,11 +88,14 @@ $gf_grid_x: 16px; .left { float:left; & > * { - margin-right:5px; + margin-right:$gf_grid_x/2; float: left; - font-size: $gf_grid_y*1.2; } } + + .ss-gridfield-buttonrow { + font-size: $gf_grid_y*1.2; + } } .ss-gridfield { diff --git a/templates/Includes/GridFieldButtonRow.ss b/templates/Includes/GridFieldButtonRow.ss index 41347c5c6..09f861796 100644 --- a/templates/Includes/GridFieldButtonRow.ss +++ b/templates/Includes/GridFieldButtonRow.ss @@ -1,4 +1,4 @@ -
    +
    $LeftFragment
    $RightFragment
    \ No newline at end of file From 006790bb267f47d1f4ae7d140138f9cf77448698 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Dec 2012 10:50:08 +0100 Subject: [PATCH 15/49] Fixed IE7 GridField "add row" alignment issue Regression from Paul's last commit --- css/GridField.css | 6 +++--- scss/GridField.scss | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/css/GridField.css b/css/GridField.css index 45f6ce957..78f373cbe 100644 --- a/css/GridField.css +++ b/css/GridField.css @@ -24,9 +24,9 @@ .cms .ss-gridfield .ss-gridfield-buttonrow { font-size: 14.4px; } .cms .ss-gridfield .grid-levelup { text-indent: -9999em; margin-bottom: 6px; } .cms .ss-gridfield .grid-levelup a.list-parent-link { background: transparent url(../images/gridfield-level-up.png) no-repeat 0 0; display: block; } -.cms .ss-gridfield .add-existing-autocompleter span { display: inline-block; } -.cms .ss-gridfield .add-existing-autocompleter input.relation-search { width: 270px; } -.cms .ss-gridfield .grid-csv-button, .cms .ss-gridfield .grid-print-button { margin-bottom: 12px; display: inline-block; } +.cms .ss-gridfield .add-existing-autocompleter span { display: -moz-inline-stack; display: inline-block; vertical-align: top; *vertical-align: auto; zoom: 1; *display: inline; } +.cms .ss-gridfield .add-existing-autocompleter input.relation-search { width: 270px; margin-bottom: 12px; } +.cms .ss-gridfield .grid-csv-button, .cms .ss-gridfield .grid-print-button { margin-bottom: 12px; display: -moz-inline-stack; display: inline-block; vertical-align: middle; *vertical-align: auto; zoom: 1; *display: inline; } .cms table.ss-gridfield-table { display: table; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; padding: 0; border-collapse: separate; border-bottom: 0 none; width: 100%; margin-bottom: 12px; } .cms table.ss-gridfield-table thead { color: #323e46; background: transparent; } .cms table.ss-gridfield-table thead tr.filter-header .fieldgroup { max-width: 512px; } diff --git a/scss/GridField.scss b/scss/GridField.scss index 72bd01e72..73423e558 100644 --- a/scss/GridField.scss +++ b/scss/GridField.scss @@ -109,15 +109,16 @@ $gf_grid_x: 16px; } .add-existing-autocompleter { span { - display: inline-block; + @include inline-block(top); } input.relation-search { width: 270px; + margin-bottom: $gf_grid_y; } } .grid-csv-button, .grid-print-button { margin-bottom: $gf_grid_y; - display: inline-block; + @include inline-block(); } } table.ss-gridfield-table { From 236e335a0abfb2141ec9aba2637fc0a19452f65b Mon Sep 17 00:00:00 2001 From: Joel Edwards Date: Thu, 13 Dec 2012 15:01:06 +1300 Subject: [PATCH 16/49] Permission list styling improvements (#8100) --- admin/css/screen.css | 4 +++- admin/scss/_SecurityAdmin.scss | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/admin/css/screen.css b/admin/css/screen.css index 8074e56d1..d9f0f04d2 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -971,7 +971,9 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; } .ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form .resetformaction { margin-right: 0px; } .ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content #Form_ImportForm { overflow: hidden; } -.SecurityAdmin .permissioncheckboxset .optionset li, .SecurityAdmin .permissioncheckboxsetfield_readonly .optionset li { float: none; width: auto; } +.permissioncheckboxset h5, .permissioncheckboxsetfield_readonly h5 { margin: 0; } +.permissioncheckboxset .optionset, .permissioncheckboxsetfield_readonly .optionset { overflow: auto; } +.permissioncheckboxset .optionset li, .permissioncheckboxsetfield_readonly .optionset li { float: none; width: auto; clear: both; } /* For user permissions the readonly checkboxes are set as display none and are replaced with a that has a green tick icon as a background this is created using compass generated classes and hardcoded in the php */ diff --git a/admin/scss/_SecurityAdmin.scss b/admin/scss/_SecurityAdmin.scss index d815717d6..f87e058fb 100644 --- a/admin/scss/_SecurityAdmin.scss +++ b/admin/scss/_SecurityAdmin.scss @@ -1,12 +1,15 @@ -.SecurityAdmin { - // Same rules in .member-profile-form - .permissioncheckboxset, .permissioncheckboxsetfield_readonly { - .optionset { - li { - float: none; - width: auto; - } - } +.permissioncheckboxset, .permissioncheckboxsetfield_readonly { + h5 { + margin: 0; + } + .optionset { + overflow: auto; + + li { + float: none; + width: auto; + clear: both; + } } } From fe08236f2103b2b05ac27f18309de3e69010809b Mon Sep 17 00:00:00 2001 From: Mateusz Uzdowski Date: Thu, 22 Nov 2012 10:01:02 +1300 Subject: [PATCH 17/49] API Add action tabsets as a interface idiom. Introduces the concept of action tabsets - usage of TabSet and Tabs in between the action buttons to allow richer set of capabilities that can be offered to the user. Goes along with c8d0cdec99c95dbed3b58ebcc098cc9d22c58206 that implements a change to the CMS actions. --- admin/css/screen.css | 227 +++++++--- admin/images/btn-icon-s37c6548b54.png | Bin 21728 -> 0 bytes admin/images/btn-icon-s97372285ea.png | Bin 0 -> 22288 bytes admin/images/btn-icon/disk.png | Bin 0 -> 620 bytes admin/images/link_arrows.png | Bin 0 -> 342 bytes admin/images/sprites-32x32-se93fc83bf9.png | Bin 18954 -> 0 bytes admin/images/sprites-32x32-sf6890c994e.png | Bin 0 -> 19229 bytes .../sprites-32x32/arrow_down_darker.png | Bin 0 -> 155 bytes .../sprites-32x32/arrow_down_lighter.png | Bin 0 -> 155 bytes .../images/sprites-32x32/arrow_up_darker.png | Bin 0 -> 156 bytes .../images/sprites-32x32/arrow_up_lighter.png | Bin 0 -> 153 bytes admin/javascript/LeftAndMain.Content.js | 3 +- admin/javascript/LeftAndMain.EditForm.js | 12 +- admin/javascript/LeftAndMain.js | 24 +- admin/javascript/ssui.core.js | 10 +- admin/scss/_actionTabs.scss | 428 ++++++++++++++++++ admin/scss/_forms.scss | 51 ++- admin/scss/_menu.scss | 38 -- admin/scss/_mixins.scss | 72 +++ admin/scss/_style.scss | 23 +- admin/scss/_uitheme.scss | 6 +- admin/scss/_uitheme.scss.orig | 116 ----- admin/scss/screen.scss | 1 + css/AssetUploadField.css | 3 + css/GridField.css | 5 +- css/UploadField.css | 5 +- docs/en/howto/extend-cms-interface.md | 54 ++- javascript/TabSet.js | 114 +++++ scss/GridField.scss | 12 +- scss/UploadField.scss | 2 +- 30 files changed, 926 insertions(+), 280 deletions(-) delete mode 100644 admin/images/btn-icon-s37c6548b54.png create mode 100644 admin/images/btn-icon-s97372285ea.png create mode 100755 admin/images/btn-icon/disk.png create mode 100644 admin/images/link_arrows.png delete mode 100644 admin/images/sprites-32x32-se93fc83bf9.png create mode 100644 admin/images/sprites-32x32-sf6890c994e.png create mode 100644 admin/images/sprites-32x32/arrow_down_darker.png create mode 100644 admin/images/sprites-32x32/arrow_down_lighter.png create mode 100644 admin/images/sprites-32x32/arrow_up_darker.png create mode 100644 admin/images/sprites-32x32/arrow_up_lighter.png create mode 100644 admin/scss/_actionTabs.scss delete mode 100644 admin/scss/_uitheme.scss.orig diff --git a/admin/css/screen.css b/admin/css/screen.css index d9f0f04d2..c992891ee 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -32,9 +32,12 @@ If more variables exist in the future, consider creating a variables file.*/ /** ----------------------------------------------- Grid Units (px) We have a vertical rhythm that the grid is based off both x (=horizontal) and y (=vertical). All internal padding and margins are scaled to this and accounting for paragraphs ------------------------------------------------ */ /** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */ /** ----------------------------- Custom mixins ------------------------------ */ +/*Mixin used to generate slightly smaller text and forms +Used in side panels and action tabs +*/ /** ----------------------------- Sprite images ----------------------------- */ /** Helper SCSS file for generating sprites for the interface. */ -.btn-icon-sprite, .ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept, .ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled, .ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add, .ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia, .ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled, .ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage, .ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled, .ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left, .ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double, .ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back, .ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled, .ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow, .ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation, .ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus, .ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil, .ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus, .ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small, .ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain, .ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain, .ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle, .ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled, .ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross, .ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline, .ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled, .ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete, .ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight, .ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil, .ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv, .ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload, .ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled, .ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print, .ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier, .ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle, .ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled, .ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation, .ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled, .ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud, .ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled, .ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil, .ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled, .ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition, .ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled, .ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview, .ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled, .ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings, .ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled, .ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish, .ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background: url('../images/btn-icon-s37c6548b54.png') no-repeat; } +.btn-icon-sprite, .ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept, .ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled, .ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add, .ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia, .ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled, .ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage, .ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled, .ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left, .ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double, .ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back, .ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled, .ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow, .ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation, .ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus, .ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil, .ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus, .ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small, .ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain, .ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain, .ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle, .ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled, .ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross, .ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline, .ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled, .ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete, .ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight, .ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk, .ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil, .ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv, .ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload, .ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled, .ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print, .ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier, .ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle, .ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled, .ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation, .ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled, .ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud, .ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled, .ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil, .ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled, .ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition, .ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled, .ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview, .ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled, .ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings, .ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled, .ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish, .ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background: url('../images/btn-icon-s97372285ea.png') no-repeat; } .ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept { background-position: 0 -96px; } .ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled { background-position: 0 -80px; } @@ -47,14 +50,14 @@ If more variables exist in the future, consider creating a variables file.*/ .ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double { background-position: 0 -324px; } .ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back { background-position: 0 -356px; } .ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled { background-position: 0 -16px; } -.ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow { background-position: 0 -708px; } +.ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow { background-position: 0 -724px; } .ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation { background-position: 0 -500px; } -.ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus { background-position: 0 -724px; } +.ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus { background-position: 0 -740px; } .ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil { background-position: 0 -660px; } -.ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus { background-position: 0 -692px; } -.ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small { background-position: 0 -756px; } +.ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus { background-position: 0 -708px; } +.ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small { background-position: 0 -772px; } .ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain { background-position: 0 -468px; } -.ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain { background-position: 0 -740px; } +.ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain { background-position: 0 -756px; } .ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle { background-position: 0 -436px; } .ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled { background-position: 0 -548px; } .ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross { background-position: 0 -276px; } @@ -62,6 +65,7 @@ If more variables exist in the future, consider creating a variables file.*/ .ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled { background-position: 0 -192px; } .ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete { background-position: 0 -452px; } .ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight { background-position: 0 -291px; } +.ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk { background-position: 0 -676px; } .ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil { background-position: 0 -532px; } .ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv { background-position: 0 -48px; } .ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload { background-position: 0 -404px; } @@ -73,7 +77,7 @@ If more variables exist in the future, consider creating a variables file.*/ .ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation { background-position: 0 -372px; } .ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled { background-position: 0 -420px; } .ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud { background-position: 0 -596px; } -.ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled { background-position: 0 -676px; } +.ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled { background-position: 0 -692px; } .ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil { background-position: 0 -228px; } .ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled { background-position: 0 -580px; } .ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition { background-position: 0 -244px; } @@ -125,8 +129,8 @@ body, html { font-size: 12px; line-height: 16px; font-family: Arial, sans-serif; .ui-widget-header .ui-dialog-title { padding: 6px 0; text-shadow: #ced7dc 1px 1px 0; } .ui-widget-header a.ui-dialog-titlebar-close { position: absolute; top: -8px; right: -15px; width: 30px; height: 30px; z-index: 100000; } .ui-widget-header a.ui-state-hover { border-color: transparent; background: transparent; } -.ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -216px no-repeat; } -.ui-widget-header .ui-icon-closethick { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -318px no-repeat; width: 30px; height: 30px; } +.ui-widget-header a.ui-state-hover .ui-icon-closethick { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -216px no-repeat; } +.ui-widget-header .ui-icon-closethick { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -318px no-repeat; width: 30px; height: 30px; } .ui-state-hover { cursor: pointer; } @@ -192,21 +196,27 @@ form.small .field input.text, form.small .field textarea, form.small .field sele .field.remove-splitter { border-bottom: none; box-shadow: none; } /** ---------------------------------------------------- Buttons ---------------------------------------------------- */ +.cms .button-no-style button, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button { background: none; border: none; display: block; margin: 0; outline: none; color: #0073c1; font-weight: normal; width: 210px; /* same as width of surrounding panel */ text-align: left; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; text-shadow: none; margin-left: -10px; } +.cms .button-no-style button.ss-ui-action-destructive, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-action-destructive { color: #c22730; } +.cms .button-no-style button span, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button span { padding-left: 0; padding-right: 0; } +.cms .button-no-style button:hover, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:hover, .cms .button-no-style button:focus, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:focus, .cms .button-no-style button:active, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:active { outline: none; background: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; border: none; } .cms .Actions > *, .cms .cms-actions-row > * { display: block; float: left; margin-right: 8px; } .cms .Actions > *:last-child, .cms .cms-actions-row > *:last-child { margin-right: 0; } -.cms .Actions { min-height: 30px; overflow: visible; padding: 8px 12px; } +.cms .Actions { min-height: 30px; overflow: auto; padding: 8px 12px; } .cms .south .Actions, .cms .ui-tabs-panel .Actions, .cms .ui-tabs-panel iframe .Actions { padding: 0; } .cms input.loading, .cms button.loading, .cms input.ui-state-default.loading, .cms .ui-widget-content input.ui-state-default.loading, .cms .ui-widget-header input.ui-state-default.loading { color: #525252; border-color: #d5d3d3; cursor: default; } .cms input.loading .ui-icon, .cms button.loading .ui-icon, .cms input.ui-state-default.loading .ui-icon, .cms .ui-widget-content input.ui-state-default.loading .ui-icon, .cms .ui-widget-header input.ui-state-default.loading .ui-icon { background: transparent url(../../images/network-save.gif) no-repeat 0 0; } .cms input.loading.ss-ui-action-constructive .ui-icon, .cms button.loading.ss-ui-action-constructive .ui-icon { background: transparent url(../../images/network-save-constructive.gif) no-repeat 0 0; } -.cms .ss-ui-button { margin-top: 0px; font-weight: bold; text-decoration: none; line-height: 16px; color: #393939; border: 1px solid #c0c0c2; border-bottom: 1px solid #a6a6a9; cursor: pointer; background-color: #e6e6e6; white-space: nowrap; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #d9d9d9)); background: -webkit-linear-gradient(#ffffff, #d9d9d9); background: -moz-linear-gradient(#ffffff, #d9d9d9); background: -o-linear-gradient(#ffffff, #d9d9d9); background: linear-gradient(#ffffff, #d9d9d9); text-shadow: white 0 1px 1px; /* constructive */ /* destructive */ } +.cms .ss-ui-button { font-size: 12px; margin-top: 0px; padding: 5px 10px; font-weight: bold; text-decoration: none; line-height: 16px; color: #393939; border: 1px solid #c0c0c2; border-bottom: 1px solid #a6a6a9; cursor: pointer; background-color: #e6e6e6; white-space: nowrap; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #d9d9d9)); background: -webkit-linear-gradient(#ffffff, #d9d9d9); background: -moz-linear-gradient(#ffffff, #d9d9d9); background: -o-linear-gradient(#ffffff, #d9d9d9); background: linear-gradient(#ffffff, #d9d9d9); text-shadow: white 0 1px 1px; /* constructive */ /* destructive */ } +.cms .ss-ui-button .ui-icon, .cms .ss-ui-button .ui-button-text { display: inline-block; line-height: 16px; padding: 0; } +.cms .ss-ui-button .ui-icon { width: 16px; padding: 0 2px; position: relative; left: -2px; margin-top: 0; top: 0; height: 16px; float: left; } .cms .ss-ui-button.ui-state-hover, .cms .ss-ui-button:hover { text-decoration: none; background-color: white; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2U2ZTZlNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #e6e6e6)); background: -webkit-linear-gradient(#ffffff, #e6e6e6); background: -moz-linear-gradient(#ffffff, #e6e6e6); background: -o-linear-gradient(#ffffff, #e6e6e6); background: linear-gradient(#ffffff, #e6e6e6); -webkit-box-shadow: 0 0 5px #b3b3b3; -moz-box-shadow: 0 0 5px #b3b3b3; box-shadow: 0 0 5px #b3b3b3; } .cms .ss-ui-button:active, .cms .ss-ui-button:focus, .cms .ss-ui-button.ui-state-active, .cms .ss-ui-button.ui-state-focus { border: 1px solid #b3b3b3; background-color: white; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2U2ZTZlNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #e6e6e6)); background: -webkit-linear-gradient(#ffffff, #e6e6e6); background: -moz-linear-gradient(#ffffff, #e6e6e6); background: -o-linear-gradient(#ffffff, #e6e6e6); background: linear-gradient(#ffffff, #e6e6e6); -webkit-box-shadow: 0 0 5px #b3b3b3 inset; -moz-box-shadow: 0 0 5px #b3b3b3 inset; box-shadow: 0 0 5px #b3b3b3 inset; } .cms .ss-ui-button.ss-ui-action-constructive { text-shadow: none; font-weight: bold; color: white; border-color: #1f9433; border-bottom-color: #166a24; background-color: #1f9433; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzkzYmU0MiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzFmOTQzMyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #93be42), color-stop(100%, #1f9433)); background: -webkit-linear-gradient(#93be42, #1f9433); background: -moz-linear-gradient(#93be42, #1f9433); background: -o-linear-gradient(#93be42, #1f9433); background: linear-gradient(#93be42, #1f9433); text-shadow: #1c872f 0 -1px -1px; } .cms .ss-ui-button.ss-ui-action-constructive.ui-state-hover, .cms .ss-ui-button.ss-ui-action-constructive:hover { border-color: #166a24; background-color: #1f9433; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2E0Y2EzYSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzIzYTkzYSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #a4ca3a), color-stop(100%, #23a93a)); background: -webkit-linear-gradient(#a4ca3a, #23a93a); background: -moz-linear-gradient(#a4ca3a, #23a93a); background: -o-linear-gradient(#a4ca3a, #23a93a); background: linear-gradient(#a4ca3a, #23a93a); } .cms .ss-ui-button.ss-ui-action-constructive:active, .cms .ss-ui-button.ss-ui-action-constructive:focus, .cms .ss-ui-button.ss-ui-action-constructive.ui-state-active, .cms .ss-ui-button.ss-ui-action-constructive.ui-state-focus { background-color: #1d8c30; -webkit-box-shadow: inset 0 1px 3px #17181a, 0 1px 0 rgba(255, 255, 255, 0.6); -moz-box-shadow: inset 0 1px 3px #17181a, 0 1px 0 rgba(255, 255, 255, 0.6); box-shadow: inset 0 1px 3px #17181a, 0 1px 0 rgba(255, 255, 255, 0.6); } .cms .ss-ui-button.ss-ui-action-destructive { color: red; background-color: #e6e6e6; } -.cms .ss-ui-button.ss-ui-button-small .ui-button-text { padding: 2px 2px; font-size: 10px; } +.cms .ss-ui-button.ss-ui-button-small .ui-button-text { font-size: 10px; } .cms .ss-ui-button.ui-state-highlight { background-color: #e6e6e6; border: 1px solid #708284; } .cms .ss-ui-button.ss-ui-action-minor { background: none; border: 0; color: #393939; text-decoration: underline; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } .cms .ss-ui-button.ss-ui-action-minor:hover { text-decoration: none; color: #1f1f1f; } @@ -338,9 +348,7 @@ body.cms { overflow: hidden; } .cms strong { font-weight: bold; } /** -------------------------------------------- Helpers -------------------------------------------- */ -.cms-helper-hide-actions .Actions { display: none; } - -.hide { display: none; } +.hide, .cms-helper-hide-actions .Actions { display: none; } /** -------------------------------------------- Panels Styles -------------------------------------------- */ .cms-container { height: 100%; /*background: $tab-panel-texture-background;*/ background: #eceff1; } @@ -388,6 +396,7 @@ body.cms { overflow: hidden; } .ui-tabs .ui-tabs-nav li.cms-tabset-icon.gallery.ui-state-active a { background: url('../images/sprites-64x64-s88957ee578.png') 0 -54px no-repeat; } .ui-tabs .ui-tabs-nav li.cms-tabset-icon.edit.ui-state-active a { background: url('../images/sprites-64x64-s88957ee578.png') 0 -404px no-repeat; } .ui-tabs .ui-tabs-nav li.cms-tabset-icon.search.ui-state-active a { background: url('../images/sprites-64x64-s88957ee578.png') 0 -104px no-repeat; } +.ui-tabs .cms-edit-form, .ui-tabs .cms-content-fields { /*not sure if .cms-content-fields effects other areas*/ } .ui-tabs .cms-edit-form .cms-panel-padded, .ui-tabs .cms-content-fields .cms-panel-padded { /* Has padded area inside it */ padding: 0; margin: 0; } .ui-tabs .cms-edit-form .ui-tabs-panel, .ui-tabs .cms-edit-form .ss-gridfield, .ui-tabs .cms-content-fields .ui-tabs-panel, .ui-tabs .cms-content-fields .ss-gridfield { margin: 12px; padding: 0 0 12px; } .ui-tabs .cms-edit-form .ui-tabs-panel .ss-gridfield, .ui-tabs .cms-edit-form .ss-gridfield .ss-gridfield, .ui-tabs .cms-content-fields .ui-tabs-panel .ss-gridfield, .ui-tabs .cms-content-fields .ss-gridfield .ss-gridfield { /* Files area & inside second level tabs */ padding: 0; /* should be zero ideally */ margin: 0 0 12px; } @@ -466,7 +475,7 @@ p.message { margin-bottom: 12px; } #PageType ul li .description { font-style: italic; } /** -------------------------------------------- Content toolbar -------------------------------------------- */ -.cms-content-toolbar { min-height: 29px; display: block; margin: 0 0 15px 0; border-bottom: 1px solid #d0d3d5; -webkit-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); -moz-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); -o-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); *zoom: 1; /* smaller treedropdown */ } +.cms-content-toolbar { min-height: 29px; display: block; margin: 0 0 15px 0; padding-bottom: 9px; border-bottom: 1px solid #d0d3d5; -webkit-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); -moz-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); -o-box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); box-shadow: 0 1px 0 rgba(248, 248, 248, 0.9); *zoom: 1; /* smaller treedropdown */ } .cms-content-toolbar:after { content: "\0020"; display: block; height: 0; clear: both; overflow: hidden; visibility: hidden; } .cms-content-toolbar .cms-tree-view-modes { float: right; padding-top: 5px; } .cms-content-toolbar .cms-tree-view-modes * { display: inline-block; } @@ -649,7 +658,7 @@ body.cms-dialog { overflow: auto; background: url("../images/textures/bg_cms_mai /** -------------------------------------------- Step labels -------------------------------------------- */ .step-label > * { display: inline-block; vertical-align: top; } .step-label .flyout { height: 18px; font-size: 14px; font-weight: bold; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; background-color: #667980; padding: 4px 3px 4px 6px; text-align: center; text-shadow: none; color: #fff; } -.step-label .arrow { height: 26px; width: 10px; background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -256px no-repeat; margin-right: 4px; } +.step-label .arrow { height: 26px; width: 10px; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -256px no-repeat; margin-right: 4px; } .step-label .title { height: 18px; padding: 4px; } /** -------------------------------------------- Item Edit Form -------------------------------------------- */ @@ -670,7 +679,6 @@ form.small .cms-file-info-data .field .middleColumn { margin-left: 120px; } /** -------------------------------------------- Users Members Admin -------------------------------------------- */ .members_grid span button#action_gridfield_relationfind { display: none; } .members_grid p button#action_export span.btn-icon-download-csv { height: 17px; } -.members_grid p button#action_export .ui-button-text { padding-left: 26px; } /** Import forms */ form.import-form ul { list-style: disc; } @@ -694,10 +702,10 @@ form.import-form label.left { width: 250px; } /** -------------------------------------------- Buttons for FileUpload -------------------------------------------- */ .ss-uploadfield-item-edit-all .ui-button-text { padding-right: 0; } -.toggle-details-icon { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -433px no-repeat; } -.ss-uploadfield-item-edit-all .toggle-details-icon { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -375px no-repeat; display: inline-block; width: 8px; height: 8px; padding-left: 5px; } -.toggle-details-icon.opened { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -1121px no-repeat; } -.ss-uploadfield-item-edit-all .toggle-details-icon.opened { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -359px no-repeat; } +.toggle-details-icon { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -433px no-repeat; } +.ss-uploadfield-item-edit-all .toggle-details-icon { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -375px no-repeat; display: inline-block; width: 8px; height: 8px; padding-left: 5px; } +.toggle-details-icon.opened { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1121px no-repeat; } +.ss-uploadfield-item-edit-all .toggle-details-icon.opened { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -359px no-repeat; } /** -------------------------------------------- Hide preview toggle link by default. May be shown in IE7 stylesheet and forced to show with js if needed -------------------------------------------- */ .cms .Actions > .cms-preview-toggle-link, .cms .cms-navigator > .cms-preview-toggle-link { display: none; } @@ -812,7 +820,7 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; } .cms-logo span { font-weight: bold; font-size: 12px; line-height: 16px; padding: 2px 0; margin-left: 30px; } .cms-login-status { border-top: 1px solid #19435c; padding: 12px 0 17px; line-height: 16px; font-size: 11px; } -.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 5px; background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -292px no-repeat; text-indent: -9999em; } +.cms-login-status .logout-link { display: inline-block; height: 16px; width: 16px; float: left; margin: 0 8px 0 5px; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -292px no-repeat; text-indent: -9999em; } .cms-menu { z-index: 80; background: #b0bec7; width: 160px; -webkit-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; -moz-box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; box-shadow: rgba(0, 0, 0, 0.9) 0 0 3px; } .cms-menu a { text-decoration: none; } @@ -836,12 +844,12 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; } .cms-menu-list li a .icon { display: inline-block; float: left; margin: 4px 10px 0 4px; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); opacity: 0.7; } .cms-menu-list li a .text { display: inline-block; float: left; } .cms-menu-list li a .toggle-children { display: inline-block; float: right; width: 20px; height: 100%; cursor: pointer; } -.cms-menu-list li a .toggle-children .toggle-children-icon { display: inline-block; width: 8px; height: 8px; background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -375px no-repeat; vertical-align: middle; } -.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -359px no-repeat; } +.cms-menu-list li a .toggle-children .toggle-children-icon { display: inline-block; width: 8px; height: 8px; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -375px no-repeat; vertical-align: middle; } +.cms-menu-list li a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -359px no-repeat; } .cms-menu-list li ul li a { border-top: 1px solid #b6c3cb; } .cms-menu-list li.current a { color: white; text-shadow: #1e5270 0 -1px 0; border-top: 1px solid #55a4d2; border-bottom: 1px solid #236184; background-color: #338dc1; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzMzOGRjMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzI4NzA5OSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #338dc1), color-stop(100%, #287099)); background-image: -webkit-linear-gradient(#338dc1, #287099); background-image: -moz-linear-gradient(#338dc1, #287099); background-image: -o-linear-gradient(#338dc1, #287099); background-image: linear-gradient(#338dc1, #287099); } -.cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -433px no-repeat; } -.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -1121px no-repeat; } +.cms-menu-list li.current a .toggle-children .toggle-children-icon { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -433px no-repeat; } +.cms-menu-list li.current a .toggle-children.opened .toggle-children-icon { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1121px no-repeat; } .cms-menu-list li.current ul { border-top: none; display: block; } .cms-menu-list li.current li { background-color: #287099; } .cms-menu-list li.current li a { font-size: 11px; padding: 0 10px 0 40px; height: 32px; line-height: 32px; color: #e2f0f7; background: none; border-top: 1px solid #2f81b1; border-bottom: 1px solid #1e5270; } @@ -850,44 +858,7 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; } .cms-menu-list li.current li.current { background: #2e7ead; border-top: 1px solid #2e7ead; border-top: none; } .cms-menu-list li.current li.current a { font-weight: bold; color: white; } .cms-menu-list li.current li.first a { border-top: none; } -.cms-menu-list li ul.collapse { display: none; /* // To specific - was overriding collapsed-flyout styles -#Menu-CMSPagesController { - a { - background-image:none; - font-size: 11px; - padding: 0 10px 0 40px; - height: 32px; - line-height: 32px; - } - } - #Menu-CMSPageAddController { - a { - background-image:none; - font-size: 11px; - padding: 0 10px 0 40px; - height: 32px; - line-height: 32px; - } - } - #Menu-AssetAdmin { - a { - background-image:none; - font-size: 11px; - padding: 0 10px 0 40px; - height: 32px; - line-height: 32px; - } - } - #Menu-CMSFileAddController { - a { - background-image:none; - font-size: 11px; - padding: 0 10px 0 40px; - height: 32px; - line-height: 32px; - } - } -*/ } +.cms-menu-list li ul.collapse { display: none; } .cms-menu-list li ul.collapse li a { background-image: none; font-size: 11px; padding: 0 10px 0 40px; height: 32px; line-height: 32px; } .cms-menu-list li ul.collapsed-flyout { display: block; } .cms-menu-list li ul.collapsed-flyout li a { font-size: 11px; padding: 0 10px 0 16px; height: 32px; line-height: 32px; } @@ -901,14 +872,14 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; } .cms-content-controls.cms-preview-controls { z-index: 1; background: #eceff1; height: 30px; /* should be set in js Layout to match page actions */ padding: 12px 12px; } .cms-content-controls .icon-view, .cms-content-controls .preview-selector.dropdown a.chzn-single { white-space: nowrap; } .cms-content-controls .icon-view:before, .cms-content-controls .preview-selector.dropdown a.chzn-single:before { display: inline-block; float: left; content: ''; width: 23px; height: 17px; overflow: hidden; } -.cms-content-controls .icon-auto:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -108px no-repeat; } -.cms-content-controls .icon-desktop:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -81px no-repeat; } -.cms-content-controls .icon-tablet:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -162px no-repeat; } -.cms-content-controls .icon-mobile:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -189px no-repeat; } -.cms-content-controls .icon-split:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -135px no-repeat; } -.cms-content-controls .icon-edit:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -27px no-repeat; } -.cms-content-controls .icon-preview:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 0 no-repeat; } -.cms-content-controls .icon-window:before { background: url('../images/sprites-32x32-se93fc83bf9.png') 0 -54px no-repeat; } +.cms-content-controls .icon-auto:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -108px no-repeat; } +.cms-content-controls .icon-desktop:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -81px no-repeat; } +.cms-content-controls .icon-tablet:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -162px no-repeat; } +.cms-content-controls .icon-mobile:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -189px no-repeat; } +.cms-content-controls .icon-split:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -135px no-repeat; } +.cms-content-controls .icon-edit:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -27px no-repeat; } +.cms-content-controls .icon-preview:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 0 no-repeat; } +.cms-content-controls .icon-window:before { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -54px no-repeat; } .cms-content-controls .cms-navigator { width: 100%; } .cms-content-controls .preview-selector.dropdown a.chzn-single { text-indent: -200px; } .cms-content-controls .preview-selector { float: right; border-bottom: none; position: relative; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; margin: 3px 0 0 4px; padding: 0; height: 28px; } @@ -966,6 +937,116 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; } .cms-preview.tabletLandscape .preview-scroll .preview-device-outer .preview-device-inner { -webkit-transition: all 0.3s ease-out; -webkit-transition-delay: 1s; -moz-transition: all 0.3s ease-out 1s; -o-transition: all 0.3s ease-out 1s; transition: all 0.3s ease-out 1s; width: 1039px; } .cms-preview.desktop .preview-scroll .preview-device-outer { -webkit-transition: all 0.3s ease-out; -webkit-transition-delay: 1s; -moz-transition: all 0.3s ease-out 1s; -o-transition: all 0.3s ease-out 1s; transition: all 0.3s ease-out 1s; height: 800px; margin: 0 auto; width: 1024px; } +/******************************************** + +Defines the styles for the action tabset, found on the site tree, +and as a single (more options) tab in page view. This is a special +use case of tabs, so the default tab styling should not apply + + +**********************************************/ +.cms { /********************** +Styles for pop-up tabs in bottom panel +************************/ /* Styles for the cms-actions in tree view, to use more limited space. +Title hidden in tree view, until hover/active state added. Active is applied +to the first tab within the template, so there should always be one title +visible. Added and removed with js in TabSet.js */ } +.cms .ss-ui-action-tabset { position: relative; float: left; /*Style the "tabs" navigation for multiple tabs*/ /* Style the tab panels */ } +.cms .ss-ui-action-tabset ul.ui-tabs-nav { overflow: hidden; *zoom: 1; padding: 0; overflow: visible; float: left; height: 28px; border: 1px solid #b3b3b3; -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav:focus, .cms .ss-ui-action-tabset ul.ui-tabs-nav:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li { width: 110px; overflow: visible; background: #eaeaea; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y4ZjhmOCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f8f8f8), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -moz-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -o-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: linear-gradient(top, #f8f8f8, #d9d9d9); border-radius: none; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; border: none; border-right: 1px solid #eee; border-left: 1px solid #b3b3b3; margin: 0; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li:focus, .cms .ss-ui-action-tabset ul.ui-tabs-nav li:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active { background: #f8f8f8; border-bottom: none !important; -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a:focus, .cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a span:focus, .cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a:active, .cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a span:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li.first { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; border-left: none; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li.last { -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; border-right: none; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li a.tab-nav-link { color: #444444; font-weight: bold; line-height: 16px; display: inline-block; padding: 5px 10px; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li a.tab-nav-link .ui-no-icon { display: inline-block; float: left; padding: 0 2px; width: 16px; height: 16px; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li a.tab-nav-link .title { display: inline-block; line-height: 18px; } +.cms .ss-ui-action-tabset ul.ui-tabs-nav li a.tab-nav-link.view-mode-batchactions-wrapper .title { margin-left: 22px; } +.cms .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav, .cms .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.first { -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; } +.cms .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.last { -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; } +.cms .ss-ui-action-tabset .batch-check, .cms .ss-ui-action-tabset .ui-icon { /* position a checkbox & icon within a tab */ display: inline-block; float: left; margin-left: -2px; padding-right: 6px; } +.cms .ss-ui-action-tabset .batch-check { margin: 6px 0px 5px 9px; position: absolute; } +.cms .ss-ui-action-tabset.single ul.ui-tabs-nav { background: none; border: none; display: inline; padding: 0; float: left; } +.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li { display: inline; background: none; border: none; padding: 0; border-bottom: none !important; } +.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li:hover, .cms .ss-ui-action-tabset.single ul.ui-tabs-nav li:focus, .cms .ss-ui-action-tabset.single ul.ui-tabs-nav li:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } +.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a { color: #0073c1; text-shadow: white 0 1px 1px; padding: 0 0 0 10px; line-height: 24px; } +.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a:hover, .cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a:focus, .cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } +.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a:hover { text-shadow: white 0 10px 10px; color: #005b98; } +.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a:hover:after { border-bottom: 4px solid #005b98; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel { display: block; clear: both; background: #f8f8f8 !important; position: absolute; top: 30px; border: 1px solid #b3b3b3; border-top: none; width: 202px; z-index: 1; padding: 10px; padding-top: 15px; margin: 0; float: left; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h3, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h4, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h3 { font-size: 13px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .ui-widget-content { background: none; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field { /* Fields are more compressed in the sidebar compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field label { float: none; width: auto; font-size: 11px; padding: 0 8px 4px 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field .middleColumn { margin: 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field input.text, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field select, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field.checkbox { padding: 0 8px 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field.checkbox input { margin: 2px 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field label { font-size: 12px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-content-fields { overflow: visible; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .chzn-container-single { width: 100% !important; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-content-actions, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field { border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-edit-form { width: 100%; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .parent-mode { padding-top: 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field { margin: 10px 0 0 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-title, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-title { position: absolute; z-index: 2; padding: 5px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel #PageType ul { padding: 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel #PageType ul li { padding: 4px 5px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel.first { left: 0; width: 203px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .ui-icon { padding-right: 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .tab-nav-link, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .ss-ui-button { font-size: 12px; } +.cms .ss-ui-action-tabset .last .ss-ui-action-tab { right: -1px; left: auto; } +.cms .south .Actions { overflow: visible; } +.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav { margin: 0; } +.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor { font-weight: normal; font-size: 13px; line-height: 24px; padding-right: 25px; } +.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1189px no-repeat; width: 16px; height: 16px; content: ""; display: inline-block; margin-left: 6px; border-bottom: 0; } +.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:hover:after { border-bottom: 0; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; } +.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; } +.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel { overflow: hidden; *zoom: 1; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; background-color: #eceff1; border: 1px solid #ccc; border-bottom: 1px solid #eceff1; clear: both; display: block; position: absolute; top: -204px; width: 190px; /* same width as buttons within panel */ z-index: 1; padding: 10px; margin: 0; margin-top: 1px; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h3, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h4, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h3 { font-size: 13px; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .ui-widget-content { background: none; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field { /* Fields are more compressed in the sidebar compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field label { float: none; width: auto; font-size: 11px; padding: 0 8px 4px 0; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field .middleColumn { margin: 0; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field input.text, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field select, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field.checkbox { padding: 0 8px 0; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field.checkbox input { margin: 2px 0; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:hover, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:focus, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:active { /*text-decoration:underline;*/ background-color: #e0e5e8; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information { border-bottom: 1px solid #d0d3d5; margin-bottom: 8px; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information p.meta-info { color: #999; font-size: 11px; line-height: 16px; margin-bottom: 8px; } +.cms .south .Actions .rise-up.ss-ui-action-tabset .last .ui-tabs-panel.ss-ui-action-tab { right: -1px; left: auto; } +.cms .cms-tree-view-sidebar { min-width: 176px; /* for when the scrollbar is present & find dropdown open */ } +.cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi ul.ui-tabs-nav > li { width: auto; } +.cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi ul.ui-tabs-nav > li a.tab-nav-link { width: 30px; overflow: hidden; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-right: 0; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; } +.cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi ul.ui-tabs-nav > li a.tab-nav-link.active { width: 110px; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; } +.cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open ul.ui-tabs-nav, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open ul.ui-tabs-nav li.first, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open ul.ui-tabs-nav li.last, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open-last ul.ui-tabs-nav, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open-last ul.ui-tabs-nav li.first, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open-last ul.ui-tabs-nav li.last { -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; } +.cms .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab { width: 162px; padding: 10px 6px; } +.cms .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab .field { max-width: 160px; } +.cms .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab .ui-icon { padding-right: 0; } +.cms .cms-tree-view-sidebar .last .ui-tabs-panel.ss-ui-action-tab { right: 0; left: auto; } + .ModelAdmin .cms-content-fields { overflow: hidden; } .ModelAdmin .cms-content-fields .cms-edit-form { overflow-y: auto; overflow-x: hidden; } .ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form .resetformaction { margin-right: 0px; } diff --git a/admin/images/btn-icon-s37c6548b54.png b/admin/images/btn-icon-s37c6548b54.png deleted file mode 100644 index 3b2195bd8f4343326a334f5d15451ac6b7418822..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21728 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�Gq11u$aMl_5 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 Hu0mjfS*mtZ diff --git a/admin/images/btn-icon-s97372285ea.png b/admin/images/btn-icon-s97372285ea.png new file mode 100644 index 0000000000000000000000000000000000000000..217f085790f8f2b894f93fa00824fa6f5b555e2c GIT binary patch literal 22288 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 literal 0 HcmV?d00001 diff --git a/admin/images/btn-icon/disk.png b/admin/images/btn-icon/disk.png new file mode 100755 index 0000000000000000000000000000000000000000..99d532e8b1750115952f97302a92d713c0486f97 GIT binary patch literal 620 zcmV-y0+aoTP)~H+MJzd|s z^YP1Hc07G_>)Lgir!F1{Qn4GcTg%?koHo<=1qRN{}nPDolOeI^o4N5I>! zU$N=L=sg~ zDx#dOA*B0N~cqPsWI(^rbbkh)DS0_H_UN0C4l_kvWIm2#Kyy6%BCh z(yIUf003&1xdx>t$*eR2ZvXxT0001Z_R$y3Iju92q*wg58};}zm(OaAH=p|y0002M zh5O5#fxp|~jc?yi@+7$`d4Q6Hl%z;WiWG??NXR{Hx%)pMd~SE0000OQI literal 0 HcmV?d00001 diff --git a/admin/images/link_arrows.png b/admin/images/link_arrows.png new file mode 100644 index 0000000000000000000000000000000000000000..d1cc76c6465888a7735367fb1a35e4b12151cec7 GIT binary patch literal 342 zcmeAS@N?(olHy`uVBq!ia0vp^Ahsh18<2dl)>H^cNtU=qlmzFem6RtIr7}3CJ#*cM#zc`GrcI2>CMT~8 zd!-#wi4ovvNo{pf*w^43sOY@@FPqyInF{$1)(IlI3|d|;Pb6ECm^1uD7;az4&hN0@ z{q0tKgVke34d>otUo{#UUPv-I^aN@fBx^LOZ02CIm3M199nB{YuI+OEKoE$C({>Ow zR?<`o0CFVOin=kL&1UuZr?4sP!2ANw6{6G96s~ejkm-oj);SB-;II}b_5dUi5U0(+ bBEi7K9`JDLviDbjVZh+&>gTe~DWM4f`Dk^v literal 0 HcmV?d00001 diff --git a/admin/images/sprites-32x32-se93fc83bf9.png b/admin/images/sprites-32x32-se93fc83bf9.png deleted file mode 100644 index a27e405a99c1cd7738965e63e553e09ac8005805..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18954 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 diff --git a/admin/images/sprites-32x32-sf6890c994e.png b/admin/images/sprites-32x32-sf6890c994e.png new file mode 100644 index 0000000000000000000000000000000000000000..0c02eb066f109cd876eef84a0d898ced27adef15 GIT binary patch literal 19229 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 literal 0 HcmV?d00001 diff --git a/admin/images/sprites-32x32/arrow_down_darker.png b/admin/images/sprites-32x32/arrow_down_darker.png new file mode 100644 index 0000000000000000000000000000000000000000..e4ca34c7f6b68741528952dd8dfbed66a5d3cbaf GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XY&~5ZLn>}1B}lL?ZfI;2bXKW4R--Vp!TB$%LRm`0tdES_ z8F%m=ywG!-!N+++*0FqsLjue(v)mb37+542p2!KaE}1B}lL?ZfI;2bYAk~L5;%92Is%53S}uTw*GIp z&2XnJA>&vs!!pGP)5qovi5-l3XBRUtF);Bk{HYUWUA9Lu5@-;Er>mdKI;Vst0Cxs4 AKmY&$ literal 0 HcmV?d00001 diff --git a/admin/images/sprites-32x32/arrow_up_darker.png b/admin/images/sprites-32x32/arrow_up_darker.png new file mode 100644 index 0000000000000000000000000000000000000000..9de2abfa716c4128bd595afeeb29af278a31ccea GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X>^xl@Ln>}1B}lL;Gr61>IG}#k#o=>}!cO`5iajhQ>JF9S z4L2qxW*%dI#N%-FOv8BrHziYJfufThYz&NS4B~dev0iSr$AKm>c)I$ztaD0e0szkQ BEpY$< literal 0 HcmV?d00001 diff --git a/admin/images/sprites-32x32/arrow_up_lighter.png b/admin/images/sprites-32x32/arrow_up_lighter.png new file mode 100644 index 0000000000000000000000000000000000000000..d150a044b1e5460e74d5e6dd4d58fc88b1ddf848 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XtUX;ELn>}1B`pvz=#?^1;nA?uaM;Iql>IsL$-h5%5|tKM yGIsEOkUZq#V9fKtq@|PNEDNvXOHNLO0}L~xg|aiJZMOy*!{F)a=d#Wzp$PyvCM`<< literal 0 HcmV?d00001 diff --git a/admin/javascript/LeftAndMain.Content.js b/admin/javascript/LeftAndMain.Content.js index 780108b50..1c2dfec29 100644 --- a/admin/javascript/LeftAndMain.Content.js +++ b/admin/javascript/LeftAndMain.Content.js @@ -15,9 +15,8 @@ // Force initialization of certain UI elements to avoid layout glitches this.find('.cms-tabset').redrawTabs(); - this.find('.ss-ui-tabs-nav').redraw(); - this._super(); + }, redraw: function() { diff --git a/admin/javascript/LeftAndMain.EditForm.js b/admin/javascript/LeftAndMain.EditForm.js index 4efc94e0a..227412dfc 100644 --- a/admin/javascript/LeftAndMain.EditForm.js +++ b/admin/javascript/LeftAndMain.EditForm.js @@ -205,14 +205,18 @@ }); /** - * Hide tabs when only one is available + * Hide tabs when only one is available. + * Special case is actiontabs - tabs between buttons, where we want to have + * extra options hidden within a tab (even if only one) by default. */ $('.cms-edit-form .ss-tabset').entwine({ onmatch: function() { - var tabs = this.find("> ul:first"); + if (!this.hasClass('ss-ui-action-tabset')) { + var tabs = this.find("> ul:first"); - if(tabs.children("li").length == 1) { - tabs.hide().parent().addClass("ss-tabset-tabshidden"); + if(tabs.children("li").length == 1) { + tabs.hide().parent().addClass("ss-tabset-tabshidden"); + } } this._super(); diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index dbfe0270c..dd11134dd 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -440,7 +440,7 @@ jQuery.noConflict(); * Can be hooked into an ajax 'success' callback. */ handleAjaxResponse: function(data, status, xhr) { - var self = this, url, activeTabs, guessFragment; + var self = this, url, selectedTabs, guessFragment; // Pseudo-redirects via X-ControllerURL might return empty data, in which // case we'll ignore the response @@ -571,19 +571,19 @@ jQuery.noConflict(); saveTabState: function() { if(typeof(window.sessionStorage)=="undefined" || window.sessionStorage == null) return; - var activeTabs = [], url = this._tabStateUrl(); + var selectedTabs = [], url = this._tabStateUrl(); this.find('.cms-tabset,.ss-tabset').each(function(i, el) { var id = $(el).attr('id'); if(!id) return; // we need a unique reference if(!$(el).data('tabs')) return; // don't act on uninit'ed controls if($(el).data('ignoreTabState')) return; // allow opt-out - activeTabs.push({id:id, active:$(el).tabs('option', 'active')}); + selectedTabs.push({id:id, selected:$(el).tabs('option', 'selected')}); }); - if(activeTabs) { + if(selectedTabs) { var tabsUrl = 'tabs-' + url; try { - window.sessionStorage.setItem(tabsUrl, JSON.stringify(activeTabs)); + window.sessionStorage.setItem(tabsUrl, JSON.stringify(selectedTabs)); } catch(err) { if (err.code === DOMException.QUOTA_EXCEEDED_ERR && window.sessionStorage.length === 0) { // If this fails we ignore the error as the only issue is that it @@ -606,12 +606,12 @@ jQuery.noConflict(); var self = this, url = this._tabStateUrl(), data = window.sessionStorage.getItem('tabs-' + url), - activeTabs = data ? JSON.parse(data) : false; - if(activeTabs) { - $.each(activeTabs, function(i, activeTab) { - var el = self.find('#' + activeTab.id); + selectedTabs = data ? JSON.parse(data) : false; + if(selectedTabs) { + $.each(selectedTabs, function(i, selectedTab) { + var el = self.find('#' + selectedTab.id); if(!el.data('tabs')) return; // don't act on uninit'ed controls - el.tabs('option', 'active', activeTab.active); + el.tabs('select', selectedTab.selected); }); } }, @@ -1012,7 +1012,7 @@ jQuery.noConflict(); }, redrawTabs: function() { this.rewriteHashlinks(); - + var id = this.attr('id'), activeTab = this.find('ul:first .ui-tabs-active'); if(!this.data('uiTabs')) this.tabs({ @@ -1025,7 +1025,7 @@ jQuery.noConflict(); activate: function(e, ui) { // Usability: Hide actions for "readonly" tabs (which don't contain any editable fields) var actions = $(this).closest('form').find('.Actions'); - if($(ui.tab).closest('li').hasClass('readonly')) { + if($(ui.newTab).closest('li').hasClass('readonly')) { actions.fadeOut(); } else { actions.show(); diff --git a/admin/javascript/ssui.core.js b/admin/javascript/ssui.core.js index 4e013ae83..bcdcd380f 100644 --- a/admin/javascript/ssui.core.js +++ b/admin/javascript/ssui.core.js @@ -83,17 +83,17 @@ } // Create missing elements. - if (this.options.alternate.text) { - this.buttonElement.append( - "" + this.options.alternate.text + "" - ); - } if (this.options.alternate.icon) { this.buttonElement.append( "" ); } + if (this.options.alternate.text) { + this.buttonElement.append( + "" + this.options.alternate.text + "" + ); + } this._refreshAlternate(); }, diff --git a/admin/scss/_actionTabs.scss b/admin/scss/_actionTabs.scss new file mode 100644 index 000000000..592327527 --- /dev/null +++ b/admin/scss/_actionTabs.scss @@ -0,0 +1,428 @@ +/******************************************** + +Defines the styles for the action tabset, found on the site tree, +and as a single (more options) tab in page view. This is a special +use case of tabs, so the default tab styling should not apply + + +**********************************************/ + + +$border: 1px solid darken(#D9D9D9, 15%); + +.cms { + .ss-ui-action-tabset{ + position:relative; + float:left; + + /*Style the "tabs" navigation for multiple tabs*/ + ul.ui-tabs-nav{ + @include clearfix; + padding:0; + overflow:visible; + float:left; + height: 28px; + border:$border; + @include border-radius(3px); + &:focus,&:active{ + outline:none; + box-shadow:none; + -webkit-box-shadow: none; + } + li{ + &:focus, &:active{ + outline:none; + box-shadow:none; + -webkit-box-shadow: none; + } + width: 110px; + overflow:visible; + background:#eaeaea; + @include background-image(linear-gradient(top, #f8f8f8, #D9D9D9)); + border-radius: none; + @include border-radius(0); + border: none; + border-right:1px solid #eee; + border-left: $border; + margin:0; + &.ui-state-active{ + background:#f8f8f8; + border-bottom:none !important; //jquery-ui style has important on it + @include border-bottom-left-radius(0px); + @include border-bottom-right-radius(0px); + a { + @include border-bottom-left-radius(0px); + @include border-bottom-right-radius(0px); + &:focus, span:focus,&:active, span:active{ + outline:none; + box-shadow:none; + -webkit-box-shadow: none; + } + } + } + &.first{ + @include border-top-left-radius(3px); + @include border-bottom-left-radius(3px); + border-left:none; + } + &.last{ + @include border-top-right-radius(3px); + @include border-bottom-right-radius(3px); + border-right:none; + } + a.tab-nav-link{ + color:$color-text; + font-weight:bold; + line-height:16px; + display:inline-block; + padding: 5px 10px; + .ui-no-icon { + display:inline-block; + float:left; + padding: 0 2px; + width:16px; + height:16px; + } + .title{ + display:inline-block; + line-height: 18px; + } + &.view-mode-batchactions-wrapper .title { + margin-left: 22px; + } + } + } + } + &.tabset-open { + ul.ui-tabs-nav, + ul.ui-tabs-nav li.first { + @include border-bottom-left-radius(0); + } + } + &.tabset-open-last { + ul.ui-tabs-nav li.last { + @include border-bottom-right-radius(0); + } + } + + .batch-check, .ui-icon { /* position a checkbox & icon within a tab */ + display: inline-block; + float:left; + margin-left: -2px; + padding-right: 6px; + } + .batch-check { + margin: 6px 0px 5px 9px; + position: absolute; + } + &.single{ + ul.ui-tabs-nav{ + background:none; + border:none; + display:inline; + padding:0; + float:left; + li{ + display:inline; + background:none; + border:none; + padding:0; + border-bottom:none !important; //jquery-ui style has important on it + + &:hover, &:focus, &:active{ + @include box-shadow(none); + outline:none; + } + a{ + color: $color-text-blue-link; + @include text-shadow(#fff 0 1px 1px); + padding:0 0 0 10px; + line-height:24px; + + &:hover, &:focus, &:active{ + @include box-shadow(none); + outline:none; + } + &:hover{ + @include text-shadow(#fff 0 10px 10px); + color: darken($color-text-blue-link,8%); + &:after{ + border-bottom: 4px solid darken($color-text-blue-link,8%); + } + } + } + } + } + } + + /* Style the tab panels */ + .ss-ui-action-tab.ui-tabs-panel{ + display:block; + clear:both; + background:#f8f8f8 !important; //Because ie7 doesn't understanding what the 'C' in CSS stands for + position:absolute; + top:30px; + border:$border; + border-top:none; + width:202px; + z-index:1; + padding:10px; + padding-top:15px; + @include tightSpacing; + margin:0; + float:left; + .field label{ + font-size:12px; + } + .cms-content-fields{ + overflow:visible; + } + .chzn-container-single{ + width:100% !important; + .chzn-single{ + padding: 0 0 0 5px; + float:none; + } + } + .cms-content-actions, .cms-preview-controls{ + padding:0; + height:auto; + border:none; + @include box-shadow(none); + + } + .field{ + border-bottom:none; + @include box-shadow(none); + } + .cms-edit-form{ + width:100%; + } + .CompositeField{ + margin:0; + padding:0; + float:none; + } + .parent-mode{ + padding-top:0; + } + + .treedropdown, .SelectionGroup li.selected div.field{ + margin:10px 0 0 0; + //@include box-shadow(inset 0 1px 0 #fff, 0 1px 1px rgba(0,0,0,0.1)); + .treedropdownfield-title{ + position:absolute; + z-index:2; + padding:5px; + } + .treedropdownfield-panel{ + margin-top:11px; + } + .treedropdownfield-toggle-panel-link{ + background:none; + border-left:none; + padding:5px 3px; + .ui-icon{ + float:right; + opacity:0.7; + } + } + } + #PageType ul{ + padding:0; + li{ + padding:4px 5px; + } + } + .cms-add-form ul.SelectionGroup{ + padding-left:0; + padding-right:0; + overflow:visible; + border-bottom:none; + } + label.extra-details{ + overflow:hidden; + margin-top:10px; + display: block; + color: lighten($color-text, 35%); + font-style:italic; + font-weight:normal; + font-size:1em; + float:left; + @include text-shadow(none); + &.fill{ + &:before{ + color:#fff; + content: '?'; + font-size:12px; + @include box-sizing('border-box'); + padding-left:3px; + padding-right:3px; + display:block; + float:left; + @include text-shadow(none); + @include border-radius(50px); + background-color:lighten($color-text, 45%); + width:15px; + height:15px; + margin-right:5px; + margin-bottom:5px; + } + } + } + &.first { + left: 0; + width: 203px; + } + .ui-icon { + padding-right: 0; + } + .tab-nav-link, .ss-ui-button { + font-size: 12px; + } + } + .last .ss-ui-action-tab{ + right:-1px; + left:auto; + } + } + + /********************** + Styles for pop-up tabs in bottom panel + ************************/ + .south .Actions{ + overflow:visible; //put this somewhere else/more generic + + .rise-up.ss-ui-action-tabset{ + ul.ui-tabs-nav { + margin: 0; + li { + a.ui-tabs-anchor { + font-weight: normal; + font-size: 13px; + line-height: 24px; + padding-right: 25px; + &:after { + background: sprite($sprites32, arrow_down_lighter) no-repeat; + width: 16px; + height: 16px; + content: ""; + display: inline-block; + margin-left: 6px; + border-bottom: 0; + + } + &:hover:after { + border-bottom: 0; + background: sprite($sprites32, arrow_down_darker) no-repeat; + } + + } + &.ui-state-active a.ui-tabs-anchor { + &:after { + background: sprite($sprites32, arrow_up_lighter) no-repeat; + } + &:hover:after { + background: sprite($sprites32, arrow_up_darker) no-repeat; + } + } + } + } + .ui-tabs-panel{ + @include clearfix; + @include border-top-radius(3px); + @include border-bottom-radius(0); + @include tightSpacing; + background-color: $tab-panel-texture-color; + border:1px solid #ccc; + border-bottom:1px solid $tab-panel-texture-color; + clear:both; + display:block; + position:absolute; + top:-204px; + width:190px; /* same width as buttons within panel */ + z-index:1; + padding:10px; + margin:0; + margin-top:1px; + .chzn-container-single .chzn-single{ + padding: 0 0 0 5px; + float:none; + } + @extend .button-no-style; + button.ss-ui-button{ + &:hover, &:focus, &:active{ + /*text-decoration:underline;*/ + background-color: darken($tab-panel-texture-color,4%); + @include box-shadow(none); + outline:none; + } + + } + .cms-sitetree-information { + border-bottom: 1px solid $color-light-separator; + margin-bottom: 8px; + p.meta-info { + color: #999; + font-size: 11px; + line-height: 16px; + margin-bottom: 8px; + } + } + } + .last .ui-tabs-panel.ss-ui-action-tab{ + right:-1px; + left:auto; + } + } + } + + + /* Styles for the cms-actions in tree view, to use more limited space. + Title hidden in tree view, until hover/active state added. Active is applied + to the first tab within the template, so there should always be one title + visible. Added and removed with js in TabSet.js */ + .cms-tree-view-sidebar{ + min-width: 176px; /* for when the scrollbar is present & find dropdown open */ + .ss-ui-action-tabset.ss-tabset.multi{ + ul.ui-tabs-nav{ + >li{ + width: auto; + a.tab-nav-link{ + width:30px; + overflow:hidden; + @include box-sizing(border-box); + padding-right:0; + @include duration(0.5s); + &.active{ + width:110px; + @include duration(0.5s); + } + } + } + } + &.tabset-open, &.tabset-open-last { + ul.ui-tabs-nav, + ul.ui-tabs-nav li.first, + ul.ui-tabs-nav li.last { + @include border-bottom-right-radius(0); + @include border-bottom-left-radius(0); + } + } + } + .ui-tabs .ui-tabs-panel.ss-ui-action-tab { + width:162px; + padding:10px 6px; + .field { + max-width:160px; + } + .ui-icon { + padding-right: 0; + } + } + .last .ui-tabs-panel.ss-ui-action-tab { + right:0; + left:auto; + } + } +} \ No newline at end of file diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss index 2bb2e3f9d..393c76d07 100644 --- a/admin/scss/_forms.scss +++ b/admin/scss/_forms.scss @@ -285,6 +285,36 @@ form.small .field, .field.small { * ---------------------------------------------------- */ .cms { + .button-no-style{ + button{ + background: none; + border: none; + display: block; + margin:0; + outline:none; + color: $color-text-blue-link; + font-weight:normal; + width: 210px; /* same as width of surrounding panel */ + text-align: left; + @include border-radius(0); + text-shadow: none; + margin-left:-10px; + + &.ss-ui-action-destructive{ + color: darken($color-error,25%); + } + span{ + padding-left:0; + padding-right:0; + } + &:hover, &:focus, &:active{ + outline:none; + background:none; + @include box-shadow(none); + border:none; + } + } + } .Actions, .cms-actions-row { > * { @@ -300,7 +330,7 @@ form.small .field, .field.small { .Actions { min-height: 30px; - overflow: visible; + overflow: auto; padding: $grid-x $grid-y * 1.5; } .south .Actions, .ui-tabs-panel .Actions, .ui-tabs-panel iframe .Actions { @@ -328,7 +358,9 @@ form.small .field, .field.small { } .ss-ui-button { + font-size: 12px; margin-top:0px; + padding: 5px 10px; font-weight: bold; text-decoration: none; line-height: $grid-y * 2; @@ -339,6 +371,22 @@ form.small .field, .field.small { background-color: $color-button-generic; white-space: nowrap; + .ui-icon, .ui-button-text { + display: inline-block; + line-height: $grid-x*2; + padding: 0; + } + .ui-icon { + width: 16px; + padding: 0 2px; + position: relative; + left: -2px; + margin-top: 0; + top: 0; + height: 16px; + float: left; + } + @include background( linear-gradient(color-stops( lighten($color-button-generic, 10%), @@ -412,7 +460,6 @@ form.small .field, .field.small { &.ss-ui-button-small { .ui-button-text { - padding: ($grid-y/4) ($grid-x/4); font-size: $font-base-size - 2; } } diff --git a/admin/scss/_menu.scss b/admin/scss/_menu.scss index 6e93297e0..e427589c9 100644 --- a/admin/scss/_menu.scss +++ b/admin/scss/_menu.scss @@ -319,44 +319,6 @@ line-height: 32px; } } - /* // To specific - was overriding collapsed-flyout styles -#Menu-CMSPagesController { - a { - background-image:none; - font-size: 11px; - padding: 0 10px 0 40px; - height: 32px; - line-height: 32px; - } - } - #Menu-CMSPageAddController { - a { - background-image:none; - font-size: 11px; - padding: 0 10px 0 40px; - height: 32px; - line-height: 32px; - } - } - #Menu-AssetAdmin { - a { - background-image:none; - font-size: 11px; - padding: 0 10px 0 40px; - height: 32px; - line-height: 32px; - } - } - #Menu-CMSFileAddController { - a { - background-image:none; - font-size: 11px; - padding: 0 10px 0 40px; - height: 32px; - line-height: 32px; - } - } -*/ } /* Style applied to the menu flyout only when the collapsed setting */ diff --git a/admin/scss/_mixins.scss b/admin/scss/_mixins.scss index 403629d43..3fd97769d 100644 --- a/admin/scss/_mixins.scss +++ b/admin/scss/_mixins.scss @@ -108,7 +108,79 @@ transition: $properties; } +@mixin duration($time, $webkit:true){ + @if($webkit){ + -webkit-transition-duration: $time; + } + -moz-transition-duration: $time; + -o-transition-duration: $time; + transition-duration: $time; +} +/*Mixin used to generate slightly smaller text and forms +Used in side panels and action tabs +*/ +@mixin tightSpacing{ + h3,h4,h5 { + font-weight: bold; + line-height: $grid-y * 2; + } + h3 { + font-size: $font-base-size + 1; + } + h4 { + font-size: $font-base-size; + margin:5px 0; + } + + .ui-widget-content { + background: none; + } + + .field { + /* + * Fields are more compressed in the sidebar compared to the + * main content editing window so the below alters the internal + * spacing of the fields so we can move that spacing to between + * the form fields rather than padding + */ + label { + float: none; + width: auto; + font-size: 11px; + padding: 0 $grid-x 4px 0; + } + + .middleColumn { + margin: 0; + } + + input.text, + select, + textarea { + padding: 5px; + font-size: 11px; + } + + &.checkbox { + padding: 0 8px 0; + + input { + margin: 2px 0; + } + } + } + .fieldgroup { + .fieldgroup-field { + padding: 0; + + .field { + margin: 0; + padding: 0; + } + } + } +} diff --git a/admin/scss/_style.scss b/admin/scss/_style.scss index 2219c522b..332ade848 100644 --- a/admin/scss/_style.scss +++ b/admin/scss/_style.scss @@ -54,16 +54,18 @@ body.cms { * Helpers * -------------------------------------------- */ -.cms-helper-hide-actions { - .Actions { - display: none; - } -} - .hide { display: none; } +.cms-helper-hide-actions { + .Actions { + @extend .hide; + } +} + + + /** -------------------------------------------- * Panels Styles * -------------------------------------------- */ @@ -248,7 +250,7 @@ body.cms { } } - .cms-edit-form, .cms-content-fields { + .cms-edit-form, .cms-content-fields { /*not sure if .cms-content-fields effects other areas*/ .cms-panel-padded { /* Has padded area inside it */ padding: 0; margin: 0; @@ -540,8 +542,9 @@ p.message { overflow: auto; } } + #PageType { - ul { + ul { padding-left: 20px; li { float: none; @@ -607,6 +610,7 @@ p.message { min-height: 29px; display: block; margin: 0 0 15px 0; + padding-bottom: 9px; @include doubleborder(bottom, $color-light-separator, $box-shadow-shine); @include legacy-pie-clearfix(); @@ -1579,9 +1583,6 @@ form.small { span.btn-icon-download-csv { height:17px; //exact height of icon } - .ui-button-text { - padding-left:26px; //to accomodate wider export icon - } } } diff --git a/admin/scss/_uitheme.scss b/admin/scss/_uitheme.scss index f6a9b5cb1..3c2e8f6f2 100644 --- a/admin/scss/_uitheme.scss +++ b/admin/scss/_uitheme.scss @@ -45,16 +45,16 @@ z-index: 100000; } - & a.ui-state-hover { + a.ui-state-hover { border-color: transparent; background: transparent; - & .ui-icon-closethick { + .ui-icon-closethick { background: sprite($sprites32, dialog-close-over) no-repeat; } } - & .ui-icon-closethick { + .ui-icon-closethick { background: sprite($sprites32, dialog-close) no-repeat; width: 30px; height: 30px; diff --git a/admin/scss/_uitheme.scss.orig b/admin/scss/_uitheme.scss.orig deleted file mode 100644 index 6fb3d3bd3..000000000 --- a/admin/scss/_uitheme.scss.orig +++ /dev/null @@ -1,116 +0,0 @@ -/** - * This file defines CMS-specific customizations to the jQuery UI theme. - * Every rule should contain ONLY overwritten jQuery UI rules (with 'ui-' prefix). - * - * This file should be fairly short, as we're using our own custom jQuery UI theme already. - * TODO Add theme reference - * - * Use _style.scss to add more generic style information, - * and read the jQuery UI theming API: http://jqueryui.com/docs/Theming/API - */ - -<<<<<<< HEAD -.cms { - .ui-tabs { - padding: 0; - background: none; - - .ui-widget-header { - border: 0; - background: none; - } - - .ui-tabs-nav { - margin: 0; - padding: 0; - - li { - top: 0; - border-bottom: 0 !important; - - a { - padding: 0 15px; - } - } - - &.ui-state-active { - border-color: $color-medium-separator; - } - } -======= -.ui-widget-content, -.ui-widget { - color: $color-text; - font-size: $font-base-size; - font-family: $font-family; - border: 0; -} - - -.ui-widget-header { - background-color: darken($color-widget-bg, 20%); - padding: 8px 8px 6px 8px; - border-bottom: 2px solid darken($color-widget-bg, 35%); - @include background-image( - linear-gradient(darken($color-widget-bg, 5%), darken($color-widget-bg, 30%)) - ); - border-bottom: 3px solid darken($color-widget-bg, 50%); - padding: 8px; - @include border-radius(0); ->>>>>>> ENHANCEMENT Tab style consolidation and design consistency - - & .ui-dialog-title { - padding: 6px 0; - text-shadow: lighten($color-base, 10%) 1px 1px 0; - } - - - - & a.ui-dialog-titlebar-close { - position: absolute; - top: -8px; - right: -15px; - width: 30px; - height: 30px; - z-index: 100000; - } - - & a.ui-state-hover { - border-color: transparent; - background: transparent; - - & .ui-icon-closethick { - background: sprite($sprites32, dialog-close-over) no-repeat; - } - } - - & .ui-icon-closethick { - background: sprite($sprites32, dialog-close) no-repeat; - width: 30px; - height: 30px; - } -} - -.ui-state-hover { - cursor: pointer; -} - -.ui-widget input, -.ui-widget select, -.ui-widget textarea, -.ui-widget button { - color: $color-text; - font-size: $font-base-size; - font-family: $font-family; -} - -.ui-accordion { - .ui-accordion-header { - border-color: $color-button-generic-border; - margin-bottom: 0; - } - .ui-accordion-content { - border: 1px solid $color-button-generic-border; - border-top: none; - } -} diff --git a/admin/scss/screen.scss b/admin/scss/screen.scss index 8e1af8b4d..382c5ed54 100644 --- a/admin/scss/screen.scss +++ b/admin/scss/screen.scss @@ -55,5 +55,6 @@ $experimental-support-for-svg: true; @import "tree.scss"; @import "menu.scss"; @import "preview.scss"; +@import "actionTabs.scss"; @import "ModelAdmin.scss"; @import "SecurityAdmin.scss"; diff --git a/css/AssetUploadField.css b/css/AssetUploadField.css index e4c0b2b3a..ad090f63c 100644 --- a/css/AssetUploadField.css +++ b/css/AssetUploadField.css @@ -7,6 +7,9 @@ /** ----------------------------------------------- Typography. ------------------------------------------------ */ /** ----------------------------------------------- Grid Units (px) We have a vertical rhythm that the grid is based off both x (=horizontal) and y (=vertical). All internal padding and margins are scaled to this and accounting for paragraphs ------------------------------------------------ */ /** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */ +/*Mixin used to generate slightly smaller text and forms +Used in side panels and action tabs +*/ .ss-uploadfield-view-allowed-extensions { padding-top: 20px; clear: both; max-width: 750px; display: block; } #AssetUploadField { border-bottom: 0; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; padding: 12px; } diff --git a/css/GridField.css b/css/GridField.css index 78f373cbe..ec6e5bddb 100644 --- a/css/GridField.css +++ b/css/GridField.css @@ -8,14 +8,15 @@ /** ----------------------------------------------- Typography. ------------------------------------------------ */ /** ----------------------------------------------- Grid Units (px) We have a vertical rhythm that the grid is based off both x (=horizontal) and y (=vertical). All internal padding and margins are scaled to this and accounting for paragraphs ------------------------------------------------ */ /** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */ -.cms .ss-gridfield > div { margin-bottom: 36px; } +/*Mixin used to generate slightly smaller text and forms +Used in side panels and action tabs +*/ .cms .ss-gridfield > div.addNewGridFieldButton { margin-bottom: 0; } .cms .ss-gridfield > div.addNewGridFieldButton .action { margin-bottom: 12px; } .cms .ss-gridfield[data-selectable] tr.ui-selected, .cms .ss-gridfield[data-selectable] tr.ui-selecting { background: #FFFAD6 !important; } .cms .ss-gridfield[data-selectable] td { cursor: pointer; } .cms .ss-gridfield span button#action_gridfield_relationfind { display: none; } .cms .ss-gridfield p button#action_export span.btn-icon-download-csv { height: 17px; } -.cms .ss-gridfield p button#action_export .ui-button-text { padding-left: 26px; } .cms .ss-gridfield .right { float: right; } .cms .ss-gridfield .right > * { float: right; margin-left: 8px; } .cms .ss-gridfield .right .pagination-records-number { font-size: 1.0em; padding: 6px 3px 6px 0; color: white; text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.2); font-weight: normal; } diff --git a/css/UploadField.css b/css/UploadField.css index 2885c4320..6ef200f4f 100644 --- a/css/UploadField.css +++ b/css/UploadField.css @@ -7,10 +7,13 @@ /** ----------------------------------------------- Typography. ------------------------------------------------ */ /** ----------------------------------------------- Grid Units (px) We have a vertical rhythm that the grid is based off both x (=horizontal) and y (=vertical). All internal padding and margins are scaled to this and accounting for paragraphs ------------------------------------------------ */ /** ----------------------------------------------- Application Logo (CMS Logo) Must be 24px x 24px ------------------------------------------------ */ +/*Mixin used to generate slightly smaller text and forms +Used in side panels and action tabs +*/ .ss-uploadfield .clear { clear: both; } .ss-insert-media .ss-uploadfield { margin-top: 20px; } .ss-insert-media .ss-uploadfield h4 { float: left; } -.ss-uploadfield .middleColumn { width: 526px; padding: 0; background: #fff; border: 1px solid #b3b3b3; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #efefef), color-stop(10%, #ffffff), color-stop(90%, #ffffff), color-stop(100%, #efefef)); background-image: -webkit-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -moz-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -o-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); } +.ss-uploadfield .middleColumn { width: 510px; padding: 0; background: #fff; border: 1px solid #b3b3b3; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #efefef), color-stop(10%, #ffffff), color-stop(90%, #ffffff), color-stop(100%, #efefef)); background-image: -webkit-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -moz-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -o-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); } .ss-uploadfield .ss-uploadfield-item { margin: 0; padding: 15px; overflow: auto; } .ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview { height: 60px; line-height: 60px; width: 80px; text-align: center; font-weight: bold; float: left; overflow: hidden; } .ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview.ss-uploadfield-dropzone { -webkit-box-shadow: gray 0 0 4px 0 inset; -moz-box-shadow: gray 0 0 4px 0 inset; box-shadow: gray 0 0 4px 0 inset; border: 2px dashed gray; background: #d0d3d5; display: none; } diff --git a/docs/en/howto/extend-cms-interface.md b/docs/en/howto/extend-cms-interface.md index 1e03503a6..eac980dd5 100644 --- a/docs/en/howto/extend-cms-interface.md +++ b/docs/en/howto/extend-cms-interface.md @@ -22,8 +22,8 @@ We can use this to create a different base template with `LeftAndMain.ss` (which corresponds to the `LeftAndMain` PHP controller class). Copy the template markup of the base implementation at `framework/admin/templates/LeftAndMain.ss` into -`mysite/templates/LeftAndMain.ss`. It will automatically be picked up by the CMS logic. Add a new section after -the `$Content` tag: +`mysite/templates/LeftAndMain.ss`. It will automatically be picked up by the CMS logic. Add a new section after the +`$Content` tag: :::ss ... @@ -125,6 +125,55 @@ and replace it with the following: <% end_loop %> +## Extending the CMS actions + +CMS actions follow a principle similar to the CMS fields: they are built in the backend with the help of `FormFields` +and `FormActions`, and the frontend is responsible for applying a consistent styling. + +The following conventions apply: + +* New actions can be added by redefining `getCMSActions`, or adding an extension with `updateCMSActions`. +* It is required the actions are contained in a `FieldSet` (`getCMSActions` returns this already). +* Standalone buttons are created by adding a top-level `FormAction` (no such button is added by default). +* Button groups are created by adding a top-level `CompositeField` with `FormActions` in it. +* A `MajorActions` button group is already provided as a default. +* Drop ups with additional actions that appear as links are created via a `TabSet` and `Tabs` with `FormActions` inside. +* A `ActionMenus.MoreOptions` tab is already provided as a default and contains some minor actions. +* You can override the actions completely by providing your own `getAllCMSFields`. + +Let's walk through a couple of examples of adding new CMS actions in `getCMSActions`. + +First of all we can add a regular standalone button anywhere in the set. Here we are inserting it in the front of all +other actions. We could also add a button group (`CompositeField`) in a similar fashion. + + :::php + $fields->unshift(FormAction::create('normal', 'Normal button')); + +We can affect the existing button group by manipulating the `CompositeField` already present in the `FieldList`. + + :::php + $fields->fieldByName('MajorActions')->push(FormAction::create('grouped', 'New group button')); + +Another option is adding actions into the drop-up - best place for placing infrequently used minor actions. + + :::php + $fields->addFieldToTab('ActionMenus.MoreOptions', FormAction::create('minor', 'Minor action')); + +We can also easily create new drop-up menus by defining new tabs within the `TabSet`. + + :::php + $fields->addFieldToTab('ActionMenus.MyDropUp', FormAction::create('minor', 'Minor action in a new drop-up')); + +
    +Empty tabs will be automatically removed from the `FieldList` to prevent clutter. +
    + +New actions will need associated controller handlers to work. You can use a `LeftAndMainExtension` to provide one. Refer +to [Controller documentation](../topics/controller) for instructions on setting up handlers. + +To make the actions more user-friendly you can also use alternating buttons as detailed in the [CMS Alternating +Button](../reference/cms-alternating-button) how-to. + ## Summary In a few lines of code, we've customized the look and feel of the CMS. @@ -136,3 +185,4 @@ blocks and concepts for more complex extensions as well. * [Reference: CMS Architecture](../reference/cms-architecture) * [Reference: Layout](../reference/layout) * [Topics: Rich Text Editing](../topics/rich-text-editing) + * [CMS Alternating Button](../reference/cms-alternating-button) diff --git a/javascript/TabSet.js b/javascript/TabSet.js index 2d30fcf58..7b0609f52 100644 --- a/javascript/TabSet.js +++ b/javascript/TabSet.js @@ -4,6 +4,80 @@ * Lightweight wrapper around jQuery UI tabs. */ $('.ss-tabset').entwine({ + + /*Custom functionality for special action tabsets*/ + actionTabs: function(){ + this.tabs( + 'option', + 'collapsible', + true + ).tabs('option', 'active', false); + + //Apply special behaviour to the cms actions row + if(this.hasClass('cms-actions-row')){ + + /* If actions panel is within the tree, apply active class + to help animate open/close on hover + Position must be reset else anyone coming from main sitetree + will see broken tabs */ + var container = this.parent().parent(); + if($(container).hasClass('cms-tree-view-sidebar')){ + $('.ui-tabs-nav li').hover(function(){ + $(this).parent().find('li .active').removeClass('active'); + $(this).find('a').addClass('active'); + }); + + this.tabs({ + beforeActivate:function(event, ui){ + var activePanel = ui.newPanel; + $(activePanel).attr("style","left : auto; right: auto"); + $(this).closest('.ss-ui-action-tabset').removeClass('tabset-open').removeClass('tabset-open-last'); + + if($(activePanel).length > 0){ + $(activePanel).parent().addClass('tabset-open'); + } + } + }); + }else{ + /* If the tabs are in the full site tree view, do some + positioning so tabPanel stays with relevent tab */ + this.tabs({ + beforeActivate:function(event, ui){ + var activePanel = ui.newPanel; + var activeTab = ui.newTab; + $(this).closest('.ss-ui-action-tabset').removeClass('tabset-open').removeClass('tabset-open-last'); + if($(activePanel).length > 0){ + if($(activeTab).hasClass("last")){ + $(activePanel).attr("style","left : auto; right: 0px"); + $(activePanel).parent().addClass('tabset-open-last');//last needs to be styled differently when open + }else{ + $(activePanel).attr("style","left: "+activeTab.position().left+"px"); + if($(activeTab).hasClass("first")){ + $(activePanel).attr("style","left: 0px"); + $(activePanel).parent().addClass('tabset-open'); + }else{ + $(activePanel).attr("style","left: "+activeTab.position().left+"px"); + } + } + } + + } + }); + } + }else if(this.parents('.south')){ + this.tabs({ + beforeActivate:function(event, ui){ + var activePanel = ui.newPanel; + var activeTab = ui.newTab; + if($(activePanel).length > 0){ + $(activePanel).attr("style","left: "+activeTab.position().left+"px"); + } + } + }); + } + //Check if tabs should open upwards, and adjust + this.riseUp(); + }, onadd: function() { // Can't name redraw() as it clashes with other CMS entwine classes this.redrawTabs(); @@ -13,9 +87,49 @@ if(this.data('uiTabs')) this.tabs('destroy'); this._super(); }, + riseUp: function(){ + /* Function checks to see if a tab should be opened upwards + (based on space concerns. If true, the rise-up class is applied + and the position is calculated and applied to the element */ + var elHeight = $(this).find('.ui-tabs-panel').outerHeight(); + var trigger = $(this).find('.ui-tabs-nav').outerHeight(); + var endOfWindow = ($(window).height() + $(document).scrollTop()) - trigger; + var elPos = $(this).find('.ui-tabs-nav').offset().top; + if(elPos + elHeight >= endOfWindow && elPos - elHeight > 0){ + this.addClass('rise-up'); + + /* Apply position to tab */ + this.tabs({ + activate:function(event, ui){ + var activePanel = ui.newPanel; + var activeTab = ui.newTab; + if(activeTab.position()!=null){ + var top = -activePanel.outerHeight(); + var containerSouth = activePanel.parents('.south'); + if(containerSouth){ + var padding = activeTab.offset().top-containerSouth.offset().top; + top = top-padding; + } + var style = $(activePanel).attr("style"); + + $(activePanel).attr("style", style+"top: "+top+"px;"); + } + } + }); + + }else{ + this.removeClass('rise-up'); + } + return false; + }, redrawTabs: function() { this.rewriteHashlinks(); this.tabs(); + + //Apply special behaviour to action tabs: closed by default, and collapsible + if(this.hasClass('ss-ui-action-tabset')){ + this.actionTabs(); + } }, /** diff --git a/scss/GridField.scss b/scss/GridField.scss index 73423e558..25d9995e4 100644 --- a/scss/GridField.scss +++ b/scss/GridField.scss @@ -39,14 +39,13 @@ $gf_grid_x: 16px; .cms { .ss-gridfield { & > div { - margin-bottom: $gf_grid_y*3; &.addNewGridFieldButton{ margin-bottom: 0; .action { - margin-bottom: $gf_grid_y; - } + margin-bottom:$gf_grid_y; } } + } &[data-selectable] { tr.ui-selected, tr.ui-selecting { @@ -66,9 +65,6 @@ $gf_grid_x: 16px; span.btn-icon-download-csv { height:17px; //exact height of icon } - .ui-button-text { - padding-left:26px; //to accomodate wider export icon - } } .right { float:right; @@ -95,8 +91,8 @@ $gf_grid_x: 16px; .ss-gridfield-buttonrow { font-size: $gf_grid_y*1.2; + } } - } .ss-gridfield { .grid-levelup { @@ -120,7 +116,7 @@ $gf_grid_x: 16px; margin-bottom: $gf_grid_y; @include inline-block(); } - } + } table.ss-gridfield-table { display: table; @include box-shadow-none; diff --git a/scss/UploadField.scss b/scss/UploadField.scss index d2474504e..cfbe8ffda 100644 --- a/scss/UploadField.scss +++ b/scss/UploadField.scss @@ -22,7 +22,7 @@ .middleColumn { // TODO .middleColumn styling should probably be theme specific (eg cms ui will look different than blackcandy) // so we should move this style into the cms and black candy files - width: 526px; + width: 510px; padding: 0; background: #fff; border: 1px solid lighten($color-medium-separator, 20%); From a80aa3c9698a2df260f768b1010e8e0d2d109819 Mon Sep 17 00:00:00 2001 From: Naomi Guyer Date: Thu, 29 Nov 2012 11:36:13 +1300 Subject: [PATCH 18/49] Provide new save icon for the use in the framework. --- admin/css/screen.css | 52 +++++++++++++------------- admin/images/btn-icon-s5a3074ba2a.png | Bin 0 -> 21980 bytes admin/images/btn-icon-s97372285ea.png | Bin 22288 -> 0 bytes admin/images/btn-icon/disk.png | Bin 620 -> 1238 bytes 4 files changed, 26 insertions(+), 26 deletions(-) create mode 100644 admin/images/btn-icon-s5a3074ba2a.png delete mode 100644 admin/images/btn-icon-s97372285ea.png diff --git a/admin/css/screen.css b/admin/css/screen.css index c992891ee..bcb41826a 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -37,7 +37,7 @@ Used in side panels and action tabs */ /** ----------------------------- Sprite images ----------------------------- */ /** Helper SCSS file for generating sprites for the interface. */ -.btn-icon-sprite, .ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept, .ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled, .ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add, .ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia, .ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled, .ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage, .ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled, .ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left, .ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double, .ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back, .ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled, .ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow, .ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation, .ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus, .ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil, .ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus, .ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small, .ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain, .ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain, .ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle, .ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled, .ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross, .ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline, .ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled, .ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete, .ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight, .ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk, .ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil, .ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv, .ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload, .ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled, .ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print, .ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier, .ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle, .ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled, .ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation, .ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled, .ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud, .ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled, .ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil, .ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled, .ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition, .ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled, .ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview, .ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled, .ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings, .ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled, .ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish, .ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background: url('../images/btn-icon-s97372285ea.png') no-repeat; } +.btn-icon-sprite, .ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept, .ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled, .ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add, .ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia, .ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled, .ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage, .ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled, .ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left, .ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double, .ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back, .ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled, .ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow, .ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation, .ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus, .ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil, .ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus, .ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small, .ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain, .ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain, .ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle, .ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled, .ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross, .ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline, .ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled, .ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete, .ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight, .ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk, .ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil, .ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv, .ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload, .ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled, .ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print, .ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier, .ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle, .ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled, .ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation, .ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled, .ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud, .ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled, .ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil, .ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled, .ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition, .ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled, .ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview, .ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled, .ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings, .ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled, .ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish, .ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background: url('../images/btn-icon-s5a3074ba2a.png') no-repeat; } .ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept { background-position: 0 -96px; } .ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled { background-position: 0 -80px; } @@ -45,47 +45,47 @@ Used in side panels and action tabs .ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia { background-position: 0 -208px; } .ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled { background-position: 0 -32px; } .ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage { background-position: 0 -144px; } -.ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled { background-position: 0 -484px; } -.ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left { background-position: 0 -340px; } -.ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double { background-position: 0 -324px; } -.ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back { background-position: 0 -356px; } +.ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled { background-position: 0 -500px; } +.ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left { background-position: 0 -356px; } +.ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double { background-position: 0 -340px; } +.ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back { background-position: 0 -372px; } .ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled { background-position: 0 -16px; } .ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow { background-position: 0 -724px; } -.ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation { background-position: 0 -500px; } +.ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation { background-position: 0 -516px; } .ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus { background-position: 0 -740px; } -.ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil { background-position: 0 -660px; } +.ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil { background-position: 0 -676px; } .ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus { background-position: 0 -708px; } .ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small { background-position: 0 -772px; } -.ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain { background-position: 0 -468px; } +.ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain { background-position: 0 -484px; } .ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain { background-position: 0 -756px; } -.ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle { background-position: 0 -436px; } -.ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled { background-position: 0 -548px; } +.ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle { background-position: 0 -452px; } +.ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled { background-position: 0 -564px; } .ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross { background-position: 0 -276px; } .ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline { background-position: 0 -128px; } .ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled { background-position: 0 -192px; } -.ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete { background-position: 0 -452px; } -.ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight { background-position: 0 -291px; } -.ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk { background-position: 0 -676px; } -.ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil { background-position: 0 -532px; } +.ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete { background-position: 0 -468px; } +.ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight { background-position: 0 -307px; } +.ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk { background-position: 0 -291px; } +.ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil { background-position: 0 -548px; } .ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv { background-position: 0 -48px; } -.ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload { background-position: 0 -404px; } -.ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled { background-position: 0 -564px; } +.ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload { background-position: 0 -420px; } +.ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled { background-position: 0 -580px; } .ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print { background-position: 0 -260px; } -.ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier { background-position: 0 -516px; } -.ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle { background-position: 0 -612px; } -.ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled { background-position: 0 -628px; } -.ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation { background-position: 0 -372px; } -.ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled { background-position: 0 -420px; } -.ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud { background-position: 0 -596px; } +.ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier { background-position: 0 -532px; } +.ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle { background-position: 0 -628px; } +.ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled { background-position: 0 -644px; } +.ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation { background-position: 0 -388px; } +.ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled { background-position: 0 -436px; } +.ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud { background-position: 0 -612px; } .ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled { background-position: 0 -692px; } .ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil { background-position: 0 -228px; } -.ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled { background-position: 0 -580px; } +.ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled { background-position: 0 -596px; } .ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition { background-position: 0 -244px; } -.ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled { background-position: 0 -644px; } +.ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled { background-position: 0 -660px; } .ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview { background-position: 0 -64px; } .ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled { background-position: 0 -160px; } -.ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings { background-position: 0 -308px; } -.ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled { background-position: 0 -388px; } +.ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings { background-position: 0 -324px; } +.ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled { background-position: 0 -404px; } .ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish { background-position: 0 -112px; } .ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background-position: 0 -176px; } diff --git a/admin/images/btn-icon-s5a3074ba2a.png b/admin/images/btn-icon-s5a3074ba2a.png new file mode 100644 index 0000000000000000000000000000000000000000..627c22b8026444dd03e9c440aaa536597df81378 GIT binary patch literal 21980 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 diff --git a/admin/images/btn-icon/disk.png b/admin/images/btn-icon/disk.png index 99d532e8b1750115952f97302a92d713c0486f97..0d0696e8a37a3324b3bcb2e6d2d216a0723bd906 100755 GIT binary patch literal 1238 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%s|1+P|wiV z#N6CmN5ROz&_Lh7NZ-&%*U;R`*vQJjKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8EkR}&8R-I5=oVMzl_XZ^<`pZ$OmImpPAEg{v+u2}(t{7puX=A(aKG z`a!A1`K3k4z=%sz23b{L-^Aq1JP;qO z-q+X4Gq1QLF)umQ)5TT^Xo6m5W{Q=8tDBRhrK^Flp|hKzp{tRTrKzctfs?b5Igqe$ zb%p75$xklLP0cHT=}kfCHN>eGloWCcfHu3N7G;*DrnnX5=PH1`Y?X<{ElwupMiv%s zEeUB2MjsThND&Pa0;V1i6P|2=9C*S{%>$pi{5)L#33UJ2z+aZOf970S#s|~Bw8@pmriaPB{`za;we(!^M2Rw% zhWEc;DfA`hn9a76TXtD<+wHZDH*B6(_L=xjb-umLLWWO3=4(l&+3c03ou#+!?Av9F8{+@TF*7D$=8)EDlvo$q0?^<^*YZ1erG}%_u`S-ty6~5{BO=H_| z|NX>NNw>ultx96Ii*KmDzw*o0^-MDT?rXxd|AclQePqL2S1;l!u}GU&V%2}fKmQuL z8LtE`(`@Ug|8lf&&Tnu1p!a)%uG<+rn04%I!vlGiAAiIW7`8QRu(w>VSOqF+JYD@< J);T3K0RY~8uqOZj delta 560 zcmV-00?+-{3G4)rBn<>-K}|sb0I$e51&ZmBAt!$U{z*hZR2Ufr!99yzRTu`)wa@H3 zLxPg1V1z_O0}56FWgzjR6@P+-Xpt&H&?2QaR#qaS2xFnx3U-2GVU;2l7Qs%%LbMXa z4BnZ0_I@ANYKL!p_ZA+*^LPUHk&Y(7-qI{flawm|>~H+MJzd|s^YP1Hc07G_>)Lgi zr!IdU2vV^dv0KaD(wsS1w8rta9G}c{zy5iA<@0ZT`Q-4%*XI_zAbfS_Ulo$L`QZ<$ zLTfg?S(auuV!M`6+O+i7Ub_6ynS=e07CaJE#X8D50@jMY$EG(`vE7w*gv9rE@7dq% z*`AL5j7|J+pk}->5IOw&iL=N zj4D+`q{=#`q;#~JNWA;mk6LS%#-=YCM#UH<062gCLF?Ef(S=H>njXGzkwy~&QK70N zQHiReHS0P^=`be=&=R0gMG{1UL`x)PU7MtI7_$%%(KHfCq)J2*RZ}XWoUS3I6SIFp zc;}T%00000;MUPk#)zc!r8TpNNciCPbpQYWaP#VsIfvZ{iLOx<4RGz!s{jB10BV-G z2Bjy-tTX~{|NaF4004OQ(HG4*tuvCOSNygc_4qTF&uWb~pZgR5006*+`^%hxzuV!B yZ{JVyB)KYifRcoiq)17M6p18A$UPWsx%)pMd~SE0000 Date: Thu, 29 Nov 2012 16:26:50 +1300 Subject: [PATCH 19/49] CSS fixes for the ActionTabSet. Thanks for contributing @clarkepaul! --- admin/css/ie7.css | 78 ++++++++++++++++++++ admin/css/screen.css | 115 ++++++++++++++++------------- admin/scss/_actionTabs.scss | 141 ++++++------------------------------ admin/scss/_mixins.scss | 92 ++++++++++++++++++++++- admin/scss/ie7.scss | 22 +++++- javascript/TabSet.js | 40 +++++++--- 6 files changed, 307 insertions(+), 181 deletions(-) diff --git a/admin/css/ie7.css b/admin/css/ie7.css index 20d35cd50..f82b5b2a3 100644 --- a/admin/css/ie7.css +++ b/admin/css/ie7.css @@ -52,6 +52,79 @@ fieldset.switch-states .switch .slide-button { display: none; } /* Hide size controls in IE - they won't work as intended */ .cms-content-controls .preview-size-selector { display: none; } +/** Helper SCSS file for generating sprites for the interface. */ +.btn-icon-sprite, .ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept, .ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled, .ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add, .ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia, .ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled, .ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage, .ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled, .ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left, .ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double, .ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back, .ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled, .ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow, .ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation, .ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus, .ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil, .ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus, .ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small, .ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain, .ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain, .ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle, .ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled, .ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross, .ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline, .ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled, .ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete, .ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight, .ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk, .ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil, .ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv, .ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload, .ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled, .ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print, .ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier, .ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle, .ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled, .ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation, .ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled, .ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud, .ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled, .ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil, .ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled, .ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition, .ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled, .ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview, .ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled, .ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings, .ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled, .ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish, .ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background: url('../images/btn-icon-s5a3074ba2a.png') no-repeat; } + +.ui-state-default .btn-icon-accept, .ui-widget-content .btn-icon-accept { background-position: 0 -96px; } +.ui-state-default .btn-icon-accept_disabled, .ui-widget-content .btn-icon-accept_disabled { background-position: 0 -80px; } +.ui-state-default .btn-icon-add, .ui-widget-content .btn-icon-add { background-position: 0 0; } +.ui-state-default .btn-icon-addMedia, .ui-widget-content .btn-icon-addMedia { background-position: 0 -208px; } +.ui-state-default .btn-icon-add_disabled, .ui-widget-content .btn-icon-add_disabled { background-position: 0 -32px; } +.ui-state-default .btn-icon-addpage, .ui-widget-content .btn-icon-addpage { background-position: 0 -144px; } +.ui-state-default .btn-icon-addpage_disabled, .ui-widget-content .btn-icon-addpage_disabled { background-position: 0 -500px; } +.ui-state-default .btn-icon-arrow-circle-135-left, .ui-widget-content .btn-icon-arrow-circle-135-left { background-position: 0 -356px; } +.ui-state-default .btn-icon-arrow-circle-double, .ui-widget-content .btn-icon-arrow-circle-double { background-position: 0 -340px; } +.ui-state-default .btn-icon-back, .ui-widget-content .btn-icon-back { background-position: 0 -372px; } +.ui-state-default .btn-icon-back_disabled, .ui-widget-content .btn-icon-back_disabled { background-position: 0 -16px; } +.ui-state-default .btn-icon-chain--arrow, .ui-widget-content .btn-icon-chain--arrow { background-position: 0 -724px; } +.ui-state-default .btn-icon-chain--exclamation, .ui-widget-content .btn-icon-chain--exclamation { background-position: 0 -516px; } +.ui-state-default .btn-icon-chain--minus, .ui-widget-content .btn-icon-chain--minus { background-position: 0 -740px; } +.ui-state-default .btn-icon-chain--pencil, .ui-widget-content .btn-icon-chain--pencil { background-position: 0 -676px; } +.ui-state-default .btn-icon-chain--plus, .ui-widget-content .btn-icon-chain--plus { background-position: 0 -708px; } +.ui-state-default .btn-icon-chain-small, .ui-widget-content .btn-icon-chain-small { background-position: 0 -772px; } +.ui-state-default .btn-icon-chain-unchain, .ui-widget-content .btn-icon-chain-unchain { background-position: 0 -484px; } +.ui-state-default .btn-icon-chain, .ui-widget-content .btn-icon-chain { background-position: 0 -756px; } +.ui-state-default .btn-icon-cross-circle, .ui-widget-content .btn-icon-cross-circle { background-position: 0 -452px; } +.ui-state-default .btn-icon-cross-circle_disabled, .ui-widget-content .btn-icon-cross-circle_disabled { background-position: 0 -564px; } +.ui-state-default .btn-icon-cross, .ui-widget-content .btn-icon-cross { background-position: 0 -276px; } +.ui-state-default .btn-icon-decline, .ui-widget-content .btn-icon-decline { background-position: 0 -128px; } +.ui-state-default .btn-icon-decline_disabled, .ui-widget-content .btn-icon-decline_disabled { background-position: 0 -192px; } +.ui-state-default .btn-icon-delete, .ui-widget-content .btn-icon-delete { background-position: 0 -468px; } +.ui-state-default .btn-icon-deleteLight, .ui-widget-content .btn-icon-deleteLight { background-position: 0 -307px; } +.ui-state-default .btn-icon-disk, .ui-widget-content .btn-icon-disk { background-position: 0 -291px; } +.ui-state-default .btn-icon-document--pencil, .ui-widget-content .btn-icon-document--pencil { background-position: 0 -548px; } +.ui-state-default .btn-icon-download-csv, .ui-widget-content .btn-icon-download-csv { background-position: 0 -48px; } +.ui-state-default .btn-icon-drive-upload, .ui-widget-content .btn-icon-drive-upload { background-position: 0 -420px; } +.ui-state-default .btn-icon-drive-upload_disabled, .ui-widget-content .btn-icon-drive-upload_disabled { background-position: 0 -580px; } +.ui-state-default .btn-icon-grid_print, .ui-widget-content .btn-icon-grid_print { background-position: 0 -260px; } +.ui-state-default .btn-icon-magnifier, .ui-widget-content .btn-icon-magnifier { background-position: 0 -532px; } +.ui-state-default .btn-icon-minus-circle, .ui-widget-content .btn-icon-minus-circle { background-position: 0 -628px; } +.ui-state-default .btn-icon-minus-circle_disabled, .ui-widget-content .btn-icon-minus-circle_disabled { background-position: 0 -644px; } +.ui-state-default .btn-icon-navigation, .ui-widget-content .btn-icon-navigation { background-position: 0 -388px; } +.ui-state-default .btn-icon-navigation_disabled, .ui-widget-content .btn-icon-navigation_disabled { background-position: 0 -436px; } +.ui-state-default .btn-icon-network-cloud, .ui-widget-content .btn-icon-network-cloud { background-position: 0 -612px; } +.ui-state-default .btn-icon-network-cloud_disabled, .ui-widget-content .btn-icon-network-cloud_disabled { background-position: 0 -692px; } +.ui-state-default .btn-icon-pencil, .ui-widget-content .btn-icon-pencil { background-position: 0 -228px; } +.ui-state-default .btn-icon-pencil_disabled, .ui-widget-content .btn-icon-pencil_disabled { background-position: 0 -596px; } +.ui-state-default .btn-icon-plug-disconnect-prohibition, .ui-widget-content .btn-icon-plug-disconnect-prohibition { background-position: 0 -244px; } +.ui-state-default .btn-icon-plug-disconnect-prohibition_disabled, .ui-widget-content .btn-icon-plug-disconnect-prohibition_disabled { background-position: 0 -660px; } +.ui-state-default .btn-icon-preview, .ui-widget-content .btn-icon-preview { background-position: 0 -64px; } +.ui-state-default .btn-icon-preview_disabled, .ui-widget-content .btn-icon-preview_disabled { background-position: 0 -160px; } +.ui-state-default .btn-icon-settings, .ui-widget-content .btn-icon-settings { background-position: 0 -324px; } +.ui-state-default .btn-icon-settings_disabled, .ui-widget-content .btn-icon-settings_disabled { background-position: 0 -404px; } +.ui-state-default .btn-icon-unpublish, .ui-widget-content .btn-icon-unpublish { background-position: 0 -112px; } +.ui-state-default .btn-icon-unpublish_disabled, .ui-widget-content .btn-icon-unpublish_disabled { background-position: 0 -176px; } + +.icon { text-indent: -9999px; border: none; outline: none; } +.icon.icon-24 { width: 24px; height: 24px; background: url('../images/menu-icons/24x24-sedfac01ed1.png'); } +.icon.icon-24.icon-assetadmin { background-position: 0 -120px; } +.icon.icon-24.icon-cmsmain { background-position: 0 -48px; } +.icon.icon-24.icon-cmspagescontroller { background-position: 0 -192px; } +.icon.icon-24.icon-cmssettingscontroller { background-position: 0 0; } +.icon.icon-24.icon-securityadmin { background-position: 0 -24px; } +.icon.icon-24.icon-reportadmin { background-position: 0 -72px; } +.icon.icon-24.icon-commentadmin { background-position: 0 -168px; } +.icon.icon-24.icon-help { background-position: 0 -96px; } +.icon.icon-16 { width: 16px; height: 16px; background: url('../images/menu-icons/16x16-sb173d358c2.png'); } +.icon.icon-16.icon-assetadmin { background-position: 0 -80px; } +.icon.icon-16.icon-cmsmain { background-position: 0 -16px; } +.icon.icon-16.icon-cmspagescontroller { background-position: 0 -112px; } +.icon.icon-16.icon-cmssettingscontroller { background-position: 0 0; } +.icon.icon-16.icon-securityadmin { background-position: 0 -48px; } +.icon.icon-16.icon-reportadmin { background-position: 0 -32px; } +.icon.icon-16.icon-commentadmin { background-position: 0 -128px; } +.icon.icon-16.icon-help { background-position: 0 -64px; } + html { overflow: hidden; } .field input.text, .field textarea, .field .TreeDropdownField { width: 94%; } @@ -116,5 +189,10 @@ table.ss-gridfield-table tr.ss-gridfield-item.even { background: #F0F4F7; } .cms .Actions > .cms-preview-toggle-link { display: block; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset a.ui-tabs-anchor { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1189px no-repeat; padding-left: 20px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset a.ui-tabs-anchor:hover { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-state-active a.ui-tabs-anchor { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-state-active a.ui-tabs-anchor:hover { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; } + .cms-panel-content-collapsed { position: relative; width: 40px; } .cms-panel-content-collapsed h2.cms-panel-header, .cms-panel-content-collapsed h3.cms-panel-header { zoom: 1; position: absolute; top: 10px; right: 10px; writing-mode: tb-rl; float: right; z-index: 5000; } diff --git a/admin/css/screen.css b/admin/css/screen.css index bcb41826a..4fdc710d0 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -196,10 +196,10 @@ form.small .field input.text, form.small .field textarea, form.small .field sele .field.remove-splitter { border-bottom: none; box-shadow: none; } /** ---------------------------------------------------- Buttons ---------------------------------------------------- */ -.cms .button-no-style button, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button { background: none; border: none; display: block; margin: 0; outline: none; color: #0073c1; font-weight: normal; width: 210px; /* same as width of surrounding panel */ text-align: left; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; text-shadow: none; margin-left: -10px; } -.cms .button-no-style button.ss-ui-action-destructive, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-action-destructive { color: #c22730; } -.cms .button-no-style button span, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button span { padding-left: 0; padding-right: 0; } -.cms .button-no-style button:hover, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:hover, .cms .button-no-style button:focus, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:focus, .cms .button-no-style button:active, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:active { outline: none; background: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; border: none; } +.cms .button-no-style button, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button { background: none; border: none; display: block; margin: 0; outline: none; color: #0073c1; font-weight: normal; width: 210px; /* same as width of surrounding panel */ text-align: left; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; text-shadow: none; margin-left: -10px; } +.cms .button-no-style button.ss-ui-action-destructive, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-action-destructive { color: #c22730; } +.cms .button-no-style button span, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button span { padding-left: 0; padding-right: 0; } +.cms .button-no-style button:hover, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:hover, .cms .button-no-style button:focus, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:focus, .cms .button-no-style button:active, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:active { outline: none; background: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; border: none; } .cms .Actions > *, .cms .cms-actions-row > * { display: block; float: left; margin-right: 8px; } .cms .Actions > *:last-child, .cms .cms-actions-row > *:last-child { margin-right: 0; } .cms .Actions { min-height: 30px; overflow: auto; padding: 8px 12px; } @@ -946,12 +946,12 @@ use case of tabs, so the default tab styling should not apply **********************************************/ .cms { /********************** -Styles for pop-up tabs in bottom panel +Styles for edit page action menus ************************/ /* Styles for the cms-actions in tree view, to use more limited space. Title hidden in tree view, until hover/active state added. Active is applied to the first tab within the template, so there should always be one title visible. Added and removed with js in TabSet.js */ } -.cms .ss-ui-action-tabset { position: relative; float: left; /*Style the "tabs" navigation for multiple tabs*/ /* Style the tab panels */ } +.cms .ss-ui-action-tabset { position: relative; float: left; /*Style the "tabs" navigation for action tabs (as in the sitetree batch actions)*/ /* Style the tab panels */ } .cms .ss-ui-action-tabset ul.ui-tabs-nav { overflow: hidden; *zoom: 1; padding: 0; overflow: visible; float: left; height: 28px; border: 1px solid #b3b3b3; -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; } .cms .ss-ui-action-tabset ul.ui-tabs-nav:focus, .cms .ss-ui-action-tabset ul.ui-tabs-nav:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } .cms .ss-ui-action-tabset ul.ui-tabs-nav li { width: 110px; overflow: visible; background: #eaeaea; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y4ZjhmOCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f8f8f8), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -moz-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -o-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: linear-gradient(top, #f8f8f8, #d9d9d9); border-radius: none; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; border: none; border-right: 1px solid #eee; border-left: 1px solid #b3b3b3; margin: 0; } @@ -969,32 +969,32 @@ visible. Added and removed with js in TabSet.js */ } .cms .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.last { -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; } .cms .ss-ui-action-tabset .batch-check, .cms .ss-ui-action-tabset .ui-icon { /* position a checkbox & icon within a tab */ display: inline-block; float: left; margin-left: -2px; padding-right: 6px; } .cms .ss-ui-action-tabset .batch-check { margin: 6px 0px 5px 9px; position: absolute; } -.cms .ss-ui-action-tabset.single ul.ui-tabs-nav { background: none; border: none; display: inline; padding: 0; float: left; } -.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li { display: inline; background: none; border: none; padding: 0; border-bottom: none !important; } -.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li:hover, .cms .ss-ui-action-tabset.single ul.ui-tabs-nav li:focus, .cms .ss-ui-action-tabset.single ul.ui-tabs-nav li:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } -.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a { color: #0073c1; text-shadow: white 0 1px 1px; padding: 0 0 0 10px; line-height: 24px; } -.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a:hover, .cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a:focus, .cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } -.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a:hover { text-shadow: white 0 10px 10px; color: #005b98; } -.cms .ss-ui-action-tabset.single ul.ui-tabs-nav li a:hover:after { border-bottom: 4px solid #005b98; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel { display: block; clear: both; background: #f8f8f8 !important; position: absolute; top: 30px; border: 1px solid #b3b3b3; border-top: none; width: 202px; z-index: 1; padding: 10px; padding-top: 15px; margin: 0; float: left; } +.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav { background: none; border: none; display: inline; padding: 0; float: left; } +.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li { display: inline; background: none; border: none; padding: 0; border-bottom: none !important; } +.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li:hover, .cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li:focus, .cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } +.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a { color: #0073c1; text-shadow: white 0 1px 1px; padding: 0 0 0 10px; line-height: 24px; } +.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a:hover, .cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a:focus, .cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } +.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a:hover { text-shadow: white 0 10px 10px; color: #005b98; } +.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a:hover:after { border-bottom: 4px solid #005b98; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel { /* Restyle for smaller area*/ background: #f8f8f8 !important; border: 1px solid #b3b3b3; border-top: none; clear: both; display: block; float: left; margin: 0; padding: 10px; padding-top: 15px; position: absolute; top: 30px; width: 202px; z-index: 1; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h3, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h4, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h3 { font-size: 13px; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .ui-widget-content { background: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field { /* Fields are more compressed in the sidebar compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field label { float: none; width: auto; font-size: 11px; padding: 0 8px 4px 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field { /* Fields are more compressed in some areas compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field label { float: none; width: auto; font-size: 12px; padding: 0 8px 4px 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field .middleColumn { margin: 0; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field input.text, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field select, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field.checkbox { padding: 0 8px 0; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field.checkbox input { margin: 2px 0; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field label { font-size: 12px; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-content-fields { overflow: visible; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .chzn-container-single { width: 100% !important; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-content-actions, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field { border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-edit-form { width: 100%; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .parent-mode { padding-top: 0; } @@ -1003,45 +1003,58 @@ visible. Added and removed with js in TabSet.js */ } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel #PageType ul { padding: 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel #PageType ul li { padding: 4px 5px; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel.first { left: 0; width: 203px; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .ui-icon { padding-right: 0; } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .tab-nav-link, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .ss-ui-button { font-size: 12px; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel #PageType ul { padding: 0; } +.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel #PageType ul li { padding: 4px 5px; } .cms .ss-ui-action-tabset .last .ss-ui-action-tab { right: -1px; left: auto; } -.cms .south .Actions { overflow: visible; } -.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav { margin: 0; } -.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor { font-weight: normal; font-size: 13px; line-height: 24px; padding-right: 25px; } -.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1189px no-repeat; width: 16px; height: 16px; content: ""; display: inline-block; margin-left: 6px; border-bottom: 0; } -.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:hover:after { border-bottom: 0; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; } -.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; } -.cms .south .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel { overflow: hidden; *zoom: 1; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; background-color: #eceff1; border: 1px solid #ccc; border-bottom: 1px solid #eceff1; clear: both; display: block; position: absolute; top: -204px; width: 190px; /* same width as buttons within panel */ z-index: 1; padding: 10px; margin: 0; margin-top: 1px; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h3, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h4, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h3 { font-size: 13px; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .ui-widget-content { background: none; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field { /* Fields are more compressed in the sidebar compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field label { float: none; width: auto; font-size: 11px; padding: 0 8px 4px 0; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field .middleColumn { margin: 0; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field input.text, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field select, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field.checkbox { padding: 0 8px 0; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field.checkbox input { margin: 2px 0; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:hover, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:focus, .cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:active { /*text-decoration:underline;*/ background-color: #e0e5e8; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information { border-bottom: 1px solid #d0d3d5; margin-bottom: 8px; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information p.meta-info { color: #999; font-size: 11px; line-height: 16px; margin-bottom: 8px; } -.cms .south .Actions .rise-up.ss-ui-action-tabset .last .ui-tabs-panel.ss-ui-action-tab { right: -1px; left: auto; } +.cms .cms-content-actions .Actions { overflow: visible; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav { margin: 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor { font-weight: normal; font-size: 13px; line-height: 24px; padding-right: 25px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1189px no-repeat; width: 16px; height: 16px; content: ""; display: inline-block; margin-left: 6px; border-bottom: 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:hover:after { border-bottom: 0; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel { overflow: hidden; *zoom: 1; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; /* Restyle for smaller area*/ background-color: #eceff1; border: 1px solid #ccc; border-bottom: 1px solid #eceff1; clear: both; display: block; position: absolute; top: -204px; width: 190px; /* same width as buttons within panel */ z-index: 1; padding: 10px; margin: 0; margin-top: 1px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h3, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h4, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h3 { font-size: 13px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .ui-widget-content { background: none; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field { /* Fields are more compressed in some areas compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field label { float: none; width: auto; font-size: 12px; padding: 0 8px 4px 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field .middleColumn { margin: 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field input.text, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field select, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field.checkbox { padding: 0 8px 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field.checkbox input { margin: 2px 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-content-fields { overflow: visible; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .chzn-container-single { width: 100% !important; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-content-actions, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-edit-form { width: 100%; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .parent-mode { padding-top: 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .treedropdown, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field { margin: 10px 0 0 0; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-title, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-title { position: absolute; z-index: 2; padding: 5px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:hover, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:focus, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:active { /*text-decoration:underline;*/ background-color: #e0e5e8; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information { border-bottom: 1px solid #d0d3d5; margin-bottom: 8px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information p.meta-info { color: #999; font-size: 11px; line-height: 16px; margin-bottom: 8px; } +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .last .ui-tabs-panel.ss-ui-action-tab { right: -1px; left: auto; } .cms .cms-tree-view-sidebar { min-width: 176px; /* for when the scrollbar is present & find dropdown open */ } -.cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi ul.ui-tabs-nav > li { width: auto; } -.cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi ul.ui-tabs-nav > li a.tab-nav-link { width: 30px; overflow: hidden; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-right: 0; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; } -.cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi ul.ui-tabs-nav > li a.tab-nav-link.active { width: 110px; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; } -.cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open ul.ui-tabs-nav, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open ul.ui-tabs-nav li.first, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open ul.ui-tabs-nav li.last, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open-last ul.ui-tabs-nav, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open-last ul.ui-tabs-nav li.first, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.ss-tabset.multi.tabset-open-last ul.ui-tabs-nav li.last { -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; } +.cms .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li { width: auto; } +.cms .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li a.tab-nav-link { width: 30px; overflow: hidden; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-right: 0; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; } +.cms .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li a.tab-nav-link.active { width: 110px; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; } +.cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.first, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.last, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.first, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.last { -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; } .cms .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab { width: 162px; padding: 10px 6px; } .cms .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab .field { max-width: 160px; } .cms .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab .ui-icon { padding-right: 0; } diff --git a/admin/scss/_actionTabs.scss b/admin/scss/_actionTabs.scss index 592327527..804589aaf 100644 --- a/admin/scss/_actionTabs.scss +++ b/admin/scss/_actionTabs.scss @@ -15,7 +15,7 @@ $border: 1px solid darken(#D9D9D9, 15%); position:relative; float:left; - /*Style the "tabs" navigation for multiple tabs*/ + /*Style the "tabs" navigation for action tabs (as in the sitetree batch actions)*/ ul.ui-tabs-nav{ @include clearfix; padding:0; @@ -115,7 +115,7 @@ $border: 1px solid darken(#D9D9D9, 15%); margin: 6px 0px 5px 9px; position: absolute; } - &.single{ + &.action-menus{ //Styles for actions-menu implementation ul.ui-tabs-nav{ background:none; border:none; @@ -127,7 +127,7 @@ $border: 1px solid darken(#D9D9D9, 15%); background:none; border:none; padding:0; - border-bottom:none !important; //jquery-ui style has important on it + border-bottom:none !important; //over-ride jquery-ui style (which also has important) &:hover, &:focus, &:active{ @include box-shadow(none); @@ -157,119 +157,20 @@ $border: 1px solid darken(#D9D9D9, 15%); /* Style the tab panels */ .ss-ui-action-tab.ui-tabs-panel{ - display:block; - clear:both; - background:#f8f8f8 !important; //Because ie7 doesn't understanding what the 'C' in CSS stands for - position:absolute; - top:30px; + @include tightSpacing; + background:#f8f8f8 !important; //Because ie7 doesn't understand what the 'C' in CSS stands for border:$border; border-top:none; - width:202px; - z-index:1; + clear:both; + display:block; + float:left; + margin:0; padding:10px; padding-top:15px; - @include tightSpacing; - margin:0; - float:left; - .field label{ - font-size:12px; - } - .cms-content-fields{ - overflow:visible; - } - .chzn-container-single{ - width:100% !important; - .chzn-single{ - padding: 0 0 0 5px; - float:none; - } - } - .cms-content-actions, .cms-preview-controls{ - padding:0; - height:auto; - border:none; - @include box-shadow(none); - - } - .field{ - border-bottom:none; - @include box-shadow(none); - } - .cms-edit-form{ - width:100%; - } - .CompositeField{ - margin:0; - padding:0; - float:none; - } - .parent-mode{ - padding-top:0; - } - - .treedropdown, .SelectionGroup li.selected div.field{ - margin:10px 0 0 0; - //@include box-shadow(inset 0 1px 0 #fff, 0 1px 1px rgba(0,0,0,0.1)); - .treedropdownfield-title{ - position:absolute; - z-index:2; - padding:5px; - } - .treedropdownfield-panel{ - margin-top:11px; - } - .treedropdownfield-toggle-panel-link{ - background:none; - border-left:none; - padding:5px 3px; - .ui-icon{ - float:right; - opacity:0.7; - } - } - } - #PageType ul{ - padding:0; - li{ - padding:4px 5px; - } - } - .cms-add-form ul.SelectionGroup{ - padding-left:0; - padding-right:0; - overflow:visible; - border-bottom:none; - } - label.extra-details{ - overflow:hidden; - margin-top:10px; - display: block; - color: lighten($color-text, 35%); - font-style:italic; - font-weight:normal; - font-size:1em; - float:left; - @include text-shadow(none); - &.fill{ - &:before{ - color:#fff; - content: '?'; - font-size:12px; - @include box-sizing('border-box'); - padding-left:3px; - padding-right:3px; - display:block; - float:left; - @include text-shadow(none); - @include border-radius(50px); - background-color:lighten($color-text, 45%); - width:15px; - height:15px; - margin-right:5px; - margin-bottom:5px; - } - } - } + position:absolute; + top:30px; + width:202px; + z-index:1; &.first { left: 0; width: 203px; @@ -279,7 +180,13 @@ $border: 1px solid darken(#D9D9D9, 15%); } .tab-nav-link, .ss-ui-button { font-size: 12px; - } + } + #PageType ul{ + padding:0; + li{ + padding:4px 5px; + } + } } .last .ss-ui-action-tab{ right:-1px; @@ -288,10 +195,10 @@ $border: 1px solid darken(#D9D9D9, 15%); } /********************** - Styles for pop-up tabs in bottom panel + Styles for edit page action menus ************************/ - .south .Actions{ - overflow:visible; //put this somewhere else/more generic + .cms-content-actions .Actions{ + overflow:visible; //for testing (changed in another branch) .rise-up.ss-ui-action-tabset{ ul.ui-tabs-nav { @@ -384,7 +291,7 @@ $border: 1px solid darken(#D9D9D9, 15%); visible. Added and removed with js in TabSet.js */ .cms-tree-view-sidebar{ min-width: 176px; /* for when the scrollbar is present & find dropdown open */ - .ss-ui-action-tabset.ss-tabset.multi{ + .ss-ui-action-tabset{ ul.ui-tabs-nav{ >li{ width: auto; diff --git a/admin/scss/_mixins.scss b/admin/scss/_mixins.scss index 3fd97769d..a18ff7553 100644 --- a/admin/scss/_mixins.scss +++ b/admin/scss/_mixins.scss @@ -141,16 +141,48 @@ Used in side panels and action tabs .field { /* - * Fields are more compressed in the sidebar compared to the + * Fields are more compressed in some areas compared to the * main content editing window so the below alters the internal * spacing of the fields so we can move that spacing to between * the form fields rather than padding */ + border-bottom:none; + @include box-shadow(none); label { float: none; width: auto; - font-size: 11px; + font-size: 12px; padding: 0 $grid-x 4px 0; + &.extra-details{ + overflow:hidden; + margin-top:10px; + display: block; + color: lighten($color-text, 35%); + font-style:italic; + font-weight:normal; + font-size:1em; + float:left; + @include text-shadow(none); + &.fill{ + &:before{ + color:#fff; + content: '?'; + font-size:12px; + @include box-sizing('border-box'); + padding-left:3px; + padding-right:3px; + display:block; + float:left; + @include text-shadow(none); + @include border-radius(50px); + background-color:lighten($color-text, 45%); + width:15px; + height:15px; + margin-right:5px; + margin-bottom:5px; + } + } + } } .middleColumn { @@ -183,4 +215,60 @@ Used in side panels and action tabs } } } + + /* Restyle for smaller area*/ + .cms-content-fields{ + overflow:visible; + } + .chzn-container-single{ + width:100% !important; + .chzn-single{ + padding: 0 0 0 5px; + float:none; + } + } + .cms-content-actions, .cms-preview-controls{ + padding:0; + height:auto; + border:none; + @include box-shadow(none); + } + .cms-edit-form{ + width:100%; + } + .CompositeField{ + margin:0; + padding:0; + float:none; + } + .parent-mode{ + padding-top:0; + } + .treedropdown, .SelectionGroup li.selected div.field{ + margin:10px 0 0 0; + //@include box-shadow(inset 0 1px 0 #fff, 0 1px 1px rgba(0,0,0,0.1)); + .treedropdownfield-title{ + position:absolute; + z-index:2; + padding:5px; + } + .treedropdownfield-panel{ + margin-top:11px; + } + .treedropdownfield-toggle-panel-link{ + background:none; + border-left:none; + padding:5px 3px; + .ui-icon{ + float:right; + opacity:0.7; + } + } + } + .cms-add-form ul.SelectionGroup{ + padding-left:0; + padding-right:0; + overflow:visible; + border-bottom:none; + } } diff --git a/admin/scss/ie7.scss b/admin/scss/ie7.scss index 4d84b5b2c..dc83c9617 100644 --- a/admin/scss/ie7.scss +++ b/admin/scss/ie7.scss @@ -1,5 +1,7 @@ @import 'themes/default'; @import 'ieShared'; +@import "compass/utilities/sprites/sprite-img"; +@import "sprites.scss"; html { overflow: hidden; @@ -222,4 +224,22 @@ table.ss-gridfield-table { .cms .Actions > .cms-preview-toggle-link{ display:block; } -@include IEVerticalPanelText; \ No newline at end of file + +//IE7 can't use before and after. Compromise +.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset{ + a.ui-tabs-anchor{ + background: sprite($sprites32, arrow_down_lighter) no-repeat; + padding-left:20px; + &:hover { + background: sprite($sprites32, arrow_down_darker) no-repeat; + } + } + .ui-state-active a.ui-tabs-anchor { + background: sprite($sprites32, arrow_up_lighter) no-repeat; + &:hover { + background: sprite($sprites32, arrow_up_darker) no-repeat; + } + } +} + +@include IEVerticalPanelText; diff --git a/javascript/TabSet.js b/javascript/TabSet.js index 7b0609f52..3adcbf6d0 100644 --- a/javascript/TabSet.js +++ b/javascript/TabSet.js @@ -64,14 +64,33 @@ } }); } - }else if(this.parents('.south')){ + }else if(this.parents('.cms-content-actions')){ + var that = this; + var closeHandler = function(event){ + if (!$(event.target).closest(that).length) { + that.tabs('option', 'active', false); + var frame = $('.cms').find('iframe'); + frame.each(function(index, iframe){ + $(iframe).contents().off('click', closeHandler); + }); + $(document).off('click', closeHandler); + }; + } this.tabs({ beforeActivate:function(event, ui){ var activePanel = ui.newPanel; - var activeTab = ui.newTab; + var activeTab = ui.newTab; + var frame = $('.cms').find('iframe'); if($(activePanel).length > 0){ $(activePanel).attr("style","left: "+activeTab.position().left+"px"); - } + } + $(document).on('click', closeHandler); + //Make sure iframe click also closes tab + if(frame.length > 0){ + frame.each(function(index, iframe){ + $(iframe).contents().on('click', closeHandler); + }); + } } }); } @@ -95,6 +114,7 @@ var trigger = $(this).find('.ui-tabs-nav').outerHeight(); var endOfWindow = ($(window).height() + $(document).scrollTop()) - trigger; var elPos = $(this).find('.ui-tabs-nav').offset().top; + var that = this; if(elPos + elHeight >= endOfWindow && elPos - elHeight > 0){ this.addClass('rise-up'); @@ -103,16 +123,16 @@ activate:function(event, ui){ var activePanel = ui.newPanel; var activeTab = ui.newTab; - if(activeTab.position()!=null){ - var top = -activePanel.outerHeight(); + if(activeTab.position() != null){ + var topPosition = -activePanel.outerHeight(); var containerSouth = activePanel.parents('.south'); if(containerSouth){ - var padding = activeTab.offset().top-containerSouth.offset().top; - top = top-padding; - } - var style = $(activePanel).attr("style"); + //If container is the southern panel, make tab appear from the top of the container + var padding = activeTab.offset().top - containerSouth.offset().top; + topPosition = topPosition-padding; + } - $(activePanel).attr("style", style+"top: "+top+"px;"); + $(activePanel).css('top',topPosition+"px"); } } }); From 618e63952613cd5d7b256718d220a792d5bfd193 Mon Sep 17 00:00:00 2001 From: Naomi Guyer Date: Fri, 30 Nov 2012 13:42:43 +1300 Subject: [PATCH 20/49] Refactor and comment TabSet.js --- admin/css/screen.css | 86 ++++----- admin/scss/_actionTabs.scss | 2 +- javascript/TabSet.js | 361 +++++++++++++++++++++++------------- 3 files changed, 275 insertions(+), 174 deletions(-) diff --git a/admin/css/screen.css b/admin/css/screen.css index 4fdc710d0..eea165dbe 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -196,10 +196,10 @@ form.small .field input.text, form.small .field textarea, form.small .field sele .field.remove-splitter { border-bottom: none; box-shadow: none; } /** ---------------------------------------------------- Buttons ---------------------------------------------------- */ -.cms .button-no-style button, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button { background: none; border: none; display: block; margin: 0; outline: none; color: #0073c1; font-weight: normal; width: 210px; /* same as width of surrounding panel */ text-align: left; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; text-shadow: none; margin-left: -10px; } -.cms .button-no-style button.ss-ui-action-destructive, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-action-destructive { color: #c22730; } -.cms .button-no-style button span, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button span { padding-left: 0; padding-right: 0; } -.cms .button-no-style button:hover, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:hover, .cms .button-no-style button:focus, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:focus, .cms .button-no-style button:active, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button:active { outline: none; background: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; border: none; } +.cms .button-no-style button, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button { background: none; border: none; display: block; margin: 0; outline: none; color: #0073c1; font-weight: normal; width: 210px; /* same as width of surrounding panel */ text-align: left; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; text-shadow: none; margin-left: -10px; } +.cms .button-no-style button.ss-ui-action-destructive, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button.ss-ui-action-destructive { color: #c22730; } +.cms .button-no-style button span, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button span { padding-left: 0; padding-right: 0; } +.cms .button-no-style button:hover, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button:hover, .cms .button-no-style button:focus, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button:focus, .cms .button-no-style button:active, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button:active { outline: none; background: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; border: none; } .cms .Actions > *, .cms .cms-actions-row > * { display: block; float: left; margin-right: 8px; } .cms .Actions > *:last-child, .cms .cms-actions-row > *:last-child { margin-right: 0; } .cms .Actions { min-height: 30px; overflow: auto; padding: 8px 12px; } @@ -1011,45 +1011,45 @@ visible. Added and removed with js in TabSet.js */ } .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel #PageType ul li { padding: 4px 5px; } .cms .ss-ui-action-tabset .last .ss-ui-action-tab { right: -1px; left: auto; } .cms .cms-content-actions .Actions { overflow: visible; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav { margin: 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor { font-weight: normal; font-size: 13px; line-height: 24px; padding-right: 25px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1189px no-repeat; width: 16px; height: 16px; content: ""; display: inline-block; margin-left: 6px; border-bottom: 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:hover:after { border-bottom: 0; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel { overflow: hidden; *zoom: 1; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; /* Restyle for smaller area*/ background-color: #eceff1; border: 1px solid #ccc; border-bottom: 1px solid #eceff1; clear: both; display: block; position: absolute; top: -204px; width: 190px; /* same width as buttons within panel */ z-index: 1; padding: 10px; margin: 0; margin-top: 1px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h3, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h4, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h3 { font-size: 13px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .ui-widget-content { background: none; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field { /* Fields are more compressed in some areas compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field label { float: none; width: auto; font-size: 12px; padding: 0 8px 4px 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field .middleColumn { margin: 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field input.text, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field select, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field.checkbox { padding: 0 8px 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .field.checkbox input { margin: 2px 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-content-fields { overflow: visible; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .chzn-container-single { width: 100% !important; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-content-actions, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-edit-form { width: 100%; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .parent-mode { padding-top: 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .treedropdown, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field { margin: 10px 0 0 0; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-title, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-title { position: absolute; z-index: 2; padding: 5px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:hover, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:focus, .cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:active { /*text-decoration:underline;*/ background-color: #e0e5e8; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information { border-bottom: 1px solid #d0d3d5; margin-bottom: 8px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information p.meta-info { color: #999; font-size: 11px; line-height: 16px; margin-bottom: 8px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .last .ui-tabs-panel.ss-ui-action-tab { right: -1px; left: auto; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav { margin: 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor { font-weight: normal; font-size: 13px; line-height: 24px; padding-right: 25px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1189px no-repeat; width: 16px; height: 16px; content: ""; display: inline-block; margin-left: 6px; border-bottom: 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:hover:after { border-bottom: 0; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel { overflow: hidden; *zoom: 1; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; /* Restyle for smaller area*/ background-color: #eceff1; border: 1px solid #ccc; border-bottom: 1px solid #eceff1; clear: both; display: block; position: absolute; top: -204px; width: 190px; /* same width as buttons within panel */ z-index: 1; padding: 10px; margin: 0; margin-top: 1px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel h3, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel h4, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel h3 { font-size: 13px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .ui-widget-content { background: none; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field { /* Fields are more compressed in some areas compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field label { float: none; width: auto; font-size: 12px; padding: 0 8px 4px 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field .middleColumn { margin: 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field input.text, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field select, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field.checkbox { padding: 0 8px 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field.checkbox input { margin: 2px 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-content-fields { overflow: visible; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .chzn-container-single { width: 100% !important; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-content-actions, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-edit-form { width: 100%; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .parent-mode { padding-top: 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .treedropdown, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field { margin: 10px 0 0 0; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-title, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-title { position: absolute; z-index: 2; padding: 5px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:hover, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:focus, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:active { /*text-decoration:underline;*/ background-color: #e0e5e8; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information { border-bottom: 1px solid #d0d3d5; margin-bottom: 8px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information p.meta-info { color: #999; font-size: 11px; line-height: 16px; margin-bottom: 8px; } +.cms .cms-content-actions .Actions .ss-ui-action-tabset .last .ui-tabs-panel.ss-ui-action-tab { right: -1px; left: auto; } .cms .cms-tree-view-sidebar { min-width: 176px; /* for when the scrollbar is present & find dropdown open */ } .cms .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li { width: auto; } .cms .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li a.tab-nav-link { width: 30px; overflow: hidden; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-right: 0; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; } diff --git a/admin/scss/_actionTabs.scss b/admin/scss/_actionTabs.scss index 804589aaf..d99618267 100644 --- a/admin/scss/_actionTabs.scss +++ b/admin/scss/_actionTabs.scss @@ -200,7 +200,7 @@ $border: 1px solid darken(#D9D9D9, 15%); .cms-content-actions .Actions{ overflow:visible; //for testing (changed in another branch) - .rise-up.ss-ui-action-tabset{ + .ss-ui-action-tabset{ ul.ui-tabs-nav { margin: 0; li { diff --git a/javascript/TabSet.js b/javascript/TabSet.js index 3adcbf6d0..205384a12 100644 --- a/javascript/TabSet.js +++ b/javascript/TabSet.js @@ -1,102 +1,20 @@ (function($){ $.entwine('ss', function($){ /** - * Lightweight wrapper around jQuery UI tabs. + * Lightweight wrapper around jQuery UI tabs for generic tab set-up + * and special rules for two other use cases (ss-ui-action-tabset): + * * Site tree action tabs (to perform actions on the site tree) + * * Actions menu (Edit page actions) */ $('.ss-tabset').entwine({ - /*Custom functionality for special action tabsets*/ - actionTabs: function(){ - this.tabs( - 'option', - 'collapsible', - true - ).tabs('option', 'active', false); - - //Apply special behaviour to the cms actions row - if(this.hasClass('cms-actions-row')){ - - /* If actions panel is within the tree, apply active class - to help animate open/close on hover - Position must be reset else anyone coming from main sitetree - will see broken tabs */ - var container = this.parent().parent(); - if($(container).hasClass('cms-tree-view-sidebar')){ - $('.ui-tabs-nav li').hover(function(){ - $(this).parent().find('li .active').removeClass('active'); - $(this).find('a').addClass('active'); - }); - - this.tabs({ - beforeActivate:function(event, ui){ - var activePanel = ui.newPanel; - $(activePanel).attr("style","left : auto; right: auto"); - $(this).closest('.ss-ui-action-tabset').removeClass('tabset-open').removeClass('tabset-open-last'); - - if($(activePanel).length > 0){ - $(activePanel).parent().addClass('tabset-open'); - } - } - }); - }else{ - /* If the tabs are in the full site tree view, do some - positioning so tabPanel stays with relevent tab */ - this.tabs({ - beforeActivate:function(event, ui){ - var activePanel = ui.newPanel; - var activeTab = ui.newTab; - $(this).closest('.ss-ui-action-tabset').removeClass('tabset-open').removeClass('tabset-open-last'); - if($(activePanel).length > 0){ - if($(activeTab).hasClass("last")){ - $(activePanel).attr("style","left : auto; right: 0px"); - $(activePanel).parent().addClass('tabset-open-last');//last needs to be styled differently when open - }else{ - $(activePanel).attr("style","left: "+activeTab.position().left+"px"); - if($(activeTab).hasClass("first")){ - $(activePanel).attr("style","left: 0px"); - $(activePanel).parent().addClass('tabset-open'); - }else{ - $(activePanel).attr("style","left: "+activeTab.position().left+"px"); - } - } - } - - } - }); - } - }else if(this.parents('.cms-content-actions')){ - var that = this; - var closeHandler = function(event){ - if (!$(event.target).closest(that).length) { - that.tabs('option', 'active', false); - var frame = $('.cms').find('iframe'); - frame.each(function(index, iframe){ - $(iframe).contents().off('click', closeHandler); - }); - $(document).off('click', closeHandler); - }; - } - this.tabs({ - beforeActivate:function(event, ui){ - var activePanel = ui.newPanel; - var activeTab = ui.newTab; - var frame = $('.cms').find('iframe'); - if($(activePanel).length > 0){ - $(activePanel).attr("style","left: "+activeTab.position().left+"px"); - } - $(document).on('click', closeHandler); - //Make sure iframe click also closes tab - if(frame.length > 0){ - frame.each(function(index, iframe){ - $(iframe).contents().on('click', closeHandler); - }); - } - } - }); - } - //Check if tabs should open upwards, and adjust - this.riseUp(); - }, + /****************************************************** + * Lightweight wrapper around jQuery UI tabs: + * * onadd + * * onremove + * * redrawTabs + * * rewriteHashlinks + *******************************************************/ onadd: function() { // Can't name redraw() as it clashes with other CMS entwine classes this.redrawTabs(); @@ -106,47 +24,11 @@ if(this.data('uiTabs')) this.tabs('destroy'); this._super(); }, - riseUp: function(){ - /* Function checks to see if a tab should be opened upwards - (based on space concerns. If true, the rise-up class is applied - and the position is calculated and applied to the element */ - var elHeight = $(this).find('.ui-tabs-panel').outerHeight(); - var trigger = $(this).find('.ui-tabs-nav').outerHeight(); - var endOfWindow = ($(window).height() + $(document).scrollTop()) - trigger; - var elPos = $(this).find('.ui-tabs-nav').offset().top; - var that = this; - if(elPos + elHeight >= endOfWindow && elPos - elHeight > 0){ - this.addClass('rise-up'); - - /* Apply position to tab */ - this.tabs({ - activate:function(event, ui){ - var activePanel = ui.newPanel; - var activeTab = ui.newTab; - if(activeTab.position() != null){ - var topPosition = -activePanel.outerHeight(); - var containerSouth = activePanel.parents('.south'); - if(containerSouth){ - //If container is the southern panel, make tab appear from the top of the container - var padding = activeTab.offset().top - containerSouth.offset().top; - topPosition = topPosition-padding; - } - - $(activePanel).css('top',topPosition+"px"); - } - } - }); - - }else{ - this.removeClass('rise-up'); - } - return false; - }, redrawTabs: function() { this.rewriteHashlinks(); this.tabs(); - //Apply special behaviour to action tabs: closed by default, and collapsible + //Apply special behaviour to ss-ui-action-tabset if(this.hasClass('ss-ui-action-tabset')){ this.actionTabs(); } @@ -164,6 +46,225 @@ if(!matches) return; $(this).attr('href', document.location.href.replace(/#.*/, '') + matches[0]); }); + }, + + /*************************************************** + * Custom functionality for special action tabsets + * * actionTabs + * * siteTreeActions + * * actionsMenu + * * closeTabs + * * riseUp + ***************************************************/ + + /* + Apply generic rules for action tabs (collapsible, rise, and close), + then call specific functions to handle each type of action tab + */ + actionTabs: function(){ + var that = this; + + //Set actionTabs to allow closing and be closed by default + this.tabs( + 'option', + 'collapsible', + true + ).tabs('option', 'active', false); + + + //Call close function on beforeactivate event + this.on( "tabsbeforeactivate", function(event, ui) { + that.closeTabs(event, ui); + }); + + // Call riseUp funciton on befporeactivate to check if tabs should + // open upwards (based on available space) and adjust + this.on( "tabsbeforeactivate", function(event, ui) { + that.riseUp(event, ui); + }); + + // Apply special behaviour depending on whether tabs are + // sitetree actions, or an actionmenu + if(this.parents('.cms-content-actions')){ + this.actionsMenu(); + } else if(this.hasClass('cms-actions-row')){ + this.siteTreeActions(); + } + }, + + /* + * Apply custom rules to the Actions Menu + * Currently includes positioning logic + */ + actionsMenu: function(){ + this.tabs({ + beforeActivate:function(event, ui){ //Set options before tab activated (but after clicked) + var activePanel = ui.newPanel; //panel about to open + var activeTab = ui.newTab; //tab nav item about to become active + + //Set the position of the opening tab (if it exists) + if($(activePanel).length > 0){ + $(activePanel).css('left', activeTab.position().left+"px"); + } + } + }); + }, + + /* + Apply rules to the siteTree actions. These action panels should + recieve different positions and classes depending on whether they are + appearing in the full page site tree view, or in the sidebar + */ + siteTreeActions: function(){ + var that = this; + var container = this.parent().parent(); + + //Remove open classes on beforeactivate + this.on( "tabsbeforeactivate", function(event, ui) { + // Remove tabset open classes (last gets a unique class + // in the bigger sitetree, so remove this too) + $(that).closest('.ss-ui-action-tabset') + .removeClass('tabset-open') + .removeClass('tabset-open-last'); + }); + + /* Apply specific rules if the actions panel appears in the side-bar + * Includes: + * * a hover helper class for animation, + * * reseting positioning of panels + */ + if($(container).hasClass('cms-tree-view-sidebar')){ + /* If actions panel is within the sidebar, apply active class + to help animate open/close on hover */ + $('.ui-tabs-nav li').hover(function(){ + $(this).parent().find('li .active').removeClass('active'); + $(this).find('a').addClass('active'); + }); + + /* Reset position of tabs, else anyone going between the large + and the small sitetree will see broken tabs */ + this.tabs({ + // Note: beforeActivate runs when a tab is clicked, + // but before it is visible. + beforeActivate:function(event, ui){ + var activePanel = ui.newPanel; //the new active panel + + //Apply styles with css, to avoid overriding currently applied styles + $(activePanel).css({'left': 'auto', 'right': 'auto'}); //reset left and right positioning + + if($(activePanel).length > 0){ + $(activePanel).parent().addClass('tabset-open'); + } + } + }); + }else{ + /* If the tabs are in the full site tree view, do some + positioning so tabPanel stays with relevent tab */ + this.tabs({ + beforeActivate:function(event, ui){ + var activePanel = ui.newPanel; + var activeTab = ui.newTab; + + if($(activePanel).length > 0){ + if($(activeTab).hasClass("last")){ + // Align open tab to the right (because opened tab is last) + $(activePanel).css({'left': 'auto', 'right': '0px'}); + + //last needs to be styled differently when open, so apply a unique class + $(activePanel).parent().addClass('tabset-open-last'); + }else{ + //Assign position to tabpanel based on position of relivent activeTab item + $(activePanel).css('left', activeTab.position().left+"px"); + + // If this is the first tab, make sure the position doesn't include border + // (hard set position to 0 ), and add the tab-set open class + if($(activeTab).hasClass("first")){ + $(activePanel).css('left',"0px"); + $(activePanel).parent().addClass('tabset-open'); + } + } + } + } + }); + } + }, + + /* + * Generic function to close open tabs when something other than + * the open tab is clicked. Stores event in a handler, and removes + * the bound event once activated. Used by ss-ui-action-tabset. + * + * Note: Should be called by a tabsbeforeactivate event + */ + closeTabs: function(event, ui){ + var that = this; + var frame = $('.cms').find('iframe'); //get all iframes on the page + + // Create a handler for the click event so we can close tabs + // and easily remove the event once done + var closeHandler = function(event){ + //close open tab + if (!$(event.target).closest(that).length) { + that.tabs('option', 'active', false); // close tabs + + //remove click event from objects it is bound to (iframe's and document) + var frame = $('.cms').find('iframe'); + frame.each(function(index, iframe){ + $(iframe).contents().off('click', closeHandler); + }); + $(document).off('click', closeHandler); + }; + } + + //Bind click event to document, and use closeHandler to handle the event + $(document).on('click', closeHandler); + // Make sure iframe click also closes tab + // iframe needs a special case, else the click event will not register here + if(frame.length > 0){ + frame.each(function(index, iframe){ + $(iframe).contents().on('click', closeHandler); + }); + } + }, + /***************************************************************** + * Function riseUp checks to see if a tab should be opened upwards + * (based on space concerns). If true, the rise-up class is applied + * and a new position is calculated and applied to the element. + * + * Note: Should be called by a tabsbeforeactivate event + ******************************************************************/ + riseUp: function(event, ui){ + + // Get the numbers needed to calculate positions + var elHeight = $(this).find('.ui-tabs-panel').outerHeight(); + var trigger = $(this).find('.ui-tabs-nav').outerHeight(); + var endOfWindow = ($(window).height() + $(document).scrollTop()) - trigger; + var elPos = $(this).find('.ui-tabs-nav').offset().top; + + var activePanel = ui.newPanel; + var activeTab = ui.newTab; + + if (elPos + elHeight >= endOfWindow && elPos - elHeight > 0){ + this.addClass('rise-up'); + + if (activeTab.position() != null){ + var topPosition = -activePanel.outerHeight(); + var containerSouth = activePanel.parents('.south'); + if (containerSouth){ + //If container is the southern panel, make tab appear from the top of the container + var padding = activeTab.offset().top - containerSouth.offset().top; + topPosition = topPosition-padding; + } + $(activePanel).css('top',topPosition+"px"); + } + } else { + //else remove the rise-up class and set top to 0 + this.removeClass('rise-up'); + if (activeTab.position() != null){ + $(activePanel).css('top','0px'); + } + } + return false; } }); }); From ee797e4a48511b3c4c534e612d11ea6028932d10 Mon Sep 17 00:00:00 2001 From: Naomi Guyer Date: Fri, 30 Nov 2012 15:29:53 +1300 Subject: [PATCH 21/49] More CSS fixes for the ActionTabSets. Thanks for contributing @clarkepaul. --- admin/css/ie7.css | 42 +++- admin/css/screen.css | 243 +++++++++--------- admin/scss/_actionTabs.scss | 478 ++++++++++++++++++------------------ admin/scss/_forms.scss | 34 ++- admin/scss/ie7.scss | 128 ++++++++-- 5 files changed, 520 insertions(+), 405 deletions(-) diff --git a/admin/css/ie7.css b/admin/css/ie7.css index f82b5b2a3..375915673 100644 --- a/admin/css/ie7.css +++ b/admin/css/ie7.css @@ -127,6 +127,21 @@ fieldset.switch-states .switch .slide-button { display: none; } html { overflow: hidden; } +.cms-content-toolbar { padding-bottom: 5px; } + +.cms-menu-list li { list-style-type: none; width: 100%; float: left; margin: 0px; padding: 0px; } + +/* Site tree +------------------------- */ +.cms-tree-view-modes div { float: left; } +.cms-tree-view-modes span { float: left; padding-top: 5px; } + +.cms-panel-content .cms-tree li { width: 200px; overflow: hidden; float: left; display: inline; } + +.jstree li a .ui-icon { text-indent: 0px !important; } + +/* Forms and files area +-----------------------------*/ .field input.text, .field textarea, .field .TreeDropdownField { width: 94%; } select { padding: 10px 0; height: 30px; } @@ -149,6 +164,8 @@ select { padding: 10px 0; height: 30px; } .jstree li a .ui-icon { text-indent: 0px !important; } +/* Gridfield +------------------------- */ .cms table.ss-gridfield-table tbody td { width: auto; } .cms table.ss-gridfield-table tr th.extra span input { height: 23px; } @@ -173,12 +190,14 @@ table.ss-gridfield-table tr.title th h2 { float: left; } table.ss-gridfield-table tr.ss-gridfield-item.odd { background: white; } table.ss-gridfield-table tr.ss-gridfield-item.even { background: #F0F4F7; } -.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form { overflow: hidden; } -.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form input { width: 160px; } - .cms .ss-gridfield table.ss-gridfield-table tbody td.col-listChildrenLink { width: 16px; } .cms .ss-gridfield table.ss-gridfield-table tbody td.col-listChildrenLink .list-children-link { background: transparent url(../images/sitetree_ss_default_icons.png) no-repeat 4px -4px; display: block; } +.ss-ui-button.ss-gridfield-button-filter { border: none !important; } + +.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form { overflow: hidden; } +.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content .cms-search-form input { width: 160px; } + .cms-content-header h2 { float: left; } .cms-content-header h2 .section-icon { display: none; } .cms-content-header .cms-content-header-tabs { position: absolute; right: 0; } @@ -189,10 +208,17 @@ table.ss-gridfield-table tr.ss-gridfield-item.even { background: #F0F4F7; } .cms .Actions > .cms-preview-toggle-link { display: block; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset a.ui-tabs-anchor { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1189px no-repeat; padding-left: 20px; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset a.ui-tabs-anchor:hover { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-state-active a.ui-tabs-anchor { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; } -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset .ui-state-active a.ui-tabs-anchor:hover { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; } - .cms-panel-content-collapsed { position: relative; width: 40px; } .cms-panel-content-collapsed h2.cms-panel-header, .cms-panel-content-collapsed h3.cms-panel-header { zoom: 1; position: absolute; top: 10px; right: 10px; writing-mode: tb-rl; float: right; z-index: 5000; } + +.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset { width: 190px; } +.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset ul.ui-tabs-nav a.ui-tabs-anchor { background: transparent url(../images/sprites-32x32/arrow_down_lighter.png) no-repeat right top; } +.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset ul.ui-tabs-nav a.ui-tabs-anchor:hover { background: transparent url(../images/sprites-32x32/arrow_down_darker.png) no-repeat right top; } +.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset ul.ui-tabs-nav .ui-state-active a.ui-tabs-anchor { background: transparent url(../images/sprites-32x32/arrow_up_lighter.png) no-repeat right top; } +.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset ul.ui-tabs-nav .ui-state-active a.ui-tabs-anchor:hover { background: transparent url(../images/sprites-32x32/arrow_up_darker.png) no-repeat right top; } +.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button { width: 190px; /* Width 100% not calculating by ie7 */ } + +/* Tempory fix as jquery loads too slow to add icons */ +button.ui-button-text-icon-primary { padding-left: 30px !important; } +button.ui-button-text-icon-primary span.ui-button-icon-primary { position: absolute !important; top: 5px !important; left: 8px !important; } +button.ui-button-text-icon-primary .ui-button-text { margin-left: 0 !important; } diff --git a/admin/css/screen.css b/admin/css/screen.css index eea165dbe..ded878d9d 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -196,10 +196,11 @@ form.small .field input.text, form.small .field textarea, form.small .field sele .field.remove-splitter { border-bottom: none; box-shadow: none; } /** ---------------------------------------------------- Buttons ---------------------------------------------------- */ -.cms .button-no-style button, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button { background: none; border: none; display: block; margin: 0; outline: none; color: #0073c1; font-weight: normal; width: 210px; /* same as width of surrounding panel */ text-align: left; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; text-shadow: none; margin-left: -10px; } -.cms .button-no-style button.ss-ui-action-destructive, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button.ss-ui-action-destructive { color: #c22730; } -.cms .button-no-style button span, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button span { padding-left: 0; padding-right: 0; } -.cms .button-no-style button:hover, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button:hover, .cms .button-no-style button:focus, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button:focus, .cms .button-no-style button:active, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button:active { outline: none; background: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; border: none; } +.cms { /* Tempory fix: Overide default icon & text styles as the order of the icon and text are mixed up */ } +.cms .button-no-style button, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button { -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: none; border: none; color: #0073c1; display: block; font-weight: normal; margin: 0; outline: none; padding-left: 10px; padding-right: 10px; text-align: left; text-shadow: none; white-space: normal; } +.cms .button-no-style button.ss-ui-action-destructive, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-action-destructive { color: #c22730; } +.cms .button-no-style button span, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button span { padding-left: 0; padding-right: 0; } +.cms .button-no-style button:hover, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button:hover, .cms .button-no-style button:focus, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button:focus, .cms .button-no-style button:active, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; background: none; border: none; } .cms .Actions > *, .cms .cms-actions-row > * { display: block; float: left; margin-right: 8px; } .cms .Actions > *:last-child, .cms .cms-actions-row > *:last-child { margin-right: 0; } .cms .Actions { min-height: 30px; overflow: auto; padding: 8px 12px; } @@ -225,6 +226,8 @@ form.small .field input.text, form.small .field textarea, form.small .field sele .cms .ss-ui-buttonset .ui-button { margin-left: -1px; } .cms .ss-ui-buttonset { margin-left: 1px; } .cms .ss-ui-loading-icon { background: url(../../images/network-save.gif) no-repeat; display: block; width: 16px; height: 16px; } +.cms .south .Actions .ss-ui-button.ss-ui-action-constructive .ui-button-text-alternate { margin-left: 22px; } +.cms .south .Actions .ss-ui-button.ss-ui-action-constructive .ui-icon { position: absolute; left: 10px; top: 5px; } /** ---------------------------------------------------- Grouped form fields ---------------------------------------------------- */ .fieldgroup .fieldgroup-field { float: left; display: block; padding: 8px 0 0 8px; } @@ -938,127 +941,131 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; } .cms-preview.desktop .preview-scroll .preview-device-outer { -webkit-transition: all 0.3s ease-out; -webkit-transition-delay: 1s; -moz-transition: all 0.3s ease-out 1s; -o-transition: all 0.3s ease-out 1s; transition: all 0.3s ease-out 1s; height: 800px; margin: 0 auto; width: 1024px; } /******************************************** - -Defines the styles for the action tabset, found on the site tree, -and as a single (more options) tab in page view. This is a special -use case of tabs, so the default tab styling should not apply - - +* Defines the styles for .ss-ui-action-tabset: +* * Site tree action tabs (to perform actions on the site tree) +* * Actions menu (Edit page actions) +* +* Reliant on TabSet.js to apply and remove some classes. +* +* Note: This is a special use case of tabs, so the default tab +* styling should not apply +* **********************************************/ -.cms { /********************** -Styles for edit page action menus -************************/ /* Styles for the cms-actions in tree view, to use more limited space. +.cms .ss-ui-action-tabset { float: left; position: relative; /* + Styles for the tab-nav of the site tree implementation + of ss-ui-action-tabset +*/ /* position a checkbox & icon within a tab */ /* Styles for the cms-actions in tree view, to use more limited space. Title hidden in tree view, until hover/active state added. Active is applied to the first tab within the template, so there should always be one title -visible. Added and removed with js in TabSet.js */ } -.cms .ss-ui-action-tabset { position: relative; float: left; /*Style the "tabs" navigation for action tabs (as in the sitetree batch actions)*/ /* Style the tab panels */ } -.cms .ss-ui-action-tabset ul.ui-tabs-nav { overflow: hidden; *zoom: 1; padding: 0; overflow: visible; float: left; height: 28px; border: 1px solid #b3b3b3; -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav:focus, .cms .ss-ui-action-tabset ul.ui-tabs-nav:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li { width: 110px; overflow: visible; background: #eaeaea; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y4ZjhmOCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f8f8f8), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -moz-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -o-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: linear-gradient(top, #f8f8f8, #d9d9d9); border-radius: none; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; border: none; border-right: 1px solid #eee; border-left: 1px solid #b3b3b3; margin: 0; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li:focus, .cms .ss-ui-action-tabset ul.ui-tabs-nav li:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active { background: #f8f8f8; border-bottom: none !important; -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a:focus, .cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a span:focus, .cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a:active, .cms .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a span:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li.first { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; border-left: none; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li.last { -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; border-right: none; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li a.tab-nav-link { color: #444444; font-weight: bold; line-height: 16px; display: inline-block; padding: 5px 10px; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li a.tab-nav-link .ui-no-icon { display: inline-block; float: left; padding: 0 2px; width: 16px; height: 16px; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li a.tab-nav-link .title { display: inline-block; line-height: 18px; } -.cms .ss-ui-action-tabset ul.ui-tabs-nav li a.tab-nav-link.view-mode-batchactions-wrapper .title { margin-left: 22px; } +visible. Added and removed with js in TabSet.js */ /**************************************************************** + Styles for the actions-menu implementation + of ss-ui-action-tabset +****************************************************************/ } +.cms .ss-ui-action-tabset.multi { /* Style the tab panels */ } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav { -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; overflow: hidden; *zoom: 1; border: 1px solid #b3b3b3; float: left; height: 28px; overflow: visible; padding: 0; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li { background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y4ZjhmOCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f8f8f8), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -moz-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -o-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: linear-gradient(top, #f8f8f8, #d9d9d9); -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: #eaeaea; border: none; border-right: 1px solid #eee; border-left: 1px solid #b3b3b3; margin: 0; overflow: visible; width: 110px; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; background: #f8f8f8; border-bottom: none !important; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active a { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active a:active, .cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active a span:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.first { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; border-left: none; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.last { -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; border-right: none; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li a.tab-nav-link { color: #444444; display: inline-block; font-weight: bold; line-height: 16px; padding: 5px 10px; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li a.tab-nav-link .ui-no-icon { display: inline-block; float: left; height: 16px; padding: 0 2px; width: 16px; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li a.tab-nav-link .title { display: inline-block; line-height: 18px; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li a.tab-nav-link.view-mode-batchactions-wrapper .title { margin-left: 22px; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel { /* Restyle for smaller area*/ background: #f8f8f8 !important; border: 1px solid #b3b3b3; border-top: none; clear: both; display: block; float: left; margin: 0; padding: 10px; padding-top: 15px; position: absolute; top: 30px; width: 202px; z-index: 1; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel h3, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel h4, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel h3 { font-size: 13px; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .ui-widget-content { background: none; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field { /* Fields are more compressed in some areas compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field label { float: none; width: auto; font-size: 12px; padding: 0 8px 4px 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field .middleColumn { margin: 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field input.text, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field select, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field.checkbox { padding: 0 8px 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .field.checkbox input { margin: 2px 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .cms-content-fields { overflow: visible; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .chzn-container-single { width: 100% !important; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .cms-content-actions, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .cms-edit-form { width: 100%; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .parent-mode { padding-top: 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .treedropdown, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field { margin: 10px 0 0 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-title, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-title { position: absolute; z-index: 2; padding: 5px; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel.first { left: 0; width: 203px; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .ui-icon { padding-right: 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .tab-nav-link, .cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel .ss-ui-button { font-size: 12px; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel #PageType ul { padding: 0; } +.cms .ss-ui-action-tabset.multi .ss-ui-action-tab.ui-tabs-panel #PageType ul li { padding: 4px 5px; } .cms .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav, .cms .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.first { -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; } .cms .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.last { -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; } -.cms .ss-ui-action-tabset .batch-check, .cms .ss-ui-action-tabset .ui-icon { /* position a checkbox & icon within a tab */ display: inline-block; float: left; margin-left: -2px; padding-right: 6px; } +.cms .ss-ui-action-tabset .batch-check, .cms .ss-ui-action-tabset .ui-icon { display: inline-block; float: left; margin-left: -2px; padding-right: 6px; } .cms .ss-ui-action-tabset .batch-check { margin: 6px 0px 5px 9px; position: absolute; } -.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav { background: none; border: none; display: inline; padding: 0; float: left; } -.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li { display: inline; background: none; border: none; padding: 0; border-bottom: none !important; } -.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li:hover, .cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li:focus, .cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } -.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a { color: #0073c1; text-shadow: white 0 1px 1px; padding: 0 0 0 10px; line-height: 24px; } -.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a:hover, .cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a:focus, .cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } -.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a:hover { text-shadow: white 0 10px 10px; color: #005b98; } -.cms .ss-ui-action-tabset.action-menus ul.ui-tabs-nav li a:hover:after { border-bottom: 4px solid #005b98; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel { /* Restyle for smaller area*/ background: #f8f8f8 !important; border: 1px solid #b3b3b3; border-top: none; clear: both; display: block; float: left; margin: 0; padding: 10px; padding-top: 15px; position: absolute; top: 30px; width: 202px; z-index: 1; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h3, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h4, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h3 { font-size: 13px; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .ui-widget-content { background: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field { /* Fields are more compressed in some areas compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field label { float: none; width: auto; font-size: 12px; padding: 0 8px 4px 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field .middleColumn { margin: 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field input.text, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field select, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field.checkbox { padding: 0 8px 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .field.checkbox input { margin: 2px 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-content-fields { overflow: visible; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .chzn-container-single { width: 100% !important; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-content-actions, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-edit-form { width: 100%; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .parent-mode { padding-top: 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field { margin: 10px 0 0 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-title, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-title { position: absolute; z-index: 2; padding: 5px; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel.first { left: 0; width: 203px; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .ui-icon { padding-right: 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .tab-nav-link, .cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel .ss-ui-button { font-size: 12px; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel #PageType ul { padding: 0; } -.cms .ss-ui-action-tabset .ss-ui-action-tab.ui-tabs-panel #PageType ul li { padding: 4px 5px; } -.cms .ss-ui-action-tabset .last .ss-ui-action-tab { right: -1px; left: auto; } +.cms .ss-ui-action-tabset .cms-tree-view-sidebar { min-width: 176px; /* for when the scrollbar is present & find dropdown open */ } +.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li { width: auto; } +.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li a.tab-nav-link { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; overflow: hidden; padding-right: 0; width: 30px; } +.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li a.tab-nav-link.active { -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; width: 110px; } +.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav, .cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.first, .cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.last, .cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav, .cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.first, .cms .ss-ui-action-tabset .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.last { -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; } +.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab { padding: 10px 6px; width: 162px; } +.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab .field { max-width: 160px; } +.cms .ss-ui-action-tabset .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab .ui-icon { padding-right: 0; } +.cms .ss-ui-action-tabset .cms-tree-view-sidebar .last .ui-tabs-panel.ss-ui-action-tab { left: auto; right: 0; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset { margin-top: 2px; /* Style the panel for actions-menu */ /* Re-align last tab */ } +.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav { margin: 0; float: left; /* needed for ie but doesnt effect other browsers */ } +.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li { background: none; border: none; border-bottom: none !important; display: inline; padding: 0; /* Make arrow point in up when nav open */ } +.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li:hover, .cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a { text-shadow: white 0 1px 1px; color: #0073c1; font-size: 13px; font-weight: normal; line-height: 24px; padding: 0 25px 0 10px; /* Arrow */ } +.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover, .cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover { text-shadow: white 0 10px 10px; color: #005b98; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1189px no-repeat; border-bottom: 0; content: ""; display: inline-block; height: 16px; margin-left: 6px; width: 16px; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li.ui-state-active a:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li.ui-state-active a:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel { overflow: hidden; *zoom: 1; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; /* Restyle for smaller area*/ clear: both; display: block; background-color: #eceff1; border: 1px solid #ccc; border-bottom: 1px solid #eceff1; margin: 0; margin-top: 2px; max-width: 250px; padding: 8px 0 2px; position: absolute; z-index: 1; min-width: 190px; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h3, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h4, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h3 { font-size: 13px; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .ui-widget-content { background: none; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field { /* Fields are more compressed in some areas compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field label { float: none; width: auto; font-size: 12px; padding: 0 8px 4px 0; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field .middleColumn { margin: 0; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field input.text, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field select, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field.checkbox { padding: 0 8px 0; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .field.checkbox input { margin: 2px 0; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-content-fields { overflow: visible; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .chzn-container-single { width: 100% !important; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-content-actions, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-edit-form { width: 100%; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .parent-mode { padding-top: 0; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .treedropdown, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .SelectionGroup li.selected div.field { margin: 10px 0 0 0; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .treedropdown .treedropdownfield-title, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-title { position: absolute; z-index: 2; padding: 5px; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-sitetree-information { border-bottom: 1px solid #e6e7e8; margin-bottom: 8px; padding: 0 20px 0 0; margin-right: 10px; margin-left: 10px; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .cms-sitetree-information p.meta-info { color: #999; font-size: 11px; line-height: 16px; margin-bottom: 8px; white-space: nowrap; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-button { width: 100%; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-button:hover, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-button:focus, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-button:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; background-color: #e0e5e8; outline: none; } +.cms .ss-ui-action-tabset.action-menus.ss-tabset .last .ui-tabs-panel.ss-ui-action-tab { left: auto; right: -1px; } .cms .cms-content-actions .Actions { overflow: visible; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav { margin: 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor { font-weight: normal; font-size: 13px; line-height: 24px; padding-right: 25px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1189px no-repeat; width: 16px; height: 16px; content: ""; display: inline-block; margin-left: 6px; border-bottom: 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav li a.ui-tabs-anchor:hover:after { border-bottom: 0; background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset ul.ui-tabs-nav li.ui-state-active a.ui-tabs-anchor:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel { overflow: hidden; *zoom: 1; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; /* Restyle for smaller area*/ background-color: #eceff1; border: 1px solid #ccc; border-bottom: 1px solid #eceff1; clear: both; display: block; position: absolute; top: -204px; width: 190px; /* same width as buttons within panel */ z-index: 1; padding: 10px; margin: 0; margin-top: 1px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel h3, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel h4, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel h5 { font-weight: bold; line-height: 16px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel h3 { font-size: 13px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .ui-widget-content { background: none; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field { /* Fields are more compressed in some areas compared to the main content editing window so the below alters the internal spacing of the fields so we can move that spacing to between the form fields rather than padding */ border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field label { float: none; width: auto; font-size: 12px; padding: 0 8px 4px 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field label.extra-details { overflow: hidden; margin-top: 10px; display: block; color: #9d9d9d; font-style: italic; font-weight: normal; font-size: 1em; float: left; text-shadow: none; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field label.extra-details.fill:before { color: #fff; content: '?'; font-size: 12px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-left: 3px; padding-right: 3px; display: block; float: left; text-shadow: none; -webkit-border-radius: 50px; -moz-border-radius: 50px; -ms-border-radius: 50px; -o-border-radius: 50px; border-radius: 50px; background-color: #b7b7b7; width: 15px; height: 15px; margin-right: 5px; margin-bottom: 5px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field .middleColumn { margin: 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field input.text, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field select, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field textarea { padding: 5px; font-size: 11px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field.checkbox { padding: 0 8px 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .field.checkbox input { margin: 2px 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field { padding: 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .fieldgroup .fieldgroup-field .field { margin: 0; padding: 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-content-fields { overflow: visible; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .chzn-container-single { width: 100% !important; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-content-actions, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-preview-controls { padding: 0; height: auto; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-edit-form { width: 100%; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .CompositeField { margin: 0; padding: 0; float: none; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .parent-mode { padding-top: 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .treedropdown, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field { margin: 10px 0 0 0; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-title, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-title { position: absolute; z-index: 2; padding: 5px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-panel, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-panel { margin-top: 11px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link { background: none; border-left: none; padding: 5px 3px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .treedropdown .treedropdownfield-toggle-panel-link .ui-icon, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .SelectionGroup li.selected div.field .treedropdownfield-toggle-panel-link .ui-icon { float: right; opacity: 0.7; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-add-form ul.SelectionGroup { padding-left: 0; padding-right: 0; overflow: visible; border-bottom: none; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .chzn-container-single .chzn-single { padding: 0 0 0 5px; float: none; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:hover, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:focus, .cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel button.ss-ui-button:active { /*text-decoration:underline;*/ background-color: #e0e5e8; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information { border-bottom: 1px solid #d0d3d5; margin-bottom: 8px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .ui-tabs-panel .cms-sitetree-information p.meta-info { color: #999; font-size: 11px; line-height: 16px; margin-bottom: 8px; } -.cms .cms-content-actions .Actions .ss-ui-action-tabset .last .ui-tabs-panel.ss-ui-action-tab { right: -1px; left: auto; } -.cms .cms-tree-view-sidebar { min-width: 176px; /* for when the scrollbar is present & find dropdown open */ } -.cms .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li { width: auto; } -.cms .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li a.tab-nav-link { width: 30px; overflow: hidden; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding-right: 0; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; } -.cms .cms-tree-view-sidebar .ss-ui-action-tabset ul.ui-tabs-nav > li a.tab-nav-link.active { width: 110px; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; } -.cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.first, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open ul.ui-tabs-nav li.last, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.first, .cms .cms-tree-view-sidebar .ss-ui-action-tabset.tabset-open-last ul.ui-tabs-nav li.last { -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; } -.cms .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab { width: 162px; padding: 10px 6px; } -.cms .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab .field { max-width: 160px; } -.cms .cms-tree-view-sidebar .ui-tabs .ui-tabs-panel.ss-ui-action-tab .ui-icon { padding-right: 0; } -.cms .cms-tree-view-sidebar .last .ui-tabs-panel.ss-ui-action-tab { right: 0; left: auto; } .ModelAdmin .cms-content-fields { overflow: hidden; } .ModelAdmin .cms-content-fields .cms-edit-form { overflow-y: auto; overflow-x: hidden; } diff --git a/admin/scss/_actionTabs.scss b/admin/scss/_actionTabs.scss index d99618267..d009ab2ed 100644 --- a/admin/scss/_actionTabs.scss +++ b/admin/scss/_actionTabs.scss @@ -1,10 +1,13 @@ /******************************************** - -Defines the styles for the action tabset, found on the site tree, -and as a single (more options) tab in page view. This is a special -use case of tabs, so the default tab styling should not apply - - +* Defines the styles for .ss-ui-action-tabset: +* * Site tree action tabs (to perform actions on the site tree) +* * Actions menu (Edit page actions) +* +* Reliant on TabSet.js to apply and remove some classes. +* +* Note: This is a special use case of tabs, so the default tab +* styling should not apply +* **********************************************/ @@ -12,87 +15,129 @@ $border: 1px solid darken(#D9D9D9, 15%); .cms { .ss-ui-action-tabset{ - position:relative; float:left; + position:relative; - /*Style the "tabs" navigation for action tabs (as in the sitetree batch actions)*/ - ul.ui-tabs-nav{ - @include clearfix; - padding:0; - overflow:visible; - float:left; - height: 28px; - border:$border; - @include border-radius(3px); - &:focus,&:active{ - outline:none; - box-shadow:none; - -webkit-box-shadow: none; - } - li{ - &:focus, &:active{ + /* + Styles for the tab-nav of the site tree implementation + of ss-ui-action-tabset + */ + &.multi{ + ul.ui-tabs-nav{ + @include border-radius(3px); + @include clearfix; + border:$border; + float:left; + height: 28px; + overflow:visible; + padding:0; + &:active{ outline:none; box-shadow:none; -webkit-box-shadow: none; - } - width: 110px; - overflow:visible; - background:#eaeaea; - @include background-image(linear-gradient(top, #f8f8f8, #D9D9D9)); - border-radius: none; - @include border-radius(0); - border: none; - border-right:1px solid #eee; - border-left: $border; - margin:0; - &.ui-state-active{ - background:#f8f8f8; - border-bottom:none !important; //jquery-ui style has important on it - @include border-bottom-left-radius(0px); - @include border-bottom-right-radius(0px); - a { + } + li{ + @include background-image(linear-gradient(top, #f8f8f8, #D9D9D9)); + @include border-radius(0); + background: #eaeaea; + border: none; + border-right:1px solid #eee; + border-left: $border; + margin:0; + overflow: visible; + width: 110px; + &:active{ + outline:none; + box-shadow:none; + -webkit-box-shadow: none; + } + &.ui-state-active{ @include border-bottom-left-radius(0px); - @include border-bottom-right-radius(0px); - &:focus, span:focus,&:active, span:active{ - outline:none; - box-shadow:none; - -webkit-box-shadow: none; + @include border-bottom-right-radius(0px); + background:#f8f8f8; + border-bottom:none !important; //jquery-ui style has important on it + a { + @include border-bottom-left-radius(0px); + @include border-bottom-right-radius(0px); + &:active, span:active{ + outline:none; + box-shadow:none; + -webkit-box-shadow: none; + } + } + } + &.first{ + @include border-top-left-radius(3px); + @include border-bottom-left-radius(3px); + border-left:none; + } + &.last{ + @include border-top-right-radius(3px); + @include border-bottom-right-radius(3px); + border-right:none; + } + a.tab-nav-link{ + color:$color-text; + display:inline-block; + font-weight:bold; + line-height:16px; + padding: 5px 10px; + .ui-no-icon { //for links that don't have icons (ie the batch actions field) + display: inline-block; + float: left; + height: 16px; + padding: 0 2px; + width: 16px; + } + .title{ + display:inline-block; + line-height: 18px; + } + &.view-mode-batchactions-wrapper .title { + margin-left: 22px; } } } - &.first{ - @include border-top-left-radius(3px); - @include border-bottom-left-radius(3px); - border-left:none; + } + /* Style the tab panels */ + .ss-ui-action-tab.ui-tabs-panel{ + @include tightSpacing; + background:#f8f8f8 !important; //Because ie7 doesn't understand what the 'C' in CSS stands for + border:$border; + border-top:none; + clear:both; + display:block; + float:left; + margin:0; + padding:10px; + padding-top:15px; + position:absolute; + top:30px; + width:202px; + z-index:1; + &.first { + left: 0; + width: 203px; } - &.last{ - @include border-top-right-radius(3px); - @include border-bottom-right-radius(3px); - border-right:none; + .ui-icon { + padding-right: 0; } - a.tab-nav-link{ - color:$color-text; - font-weight:bold; - line-height:16px; - display:inline-block; - padding: 5px 10px; - .ui-no-icon { - display:inline-block; - float:left; - padding: 0 2px; - width:16px; - height:16px; + .tab-nav-link, .ss-ui-button { + font-size: 12px; + } + #PageType ul{ + padding:0; + li{ + padding:4px 5px; } - .title{ - display:inline-block; - line-height: 18px; - } - &.view-mode-batchactions-wrapper .title { - margin-left: 22px; - } - } + } + } + .last .ss-ui-action-tab{ + //right:-1px; + // left:auto; } } + // Classes applied by javascript &.tabset-open { ul.ui-tabs-nav, ul.ui-tabs-nav li.first { @@ -103,9 +148,9 @@ $border: 1px solid darken(#D9D9D9, 15%); ul.ui-tabs-nav li.last { @include border-bottom-right-radius(0); } - } - - .batch-check, .ui-icon { /* position a checkbox & icon within a tab */ + } + /* position a checkbox & icon within a tab */ + .batch-check, .ui-icon { display: inline-block; float:left; margin-left: -2px; @@ -115,221 +160,172 @@ $border: 1px solid darken(#D9D9D9, 15%); margin: 6px 0px 5px 9px; position: absolute; } - &.action-menus{ //Styles for actions-menu implementation + + + /* Styles for the cms-actions in tree view, to use more limited space. + Title hidden in tree view, until hover/active state added. Active is applied + to the first tab within the template, so there should always be one title + visible. Added and removed with js in TabSet.js */ + .cms-tree-view-sidebar{ + min-width: 176px; /* for when the scrollbar is present & find dropdown open */ + .ss-ui-action-tabset{ + ul.ui-tabs-nav{ + >li{ + width: auto; + a.tab-nav-link{ + @include box-sizing(border-box); + @include duration(0.5s); + overflow:hidden; + padding-right:0; + width:30px; + &.active{ + @include duration(0.5s); + width:110px; + + } + } + } + } + &.tabset-open, &.tabset-open-last { + ul.ui-tabs-nav, + ul.ui-tabs-nav li.first, + ul.ui-tabs-nav li.last { + @include border-bottom-right-radius(0); + @include border-bottom-left-radius(0); + } + } + } + .ui-tabs .ui-tabs-panel.ss-ui-action-tab { + padding:10px 6px; + width:162px; + .field { + max-width:160px; + } + .ui-icon { + padding-right: 0; + } + } + .last .ui-tabs-panel.ss-ui-action-tab { + left:auto; + right:0; + } + } + + /**************************************************************** + Styles for the actions-menu implementation + of ss-ui-action-tabset + ****************************************************************/ + &.action-menus.ss-tabset { + margin-top: 2px; + + //Style the tabs naivgation ul.ui-tabs-nav{ - background:none; - border:none; - display:inline; - padding:0; - float:left; + margin: 0; + float: left; /* needed for ie but doesnt effect other browsers */ + li{ - display:inline; - background:none; - border:none; - padding:0; - border-bottom:none !important; //over-ride jquery-ui style (which also has important) - - &:hover, &:focus, &:active{ + background: none; + border: none; + border-bottom: none !important; //over-ride jquery-ui style (which also has important) + display: inline; + padding: 0; + &:hover, &:active{ @include box-shadow(none); outline:none; } a{ - color: $color-text-blue-link; @include text-shadow(#fff 0 1px 1px); - padding:0 0 0 10px; - line-height:24px; - - &:hover, &:focus, &:active{ + color: $color-text-blue-link; + font-size: 13px; + font-weight: normal; + line-height: 24px; + padding:0 25px 0 10px; + &:hover, &:active{ @include box-shadow(none); outline:none; } &:hover{ @include text-shadow(#fff 0 10px 10px); color: darken($color-text-blue-link,8%); - &:after{ - border-bottom: 4px solid darken($color-text-blue-link,8%); - } } - } - } - } - } - - /* Style the tab panels */ - .ss-ui-action-tab.ui-tabs-panel{ - @include tightSpacing; - background:#f8f8f8 !important; //Because ie7 doesn't understand what the 'C' in CSS stands for - border:$border; - border-top:none; - clear:both; - display:block; - float:left; - margin:0; - padding:10px; - padding-top:15px; - position:absolute; - top:30px; - width:202px; - z-index:1; - &.first { - left: 0; - width: 203px; - } - .ui-icon { - padding-right: 0; - } - .tab-nav-link, .ss-ui-button { - font-size: 12px; - } - #PageType ul{ - padding:0; - li{ - padding:4px 5px; - } - } - } - .last .ss-ui-action-tab{ - right:-1px; - left:auto; - } - } - - /********************** - Styles for edit page action menus - ************************/ - .cms-content-actions .Actions{ - overflow:visible; //for testing (changed in another branch) - - .ss-ui-action-tabset{ - ul.ui-tabs-nav { - margin: 0; - li { - a.ui-tabs-anchor { - font-weight: normal; - font-size: 13px; - line-height: 24px; - padding-right: 25px; + /* Arrow */ &:after { background: sprite($sprites32, arrow_down_lighter) no-repeat; - width: 16px; - height: 16px; + border-bottom: 0; content: ""; display: inline-block; + height: 16px; margin-left: 6px; - border-bottom: 0; - + width: 16px; } - &:hover:after { - border-bottom: 0; + &:hover:after { background: sprite($sprites32, arrow_down_darker) no-repeat; - } - + } } - &.ui-state-active a.ui-tabs-anchor { + /* Make arrow point in up when nav open */ + &.ui-state-active a { &:after { background: sprite($sprites32, arrow_up_lighter) no-repeat; } &:hover:after { background: sprite($sprites32, arrow_up_darker) no-repeat; } - } + } } } + /* Style the panel for actions-menu */ .ui-tabs-panel{ @include clearfix; @include border-top-radius(3px); @include border-bottom-radius(0); @include tightSpacing; + @extend .button-no-style; + clear:both; + display:block; background-color: $tab-panel-texture-color; border:1px solid #ccc; border-bottom:1px solid $tab-panel-texture-color; - clear:both; - display:block; - position:absolute; - top:-204px; - width:190px; /* same width as buttons within panel */ - z-index:1; - padding:10px; margin:0; - margin-top:1px; - .chzn-container-single .chzn-single{ - padding: 0 0 0 5px; - float:none; - } - @extend .button-no-style; - button.ss-ui-button{ - &:hover, &:focus, &:active{ - /*text-decoration:underline;*/ - background-color: darken($tab-panel-texture-color,4%); - @include box-shadow(none); - outline:none; - } - - } + margin-top:2px; + max-width:250px; + padding: 8px 0 2px; + position:absolute; + z-index:1; + min-width: 190px; + + //Styles for the information displayed in popup above the main action buttons .cms-sitetree-information { - border-bottom: 1px solid $color-light-separator; + border-bottom: 1px solid lighten($color-light-separator, 8%); margin-bottom: 8px; - p.meta-info { + padding: 0 20px 0 0; + margin-right: 10px; + margin-left: 10px; + p.meta-info { color: #999; font-size: 11px; line-height: 16px; margin-bottom: 8px; + white-space: nowrap; } + } + button.ss-ui-button{ + width:100%; + &:hover, &:focus, &:active{ + @include box-shadow(none); + background-color: darken($tab-panel-texture-color,4%); + outline:none; + } } } + /* Re-align last tab */ .last .ui-tabs-panel.ss-ui-action-tab{ - right:-1px; left:auto; - } - } + right:-1px; + } + } } - - /* Styles for the cms-actions in tree view, to use more limited space. - Title hidden in tree view, until hover/active state added. Active is applied - to the first tab within the template, so there should always be one title - visible. Added and removed with js in TabSet.js */ - .cms-tree-view-sidebar{ - min-width: 176px; /* for when the scrollbar is present & find dropdown open */ - .ss-ui-action-tabset{ - ul.ui-tabs-nav{ - >li{ - width: auto; - a.tab-nav-link{ - width:30px; - overflow:hidden; - @include box-sizing(border-box); - padding-right:0; - @include duration(0.5s); - &.active{ - width:110px; - @include duration(0.5s); - } - } - } - } - &.tabset-open, &.tabset-open-last { - ul.ui-tabs-nav, - ul.ui-tabs-nav li.first, - ul.ui-tabs-nav li.last { - @include border-bottom-right-radius(0); - @include border-bottom-left-radius(0); - } - } - } - .ui-tabs .ui-tabs-panel.ss-ui-action-tab { - width:162px; - padding:10px 6px; - .field { - max-width:160px; - } - .ui-icon { - padding-right: 0; - } - } - .last .ui-tabs-panel.ss-ui-action-tab { - right:0; - left:auto; - } - } + .cms-content-actions .Actions{ + overflow:visible; //for testing (changed in another branch) + } } \ No newline at end of file diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss index 393c76d07..a80be6a61 100644 --- a/admin/scss/_forms.scss +++ b/admin/scss/_forms.scss @@ -287,19 +287,19 @@ form.small .field, .field.small { .cms { .button-no-style{ button{ + @include border-radius(0); background: none; border: none; + color: $color-text-blue-link; display: block; + font-weight:normal; margin:0; outline:none; - color: $color-text-blue-link; - font-weight:normal; - width: 210px; /* same as width of surrounding panel */ - text-align: left; - @include border-radius(0); - text-shadow: none; - margin-left:-10px; - + padding-left:10px; + padding-right:10px; + text-align: left; + text-shadow: none; + white-space:normal; &.ss-ui-action-destructive{ color: darken($color-error,25%); } @@ -307,10 +307,10 @@ form.small .field, .field.small { padding-left:0; padding-right:0; } - &:hover, &:focus, &:active{ + &:hover, &:focus, &:active{ + @include box-shadow(none); outline:none; - background:none; - @include box-shadow(none); + background:none; border:none; } } @@ -509,6 +509,18 @@ form.small .field, .field.small { width: 16px; height: 16px; } + + /* Tempory fix: Overide default icon & text styles as the order of the icon and text are mixed up */ + .south .Actions .ss-ui-button.ss-ui-action-constructive { + .ui-button-text-alternate { + margin-left: 22px; + } + .ui-icon { + position: absolute; + left: 10px; + top: 5px; + } + } } /** ---------------------------------------------------- diff --git a/admin/scss/ie7.scss b/admin/scss/ie7.scss index dc83c9617..4c5833e40 100644 --- a/admin/scss/ie7.scss +++ b/admin/scss/ie7.scss @@ -7,6 +7,52 @@ html { overflow: hidden; } +//add line below the cms-content-toolbar +.cms-content-toolbar { + padding-bottom:5px; +} + +.cms-menu-list{ + li{ + list-style-type: none; + width: 100%; + float: left; + margin: 0px; + padding: 0px; + } +} + + +/* Site tree +------------------------- */ + +//fix for the tree view modes not displaying inline +.cms-tree-view-modes { + div { + float:left; + } + span { + float:left; + padding-top:5px; + } +} +.cms-panel-content .cms-tree{ + li{ + width:200px; + overflow:hidden; + float:left; + display:inline; + } +} + +// fix jstree themeroller plugin bug: tree disappear in IE7 +.jstree li a .ui-icon { + text-indent: 0px !important; +} + + +/* Forms and files area +-----------------------------*/ .field { input.text, textarea, @@ -43,7 +89,6 @@ select { float:left; } - //fix for the tree view modes not displaying inline .cms-tree-view-modes { div { @@ -86,6 +131,9 @@ select { text-indent: 0px !important; } +/* Gridfield +------------------------- */ + .cms table.ss-gridfield-table { tbody td { // Overrule width: 100% setting to trigger "shrink fit" @@ -106,8 +154,6 @@ select { margin: -1px -5px; } - - //fix for edit and delete icons .cms .ss-gridfield table.ss-gridfield-table tbody { td { @@ -173,16 +219,6 @@ table.ss-gridfield-table { } } -//fix for model admin filter styling -.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content { - .cms-search-form { - overflow:hidden; - input { - width:160px; - } - } -} - //fix for view children arrow in pages list view .cms .ss-gridfield table.ss-gridfield-table tbody { td { @@ -194,7 +230,26 @@ table.ss-gridfield-table { } } } -} +} + +.ss-ui-button{ + &.ss-gridfield-button-filter{ + border:none !important; + } +} + + + +//fix for model admin filter styling +.ModelAdmin .cms-content-fields .cms-content-tools .cms-panel-content { + .cms-search-form { + overflow:hidden; + input { + width:160px; + } + } +} + // CMS Content header & tab fix .cms-content-header { @@ -225,21 +280,40 @@ table.ss-gridfield-table { display:block; } +@include IEVerticalPanelText; + //IE7 can't use before and after. Compromise -.cms .cms-content-actions .Actions .rise-up.ss-ui-action-tabset{ - a.ui-tabs-anchor{ - background: sprite($sprites32, arrow_down_lighter) no-repeat; - padding-left:20px; - &:hover { - background: sprite($sprites32, arrow_down_darker) no-repeat; +.cms .cms-content-actions .Actions .action-menus.ss-ui-action-tabset { + width: 190px; + ul.ui-tabs-nav { + a.ui-tabs-anchor{ + background: transparent url(../images/sprites-32x32/arrow_down_lighter.png) no-repeat right top; + &:hover { + background: transparent url(../images/sprites-32x32/arrow_down_darker.png) no-repeat right top; + } + } + .ui-state-active a.ui-tabs-anchor { + background: transparent url(../images/sprites-32x32/arrow_up_lighter.png) no-repeat right top; + &:hover { + background: transparent url(../images/sprites-32x32/arrow_up_darker.png) no-repeat right top; + } } } - .ui-state-active a.ui-tabs-anchor { - background: sprite($sprites32, arrow_up_lighter) no-repeat; - &:hover { - background: sprite($sprites32, arrow_up_darker) no-repeat; - } - } + .ui-tabs-panel button.ss-ui-button { + width: 190px; /* Width 100% not calculating by ie7 */ + } } -@include IEVerticalPanelText; + +/* Tempory fix as jquery loads too slow to add icons */ +button.ui-button-text-icon-primary { + padding-left: 30px !important; + span.ui-button-icon-primary { + position: absolute !important; + top: 5px !important; + left: 8px !important; + } + .ui-button-text { + margin-left: 0 !important; + } +} From 5cef05ebea31d4df2d1eb621f8a88d6413a3fa99 Mon Sep 17 00:00:00 2001 From: Naomi Guyer Date: Wed, 5 Dec 2012 16:07:07 +1300 Subject: [PATCH 22/49] Separate out ActionTabSet functionality into a new file & clean up. --- admin/code/LeftAndMain.php | 1 + admin/javascript/LeftAndMain.ActionTabSet.js | 222 +++++++++++++++++ admin/javascript/LeftAndMain.js | 2 +- admin/scss/_forms.scss | 1 - javascript/TabSet.js | 237 +------------------ 5 files changed, 225 insertions(+), 238 deletions(-) create mode 100644 admin/javascript/LeftAndMain.ActionTabSet.js diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index 6b524eb29..27c2e6128 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -296,6 +296,7 @@ class LeftAndMain extends Controller implements PermissionProvider { array( FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Layout.js', FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.js', + FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.ActionTabSet.js', FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Panel.js', FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Tree.js', FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Ping.js', diff --git a/admin/javascript/LeftAndMain.ActionTabSet.js b/admin/javascript/LeftAndMain.ActionTabSet.js new file mode 100644 index 000000000..34fe1ba58 --- /dev/null +++ b/admin/javascript/LeftAndMain.ActionTabSet.js @@ -0,0 +1,222 @@ +/** + * File: LeftAndMain.ActionTabset.js + * + * Contains rules for .ss-ui-action-tabset, used for: + * * Site tree action tabs (to perform actions on the site tree) + * * Actions menu (Edit page actions) + * + */ +(function($){ + $.entwine('ss', function($) { + /** + * Generic rules for all ss-ui-action-tabsets + * * ActionMenus + * * SiteTree ActionTabs + */ + $('.ss-tabset.ss-ui-action-tabset').entwine({ + + onadd: function() { + // Make sure the .ss-tabset is already initialised to apply our modifications on top. + this._super(); + //Set actionTabs to allow closing and be closed by default + this.tabs({'collapsible': true, 'active': false}); + }, + + /** + * Deal with available vertical space + */ + 'ontabsbeforeactivate': function(event, ui) { + this.riseUp(event, ui); + }, + + /** + * Handle opening and closing tabs + */ + onclick: function(event, ui) { + this.attachCloseHandler(event, ui); + }, + + /** + * Generic function to close open tabs. Stores event in a handler, + * and removes the bound event once activated. + * + * Note: Should be called by a click event attached to 'this' + */ + attachCloseHandler: function(event, ui) { + var that = this, frame = $('.cms').find('iframe'), closeHandler; + + // Create a handler for the click event so we can close tabs + // and easily remove the event once done + closeHandler = function(event) { + var panel, frame; + panel = $(event.target).closest('.ss-ui-action-tabset .ui-tabs-panel'); + + // If anything except the ui-nav button is clicked, + // close panel and remove handler + if (!$(event.target).closest(that).length || $(panel).length) { + that.tabs('option', 'active', false); // close tabs + + // remove click event from objects it is bound to (iframe's and document) + frame = $('.cms').find('iframe'); + frame.each(function(index, iframe){ + $(iframe).contents().off('click', closeHandler); + }); + $(document).off('click', closeHandler); + } + }; + + // Bind click event to document, and use closeHandler to handle the event + $(document).on('click', closeHandler); + // Make sure iframe click also closes tab + // iframe needs a special case, else the click event will not register here + if(frame.length > 0){ + frame.each(function(index, iframe) { + $(iframe).contents().on('click', closeHandler); + }); + } + }, + /** + * Function riseUp checks to see if a tab should be opened upwards + * (based on space concerns). If true, the rise-up class is applied + * and a new position is calculated and applied to the element. + * + * Note: Should be called by a tabsbeforeactivate event + */ + riseUp: function(event, ui) { + var elHeight, trigger, endOfWindow, elPos, activePanel, activeTab, topPosition, containerSouth, padding; + + // Get the numbers needed to calculate positions + elHeight = $(this).find('.ui-tabs-panel').outerHeight(); + trigger = $(this).find('.ui-tabs-nav').outerHeight(); + endOfWindow = ($(window).height() + $(document).scrollTop()) - trigger; + elPos = $(this).find('.ui-tabs-nav').offset().top; + + activePanel = ui.newPanel; + activeTab = ui.newTab; + + if (elPos + elHeight >= endOfWindow && elPos - elHeight > 0){ + this.addClass('rise-up'); + + if (activeTab.position() !== null){ + topPosition = -activePanel.outerHeight(); + containerSouth = activePanel.parents('.south'); + if (containerSouth){ + // If container is the southern panel, make tab appear from the top of the container + padding = activeTab.offset().top - containerSouth.offset().top; + topPosition = topPosition-padding; + } + $(activePanel).css('top',topPosition+"px"); + } + } else { + // else remove the rise-up class and set top to 0 + this.removeClass('rise-up'); + if (activeTab.position() !== null){ + $(activePanel).css('top','0px'); + } + } + return false; + } + }); + + + /** + * ActionMenus + * * Specific rules for ActionMenus, used for edit page actions + */ + $('.cms-content-actions .ss-tabset.ss-ui-action-tabset').entwine({ + /** + * Make necessary adjustments before tab is activated + */ + 'ontabsbeforeactivate': function(event, ui) { + this._super(event, ui); + //Set the position of the opening tab (if it exists) + if($(ui.newPanel).length > 0){ + $(ui.newPanel).css('left', ui.newTab.position().left+"px"); + } + } + }); + + /** + * SiteTree ActionTabs + * Specific rules for site tree action tabs. Applies to tabs + * within the expanded content area, and within the sidebar + */ + $('.cms-actions-row.ss-tabset.ss-ui-action-tabset').entwine({ + /** + * Make necessary adjustments before tab is activated + */ + 'ontabsbeforeactivate': function(event, ui) { + this._super(event, ui); + // Remove tabset open classes (Last gets a unique class + // in the bigger sitetree. Remove this if we have it) + $(this).closest('.ss-ui-action-tabset') + .removeClass('tabset-open tabset-open-last'); + } + }); + + /** + * SiteTree ActionTabs: expanded + * * Specific rules for siteTree actions within the expanded content area. + */ + $('.cms-content-fields .ss-tabset.ss-ui-action-tabset').entwine({ + /** + * Make necessary adjustments before tab is activated + */ + 'ontabsbeforeactivate': function(event, ui) { + this._super(event, ui); + if($( ui.newPanel).length > 0){ + if($(ui.newTab).hasClass("last")){ + // Align open tab to the right (because opened tab is last) + $(ui.newPanel).css({'left': 'auto', 'right': '0px'}); + + // Last needs to be styled differently when open, so apply a unique class + $(ui.newPanel).parent().addClass('tabset-open-last'); + }else{ + // Assign position to tabpanel based on position of relivent active tab item + $(ui.newPanel).css('left', ui.newTab.position().left+"px"); + + // If this is the first tab, make sure the position doesn't include border + // (hard set position to 0 ), and add the tab-set open class + if($(ui.newTab).hasClass("first")){ + $(ui.newPanel).css('left',"0px"); + $(ui.newPanel).parent().addClass('tabset-open'); + } + } + } + } + }); + + /** + * SiteTree ActionTabs: sidebar + * * Specific rules for when the site tree actions panel appears in + * * the side-bar + */ + $('.cms-tree-view-sidebar .cms-actions-row.ss-tabset.ss-ui-action-tabset').entwine({ + + // If actions panel is within the sidebar, apply active class + // to help animate open/close on hover + 'from .ui-tabs-nav li': { + onhover: function(e) { + $(e.target).parent().find('li .active').removeClass('active'); + $(e.target).find('a').addClass('active'); + } + }, + + /** + * Make necessary adjustments before tab is activated + */ + 'ontabsbeforeactivate': function(event, ui) { + this._super(event, ui); + // Reset position of tabs, else anyone going between the large + // and the small sitetree will see broken tabs + // Apply styles with .css, to avoid overriding currently applied styles + $(ui.newPanel).css({'left': 'auto', 'right': 'auto'}); + + if($(ui.newPanel).length > 0){ + $(ui.newPanel).parent().addClass('tabset-open'); + } + } + }); + + }); +}(jQuery)); diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index dd11134dd..0adc315b4 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -572,7 +572,7 @@ jQuery.noConflict(); if(typeof(window.sessionStorage)=="undefined" || window.sessionStorage == null) return; var selectedTabs = [], url = this._tabStateUrl(); - this.find('.cms-tabset,.ss-tabset').each(function(i, el) { + this.find('.cms-tabset,.ss-tabset').each(function(i, el) { var id = $(el).attr('id'); if(!id) return; // we need a unique reference if(!$(el).data('tabs')) return; // don't act on uninit'ed controls diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss index a80be6a61..abeea4f0f 100644 --- a/admin/scss/_forms.scss +++ b/admin/scss/_forms.scss @@ -812,4 +812,3 @@ fieldset.switch-states{ } //old web-kit browser fix @-webkit-keyframes bugfix { from { position: relative; } to { position: relative; } } - diff --git a/javascript/TabSet.js b/javascript/TabSet.js index 205384a12..d3b78f2bf 100644 --- a/javascript/TabSet.js +++ b/javascript/TabSet.js @@ -2,19 +2,8 @@ $.entwine('ss', function($){ /** * Lightweight wrapper around jQuery UI tabs for generic tab set-up - * and special rules for two other use cases (ss-ui-action-tabset): - * * Site tree action tabs (to perform actions on the site tree) - * * Actions menu (Edit page actions) */ $('.ss-tabset').entwine({ - - /****************************************************** - * Lightweight wrapper around jQuery UI tabs: - * * onadd - * * onremove - * * redrawTabs - * * rewriteHashlinks - *******************************************************/ onadd: function() { // Can't name redraw() as it clashes with other CMS entwine classes this.redrawTabs(); @@ -27,11 +16,6 @@ redrawTabs: function() { this.rewriteHashlinks(); this.tabs(); - - //Apply special behaviour to ss-ui-action-tabset - if(this.hasClass('ss-ui-action-tabset')){ - this.actionTabs(); - } }, /** @@ -46,226 +30,7 @@ if(!matches) return; $(this).attr('href', document.location.href.replace(/#.*/, '') + matches[0]); }); - }, - - /*************************************************** - * Custom functionality for special action tabsets - * * actionTabs - * * siteTreeActions - * * actionsMenu - * * closeTabs - * * riseUp - ***************************************************/ - - /* - Apply generic rules for action tabs (collapsible, rise, and close), - then call specific functions to handle each type of action tab - */ - actionTabs: function(){ - var that = this; - - //Set actionTabs to allow closing and be closed by default - this.tabs( - 'option', - 'collapsible', - true - ).tabs('option', 'active', false); - - - //Call close function on beforeactivate event - this.on( "tabsbeforeactivate", function(event, ui) { - that.closeTabs(event, ui); - }); - - // Call riseUp funciton on befporeactivate to check if tabs should - // open upwards (based on available space) and adjust - this.on( "tabsbeforeactivate", function(event, ui) { - that.riseUp(event, ui); - }); - - // Apply special behaviour depending on whether tabs are - // sitetree actions, or an actionmenu - if(this.parents('.cms-content-actions')){ - this.actionsMenu(); - } else if(this.hasClass('cms-actions-row')){ - this.siteTreeActions(); - } - }, - - /* - * Apply custom rules to the Actions Menu - * Currently includes positioning logic - */ - actionsMenu: function(){ - this.tabs({ - beforeActivate:function(event, ui){ //Set options before tab activated (but after clicked) - var activePanel = ui.newPanel; //panel about to open - var activeTab = ui.newTab; //tab nav item about to become active - - //Set the position of the opening tab (if it exists) - if($(activePanel).length > 0){ - $(activePanel).css('left', activeTab.position().left+"px"); - } - } - }); - }, - - /* - Apply rules to the siteTree actions. These action panels should - recieve different positions and classes depending on whether they are - appearing in the full page site tree view, or in the sidebar - */ - siteTreeActions: function(){ - var that = this; - var container = this.parent().parent(); - - //Remove open classes on beforeactivate - this.on( "tabsbeforeactivate", function(event, ui) { - // Remove tabset open classes (last gets a unique class - // in the bigger sitetree, so remove this too) - $(that).closest('.ss-ui-action-tabset') - .removeClass('tabset-open') - .removeClass('tabset-open-last'); - }); - - /* Apply specific rules if the actions panel appears in the side-bar - * Includes: - * * a hover helper class for animation, - * * reseting positioning of panels - */ - if($(container).hasClass('cms-tree-view-sidebar')){ - /* If actions panel is within the sidebar, apply active class - to help animate open/close on hover */ - $('.ui-tabs-nav li').hover(function(){ - $(this).parent().find('li .active').removeClass('active'); - $(this).find('a').addClass('active'); - }); - - /* Reset position of tabs, else anyone going between the large - and the small sitetree will see broken tabs */ - this.tabs({ - // Note: beforeActivate runs when a tab is clicked, - // but before it is visible. - beforeActivate:function(event, ui){ - var activePanel = ui.newPanel; //the new active panel - - //Apply styles with css, to avoid overriding currently applied styles - $(activePanel).css({'left': 'auto', 'right': 'auto'}); //reset left and right positioning - - if($(activePanel).length > 0){ - $(activePanel).parent().addClass('tabset-open'); - } - } - }); - }else{ - /* If the tabs are in the full site tree view, do some - positioning so tabPanel stays with relevent tab */ - this.tabs({ - beforeActivate:function(event, ui){ - var activePanel = ui.newPanel; - var activeTab = ui.newTab; - - if($(activePanel).length > 0){ - if($(activeTab).hasClass("last")){ - // Align open tab to the right (because opened tab is last) - $(activePanel).css({'left': 'auto', 'right': '0px'}); - - //last needs to be styled differently when open, so apply a unique class - $(activePanel).parent().addClass('tabset-open-last'); - }else{ - //Assign position to tabpanel based on position of relivent activeTab item - $(activePanel).css('left', activeTab.position().left+"px"); - - // If this is the first tab, make sure the position doesn't include border - // (hard set position to 0 ), and add the tab-set open class - if($(activeTab).hasClass("first")){ - $(activePanel).css('left',"0px"); - $(activePanel).parent().addClass('tabset-open'); - } - } - } - } - }); - } - }, - - /* - * Generic function to close open tabs when something other than - * the open tab is clicked. Stores event in a handler, and removes - * the bound event once activated. Used by ss-ui-action-tabset. - * - * Note: Should be called by a tabsbeforeactivate event - */ - closeTabs: function(event, ui){ - var that = this; - var frame = $('.cms').find('iframe'); //get all iframes on the page - - // Create a handler for the click event so we can close tabs - // and easily remove the event once done - var closeHandler = function(event){ - //close open tab - if (!$(event.target).closest(that).length) { - that.tabs('option', 'active', false); // close tabs - - //remove click event from objects it is bound to (iframe's and document) - var frame = $('.cms').find('iframe'); - frame.each(function(index, iframe){ - $(iframe).contents().off('click', closeHandler); - }); - $(document).off('click', closeHandler); - }; - } - - //Bind click event to document, and use closeHandler to handle the event - $(document).on('click', closeHandler); - // Make sure iframe click also closes tab - // iframe needs a special case, else the click event will not register here - if(frame.length > 0){ - frame.each(function(index, iframe){ - $(iframe).contents().on('click', closeHandler); - }); - } - }, - /***************************************************************** - * Function riseUp checks to see if a tab should be opened upwards - * (based on space concerns). If true, the rise-up class is applied - * and a new position is calculated and applied to the element. - * - * Note: Should be called by a tabsbeforeactivate event - ******************************************************************/ - riseUp: function(event, ui){ - - // Get the numbers needed to calculate positions - var elHeight = $(this).find('.ui-tabs-panel').outerHeight(); - var trigger = $(this).find('.ui-tabs-nav').outerHeight(); - var endOfWindow = ($(window).height() + $(document).scrollTop()) - trigger; - var elPos = $(this).find('.ui-tabs-nav').offset().top; - - var activePanel = ui.newPanel; - var activeTab = ui.newTab; - - if (elPos + elHeight >= endOfWindow && elPos - elHeight > 0){ - this.addClass('rise-up'); - - if (activeTab.position() != null){ - var topPosition = -activePanel.outerHeight(); - var containerSouth = activePanel.parents('.south'); - if (containerSouth){ - //If container is the southern panel, make tab appear from the top of the container - var padding = activeTab.offset().top - containerSouth.offset().top; - topPosition = topPosition-padding; - } - $(activePanel).css('top',topPosition+"px"); - } - } else { - //else remove the rise-up class and set top to 0 - this.removeClass('rise-up'); - if (activeTab.position() != null){ - $(activePanel).css('top','0px'); - } - } - return false; - } + } }); }); })(jQuery); From 4d6d823cb1b76264556f49335686cfbf61602c3d Mon Sep 17 00:00:00 2001 From: Mateusz Uzdowski Date: Tue, 11 Dec 2012 11:30:06 +1300 Subject: [PATCH 23/49] API Allow ignoring persistent tab state through entwine property. In this case we don't want to rely on data attributes in the DOM, as this should be an inbuilt property associated with this class. --- admin/javascript/LeftAndMain.ActionTabSet.js | 2 ++ admin/javascript/LeftAndMain.js | 5 ++++- javascript/TabSet.js | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/admin/javascript/LeftAndMain.ActionTabSet.js b/admin/javascript/LeftAndMain.ActionTabSet.js index 34fe1ba58..b1e36a097 100644 --- a/admin/javascript/LeftAndMain.ActionTabSet.js +++ b/admin/javascript/LeftAndMain.ActionTabSet.js @@ -14,6 +14,8 @@ * * SiteTree ActionTabs */ $('.ss-tabset.ss-ui-action-tabset').entwine({ + // Ignore tab state so it will not be reopened on form submission. + IgnoreTabState: true, onadd: function() { // Make sure the .ss-tabset is already initialised to apply our modifications on top. diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index 0adc315b4..cbce0f185 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -576,7 +576,10 @@ jQuery.noConflict(); var id = $(el).attr('id'); if(!id) return; // we need a unique reference if(!$(el).data('tabs')) return; // don't act on uninit'ed controls - if($(el).data('ignoreTabState')) return; // allow opt-out + + // Allow opt-out via data element or entwine property. + if($(el).data('ignoreTabState') || $(el).getIgnoreTabState()) return; + selectedTabs.push({id:id, selected:$(el).tabs('option', 'selected')}); }); diff --git a/javascript/TabSet.js b/javascript/TabSet.js index d3b78f2bf..44d5203aa 100644 --- a/javascript/TabSet.js +++ b/javascript/TabSet.js @@ -4,6 +4,8 @@ * Lightweight wrapper around jQuery UI tabs for generic tab set-up */ $('.ss-tabset').entwine({ + IgnoreTabState: false, + onadd: function() { // Can't name redraw() as it clashes with other CMS entwine classes this.redrawTabs(); From 5d93f8daec6b4b1578e06a4541eb21e98395fd19 Mon Sep 17 00:00:00 2001 From: Paul Clarke Date: Wed, 12 Dec 2012 13:45:50 +1300 Subject: [PATCH 24/49] Bug fix preview note "Website preview" --- admin/scss/_preview.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/admin/scss/_preview.scss b/admin/scss/_preview.scss index 57e192101..8aae9d4f6 100644 --- a/admin/scss/_preview.scss +++ b/admin/scss/_preview.scss @@ -220,12 +220,13 @@ font-size: 22px; font-weight: bold; height: 82px; - margin-top: -50px; + margin-top: -50px; margin-left: -150px; /* half of width */ position: absolute; text-align: center; text-shadow: 0 1px 0 #fff; top: 50%; + left: 50%; width: 300px; span { background: sprite($sprites64, preview) no-repeat; From 6100eb957e9ffcab4d6346876e6300fc52d5ccfc Mon Sep 17 00:00:00 2001 From: Naomi Guyer Date: Wed, 12 Dec 2012 14:17:57 +1300 Subject: [PATCH 25/49] Remove or comment magic numbers, whitespace --- admin/scss/_actionTabs.scss | 139 +++++++++++++++++------------------- 1 file changed, 66 insertions(+), 73 deletions(-) diff --git a/admin/scss/_actionTabs.scss b/admin/scss/_actionTabs.scss index d009ab2ed..bfd6da56c 100644 --- a/admin/scss/_actionTabs.scss +++ b/admin/scss/_actionTabs.scss @@ -16,36 +16,35 @@ $border: 1px solid darken(#D9D9D9, 15%); .cms { .ss-ui-action-tabset{ float:left; - position:relative; + position:relative; - /* + /* Styles for the tab-nav of the site tree implementation of ss-ui-action-tabset */ &.multi{ - ul.ui-tabs-nav{ - @include border-radius(3px); - @include clearfix; - border:$border; - float:left; - height: 28px; + ul.ui-tabs-nav{ + @include border-radius(3px); + @include clearfix; + border:$border; + float:left; overflow:visible; - padding:0; + padding:0; &:active{ outline:none; box-shadow:none; -webkit-box-shadow: none; - } + } li{ @include background-image(linear-gradient(top, #f8f8f8, #D9D9D9)); @include border-radius(0); - background: #eaeaea; + background: #eaeaea; border: none; border-right:1px solid #eee; - border-left: $border; + border-left: $border; margin:0; overflow: visible; - width: 110px; + min-width: 110px; //To make label width more uniform, but allow growth if needed &:active{ outline:none; box-shadow:none; @@ -58,7 +57,7 @@ $border: 1px solid darken(#D9D9D9, 15%); border-bottom:none !important; //jquery-ui style has important on it a { @include border-bottom-left-radius(0px); - @include border-bottom-right-radius(0px); + @include border-bottom-right-radius(0px); &:active, span:active{ outline:none; box-shadow:none; @@ -71,7 +70,7 @@ $border: 1px solid darken(#D9D9D9, 15%); @include border-bottom-left-radius(3px); border-left:none; } - &.last{ + &.last{ @include border-top-right-radius(3px); @include border-bottom-right-radius(3px); border-right:none; @@ -79,17 +78,17 @@ $border: 1px solid darken(#D9D9D9, 15%); a.tab-nav-link{ color:$color-text; display:inline-block; - font-weight:bold; - line-height:16px; + font-weight:bold; + line-height:16px; padding: 5px 10px; .ui-no-icon { //for links that don't have icons (ie the batch actions field) display: inline-block; float: left; height: 16px; padding: 0 2px; - width: 16px; + width: 16px; } - .title{ + .title{ display:inline-block; line-height: 18px; } @@ -108,16 +107,16 @@ $border: 1px solid darken(#D9D9D9, 15%); clear:both; display:block; float:left; - margin:0; + margin:0; padding:10px; padding-top:15px; position:absolute; - top:30px; - width:202px; - z-index:1; + top:30px; + width:202px; //Width is approx the size of two tab nav panels. + z-index:1; &.first { left: 0; - width: 203px; + width: 203px; //Width is approx the size of two tab nav panels with 1px border. } .ui-icon { padding-right: 0; @@ -128,27 +127,23 @@ $border: 1px solid darken(#D9D9D9, 15%); #PageType ul{ padding:0; li{ - padding:4px 5px; + padding:4px 5px; } - } - } - .last .ss-ui-action-tab{ - //right:-1px; - // left:auto; + } } } // Classes applied by javascript &.tabset-open { - ul.ui-tabs-nav, + ul.ui-tabs-nav, ul.ui-tabs-nav li.first { @include border-bottom-left-radius(0); } } - &.tabset-open-last { + &.tabset-open-last { ul.ui-tabs-nav li.last { @include border-bottom-right-radius(0); } - } + } /* position a checkbox & icon within a tab */ .batch-check, .ui-icon { display: inline-block; @@ -167,24 +162,23 @@ $border: 1px solid darken(#D9D9D9, 15%); to the first tab within the template, so there should always be one title visible. Added and removed with js in TabSet.js */ .cms-tree-view-sidebar{ - min-width: 176px; /* for when the scrollbar is present & find dropdown open */ + min-width: 176px; /* for when the scrollbar is present & find dropdown open */ .ss-ui-action-tabset{ ul.ui-tabs-nav{ - >li{ - width: auto; + >li{ + width: auto; a.tab-nav-link{ @include box-sizing(border-box); - @include duration(0.5s); - overflow:hidden; + @include duration(0.5s); + overflow:hidden; padding-right:0; - width:30px; + width:30px; &.active{ @include duration(0.5s); - width:110px; - + width:110px; } - } - } + } + } } &.tabset-open, &.tabset-open-last { ul.ui-tabs-nav, @@ -197,21 +191,21 @@ $border: 1px solid darken(#D9D9D9, 15%); } .ui-tabs .ui-tabs-panel.ss-ui-action-tab { padding:10px 6px; - width:162px; + width:162px; .field { - max-width:160px; + max-width:160px; } .ui-icon { padding-right: 0; - } + } } .last .ui-tabs-panel.ss-ui-action-tab { left:auto; - right:0; + right:0; } - } + } - /**************************************************************** + /**************************************************************** Styles for the actions-menu implementation of ss-ui-action-tabset ****************************************************************/ @@ -219,48 +213,47 @@ $border: 1px solid darken(#D9D9D9, 15%); margin-top: 2px; //Style the tabs naivgation - ul.ui-tabs-nav{ - margin: 0; + ul.ui-tabs-nav{ + margin: 0; float: left; /* needed for ie but doesnt effect other browsers */ - li{ background: none; border: none; border-bottom: none !important; //over-ride jquery-ui style (which also has important) - display: inline; - padding: 0; + display: inline; + padding: 0; &:hover, &:active{ @include box-shadow(none); outline:none; } a{ @include text-shadow(#fff 0 1px 1px); - color: $color-text-blue-link; + color: $color-text-blue-link; font-size: 13px; font-weight: normal; - line-height: 24px; - padding:0 25px 0 10px; + line-height: 24px; + padding:0 25px 0 10px; &:hover, &:active{ @include box-shadow(none); - outline:none; + outline:none; } &:hover{ @include text-shadow(#fff 0 10px 10px); color: darken($color-text-blue-link,8%); } - /* Arrow */ + /* Arrow */ &:after { background: sprite($sprites32, arrow_down_lighter) no-repeat; - border-bottom: 0; + border-bottom: 0; content: ""; display: inline-block; height: 16px; margin-left: 6px; - width: 16px; + width: 16px; } - &:hover:after { + &:hover:after { background: sprite($sprites32, arrow_down_darker) no-repeat; - } + } } /* Make arrow point in up when nav open */ &.ui-state-active a { @@ -270,7 +263,7 @@ $border: 1px solid darken(#D9D9D9, 15%); &:hover:after { background: sprite($sprites32, arrow_up_darker) no-repeat; } - } + } } } /* Style the panel for actions-menu */ @@ -288,9 +281,9 @@ $border: 1px solid darken(#D9D9D9, 15%); margin:0; margin-top:2px; max-width:250px; - padding: 8px 0 2px; + padding: 8px 0 2px; position:absolute; - z-index:1; + z-index:1; min-width: 190px; //Styles for the information displayed in popup above the main action buttons @@ -300,29 +293,29 @@ $border: 1px solid darken(#D9D9D9, 15%); padding: 0 20px 0 0; margin-right: 10px; margin-left: 10px; - p.meta-info { + p.meta-info { color: #999; font-size: 11px; line-height: 16px; margin-bottom: 8px; white-space: nowrap; - } - } + } + } button.ss-ui-button{ width:100%; &:hover, &:focus, &:active{ @include box-shadow(none); - background-color: darken($tab-panel-texture-color,4%); + background-color: darken($tab-panel-texture-color,4%); outline:none; - } + } } } /* Re-align last tab */ .last .ui-tabs-panel.ss-ui-action-tab{ left:auto; - right:-1px; + right:-1px; } - } + } } .cms-content-actions .Actions{ From c0a122613ea04c11e442475ce34981b2264eb457 Mon Sep 17 00:00:00 2001 From: Paul Clarke Date: Wed, 12 Dec 2012 15:25:05 +1300 Subject: [PATCH 26/49] Bug fix and code clean up Removed tempory button fixs not needed any more, Removed unrelated commits, fix for preview note --- admin/css/screen.css | 5 +---- admin/scss/_forms.scss | 12 ------------ css/GridField.css | 4 +++- css/UploadField.css | 2 +- scss/GridField.scss | 5 ++++- scss/UploadField.scss | 2 +- 6 files changed, 10 insertions(+), 20 deletions(-) diff --git a/admin/css/screen.css b/admin/css/screen.css index ded878d9d..1f9c8bf36 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -196,7 +196,6 @@ form.small .field input.text, form.small .field textarea, form.small .field sele .field.remove-splitter { border-bottom: none; box-shadow: none; } /** ---------------------------------------------------- Buttons ---------------------------------------------------- */ -.cms { /* Tempory fix: Overide default icon & text styles as the order of the icon and text are mixed up */ } .cms .button-no-style button, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button { -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: none; border: none; color: #0073c1; display: block; font-weight: normal; margin: 0; outline: none; padding-left: 10px; padding-right: 10px; text-align: left; text-shadow: none; white-space: normal; } .cms .button-no-style button.ss-ui-action-destructive, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-action-destructive { color: #c22730; } .cms .button-no-style button span, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button span { padding-left: 0; padding-right: 0; } @@ -226,8 +225,6 @@ form.small .field input.text, form.small .field textarea, form.small .field sele .cms .ss-ui-buttonset .ui-button { margin-left: -1px; } .cms .ss-ui-buttonset { margin-left: 1px; } .cms .ss-ui-loading-icon { background: url(../../images/network-save.gif) no-repeat; display: block; width: 16px; height: 16px; } -.cms .south .Actions .ss-ui-button.ss-ui-action-constructive .ui-button-text-alternate { margin-left: 22px; } -.cms .south .Actions .ss-ui-button.ss-ui-action-constructive .ui-icon { position: absolute; left: 10px; top: 5px; } /** ---------------------------------------------------- Grouped form fields ---------------------------------------------------- */ .fieldgroup .fieldgroup-field { float: left; display: block; padding: 8px 0 0 8px; } @@ -919,7 +916,7 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; } /* Styling for the preview screen sizes */ .cms-preview { background-color: #eceff1; height: 100%; width: 100%; } -.cms-preview .preview-note { color: #CDD7DC; display: block; font-size: 22px; font-weight: bold; height: 82px; margin-top: -50px; margin-left: -150px; /* half of width */ position: absolute; text-align: center; text-shadow: 0 1px 0 #fff; top: 50%; width: 300px; } +.cms-preview .preview-note { color: #CDD7DC; display: block; font-size: 22px; font-weight: bold; height: 82px; margin-top: -50px; margin-left: -150px; /* half of width */ position: absolute; text-align: center; text-shadow: 0 1px 0 #fff; top: 50%; left: 50%; width: 300px; } .cms-preview .preview-note span { background: url('../images/sprites-64x64-s88957ee578.png') 0 0 no-repeat; display: block; height: 41px; margin: 0 auto 20px; width: 50px; } .cms-preview .preview-scroll { height: 100%; overflow: auto; position: relative; width: 100%; } .cms-preview .preview-scroll .preview-device-outer { height: 100%; width: 100%; } diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss index abeea4f0f..2a114703f 100644 --- a/admin/scss/_forms.scss +++ b/admin/scss/_forms.scss @@ -509,18 +509,6 @@ form.small .field, .field.small { width: 16px; height: 16px; } - - /* Tempory fix: Overide default icon & text styles as the order of the icon and text are mixed up */ - .south .Actions .ss-ui-button.ss-ui-action-constructive { - .ui-button-text-alternate { - margin-left: 22px; - } - .ui-icon { - position: absolute; - left: 10px; - top: 5px; - } - } } /** ---------------------------------------------------- diff --git a/css/GridField.css b/css/GridField.css index ec6e5bddb..a7d3cf7bd 100644 --- a/css/GridField.css +++ b/css/GridField.css @@ -11,6 +11,7 @@ /*Mixin used to generate slightly smaller text and forms Used in side panels and action tabs */ +.cms .ss-gridfield > div { margin-bottom: 36px; } .cms .ss-gridfield > div.addNewGridFieldButton { margin-bottom: 0; } .cms .ss-gridfield > div.addNewGridFieldButton .action { margin-bottom: 12px; } .cms .ss-gridfield[data-selectable] tr.ui-selected, .cms .ss-gridfield[data-selectable] tr.ui-selecting { background: #FFFAD6 !important; } @@ -21,10 +22,11 @@ Used in side panels and action tabs .cms .ss-gridfield .right > * { float: right; margin-left: 8px; } .cms .ss-gridfield .right .pagination-records-number { font-size: 1.0em; padding: 6px 3px 6px 0; color: white; text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.2); font-weight: normal; } .cms .ss-gridfield .left { float: left; } -.cms .ss-gridfield .left > * { margin-right: 8px; float: left; } +.cms .ss-gridfield .left > * { margin-right: 8px; float: left; font-size: 14.4px; } .cms .ss-gridfield .ss-gridfield-buttonrow { font-size: 14.4px; } .cms .ss-gridfield .grid-levelup { text-indent: -9999em; margin-bottom: 6px; } .cms .ss-gridfield .grid-levelup a.list-parent-link { background: transparent url(../images/gridfield-level-up.png) no-repeat 0 0; display: block; } +.cms .ss-gridfield .add-existing-autocompleter { width: 500px; } .cms .ss-gridfield .add-existing-autocompleter span { display: -moz-inline-stack; display: inline-block; vertical-align: top; *vertical-align: auto; zoom: 1; *display: inline; } .cms .ss-gridfield .add-existing-autocompleter input.relation-search { width: 270px; margin-bottom: 12px; } .cms .ss-gridfield .grid-csv-button, .cms .ss-gridfield .grid-print-button { margin-bottom: 12px; display: -moz-inline-stack; display: inline-block; vertical-align: middle; *vertical-align: auto; zoom: 1; *display: inline; } diff --git a/css/UploadField.css b/css/UploadField.css index 6ef200f4f..70e18efca 100644 --- a/css/UploadField.css +++ b/css/UploadField.css @@ -13,7 +13,7 @@ Used in side panels and action tabs .ss-uploadfield .clear { clear: both; } .ss-insert-media .ss-uploadfield { margin-top: 20px; } .ss-insert-media .ss-uploadfield h4 { float: left; } -.ss-uploadfield .middleColumn { width: 510px; padding: 0; background: #fff; border: 1px solid #b3b3b3; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #efefef), color-stop(10%, #ffffff), color-stop(90%, #ffffff), color-stop(100%, #efefef)); background-image: -webkit-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -moz-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -o-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); } +.ss-uploadfield .middleColumn { width: 526px; padding: 0; background: #fff; border: 1px solid #b3b3b3; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #efefef), color-stop(10%, #ffffff), color-stop(90%, #ffffff), color-stop(100%, #efefef)); background-image: -webkit-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -moz-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: -o-linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); background-image: linear-gradient(#efefef, #ffffff 10%, #ffffff 90%, #efefef); } .ss-uploadfield .ss-uploadfield-item { margin: 0; padding: 15px; overflow: auto; } .ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview { height: 60px; line-height: 60px; width: 80px; text-align: center; font-weight: bold; float: left; overflow: hidden; } .ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview.ss-uploadfield-dropzone { -webkit-box-shadow: gray 0 0 4px 0 inset; -moz-box-shadow: gray 0 0 4px 0 inset; box-shadow: gray 0 0 4px 0 inset; border: 2px dashed gray; background: #d0d3d5; display: none; } diff --git a/scss/GridField.scss b/scss/GridField.scss index 25d9995e4..32bf65879 100644 --- a/scss/GridField.scss +++ b/scss/GridField.scss @@ -39,6 +39,7 @@ $gf_grid_x: 16px; .cms { .ss-gridfield { & > div { + margin-bottom: $gf_grid_y*3; &.addNewGridFieldButton{ margin-bottom: 0; .action { @@ -86,12 +87,13 @@ $gf_grid_x: 16px; & > * { margin-right:$gf_grid_x/2; float: left; + font-size: $gf_grid_y*1.2; } } .ss-gridfield-buttonrow { font-size: $gf_grid_y*1.2; - } + } } .ss-gridfield { @@ -111,6 +113,7 @@ $gf_grid_x: 16px; width: 270px; margin-bottom: $gf_grid_y; } + width: 500px; } .grid-csv-button, .grid-print-button { margin-bottom: $gf_grid_y; diff --git a/scss/UploadField.scss b/scss/UploadField.scss index cfbe8ffda..d2474504e 100644 --- a/scss/UploadField.scss +++ b/scss/UploadField.scss @@ -22,7 +22,7 @@ .middleColumn { // TODO .middleColumn styling should probably be theme specific (eg cms ui will look different than blackcandy) // so we should move this style into the cms and black candy files - width: 510px; + width: 526px; padding: 0; background: #fff; border: 1px solid lighten($color-medium-separator, 20%); From 7e4629073a267d732591472ae7c0f91a44b43208 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Dec 2012 19:01:27 +0100 Subject: [PATCH 27/49] NEW Date->Ago() with "less than a minute" support --- model/fieldtypes/Date.php | 76 ++++++++++++++++++++---------------- tests/model/DateTest.php | 42 ++++++++++++++++++++ tests/model/DatetimeTest.php | 60 ++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 33 deletions(-) diff --git a/model/fieldtypes/Date.php b/model/fieldtypes/Date.php index f789dd82c..a4c4dc31a 100644 --- a/model/fieldtypes/Date.php +++ b/model/fieldtypes/Date.php @@ -197,58 +197,68 @@ class Date extends DBField { } /** - * Returns the number of seconds/minutes/hours/days or months since the timestamp + * Returns the number of seconds/minutes/hours/days or months since the timestamp. + * + * @param boolean $includeSeconds Show seconds, or just round to "less than a minute". + * @return String */ - public function Ago() { + public function Ago($includeSeconds = true) { if($this->value) { - if(strtotime($this->value) == time() || time() > strtotime($this->value)) { + $time = SS_Datetime::now()->Format('U'); + if(strtotime($this->value) == $time || $time > strtotime($this->value)) { return _t( 'Date.TIMEDIFFAGO', "{difference} ago", 'Natural language time difference, e.g. 2 hours ago', - array('difference' => $this->TimeDiff()) + array('difference' => $this->TimeDiff($includeSeconds)) ); } else { return _t( 'Date.TIMEDIFFIN', "in {difference}", 'Natural language time difference, e.g. in 2 hours', - array('difference' => $this->TimeDiff()) + array('difference' => $this->TimeDiff($includeSeconds)) ); } } } - public function TimeDiff() { + /** + * @param boolean $includeSeconds Show seconds, or just round to "less than a minute". + * @return String + */ + public function TimeDiff($includeSeconds = true) { + if(!$this->value) return false; - if($this->value) { - $ago = abs(time() - strtotime($this->value)); - - if($ago < 60) { - $span = $ago; - return ($span != 1) ? "{$span} "._t("Date.SECS", " secs") : "{$span} "._t("Date.SEC", " sec"); - } - if($ago < 3600) { - $span = round($ago/60); - return ($span != 1) ? "{$span} "._t("Date.MINS", " mins") : "{$span} "._t("Date.MIN", " min"); - } - if($ago < 86400) { - $span = round($ago/3600); - return ($span != 1) ? "{$span} "._t("Date.HOURS", " hours") : "{$span} "._t("Date.HOUR", " hour"); - } - if($ago < 86400*30) { - $span = round($ago/86400); - return ($span != 1) ? "{$span} "._t("Date.DAYS", " days") : "{$span} "._t("Date.DAY", " day"); - } - if($ago < 86400*365) { - $span = round($ago/86400/30); - return ($span != 1) ? "{$span} "._t("Date.MONTHS", " months") : "{$span} "._t("Date.MONTH", " month"); - } - if($ago > 86400*365) { - $span = round($ago/86400/365); - return ($span != 1) ? "{$span} "._t("Date.YEARS", " years") : "{$span} "._t("Date.YEAR", " year"); - } + $time = SS_Datetime::now()->Format('U'); + $ago = abs($time - strtotime($this->value)); + + if($ago < 60 && $includeSeconds) { + $span = $ago; + $result = ($span != 1) ? "{$span} "._t("Date.SECS", "secs") : "{$span} "._t("Date.SEC", "sec"); + } elseif($ago < 60) { + $result = _t('Date.LessThanMinuteAgo', 'less than a minute'); + } elseif($ago < 3600) { + $span = round($ago/60); + $result = ($span != 1) ? "{$span} "._t("Date.MINS", "mins") : "{$span} "._t("Date.MIN", "min"); + } elseif($ago < 86400) { + $span = round($ago/3600); + $result = ($span != 1) ? "{$span} "._t("Date.HOURS", "hours") : "{$span} "._t("Date.HOUR", "hour"); + } elseif($ago < 86400*30) { + $span = round($ago/86400); + $result = ($span != 1) ? "{$span} "._t("Date.DAYS", "days") : "{$span} "._t("Date.DAY", "day"); + } elseif($ago < 86400*365) { + $span = round($ago/86400/30); + $result = ($span != 1) ? "{$span} "._t("Date.MONTHS", "months") : "{$span} "._t("Date.MONTH", "month"); + } elseif($ago > 86400*365) { + $span = round($ago/86400/365); + $result = ($span != 1) ? "{$span} "._t("Date.YEARS", "years") : "{$span} "._t("Date.YEAR", "year"); } + + // Replace duplicate spaces, backwards compat with existing translations + $result = preg_replace('/\s+/', ' ', $result); + + return $result; } /** diff --git a/tests/model/DateTest.php b/tests/model/DateTest.php index 11fbed9c4..1e805c2ca 100644 --- a/tests/model/DateTest.php +++ b/tests/model/DateTest.php @@ -160,4 +160,46 @@ class DateTest extends SapphireTest { $this->assertEquals('03 Apr 3000', $date->Format('d M Y')); } + public function testAgoInPast() { + SS_Datetime::set_mock_now('2000-12-31 12:00:00'); + + $this->assertEquals( + '10 years ago', + DBField::create_field('Date', '1990-12-31')->Ago(), + 'Exact past match on years' + ); + + $this->assertEquals( + '10 years ago', + DBField::create_field('Date', '1990-12-30')->Ago(), + 'Approximate past match on years' + ); + + $this->assertEquals( + '1 year ago', + DBField::create_field('Date', '1999-12-30')->Ago(), + 'Approximate past match in singular' + ); + + SS_Datetime::clear_mock_now(); + } + + public function testAgoInFuture() { + SS_Datetime::set_mock_now('2000-12-31 00:00:00'); + + $this->assertEquals( + 'in 100 years', + DBField::create_field('Date', '2100-12-31')->Ago(), + 'Exact past match on years' + ); + + $this->assertEquals( + 'in 1 day', + DBField::create_field('Date', '2001-01-01')->Ago(), + 'Approximate past match on minutes' + ); + + SS_Datetime::clear_mock_now(); + } + } diff --git a/tests/model/DatetimeTest.php b/tests/model/DatetimeTest.php index b078a9ed6..12246bc29 100644 --- a/tests/model/DatetimeTest.php +++ b/tests/model/DatetimeTest.php @@ -89,4 +89,64 @@ class SS_DatetimeTest extends SapphireTest { $this->assertEquals('2001-12-31%2022:10:59', $date->URLDateTime()); } + public function testAgoInPast() { + SS_Datetime::set_mock_now('2000-12-31 12:00:00'); + + $this->assertEquals( + '10 years ago', + DBField::create_field('SS_Datetime', '1990-12-31 12:00:00')->Ago(), + 'Exact past match on years' + ); + + $this->assertEquals( + '10 years ago', + DBField::create_field('SS_Datetime', '1990-12-30 12:00:00')->Ago(), + 'Approximate past match on years' + ); + + $this->assertEquals( + '1 year ago', + DBField::create_field('SS_Datetime', '1999-12-30 12:00:12')->Ago(), + 'Approximate past match in singular' + ); + + $this->assertEquals( + '50 mins ago', + DBField::create_field('SS_Datetime', '2000-12-31 11:10:11')->Ago(), + 'Approximate past match on minutes' + ); + + $this->assertEquals( + '59 secs ago', + DBField::create_field('SS_Datetime', '2000-12-31 11:59:01')->Ago(), + 'Approximate past match on seconds' + ); + + $this->assertEquals( + 'less than a minute ago', + DBField::create_field('SS_Datetime', '2000-12-31 11:59:01')->Ago(false), + 'Approximate past match on seconds with $includeSeconds=false' + ); + + SS_Datetime::clear_mock_now(); + } + + public function testAgoInFuture() { + SS_Datetime::set_mock_now('2000-12-31 00:00:00'); + + $this->assertEquals( + 'in 100 years', + DBField::create_field('SS_Datetime', '2100-12-31 12:00:00')->Ago(), + 'Exact past match on years' + ); + + $this->assertEquals( + 'in 1 hour', + DBField::create_field('SS_Datetime', '2000-12-31 1:01:05')->Ago(), + 'Approximate past match on minutes' + ); + + SS_Datetime::clear_mock_now(); + } + } From c6b1d4aa6bb80e0506a1fc1009e9489006a88b53 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 6 Dec 2012 16:25:45 +0100 Subject: [PATCH 28/49] API Storing alternative DB name in cookie rather than session Session is not initialized by the time we need to use the setting in DB::connect(). Cookie values get initialized automatically for each request. Tightened name format validation to ensure it can only be used for temporary databases, rather than switching the browser session to a different production database. Encrypting token for secure cookie usage. Added dev/generatesecuretoken to generate this token. Not storing in YML config directly because of web access issues. --- dev/DevelopmentAdmin.php | 40 +++++++++++++----- dev/TestRunner.php | 35 ++++++++-------- docs/en/changelogs/3.0.4.md | 12 ++++++ model/DB.php | 82 ++++++++++++++++++++++++++++++++++--- security/Security.php | 8 ++++ tests/model/DBTest.php | 36 ++++++++++++++++ 6 files changed, 181 insertions(+), 32 deletions(-) create mode 100644 docs/en/changelogs/3.0.4.md create mode 100644 tests/model/DBTest.php diff --git a/dev/DevelopmentAdmin.php b/dev/DevelopmentAdmin.php index 07d0874e7..786d7a2f1 100644 --- a/dev/DevelopmentAdmin.php +++ b/dev/DevelopmentAdmin.php @@ -18,15 +18,16 @@ class DevelopmentAdmin extends Controller { ); static $allowed_actions = array( - 'index', - 'tests', - 'jstests', - 'tasks', - 'viewmodel', - 'build', - 'reset', - 'viewcode' - ); + 'index', + 'tests', + 'jstests', + 'tasks', + 'viewmodel', + 'build', + 'reset', + 'viewcode', + 'generatesecuretoken', + ); public function init() { parent::init(); @@ -56,7 +57,7 @@ class DevelopmentAdmin extends Controller { $matched = false; if(isset($_FILE_TO_URL_MAPPING[$testPath])) { $matched = true; - break; + break; } $testPath = dirname($testPath); } @@ -172,6 +173,25 @@ class DevelopmentAdmin extends Controller { } } + /** + * Generate a secure token which can be used as a crypto key. + * Returns the token and suggests PHP configuration to set it. + */ + public function generatesecuretoken() { + $generator = Injector::inst()->create('RandomGenerator'); + $token = $generator->randomToken('sha1'); + + echo <<update('Security', 'token', '$token'); + + +TXT; + } + public function reset() { $link = BASE_URL.'/dev/tests/startsession'; diff --git a/dev/TestRunner.php b/dev/TestRunner.php index 44faddd50..d186ae87e 100644 --- a/dev/TestRunner.php +++ b/dev/TestRunner.php @@ -349,6 +349,9 @@ class TestRunner extends Controller { * See {@link setdb()} for an alternative approach which just sets a database * name, and is used for more advanced use cases like interacting with test databases * directly during functional tests. + * + * Requires PHP's mycrypt extension in order to set the database name + * as an encrypted cookie. */ public function startsession() { if(!Director::isLive()) { @@ -420,7 +423,7 @@ HTML; } /** - * Set an alternative database name in the current browser session. + * Set an alternative database name in the current browser session as a cookie. * Useful for functional testing libraries like behat to create a "clean slate". * Does not actually create the database, that's usually handled * by {@link SapphireTest::create_temp_db()}. @@ -432,33 +435,33 @@ HTML; * * See {@link startsession()} for a different approach which actually creates * the DB and loads a fixture file instead. + * + * Requires PHP's mycrypt extension in order to set the database name + * as an encrypted cookie. */ public function setdb() { if(Director::isLive()) { - return $this->permissionFailure("dev/tests/setdb can only be used on dev and test sites"); + return $this->httpError(403, "dev/tests/setdb can only be used on dev and test sites"); } - if(!isset($_GET['database'])) { - return $this->permissionFailure("dev/tests/setdb must be used with a 'database' parameter"); + return $this->httpError(400, "dev/tests/setdb must be used with a 'database' parameter"); } - $database_name = $_GET['database']; + $name = $_GET['database']; $prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_'; $pattern = strtolower(sprintf('#^%stmpdb\d{7}#', $prefix)); - if(!preg_match($pattern, $database_name)) { - return $this->permissionFailure("Invalid database name format"); + if($name && !preg_match($pattern, $name)) { + return $this->httpError(400, "Invalid database name format"); } - DB::set_alternative_database_name($database_name); + DB::set_alternative_database_name($name); - return "

    Set database session to '$database_name'. Time to start testing; where would you like to start?

    - "; + if($name) { + return "

    Set database session to '$name'.

    "; + } else { + return "

    Unset database session.

    "; + } + } public function emptydb() { diff --git a/docs/en/changelogs/3.0.4.md b/docs/en/changelogs/3.0.4.md new file mode 100644 index 000000000..fd9f55e1f --- /dev/null +++ b/docs/en/changelogs/3.0.4.md @@ -0,0 +1,12 @@ +# 3.0.4 + +## Overview + + * Changed `dev/tests/setdb` and `dev/tests/startsession` from session to cookie storage. + +## Upgrading + + * If you are using `dev/tests/setdb` and `dev/tests/startsession`, + you'll need to configure a secure token in order to encrypt the cookie value: + Simply run `sake dev/generatesecuretoken` and add the resulting code to your `mysite/_config.php`. + Note that this functionality now requires the PHP `mcrypt` extension. \ No newline at end of file diff --git a/model/DB.php b/model/DB.php index 70ef907ac..d9c1a3dac 100644 --- a/model/DB.php +++ b/model/DB.php @@ -60,19 +60,89 @@ class DB { } /** - * Set an alternative database to use for this browser session. - * This is useful when using testing systems other than SapphireTest; for example, Windmill. + * Set an alternative database in a browser cookie, + * with the cookie lifetime set to the browser session. + * This is useful for integration testing on temporary databases. + * + * There is a strict naming convention for temporary databases to avoid abuse: + * (default: 'ss_') + tmpdb + <7 digits> + * As an additional security measure, temporary databases will + * be ignored in "live" mode. + * + * Note that the database will be set on the next request. * Set it to null to revert to the main database. */ - public static function set_alternative_database_name($dbname) { - Session::set("alternativeDatabaseName", $dbname); + public static function set_alternative_database_name($name = null) { + if($name) { + if(!self::valid_alternative_database_name($name)) { + throw new InvalidArgumentException(sprintf( + 'Invalid alternative database name: "%s"', + $name + )); + } + + $key = Config::inst()->get('Security', 'token'); + if(!$key) { + throw new LogicException('"Security.token" not found, run "sake dev/generatesecuretoken"'); + } + if(!function_exists('mcrypt_encrypt')) { + throw new LogicException('DB::set_alternative_database_name() requires the mcrypt PHP extension'); + } + + $key = md5($key); // Ensure key is correct length for chosen cypher + $ivSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CFB); + $iv = mcrypt_create_iv($ivSize); + $encrypted = mcrypt_encrypt( + MCRYPT_RIJNDAEL_256, $key, $name, MCRYPT_MODE_CFB, $iv + ); + + // Set to browser session lifetime, and restricted to HTTP access only + Cookie::set("alternativeDatabaseName", base64_encode($encrypted), 0, null, null, false, true); + Cookie::set("alternativeDatabaseNameIv", base64_encode($iv), 0, null, null, false, true); + } else { + Cookie::set("alternativeDatabaseName", null, 0, null, null, false, true); + Cookie::set("alternativeDatabaseNameIv", null, 0, null, null, false, true); + } } /** * Get the name of the database in use */ public static function get_alternative_database_name() { - return Session::get("alternativeDatabaseName"); + $name = Cookie::get("alternativeDatabaseName"); + $iv = Cookie::get("alternativeDatabaseNameIv"); + + if($name) { + $key = Config::inst()->get('Security', 'token'); + if(!$key) { + throw new LogicException('"Security.token" not found, run "sake dev/generatesecuretoken"'); + } + if(!function_exists('mcrypt_encrypt')) { + throw new LogicException('DB::set_alternative_database_name() requires the mcrypt PHP extension'); + } + $key = md5($key); // Ensure key is correct length for chosen cypher + $decrypted = mcrypt_decrypt( + MCRYPT_RIJNDAEL_256, $key, base64_decode($name), MCRYPT_MODE_CFB, base64_decode($iv) + ); + return (self::valid_alternative_database_name($decrypted)) ? $decrypted : false; + } else { + return false; + } + } + + /** + * Determines if the name is valid, as a security + * measure against setting arbitrary databases. + * + * @param String $name + * @return Boolean + */ + public static function valid_alternative_database_name($name) { + if(Director::isLive()) return false; + + $prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_'; + $pattern = strtolower(sprintf('/^%stmpdb\d{7}$/', $prefix)); + return (bool)preg_match($pattern, $name); } /** @@ -84,7 +154,7 @@ class DB { */ public static function connect($databaseConfig) { // This is used by TestRunner::startsession() to test up a test session using an alt - if($name = Session::get('alternativeDatabaseName')) { + if($name = self::get_alternative_database_name()) { $databaseConfig['database'] = $name; } diff --git a/security/Security.php b/security/Security.php index 5b3daf07c..f8da0e029 100644 --- a/security/Security.php +++ b/security/Security.php @@ -82,6 +82,14 @@ class Security extends Controller { * @var array|string */ protected static $default_message_set = ''; + + /** + * Random secure token, can be used as a crypto key internally. + * Generate one through 'sake dev/generatesecuretoken'. + * + * @var String + */ + public static $token; /** * Get location of word list file diff --git a/tests/model/DBTest.php b/tests/model/DBTest.php new file mode 100644 index 000000000..136d74905 --- /dev/null +++ b/tests/model/DBTest.php @@ -0,0 +1,36 @@ +origEnvType = Director::get_environment_type(); + Director::set_environment_type('dev'); + + parent::setUp(); + } + + function tearDown() { + Director::set_environment_type($this->origEnvType); + + parent::tearDown(); + } + + function testValidAlternativeDatabaseName() { + $this->assertTrue(DB::valid_alternative_database_name('ss_tmpdb1234567')); + $this->assertFalse(DB::valid_alternative_database_name('ss_tmpdb12345678')); + $this->assertFalse(DB::valid_alternative_database_name('tmpdb1234567')); + $this->assertFalse(DB::valid_alternative_database_name('random')); + $this->assertFalse(DB::valid_alternative_database_name('')); + + $origEnvType = Director::get_environment_type(); + Director::set_environment_type('live'); + $this->assertFalse(DB::valid_alternative_database_name('ss_tmpdb1234567')); + Director::set_environment_type($origEnvType); + } + +} \ No newline at end of file From f41f30711869c6807778d3bc164a65885f604b25 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 14 Dec 2012 00:09:30 +0100 Subject: [PATCH 29/49] Fixed spacing --- dev/DevelopmentAdmin.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dev/DevelopmentAdmin.php b/dev/DevelopmentAdmin.php index 786d7a2f1..e4bd40864 100644 --- a/dev/DevelopmentAdmin.php +++ b/dev/DevelopmentAdmin.php @@ -18,16 +18,16 @@ class DevelopmentAdmin extends Controller { ); static $allowed_actions = array( - 'index', - 'tests', - 'jstests', - 'tasks', - 'viewmodel', - 'build', - 'reset', - 'viewcode', - 'generatesecuretoken', - ); + 'index', + 'tests', + 'jstests', + 'tasks', + 'viewmodel', + 'build', + 'reset', + 'viewcode', + 'generatesecuretoken', + ); public function init() { parent::init(); @@ -57,7 +57,7 @@ class DevelopmentAdmin extends Controller { $matched = false; if(isset($_FILE_TO_URL_MAPPING[$testPath])) { $matched = true; - break; + break; } $testPath = dirname($testPath); } From aed58a55c485e0fb8b423f47175529260a4d2548 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Dec 2012 22:03:43 +0100 Subject: [PATCH 30/49] Loading indicator for "more options" buttons --- admin/css/screen.css | 10 +++--- admin/javascript/LeftAndMain.ActionTabSet.js | 33 ++++++++++++++------ admin/scss/_actionTabs.scss | 2 +- admin/scss/_forms.scss | 8 +++++ 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/admin/css/screen.css b/admin/css/screen.css index 1f9c8bf36..e075d1518 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -200,6 +200,8 @@ form.small .field input.text, form.small .field textarea, form.small .field sele .cms .button-no-style button.ss-ui-action-destructive, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.ss-ui-action-destructive { color: #c22730; } .cms .button-no-style button span, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button span { padding-left: 0; padding-right: 0; } .cms .button-no-style button:hover, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button:hover, .cms .button-no-style button:focus, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button:focus, .cms .button-no-style button:active, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button:active { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; outline: none; background: none; border: none; } +.cms .button-no-style button.loading, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.loading { background: transparent url(../../images/network-save.gif) no-repeat 8px center; } +.cms .button-no-style button.loading .ui-button-text, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel button.loading .ui-button-text { padding-left: 20px; } .cms .Actions > *, .cms .cms-actions-row > * { display: block; float: left; margin-right: 8px; } .cms .Actions > *:last-child, .cms .cms-actions-row > *:last-child { margin-right: 0; } .cms .Actions { min-height: 30px; overflow: auto; padding: 8px 12px; } @@ -948,20 +950,20 @@ li.class-ErrorPage > a a .jstree-pageicon { background-position: 0 -112px; } * styling should not apply * **********************************************/ -.cms .ss-ui-action-tabset { float: left; position: relative; /* +.cms .ss-ui-action-tabset { float: left; position: relative; /* Styles for the tab-nav of the site tree implementation of ss-ui-action-tabset */ /* position a checkbox & icon within a tab */ /* Styles for the cms-actions in tree view, to use more limited space. Title hidden in tree view, until hover/active state added. Active is applied to the first tab within the template, so there should always be one title -visible. Added and removed with js in TabSet.js */ /**************************************************************** +visible. Added and removed with js in TabSet.js */ /**************************************************************** Styles for the actions-menu implementation of ss-ui-action-tabset ****************************************************************/ } .cms .ss-ui-action-tabset.multi { /* Style the tab panels */ } -.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav { -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; overflow: hidden; *zoom: 1; border: 1px solid #b3b3b3; float: left; height: 28px; overflow: visible; padding: 0; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav { -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; overflow: hidden; *zoom: 1; border: 1px solid #b3b3b3; float: left; overflow: visible; padding: 0; } .cms .ss-ui-action-tabset.multi ul.ui-tabs-nav:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } -.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li { background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y4ZjhmOCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f8f8f8), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -moz-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -o-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: linear-gradient(top, #f8f8f8, #d9d9d9); -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: #eaeaea; border: none; border-right: 1px solid #eee; border-left: 1px solid #b3b3b3; margin: 0; overflow: visible; width: 110px; } +.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li { background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y4ZjhmOCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f8f8f8), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -moz-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -o-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: linear-gradient(top, #f8f8f8, #d9d9d9); -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: #eaeaea; border: none; border-right: 1px solid #eee; border-left: 1px solid #b3b3b3; margin: 0; overflow: visible; min-width: 110px; } .cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li:active { outline: none; box-shadow: none; -webkit-box-shadow: none; } .cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; background: #f8f8f8; border-bottom: none !important; } .cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active a { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; } diff --git a/admin/javascript/LeftAndMain.ActionTabSet.js b/admin/javascript/LeftAndMain.ActionTabSet.js index b1e36a097..0806be353 100644 --- a/admin/javascript/LeftAndMain.ActionTabSet.js +++ b/admin/javascript/LeftAndMain.ActionTabSet.js @@ -24,6 +24,19 @@ this.tabs({'collapsible': true, 'active': false}); }, + onremove: function() { + // Remove all bound events. + // This guards against an edge case where the click handlers are not unbound + // because the panel is still open when the ajax edit form reloads. + var frame = $('.cms').find('iframe'); + frame.each(function(index, iframe){ + $(iframe).contents().off('click.ss-ui-action-tabset'); + }); + $(document).off('click.ss-ui-action-tabset'); + + this._super(); + }, + /** * Deal with available vertical space */ @@ -53,27 +66,29 @@ var panel, frame; panel = $(event.target).closest('.ss-ui-action-tabset .ui-tabs-panel'); - // If anything except the ui-nav button is clicked, - // close panel and remove handler - if (!$(event.target).closest(that).length || $(panel).length) { + // If anything except the ui-nav button or panel is clicked, + // close panel and remove handler. We can't close if click was + // within panel, as it might've caused a button action, + // and we need to show its loading indicator. + if (!$(event.target).closest(that).length && !panel.length) { that.tabs('option', 'active', false); // close tabs // remove click event from objects it is bound to (iframe's and document) frame = $('.cms').find('iframe'); frame.each(function(index, iframe){ - $(iframe).contents().off('click', closeHandler); + $(iframe).contents().off('click.ss-ui-action-tabset', closeHandler); }); - $(document).off('click', closeHandler); + $(document).off('click.ss-ui-action-tabset', closeHandler); } }; // Bind click event to document, and use closeHandler to handle the event - $(document).on('click', closeHandler); + $(document).on('click.ss-ui-action-tabset', closeHandler); // Make sure iframe click also closes tab // iframe needs a special case, else the click event will not register here if(frame.length > 0){ frame.each(function(index, iframe) { - $(iframe).contents().on('click', closeHandler); + $(iframe).contents().on('click.ss-ui-action-tabset', closeHandler); }); } }, @@ -131,7 +146,7 @@ */ 'ontabsbeforeactivate': function(event, ui) { this._super(event, ui); - //Set the position of the opening tab (if it exists) + //Set the position of the opening tab (if it exists) if($(ui.newPanel).length > 0){ $(ui.newPanel).css('left', ui.newTab.position().left+"px"); } @@ -211,7 +226,7 @@ this._super(event, ui); // Reset position of tabs, else anyone going between the large // and the small sitetree will see broken tabs - // Apply styles with .css, to avoid overriding currently applied styles + // Apply styles with .css, to avoid overriding currently applied styles $(ui.newPanel).css({'left': 'auto', 'right': 'auto'}); if($(ui.newPanel).length > 0){ diff --git a/admin/scss/_actionTabs.scss b/admin/scss/_actionTabs.scss index bfd6da56c..d88f6e1b5 100644 --- a/admin/scss/_actionTabs.scss +++ b/admin/scss/_actionTabs.scss @@ -302,7 +302,7 @@ $border: 1px solid darken(#D9D9D9, 15%); } } button.ss-ui-button{ - width:100%; + width: 100%; &:hover, &:focus, &:active{ @include box-shadow(none); background-color: darken($tab-panel-texture-color,4%); diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss index 2a114703f..b9ff0b522 100644 --- a/admin/scss/_forms.scss +++ b/admin/scss/_forms.scss @@ -313,6 +313,14 @@ form.small .field, .field.small { background:none; border:none; } + &.loading { + background: transparent url(../../images/network-save.gif) no-repeat $grid-x center; + .ui-button-text { + padding-left: 16px /* icon */ + ($grid-x/2); + } + + + } } } From 644cc79ebb06a405101bd2d0521b82164fa77aff Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 14 Dec 2012 01:05:11 +0100 Subject: [PATCH 31/49] API Removed methods previously deprecated in 3.0 --- docs/en/changelogs/3.1.0.md | 11 ++++- forms/Form.php | 8 ---- forms/FormField.php | 10 ----- security/Group.php | 11 ----- security/Member.php | 82 ---------------------------------- security/PasswordEncryptor.php | 23 ---------- tests/security/GroupTest.php | 24 ---------- 7 files changed, 10 insertions(+), 159 deletions(-) diff --git a/docs/en/changelogs/3.1.0.md b/docs/en/changelogs/3.1.0.md index 78a6149c4..38e3a0271 100644 --- a/docs/en/changelogs/3.1.0.md +++ b/docs/en/changelogs/3.1.0.md @@ -20,4 +20,13 @@ * Deprecated global email methods `htmlEmail()` and `plaintextEmail`, as well as various email helper methods like `encodeMultipart()`. Use the `Email` API, or the `Mailer` class where applicable. * Removed non-functional `$inlineImages` option for sending emails * Removed support for keyed arrays in `SelectionGroup`, use new `SelectionGroup_Item` object - to populate the list instead (see [API docs](api:SelectionGroup)). \ No newline at end of file + to populate the list instead (see [API docs](api:SelectionGroup)). + * Removed `Form->Name()`: Use getName() + * Removed `FormField->setContainerFieldSet()`: Use setContainerFieldList() + * Removed `FormField->rootFieldSet()`: Use rootFieldList() + * Removed `Group::map()`: Use DataList::("Group")->map() + * Removed `Member->generateAutologinHash()`: Tokens are no longer saved directly into the database in plaintext. Use the return value of the Member::generateAutologinTokenAndHash to get the token + * Removed `Member->sendInfo()`: use Member_ChangePasswordEmail or Member_ForgotPasswordEmail directly + * Removed `SQLMap::map()`: Use DataList::("Member")->map() + * Removed `SQLMap::mapInGroups()`: Use Member::map_in_groups() + * Removed `PasswordEncryptor::register()/unregister()`: Use config system instead \ No newline at end of file diff --git a/forms/Form.php b/forms/Form.php index 0600c8b60..fd66b6186 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -864,14 +864,6 @@ class Form extends RequestHandler { return $this; } - /** - * @return string - */ - public function Name() { - Deprecation::notice('3.0', 'Use getName() instead.'); - return $this->getName(); - } - /** * Get the name of the form. * @return string diff --git a/forms/FormField.php b/forms/FormField.php index 7e1fb80d0..f0727c632 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -814,11 +814,6 @@ class FormField extends RequestHandler { } } - public function setContainerFieldSet($list) { - Deprecation::notice('3.0', 'Use setContainerFieldList() instead.'); - return $this->setContainerFieldList($list); - } - /** * Set the FieldList that contains this field. * @@ -830,11 +825,6 @@ class FormField extends RequestHandler { return $this; } - public function rootFieldSet() { - Deprecation::notice('3.0', 'Use rootFieldList() instead.'); - return $this->rootFieldList(); - } - public function rootFieldList() { if(is_object($this->containerFieldList)) return $this->containerFieldList->rootFieldList(); else user_error("rootFieldList() called on $this->class object without a containerFieldList", E_USER_ERROR); diff --git a/security/Group.php b/security/Group.php index c4484ac2f..108bd2348 100755 --- a/security/Group.php +++ b/security/Group.php @@ -251,18 +251,7 @@ class Group extends DataObject { public function DirectMembers() { return $this->getManyManyComponents('Members'); } - - public static function map($filter = "", $sort = "", $blank="") { - Deprecation::notice('3.0', 'Use DataList::("Group")->map()'); - - $list = Group::get()->where($filter)->sort($sort); - $map = $list->map(); - - if($blank) $map->unshift(0, $blank); - return $map; - } - /** * Return a set of this record's "family" of IDs - the IDs of * this record and all its descendants. diff --git a/security/Member.php b/security/Member.php index 5313b9781..b531efb43 100644 --- a/security/Member.php +++ b/security/Member.php @@ -472,23 +472,6 @@ class Member extends DataObject implements TemplateGlobalProvider { return $token; } - /** - * @deprecated 3.0 - */ - public function generateAutologinHash($lifetime = 2) { - Deprecation::notice('3.0', - 'Member::generateAutologinHash is deprecated - tokens are no longer saved directly into the database '. - 'in plaintext. Use the return value of the Member::generateAutologinTokenAndHash to get the token '. - 'instead.', - Deprecation::SCOPE_METHOD); - - user_error( - 'Member::generateAutologinHash is deprecated - tokens are no longer saved directly into the database '. - 'in plaintext. Use the return value of the Member::generateAutologinTokenAndHash to get the token '. - 'instead.', - E_USER_ERROR); - } - /** * Check the token against the member. * @@ -526,35 +509,6 @@ class Member extends DataObject implements TemplateGlobalProvider { return $member; } - /** - * Send signup, change password or forgot password informations to an user - * - * @param string $type Information type to send ("signup", "changePassword" or "forgotPassword") - * @param array $data Additional data to pass to the email (can be used in the template) - */ - public function sendInfo($type = 'signup', $data = null) { - Deprecation::notice('3.0', - 'Please use Member_ChangePasswordEmail or Member_ForgotPasswordEmail directly instead'); - - switch($type) { - case "changePassword": - $e = Member_ChangePasswordEmail::create(); - break; - case "forgotPassword": - $e = Member_ForgotPasswordEmail::create(); - break; - } - - if(is_array($data)) { - foreach($data as $key => $value) - $e->$key = $value; - } - - $e->populateTemplate($this); - $e->setTo($this->Email); - $e->send(); - } - /** * Returns the fields for the member form - used in the registration/profile module. * It should return fields that are editable by the admin and the logged-in user. @@ -1010,42 +964,6 @@ class Member extends DataObject implements TemplateGlobalProvider { public function DirectGroups() { return $this->getManyManyComponents('Groups'); } - - - /** - * Get member SQLMap - * - * @param string $filter Filter for the SQL statement (WHERE clause) - * @param string $sort Sorting function (ORDER clause) - * @param string $blank Shift a blank member in the items - * @return SQLMap Returns an SQLMap that returns all Member data. - * - * @todo Improve documentation of this function! (Markus) - */ - public static function map($filter = "", $sort = "", $blank="") { - Deprecation::notice('3.0', 'Use DataList::("Member")->map()'); - - $list = Member::get()->where($filter)->sort($sort); - $map = $list->map(); - - if($blank) $map->unshift(0, $blank); - - return $map; - } - - /** - * Get a member SQLMap of members in specific groups - * - * If no $groups is passed, all members will be returned - * - * @param mixed $groups - takes a SS_List, an array or a single Group.ID - * @return SQLMap Returns an SQLMap that returns all Member data. - * @see map() - */ - public static function mapInGroups($groups = null) { - Deprecation::notice('3.0', 'Use Member::map_in_groups() instead'); - return self::map_in_groups(); - } /** * Get a member SQLMap of members in specific groups diff --git a/security/PasswordEncryptor.php b/security/PasswordEncryptor.php index 2718dbb5f..283f4b358 100644 --- a/security/PasswordEncryptor.php +++ b/security/PasswordEncryptor.php @@ -25,29 +25,6 @@ abstract class PasswordEncryptor { return Config::inst()->get('PasswordEncryptor', 'encryptors'); } - /** - * Add a new encryptor implementation. - * - * Note: Due to portability concerns, its not advisable to - * override an existing $code mapping with different behaviour. - * - * @param String $code This value will be stored stored in the - * {@link Member->PasswordEncryption} property. - * @param String $class Classname of a {@link PasswordEncryptor} subclass - */ - public static function register($code, $class) { - Deprecation::notice('3.0', 'Use the Config system to register Password encryptors'); - self::$encryptors[$code] = $class; - } - - /** - * @param String $code Unique lookup. - */ - public static function unregister($code) { - Deprecation::notice('3.0', 'Use the Config system to unregister Password encryptors'); - if(isset(self::$encryptors[$code])) unset(self::$encryptors[$code]); - } - /** * @param String $algorithm * @return PasswordEncryptor diff --git a/tests/security/GroupTest.php b/tests/security/GroupTest.php index e923dad41..eff91550f 100644 --- a/tests/security/GroupTest.php +++ b/tests/security/GroupTest.php @@ -25,30 +25,6 @@ class GroupTest extends FunctionalTest { $this->assertNull($g3->Code, 'Default title doesnt trigger attribute setting'); } - /** - * Test the Group::map() function - */ - public function testGroupMap() { - // 2.4 only - $originalDeprecation = Deprecation::dump_settings(); - Deprecation::notification_version('2.4'); - - /* Group::map() returns an SQLMap object implementing iterator. You can use foreach to get ID-Title pairs. */ - - // We will iterate over the map and build mapOuput to more easily call assertions on the result. - $map = Group::map(); - $mapOutput = $map->toArray(); - - $group1 = $this->objFromFixture('Group', 'group1'); - $group2 = $this->objFromFixture('Group', 'group2'); - - /* We have added 2 groups to our fixture. They should both appear in $mapOutput. */ - $this->assertEquals($mapOutput[$group1->ID], $group1->Title); - $this->assertEquals($mapOutput[$group2->ID], $group2->Title); - - Deprecation::restore_settings($originalDeprecation); - } - public function testMemberGroupRelationForm() { Session::set('loggedInAs', $this->idFromFixture('GroupTest_Member', 'admin')); From 27113f82c30dd08a2d60bf0f481ae06054363ad0 Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Wed, 12 Dec 2012 17:22:45 +1300 Subject: [PATCH 32/49] API Make DataList and ArrayList immutable In 3.0 there was some confusion about whether DataLists and ArrayLists were mutable or not. If DataLists were immutable, they'd return the result, and your code would look like $list = $list->filter(....); If DataLists were mutable, they'd operate on themselves, returning nothing, and your code would look like $list->filter(....); This makes all DataLists and ArrayList immutable for all _searching_ operations. Operations on DataList that modify the underlying SQL data store remain mutating. - These functions no longer mutate the existing object, and if you do not capture the value returned by them will have no effect: ArrayList#reverse ArrayList#sort ArrayList#filter ArrayList#exclude DataList#dataQuery (use DataList#alterDataQuery to modify dataQuery in a safe manner) DataList#where DataList#limit DataList#sort DataList#addFilter DataList#applyFilterContext DataList#innerJoin DataList#leftJoin DataList#find DataList#byIDs DataList#reverse - DataList#setDataQueryParam has been added as syntactic sugar around the most common cause of accessing the dataQuery directly - setting query parameters - RelationList#setForeignID has been removed. Always use RelationList#forForeignID when querying, and overload RelationList#foreignIDList when subclassing. - Relatedly,the protected variable RelationList->foreignID has been removed, as the ID is now stored on a query parameter. Use RelationList#getForeignID to read it. --- docs/en/changelogs/3.1.0.md | 20 +++++- model/ArrayList.php | 23 ++++--- model/DataList.php | 122 +++++++++++++--------------------- model/DataObject.php | 4 +- model/Filterable.php | 8 ++- model/HasManyList.php | 22 +++--- model/Hierarchy.php | 11 +-- model/Limitable.php | 4 +- model/ManyManyList.php | 49 +++++++++----- model/RelationList.php | 62 ++++++++--------- model/Sortable.php | 8 +-- model/UnsavedRelationList.php | 17 +---- model/Versioned.php | 32 +++++---- search/SearchContext.php | 2 +- security/Group.php | 4 +- security/Member.php | 24 ++++--- tests/model/ArrayListTest.php | 49 +++++++------- tests/model/DataListTest.php | 24 +++++-- 18 files changed, 262 insertions(+), 223 deletions(-) diff --git a/docs/en/changelogs/3.1.0.md b/docs/en/changelogs/3.1.0.md index 38e3a0271..95ebdc5dc 100644 --- a/docs/en/changelogs/3.1.0.md +++ b/docs/en/changelogs/3.1.0.md @@ -18,7 +18,7 @@ including `Email_BounceHandler` and `Email_BounceRecord` classes, as well as the `Member->Bounced` property. * Deprecated global email methods `htmlEmail()` and `plaintextEmail`, as well as various email helper methods like `encodeMultipart()`. Use the `Email` API, or the `Mailer` class where applicable. - * Removed non-functional `$inlineImages` option for sending emails + * Removed non-functional `$inlineImages` option for sending emails * Removed support for keyed arrays in `SelectionGroup`, use new `SelectionGroup_Item` object to populate the list instead (see [API docs](api:SelectionGroup)). * Removed `Form->Name()`: Use getName() @@ -29,4 +29,20 @@ * Removed `Member->sendInfo()`: use Member_ChangePasswordEmail or Member_ForgotPasswordEmail directly * Removed `SQLMap::map()`: Use DataList::("Member")->map() * Removed `SQLMap::mapInGroups()`: Use Member::map_in_groups() - * Removed `PasswordEncryptor::register()/unregister()`: Use config system instead \ No newline at end of file + * Removed `PasswordEncryptor::register()/unregister()`: Use config system instead + * Methods on DataList and ArrayList that used to both modify the existing list & return a new version now just return a new version. Make sure you change statements like `$list->filter(...)` to $`list = $list->filter(...)` for these methods: + - `ArrayList#reverse` + - `ArrayList#sort` + - `ArrayList#filter` + - `ArrayList#exclude` + - `DataList#where` + - `DataList#limit` + - `DataList#sort` + - `DataList#addFilter` + - `DataList#applyFilterContext` + - `DataList#innerJoin` + - `DataList#leftJoin` + - `DataList#find` + - `DataList#byIDs` + - `DataList#reverse` + * `DataList#dataQuery` has been changed to return a clone of the query, and so can't be used to modify the list's query directly. Use `DataList#alterDataQuery` instead to modify dataQuery in a safe manner. diff --git a/model/ArrayList.php b/model/ArrayList.php index dccd6b3e2..f84de0a56 100644 --- a/model/ArrayList.php +++ b/model/ArrayList.php @@ -2,6 +2,17 @@ /** * A list object that wraps around an array of objects or arrays. * + * Note that (like DataLists), the implementations of the methods from SS_Filterable, SS_Sortable and + * SS_Limitable return a new instance of ArrayList, rather than modifying the existing instance. + * + * For easy reference, methods that operate in this way are: + * + * - limit + * - reverse + * - sort + * - filter + * - exclude + * * @package framework * @subpackage model */ @@ -309,8 +320,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta * @return ArrayList */ public function reverse() { - // TODO 3.1: This currently mutates existing array - $list = /* clone */ $this; + $list = clone $this; $list->items = array_reverse($this->items); return $list; @@ -376,8 +386,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta $multisortArgs[] = &$sortDirection[$column]; } - // TODO 3.1: This currently mutates existing array - $list = /* clone */ $this; + $list = clone $this; // As the last argument we pass in a reference to the items that all the sorting will be applied upon $multisortArgs[] = &$list->items; call_user_func_array('array_multisort', $multisortArgs); @@ -440,8 +449,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta } } - // TODO 3.1: This currently mutates existing array - $list = /* clone */ $this; + $list = clone $this; $list->items = $itemsToKeep; return $list; } @@ -504,8 +512,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta } } - // TODO 3.1: This currently mutates existing array - $list = /* clone */ $this; + $list = clone $this; $list->items = $itemsToKeep; return $list; } diff --git a/model/DataList.php b/model/DataList.php index b39b32529..8734e1563 100644 --- a/model/DataList.php +++ b/model/DataList.php @@ -3,21 +3,21 @@ * Implements a "lazy loading" DataObjectSet. * Uses {@link DataQuery} to do the actual query generation. * - * todo 3.1: In 3.0 the below is not currently true for backwards compatible reasons, but code should not rely on - * current behaviour. + * DataLists are _immutable_ as far as the query they represent is concerned. When you call a method that + * alters the query, a new DataList instance is returned, rather than modifying the existing instance * - * DataLists have two sets of methods. + * When you add or remove an element to the list the query remains the same, but because you have modified + * the underlying data the contents of the list changes. These are some of those methods: * - * 1). Selection methods (SS_Filterable, SS_Sortable, SS_Limitable) change the way the list is built, but does not - * alter underlying data. There are no external affects from selection methods once this list instance is - * destructed. + * - add + * - addMany + * - remove + * - removeMany + * - removeByID + * - removeByFilter + * - removeAll * - * 2). Mutation methods change the underlying data. The change persists into the underlying data storage layer. - * - * DataLists are _immutable_ as far as selection methods go - they all return new instances of DataList, rather - * than change the current list. - * - * DataLists are _mutable_ as far as mutation methods go - they all act on the existing DataList instance. + * Subclasses of DataList may add other methods that have the same effect. * * @package framework * @subpackage model @@ -85,17 +85,13 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab /** * Return a copy of the internal {@link DataQuery} object * - * todo 3.1: In 3.0 the below is not currently true for backwards compatible reasons, but code should not rely on - * this - * * Because the returned value is a copy, modifying it won't affect this list's contents. If * you want to alter the data query directly, use the alterDataQuery method * * @return DataQuery */ public function dataQuery() { - // TODO 3.1: This method potentially mutates self - return /* clone */ $this->dataQuery; + return clone $this->dataQuery; } /** @@ -122,7 +118,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab if ($this->inAlterDataQueryCall) { $list = $this; - $res = $callback($list->dataQuery, $list); + $res = call_user_func($callback, $list->dataQuery, $list); if ($res) $list->dataQuery = $res; return $list; @@ -132,7 +128,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab $list->inAlterDataQueryCall = true; try { - $res = $callback($list->dataQuery, $list); + $res = call_user_func($callback, $list->dataQuery, $list); if ($res) $list->dataQuery = $res; } catch (Exception $e) { @@ -145,39 +141,6 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab } } - /** - * In 3.0.0 some methods in DataList mutate their list. We don't want to change that in the 3.0.x - * line, but we don't want people relying on it either. This does the same as alterDataQuery, but - * _does_ mutate the existing list. - * - * todo 3.1: All methods that call this need to call alterDataQuery instead - */ - protected function alterDataQuery_30($callback) { - Deprecation::notice('3.1', 'DataList will become immutable in 3.1'); - - if ($this->inAlterDataQueryCall) { - $res = $callback($this->dataQuery, $this); - if ($res) $this->dataQuery = $res; - - return $this; - } - else { - $this->inAlterDataQueryCall = true; - - try { - $res = $callback($this->dataQuery, $this); - if ($res) $this->dataQuery = $res; - } - catch (Exception $e) { - $this->inAlterDataQueryCall = false; - throw $e; - } - - $this->inAlterDataQueryCall = false; - return $this; - } - } - /** * Return a new DataList instance with the underlying {@link DataQuery} object changed * @@ -190,6 +153,21 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab return $clone; } + public function setDataQueryParam($keyOrArray, $val = null) { + $clone = clone $this; + + if(is_array($keyOrArray)) { + foreach($keyOrArray as $key => $val) { + $clone->dataQuery->setQueryParam($key, $val); + } + } + else { + $clone->dataQuery->setQueryParam($keyOrArray, $val); + } + + return $clone; + } + /** * Returns the SQL query that will be used to get this DataList's records. Good for debugging. :-) * @@ -206,7 +184,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return DataList */ public function where($filter) { - return $this->alterDataQuery_30(function($query) use ($filter){ + return $this->alterDataQuery(function($query) use ($filter){ $query->where($filter); }); } @@ -243,7 +221,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab if(!$limit && !$offset) { return $this; } - return $this->alterDataQuery_30(function($query) use ($limit, $offset){ + return $this->alterDataQuery(function($query) use ($limit, $offset){ $query->limit($limit, $offset); }); } @@ -281,7 +259,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab $sort = func_get_arg(0); } - return $this->alterDataQuery_30(function($query, $list) use ($sort, $col, $dir){ + return $this->alterDataQuery(function($query, $list) use ($sort, $col, $dir){ if ($col) { // sort('Name','Desc') @@ -346,25 +324,24 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab throw new InvalidArgumentException('Incorrect number of arguments passed to filter()'); } - // TODO 3.1: Once addFilter doesn't mutate self, this results in a double clone - $clone = clone $this; - $clone->addFilter($filters); - return $clone; + return $this->addFilter($filters); } /** * Return a new instance of the list with an added filter */ public function addFilter($filterArray) { + $list = $this; + foreach($filterArray as $field => $value) { $fieldArgs = explode(':', $field); $field = array_shift($fieldArgs); $filterType = array_shift($fieldArgs); $modifiers = $fieldArgs; - $this->applyFilterContext($field, $filterType, $modifiers, $value); + $list = $list->applyFilterContext($field, $filterType, $modifiers, $value); } - return $this; + return $list; } /** @@ -485,7 +462,6 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @todo Deprecated SearchContexts and pull their functionality into the core of the ORM */ private function applyFilterContext($field, $comparisators, $modifiers, $value) { - $t = singleton($this->dataClass())->dbObject($field); if($comparisators) { $className = "{$comparisators}Filter"; } else { @@ -496,7 +472,8 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab array_unshift($modifiers, $comparisators); } $t = new $className($field, $value, $modifiers); - $t->apply($this->dataQuery()); + + return $this->alterDataQuery(array($t, 'apply')); } /** @@ -581,7 +558,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return DataList */ public function innerJoin($table, $onClause, $alias = null) { - return $this->alterDataQuery_30(function($query) use ($table, $onClause, $alias){ + return $this->alterDataQuery(function($query) use ($table, $onClause, $alias){ $query->innerJoin($table, $onClause, $alias); }); } @@ -595,7 +572,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return DataList */ public function leftJoin($table, $onClause, $alias = null) { - return $this->alterDataQuery_30(function($query) use ($table, $onClause, $alias){ + return $this->alterDataQuery(function($query) use ($table, $onClause, $alias){ $query->leftJoin($table, $onClause, $alias); }); } @@ -810,9 +787,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab $SQL_col = sprintf('"%s"', Convert::raw2sql($key)); } - // todo 3.1: In 3.1 where won't be mutating, so this can be on $this directly - $clone = clone $this; - return $clone->where("$SQL_col = '" . Convert::raw2sql($value) . "'")->First(); + return $this->where("$SQL_col = '" . Convert::raw2sql($value) . "'")->First(); } /** @@ -836,9 +811,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab public function byIDs(array $ids) { $ids = array_map('intval', $ids); // sanitize $baseClass = ClassInfo::baseDataClass($this->dataClass); - $this->where("\"$baseClass\".\"ID\" IN (" . implode(',', $ids) .")"); - - return $this; + return $this->where("\"$baseClass\".\"ID\" IN (" . implode(',', $ids) .")"); } /** @@ -849,10 +822,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab */ public function byID($id) { $baseClass = ClassInfo::baseDataClass($this->dataClass); - - // todo 3.1: In 3.1 where won't be mutating, so this can be on $this directly - $clone = clone $this; - return $clone->where("\"$baseClass\".\"ID\" = " . (int)$id)->First(); + return $this->where("\"$baseClass\".\"ID\" = " . (int)$id)->First(); } /** @@ -1028,7 +998,7 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @return DataList */ public function reverse() { - return $this->alterDataQuery_30(function($query){ + return $this->alterDataQuery(function($query){ $query->reverseSort(); }); } diff --git a/model/DataObject.php b/model/DataObject.php index 6c271d81c..962533bc5 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -2722,9 +2722,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity if($limit && strpos($limit, ',') !== false) { $limitArguments = explode(',', $limit); - $result->limit($limitArguments[1],$limitArguments[0]); + $result = $result->limit($limitArguments[1],$limitArguments[0]); } elseif($limit) { - $result->limit($limit); + $result = $result->limit($limit); } if($join) $result = $result->join($join); diff --git a/model/Filterable.php b/model/Filterable.php index a508f65ef..72d0d918f 100644 --- a/model/Filterable.php +++ b/model/Filterable.php @@ -19,8 +19,9 @@ interface SS_Filterable { public function canFilterBy($by); /** - * Filter the list to include items with these charactaristics - * + * Return a new instance of this list that only includes items with these charactaristics + * + * @return SS_Filterable * @example $list = $list->filter('Name', 'bob'); // only bob in the list * @example $list = $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list * @example $list = $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the age 21 @@ -31,8 +32,9 @@ interface SS_Filterable { public function filter(); /** - * Exclude the list to not contain items with these charactaristics + * Return a new instance of this list that excludes any items with these charactaristics * + * @return SS_Filterable * @example $list = $list->exclude('Name', 'bob'); // exclude bob from list * @example $list = $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list * @example $list = $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21 diff --git a/model/HasManyList.php b/model/HasManyList.php index c39efceb4..8d30a5482 100644 --- a/model/HasManyList.php +++ b/model/HasManyList.php @@ -21,14 +21,16 @@ class HasManyList extends RelationList { $this->foreignKey = $foreignKey; } - protected function foreignIDFilter() { + protected function foreignIDFilter($id = null) { + if ($id === null) $id = $this->getForeignID(); + // Apply relation filter - if(is_array($this->foreignID)) { - return "\"$this->foreignKey\" IN ('" . - implode("', '", array_map('Convert::raw2sql', $this->foreignID)) . "')"; - } else if($this->foreignID !== null){ + if(is_array($id)) { + return "\"$this->foreignKey\" IN ('" . + implode("', '", array_map('Convert::raw2sql', $id)) . "')"; + } else if($id !== null){ return "\"$this->foreignKey\" = '" . - Convert::raw2sql($this->foreignID) . "'"; + Convert::raw2sql($id) . "'"; } } @@ -44,18 +46,20 @@ class HasManyList extends RelationList { user_error("HasManyList::add() expecting a $this->dataClass object, or ID value", E_USER_ERROR); } + $foreignID = $this->getForeignID(); + // Validate foreignID - if(!$this->foreignID) { + if(!$foreignID) { user_error("ManyManyList::add() can't be called until a foreign ID is set", E_USER_WARNING); return; } - if(is_array($this->foreignID)) { + if(is_array($foreignID)) { user_error("ManyManyList::add() can't be called on a list linked to mulitple foreign IDs", E_USER_WARNING); return; } $fk = $this->foreignKey; - $item->$fk = $this->foreignID; + $item->$fk = $foreignID; $item->write(); } diff --git a/model/Hierarchy.php b/model/Hierarchy.php index 4fa8f98d9..117c3dd95 100644 --- a/model/Hierarchy.php +++ b/model/Hierarchy.php @@ -591,12 +591,13 @@ class Hierarchy extends DataExtension { $id = $this->owner->ID; $children = DataObject::get($baseClass) - ->where("\"{$baseClass}\".\"ParentID\" = $id AND \"{$baseClass}\".\"ID\" != $id"); - if(!$showAll) $children = $children->where('"ShowInMenus" = 1'); + ->where("\"{$baseClass}\".\"ParentID\" = $id AND \"{$baseClass}\".\"ID\" != $id") + ->setDataQueryParam(array( + 'Versioned.mode' => $onlyDeletedFromStage ? 'stage_unique' : 'stage', + 'Versioned.stage' => 'Live' + )); - // Query the live site - $children->dataQuery()->setQueryParam('Versioned.mode', $onlyDeletedFromStage ? 'stage_unique' : 'stage'); - $children->dataQuery()->setQueryParam('Versioned.stage', 'Live'); + if(!$showAll) $children = $children->where('"ShowInMenus" = 1'); return $children; } diff --git a/model/Limitable.php b/model/Limitable.php index 8e062de1e..633762d2a 100644 --- a/model/Limitable.php +++ b/model/Limitable.php @@ -11,11 +11,11 @@ interface SS_Limitable { /** - * Returns a filtered version of this where no more than $limit records are included. + * Returns a new instance of this list where no more than $limit records are included. * If $offset is specified, then that many records at the beginning of the list will be skipped. * This matches the behaviour of the SQL LIMIT clause. * - * @return SS_List + * @return SS_Limitable */ public function limit($limit, $offset = 0); diff --git a/model/ManyManyList.php b/model/ManyManyList.php index 1cfb5069c..7b7ecd90e 100644 --- a/model/ManyManyList.php +++ b/model/ManyManyList.php @@ -9,7 +9,7 @@ class ManyManyList extends RelationList { protected $localKey; - protected $foreignKey, $foreignID; + protected $foreignKey; protected $extraFields; @@ -48,19 +48,35 @@ class ManyManyList extends RelationList { } /** - * Return a filter expression for the foreign ID. + * Return a filter expression for when getting the contents of the relationship for some foreign ID + * @return string */ - protected function foreignIDFilter() { + protected function foreignIDFilter($id = null) { + if ($id === null) $id = $this->getForeignID(); + // Apply relation filter - if(is_array($this->foreignID)) { + if(is_array($id)) { return "\"$this->joinTable\".\"$this->foreignKey\" IN ('" . - implode("', '", array_map('Convert::raw2sql', $this->foreignID)) . "')"; - } else if($this->foreignID !== null){ + implode("', '", array_map('Convert::raw2sql', $id)) . "')"; + } else if($id !== null){ return "\"$this->joinTable\".\"$this->foreignKey\" = '" . - Convert::raw2sql($this->foreignID) . "'"; + Convert::raw2sql($id) . "'"; } } + /** + * Return a filter expression for the join table when writing to the join table + * + * When writing (add, remove, removeByID), we need to filter the join table to just the relevant + * entries. However some subclasses of ManyManyList (Member_GroupSet) modify foreignIDFilter to + * include additional calculated entries, so we need different filters when reading and when writing + * + * @return string + */ + protected function foreignIDWriteFilter($id = null) { + return $this->foreignIDFilter($id); + } + /** * Add an item to this many_many relationship * Does so by adding an entry to the joinTable. @@ -73,22 +89,25 @@ class ManyManyList extends RelationList { throw new InvalidArgumentException("ManyManyList::add() expecting a $this->dataClass object, or ID value", E_USER_ERROR); } - + + $foreignIDs = $this->getForeignID(); + $foreignFilter = $this->foreignIDWriteFilter(); + // Validate foreignID - if(!$this->foreignID) { + if(!$foreignIDs) { throw new Exception("ManyManyList::add() can't be called until a foreign ID is set", E_USER_WARNING); } - if($filter = $this->foreignIDFilter()) { + if($foreignFilter) { $query = new SQLQuery("*", array("\"$this->joinTable\"")); - $query->setWhere($filter); + $query->setWhere($foreignFilter); $hasExisting = ($query->count() > 0); } else { $hasExisting = false; } // Insert or update - foreach((array)$this->foreignID as $foreignID) { + foreach((array)$foreignIDs as $foreignID) { $manipulation = array(); if($hasExisting) { $manipulation[$this->joinTable]['command'] = 'update'; @@ -134,8 +153,8 @@ class ManyManyList extends RelationList { $query = new SQLQuery("*", array("\"$this->joinTable\"")); $query->setDelete(true); - - if($filter = $this->foreignIDFilter()) { + + if($filter = $this->foreignIDWriteFilter($this->getForeignID())) { $query->setWhere($filter); } else { user_error("Can't call ManyManyList::remove() until a foreign ID is set", E_USER_WARNING); @@ -177,7 +196,7 @@ class ManyManyList extends RelationList { if($this->extraFields) { foreach($this->extraFields as $fieldName => $dbFieldSpec) { $query = new SQLQuery("\"$fieldName\"", array("\"$this->joinTable\"")); - if($filter = $this->foreignIDFilter()) { + if($filter = $this->foreignIDWriteFilter($this->getForeignID())) { $query->setWhere($filter); } else { user_error("Can't call ManyManyList::getExtraData() until a foreign ID is set", E_USER_WARNING); diff --git a/model/RelationList.php b/model/RelationList.php index a09366b56..5e11f5180 100644 --- a/model/RelationList.php +++ b/model/RelationList.php @@ -7,44 +7,44 @@ * @todo Is this additional class really necessary? */ abstract class RelationList extends DataList { - protected $foreignID; - - /** - * Set the ID of the record that this ManyManyList is linking *from*. - * - * This is the mutatable version of this function, and will be protected only - * from 3.1. Use forForeignID instead - * - * @param $id A single ID, or an array of IDs - */ - public function setForeignID($id) { - // If already filtered on foreign ID, remove that first - if($this->foreignID !== null) { - $oldFilter = $this->foreignIDFilter(); - try { - $this->dataQuery->removeFilterOn($oldFilter); - } - catch(InvalidArgumentException $e) { /* NOP */ } - } - // Turn a 1-element array into a simple value - if(is_array($id) && sizeof($id) == 1) $id = reset($id); - $this->foreignID = $id; - - $this->dataQuery->where($this->foreignIDFilter()); - - return $this; + public function getForeignID() { + return $this->dataQuery->getQueryParam('Foreign.ID'); } - + /** * Returns a copy of this list with the ManyMany relationship linked to the given foreign ID. * @param $id An ID or an array of IDs. */ public function forForeignID($id) { - return $this->alterDataQuery_30(function($query, $list) use ($id){ - $list->setForeignID($id); + // Turn a 1-element array into a simple value + if(is_array($id) && sizeof($id) == 1) $id = reset($id); + + // Calculate the new filter + $filter = $this->foreignIDFilter($id); + + $list = $this->alterDataQuery(function($query, $list) use ($id, $filter){ + // Check if there is an existing filter, remove if there is + $currentFilter = $query->getQueryParam('Foreign.Filter'); + if($currentFilter) { + try { + $query->removeFilterOn($currentFilter); + } + catch (Exception $e) { /* NOP */ } + } + + // Add the new filter + $query->setQueryParam('Foreign.ID', $id); + $query->setQueryParam('Foreign.Filter', $filter); + $query->where($filter); }); + + return $list; } - - abstract protected function foreignIDFilter(); + + /** + * Returns a where clause that filters the members of this relationship to just the related items + * @param $id (optional) An ID or an array of IDs - if not provided, will use the current ids as per getForeignID + */ + abstract protected function foreignIDFilter($id = null); } diff --git a/model/Sortable.php b/model/Sortable.php index 952e9d8a8..c3432a945 100644 --- a/model/Sortable.php +++ b/model/Sortable.php @@ -19,9 +19,10 @@ interface SS_Sortable { public function canSortBy($by); /** - * Sorts this list by one or more fields. You can either pass in a single + * Return a new instance of this list that is sorted by one or more fields. You can either pass in a single * field name and direction, or a map of field names to sort directions. * + * @return SS_Sortable * @example $list = $list->sort('Name'); // default ASC sorting * @example $list = $list->sort('Name DESC'); // DESC sorting * @example $list = $list->sort('Name', 'ASC'); @@ -31,11 +32,10 @@ interface SS_Sortable { /** - * Reverses the list based on reversing the current sort. + * Return a new instance of this list based on reversing the current sort. * + * @return SS_Sortable * @example $list = $list->reverse(); - * - * @return array */ public function reverse(); } diff --git a/model/UnsavedRelationList.php b/model/UnsavedRelationList.php index 39b52e973..2a7ef640d 100644 --- a/model/UnsavedRelationList.php +++ b/model/UnsavedRelationList.php @@ -239,25 +239,14 @@ class UnsavedRelationList extends ArrayList { return $list->column('ID'); } - /** - * Set the ID of the record that this RelationList is linking. - * - * Adds the - * - * @param $id A single ID, or an array of IDs - */ - public function setForeignID($id) { - $class = singleton($this->baseClass); - $class->ID = 1; - return $class->{$this->relationName}()->setForeignID($id); - } - /** * Returns a copy of this list with the relationship linked to the given foreign ID. * @param $id An ID or an array of IDs. */ public function forForeignID($id) { - return $this->setForeignID($id); + $class = singleton($this->baseClass); + $class->ID = 1; + return $class->{$this->relationName}()->forForeignID($id); } /** diff --git a/model/Versioned.php b/model/Versioned.php index a74928408..560358475 100644 --- a/model/Versioned.php +++ b/model/Versioned.php @@ -1003,10 +1003,10 @@ class Versioned extends DataExtension { $containerClass = 'DataList') { $result = DataObject::get($class, $filter, $sort, $join, $limit, $containerClass); - $dq = $result->dataQuery(); - $dq->setQueryParam('Versioned.mode', 'stage'); - $dq->setQueryParam('Versioned.stage', $stage); - return $result; + return $result->setDataQueryParam(array( + 'Versioned.mode' => 'stage', + 'Versioned.stage' => $stage + )); } public function deleteFromStage($stage) { @@ -1049,8 +1049,10 @@ class Versioned extends DataExtension { */ public static function get_latest_version($class, $id) { $baseClass = ClassInfo::baseDataClass($class); - $list = DataList::create($baseClass)->where("\"$baseClass\".\"RecordID\" = $id"); - $list->dataQuery()->setQueryParam("Versioned.mode", "latest_versions"); + $list = DataList::create($baseClass) + ->where("\"$baseClass\".\"RecordID\" = $id") + ->setDataQueryParam("Versioned.mode", "latest_versions"); + return $list->First(); } @@ -1077,8 +1079,11 @@ class Versioned extends DataExtension { * In particular, this will query deleted records as well as active ones. */ public static function get_including_deleted($class, $filter = "", $sort = "") { - $list = DataList::create($class)->where($filter)->sort($sort); - $list->dataQuery()->setQueryParam("Versioned.mode", "latest_versions"); + $list = DataList::create($class) + ->where($filter) + ->sort($sort) + ->setDataQueryParam("Versioned.mode", "latest_versions"); + return $list; } @@ -1093,8 +1098,9 @@ class Versioned extends DataExtension { $baseClass = ClassInfo::baseDataClass($class); $list = DataList::create($baseClass) ->where("\"$baseClass\".\"RecordID\" = $id") - ->where("\"$baseClass\".\"Version\" = " . (int)$version); - $list->dataQuery()->setQueryParam('Versioned.mode', 'all_versions'); + ->where("\"$baseClass\".\"Version\" = " . (int)$version) + ->setDataQueryParam("Versioned.mode", 'all_versions'); + return $list->First(); } @@ -1104,8 +1110,10 @@ class Versioned extends DataExtension { */ public static function get_all_versions($class, $id) { $baseClass = ClassInfo::baseDataClass($class); - $list = DataList::create($class)->where("\"$baseClass\".\"RecordID\" = $id"); - $list->dataQuery()->setQueryParam('Versioned.mode', 'all_versions'); + $list = DataList::create($class) + ->where("\"$baseClass\".\"RecordID\" = $id") + ->setDataQueryParam('Versioned.mode', 'all_versions'); + return $list; } diff --git a/search/SearchContext.php b/search/SearchContext.php index de34b4086..fcce9dc33 100644 --- a/search/SearchContext.php +++ b/search/SearchContext.php @@ -153,7 +153,7 @@ class SearchContext extends Object { $filter->setModel($this->modelClass); $filter->setValue($value); if(! $filter->isEmpty()) { - $filter->apply($query->dataQuery()); + $query = $query->alterDataQuery(array($filter, 'apply')); } } } diff --git a/security/Group.php b/security/Group.php index 108bd2348..837143c37 100755 --- a/security/Group.php +++ b/security/Group.php @@ -236,7 +236,9 @@ class Group extends DataObject { // Remove the default foreign key filter in prep for re-applying a filter containing all children groups. // Filters are conjunctive in DataQuery by default, so this filter would otherwise overrule any less specific // ones. - $result->dataQuery()->removeFilterOn('Group_Members'); + $result = $result->alterDataQuery(function($query){ + $query->removeFilterOn('Group_Members'); + }); // Now set all children groups as a new foreign key $groups = Group::get()->byIDs($this->collateFamilyIDs()); $result = $result->forForeignID($groups->column('ID'))->where($filter)->sort($sort)->limit($limit); diff --git a/security/Member.php b/security/Member.php index b531efb43..964b329c8 100644 --- a/security/Member.php +++ b/security/Member.php @@ -1420,14 +1420,12 @@ class Member_GroupSet extends ManyManyList { /** * Link this group set to a specific member. */ - public function setForeignID($id) { - // Turn a 1-element array into a simple value - if(is_array($id) && sizeof($id) == 1) $id = reset($id); - $this->foreignID = $id; - + public function foreignIDFilter($id = null) { + if ($id === null) $id = $this->getForeignID(); + // Find directly applied groups - $manymanyFilter = $this->foreignIDFilter(); - $groupIDs = DB::query('SELECT "GroupID" FROM "Group_Members" WHERE ' . $manymanyFilter)->column(); + $manyManyFilter = parent::foreignIDFilter($id); + $groupIDs = DB::query('SELECT "GroupID" FROM "Group_Members" WHERE ' . $manyManyFilter)->column(); // Get all ancestors $allGroupIDs = array(); @@ -1438,8 +1436,16 @@ class Member_GroupSet extends ManyManyList { } // Add a filter to this DataList - if($allGroupIDs) $this->byIDs($allGroupIDs); - else $this->byIDs(array(0)); + if($allGroupIDs) { + return "\"Group\".\"ID\" IN (" . implode(',', $allGroupIDs) .")"; + } + else { + return "\"Group\".\"ID\" = 0"; + } + } + + public function foreignIDWriteFilter($id = null) { + return parent::foreignIDFilter($id); } } diff --git a/tests/model/ArrayListTest.php b/tests/model/ArrayListTest.php index 0d568f05d..bbb57e75e 100644 --- a/tests/model/ArrayListTest.php +++ b/tests/model/ArrayListTest.php @@ -229,14 +229,14 @@ class ArrayListTest extends SapphireTest { )); } - public function testSortSimpleDefualtIsSortedASC() { + public function testSortSimpleDefaultIsSortedASC() { $list = new ArrayList(array( array('Name' => 'Steve'), (object) array('Name' => 'Bob'), array('Name' => 'John') )); - $list->sort('Name'); + $list = $list->sort('Name'); $this->assertEquals($list->toArray(), array( (object) array('Name' => 'Bob'), array('Name' => 'John'), @@ -250,7 +250,8 @@ class ArrayListTest extends SapphireTest { (object) array('Name' => 'Bob'), array('Name' => 'John') )); - $list->sort('Name','asc'); + + $list = $list->sort('Name','asc'); $this->assertEquals($list->toArray(), array( (object) array('Name' => 'Bob'), array('Name' => 'John'), @@ -265,7 +266,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'John') )); - $list->sort('Name', 'DESC'); + $list = $list->sort('Name', 'DESC'); $this->assertEquals($list->toArray(), array( array('Name' => 'Steve'), array('Name' => 'John'), @@ -280,8 +281,8 @@ class ArrayListTest extends SapphireTest { array('Name' => 'Steve') )); - $list->sort('Name', 'ASC'); - $list->reverse(); + $list = $list->sort('Name', 'ASC'); + $list = $list->reverse(); $this->assertEquals($list->toArray(), array( array('Name' => 'Steve'), @@ -297,11 +298,11 @@ class ArrayListTest extends SapphireTest { (object) array('Name'=>'Object3', 'F1'=>5, 'F2'=>2, 'F3'=>2), )); - $list->sort('F3', 'ASC'); + $list = $list->sort('F3', 'ASC'); $this->assertEquals($list->first()->Name, 'Object3', 'Object3 should be first in the list'); $this->assertEquals($list->last()->Name, 'Object2', 'Object2 should be last in the list'); - $list->sort('F3', 'DESC'); + $list = $list->sort('F3', 'DESC'); $this->assertEquals($list->first()->Name, 'Object2', 'Object2 should be first in the list'); $this->assertEquals($list->last()->Name, 'Object3', 'Object3 should be last in the list'); } @@ -313,11 +314,11 @@ class ArrayListTest extends SapphireTest { (object) array('ID'=>2, 'Name'=>'Aron', 'Importance'=>1), )); - $list->sort(array('Name'=>'ASC', 'Importance'=>'ASC')); + $list = $list->sort(array('Name'=>'ASC', 'Importance'=>'ASC')); $this->assertEquals($list->first()->ID, 2, 'Aron.2 should be first in the list'); $this->assertEquals($list->last()->ID, 3, 'Bert.3 should be last in the list'); - $list->sort(array('Name'=>'ASC', 'Importance'=>'DESC')); + $list = $list->sort(array('Name'=>'ASC', 'Importance'=>'DESC')); $this->assertEquals($list->first()->ID, 1, 'Aron.2 should be first in the list'); $this->assertEquals($list->last()->ID, 3, 'Bert.3 should be last in the list'); } @@ -331,7 +332,7 @@ class ArrayListTest extends SapphireTest { (object) array('Name' => 'Bob'), array('Name' => 'John') )); - $list->filter('Name','Bob'); + $list = $list->filter('Name','Bob'); $this->assertEquals(array((object)array('Name'=>'Bob')), $list->toArray(), 'List should only contain Bob'); } @@ -349,7 +350,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'Steve'), array('Name' => 'John') ); - $list->filter('Name',array('Steve','John')); + $list = $list->filter('Name',array('Steve','John')); $this->assertEquals($expected, $list->toArray(), 'List should only contain Steve and John'); } @@ -362,7 +363,7 @@ class ArrayListTest extends SapphireTest { (object) array('Name' => 'Steve', 'ID' => 2), array('Name' => 'John', 'ID' => 2) )); - $list->filter(array('Name'=>'Clair')); + $list = $list->filter(array('Name'=>'Clair')); $this->assertEquals(array(), $list->toArray(), 'List should be empty'); } @@ -375,7 +376,7 @@ class ArrayListTest extends SapphireTest { (object) array('Name' => 'Steve', 'ID' => 2), array('Name' => 'John', 'ID' => 2) )); - $list->filter(array('Name'=>'Steve', 'ID'=>2)); + $list = $list->filter(array('Name'=>'Steve', 'ID'=>2)); $this->assertEquals(array((object)array('Name'=>'Steve', 'ID'=>2)), $list->toArray(), 'List should only contain object Steve'); } @@ -389,7 +390,7 @@ class ArrayListTest extends SapphireTest { (object) array('Name' => 'Steve', 'ID' => 2), array('Name' => 'John', 'ID' => 2) )); - $list->filter(array('Name'=>'Steve', 'ID'=>4)); + $list = $list->filter(array('Name'=>'Steve', 'ID'=>4)); $this->assertEquals(array(), $list->toArray(), 'List should be empty'); } @@ -404,7 +405,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'Steve', 'ID' => 3, 'Age'=>43) )); - $list->filter(array('Name'=>'Steve','Age'=>array(21, 43))); + $list = $list->filter(array('Name'=>'Steve','Age'=>array(21, 43))); $expected = array( array('Name' => 'Steve', 'ID' => 1, 'Age'=>21), @@ -426,7 +427,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'Steve', 'ID' => 3, 'Age'=>43) )); - $list->filter(array('Name'=>array('Steve','Clair'),'Age'=>array(21, 43))); + $list = $list->filter(array('Name'=>array('Steve','Clair'),'Age'=>array(21, 43))); $expected = array( array('Name' => 'Steve', 'ID' => 1, 'Age'=>21), @@ -448,7 +449,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'John') )); - $list->exclude('Name', 'Bob'); + $list = $list->exclude('Name', 'Bob'); $expected = array( array('Name' => 'Steve'), array('Name' => 'John') @@ -467,7 +468,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'John') )); - $list->exclude('Name', 'Clair'); + $list = $list->exclude('Name', 'Clair'); $expected = array( array('Name' => 'Steve'), array('Name' => 'Bob'), @@ -485,7 +486,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'Bob'), array('Name' => 'John') )); - $list->exclude('Name', array('Steve','John')); + $list = $list->exclude('Name', array('Steve','John')); $expected = array(array('Name' => 'Bob')); $this->assertEquals(1, $list->count()); $this->assertEquals($expected, $list->toArray(), 'List should only contain Bob'); @@ -501,7 +502,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'John', 'Age' => 21) )); - $list->exclude(array('Name' => 'Bob', 'Age' => 21)); + $list = $list->exclude(array('Name' => 'Bob', 'Age' => 21)); $expected = array( array('Name' => 'Bob', 'Age' => 32), @@ -527,7 +528,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'phil', 'Age' => 16) )); - $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16))); + $list = $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16))); $expected = array( array('Name' => 'phil', 'Age' => 11), array('Name' => 'bob', 'Age' => 12), @@ -553,7 +554,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'phil', 'Age' => 16) )); - $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16),'Bananas'=>true)); + $list = $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16),'Bananas'=>true)); $expected = array( array('Name' => 'bob', 'Age' => 10), array('Name' => 'phil', 'Age' => 11), @@ -584,7 +585,7 @@ class ArrayListTest extends SapphireTest { array('Name' => 'clair','Age' => 16, 'HasBananas'=>true) )); - $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16),'HasBananas'=>true)); + $list = $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16),'HasBananas'=>true)); $expected = array( array('Name' => 'bob', 'Age' => 10, 'HasBananas'=>false), array('Name' => 'phil','Age' => 11, 'HasBananas'=>true), diff --git a/tests/model/DataListTest.php b/tests/model/DataListTest.php index 0ebc0c68d..385fb9ea9 100644 --- a/tests/model/DataListTest.php +++ b/tests/model/DataListTest.php @@ -79,9 +79,14 @@ class DataListTest extends SapphireTest { public function testInnerJoin() { $db = DB::getConn(); + $list = DataObjectTest_TeamComment::get(); - $list->innerJoin('DataObjectTest_Team', '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"', - 'Team'); + $list = $list->innerJoin( + 'DataObjectTest_Team', + '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"', + 'Team' + ); + $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", "DataObjectTest_TeamComment"."Created",' . ' "DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Name",' . ' "DataObjectTest_TeamComment"."Comment", "DataObjectTest_TeamComment"."TeamID",' @@ -89,14 +94,20 @@ class DataListTest extends SapphireTest { . ' THEN "DataObjectTest_TeamComment"."ClassName" ELSE '.$db->prepStringForDB('DataObjectTest_TeamComment') . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" INNER JOIN "DataObjectTest_Team" AS "Team"' . ' ON "DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"'; + $this->assertEquals($expected, $list->sql()); } public function testLeftJoin() { $db = DB::getConn(); + $list = DataObjectTest_TeamComment::get(); - $list->leftJoin('DataObjectTest_Team', '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"', - 'Team'); + $list = $list->leftJoin( + 'DataObjectTest_Team', + '"DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"', + 'Team' + ); + $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", "DataObjectTest_TeamComment"."Created",' . ' "DataObjectTest_TeamComment"."LastEdited", "DataObjectTest_TeamComment"."Name",' . ' "DataObjectTest_TeamComment"."Comment", "DataObjectTest_TeamComment"."TeamID",' @@ -104,14 +115,16 @@ class DataListTest extends SapphireTest { . ' THEN "DataObjectTest_TeamComment"."ClassName" ELSE '.$db->prepStringForDB('DataObjectTest_TeamComment') . ' END AS "RecordClassName" FROM "DataObjectTest_TeamComment" LEFT JOIN "DataObjectTest_Team" AS "Team"' . ' ON "DataObjectTest_Team"."ID" = "DataObjectTest_TeamComment"."TeamID"'; + $this->assertEquals($expected, $list->sql()); // Test with namespaces (with non-sensical join, but good enough for testing) $list = DataObjectTest_TeamComment::get(); - $list->leftJoin( + $list = $list->leftJoin( 'DataObjectTest\NamespacedClass', '"DataObjectTest\NamespacedClass"."ID" = "DataObjectTest_TeamComment"."ID"' ); + $expected = 'SELECT DISTINCT "DataObjectTest_TeamComment"."ClassName", ' . '"DataObjectTest_TeamComment"."Created", ' . '"DataObjectTest_TeamComment"."LastEdited", ' @@ -126,6 +139,7 @@ class DataListTest extends SapphireTest { . 'LEFT JOIN "DataObjectTest\NamespacedClass" ON ' . '"DataObjectTest\NamespacedClass"."ID" = "DataObjectTest_TeamComment"."ID"'; $this->assertEquals($expected, $list->sql(), 'Retains backslashes in namespaced classes'); + } public function testToNestedArray() { From 9979b11b59483c7178500b07d0b636ac99d76246 Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Thu, 13 Dec 2012 10:02:18 +1300 Subject: [PATCH 33/49] FIX Make sure ArrayList#limit uses clone so for subclasses it returns instances of same subclass --- model/ArrayList.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/model/ArrayList.php b/model/ArrayList.php index f84de0a56..95bb0f9c9 100644 --- a/model/ArrayList.php +++ b/model/ArrayList.php @@ -130,7 +130,9 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta * @return ArrayList */ public function limit($length, $offset = 0) { - return new ArrayList(array_slice($this->items, $offset, $length)); + $list = clone $this; + $list->items = array_slice($this->items, $offset, $length); + return $list; } /** From bd59f842f0d91ea07638cae417b9e9462038b89a Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Thu, 13 Dec 2012 10:18:21 +1300 Subject: [PATCH 34/49] FIX Make sure you can only remove items from a DataList that are actually in it --- model/DataList.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/model/DataList.php b/model/DataList.php index 8734e1563..f1a0e62aa 100644 --- a/model/DataList.php +++ b/model/DataList.php @@ -978,9 +978,8 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab */ public function remove($item) { // By default, we remove an item from a DataList by deleting it. - if($item instanceof $this->dataClass) $item->delete(); - - } + $this->removeByID($item->ID); + } /** * Remove an item from this DataList by ID From a355e1d03df807cd404a3e1958cd4b04e86c91a0 Mon Sep 17 00:00:00 2001 From: Justin Martin Date: Thu, 13 Dec 2012 10:23:52 -0800 Subject: [PATCH 35/49] BUG: Set visibility on login form methods to public. --- security/Security.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/Security.php b/security/Security.php index 5b3daf07c..773164b98 100644 --- a/security/Security.php +++ b/security/Security.php @@ -243,7 +243,7 @@ class Security extends Controller { /** * Get the login form to process according to the submitted data */ - protected function LoginForm() { + public function LoginForm() { if(isset($this->requestParams['AuthenticationMethod'])) { $authenticator = trim($_REQUEST['AuthenticationMethod']); @@ -270,7 +270,7 @@ class Security extends Controller { * * @todo Check how to activate/deactivate authentication methods */ - protected function GetLoginForms() + public function GetLoginForms() { $forms = array(); From b65180a7f622adeea61dcd0098e1063fb82c5386 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 14 Dec 2012 01:53:36 +0100 Subject: [PATCH 36/49] Changelog update for grouped CMS buttons --- docs/en/changelogs/3.1.0.md | 44 ++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/docs/en/changelogs/3.1.0.md b/docs/en/changelogs/3.1.0.md index 95ebdc5dc..79acfc0d9 100644 --- a/docs/en/changelogs/3.1.0.md +++ b/docs/en/changelogs/3.1.0.md @@ -4,6 +4,48 @@ ## Upgrading +### Grouped CMS Buttons + +The CMS buttons are now grouped, in order to hide minor actions by default and declutter the interface. +This required changing the form field structure from a simple `FieldList` +to a `FieldList` which contains a `CompositeField` for all "major actions", +and a `TabSet` with a single tab for all "minor actions". +If you have previously added, removed or altered built-in CMS actions in any way, +you'll need to adjust your code. + + :::php + class MyPage extends Page { + function getCMSActions() { + $actions = parent::getCMSActions(); + + // Inserting a new toplevel action (old) + $actions->push(new FormAction('MyAction')); + + // Inserting a new toplevel action (new) + $actions->insertAfter(new FormAction('MyAction'), 'MajorActions'); + + // Removing an action, both toplevel and nested (no change required) + $actions->removeByName('action_unpublish'); + + // Inserting a new minor action (new) + $actions->addFieldToTab( + 'Root.ActionMenus.MoreOptions', + new FormAction('MyMinorAction') + ); + + // Finding a toplevel action (no change required) + $match = $actions->dataFieldByName('action_save'); + + // Finding a nested action (new) + $match = $actions->fieldByName('ActionMenus.MoreOptions') + ->fieldByName('action_MyMinorAction'); + + return $actions; + } + } + +### Other + * `TableListField`, `ComplexTableField`, `TableField`, `HasOneComplexTableField`, `HasManyComplexTableField` and `ManyManyComplexTableField` have been removed from the core and placed into a module called "legacytablefields" located at https://github.com/silverstripe-labs/legacytablefields * `prototype.js` and `behaviour.js` have been removed from the core, they are no longer used. If you have custom code relying on these two libraries, please update your code to include the files yourself * `Object::has_extension()` and `Object::add_extension()` deprecated in favour of using late static binding, please use `{class}::has_extension()` and `{class}::add_extension()` instead, where {class} is the class name of your DataObject class. @@ -45,4 +87,4 @@ - `DataList#find` - `DataList#byIDs` - `DataList#reverse` - * `DataList#dataQuery` has been changed to return a clone of the query, and so can't be used to modify the list's query directly. Use `DataList#alterDataQuery` instead to modify dataQuery in a safe manner. + * `DataList#dataQuery` has been changed to return a clone of the query, and so can't be used to modify the list's query directly. Use `DataList#alterDataQuery` instead to modify dataQuery in a safe manner. \ No newline at end of file From 26141718ecdf3f3ab03664a93ebc7b4d449d0b45 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 30 Nov 2012 10:59:26 +0100 Subject: [PATCH 37/49] BUG Deep cloning for DateTimeField --- forms/DatetimeField.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/forms/DatetimeField.php b/forms/DatetimeField.php index f4b665cc5..52b4c258e 100644 --- a/forms/DatetimeField.php +++ b/forms/DatetimeField.php @@ -279,6 +279,11 @@ class DatetimeField extends FormField { return $field; } + + public function __clone() { + $this->dateField = clone $this->dateField; + $this->timeField = clone $this->timeField; + } } /** From dbaf407569e8a4781ba87c2debbf13adf441a5bc Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 30 Nov 2012 11:45:23 +0100 Subject: [PATCH 38/49] Fixed help text alignment for checkbox and grid fields --- admin/css/screen.css | 3 ++- admin/scss/_forms.scss | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/admin/css/screen.css b/admin/css/screen.css index e075d1518..91f99fc2c 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -162,7 +162,8 @@ form.nostyle input.text, form.nostyle textarea, form.nostyle select, form.nostyl .field .fieldgroup .fieldgroup-field.last { /* This is used on page/settings/visibility */ padding-bottom: 8px; /* replicates li item spacing */ } .field .help { clear: both; color: #777777; display: block; font-style: italic; margin: 4px 0 0 184px; } .field.help label.right { clear: both; color: #777777; display: block; font-style: italic; margin: 4px 0 0 184px; } -.field input.text, .field textarea, .field select, .field .TreeDropdownField { margin-left: 10px; width: 100%; max-width: 512px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } +.field.checkbox .help, .field.ss-gridfield .help { margin-left: 0; } +.field input.text, .field textarea, .field select, .field .TreeDropdownField { width: 100%; max-width: 512px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .field input.text.help, .field textarea.help, .field select.help, .field .TreeDropdownField.help { margin: 0; } .field input.text .help, .field textarea .help, .field select .help, .field .TreeDropdownField .help { max-width: 512px; } .field input.text, .field textarea, .field .TreeDropdownField { background: #fff; border: 1px solid #b3b3b3; padding: 7px 7px; line-height: 16px; margin: 0; outline: none; -moz-transition: 0.2s box-shadow ease-in; -webkit-transition: 0.2s box-shadow ease-in; -o-transition: 0.2s box-shadow ease-in; transition: 0.2s box-shadow ease-in; -moz-transition: 0.2s border ease-in; -webkit-transition: 0.2s border ease-in; -o-transition: 0.2s border ease-in; transition: 0.2s border ease-in; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VhZWFlYSIvPjxzdG9wIG9mZnNldD0iMTAlIiBzdG9wLWNvbG9yPSIjZmZmZmZmIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g'); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #eaeaea), color-stop(10%, #ffffff)); background-image: -webkit-linear-gradient(#eaeaea, #ffffff 10%); background-image: -moz-linear-gradient(#eaeaea, #ffffff 10%); background-image: -o-linear-gradient(#eaeaea, #ffffff 10%); background-image: linear-gradient(#eaeaea, #ffffff 10%); } diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss index b9ff0b522..65718a92d 100644 --- a/admin/scss/_forms.scss +++ b/admin/scss/_forms.scss @@ -106,6 +106,9 @@ form.nostyle { margin: $grid-y/2 0 0 $grid-x*23; } } + &.checkbox .help, &.ss-gridfield .help { + margin-left: 0; + } input.text, textarea, From 212c427c4517bbef5ab75c6340b55de9619a817e Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 30 Nov 2012 12:10:09 +0100 Subject: [PATCH 39/49] Pass setDescription() through to sub-fields in DatetimeField and PhoneNumberField --- forms/DatetimeField.php | 11 ++++++++++- forms/PhoneNumberField.php | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/forms/DatetimeField.php b/forms/DatetimeField.php index 52b4c258e..10a5775c4 100644 --- a/forms/DatetimeField.php +++ b/forms/DatetimeField.php @@ -87,7 +87,7 @@ class DatetimeField extends FormField { public function Field($properties = array()) { Requirements::css(FRAMEWORK_DIR . '/css/DatetimeField.css'); - + $tzField = ($this->getConfig('usertimezone')) ? $this->timezoneField->FieldHolder() : ''; return $this->dateField->FieldHolder() . $this->timeField->FieldHolder() . @@ -236,6 +236,15 @@ class DatetimeField extends FormField { public function getLocale() { return $this->dateField->getLocale(); } + + public function setDescription($description) { + parent::setDescription($description); + + $this->dateField->setDescription($description); + $this->timeField->setDescription($description); + + return $this; + } /** * Note: Use {@link getDateField()} and {@link getTimeField()} diff --git a/forms/PhoneNumberField.php b/forms/PhoneNumberField.php index 93c138bfb..c698e8047 100644 --- a/forms/PhoneNumberField.php +++ b/forms/PhoneNumberField.php @@ -54,6 +54,9 @@ class PhoneNumberField extends FormField { $field->push(new NumericField( $this->name.'[Extension]', 'ext', $extension, 6)); } + $description = $this->getDescription(); + if($description) $fields->getChildren()->First()->setDescription($description); + foreach($fields as $field) { $field->setDisabled($this->isDisabled()); $field->setReadonly($this->isReadonly()); From 255b4c44d304f9dc049022a901f03981384337b4 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 30 Nov 2012 12:16:02 +0100 Subject: [PATCH 40/49] UploadField->setDescription() support, removed extraneous "title" attrs The "title" attrs interfere with the new default tooltip logic in LeftAndMain.FieldHelp.js --- templates/UploadField.ss | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/templates/UploadField.ss b/templates/UploadField.ss index 6a10370cd..83a901921 100644 --- a/templates/UploadField.ss +++ b/templates/UploadField.ss @@ -33,7 +33,9 @@
    <% end_if %> <% else %> -
    style="display: none;"<% end_if %>> +
    style="display: none;"<% end_if %> <% if Description %>title="$Description" + + <% end_if %>>
    <% if $multiple %> <% _t('UploadField.DROPFILES', 'drop files') %> @@ -49,13 +51,13 @@ <% _t('UploadField.ATTACHFILE', 'Attach a file') %> <% end_if %> -
    From 6f9d01f621eab063fd8021c782b48913a6070978 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Dec 2012 16:22:58 +0100 Subject: [PATCH 41/49] API FormField->setDescription() visible in default template Renders into instead of "title" attribute --- docs/en/changelogs/3.1.0.md | 4 ++-- forms/FormField.php | 1 - templates/forms/CheckboxSetField.ss | 2 +- templates/forms/FormField.ss | 4 +--- templates/forms/FormField_holder.ss | 1 + templates/forms/OptionsetField.ss | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/en/changelogs/3.1.0.md b/docs/en/changelogs/3.1.0.md index 79acfc0d9..a880275c9 100644 --- a/docs/en/changelogs/3.1.0.md +++ b/docs/en/changelogs/3.1.0.md @@ -60,10 +60,10 @@ you'll need to adjust your code. including `Email_BounceHandler` and `Email_BounceRecord` classes, as well as the `Member->Bounced` property. * Deprecated global email methods `htmlEmail()` and `plaintextEmail`, as well as various email helper methods like `encodeMultipart()`. Use the `Email` API, or the `Mailer` class where applicable. - * Removed non-functional `$inlineImages` option for sending emails + * Removed non-functional `$inlineImages` option for sending emails * Removed support for keyed arrays in `SelectionGroup`, use new `SelectionGroup_Item` object to populate the list instead (see [API docs](api:SelectionGroup)). - * Removed `Form->Name()`: Use getName() + * `FormField->setDescription()` now renders in a `` by default, rather than a `title` attribute * Removed `Form->Name()`: Use getName() * Removed `FormField->setContainerFieldSet()`: Use setContainerFieldList() * Removed `FormField->rootFieldSet()`: Use rootFieldList() * Removed `Group::map()`: Use DataList::("Group")->map() diff --git a/forms/FormField.php b/forms/FormField.php index f0727c632..9fc412470 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -356,7 +356,6 @@ class FormField extends RequestHandler { 'class' => $this->extraClass(), 'id' => $this->ID(), 'disabled' => $this->isDisabled(), - 'title' => $this->getDescription(), ); return array_merge($attrs, $this->attributes); diff --git a/templates/forms/CheckboxSetField.ss b/templates/forms/CheckboxSetField.ss index b8962957a..cb6a3bb45 100644 --- a/templates/forms/CheckboxSetField.ss +++ b/templates/forms/CheckboxSetField.ss @@ -1,4 +1,4 @@ -
      title="$Description"<% end_if %>> +
        <% if Options.Count %> <% loop Options %>
      • diff --git a/templates/forms/FormField.ss b/templates/forms/FormField.ss index a29e2a33e..73a06489b 100644 --- a/templates/forms/FormField.ss +++ b/templates/forms/FormField.ss @@ -1,7 +1,5 @@ <% if isReadonly %> - class="$extraClass"<% end_if %> - <% if $Description %>title="$Description"<% end_if %>> + class="$extraClass"<% end_if %>> $Value <% else %> diff --git a/templates/forms/FormField_holder.ss b/templates/forms/FormField_holder.ss index ffa9fc725..fa23b8595 100644 --- a/templates/forms/FormField_holder.ss +++ b/templates/forms/FormField_holder.ss @@ -5,4 +5,5 @@
    <% if RightTitle %><% end_if %> <% if Message %>$Message<% end_if %> + <% if Description %>$Description<% end_if %>
    \ No newline at end of file diff --git a/templates/forms/OptionsetField.ss b/templates/forms/OptionsetField.ss index d1a29d0cb..445e3cd0b 100644 --- a/templates/forms/OptionsetField.ss +++ b/templates/forms/OptionsetField.ss @@ -1,4 +1,4 @@ -
      title="$Description"<% end_if %>> +
        <% loop Options %>
      • checked<% end_if %><% if isDisabled %> disabled<% end_if %> /> From 559abecd56eec1c9a153a515c0645f1c84088e39 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Dec 2012 13:51:28 +0100 Subject: [PATCH 42/49] API Copying instance props on FormField readonly/disabled transformations Introduced new FormField->castedCopy() method which tries to replicate the existing form field instance as closely as possible. Primarily, the fix was targeted at consistently passing through FormField->description to all of its variations. --- forms/CheckboxField.php | 6 ---- forms/CheckboxSetField.php | 6 ++-- forms/CompositeField.php | 7 ++++ forms/ConfirmedPasswordField.php | 6 ++-- forms/CurrencyField.php | 4 +-- forms/DateField.php | 16 +++++++-- forms/DatetimeField.php | 18 ++++++++-- forms/DropdownField.php | 6 ++-- forms/FormField.php | 59 +++++++++++++++++++++++++------ forms/HtmlEditorField.php | 4 +-- forms/InlineFormAction.php | 2 +- forms/LookupField.php | 2 +- forms/OptionsetField.php | 5 ++- forms/PasswordField.php | 8 ++--- forms/TextareaField.php | 14 -------- forms/TimeField.php | 14 +++++++- forms/TreeDropdownField.php | 51 ++++++++++++++++++++++++-- forms/TreeMultiselectField.php | 12 +++---- tests/forms/CheckboxFieldTest.php | 1 + 19 files changed, 172 insertions(+), 69 deletions(-) diff --git a/forms/CheckboxField.php b/forms/CheckboxField.php index c90341037..6adf1a325 100644 --- a/forms/CheckboxField.php +++ b/forms/CheckboxField.php @@ -40,12 +40,6 @@ class CheckboxField extends FormField { $field->setForm($this->form); return $field; } - - public function performDisabledTransformation() { - $clone = clone $this; - $clone->setDisabled(true); - return $clone; - } } diff --git a/forms/CheckboxSetField.php b/forms/CheckboxSetField.php index 9d174706c..e7af85412 100644 --- a/forms/CheckboxSetField.php +++ b/forms/CheckboxSetField.php @@ -276,10 +276,8 @@ class CheckboxSetField extends OptionsetField { } } - $title = ($this->title) ? $this->title : ''; - - $field = new ReadonlyField($this->name, $title, $values); - $field->setForm($this->form); + $field = $this->castedCopy('ReadonlyField'); + $field->setValue($values); return $field; } diff --git a/forms/CompositeField.php b/forms/CompositeField.php index a41c4555c..23e66a20d 100644 --- a/forms/CompositeField.php +++ b/forms/CompositeField.php @@ -265,6 +265,8 @@ class CompositeField extends FormField { $clone->children = $newChildren; $clone->readonly = true; + $clone->addExtraClass($this->extraClass()); + $clone->setDescription($this->getDescription()); return $clone; } @@ -285,6 +287,11 @@ class CompositeField extends FormField { $clone->children = $newChildren; $clone->readonly = true; + $clone->addExtraClass($this->extraClass()); + $clone->setDescription($this->getDescription()); + foreach($this->attributes as $k => $v) { + $clone->setAttribute($k, $v); + } return $clone; } diff --git a/forms/ConfirmedPasswordField.php b/forms/ConfirmedPasswordField.php index 844e98367..e92cb468a 100644 --- a/forms/ConfirmedPasswordField.php +++ b/forms/ConfirmedPasswordField.php @@ -320,10 +320,10 @@ class ConfirmedPasswordField extends FormField { * Makes a pretty readonly field with some stars in it */ public function performReadonlyTransformation() { - $stars = '*****'; + $field = $this->castedCopy('ReadonlyField') + ->setTitle($this->title ? $this->title : _t('Member.PASSWORD')) + ->setValue('*****'); - $field = new ReadonlyField($this->name, $this->title ? $this->title : _t('Member.PASSWORD'), $stars); - $field->setForm($this->form); return $field; } } diff --git a/forms/CurrencyField.php b/forms/CurrencyField.php index 440e926f9..9d934a3ba 100644 --- a/forms/CurrencyField.php +++ b/forms/CurrencyField.php @@ -40,9 +40,7 @@ class CurrencyField extends TextField { * Create a new class for this field */ public function performReadonlyTransformation() { - $field = new CurrencyField_Readonly($this->name, $this->title, $this->value); - $field -> addExtraClass($this->extraClass()); - return $field; + return $this->castedCopy('CurrencyField_Readonly'); } public function validate($validator) { diff --git a/forms/DateField.php b/forms/DateField.php index 0a8c4e191..a6a1dba99 100644 --- a/forms/DateField.php +++ b/forms/DateField.php @@ -279,13 +279,25 @@ class DateField extends TextField { } public function performReadonlyTransformation() { - $field = new DateField_Disabled($this->name, $this->title, $this->dataValue()); - $field->setForm($this->form); + $field = $this->castedCopy('DateField_Disabled'); + $field->setValue($this->dataValue()); $field->readonly = true; return $field; } + public function castedCopy($class) { + $copy = new $class($this->name); + if($copy->hasMethod('setConfig')) { + $config = $this->getConfig(); + foreach($config as $k => $v) { + $copy->setConfig($k, $v); + } + } + + return parent::castedCopy($copy); + } + /** * Validate an array with expected keys 'day', 'month' and 'year. * Used because Zend_Date::isDate() doesn't provide this. diff --git a/forms/DatetimeField.php b/forms/DatetimeField.php index 10a5775c4..13ce16a53 100644 --- a/forms/DatetimeField.php +++ b/forms/DatetimeField.php @@ -283,8 +283,22 @@ class DatetimeField extends FormField { } public function performReadonlyTransformation() { - $field = new DatetimeField_Readonly($this->name, $this->title, $this->dataValue()); - $field->setForm($this->form); + $field = $this->castedCopy('DatetimeField_Readonly'); + $field->setValue($this->dataValue()); + + $dateFieldConfig = $this->getDateField()->getConfig(); + if($dateFieldConfig) { + foreach($dateFieldConfig as $k => $v) { + $field->getDateField()->setConfig($k, $v); + } + } + + $timeFieldConfig = $this->getTimeField()->getConfig(); + if($timeFieldConfig) { + foreach($timeFieldConfig as $k => $v) { + $field->getTimeField()->setConfig($k, $v); + } + } return $field; } diff --git a/forms/DropdownField.php b/forms/DropdownField.php index 449747d93..e1d018936 100644 --- a/forms/DropdownField.php +++ b/forms/DropdownField.php @@ -239,10 +239,10 @@ class DropdownField extends FormField { } public function performReadonlyTransformation() { - $field = new LookupField($this->name, $this->title, $this->getSource()); - $field->setValue($this->value); - $field->setForm($this->form); + $field = $this->castedCopy('LookupField'); + $field->setSource($this->getSource()); $field->setReadonly(true); + return $field; } } diff --git a/forms/FormField.php b/forms/FormField.php index 9fc412470..b5f73a535 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -357,7 +357,7 @@ class FormField extends RequestHandler { 'id' => $this->ID(), 'disabled' => $this->isDisabled(), ); - + return array_merge($attrs, $this->attributes); } @@ -709,10 +709,9 @@ class FormField extends RequestHandler { * Returns a readonly version of this field */ public function performReadonlyTransformation() { - $field = new ReadonlyField($this->name, $this->title, $this->value); - $field->addExtraClass($this->extraClass()); - $field->setForm($this->form); - return $field; + $copy = $this->castedCopy('ReadonlyField'); + $copy->setReadonly(true); + return $copy; } /** @@ -723,14 +722,15 @@ class FormField extends RequestHandler { * @return FormField */ public function performDisabledTransformation() { - $clone = clone $this; - $disabledClassName = $clone->class . '_Disabled'; + $disabledClassName = $this->class . '_Disabled'; if(ClassInfo::exists($disabledClassName)) { - return new $disabledClassName($this->name, $this->title, $this->value); + $clone = $this->castedCopy($disabledClassName); } else { + $clone = clone $this; $clone->setDisabled(true); - return $clone; } + + return $clone; } public function transform(FormTransformation $trans) { @@ -774,7 +774,8 @@ class FormField extends RequestHandler { /** * Describe this field, provide help text for it. - * By default, renders as a "title" attribute on the form field. + * By default, renders as a + * underneath the form field. * * @return string Description */ @@ -828,5 +829,43 @@ class FormField extends RequestHandler { if(is_object($this->containerFieldList)) return $this->containerFieldList->rootFieldList(); else user_error("rootFieldList() called on $this->class object without a containerFieldList", E_USER_ERROR); } + + /** + * Returns another instance of this field, but "cast" to a different class. + * The logic tries to retain all of the instance properties, + * and may be overloaded by subclasses to set additional ones. + * + * Assumes the standard FormField parameter signature with + * its name as the only mandatory argument. Mainly geared towards + * creating *_Readonly or *_Disabled subclasses of the same type, + * or casting to a {@link ReadonlyField}. + * + * Does not copy custom field templates, since they probably won't apply to + * the new instance. + * + * @param String $classOrCopy Class name for copy, or existing copy instance to update + * @return FormField + */ + public function castedCopy($classOrCopy) { + $field = (is_object($classOrCopy)) ? $classOrCopy : new $classOrCopy($this->name); + $field + ->setValue($this->value) // get value directly from property, avoid any conversions + ->setForm($this->form) + ->setTitle($this->Title()) + ->setLeftTitle($this->LeftTitle()) + ->setRightTitle($this->RightTitle()) + ->addExtraClass($this->extraClass()) + ->setDescription($this->getDescription()); + + // Only include built-in attributes, ignore anything + // set through getAttributes(), since those might change important characteristics + // of the field, e.g. its "type" attribute. + foreach($this->attributes as $k => $v) { + $field->setAttribute($k, $v); + } + $field->dontEscape = $this->dontEscape; + + return $field; + } } diff --git a/forms/HtmlEditorField.php b/forms/HtmlEditorField.php index e4c0a7fa6..c4df22b65 100644 --- a/forms/HtmlEditorField.php +++ b/forms/HtmlEditorField.php @@ -204,9 +204,9 @@ class HtmlEditorField extends TextareaField { * @return HtmlEditorField_Readonly */ public function performReadonlyTransformation() { - $field = new HtmlEditorField_Readonly($this->name, $this->title, $this->value); - $field->setForm($this->form); + $field = $this->castedCopy('HtmlEditorField_Readonly'); $field->dontEscape = true; + return $field; } diff --git a/forms/InlineFormAction.php b/forms/InlineFormAction.php index 25a0b6811..58d7de909 100644 --- a/forms/InlineFormAction.php +++ b/forms/InlineFormAction.php @@ -24,7 +24,7 @@ class InlineFormAction extends FormField { } public function performReadonlyTransformation() { - return new InlineFormAction_ReadOnly( $this->name, $this->title ); + return $this->castedCopy('InlineFormAction_ReadOnly'); } public function Field($properties = array()) { diff --git a/forms/LookupField.php b/forms/LookupField.php index 1a9ef2be4..82a5d52ea 100644 --- a/forms/LookupField.php +++ b/forms/LookupField.php @@ -32,7 +32,7 @@ class LookupField extends DropdownField { // Don't check if string arguments are matching against the source, // as they might be generated HTML diff views instead of the actual values - if($this->value && !$mapped) { + if($this->value && !is_array($this->value) && !$mapped) { $mapped = array(trim($this->value)); $values = array(); } diff --git a/forms/OptionsetField.php b/forms/OptionsetField.php index f223aaf23..5e2e36e12 100644 --- a/forms/OptionsetField.php +++ b/forms/OptionsetField.php @@ -92,9 +92,8 @@ class OptionsetField extends DropdownField { public function performReadonlyTransformation() { // Source and values are DataObject sets. - $items = $this->getSource(); - $field = new LookupField($this->name, $this->title ? $this->title : '', $items, $this->value); - $field->setForm($this->form); + $field = $this->castedCopy('LookupField'); + $field->setValue($this->getSource()); $field->setReadonly(true); return $field; diff --git a/forms/PasswordField.php b/forms/PasswordField.php index 10cc45715..156deb8be 100644 --- a/forms/PasswordField.php +++ b/forms/PasswordField.php @@ -31,11 +31,9 @@ class PasswordField extends TextField { * Makes a pretty readonly field with some stars in it */ public function performReadonlyTransformation() { - $stars = '*****'; - - $field = new ReadonlyField($this->name, $this->title ? $this->title : '', $stars); - $field->setForm($this->form); - $field->setReadonly(true); + $field = $this->castedCopy('ReadonlyField'); + $field->setValue('*****'); + return $field; } diff --git a/forms/TextareaField.php b/forms/TextareaField.php index 92863c6ca..3dfe30971 100644 --- a/forms/TextareaField.php +++ b/forms/TextareaField.php @@ -44,20 +44,6 @@ class TextareaField extends FormField { ); } - /** - * Performs a disabled transformation on this field. You shouldn't be able to - * copy from this field, and it should not send any data when you submit the - * form it's attached to. - * - * The element shouldn't be both disabled and readonly at the same time. - */ - public function performDisabledTransformation() { - $clone = clone $this; - $clone->setDisabled(true); - $clone->setReadonly(false); - return $clone; - } - public function Type() { return parent::Type() . ($this->readonly ? ' readonly' : ''); } diff --git a/forms/TimeField.php b/forms/TimeField.php index b1e5e427d..33c0fa079 100644 --- a/forms/TimeField.php +++ b/forms/TimeField.php @@ -191,7 +191,19 @@ class TimeField extends TextField { * Creates a new readonly field specified below */ public function performReadonlyTransformation() { - return new TimeField_Readonly($this->name, $this->title, $this->dataValue(), $this->getConfig('timeformat')); + return $this->castedCopy('TimeField_Readonly'); + } + + public function castedCopy($class) { + $copy = parent::castedCopy($class); + if($copy->hasMethod('setConfig')) { + $config = $this->getConfig(); + foreach($config as $k => $v) { + $copy->setConfig($k, $v); + } + } + + return $copy; } } diff --git a/forms/TreeDropdownField.php b/forms/TreeDropdownField.php index 3d7874e8f..01112b9cc 100644 --- a/forms/TreeDropdownField.php +++ b/forms/TreeDropdownField.php @@ -291,6 +291,48 @@ class TreeDropdownField extends FormField { return true; } + + /** + * @param String $field + */ + public function setLabelField($field) { + $this->labelField = $field; + } + + /** + * @return String + */ + public function getLabelField() { + return $this->labelField; + } + + /** + * @param String $field + */ + public function setKeyField($field) { + $this->keyField = $field; + } + + /** + * @return String + */ + public function getKeyField() { + return $this->keyField; + } + + /** + * @param String $field + */ + public function setSourceObject($class) { + $this->sourceObject = $class; + } + + /** + * @return String + */ + public function getSourceObject() { + return $this->sourceObject; + } /** * Populate $this->searchIds with the IDs of the pages matching the searched parameter and their parents. @@ -342,9 +384,14 @@ class TreeDropdownField extends FormField { * Changes this field to the readonly field. */ public function performReadonlyTransformation() { - return new TreeDropdownField_Readonly($this->name, $this->title, $this->sourceObject, $this->keyField, - $this->labelField); + $copy = $this->castedCopy('TreeDropdownField_Readonly'); + $copy->setKeyField($this->keyField); + $copy->setLabelField($this->labelField); + $copy->setSourceObject($this->sourceObject); + + return $copy; } + } /** diff --git a/forms/TreeMultiselectField.php b/forms/TreeMultiselectField.php index f651e0ad2..e8c53a299 100644 --- a/forms/TreeMultiselectField.php +++ b/forms/TreeMultiselectField.php @@ -175,14 +175,12 @@ class TreeMultiselectField extends TreeDropdownField { * Changes this field to the readonly field. */ public function performReadonlyTransformation() { - $field = new TreeMultiselectField_Readonly($this->name, $this->title, $this->sourceObject, - $this->keyField, $this->labelField); + $copy = $this->castedCopy('TreeMultiselectField_Readonly'); + $copy->setKeyField($this->keyField); + $copy->setLabelField($this->labelField); + $copy->setSourceObject($this->sourceObject); - $field->addExtraClass($this->extraClass()); - $field->setForm($this->form); - $field->setValue($this->value); - - return $field; + return $copy; } } diff --git a/tests/forms/CheckboxFieldTest.php b/tests/forms/CheckboxFieldTest.php index 28fac29a4..13e007683 100644 --- a/tests/forms/CheckboxFieldTest.php +++ b/tests/forms/CheckboxFieldTest.php @@ -122,6 +122,7 @@ class CheckboxFieldTest extends SapphireTest { // Test 1: a checked checkbox goes to "Yes" $field1 = new CheckboxField('IsChecked', 'Checked'); $field1->setValue('on'); + $copy = $field1->performReadonlyTransformation(); $this->assertEquals(_t('CheckboxField.YES', 'Yes'), trim(strip_tags($field1->performReadonlyTransformation()->Field()))); From 1ca3883a769af9b33ee1f08f0566be61186ef193 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 13 Dec 2012 16:33:58 +0100 Subject: [PATCH 43/49] NEW Tooltip and inline help text support for CMS form fields --- admin/css/screen.css | 15 ++++---- admin/javascript/LeftAndMain.FieldHelp.js | 42 ++++++++++++++--------- admin/scss/_forms.scss | 24 +++++-------- docs/en/howto/cms-formfield-help-text.md | 30 ++++++++++++++++ docs/en/howto/index.md | 1 + forms/gridfield/GridField.php | 7 ++++ templates/forms/CheckboxField_holder.ss | 1 + templates/forms/CompositeField_holder.ss | 2 ++ 8 files changed, 81 insertions(+), 41 deletions(-) create mode 100644 docs/en/howto/cms-formfield-help-text.md diff --git a/admin/css/screen.css b/admin/css/screen.css index 91f99fc2c..2f058efb2 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -153,19 +153,18 @@ form.nostyle input.text, form.nostyle textarea, form.nostyle select, form.nostyl .field:last-child { border-bottom: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } .field:after { content: "\0020"; display: block; height: 0; clear: both; overflow: hidden; visibility: hidden; } .field.nolabel .middleColumn { margin-left: 0; } -.field.nolabel .help { margin-left: 0; } +.field.nolabel .description { margin-left: 0; } .field.checkbox label.right { margin: 4px 0 0 0; display: inline; font-style: normal; color: #444444; clear: none; } .field label.left { float: left; display: block; width: 176px; padding: 8px 8px 8px 0; line-height: 16px; font-weight: bold; text-shadow: 1px 1px 0 white; } .field label.right { cursor: pointer; clear: both; color: #777777; display: block; font-style: italic; margin: 4px 0 0 184px; } .field .middleColumn { margin-left: 184px; } .field span.readonly { padding-top: 8px; line-height: 16px; display: block; } .field .fieldgroup .fieldgroup-field.last { /* This is used on page/settings/visibility */ padding-bottom: 8px; /* replicates li item spacing */ } -.field .help { clear: both; color: #777777; display: block; font-style: italic; margin: 4px 0 0 184px; } -.field.help label.right { clear: both; color: #777777; display: block; font-style: italic; margin: 4px 0 0 184px; } -.field.checkbox .help, .field.ss-gridfield .help { margin-left: 0; } -.field input.text, .field textarea, .field select, .field .TreeDropdownField { width: 100%; max-width: 512px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } -.field input.text.help, .field textarea.help, .field select.help, .field .TreeDropdownField.help { margin: 0; } -.field input.text .help, .field textarea .help, .field select .help, .field .TreeDropdownField .help { max-width: 512px; } +.field .description { clear: both; color: #777777; display: block; font-style: italic; margin: 4px 0 0 184px; } +.field.checkbox .description, .field.ss-gridfield .description { margin-left: 0; } +.field input.text, .field textarea, .field select, .field .TreeDropdownField { margin-left: 10px; width: 100%; max-width: 512px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } +.field input.text.description, .field textarea.description, .field select.description, .field .TreeDropdownField.description { margin: 0; } +.field input.text .description, .field textarea .description, .field select .description, .field .TreeDropdownField .description { max-width: 512px; } .field input.text, .field textarea, .field .TreeDropdownField { background: #fff; border: 1px solid #b3b3b3; padding: 7px 7px; line-height: 16px; margin: 0; outline: none; -moz-transition: 0.2s box-shadow ease-in; -webkit-transition: 0.2s box-shadow ease-in; -o-transition: 0.2s box-shadow ease-in; transition: 0.2s box-shadow ease-in; -moz-transition: 0.2s border ease-in; -webkit-transition: 0.2s border ease-in; -o-transition: 0.2s border ease-in; transition: 0.2s border ease-in; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VhZWFlYSIvPjxzdG9wIG9mZnNldD0iMTAlIiBzdG9wLWNvbG9yPSIjZmZmZmZmIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g'); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #eaeaea), color-stop(10%, #ffffff)); background-image: -webkit-linear-gradient(#eaeaea, #ffffff 10%); background-image: -moz-linear-gradient(#eaeaea, #ffffff 10%); background-image: -o-linear-gradient(#eaeaea, #ffffff 10%); background-image: linear-gradient(#eaeaea, #ffffff 10%); } .field input.text:focus, .field textarea:focus, .field .TreeDropdownField:focus { border: 1px solid #9a9a9a; border-top-color: gray; -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) inset; -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) inset; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2) inset; } .field input[disabled], .field input.disabled, .field textarea[disabled], .field textarea.disabled, .field select[disabled], .field select.disabled { color: #777777; background: #efefef; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2JjYmNiYyIvPjxzdG9wIG9mZnNldD0iMTAlIiBzdG9wLWNvbG9yPSIjZWZlZmVmIi8+PHN0b3Agb2Zmc2V0PSI5MCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNiY2JjYmMiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #bcbcbc), color-stop(10%, #efefef), color-stop(90%, #ffffff), color-stop(100%, #bcbcbc)); background-image: -webkit-linear-gradient(#bcbcbc, #efefef 10%, #ffffff 90%, #bcbcbc); background-image: -moz-linear-gradient(#bcbcbc, #efefef 10%, #ffffff 90%, #bcbcbc); background-image: -o-linear-gradient(#bcbcbc, #efefef 10%, #ffffff 90%, #bcbcbc); background-image: linear-gradient(#bcbcbc, #efefef 10%, #ffffff 90%, #bcbcbc); border: 1px solid #b3b3b3; } @@ -243,7 +242,7 @@ form.small .field input.text, form.small .field textarea, form.small .field sele .ss-toggle .ui-accordion-content .field:last-child { margin-bottom: 0; } .ss-toggle .ui-accordion-content .field .middleColumn { margin-left: 0; } .ss-toggle .ui-accordion-content .field label { float: none; margin-left: 0; } -.ss-toggle .ui-accordion-content .field .help { margin-left: 0; } +.ss-toggle .ui-accordion-content .field .description { margin-left: 0; } /** ---------------------------------------------------- Checkbox Field ---------------------------------------------------- */ .field.checkbox { padding-left: 184px; margin-bottom: 8px; } diff --git a/admin/javascript/LeftAndMain.FieldHelp.js b/admin/javascript/LeftAndMain.FieldHelp.js index a773cd437..d5e05c980 100644 --- a/admin/javascript/LeftAndMain.FieldHelp.js +++ b/admin/javascript/LeftAndMain.FieldHelp.js @@ -1,27 +1,35 @@ (function($) { $.entwine('ss', function($) { /** - * Takes form fields with a title attribute, extracts it, and displays - * it as inline help text below the field. + * Converts an inline field description into a tooltip + * which is shown on hover over any part of the field container, + * as well as when focusing into an input element within the field container. + * + * Note that some fields don't have distinct focusable + * input fields (e.g. GridField), and aren't compatible + * with showing tooltips. */ - $(".cms form .field .middleColumn > [title]").entwine({ + $(".cms .field.cms-description-tooltip").entwine({ onmatch: function() { - - var title = this.prop("title"); - var field = this.closest(".field"); - - if(title && title.length && field.has('.help').length == 0) { - var span = $("", { - "class": "help", - "text": title - }); - - field.append(span); - this.removeProp("title"); + var descriptionEl = this.find('.description'), inputEl, tooltipEl; + if(descriptionEl.length) { + this + // TODO Remove title setting, shouldn't be necessary + .attr('title', descriptionEl.text()) + .tooltip({content: descriptionEl.html()}); + descriptionEl.remove(); } - - this._super(); } }); + + $(".cms .field.cms-description-tooltip :input").entwine({ + onfocusin: function(e) { + this.closest('.field').tooltip('open'); + }, + onfocusout: function(e) { + this.closest('.field').tooltip('close'); + } + }); + }); }(jQuery)); diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss index 65718a92d..79c68631c 100644 --- a/admin/scss/_forms.scss +++ b/admin/scss/_forms.scss @@ -42,7 +42,7 @@ form.nostyle { .middleColumn { margin-left: 0; } - .help { + .description { margin-left: 0; } } @@ -90,23 +90,15 @@ form.nostyle { // Additional help text to clarify the field intent, // displayed alongside the field (rather than in a tooltip) - .help { + .description { clear: both; color: lighten($color-text, 20%); display: block; font-style: italic; - margin: $grid-y/2 0 0 $grid-x*23; + margin: $grid-y/2 0 0 $grid-x*23; // left align with .middleColumn } - &.help { - label.right { - clear: both; - color: lighten($color-text, 20%); - display: block; - font-style: italic; - margin: $grid-y/2 0 0 $grid-x*23; - } - } - &.checkbox .help, &.ss-gridfield .help { + + &.checkbox .description, &.ss-gridfield .description { margin-left: 0; } @@ -118,10 +110,10 @@ form.nostyle { width: 100%; max-width: $grid-x * 64; @include box-sizing(border-box); - &.help { + &.description { margin:0; //overrides help class adding left margin to the textarea input. } - .help { + .description { max-width: $grid-x * 64; } } @@ -588,7 +580,7 @@ form.small .field, .field.small { float: none; margin-left: 0; } - .help { + .description { margin-left: 0; } } diff --git a/docs/en/howto/cms-formfield-help-text.md b/docs/en/howto/cms-formfield-help-text.md new file mode 100644 index 000000000..37f7c5b23 --- /dev/null +++ b/docs/en/howto/cms-formfield-help-text.md @@ -0,0 +1,30 @@ +# How to Show Help Text on CMS Form Fields + +Sometimes you need to express more context for a form field +than is suitable for its `
      • \n"; $id = $this->id(); return "
          extraClass()}\">\n$options
        \n"; @@ -64,42 +61,46 @@ class MemberDatetimeOptionsetField extends OptionsetField { /** * @todo Put this text into a template? */ - public function getFormattingHelpText() { - $output = '
          '; - $output .= '
        • YYYY = ' . _t('MemberDatetimeOptionsetField.FOURDIGITYEAR', 'Four-digit year', - 40, 'Help text describing what "YYYY" means in ISO date formatting') . '
        • '; - $output .= '
        • YY = ' . _t('MemberDatetimeOptionsetField.TWODIGITYEAR', 'Two-digit year', - 40, 'Help text describing what "YY" means in ISO date formatting') . '
        • '; - $output .= '
        • MMMM = ' . _t('MemberDatetimeOptionsetField.FULLNAMEMONTH', 'Full name of month (e.g. June)', - 40, 'Help text describing what "MMMM" means in ISO date formatting') . '
        • '; - $output .= '
        • MMM = ' . _t('MemberDatetimeOptionsetField.SHORTMONTH', 'Short name of month (e.g. Jun)', - 40, 'Help text letting describing what "MMM" means in ISO date formatting') . '
        • '; - $output .= '
        • MM = ' . _t('MemberDatetimeOptionsetField.TWODIGITMONTH', 'Two-digit month (01=January, etc.)', - 40, 'Help text describing what "MM" means in ISO date formatting') . '
        • '; - $output .= '
        • M = ' . _t('MemberDatetimeOptionsetField.MONTHNOLEADING', 'Month digit without leading zero', - 40, 'Help text describing what "M" means in ISO date formatting') . '
        • '; - $output .= '
        • dd = ' . _t('MemberDatetimeOptionsetField.TWODIGITDAY', 'Two-digit day of month', - 40, 'Help text describing what "dd" means in ISO date formatting') . '
        • '; - $output .= '
        • d = ' . _t('MemberDatetimeOptionsetField.DAYNOLEADING', 'Day of month without leading zero', - 40, 'Help text describing what "d" means in ISO date formatting') . '
        • '; - $output .= '
        • hh = ' . _t('MemberDatetimeOptionsetField.TWODIGITHOUR', 'Two digits of hour (00 through 23)', - 40, 'Help text describing what "hh" means in ISO date formatting') . '
        • '; - $output .= '
        • h = ' . _t('MemberDatetimeOptionsetField.HOURNOLEADING', 'Hour without leading zero', - 40, 'Help text describing what "h" means in ISO date formatting') . '
        • '; - $output .= '
        • mm = ' . _t('MemberDatetimeOptionsetField.TWODIGITMINUTE', + public function getDescription() { + $output = + '' + . _t('MemberDatetimeOptionsetField.Toggle', 'Show formatting help') + . '' + . '
            ' + . '
          • YYYY = ' . _t('MemberDatetimeOptionsetField.FOURDIGITYEAR', 'Four-digit year', + 40, 'Help text describing what "YYYY" means in ISO date formatting') . '
          • ' + . '
          • YY = ' . _t('MemberDatetimeOptionsetField.TWODIGITYEAR', 'Two-digit year', + 40, 'Help text describing what "YY" means in ISO date formatting') . '
          • ' + . '
          • MMMM = ' . _t('MemberDatetimeOptionsetField.FULLNAMEMONTH', 'Full name of month (e.g. June)', + 40, 'Help text describing what "MMMM" means in ISO date formatting') . '
          • ' + . '
          • MMM = ' . _t('MemberDatetimeOptionsetField.SHORTMONTH', 'Short name of month (e.g. Jun)', + 40, 'Help text letting describing what "MMM" means in ISO date formatting') . '
          • ' + . '
          • MM = ' . _t('MemberDatetimeOptionsetField.TWODIGITMONTH', 'Two-digit month (01=January, etc.)', + 40, 'Help text describing what "MM" means in ISO date formatting') . '
          • ' + . '
          • M = ' . _t('MemberDatetimeOptionsetField.MONTHNOLEADING', 'Month digit without leading zero', + 40, 'Help text describing what "M" means in ISO date formatting') . '
          • ' + . '
          • dd = ' . _t('MemberDatetimeOptionsetField.TWODIGITDAY', 'Two-digit day of month', + 40, 'Help text describing what "dd" means in ISO date formatting') . '
          • ' + . '
          • d = ' . _t('MemberDatetimeOptionsetField.DAYNOLEADING', 'Day of month without leading zero', + 40, 'Help text describing what "d" means in ISO date formatting') . '
          • ' + . '
          • hh = ' . _t('MemberDatetimeOptionsetField.TWODIGITHOUR', 'Two digits of hour (00 through 23)', + 40, 'Help text describing what "hh" means in ISO date formatting') . '
          • ' + . '
          • h = ' . _t('MemberDatetimeOptionsetField.HOURNOLEADING', 'Hour without leading zero', + 40, 'Help text describing what "h" means in ISO date formatting') . '
          • ' + . '
          • mm = ' . _t('MemberDatetimeOptionsetField.TWODIGITMINUTE', 'Two digits of minute (00 through 59)', - 40, 'Help text describing what "mm" means in ISO date formatting') . '
          • '; - $output .= '
          • m = ' . _t('MemberDatetimeOptionsetField.MINUTENOLEADING', 'Minute without leading zero', - 40, 'Help text describing what "m" means in ISO date formatting') . '
          • '; - $output .= '
          • ss = ' . _t('MemberDatetimeOptionsetField.TWODIGITSECOND', + 40, 'Help text describing what "mm" means in ISO date formatting') . '
          • ' + . '
          • m = ' . _t('MemberDatetimeOptionsetField.MINUTENOLEADING', 'Minute without leading zero', + 40, 'Help text describing what "m" means in ISO date formatting') . '
          • ' + . '
          • ss = ' . _t('MemberDatetimeOptionsetField.TWODIGITSECOND', 'Two digits of second (00 through 59)', - 40, 'Help text describing what "ss" means in ISO date formatting') . '
          • '; - $output .= '
          • s = ' . _t('MemberDatetimeOptionsetField.DIGITSDECFRACTIONSECOND', + 40, 'Help text describing what "ss" means in ISO date formatting') . '
          • ' + . '
          • s = ' . _t('MemberDatetimeOptionsetField.DIGITSDECFRACTIONSECOND', 'One or more digits representing a decimal fraction of a second', - 40, 'Help text describing what "s" means in ISO date formatting') . '
          • '; - $output .= '
          • a = ' . _t('MemberDatetimeOptionsetField.AMORPM', 'AM (Ante meridiem) or PM (Post meridiem)', - 40, 'Help text describing what "a" means in ISO date formatting') . '
          • '; - $output .= '
          '; + 40, 'Help text describing what "s" means in ISO date formatting') . '
        • ' + . '
        • a = ' . _t('MemberDatetimeOptionsetField.AMORPM', 'AM (Ante meridiem) or PM (Post meridiem)', + 40, 'Help text describing what "a" means in ISO date formatting') . '
        • ' + . '
        '; return $output; } diff --git a/tests/behat/features/profile.feature b/tests/behat/features/profile.feature new file mode 100644 index 000000000..3ca0e3ab9 --- /dev/null +++ b/tests/behat/features/profile.feature @@ -0,0 +1,13 @@ +@database-defaults +Feature: My Profile + As a CMS user + I want to be able to change personal settings + In order to streamline my CMS experience + + @javascript + Scenario: I can see date formatting help + Given I am logged in with "ADMIN" permissions + # Only tests this specific field and admin UI because its got built-in tooltips + When I go to "/admin/myprofile" + And I follow "Show formatting help" + Then I should see "Four-digit year" \ No newline at end of file From d2c1d53a893606d58934409eb65287389e178d52 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 14 Dec 2012 02:04:47 +0100 Subject: [PATCH 45/49] Fixed UploadField->setDescription() handlgin --- templates/UploadField.ss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/templates/UploadField.ss b/templates/UploadField.ss index 83a901921..91cef77d6 100644 --- a/templates/UploadField.ss +++ b/templates/UploadField.ss @@ -33,9 +33,7 @@ <% end_if %> <% else %> -
        style="display: none;"<% end_if %> <% if Description %>title="$Description" - - <% end_if %>> +
        style="display: none;"<% end_if %>>
        <% if $multiple %> <% _t('UploadField.DROPFILES', 'drop files') %> @@ -64,3 +62,4 @@
        <% end_if %> +<% if Description %>$Description<% end_if %> \ No newline at end of file From 244bc97794319037b1ce59e06b4ec6f6256f9189 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 14 Dec 2012 14:28:26 +1300 Subject: [PATCH 46/49] Don't register a PGSQL failure as a Travis build failure. Although PGSQL build will still be executed, a failure of that build won't registre as a Travis failure. This is a workaround until the PGSQL build has been fixed. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1f49fe992..2932002a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,8 @@ matrix: env: TESTDB=PGSQL - php: 5.4 env: TESTDB=SQLITE + allow_failures: + - env: TESTDB=PGSQL before_script: - pear install pear/PHP_CodeSniffer From 681a0241e7b5d71356a19a25fdb5b07c29a95292 Mon Sep 17 00:00:00 2001 From: Stig Lindqvist Date: Fri, 14 Dec 2012 16:15:22 +1300 Subject: [PATCH 47/49] =?UTF-8?q?DOC=20Removed=20link=20to=20missing=C2=A0?= =?UTF-8?q?'extending-the-cms.md'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/topics/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/en/topics/index.md b/docs/en/topics/index.md index 49b5447ae..b70885bff 100644 --- a/docs/en/topics/index.md +++ b/docs/en/topics/index.md @@ -14,7 +14,6 @@ It is where most documentation should live, and is the natural "second step" aft * [Emails](email): Configuring and sending emails * [Environment management](environment-management): Sharing configuration details (e.g. database login, passwords) with multiple websites via a `_ss_environment.php` file * [Error Handling](error-handling): Error messages and filesystem logs - * [Extending the CMS](extending-the-cms): Introduction to changing the default CMS editor * [Files and Images](files): File and Image management in the database and how to manipulate images * [Form Validation](form-validation): Built-in validation on form fields, and how to extend it * [Forms](forms): Create your own form, add fields and create your own form template using the existing `Form` class @@ -29,4 +28,4 @@ It is where most documentation should live, and is the natural "second step" aft * [Testing](testing): Functional and Unit Testing with PHPUnit and SilverStripe's testing framework * [Developing Themes](theme-development): Package templates, images and CSS to a reusable theme * [Widgets](widgets): Small feature blocks which can be placed on a page by the CMS editor, also outlines how to create and add widgets - * [Versioning](versioning): Extension for SiteTree and other classes to store old versions and provide "staging" \ No newline at end of file + * [Versioning](versioning): Extension for SiteTree and other classes to store old versions and provide "staging" From 90084bb708075162c1ae2e01994bae1a53501149 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 14 Dec 2012 10:15:52 +0100 Subject: [PATCH 48/49] Separate PHPCS run on travis, don't fail whole build for it --- .travis.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2932002a0..1083a7d3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,15 @@ matrix: env: TESTDB=PGSQL - php: 5.4 env: TESTDB=SQLITE + include: + - php: 5.4 + env: + - PHPCS=1 allow_failures: - env: TESTDB=PGSQL + - php: 5.4 + env: + - PHPCS=1 before_script: - pear install pear/PHP_CodeSniffer @@ -23,17 +30,13 @@ before_script: - cd ~/builds/ss script: - - phpunit -c phpunit.xml.dist - - phpcs --encoding=utf-8 --tab-width=4 --standard=framework/tests/phpcs/ruleset.xml -np framework - - phpcs --encoding=utf-8 --standard=framework/tests/phpcs/tabs.xml -np framework + - sh -c "if [ '$PHPCS' != '1' ]; then phpunit -c phpunit.xml.dist; else phpcs --encoding=utf-8 --tab-width=4 --standard=framework/tests/phpcs/ruleset.xml -np framework && phpcs --encoding=utf-8 --standard=framework/tests/phpcs/tabs.xml -np framework; fi" branches: except: - 2.1 - 2.2 - 2.3 - - 2.4 - - post-2.4 - translation-staging notifications: From 4f5b3fa3a6e621574c13fd1020c2d9d27d6cca3a Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 14 Dec 2012 10:40:40 +0100 Subject: [PATCH 49/49] Readd SQlite to travis builds, having it fail harms TDD --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1083a7d3f..214f1ca08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ php: env: - TESTDB=MYSQL - TESTDB=PGSQL + - TESTDB=SQLITE matrix: exclude: