From a4fff63020578222dcbf845d22128a4c5c141f09 Mon Sep 17 00:00:00 2001 From: Loz Calver Date: Tue, 10 Mar 2015 16:55:24 +0000 Subject: [PATCH 01/76] Performance improvements to DataObject::is_composite_field() --- model/DataObject.php | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/model/DataObject.php b/model/DataObject.php index 239ae2e5f..88132c894 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -161,6 +161,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity protected static $_cache_get_one; protected static $_cache_get_class_ancestry; protected static $_cache_composite_fields = array(); + protected static $_cache_is_composite_field = array(); protected static $_cache_custom_database_fields = array(); protected static $_cache_field_labels = array(); @@ -308,14 +309,25 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * Will check all applicable ancestor classes and aggregate results. */ public static function is_composite_field($class, $name, $aggregated = true) { - if(!isset(DataObject::$_cache_composite_fields[$class])) self::cache_composite_fields($class); + $key = $class . '_' . $name . '_' . (string)$aggregated; + + if(!isset(DataObject::$_cache_is_composite_field[$key])) { + $isComposite = null; + + if(!isset(DataObject::$_cache_composite_fields[$class])) { + self::cache_composite_fields($class); + } - if(isset(DataObject::$_cache_composite_fields[$class][$name])) { - return DataObject::$_cache_composite_fields[$class][$name]; - - } else if($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') { - return self::is_composite_field($parentClass, $name); + if(isset(DataObject::$_cache_composite_fields[$class][$name])) { + $isComposite = DataObject::$_cache_composite_fields[$class][$name]; + } elseif($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') { + $isComposite = self::is_composite_field($parentClass, $name); + } + + DataObject::$_cache_is_composite_field[$key] = ($isComposite) ? $isComposite : false; } + + return DataObject::$_cache_is_composite_field[$key] ?: null; } /** @@ -2960,6 +2972,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity DataObject::$_cache_db = array(); DataObject::$_cache_get_one = array(); DataObject::$_cache_composite_fields = array(); + DataObject::$_cache_is_composite_field = array(); DataObject::$_cache_custom_database_fields = array(); DataObject::$_cache_get_class_ancestry = array(); DataObject::$_cache_field_labels = array(); From f2b1fa9aed57cb1b013b1962afd88dc14300bae1 Mon Sep 17 00:00:00 2001 From: Jeremy Shipman Date: Mon, 16 Mar 2015 09:55:33 +1300 Subject: [PATCH 02/76] FIX: broken link in docs to how_tos/extend_cms_interface I'm not certain this fixes the issue, as I haven't tested it. It at least points it out. --- .../15_Customising_the_Admin_Interface/05_CMS_Architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/05_CMS_Architecture.md b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/05_CMS_Architecture.md index 11ac98f58..640836c15 100644 --- a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/05_CMS_Architecture.md +++ b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/05_CMS_Architecture.md @@ -13,7 +13,7 @@ feel familiar to you. This is just a quick run down to get you started with some special conventions. For a more practical-oriented approach to CMS customizations, refer to the -[Howto: Extend the CMS Interface](../how_tos/extend_cms_interface) which builds +[Howto: Extend the CMS Interface](how_tos/extend_cms_interface) which builds ## Markup and Style Conventions From 03ec9e80f0614bf27f4c357a9918f11807308ff5 Mon Sep 17 00:00:00 2001 From: Jacob Buck Date: Thu, 26 Mar 2015 11:48:24 +1300 Subject: [PATCH 03/76] Add more 3xx status codes to SS_HTTPResponse::isFinished method --- control/HTTPResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/HTTPResponse.php b/control/HTTPResponse.php index 1df15f913..90c37d7db 100644 --- a/control/HTTPResponse.php +++ b/control/HTTPResponse.php @@ -286,7 +286,7 @@ EOT * @return bool */ public function isFinished() { - return in_array($this->statusCode, array(301, 302, 401, 403)); + return in_array($this->statusCode, array(301, 302, 303, 304, 305, 307, 401, 403)); } } From 636cddb4bc94901cb5de54351dc93cf0dacb8352 Mon Sep 17 00:00:00 2001 From: Naomi Guyer Date: Thu, 26 Mar 2015 14:08:46 +1300 Subject: [PATCH 04/76] BUG: export and print buttons outside button row Export and print buttons are appearing outside the button row in model admin, meaning that if the add button is removed (say, by removing the create permission), the buttons are flush with the gridfield. --- admin/code/ModelAdmin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/code/ModelAdmin.php b/admin/code/ModelAdmin.php index 9a0d20abc..dd78c1dd7 100644 --- a/admin/code/ModelAdmin.php +++ b/admin/code/ModelAdmin.php @@ -137,7 +137,7 @@ abstract class ModelAdmin extends LeftAndMain { public function getEditForm($id = null, $fields = null) { $list = $this->getList(); - $exportButton = new GridFieldExportButton('before'); + $exportButton = new GridFieldExportButton('buttons-before-left'); $exportButton->setExportColumns($this->getExportFields()); $listField = GridField::create( $this->sanitiseClassName($this->modelClass), @@ -146,7 +146,7 @@ abstract class ModelAdmin extends LeftAndMain { $fieldConfig = GridFieldConfig_RecordEditor::create($this->stat('page_length')) ->addComponent($exportButton) ->removeComponentsByType('GridFieldFilterHeader') - ->addComponents(new GridFieldPrintButton('before')) + ->addComponents(new GridFieldPrintButton('buttons-before-left')) ); // Validation From c5006be3d6f6ff2a127692afd5cac200b8fde425 Mon Sep 17 00:00:00 2001 From: John Milmine Date: Wed, 1 Apr 2015 21:55:24 +1300 Subject: [PATCH 05/76] fixing print functionality if passed array data --- forms/gridfield/GridFieldPrintButton.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/forms/gridfield/GridFieldPrintButton.php b/forms/gridfield/GridFieldPrintButton.php index 4bb2e3098..9019c9d49 100644 --- a/forms/gridfield/GridFieldPrintButton.php +++ b/forms/gridfield/GridFieldPrintButton.php @@ -204,8 +204,9 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr $itemRows->push(new ArrayData(array( "ItemRow" => $itemRow ))); - - $item->destroy(); + if ($item->hasMethod('destroy')) { + $item->destroy(); + } } $ret = new ArrayData(array( From 72bb9a208752eafa4d9581e34cf60aeda28ffb31 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Mon, 23 Mar 2015 14:52:18 +0000 Subject: [PATCH 06/76] FIX Debug::text no longer incorrecty returns "ViewableData_debugger" --- dev/Debug.php | 99 +++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/dev/Debug.php b/dev/Debug.php index a6d068e16..b01354521 100644 --- a/dev/Debug.php +++ b/dev/Debug.php @@ -1,34 +1,34 @@ \n
\n" . "

Debug ($caller[class]$caller[type]$caller[function]()" . " \nin " . basename($caller['file']) . ":$caller[line])\n

\n"; } - + echo Debug::text($val); - + if(!Director::is_ajax() && !Director::is_cli()) echo ""; else echo "\n\n"; } @@ -73,7 +73,7 @@ class Debug { /** * Returns the caller for a specific method - * + * * @return array */ public static function caller() { @@ -100,7 +100,7 @@ class Debug { die(); } } - + /** * Quick dump of a variable. * @@ -123,9 +123,14 @@ class Debug { } else { $hasDebugMethod = method_exists($val, 'debug'); } - + if($hasDebugMethod) { - return $val->debug(); + $debug = $val->debug(); + // Conditional not necessary after 3.2. See https://github.com/silverstripe/silverstripe-framework/pull/4034 + if ($debug instanceof ViewableData_Debugger) { + $debug = $debug->forTemplate(); + } + return $debug; } } @@ -168,15 +173,15 @@ class Debug { } } } - + // Keep track of how many headers have been sent private static $headerCount = 0; - + /** * Send a debug message in an HTTP header. Only works if you are * on Dev, and headers have not yet been sent. * - * @param string $msg + * @param string $msg * @param string $prefix (optional) * @return void */ @@ -217,7 +222,7 @@ class Debug { public static function noticeHandler($errno, $errstr, $errfile, $errline, $errcontext) { if(error_reporting() == 0) return; ini_set('display_errors', 0); - + // Send out the error details to the logger for writing SS_Log::log( array( @@ -261,7 +266,7 @@ class Debug { ), SS_Log::WARN ); - + if(Director::isDev()) { return self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Warning"); } else { @@ -271,7 +276,7 @@ class Debug { /** * Handle a fatal error, depending on the mode of the site (ie: Dev, Test, or Live). - * + * * Runtime execution dies immediately once the error is generated. * * @param unknown_type $errno @@ -282,7 +287,7 @@ class Debug { */ public static function fatalHandler($errno, $errstr, $errfile, $errline, $errcontext) { ini_set('display_errors', 0); - + // Send out the error details to the logger for writing SS_Log::log( array( @@ -294,7 +299,7 @@ class Debug { ), SS_Log::ERR ); - + if(Director::isDev() || Director::is_cli()) { self::showError($errno, $errstr, $errfile, $errline, $errcontext, "Error"); } else { @@ -302,12 +307,12 @@ class Debug { } return false; } - + /** * Render a user-facing error page, using the default HTML error template * rendered by {@link ErrorPage} if it exists. Doesn't use the standard {@link SS_HTTPResponse} class - * the keep dependencies minimal. - * + * the keep dependencies minimal. + * * @uses ErrorPage * * @param int $statusCode HTTP Status Code (Default: 500) @@ -321,7 +326,7 @@ class Debug { if(!$friendlyErrorMessage) { $friendlyErrorMessage = Config::inst()->get('Debug', 'friendly_error_header'); } - + if(!$friendlyErrorDetail) { $friendlyErrorDetail = Config::inst()->get('Debug', 'friendly_error_detail'); } @@ -343,7 +348,7 @@ class Debug { } else { if(class_exists('ErrorPage')){ $errorFilePath = ErrorPage::get_filepath_for_errorcode( - $statusCode, + $statusCode, class_exists('Translatable') ? Translatable::get_current_locale() : null ); if(file_exists($errorFilePath)) { @@ -356,7 +361,7 @@ class Debug { $renderer = new DebugView(); $renderer->writeHeader(); $renderer->writeInfo("Website Error", $friendlyErrorMessage, $friendlyErrorDetail); - + if(Email::config()->admin_email) { $mailto = Email::obfuscate(Email::config()->admin_email); $renderer->writeParagraph('Contact an administrator: ' . $mailto . ''); @@ -370,7 +375,7 @@ class Debug { /** * Create an instance of an appropriate DebugView object. - * + * * @return DebugView */ public static function create_debug_view() { @@ -396,15 +401,15 @@ class Debug { $errText = str_replace(array("\n","\r")," ",$errText); if(!headers_sent()) header($_SERVER['SERVER_PROTOCOL'] . " 500 $errText"); - + // if error is displayed through ajax with CliDebugView, use plaintext output if(Director::is_ajax()) { header('Content-Type: text/plain'); - } + } } - + $reporter = self::create_debug_view(); - + // Coupling alert: This relies on knowledge of how the director gets its URL, it could be improved. $httpRequest = null; if(isset($_SERVER['REQUEST_URI'])) { @@ -431,7 +436,7 @@ class Debug { $reporter->writeTrace(($errcontext ? $errcontext : debug_backtrace())); $reporter->writeFooter(); } - + /** * Utility method to render a snippet of PHP source code, from selected file * and highlighting the given line number. @@ -454,9 +459,9 @@ class Debug { } $offset++; } - echo ''; + echo ''; } - + /** * Check if the user has permissions to run URL debug tools, * else redirect them to log in. @@ -470,12 +475,12 @@ class Debug { // This means we have to be careful about what objects we create, as we don't want Object::defineMethods() // being called again. // This basically calls Permission::checkMember($_SESSION['loggedInAs'], 'ADMIN'); - + $memberID = $_SESSION['loggedInAs']; - + $groups = DB::query("SELECT \"GroupID\" from \"Group_Members\" WHERE \"MemberID\" = " . $memberID); $groupCSV = implode($groups->column(), ','); - + $permission = DB::query(" SELECT \"ID\" FROM \"Permission\" @@ -485,12 +490,12 @@ class Debug { AND \"GroupID\" IN ($groupCSV) ) ")->value(); - + if($permission) { return; } } - + // This basically does the same as // Security::permissionFailure(null, "You need to login with developer access to make use of debugging tools.") // We have to do this because of how early this method is called in execution. @@ -515,8 +520,8 @@ class Debug { /** * Generic callback, to catch uncaught exceptions when they bubble up to the top of the call chain. - * - * @ignore + * + * @ignore * @param Exception $exception */ function exceptionHandler($exception) { @@ -533,8 +538,8 @@ function exceptionHandler($exception) { * Generic callback to catch standard PHP runtime errors thrown by the interpreter * or manually triggered with the user_error function. * Caution: The error levels default to E_ALL is the site is in dev-mode (set in main.php). - * - * @ignore + * + * @ignore * @param int $errno * @param string $errstr * @param string $errfile From 83a3c4b16651f032d401a76fb1d963c5e20e7324 Mon Sep 17 00:00:00 2001 From: Elliot Sawyer Date: Thu, 2 Apr 2015 15:51:51 +1300 Subject: [PATCH 07/76] Update 01_Extensions.md Fix a typo --- docs/en/02_Developer_Guides/05_Extending/01_Extensions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/02_Developer_Guides/05_Extending/01_Extensions.md b/docs/en/02_Developer_Guides/05_Extending/01_Extensions.md index 08aab63e9..a00a037cf 100644 --- a/docs/en/02_Developer_Guides/05_Extending/01_Extensions.md +++ b/docs/en/02_Developer_Guides/05_Extending/01_Extensions.md @@ -112,7 +112,7 @@ we added a `SayHi` method which is unique to our extension. ## Modifying Existing Methods -If the `Extension` needs to modify an existing method it's a little tricker. It requires that the method you want to +If the `Extension` needs to modify an existing method it's a little trickier. It requires that the method you want to customize has provided an *Extension Hook* in the place where you want to modify the data. An *Extension Hook* is done through the `[api:Object->extend]` method. From 27727df096a6aaf67584f9e12ebd9a5804458e0f Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 8 Apr 2015 22:38:20 +1200 Subject: [PATCH 08/76] Updated MAMP install docs, added Homebrew install docs * Removed the `php.ini` configuration bits from both MAMP and Homebrew instructions, both actually come with reasonable defaults now (high `memory_limit`, `error_reporting` on full, `date.timezone` set). * Removed chapter about package install, we should favour composer now * Compressed the install wizard instructions --- .../01_Installation/02_Mac_OSX.md | 98 ++++++--------- .../Mac_OSX_Homebrew.md | 118 ++++++++++++++++++ docs/en/00_Getting_Started/index.md | 3 +- 3 files changed, 155 insertions(+), 64 deletions(-) create mode 100644 docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Mac_OSX_Homebrew.md diff --git a/docs/en/00_Getting_Started/01_Installation/02_Mac_OSX.md b/docs/en/00_Getting_Started/01_Installation/02_Mac_OSX.md index ac1bfecff..92ee440db 100644 --- a/docs/en/00_Getting_Started/01_Installation/02_Mac_OSX.md +++ b/docs/en/00_Getting_Started/01_Installation/02_Mac_OSX.md @@ -1,75 +1,49 @@ -# Mac OSX +# Mac OSX with MAMP -This topic covers setting up your Mac as a Web Server and installing SilverStripe. +This topic covers setting up your Mac as a web server and installing SilverStripe. -While OSX Comes bundled with PHP and Apache (Thanks Apple!) Its not quite ideal for SilverStripe so for setting up a -webserver on OSX we suggest using [MAMP](http://www.mamp.info/en/index.php) or using [MacPorts](http://www.macports.org/) -to manage your packages. +OSX comes bundled with PHP and Apache, but you're stuck with the versions it ships with. +It is also a bit harder to install additional PHP modules required by SilverStripe. +[MAMP](http://www.mamp.info/en/) is a simple way to get a complete webserver +environment going on your OSX machine, without removing or altering any system-level configuration. -If you want to use the default OSX PHP version then you will need to recompile your own versions of PHP with GD. Providing instructions -for how to recompile PHP is beyond the scope of our documentation but try an online search. +Check out the [MAC OSX with Homebrew](other_installation_options/Mac_OSX_Homebrew) +for an alternative, more configurable installation process. -## Installing MAMP +## Requirements -If you have decided to install using MacPorts you can skip this section. +Please check the [system requirements](http://www.mamp.info/en/documentation/) for MAMP, +you'll need a fairly new version of OSX to run it. -Once you have downloaded and Installed MAMP start the Application and Make sure everything is running by clicking the -MAMP icon. Under `Preferences -> PHP` make sure Version 5 is Selected. +## MAMP Installation -Open up `/Applications/MAMP/conf/PHP5/php.ini` and make the following configuration changes: + * [Download MAMP](http://www.mamp.info/en/) + * Install and start MAMP + * Check out your new web server environment on `http://localhost:8888` - memory_limit = 64M +## SilverStripe Installation -Once you make that change open the MAMP App Again by clicking on the MAMP Icon and click Stop Servers then Start -Servers - this is so our changes to the php.ini take effect. +[Composer](http://getcomposer.org) is a dependancy manager for PHP, and the preferred way to +install SilverStripe. It ensures that you get the correct set of files for your project. +Composer uses your MAMP PHP executable to run and also requires [git](http://git-scm.com) +to automatically download the required files from GitHub and other repositories. -## Installing SilverStripe +In order to install Composer, we need to let the system know where to find the PHP executable. +Open or create the `~/.bash_profile` file in your home folder, then add the following line: +`export PATH=/Applications/MAMP/bin/php/php5.5.22/bin:$PATH` +You'll need to adjust the PHP version number (`php5.5.22`). The currently running PHP version is shown on `http://localhost:8888/MAMP/index.php?page=phpinfo`. +Run `source ~/.bash_profile` for the changes to take effect. You can verify that the correct executable +is used by running `which php`. It should show the path to MAMP from above. -### Composer -[Composer (a dependancy manager for PHP)](http://getcomposer.org) is the preferred way to install SilverStripe and ensure you get the correct set of files for your project. +Now you're ready to install Composer: Run `curl -sS https://getcomposer.org/installer | php`. +We recommend that you make the `composer` executable available globally, +which requires moving the file to a different folder. Run `mv composer.phar /usr/local/bin/composer`. +More detailed installation instructions are available on [getcomposer.org](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx). +You can verify the installation by typing the `composer` command, which should show you a command overview. -Composer uses your MAMP PHP executable to run and also requires [git](http://git-scm.com) (so it can automatically download the required files from GitHub). +Finally, we're ready to install SilverStripe through composer: +`composer create-project silverstripe/installer /Applications/MAMP/htdocs/silverstripe/`. +After finishing, the installation wizard should be available at `http://localhost:8888/silverstripe`. +The MAMP default database credentials are user `root` and password `root`. -#### Install composer using MAMP - 1. First create an alias for our bash profile, using your preferred terminal text editor (nano, vim, etc) open `~/.bash_profile`. - - 2. Add the following line (adjusting the version number of PHP to your installation of MAMP): `alias phpmamp='/Applications/MAMP/bin/php/php5.4.10/bin/php'`. - - 3. The run `. ~/.bash_profile` to reload the bash profile and make it accessible. - - 4. This will create an alias, `phpmamp`, allowing you to use the MAMP installation of PHP. Please take note of the PHP version, in this case 5.4.10, as with different versions of MAMP this may be different. Check your installation and see what version you have, and replace the number accordingly (this was written with MAMP version 2.1.2). - - 5. With that setup, we are ready to install `composer`. This is a two step process if we would like this to be installed globally (only do the first step if you would like `composer` installed to the local working directory only). - - First, run the following command in the terminal: `curl -sS https://getcomposer.org/installer | phpmamp` - - We are using `phpmamp` so that we correctly use the MAMP installation of PHP from above. - - - Second, if you want to make composer available globally, we need to move the file to '/usr/local/bin/composer'. To do this, run the following command: - `sudo mv composer.phar /usr/local/bin/composer` - - Terminal will ask you for your root password, after entering it and pressing the 'return' (or enter) key, you'll have a working global installation of composer on your mac that uses MAMP. - - 6. You can verify your installation worked by typing the following command: - `composer` - It'll show you the current version and a list of commands you can use. - - 7. Run the following command to get a fresh copy of SilverStripe via composer: - - `composer create-project silverstripe/installer /Applications/MAMP/htdocs/silverstripe/` - - 8. You can now [use composer](http://doc.silverstripe.org/framework/en/getting_started/composer/) to manage future SilverStripe updates and adding modules with a few easy commands. - - -### Package Download - -[Download](http://silverstripe.org/software/download/) the latest SilverStripe installer package. Copy the tar.gz or zip file to the 'Document Root' for MAMP - By Default its `/Applications/MAMP/htdocs`. -Don't know what your Document Root is? Open MAMP Click `Preferences -> Apache`. - -Extract the tar.gz file to a folder, e.g. `silverstripe/` (you always move the tar.gz file first and not the other way -around as SilverStripe uses a '.htaccess' file which is hidden from OSX so if you move SilverStripe the .htaccess file -won't come along. - -### Run the installation wizard -Once you have a copy of the required code (by either of the above methods), open your web browser and go to `http://localhost:8888/silverstripe/`. Enter your database details - by default with MAMP its user `root` and password `root` and select your account details. Click "Check Details". - -Once everything is sorted hit "Install!" and Voila, you have SilverStripe installed +We have a separate in-depth tutorial for [Composer Installation and Usage](composer). \ No newline at end of file diff --git a/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Mac_OSX_Homebrew.md b/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Mac_OSX_Homebrew.md new file mode 100644 index 000000000..2806c804b --- /dev/null +++ b/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Mac_OSX_Homebrew.md @@ -0,0 +1,118 @@ +# Mac OSX with Homebrew + +This topic covers setting up your Mac as a web server and installing SilverStripe. + +OSX comes bundled with PHP, but you're stuck with the version and modules it ships with. +If you run projects on different PHP versions, or care about additional PHP module support +and other dependencies such as MariaDB, we recommend an installation through [Homebrew](http://brew.sh/). + +Check out the [MAC OSX with MAMP](../Mac_OSX) for an alternative installation process +which packages up the whole environment into a convenient application. + +## Requirements + +Since we're compiling PHP, some build tooling is required. +Run the following command to install Xcode Command Line Tools. + + xcode-select --install + +Now you can install Homebrew itself: + + ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + +## Install PHP + +First we're telling Homebrew about some new repositories to get the PHP installation from: + + brew tap homebrew/dupes + brew tap homebrew/php + +We're installing PHP 5.5 here, with the required `mcrypt` module: + + brew install php55 php55-mcrypt + +There's a [Homebrew Troubleshooting](https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Troubleshooting.md) guide if Homebrew doesn't work out as expected (run `brew update` and `brew doctor`). + +Have a look at the [brew-php-switcher](https://github.com/philcook/brew-php-switcher) +project to install multiple PHP versions in parallel and switch between them easily. + +## Install the Database (MariaDB/MySQL) + + brew install mariadb + unset TMPDIR + mysql_install_db --user=`whoami` --basedir="$(brew --prefix mariadb)" --datadir=/usr/local/var/mysql --tmpdir=/tmp + mysql.server start + '/usr/local/opt/mariadb/bin/mysql_secure_installation' + +To start the database server on boot, run the following: + + ln -sfv /usr/local/opt/mariadb/*.plist ~/Library/LaunchAgents + +You can also use `mysql.server start` and `mysql.server stop` on demand. + +## Configure PHP and Apache + +We're not installing Apache, since OSX already ships with a perfectly fine installation of it. + +Edit the existing configuration at `/etc/apache2/httpd.conf`, +and uncomment/add the following lines to activate the required modules: + + LoadModule rewrite_module libexec/apache2/mod_rewrite.so + LoadModule php5_module /usr/local/opt/php55/libexec/apache2/libphp5.so + +Change the `DocumentRoot` setting to your user folder (replacing `` with your OSX user name): + + DocumentRoot "/Users//Sites" + +Now find the section starting with `` and change it as follows, +again replacing `` with your OSX user name: + + /Sites"> + Options FollowSymLinks Multiviews + MultiviewsMatch Any + AllowOverride All + Require all granted + + +We also recommend running the web server process with your own user on a development environment, +since it makes permissions easier to handle when running commands both +from the command line and through the web server. Find and adjust the following options, +replacing the `` placeholder: + + User + Group staff + +Now start the web server: + + sudo apachectl start + +Every configuration change requires a restart: + + sudo apachectl restart + +You can also load this webserver on boot: + + sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist + +After starting the webserver, you should see a simple "Forbidden" page generated by Apache +when accessing `http://localhost`. + +## SilverStripe Installation + +[Composer](http://getcomposer.org) is a dependancy manager for PHP, and the preferred way to +install SilverStripe. It ensures that you get the correct set of files for your project. +Composer uses the PHP executable we've just installed. It also needs [git](http://git-scm.com) +to automatically download the required files from GitHub and other repositories. + +Run `curl -sS https://getcomposer.org/installer | php` to install the `composer` executable. +We recommend that you make the executable available globally, +which requires moving the file to a different folder. Run `mv composer.phar /usr/local/bin/composer`. +More detailed installation instructions are available on [getcomposer.org](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx). +You can verify the installation by typing the `composer` command, which should show you a command overview. + +Finally, we're ready to install SilverStripe through composer: +`composer create-project silverstripe/installer /Users//Sites/silverstripe/`. +After finishing, the installation wizard should be available at `http://localhost/silverstripe`. +The Homebrew MariaDB default database credentials are user `root` and password `root`. + +We have a separate in-depth tutorial for [Composer Installation and Usage](composer). \ No newline at end of file diff --git a/docs/en/00_Getting_Started/index.md b/docs/en/00_Getting_Started/index.md index 83ddfcce7..003d8d11a 100644 --- a/docs/en/00_Getting_Started/index.md +++ b/docs/en/00_Getting_Started/index.md @@ -37,8 +37,7 @@ For more flexibility, you can set up either of the following web servers, and us Mac OS X comes with a built-in webserver, but there are a number of other options: * [Install using MAMP](installation/mac_osx) - * Install using the built-in webserver (no docs yet) - * Install using MacPorts (no docs yet) + * [Install using Homebrew](installation/other_installation_options/mac_osx_homebrew) ## Troubleshooting From b633b584f67bc077d28228de86620f89aa825cb2 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 8 Apr 2015 22:38:34 +1200 Subject: [PATCH 09/76] Simplified base installation step docs Mention composer there rather than just the package download. --- .../00_Getting_Started/01_Installation/index.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/en/00_Getting_Started/01_Installation/index.md b/docs/en/00_Getting_Started/01_Installation/index.md index 9b90f4122..d8daddbbd 100644 --- a/docs/en/00_Getting_Started/01_Installation/index.md +++ b/docs/en/00_Getting_Started/01_Installation/index.md @@ -1,19 +1,16 @@ # Installation -These instructions show you how to install SilverStripe on any web server. -The best way to install from the source code is to use [Composer](../composer). +These instructions show you how to install SilverStripe on any web server. Check out our operating system specific guides for [Linux](linux_unix), [Windows Server](windows) and [Mac OSX](mac_osx). ## Installation Steps -* [Download](http://silverstripe.org/download) the installer package -* Make sure the webserver has MySQL and PHP support. See [Server Requirements](../server_requirements) for more information. -* Unpack the installer somewhere into your web-root. Usually the www folder or similar. Most downloads from SilverStripe -are compressed tarballs. To extract these files you can either do them natively (Unix) or with 7-Zip (Windows) -* Visit your sites domain or IP address in your web browser. -* You will be presented with a form where you enter your MySQL login details and are asked to give your site a 'project -name' and the default login details. Follow the questions and select the *install* button at the bottom of the page. +* Make sure the webserver has MySQL and PHP support (check our [server requirements](../server_requirements)). +* Either [download the installer package](http://silverstripe.org/download), or [install through Composer](../composer). +* If using with the installer download, extract it into your webroot. +* Visit your domain or IP address in your web browser. +* You will be presented with an installation wizard asking for database and login credentials. * After a couple of minutes, your site will be set up. Visit your site and enjoy! ## Issues? From 711efc4542002259e05d4236e72ac9856c4daa2b Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 8 Apr 2015 22:31:43 +1200 Subject: [PATCH 10/76] More install options (Vagrant and Bitnami) --- docs/en/00_Getting_Started/index.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/en/00_Getting_Started/index.md b/docs/en/00_Getting_Started/index.md index 003d8d11a..575969450 100644 --- a/docs/en/00_Getting_Started/index.md +++ b/docs/en/00_Getting_Started/index.md @@ -39,6 +39,26 @@ Mac OS X comes with a built-in webserver, but there are a number of other option * [Install using MAMP](installation/mac_osx) * [Install using Homebrew](installation/other_installation_options/mac_osx_homebrew) +### Virtual Machines through Vagrant + +[Vagrant](https://www.vagrantup.com/) creates portable development environments +which can be hosted on Linux, Windows and Mac OS X. The virtual machine +usually runs a flavour of Linux. As a self-contained pre-configured environment, +getting up an running with Vagrant tends to be easier than creating a complete +development environment from scratch on your own machine. + + * [silverstripe-australia/vagrant-environment](https://github.com/silverstripe-australia/vagrant-environment) + * [BetterBrief/vagrant-skeleton](https://github.com/BetterBrief/vagrant-skeleton) + +Note: These instructions are supported by the community. + +## Virtual Machines through Bitnami + +[Bitnami](https://bitnami.com) is an online service that makes it easy to get +apps running on cloud providers like Amazon Web Services as well as local +virtualised environments. Bitnami has a [SilverStripe Virtual Machine](https://bitnami.com/stack/silverstripe/virtual-machine) +ready for download or installation on a cloud platform. + ## Troubleshooting If you run into trouble, see [common-problems](installation/common_problems) or post to the From 602fdd57fd36a102dcb0675422f42fc615b644fd Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 8 Apr 2015 22:35:54 +1200 Subject: [PATCH 11/76] Removed outdated IIS6 instructions Not linked from anywhere (other than the menu). IIS 6.0 is part of Windows 2003, which has been EOLed as far as I can tell: https://support.microsoft.com/en-us/lifecycle?p1=3198 --- .../Windows_IIS6.md | 152 ------------------ 1 file changed, 152 deletions(-) delete mode 100644 docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Windows_IIS6.md diff --git a/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Windows_IIS6.md b/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Windows_IIS6.md deleted file mode 100644 index 0ad4ee086..000000000 --- a/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Windows_IIS6.md +++ /dev/null @@ -1,152 +0,0 @@ -# Install SilverStripe manually on Windows using IIS 6 - -
Note: These instructions may not work, as they're no longer maintained.
- -How to prepare Windows Server 2003 for SilverStripe using IIS 6 and FastCGI. - -This guide will work for the following operating systems: - - * Windows Server 2003 - * Windows Server 2003 R2 - -Database install and configuration is not covered here, it is assumed you will do this yourself. - -PHP comes with MySQL support out of the box, but you will need to install the [SQL Server Driver for PHP](http://www.microsoft.com/downloads/en/details.aspx?displaylang=en&FamilyID=80e44913-24b4-4113-8807-caae6cf2ca05) -from Microsoft if you want to use SQL Server. - -## Preparation - -Open **Windows Update** and make sure everything is updated, including optional updates. It is important that all .NET Framework updates including service packs are installed. - -## Install IIS - - - Open **Control Panel** > **Add/Remove Programs** - - Click **Add/Remove Windows Components** on the left hand bar - - Check **Application Server** and then click **Next** to install it - -## Install FastCGI for IIS - - - Download and install this package: http://www.iis.net/download/fastcgi - - Open **inetmgr.exe** - - Right click **Web Sites** and go to **Properties** - - Click the **Home Directory** tab - - Click **Configuration...** then **Add** - - In the **Add/Edit Extension Mapping** dialog, click **Browse...** and navigate to fcgiext.dll which is located in %windir%\system32\inetsrv - - In the **Extension** text box, enter **.php** - - Under **Verbs** in the **Limit to** text box, enter **GET,HEAD,POST** - - Ensure that the **Script engine** and **Verify that file exists** boxes are checked then click **OK** - - Open fcgiext.ini located in %windir%\system32\inetsrv. In the [Types] section of the file, add **php=PHP** - - Create a new section called **[PHP]** at the bottom of the file, like this: - - [PHP] - ExePath=c:\php5\php-cgi.exe - -Finally, run these commands in **Command Prompt** - - cd %windir%\system32\inetsrv - cscript fcgiconfig.js -set -section:"PHP" -InstanceMaxRequests:10000 - cscript fcgiconfig.js -set -section:"PHP" -EnvironmentVars:PHP_FCGI_MAX_REQUESTS:10000 - cscript fcgiconfig.js -set -section:"PHP" -ActivityTimeout:300 - -## Install PHP - - - [Download PHP](http://windows.php.net/download) (**Zip** link underneath the **VC9 x86 Non Thread Safe** section) - - [Download WinCache](http://www.iis.net/download/WinCacheForPHP) (**WinCache 1.1 for PHP 5.3**) - - Extract the PHP zip contents to **c:\php5** - - Run the WinCache self-extractor and extract to **c:\php5\ext**. A file called **php_wincache.dll** should now reside in **c:\php5\ext** - - Rename **php.ini-development** to **php.ini** in **c:\php5** - - Open **php.ini**, located in **c:\php5** with **Notepad** or another editor like **Notepad++** - - Search for **date.timezone**, uncomment it by removing the semicolon and set a timezone from here: http://php.net/manual/en/timezones.php - - Search for **fastcgi.impersonate**, uncomment it by removing the semicolon and set it like this: **fastcgi.impersonate = 1** - - Search for **cgi.fix_pathinfo**, uncomment it by removing the semicolon and set it like this: **cgi.fix_pathinfo = 1** - - Search for **cgi.force_redirect**, uncomment it by removing the semicolon and set it like this: **cgi.force_redirect = 0** - - Search for **fastcgi.logging**, uncomment it by removing the semicolon and set it like this: **fastcgi.logging = 0** - - Search for **extension_dir** and make sure it looks like this: **extension_dir = "ext"** (use proper double quotation characters here) - - Find the "Dynamic Extensions" part of the file, and replace all extension entries with the following: - - ;extension=php_bz2.dll - extension=php_curl.dll - ;extension=php_enchant.dll - ;extension=php_exif.dll - ;extension=php_fileinfo.dll - extension=php_gd2.dll - ;extension=php_gettext.dll - ;extension=php_gmp.dll - ;extension=php_imap.dll - ;extension=php_intl.dll - ;extension=php_ldap.dll - extension=php_mbstring.dll - extension=php_mysql.dll - extension=php_mysqli.dll - ;extension=php_oci8.dll - ;extension=php_oci8_11g.dll - ;extension=php_openssl.dll - ;extension=php_pdo_mysql.dll - ;extension=php_pdo_oci.dll - ;extension=php_pdo_odbc.dll - ;extension=php_pdo_pgsql.dll - ;extension=php_pdo_sqlite.dll - ;extension=php_pgsql.dll - ;extension=php_shmop.dll - ;extension=php_snmp.dll - ;extension=php_soap.dll - ;extension=php_sockets.dll - ;extension=php_sqlite3.dll - ;extension=php_sqlite.dll - extension=php_tidy.dll - extension=php_wincache.dll - ;extension=php_xmlrpc.dll - ;extension=php_xsl.dll - -This is a minimal set of loaded extensions which will get you started. - -If want to use **SQL Server** as a database, you will need to install the [SQL Server Driver for PHP](http://www.microsoft.com/downloads/en/details.aspx?displaylang=en&FamilyID=80e44913-24b4-4113-8807-caae6cf2ca05) and add an extension entry for it to the list above. - -## Test PHP - - - Open **Command Prompt** and type the following: - c:\php5\php.exe -v - -You should see some output showing the PHP version. If you get something else, or nothing at all, then there are missing updates for your copy of Windows Server 2003. Open **Windows Update** and make sure you've updated everything including optional updates. - -## Install SilverStripe - - - [Download SilverStripe](http://silverstripe.org/downloads) - - Extract the download contents to **C:\Inetpub\wwwroot\silverstripe** - - Open **inetmgr.exe** - - Right click **Web Sites** and go to **New** > **Web Site** - - Fill in all appropriate details. If you enter **(All Unassigned)** for the IP address field, make sure the port is something other than **80**, as this will conflict with "Default Web Site" in IIS. When asked for path, enter **C:\Inetpub\wwwroot\silverstripe** - - Browse to **http://localhost:8888** or to the IP address you just assigned in your browser. - -An installation screen should appear. There may be some permission problems, which you should be able to correct by assigning the **Users** group write permissions by right clicking files / folders in Windows Explorer and going to **Properties** then the **Security** tab. - -When ready, hit **Install SilverStripe**. - -SilverStripe should now be installed and you should have a basic site with three pages. - -However, URLs will not look "nice", like this: http://localhost/index.php/about-us. In order to fix this problem, we need to install a third-party URL rewriting tool, as IIS 6 does not support this natively. - -Proceed to **Install IIRF** below to enable nice URLs. - -## Install IIRF - -At the moment, all URLs will have index.php in them. This is because IIS does not support URL rewriting. To make this work, we need to install IIRF which is a third-party plugin for IIS. - - - [Download IIRF](http://iirf.codeplex.com/releases/view/36814) and install it - - Create a new file called iirf.ini in C:\inetpub\wwwroot\silverstripe with this content - RewriteEngine On - MaxMatchCount 10 - IterationLimit 5 - # URLs with query strings - # Don't catch successful file references - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^(.*)\?(.+)$ /framework/main.php?url=$1&$2 - # URLs without query strings - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^(.*)$ /framework/main.php?url=$1 - -Friendly URLs should now be working when you browse to your site. - -Remember that IIRF works on a per-virtual host basis. This means for each site you want IIRF to work for, you need to add a new entry to **Web Sites** in **inetmgr.exe**. - -Thanks to **kcd** for the rules: [http://www.silverstripe.org/installing-silverstripe/show/10488#post294415](http://www.silverstripe.org/installing-silverstripe/show/10488#post294415) From 2a16b5e2a370fe85c1e4ac883854666a815ecbc3 Mon Sep 17 00:00:00 2001 From: Sean Harvey Date: Mon, 13 Apr 2015 11:35:24 +1200 Subject: [PATCH 12/76] Fixing spelling mistake --- docs/en/00_Getting_Started/02_Composer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/00_Getting_Started/02_Composer.md b/docs/en/00_Getting_Started/02_Composer.md index b254ca798..a363206e1 100644 --- a/docs/en/00_Getting_Started/02_Composer.md +++ b/docs/en/00_Getting_Started/02_Composer.md @@ -77,7 +77,7 @@ You can find other packages with the following command: composer search silverstripe -This will return a list of package names of the forum `vendor/package`. If you prefer, you can search for pacakges on [packagist.org](https://packagist.org/search/?q=silverstripe). +This will return a list of package names of the forum `vendor/package`. If you prefer, you can search for packages on [packagist.org](https://packagist.org/search/?q=silverstripe). The second part after the colon, `*`, is a version string. `*` is a good default: it will give you the latest version that works with the other modules you have installed. Alternatively, you can specificy a specific version, or a constraint such as `>=3.0`. For more information, read the [Composer documentation](http://getcomposer.org/doc/01-basic-usage.md#the-require-key). From 0d44ea1af1f95a19864cd78ec9ef545fd8fb929a Mon Sep 17 00:00:00 2001 From: Cam Findlay Date: Mon, 13 Apr 2015 13:28:13 +1200 Subject: [PATCH 13/76] DOCS Make a note to ensure people install Git and Composer Since Composer uses git and this isn't explicitly documented anywhere at our end I have added a note and link for newcomers to follow and get Git before getting Composer. --- docs/en/00_Getting_Started/02_Composer.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/00_Getting_Started/02_Composer.md b/docs/en/00_Getting_Started/02_Composer.md index b254ca798..abb73d515 100644 --- a/docs/en/00_Getting_Started/02_Composer.md +++ b/docs/en/00_Getting_Started/02_Composer.md @@ -9,7 +9,9 @@ We also have separate instructions for [installing modules with Composer](/devel ## Installing composer -To install Composer, run the following commands from your command-line. +Before installing Composer you should ensure your system has the version control system, [Git installed](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git). Composer uses Git to check out the code dependancies you need to run your SilverStripe CMS website from the code repositories maintained on GitHub. + +Next, to install Composer, run the following commands from your command-line. # Download composer.phar curl -s https://getcomposer.org/installer | php From 8caaae601baf7065ab8fcd4293ac07fbd979d7d2 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Tue, 14 Apr 2015 13:18:57 +1200 Subject: [PATCH 14/76] BUG Fix accordion sometimes displaying scrollbars --- javascript/ToggleCompositeField.js | 1 + 1 file changed, 1 insertion(+) diff --git a/javascript/ToggleCompositeField.js b/javascript/ToggleCompositeField.js index 75d8b8c37..464e3b4e1 100644 --- a/javascript/ToggleCompositeField.js +++ b/javascript/ToggleCompositeField.js @@ -5,6 +5,7 @@ this._super(); this.accordion({ + heightStyle: "content", collapsible: true, active: (this.hasClass("ss-toggle-start-closed")) ? false : 0 }); From 0d7d6b4bd3580859aa41f9062c1063632f66beb6 Mon Sep 17 00:00:00 2001 From: Steve D Date: Wed, 15 Apr 2015 13:08:39 +1000 Subject: [PATCH 15/76] Improve SQL Format information paragraph Improve SQL Format information paragraph in the SilverStripe Coding Conventions --- docs/en/00_Getting_Started/05_Coding_Conventions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/00_Getting_Started/05_Coding_Conventions.md b/docs/en/00_Getting_Started/05_Coding_Conventions.md index 65b9266c4..22dc4899b 100644 --- a/docs/en/00_Getting_Started/05_Coding_Conventions.md +++ b/docs/en/00_Getting_Started/05_Coding_Conventions.md @@ -431,7 +431,7 @@ Put code into the classes in the following order (where applicable). ### SQL Format -If you have to use raw SQL, make sure your code works across databases make sure you escape your queries like below, +If you have to use raw SQL, make sure your code works across databases. Make sure you escape your queries like below, with the column or table name escaped with double quotes and values with single quotes. :::php From 25c9e9141e6574813cf33cd864d3015d361228d9 Mon Sep 17 00:00:00 2001 From: scott1702 Date: Thu, 16 Apr 2015 15:31:48 +1200 Subject: [PATCH 16/76] Fix z-index bug with sidebar --- admin/css/screen.css | 2 +- admin/scss/_style.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/css/screen.css b/admin/css/screen.css index 916b81774..a23768f04 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -439,7 +439,7 @@ body.cms { overflow: hidden; } .ss-loading-screen .loading-animation { display: none; position: absolute; left: 49%; top: 75%; } /** -------------------------------------------- Actions -------------------------------------------- */ -.cms-content-actions, .cms-preview-controls { margin: 0; padding: 12px 12px; z-index: 0; border-top: 1px solid #cacacc; -moz-box-shadow: 1px 0 0 #ECEFF1, rgba(248, 248, 248, 0.9) 0 1px 0px inset, rgba(201, 205, 206, 0.8) 0 0 1px; -webkit-box-shadow: 1px 0 0 #ECEFF1, rgba(248, 248, 248, 0.9) 0 1px 0px inset, rgba(201, 205, 206, 0.8) 0 0 1px; box-shadow: 1px 0 0 #ECEFF1, rgba(248, 248, 248, 0.9) 0 1px 0px inset, rgba(201, 205, 206, 0.8) 0 0 1px; height: 28px; background-color: #ECEFF1; } +.cms-content-actions, .cms-preview-controls { margin: 0; padding: 12px 12px; z-index: 999; border-top: 1px solid #cacacc; -moz-box-shadow: 1px 0 0 #ECEFF1, rgba(248, 248, 248, 0.9) 0 1px 0px inset, rgba(201, 205, 206, 0.8) 0 0 1px; -webkit-box-shadow: 1px 0 0 #ECEFF1, rgba(248, 248, 248, 0.9) 0 1px 0px inset, rgba(201, 205, 206, 0.8) 0 0 1px; box-shadow: 1px 0 0 #ECEFF1, rgba(248, 248, 248, 0.9) 0 1px 0px inset, rgba(201, 205, 206, 0.8) 0 0 1px; height: 28px; background-color: #ECEFF1; } /** -------------------------------------------- Messages -------------------------------------------- */ .message { display: block; clear: both; margin: 0 0 8px; padding: 10px 12px; font-weight: normal; border: 1px #ccc solid; background: #fff; background: rgba(255, 255, 255, 0.5); text-shadow: none; -moz-border-radius: 3px 3px 3px 3px; -webkit-border-radius: 3px; border-radius: 3px 3px 3px 3px; } diff --git a/admin/scss/_style.scss b/admin/scss/_style.scss index ed7f7e27e..688ab9a2f 100644 --- a/admin/scss/_style.scss +++ b/admin/scss/_style.scss @@ -428,7 +428,7 @@ body.cms { .cms-content-actions, .cms-preview-controls { margin: 0; padding: $grid-y*1.5 $grid-y*1.5; - z-index: 0; + z-index: 999; border-top: 1px solid lighten($color-separator, 4%); @include box-shadow( 1px 0 0 $tab-panel-texture-color, From 2d9a0031683221bb0f78f7004aff59943f90c474 Mon Sep 17 00:00:00 2001 From: Christopher Pitt Date: Fri, 17 Apr 2015 12:07:51 +1200 Subject: [PATCH 17/76] Add row and cell generator methods --- forms/gridfield/GridField.php | 278 ++++++++++++++++++++-------------- 1 file changed, 168 insertions(+), 110 deletions(-) diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index 43bfc9b02..f6e00fab3 100644 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -1,25 +1,26 @@ * $gridField = new GridField('ExampleGrid', 'Example grid', new DataList('Page')); * - * + * * @see SS_List - * + * * @package forms * @subpackage fields-gridfield */ class GridField extends FormField { - + /** * * @var array @@ -28,46 +29,49 @@ class GridField extends FormField { 'index', 'gridFieldAlterAction' ); - + /** * The datasource + * * @var SS_List */ protected $list = null; /** * The classname of the DataObject that the GridField will display. Defaults to the value of $this->list->dataClass + * * @var string */ protected $modelClassName = ''; /** * the current state of the GridField + * * @var GridState */ protected $state = null; - + /** * * @var GridFieldConfig */ protected $config = null; - + /** * The components list * * @var array */ protected $components = array(); - + /** * Internal dispatcher for column handlers. * Keys are column names and values are GridField_ColumnProvider objects - * + * * @var array */ protected $columnDispatch = null; - + /** * Map of callbacks for custom data fields * @@ -99,37 +103,38 @@ class GridField extends FormField { $this->setConfig($config ?: GridFieldConfig_Base::create()); $this->config->addComponent(new GridState_Component()); - $this->state = new GridState($this); - + $this->state = new GridState($this); + $this->addExtraClass('ss-gridfield'); } public function index($request) { return $this->gridFieldAlterAction(array(), $this->getForm(), $request); } - + /** * Set the modelClass (dataobject) that this field will get it column headers from. * If no $displayFields has been set, the displayfields will be fetched from * this modelclass $summary_fields - * + * * @param string $modelClassName + * * @see GridFieldDataColumns::getDisplayFields() */ public function setModelClass($modelClassName) { $this->modelClassName = $modelClassName; return $this; } - + /** * Returns a dataclass that is a DataObject type that this GridField should look like. - * + * * @throws Exception * @return string */ public function getModelClass() { - if ($this->modelClassName) return $this->modelClassName; - if ($this->list && method_exists($this->list, 'dataClass')) { + if($this->modelClassName) return $this->modelClassName; + if($this->list && method_exists($this->list, 'dataClass')) { $class = $this->list->dataClass(); if($class) return $class; } @@ -149,6 +154,7 @@ class GridField extends FormField { /** * @param GridFieldConfig $config + * * @return GridField */ public function setConfig(GridFieldConfig $config) { @@ -159,12 +165,13 @@ class GridField extends FormField { public function getComponents() { return $this->config->getComponents(); } - + /** * Cast a arbitrary value with the help of a castingDefintion - * - * @param $value + * + * @param $value * @param $castingDefinition + * * @todo refactor this into GridFieldComponent */ public function getCastedValue($value, $castingDefinition) { @@ -175,21 +182,21 @@ class GridField extends FormField { } else { $castingParams = array(); } - - if(strpos($castingDefinition,'->') === false) { + + if(strpos($castingDefinition, '->') === false) { $castingFieldType = $castingDefinition; $castingField = DBField::create_field($castingFieldType, $value); - $value = call_user_func_array(array($castingField,'XML'),$castingParams); + $value = call_user_func_array(array($castingField, 'XML'), $castingParams); } else { $fieldTypeParts = explode('->', $castingDefinition); - $castingFieldType = $fieldTypeParts[0]; + $castingFieldType = $fieldTypeParts[0]; $castingMethod = $fieldTypeParts[1]; $castingField = DBField::create_field($castingFieldType, $value); - $value = call_user_func_array(array($castingField,$castingMethod),$castingParams); + $value = call_user_func_array(array($castingField, $castingMethod), $castingParams); } - + return $value; - } + } /** * Set the datasource @@ -224,7 +231,7 @@ class GridField extends FormField { } return $list; } - + /** * Get the current GridState_Data or the GridState * @@ -262,7 +269,7 @@ class GridField extends FormField { // Get data $list = $this->getManipulatedList(); - + // Render headers, footers, etc $content = array( "before" => "", @@ -271,7 +278,7 @@ class GridField extends FormField { "footer" => "", ); - foreach($this->getComponents() as $item) { + foreach($this->getComponents() as $item) { if($item instanceof GridField_HTMLProvider) { $fragments = $item->getHTMLFragments($this); if($fragments) foreach($fragments as $k => $v) { @@ -290,10 +297,10 @@ class GridField extends FormField { // Nested dependencies are handled by deferring the rendering of any content item that // Circular dependencies are detected by disallowing any item to be deferred more than 5 times // It's a fairly crude algorithm but it works - + $fragmentDefined = array('header' => true, 'footer' => true, 'before' => true, 'after' => true); reset($content); - while(list($k,$v) = each($content)) { + while(list($k, $v) = each($content)) { if(preg_match_all('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $v, $matches)) { foreach($matches[1] as $match) { $fragmentName = strtolower($match); @@ -305,10 +312,10 @@ class GridField extends FormField { if(preg_match('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $fragment, $matches)) { // If we've already deferred this fragment, then we have a circular dependency if(isset($fragmentDeferred[$k]) && $fragmentDeferred[$k] > 5) { - throw new LogicException("GridField HTML fragment '$fragmentName' and '$matches[1]' " . + throw new LogicException("GridField HTML fragment '$fragmentName' and '$matches[1]' " . "appear to have a circular dependency."); } - + // Otherwise we can push to the end of the content array unset($content[$k]); $content[$k] = $v; @@ -331,7 +338,7 @@ class GridField extends FormField { foreach($content as $k => $v) { if(empty($fragmentDefined[$k])) { throw new LogicException("GridField HTML fragment '$k' was given content," - . " but not defined. Perhaps there is a supporting GridField component you need to add?"); + . " but not defined. Perhaps there is a supporting GridField component you need to add?"); } } @@ -345,30 +352,22 @@ class GridField extends FormField { $rowContent = ''; foreach($this->getColumns() as $column) { $colContent = $this->getColumnContent($record, $column); + // A return value of null means this columns should be skipped altogether. - if($colContent === null) continue; + if($colContent === null) { + continue; + } + $colAttributes = $this->getColumnAttributes($record, $column); - $rowContent .= FormField::create_tag('td', $colAttributes, $colContent); + + $rowContent .= $this->newCell($this, $total, $idx, $record, $colAttributes, $colContent); } - $classes = array('ss-gridfield-item'); - if ($idx == 0) $classes[] = 'first'; - if ($idx == $total-1) $classes[] = 'last'; - $classes[] = ($idx % 2) ? 'even' : 'odd'; - $row = FormField::create_tag( - 'tr', - array( - "class" => implode(' ', $classes), - 'data-id' => $record->ID, - // TODO Allow per-row customization similar to GridFieldDataColumns - 'data-class' => $record->ClassName, - ), - $rowContent - ); - $rows[] = $row; + + $rows[] = $this->newRow($this, $total, $idx, $record, array(), $rowContent); } $content['body'] = implode("\n", $rows); - } - + } + // Display a message when the grid field is empty if(!(isset($content['body']) && $content['body'])) { $content['body'] = FormField::create_tag( @@ -395,7 +394,7 @@ class GridField extends FormField { $this->addExtraClass('ss-gridfield field'); $attrs = array_diff_key( - $this->getAttributes(), + $this->getAttributes(), array('value' => false, 'type' => false, 'name' => false) ); $attrs['data-name'] = $this->getName(); @@ -403,25 +402,77 @@ class GridField extends FormField { 'id' => isset($this->id) ? $this->id : null, 'class' => 'ss-gridfield-table', 'cellpadding' => '0', - 'cellspacing' => '0' + 'cellspacing' => '0' ); if($this->getDescription()) { $content['after'] .= FormField::create_tag( - 'span', - array('class' => 'description'), + 'span', + array('class' => 'description'), $this->getDescription() ); } return - FormField::create_tag('fieldset', $attrs, + FormField::create_tag('fieldset', $attrs, $content['before'] . - FormField::create_tag('table', $tableAttrs, $head."\n".$foot."\n".$body) . + FormField::create_tag('table', $tableAttrs, $head . "\n" . $foot . "\n" . $body) . $content['after'] ); } - + + /** + * @param GridField $gridfield + * @param int $total + * @param int $index + * @param DataObject $record + * @param array $attributes + * @param string $content + * + * @return string + */ + protected function newCell($gridfield, $total, $index, $record, $attributes, $content) { + return FormField::create_tag( + 'td', + $attributes, + $content + ); + } + + /** + * @param GridField $gridfield + * @param int $total + * @param int $index + * @param DataObject $record + * @param array $attributes + * @param string $content + * + * @return string + */ + protected function newRow($gridfield, $total, $index, $record, $attributes, $content) { + $classes = array('ss-gridfield-item'); + + if($index == 0) { + $classes[] = 'first'; + } + + if($index == $total - 1) { + $classes[] = 'last'; + } + + $classes[] = ($index % 2) ? 'even' : 'odd'; + + return FormField::create_tag( + 'tr', + array( + 'class' => implode(' ', $classes), + 'data-id' => $record->ID, + 'data-class' => $record->ClassName, + ), + $content + ); + } + public function Field($properties = array()) { return $this->FieldHolder($properties); } @@ -452,6 +503,7 @@ class GridField extends FormField { * * @param DataObject $record * @param string $column + * * @return string * @throws InvalidArgumentException */ @@ -460,7 +512,7 @@ class GridField extends FormField { if(!$this->columnDispatch) { $this->buildColumnDispatch(); } - + if(!empty($this->columnDispatch[$column])) { $content = ""; foreach($this->columnDispatch[$column] as $handler) { @@ -471,12 +523,12 @@ class GridField extends FormField { throw new InvalidArgumentException("Bad column '$column'"); } } - + /** * Add additional calculated data fields to be used on this GridField * * @param array $fields a map of fieldname to callback. The callback will - * be passed the record as an argument. + * be passed the record as an argument. */ public function addDataFields($fields) { if($this->customDataFields) { @@ -485,7 +537,7 @@ class GridField extends FormField { $this->customDataFields = $fields; } } - + /** * Get the value of a named field on the given record. * Use of this method ensures that any special rules around the data for this gridfield are followed. @@ -496,7 +548,7 @@ class GridField extends FormField { $callback = $this->customDataFields[$fieldName]; return $callback($record); } - + // Default implementation if($record->hasMethod('relField')) { return $record->relField($fieldName); @@ -512,6 +564,7 @@ class GridField extends FormField { * * @param DataObject $record * @param string $column + * * @return array * @throws LogicException * @throws InvalidArgumentException @@ -521,7 +574,7 @@ class GridField extends FormField { if(!$this->columnDispatch) { $this->buildColumnDispatch(); } - + if(!empty($this->columnDispatch[$column])) { $attrs = array(); @@ -546,6 +599,7 @@ class GridField extends FormField { * Get metadata for a column, example array('Title'=>'Email address') * * @param string $column + * * @return array * @throws LogicException * @throws InvalidArgumentException @@ -555,22 +609,22 @@ class GridField extends FormField { if(!$this->columnDispatch) { $this->buildColumnDispatch(); } - + if(!empty($this->columnDispatch[$column])) { $metadata = array(); foreach($this->columnDispatch[$column] as $handler) { $column_metadata = $handler->getColumnMetadata($this, $column); - + if(is_array($column_metadata)) { $metadata = array_merge($metadata, $column_metadata); } else { $methodSignature = get_class($handler) . "::getColumnMetadata()"; throw new LogicException("Non-array response from $methodSignature."); } - + } - + return $metadata; } throw new InvalidArgumentException("Bad column '$column'"); @@ -584,13 +638,13 @@ class GridField extends FormField { public function getColumnCount() { // Build the column dispatch if(!$this->columnDispatch) $this->buildColumnDispatch(); - return count($this->columnDispatch); + return count($this->columnDispatch); } /** * Build an columnDispatch that maps a GridField_ColumnProvider to a column * for reference later - * + * */ protected function buildColumnDispatch() { $this->columnDispatch = array(); @@ -610,7 +664,8 @@ class GridField extends FormField { * This is the action that gets executed when a GridField_AlterAction gets clicked. * * @param array $data - * @return string + * + * @return string */ public function gridFieldAlterAction($data, $form, SS_HTTPRequest $request) { $html = ''; @@ -639,7 +694,7 @@ class GridField extends FormField { if($html) return $html; } } - + switch($request->getHeader('X-Pjax')) { case 'CurrentField': return $this->FieldHolder(); @@ -661,6 +716,7 @@ class GridField extends FormField { * @param string $actionName * @param mixed $args * @param arrray $data - send data from a form + * * @return type * @throws InvalidArgumentException */ @@ -670,17 +726,17 @@ class GridField extends FormField { if(!($component instanceof GridField_ActionProvider)) { continue; } - - if(in_array($actionName, array_map('strtolower', (array)$component->getActions($this)))) { + + if(in_array($actionName, array_map('strtolower', (array) $component->getActions($this)))) { return $component->handleAction($this, $actionName, $args, $data); } } throw new InvalidArgumentException("Can't handle action '$actionName'"); } - + /** * Custom request handler that will check component handlers before proceeding to the default implementation. - * + * * @todo There is too much code copied from RequestHandler here. */ public function handleRequest(SS_HTTPRequest $request, DataModel $model) { @@ -693,18 +749,18 @@ class GridField extends FormField { $fieldData = $this->request->requestVar($this->getName()); if($fieldData && isset($fieldData['GridState'])) $this->getState(false)->setValue($fieldData['GridState']); - + foreach($this->getComponents() as $component) { if(!($component instanceof GridField_URLHandler)) { continue; } - + $urlHandlers = $component->getURLHandlers($this); - + if($urlHandlers) foreach($urlHandlers as $rule => $action) { if($params = $request->match($rule, true)) { // Actions can reference URL parameters, eg, '$Action/$ID/$OtherID' => '$Action', - if($action[0] == '$') $action = $params[substr($action,1)]; + if($action[0] == '$') $action = $params[substr($action, 1)]; if(!method_exists($component, 'checkAccessAction') || $component->checkAccessAction($action)) { if(!$action) { $action = "index"; @@ -723,7 +779,8 @@ class GridField extends FormField { } if($this !== $result && !$request->isEmptyPattern($rule) && is_object($result) - && $result instanceof RequestHandler) { + && $result instanceof RequestHandler + ) { $returnValue = $result->handleRequest($request, $model); @@ -733,11 +790,11 @@ class GridField extends FormField { return $returnValue; - // If we return some other data, and all the URL is parsed, then return that + // If we return some other data, and all the URL is parsed, then return that } else if($request->allParsed()) { return $result; - // But if we have more content on the URL and we don't know what to do with it, return an error + // But if we have more content on the URL and we don't know what to do with it, return an error } else { return $this->httpError(404, "I can't handle sub-URLs of a " . get_class($result) . " object."); @@ -746,7 +803,7 @@ class GridField extends FormField { } } } - + return parent::handleRequest($request, $model); } @@ -762,10 +819,10 @@ class GridField extends FormField { /** - * This class is the base class when you want to have an action that alters - * the state of the {@link GridField}, rendered as a button element. - * - * @package forms + * This class is the base class when you want to have an action that alters + * the state of the {@link GridField}, rendered as a button element. + * + * @package forms * @subpackage fields-gridfield */ class GridField_FormAction extends FormAction { @@ -774,12 +831,12 @@ class GridField_FormAction extends FormAction { * @var GridField */ protected $gridField; - + /** - * @var array + * @var array */ protected $stateValues; - + /** * @var array */ @@ -800,7 +857,7 @@ class GridField_FormAction extends FormAction { * @param type $name * @param type $label * @param type $actionName - * @param type $args + * @param type $args */ public function __construct(GridField $gridField, $name, $title, $actionName, $args) { $this->gridField = $gridField; @@ -811,9 +868,9 @@ class GridField_FormAction extends FormAction { } /** - * urlencode encodes less characters in percent form than we need - we + * urlencode encodes less characters in percent form than we need - we * need everything that isn't a \w. - * + * * @param string $val */ public function nameEncode($val) { @@ -822,11 +879,11 @@ class GridField_FormAction extends FormAction { /** * The callback for nameEncode - * + * * @param string $val */ public function _nameEncode($match) { - return '%'.dechex(ord($match[0])); + return '%' . dechex(ord($match[0])); } /** @@ -841,16 +898,16 @@ class GridField_FormAction extends FormAction { ); // Ensure $id doesn't contain only numeric characters - $id = 'gf_'.substr(md5(serialize($state)), 0, 8); + $id = 'gf_' . substr(md5(serialize($state)), 0, 8); Session::set($id, $state); $actionData['StateID'] = $id; - + return array_merge( parent::getAttributes(), array( // Note: This field needs to be less than 65 chars, otherwise Suhosin security patch // will strip it from the requests - 'name' => 'action_gridFieldAlterAction'. '?' . http_build_query($actionData), + 'name' => 'action_gridFieldAlterAction' . '?' . http_build_query($actionData), 'data-url' => $this->gridField->Link(), ) ); @@ -860,16 +917,17 @@ class GridField_FormAction extends FormAction { * Calculate the name of the gridfield relative to the Form * * @param GridField $base + * * @return string */ protected function getNameFromParent() { $base = $this->gridField; $name = array(); - + do { array_unshift($name, $base->getName()); $base = $base->getForm(); - } while ($base && !($base instanceof Form)); + } while($base && !($base instanceof Form)); return implode('.', $name); } From cb7aed46cdba4763d32a244ba6c76cceb8aec659 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Fri, 17 Apr 2015 11:38:04 +0100 Subject: [PATCH 18/76] Adding loading spinner to ui-autocomplete fields when making ajax reqests --- admin/css/screen.css | 1 + admin/scss/_uitheme.scss | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/admin/css/screen.css b/admin/css/screen.css index a23768f04..62ee1e6bd 100644 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -140,6 +140,7 @@ body, html { font-size: 12px; line-height: 16px; font-family: Arial, sans-serif; .ui-accordion .ui-accordion-content { border: 1px solid #c0c0c2; border-top: none; } .ui-autocomplete { max-height: 240px; overflow-x: hidden; overflow-y: auto; } +.ui-autocomplete-loading { background-image: url(../images/throbber.gif) !important; background-position: 97% center !important; background-repeat: no-repeat !important; background-size: auto !important; } /** This file defines common styles for form elements used throughout the CMS interface. It is an addition to the base styles defined in framework/css/Form.css. @package framework @subpackage admin */ /** ---------------------------------------------------- Basic form fields ---------------------------------------------------- */ diff --git a/admin/scss/_uitheme.scss b/admin/scss/_uitheme.scss index 797387839..1671c53f8 100644 --- a/admin/scss/_uitheme.scss +++ b/admin/scss/_uitheme.scss @@ -85,8 +85,16 @@ } } -.ui-autocomplete{ +.ui-autocomplete { max-height: 240px; overflow-x: hidden; overflow-y: auto; + + /** sorry about the !important but the specificity of other selectors mandates it over writing out very specific selectors **/ + &-loading { + background-image: url(../images/throbber.gif) !important; + background-position: 97% center !important; + background-repeat: no-repeat !important; + background-size: auto !important; + } } From 9567d9c3f2b757de2df6561354e6583111dc50eb Mon Sep 17 00:00:00 2001 From: spekulatius Date: Sun, 19 Apr 2015 20:21:49 +1200 Subject: [PATCH 19/76] Update 01_Validation.md missing semicolon --- docs/en/02_Developer_Guides/03_Forms/01_Validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/02_Developer_Guides/03_Forms/01_Validation.md b/docs/en/02_Developer_Guides/03_Forms/01_Validation.md index a9c9f0064..c3ff0caee 100644 --- a/docs/en/02_Developer_Guides/03_Forms/01_Validation.md +++ b/docs/en/02_Developer_Guides/03_Forms/01_Validation.md @@ -137,7 +137,7 @@ reusable and would not be possible within the `CMS` or other automated `UI` but $form = new Form($controller, 'MyForm', $fields, $actions); - return $form + return $form; } public function doSubmitForm($data, $form) { From 8f5932aceccd5791131e9962cafbfd2108cccca0 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Mon, 20 Apr 2015 09:11:57 +1200 Subject: [PATCH 20/76] Allow empty has_one returns in DataDifferencer Sometimes the has_one getter is incorrectly implemented, resulting in an empty return object. While that's technically a core API validation, there's no checks around it (i.e. no PHP class interface). DataDifferencer has the option to continue here, so we should program it defensively rather than resulting in a fatal error. --- model/DataDifferencer.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/model/DataDifferencer.php b/model/DataDifferencer.php index f5021f77d..cc7e00ae9 100644 --- a/model/DataDifferencer.php +++ b/model/DataDifferencer.php @@ -116,7 +116,11 @@ class DataDifferencer extends ViewableData { $toTitle = ''; if($this->toRecord->hasMethod($relName)) { $relObjTo = $this->toRecord->$relName(); - $toTitle = $relObjTo->hasMethod('Title') || $relObjTo->hasField('Title') ? $relObjTo->Title : ''; + if($relObjTo) { + $toTitle = ($relObjTo->hasMethod('Title') || $relObjTo->hasField('Title')) ? $relObjTo->Title : ''; + } else { + $toTitle = ''; + } } if(!$this->fromRecord) { @@ -134,7 +138,12 @@ class DataDifferencer extends ViewableData { $fromTitle = ''; if($this->fromRecord->hasMethod($relName)) { $relObjFrom = $this->fromRecord->$relName(); - $fromTitle = $relObjFrom->hasMethod('Title') || $relObjFrom->hasField('Title') ? $relObjFrom->Title : ''; + if($relObjFrom) { + $fromTitle = ($relObjFrom->hasMethod('Title') || $relObjFrom->hasField('Title')) ? $relObjFrom->Title : ''; + } else { + $fromTitle = ''; + } + } if(isset($relObjFrom) && $relObjFrom instanceof Image) { // TODO Use CMSThumbnail (see above) From 6ec466edbf4343d47834933caa8b3c47110b1f4c Mon Sep 17 00:00:00 2001 From: scott1702 Date: Mon, 20 Apr 2015 15:12:48 +1200 Subject: [PATCH 21/76] Remove 'important' from gridfield hover --- css/GridField.css | 5 ++++- scss/GridField.scss | 13 ++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/css/GridField.css b/css/GridField.css index 1da9ca33e..c671c0695 100644 --- a/css/GridField.css +++ b/css/GridField.css @@ -68,11 +68,14 @@ Used in side panels and action tabs .cms table.ss-gridfield-table tr.sortable-header { background: #dbe3e8; } .cms table.ss-gridfield-table tr.sortable-header th { padding: 0; font-weight: normal; } .cms table.ss-gridfield-table tr.sortable-header th .ss-ui-button { font-weight: normal; } -.cms table.ss-gridfield-table tr:hover { background: #FFFAD6 !important; } +.cms table.ss-gridfield-table tr:hover { background: #FFFAD6; } .cms table.ss-gridfield-table tr:first-child { background: transparent; } +.cms table.ss-gridfield-table tr:first-child:hover { background: #FFFAD6; } .cms table.ss-gridfield-table tr.ss-gridfield-even { background: #F0F4F7; } .cms table.ss-gridfield-table tr.ss-gridfield-even.ss-gridfield-last { border-bottom: none; } +.cms table.ss-gridfield-table tr.ss-gridfield-even:hover { background: #FFFAD6; } .cms table.ss-gridfield-table tr.even { background: #F0F4F7; } +.cms table.ss-gridfield-table tr.even:hover { background: #FFFAD6; } .cms table.ss-gridfield-table tr th { font-weight: bold; font-size: 12px; color: #FFF; padding: 5px; border-right: 1px solid rgba(0, 0, 0, 0.1); } .cms table.ss-gridfield-table tr th div.fieldgroup, .cms table.ss-gridfield-table tr th div.fieldgroup-field { width: 100%; position: relative; } .cms table.ss-gridfield-table tr th div.fieldgroup { min-width: 200px; padding-right: 0; } diff --git a/scss/GridField.scss b/scss/GridField.scss index 3d4eb44ee..27bcc76f5 100644 --- a/scss/GridField.scss +++ b/scss/GridField.scss @@ -332,10 +332,14 @@ $gf_grid_x: 16px; } } &:hover { - background: #FFFAD6 !important; + background: #FFFAD6; } &:first-child { background: transparent; + + &:hover { + background: #FFFAD6; + } } &.ss-gridfield-even { background: $gf_colour_zebra; @@ -343,9 +347,16 @@ $gf_grid_x: 16px; &.ss-gridfield-last { border-bottom: none; } + &:hover { + background: #FFFAD6; + } } &.even { background: $gf_colour_zebra; + + &:hover { + background: #FFFAD6; + } } th { From 5231dc7cc76ec3d06dcdaf9b820abd112906b385 Mon Sep 17 00:00:00 2001 From: Christopher Pitt Date: Mon, 20 Apr 2015 14:38:49 +1200 Subject: [PATCH 22/76] Abstracting GridField row class generation --- forms/gridfield/GridField.php | 56 +++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index f6e00fab3..7e8229b0b 100644 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -349,7 +349,9 @@ class GridField extends FormField { if($record->hasMethod('canView') && !$record->canView()) { continue; } + $rowContent = ''; + foreach($this->getColumns() as $column) { $colContent = $this->getColumnContent($record, $column); @@ -360,10 +362,12 @@ class GridField extends FormField { $colAttributes = $this->getColumnAttributes($record, $column); - $rowContent .= $this->newCell($this, $total, $idx, $record, $colAttributes, $colContent); + $rowContent .= $this->newCell($total, $idx, $record, $colAttributes, $colContent); } - $rows[] = $this->newRow($this, $total, $idx, $record, array(), $rowContent); + $rowAttributes = $this->getRowAttributes($total, $idx, $record); + + $rows[] = $this->newRow($total, $idx, $record, $rowAttributes, $rowContent); } $content['body'] = implode("\n", $rows); } @@ -422,7 +426,6 @@ class GridField extends FormField { } /** - * @param GridField $gridfield * @param int $total * @param int $index * @param DataObject $record @@ -431,7 +434,7 @@ class GridField extends FormField { * * @return string */ - protected function newCell($gridfield, $total, $index, $record, $attributes, $content) { + protected function newCell($total, $index, $record, $attributes, $content) { return FormField::create_tag( 'td', $attributes, @@ -440,7 +443,6 @@ class GridField extends FormField { } /** - * @param GridField $gridfield * @param int $total * @param int $index * @param DataObject $record @@ -449,7 +451,39 @@ class GridField extends FormField { * * @return string */ - protected function newRow($gridfield, $total, $index, $record, $attributes, $content) { + protected function newRow($total, $index, $record, $attributes, $content) { + return FormField::create_tag( + 'tr', + $attributes, + $content + ); + } + + /** + * @param int $total + * @param int $index + * @param DataObject $record + * + * @return array + */ + protected function getRowAttributes($total, $index, $record) { + $rowClasses = $this->newRowClasses($total, $index, $record); + + return array( + 'class' => implode(' ', $rowClasses), + 'data-id' => $record->ID, + 'data-class' => $record->ClassName, + ); + } + + /** + * @param int $total + * @param int $index + * @param DataObject $record + * + * @return array + */ + protected function newRowClasses($total, $index, $record) { $classes = array('ss-gridfield-item'); if($index == 0) { @@ -462,15 +496,7 @@ class GridField extends FormField { $classes[] = ($index % 2) ? 'even' : 'odd'; - return FormField::create_tag( - 'tr', - array( - 'class' => implode(' ', $classes), - 'data-id' => $record->ID, - 'data-class' => $record->ClassName, - ), - $content - ); + return $classes; } public function Field($properties = array()) { From 8e245112669fcb6e03955fc6c272c4ce1c49dba2 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Mon, 20 Apr 2015 18:02:08 +1200 Subject: [PATCH 23/76] BUG Fix users with all cms section access not able to edit files Fixes #4078 --- filesystem/File.php | 2 +- tests/filesystem/FileTest.php | 8 ++++++-- tests/filesystem/FileTest.yml | 10 +++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/filesystem/File.php b/filesystem/File.php index ca0b51c9e..a6a4f9f29 100644 --- a/filesystem/File.php +++ b/filesystem/File.php @@ -305,7 +305,7 @@ class File extends DataObject { $result = $this->extendedCan('canEdit', $member); if($result !== null) return $result; - return Permission::checkMember($member, 'CMS_ACCESS_AssetAdmin'); + return Permission::checkMember($member, array('CMS_ACCESS_AssetAdmin', 'CMS_ACCESS_LeftAndMain')); } /** diff --git a/tests/filesystem/FileTest.php b/tests/filesystem/FileTest.php index 1466810a8..efd74e304 100644 --- a/tests/filesystem/FileTest.php +++ b/tests/filesystem/FileTest.php @@ -393,9 +393,13 @@ class FileTest extends SapphireTest { $this->objFromFixture('Member', 'frontend')->logIn(); $this->assertFalse($file->canEdit(), "Permissionless users can't edit files"); - // Test cms non-asset user + // Test global CMS section users $this->objFromFixture('Member', 'cms')->logIn(); - $this->assertFalse($file->canEdit(), "Basic CMS users can't edit files"); + $this->assertTrue($file->canEdit(), "Users with all CMS section access can edit files"); + + // Test cms access users without file access + $this->objFromFixture('Member', 'security')->logIn(); + $this->assertFalse($file->canEdit(), "Security CMS users can't edit files"); // Test asset-admin user $this->objFromFixture('Member', 'assetadmin')->logIn(); diff --git a/tests/filesystem/FileTest.yml b/tests/filesystem/FileTest.yml index 636339f15..58d9b13ef 100644 --- a/tests/filesystem/FileTest.yml +++ b/tests/filesystem/FileTest.yml @@ -35,6 +35,8 @@ Permission: Code: CMS_ACCESS_LeftAndMain assetadmin: Code: CMS_ACCESS_AssetAdmin + securityadmin: + Code: CMS_ACCESS_SecurityAdmin Group: admins: Title: Administrators @@ -42,9 +44,12 @@ Group: cmsusers: Title: 'CMS Users' Permissions: =>Permission.cmsmain + securityusers: + Title: 'Security Users' + Permissions: =>Permission.securityadmin assetusers: Title: 'Asset Users' - Permissions: =>Permission.cmsmain, =>Permission.assetadmin + Permissions: =>Permission.assetadmin Member: frontend: Email: frontend@example.com @@ -57,3 +62,6 @@ Member: assetadmin: Email: assetadmin@silverstripe.com Groups: =>Group.assetusers + security: + Email: security@silverstripe.com + Groups: =>Group.securityusers From 27d3a27a97dbeaec04939641307d5cf944fb5bb1 Mon Sep 17 00:00:00 2001 From: Antony Thorpe Date: Tue, 21 Apr 2015 18:17:56 +1200 Subject: [PATCH 24/76] Update 03_Testing_Email.md Fix typo and link. It is assertEmailSent not assertEmailSend and a method of SapphireTest. Thanks. --- .../06_Testing/How_Tos/03_Testing_Email.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/02_Developer_Guides/06_Testing/How_Tos/03_Testing_Email.md b/docs/en/02_Developer_Guides/06_Testing/How_Tos/03_Testing_Email.md index 8b456bff1..db8f66a69 100644 --- a/docs/en/02_Developer_Guides/06_Testing/How_Tos/03_Testing_Email.md +++ b/docs/en/02_Developer_Guides/06_Testing/How_Tos/03_Testing_Email.md @@ -15,10 +15,10 @@ email was sent using this method. $e->send(); } -To test that `MyMethod` sends the correct email, use the [api:Email::assertEmailSent] method. +To test that `MyMethod` sends the correct email, use the [api:SapphireTest::assertEmailSent] method. :::php - $this->assertEmailSend($to, $from, $subject, $body); + $this->assertEmailSent($to, $from, $subject, $body); // to assert that the email is sent to the correct person $this->assertEmailSent("someone@example.com", null, "/th.*e$/"); From 996ddea4a96add107b52955e0fdb43096730b89a Mon Sep 17 00:00:00 2001 From: Phill Price Date: Thu, 23 Apr 2015 17:12:49 +0100 Subject: [PATCH 25/76] DOCS: formatting fix the end # removed from titles as they were showing on the page. --- .../How_Tos/CMS_Alternating_Button.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/CMS_Alternating_Button.md b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/CMS_Alternating_Button.md index 29a8d1bcd..35eb56b6a 100644 --- a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/CMS_Alternating_Button.md +++ b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/CMS_Alternating_Button.md @@ -1,6 +1,6 @@ -# How to implement an alternating button # +# How to implement an alternating button -## Introduction ## +## Introduction *Save* and *Save & publish* buttons alternate their appearance to reflect the state of the underlying `SiteTree` object. This is based on a `ssui.button` extension available in `ssui.core.js`. @@ -16,7 +16,7 @@ This how-to will walk you through creation of a "Clean-up" button with two appea The controller code that goes with this example is listed in [Extend CMS Interface](extend_cms_interface). -## Backend support ## +## Backend support First create and configure the action button with alternate state on a page type. The button comes with the default state already, so you just need to add the alternate state using two data additional attributes: @@ -60,7 +60,7 @@ Here we initialise the button based on the backend check, and assume that the bu // ... } -## Frontend support ## +## Frontend support As with the *Save* and *Save & publish* buttons, you might want to add some scripted reactions to user actions on the frontend. You can affect the state of the button through the jQuery UI calls. @@ -103,7 +103,7 @@ CMS core that tracks the changes to the input fields and reacts by enabling the } }); -## Frontend hooks ## +## Frontend hooks `ssui.button` defines several additional events so that you can extend the code with your own behaviours. For example this is used in the CMS to style the buttons. Three events are available: @@ -153,7 +153,7 @@ cases. }(jQuery)); -## Summary ## +## Summary The code presented gives you a fully functioning alternating button, similar to the defaults that come with the the CMS. These alternating buttons can be used to give user the advantage of visual feedback upon their actions. From c2fd18e829c9f05547f56b00d1e003033a435fb2 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Thu, 23 Apr 2015 17:20:07 +0100 Subject: [PATCH 26/76] FIX use config for Security::$login_url --- security/Security.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/security/Security.php b/security/Security.php index b483ac082..1033fbe99 100644 --- a/security/Security.php +++ b/security/Security.php @@ -1047,7 +1047,8 @@ class Security extends Controller { * Set a custom log-in URL if you have built your own log-in page. */ public static function set_login_url($loginUrl) { - self::$login_url = $loginUrl; + Deprecation::notice('3.1', 'Use the "Security.login_url" config setting instead'); + static::config()->update('login_url', $loginUrl); } /** @@ -1055,7 +1056,7 @@ class Security extends Controller { * Defaults to Security/login but can be re-set with {@link set_login_url()} */ public static function login_url() { - return self::$login_url; + return static::config()->get('login_url'); } } From 19423e9a44dc520c0db2850ec2ab2d8bcef2e928 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 23 Apr 2015 10:08:33 +1200 Subject: [PATCH 27/76] BUG Fix tinymce errors crashing CMS When removing a tinymce field, internal third party errors should be caught and ignored gracefully rather than breaking the whole CMS. --- javascript/HtmlEditorField.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/javascript/HtmlEditorField.js b/javascript/HtmlEditorField.js index c2596095d..815c8e311 100644 --- a/javascript/HtmlEditorField.js +++ b/javascript/HtmlEditorField.js @@ -293,8 +293,15 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; onremove: function() { var ed = tinyMCE.get(this.attr('id')); if (ed) { - ed.remove(); - ed.destroy(); + try { + ed.remove(); + } catch(ex) {} + try { + ed.destroy(); + } catch(ex) {} + + // Remove any residual tinyMCE editor element + this.next('.mceEditor').remove(); // TinyMCE leaves behind events. We should really fix TinyMCE, but lets brute force it for now $.each(jQuery.cache, function(){ From 242de4ec7c3ec461af4bf167fe3629ae10fe2594 Mon Sep 17 00:00:00 2001 From: Michael Strong Date: Fri, 24 Apr 2015 17:05:15 +1200 Subject: [PATCH 28/76] NEW Added Youtube's short URL. At the moment using the short URL (eg. https://youtu.be/qA0T8WumxT4) defaults to http. This introduces issue when running over https. --- _config/Oembed.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/_config/Oembed.yml b/_config/Oembed.yml index 659d0f3d6..02be4c601 100644 --- a/_config/Oembed.yml +++ b/_config/Oembed.yml @@ -8,6 +8,12 @@ Oembed: 'https://*.youtube.com/watch*': http: 'http://www.youtube.com/oembed/', https: 'https://www.youtube.com/oembed/?scheme=https' + 'http://*.youtu.be/*': + http: 'http://www.youtube.com/oembed/', + https: 'https://www.youtube.com/oembed/?scheme=https' + 'https://youtu.be/*': + http: 'http://www.youtube.com/oembed/', + https: 'https://www.youtube.com/oembed/?scheme=https' 'http://*.flickr.com/*': 'http://www.flickr.com/services/oembed/' 'http://*.viddler.com/*': From c010cb4cec221907a6d22d29b49c12b5c7d6b9e9 Mon Sep 17 00:00:00 2001 From: Antony Thorpe Date: Sat, 25 Apr 2015 18:04:40 +1200 Subject: [PATCH 29/76] Update 04_GridField.md docs for typo Variable name should be $grid, as defined when creating the GridField just above. Thanks. --- .../en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md b/docs/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md index 3f5eac5f4..5b8d91d2a 100644 --- a/docs/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md +++ b/docs/en/02_Developer_Guides/03_Forms/Field_types/04_GridField.md @@ -71,7 +71,7 @@ the `getConfig()` method on `GridField`. ); // GridField configuration - $config = $gridField->getConfig(); + $config = $grid->getConfig(); // // Modification of existing components can be done by fetching that component. From bfd8b6663afd747e9b2111cd6f4f27e059aaed73 Mon Sep 17 00:00:00 2001 From: Patrick Nelson Date: Sat, 25 Apr 2015 18:26:11 -0400 Subject: [PATCH 30/76] FIX for #4104, minor revision of error messages in ListboxField (more intuitive). --- forms/ListboxField.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/forms/ListboxField.php b/forms/ListboxField.php index 9c672e045..b793a7b75 100644 --- a/forms/ListboxField.php +++ b/forms/ListboxField.php @@ -215,13 +215,14 @@ class ListboxField extends DropdownField { if($val) { if(!$this->multiple && is_array($val)) { - throw new InvalidArgumentException('No array values allowed with multiple=false'); + throw new InvalidArgumentException('Array values are not allowed (when multiple=false).'); } if($this->multiple) { $parts = (is_array($val)) ? $val : preg_split("/ *, */", trim($val)); if(ArrayLib::is_associative($parts)) { - throw new InvalidArgumentException('No associative arrays allowed multiple=true'); + // This is due to the possibility of accidentally passing an array of values (as keys) and titles (as values) when only the keys were intended to be saved. + throw new InvalidArgumentException('Associative arrays are not allowed as values (when multiple=true), only indexed arrays.'); } // Doesn't check against unknown values in order to allow for less rigid data handling. From b90432cb26364ca8e87bd39cc7ebc7961b650acb Mon Sep 17 00:00:00 2001 From: Antony Thorpe Date: Sun, 26 Apr 2015 18:16:13 +1200 Subject: [PATCH 31/76] Update 03_Authentication.md Create a list to improve presentation on the silverstripe.org website. Hopefully this works. The API descriptions/links are currently missing. --- .../09_Security/03_Authentication.md | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/docs/en/02_Developer_Guides/09_Security/03_Authentication.md b/docs/en/02_Developer_Guides/09_Security/03_Authentication.md index 8a7033a3b..03e376dc5 100644 --- a/docs/en/02_Developer_Guides/09_Security/03_Authentication.md +++ b/docs/en/02_Developer_Guides/09_Security/03_Authentication.md @@ -7,25 +7,20 @@ By default, SilverStripe provides a `[api:MemberAuthenticator]` class which hook authentication system. The main login system uses these controllers to handle the various security requests: - -`[api:Security]` Which is the controller which handles most front-end security requests, including +* `[api:Security]` Which is the controller which handles most front-end security requests, including Logging in, logging out, resetting password, or changing password. This class also provides an interface - to allow configured `[api:Authenticator]` classes to each display a custom login form. - -`[api:CMSSecurity]` Which is the controller which handles security requests within the CMS, and allows + to allow configured `[api:Authenticator]` classes to each display a custom login form. +* `[api:CMSSecurity]` Which is the controller which handles security requests within the CMS, and allows users to re-login without leaving the CMS. ## Member Authentication The default member authentication system is implemented in the following classes: - -`[api:MemberAuthenticator]` Which is the default member authentication implementation. This uses the email - and password stored internally for each member to authenticate them. - -`[api:MemberLoginForm]` Is the default form used by `MemberAuthenticator`, and is displayed on the public site +* `[api:MemberAuthenticator]` Which is the default member authentication implementation. This uses the email + and password stored internally for each member to authenticate them. +* `[api:MemberLoginForm]` Is the default form used by `MemberAuthenticator`, and is displayed on the public site at the url `Security/login` by default. - -`[api:CMSMemberLoginForm]` Is the secondary form used by `MemberAuthenticator`, and will be displayed to the +* `[api:CMSMemberLoginForm]` Is the secondary form used by `MemberAuthenticator`, and will be displayed to the user within the CMS any time their session expires or they are logged out via an action. This form is presented via a popup dialog, and can be used to re-authenticate that user automatically without them having to lose their workspace. E.g. if editing a form, the user can login and continue to publish their content. @@ -34,12 +29,10 @@ The default member authentication system is implemented in the following classes Additional authentication methods (oauth, etc) can be implemented by creating custom implementations of each of the following base classes: - -`[api:Authenticator]` The base class for authentication systems. This class also acts as the factory +* `[api:Authenticator]` The base class for authentication systems. This class also acts as the factory to generate various login forms for parts of the system. If an authenticator supports in-cms reauthentication then it will be necessary to override the `supports_cms` and `get_cms_login_form` methods. - -`[api:LoginForm]` which is the base class for a login form which links to a specific authenticator. At the very +* `[api:LoginForm]` which is the base class for a login form which links to a specific authenticator. At the very least, it will be necessary to implement a form class which provides a default login interface. If in-cms re-authentication is desired, then a specialised subclass of this method may be necessary. For example, this form could be extended to require confirmation of username as well as password. From 5ae0ca14076cd8810182e3a518e7ec4c7a8fac93 Mon Sep 17 00:00:00 2001 From: Patrick Nelson Date: Thu, 23 Apr 2015 16:30:29 -0400 Subject: [PATCH 32/76] FIX #4100 Setup the ability to overload the ShortcodeParser class and ensuring its methods/properties are extensible via the "static" keyword. --- parsers/ShortcodeParser.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/parsers/ShortcodeParser.php b/parsers/ShortcodeParser.php index f4f66eda0..6cd0f737b 100644 --- a/parsers/ShortcodeParser.php +++ b/parsers/ShortcodeParser.php @@ -9,15 +9,15 @@ * @package framework * @subpackage misc */ -class ShortcodeParser { +class ShortcodeParser extends Object { public function img_shortcode($attrs) { return ""; } - private static $instances = array(); + protected static $instances = array(); - private static $active_instance = 'default'; + protected static $active_instance = 'default'; // -------------------------------------------------------------------------------------------------------------- @@ -33,7 +33,7 @@ class ShortcodeParser { */ public static function get($identifier = 'default') { if(!array_key_exists($identifier, self::$instances)) { - self::$instances[$identifier] = new ShortcodeParser(); + self::$instances[$identifier] = static::create(); } return self::$instances[$identifier]; @@ -45,7 +45,7 @@ class ShortcodeParser { * @return ShortcodeParser */ public static function get_active() { - return self::get(self::$active_instance); + return static::get(self::$active_instance); } /** @@ -140,15 +140,15 @@ class ShortcodeParser { } } - private static $marker_class = '--ss-shortcode-marker'; + protected static $marker_class = '--ss-shortcode-marker'; - private static $block_level_elements = array( + protected static $block_level_elements = array( 'address', 'article', 'aside', 'audio', 'blockquote', 'canvas', 'dd', 'div', 'dl', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'ol', 'output', 'p', 'pre', 'section', 'table', 'ul' ); - private static $attrrx = ' + protected static $attrrx = ' ([^\s\/\'"=,]+) # Name \s* = \s* (?: @@ -158,11 +158,11 @@ class ShortcodeParser { ) '; - private static function attrrx() { + protected static function attrrx() { return '/'.self::$attrrx.'/xS'; } - private static $tagrx = ' + protected static $tagrx = ' # HTML Tag <(?(?:"[^"]*"[\'"]*|\'[^\']*\'[\'"]*|[^\'">])+)> @@ -182,7 +182,7 @@ class ShortcodeParser { (?\]?) '; - private static function tagrx() { + protected static function tagrx() { return '/'.sprintf(self::$tagrx, self::$attrrx).'/xS'; } @@ -207,7 +207,7 @@ class ShortcodeParser { protected function extractTags($content) { $tags = array(); - if(preg_match_all(self::tagrx(), $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + if(preg_match_all(static::tagrx(), $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { foreach($matches as $match) { // Ignore any elements if (empty($match['open'][0]) && empty($match['close'][0])) continue; @@ -216,7 +216,7 @@ class ShortcodeParser { $attrs = array(); if (!empty($match['attrs'][0])) { - preg_match_all(self::attrrx(), $match['attrs'][0], $attrmatches, PREG_SET_ORDER); + preg_match_all(static::attrrx(), $match['attrs'][0], $attrmatches, PREG_SET_ORDER); foreach ($attrmatches as $attr) { list($whole, $name, $value) = array_values(array_filter($attr)); From 6d19006cabbe2cf23a93314496b018105a44d1f5 Mon Sep 17 00:00:00 2001 From: Christopher Pitt Date: Mon, 27 Apr 2015 11:33:13 +1200 Subject: [PATCH 33/76] Clean up Validator --- forms/Validator.php | 80 ++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/forms/Validator.php b/forms/Validator.php index f23df34bb..9f2b22b33 100644 --- a/forms/Validator.php +++ b/forms/Validator.php @@ -1,22 +1,22 @@ form = $form; + return $this; } - + /** - * @return array Errors (if any) + * Returns any errors there may be. + * + * @return null|array */ - public function validate(){ + public function validate() { $this->errors = null; + $this->php($this->form->getData()); + return $this->errors; } - + /** * Callback to register an error on a field (Called from implementations of {@link FormField::validate}) - * - * @param $fieldName name of the field - * @param $message error message to display - * @param $messageType optional parameter, gets loaded into the HTML class attribute in the rendered output. - * See {@link getErrors()} for details. + * + * @param string $fieldName name of the field + * @param string $message error message to display + * @param string $messageType optional parameter, gets loaded into the HTML class attribute in the rendered output. + * + * See {@link getErrors()} for details. */ - public function validationError($fieldName, $message, $messageType='') { + public function validationError($fieldName, $message, $messageType = '') { $this->errors[] = array( 'fieldName' => $fieldName, 'message' => $message, 'messageType' => $messageType, ); } - + /** * Returns all errors found by a previous call to {@link validate()}. + * * The array contains the following keys for each error: * - 'fieldName': the name of the FormField instance * - 'message': Validation message (optionally localized) * - 'messageType': Arbitrary type of the message which is rendered as a CSS class in the FormField template, * e.g. . Usually "bad|message|validation|required", which renders differently * if framework/css/Form.css is included. - * + * * @return array */ public function getErrors() { return $this->errors; } - + + /** + * @param string $fieldName + * @param array $data + */ public function requireField($fieldName, $data) { if(is_array($data[$fieldName]) && count($data[$fieldName])) { - foreach($data[$fieldName] as $componentkey => $componentVal){ - if(!strlen($componentVal)) { - $this->validationError($fieldName, "$fieldName $componentkey is required", "required"); + foreach($data[$fieldName] as $componentKey => $componentValue) { + if(!strlen($componentValue)) { + $this->validationError( + $fieldName, + sprintf('%s %s is required', $fieldName, $componentKey), + 'required' + ); } } - } else if(!strlen($data[$fieldName])) { - $this->validationError($fieldName, "$fieldName is required", "required"); + $this->validationError( + $fieldName, + sprintf('%s is required', $fieldName), + 'required' + ); } } - + /** * Returns true if the named field is "required". + * * Used by FormField to return a value for FormField::Required(), to do things like show *s on the form template. + * * By default, it always returns false. */ public function fieldIsRequired($fieldName) { return false; } - + + /** + * @param array $data + * + * @return mixed + */ abstract public function php($data); } - From 3e3139d7c74037aff5af059dd44411c03729230b Mon Sep 17 00:00:00 2001 From: Christopher Pitt Date: Mon, 27 Apr 2015 11:59:06 +1200 Subject: [PATCH 34/76] Clean up TextField --- forms/TextField.php | 66 ++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/forms/TextField.php b/forms/TextField.php index 336be1aeb..c5b45715a 100644 --- a/forms/TextField.php +++ b/forms/TextField.php @@ -1,4 +1,5 @@ maxLength = $maxLength; - - parent::__construct($name, $title, $value, $form); + if($maxLength) { + $this->setMaxLength($maxLength); + } + + if($form) { + $this->setForm($form); + } + + parent::__construct($name, $title, $value); } - + /** - * @param int $length + * @param int $maxLength + * + * @return static */ - public function setMaxLength($length) { - $this->maxLength = $length; - + public function setMaxLength($maxLength) { + $this->maxLength = $maxLength; + return $this; } - + /** - * @return int + * @return null|int */ public function getMaxLength() { return $this->maxLength; } + /** + * @return array + */ public function getAttributes() { + $maxLength = $this->getMaxLength(); + + $attributes = array(); + + if($maxLength) { + $attributes['maxLength'] = $maxLength; + $attributes['size'] = min($maxLength, 30); + } + return array_merge( parent::getAttributes(), - array( - 'maxlength' => $this->getMaxLength(), - 'size' => ($this->getMaxLength()) ? min($this->getMaxLength(), 30) : null - ) + $attributes ); } + /** + * @return string + */ public function InternallyLabelledField() { - if(!$this->value) $this->value = $this->Title(); + if(!$this->value) { + $this->value = $this->Title(); + } + return $this->Field(); } - } From 120b983ad6aa7b9704ab46ffbe336909fb0e47b4 Mon Sep 17 00:00:00 2001 From: Loz Calver Date: Mon, 27 Apr 2015 15:38:45 +0100 Subject: [PATCH 35/76] FIX: X-Reload & X-ControllerURL didn't support absolute URLs (fixes #4119) --- admin/javascript/LeftAndMain.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index 2c62417a0..fb827fd48 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -633,8 +633,11 @@ jQuery.noConflict(); // Support a full reload if(xhr.getResponseHeader('X-Reload') && xhr.getResponseHeader('X-ControllerURL')) { - document.location.href = $('base').attr('href').replace(/\/*$/, '') - + '/' + xhr.getResponseHeader('X-ControllerURL'); + var baseUrl = $('base').attr('href'), + rawURL = xhr.getResponseHeader('X-ControllerURL'), + url = $.path.isAbsoluteUrl(rawURL) ? rawURL : $.path.makeUrlAbsolute(rawURL, baseUrl); + + document.location.href = url; return; } From 8727f27f2a514e3d6d237eb4a199b7e855bd5f7a Mon Sep 17 00:00:00 2001 From: Nik Rolls Date: Wed, 29 Apr 2015 20:17:08 +1200 Subject: [PATCH 36/76] Documentation tidy for Requirements.php Documentation-only changes, except where a couple of methods have been moved to live next to other related functions. --- view/Requirements.php | 734 ++++++++++++++++++++++++------------------ 1 file changed, 416 insertions(+), 318 deletions(-) diff --git a/view/Requirements.php b/view/Requirements.php index 932dbe9d6..c87d72cb5 100644 --- a/view/Requirements.php +++ b/view/Requirements.php @@ -1,8 +1,7 @@ set_combined_files_enabled($enable); @@ -26,14 +26,16 @@ class Requirements implements Flushable { /** * Checks whether combining of css/javascript files is enabled. - * @return boolean + * + * @return bool */ public static function get_combined_files_enabled() { return self::backend()->get_combined_files_enabled(); } /** - * Set the relative folder e.g. "assets" for where to store combined files + * Set the relative folder e.g. 'assets' for where to store combined files + * * @param string $folder Path to folder */ public static function set_combined_files_folder($folder) { @@ -41,8 +43,10 @@ class Requirements implements Flushable { } /** - * Set whether we want to suffix requirements with the time / - * location on to the requirements + * Set whether to add caching query params to the requests for file-based requirements. + * Eg: themes/myTheme/js/main.js?m=123456789. The parameter is a timestamp generated by + * filemtime. This has the benefit of allowing the browser to cache the URL infinitely, + * while automatically busting this cache every time the file is changed. * * @param bool */ @@ -51,7 +55,7 @@ class Requirements implements Flushable { } /** - * Return whether we want to suffix requirements + * Check whether we want to suffix requirements * * @return bool */ @@ -60,9 +64,10 @@ class Requirements implements Flushable { } /** - * Instance of requirements for storage + * Instance of the requirements for storage. You can create your own backend to change the + * default JS and CSS inclusion behaviour. * - * @var Requirements + * @var Requirements_Backend */ private static $backend = null; @@ -76,74 +81,78 @@ class Requirements implements Flushable { /** * Setter method for changing the Requirements backend * - * @param Requirements $backend + * @param Requirements_Backend $backend */ public static function set_backend(Requirements_Backend $backend) { self::$backend = $backend; } /** - * Register the given javascript file as required. - * - * See {@link Requirements_Backend::javascript()} for more info - * + * Register the given JavaScript file as required. + * + * @param string $file Relative to docroot */ public static function javascript($file) { self::backend()->javascript($file); } /** - * Add the javascript code to the header of the page + * Register the given JavaScript code into the list of requirements * - * See {@link Requirements_Backend::customScript()} for more info - * @param script The script content - * @param uniquenessID Use this to ensure that pieces of code only get added once. + * @param string $script The script content as a string (without enclosing