Development

Changeset 8020

You must first sign up to be able to contribute.

Changeset 8020

Show
Ignore:
Timestamp:
03/21/08 11:01:15 (1 year ago)
Author:
fabien
Message:

merged 1.1 branch changes (to r8006)

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/COPYRIGHT

    r5106 r8020  
    55------- 
    66 
    7 symfony is originally based on Mojavi3 by Sean Kerr 
     7symfony is originally based on Mojavi3 by Sean Kerr <sean@code-box.org> 
    88 
    99Url:       http://www.symfony-project.com/ 
     
    6464License:   BSD - see licenses/LICENSE.prado file 
    6565 
    66 Spyc 
    67 ---- 
    68  
    69 symfony contains the Spyc software 
    70  
    71 Url:       http://spyc.sourceforge.net/ 
    72 Copyright: 2005-2006 Chris Wanstrath 
    73 License:   MIT - http://www.opensource.org/licenses/mit-license.php 
    74  
    7566lime 
    7667---- 
  • trunk/UPGRADE

    r7131 r8020  
    1010--------------- 
    1111 
    12 To upgrade a project, you first need to upgrade symfony via PEAR or 
    13 change your `config/config.php` to update the symfony directory. 
    14  
    15 Then, you can launch the following task from your project directory to 
    16 perform an automatic upgrade: 
    17  
    18   ./symfony project:upgrade1.1 
    19  
    20 This task can be launched several times without any side effect. Each time 
    21 you upgrade to a new symfony 1.1 beta / RC or the final symfony 1.1, you 
    22 need to launch this task. 
    23  
    24 If you don't plan to upgrade the validation system or all your helpers to 
    25 the new system, you must enable the compatibility mode in `settings.yml`: 
    26  
    27   [yml] 
    28   all: 
    29     .settings: 
    30       compat_10: on 
    31  
    32 Here is a list of the things that will be enabled when switching to the 
    33 compatibility mode (see the bundled `sfCompat10Plugin` plugin for 
    34 more information): 
    35  
    36   * Zend Framework and ezComponents bridges 
    37   * sfProcessCache 
    38   * validation system (validate.yml, validator classes, ...) 
    39   * fill in filter 
    40   * helpers 
    41   * sfMail with phpmailer 
     12To upgrade a project: 
     13 
     14  * If you don't use a SCM tool, please make a backup of your project. 
     15    As symfony replaces some files during the upgrade 
     16    (front controllers for example), you need a way to merge your 
     17    customizations after the upgrade. 
     18 
     19  * Update the `symfony` file located in the project root directory 
     20    by changing those three lines: 
     21 
     22        [php] 
     23        chdir(dirname(__FILE__)); 
     24        include('config/config.php'); 
     25        include($sf_symfony_data_dir.'/bin/symfony.php'); 
     26 
     27    to 
     28 
     29        [php] 
     30        chdir(dirname(__FILE__)); 
     31        require_once(dirname(__FILE__).'/config/ProjectConfiguration.class.php'); 
     32        $configuration = new ProjectConfiguration(); 
     33        include($configuration->getSymfonyLibDir().'/command/cli.php'); 
     34 
     35    You can also copy the skeleton file from the symfony project skeleton directly: 
     36 
     37        $ cp /path/to/symfony/lib/task/generator/skeleton/project/symfony symfony 
     38 
     39  * Create a `config/ProjectConfiguration.class.php` file with the following content: 
     40 
     41        [php] 
     42        <?php 
     43 
     44        require_once '##SYMFONY_LIB_DIR##/autoload/sfCoreAutoload.class.php'; 
     45        sfCoreAutoload::register(); 
     46 
     47        class ProjectConfiguration extends sfProjectConfiguration 
     48        { 
     49          public function setup() 
     50          { 
     51          } 
     52        } 
     53 
     54    Then, replace `##SYMFONY_LIB_DIR##` with the path to the symfony 1.1 
     55    `lib/` directory. This is the new way to change the symfony version used 
     56    for your project. 
     57 
     58    You can also copy the skeleton file from the symfony project skeleton directly: 
     59 
     60        $ cp /path/to/symfony/lib/task/generator/skeleton/project/config/ProjectConfiguration.class.php config/ProjectConfiguration.class.php 
     61 
     62  * Launch the `project:upgrade1.1` task from your project directory 
     63    to perform an automatic upgrade: 
     64 
     65        $ ./symfony project:upgrade1.1 
     66 
     67    This task can be launched several times without any side effect. Each time 
     68    you upgrade to a new symfony 1.1 beta / RC or the final symfony 1.1, you 
     69    need to launch this task. 
     70 
     71  * If you don't plan to upgrade the validation or mailing system to 
     72    the new system, you must enable the compatibility mode in `settings.yml`: 
     73 
     74        [yml] 
     75        all: 
     76          .settings: 
     77            compat_10: on 
     78 
     79    Here is a list of the things that will be enabled when switching to the 
     80    compatibility mode (see the bundled `sfCompat10Plugin` plugin for 
     81    more information): 
     82 
     83      * Zend Framework and ezComponents bridges 
     84      * sfProcessCache 
     85      * validation system (validate.yml, validator classes, ...) 
     86      * fill in filter 
     87      * sfMail with phpmailer 
    4288 
    4389The remaining sections explains backward incompatible changes. 
     
    4894Flash attributes are now managed directly by `sfUser`. New usage: 
    4995 
    50   [php] 
    51   // action 
    52   $this->getUser()->setFlash('notice', 'foo'); 
    53   $notice = $this->getUser()->getFlash('notice'); 
    54  
    55   // template 
    56   <?php $sf_user->hasFlash('notice'): ?> 
    57     <?php echo $sf_user->getFlash('notice') ?> 
    58   <?php endif; ?> 
     96    [php] 
     97    // action 
     98    $this->getUser()->setFlash('notice', 'foo'); 
     99    $notice = $this->getUser()->getFlash('notice'); 
     100 
     101    // template 
     102    <?php $sf_user->hasFlash('notice'): ?> 
     103      <?php echo $sf_user->getFlash('notice') ?> 
     104    <?php endif; ?> 
    59105 
    60106The `flash` entry in `filters.yml` must be removed too as the `sfFlashFilter` 
     
    73119They are accessible from `sfController`: 
    74120 
    75   [php] 
    76   // action 
    77   $this->getController()->getPresentationFor(...); 
     121    [php] 
     122    // action 
     123    $this->getController()->getPresentationFor(...); 
    78124 
    79125The `project:upgrade1.1` task makes all those changes for you. 
     
    88134available from `sfContext`: 
    89135 
    90   [php] 
    91   sfContext::getInstance()->getI18N() 
    92   sfContext::getInstance()->getRouting() 
    93   sfContext::getInstance()->getLogger() 
     136    [php] 
     137    sfContext::getInstance()->getI18N() 
     138    sfContext::getInstance()->getRouting() 
     139    sfContext::getInstance()->getLogger() 
    94140 
    95141Routing 
     
    98144Here is the default configuration for the routing in `factories.yml`: 
    99145 
    100   routing: 
    101     class: sfPatternRouting 
    102     param: 
    103       load_configuration: true 
     146    [yml] 
     147    routing: 
     148      class: sfPatternRouting 
     149      param: 
     150        load_configuration: true 
    104151 
    105152The `project:upgrade1.1` task makes all the changes for you. 
     
    110157Here is the default configuration for logging in `factories.yml`: 
    111158 
    112   logger: 
    113     class: sfAggregateLogger 
    114     param: 
    115       level: debug 
    116       loggers: 
    117         sf_web_debug: 
    118           class: sfWebDebugLogger 
    119           param: 
    120             condition: %SF_WEB_DEBUG% 
    121             xdebug_logging: true 
    122         sf_file_debug: 
    123           class: sfFileLogger 
    124           param: 
    125             file: %SF_LOG_DIR%/%SF_APP%_%SF_ENVIRONMENT%.log 
     159    [yml] 
     160    logger: 
     161      class: sfAggregateLogger 
     162      param: 
     163        level: debug 
     164        loggers: 
     165          sf_web_debug: 
     166            class: sfWebDebugLogger 
     167            param: 
     168              condition: %SF_WEB_DEBUG% 
     169              xdebug_logging: true 
     170          sf_file_debug: 
     171            class: sfFileLogger 
     172            param: 
     173              file: %SF_LOG_DIR%/%SF_APP%_%SF_ENVIRONMENT%.log 
    126174 
    127175The `logging.yml` configuration file is not used anymore. 
     
    131179your application `factories.yml`: 
    132180 
    133   prod: 
    134     logger: 
    135       class:   sfNoLogger 
    136       param: 
    137         level:   err 
    138         loggers: ~ 
     181    [yml] 
     182    prod: 
     183      logger: 
     184        class:   sfNoLogger 
     185        param: 
     186          level:   err 
     187          loggers: ~ 
    139188 
    140189There is also a new `logging_enabled` setting in `settings.yml`. 
    141190This can be used to prevent logging in the production environment altogether: 
    142191 
    143   prod: 
    144     .settings: 
    145       logging_enabled: off 
     192    [yml] 
     193    prod: 
     194      .settings: 
     195        logging_enabled: off 
    146196 
    147197The `project:upgrade1.1` task makes all those changes for you. 
     
    152202Here is the default configuration for i18n in `factories.yml`: 
    153203 
    154   i18n: 
    155     class: sfI18N 
    156     param: 
    157       source:              XLIFF 
    158       debug:               off 
    159       untranslated_prefix: "[T]" 
    160       untranslated_suffix: "[/T]" 
    161       cache: 
    162         class: sfFileCache 
    163         param: 
    164           automatic_cleaning_factor: 0 
    165           cache_dir:                 %SF_I18N_CACHE_DIR% 
    166           lifetime:                  86400 
    167           prefix:                    %SF_APP_DIR% 
     204    [yml] 
     205    i18n: 
     206      class: sfI18N 
     207      param: 
     208        source:              XLIFF 
     209        debug:               off 
     210        untranslated_prefix: "[T]" 
     211        untranslated_suffix: "[/T]" 
     212        cache: 
     213          class: sfFileCache 
     214          param: 
     215            automatic_cleaning_factor: 0 
     216            cache_dir:                 %SF_I18N_CACHE_DIR% 
     217            lifetime:                  86400 
     218            prefix:                    %SF_APP_DIR% 
    168219 
    169220The `i18n.yml` configuration file is not used anymore. 
    170221Instead, you can configure i18n in `factories.yml`. 
    171222 
    172 The only exception is the `default_culture` setting which is now configurable in `settings.yml` 
    173 and do not depend on the i18n framework anymore: 
     223The only exception is the `default_culture` setting which is now configurable 
     224in `settings.yml` and do not depend on the i18n framework anymore: 
    174225 
    175226  default_culture: en 
    176227 
    177 If your project has some specific settings, you must move your current configuration from the `i18n.yml` to 
    178 the `factories.yml` and add the default culture in `settings.yml` as shown above. 
     228If your project has some specific settings, you must move your current 
     229configuration from the `i18n.yml` to the `factories.yml` and add the default 
     230culture in `settings.yml` as shown above. 
    179231 
    180232Cache Framework 
     
    196248 
    197249The `autoloading_function` setting in `settings.yml` is not used anymore. 
    198 You can register autoloading callables in the application `config.php`. 
    199 Here is the new default `config.php`: 
    200  
    201   [php] 
    202   <?php 
    203  
    204   // include project configuration 
    205   include(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php'); 
    206  
    207   // symfony bootstraping 
    208   require_once($sf_symfony_lib_dir.'/util/sfCore.class.php'); 
    209   sfCore::bootstrap($sf_symfony_lib_dir, $sf_symfony_data_dir); 
    210  
    211   // insert your own autoloading callables here 
    212  
    213   if (sfConfig::get('sf_debug')) 
    214   { 
    215     spl_autoload_register(array('sfAutoload', 'autoloadAgain')); 
    216   } 
    217  
    218 Thanks to the new `sfAutoload::autoloadAgain()` method, you won't need to clear the 
    219 cache when you add or move classes in your project. This method will automatically 
    220 find the changes and flush the autoloading cache. 
    221  
    222 The `project:upgrade1.1` task makes all those changes for you. 
     250You can register autoloading callables in your application configuration class. 
     251 
     252Thanks to the new `sfAutoload::autoloadAgain()` method, you won't need to clear 
     253the cache when you add or move classes in your project. This method will 
     254automatically find the changes and flush the autoloading cache. 
    223255 
    224256VERSION 
    225257------- 
    226258 
    227 The lib/VERSION file has been removed. If you want to get the current symfony version, 
    228 you can use the `sfCore::VERSION` constant. 
     259The lib/VERSION file has been removed. If you want to get the current symfony 
     260version, you can use the `SYMFONY_VERSION` constant. This constant is defined 
     261in `autoload/sfCoreAutoload.class.php` 
    229262 
    230263Routing 
    231264------- 
    232265 
    233 To inject default route parameters, you can now use the `->setDefaultParameter()` method 
    234 instead of using the `sf_routing_defaults` setting: 
    235  
    236   [php] 
    237   $this->context->getRouting()->setDefaultParameter($key, $value); 
     266To inject default route parameters, you can now use the `->setDefaultParameter()` 
     267method instead of using the `sf_routing_defaults` setting: 
     268 
     269    [php] 
     270    $this->context->getRouting()->setDefaultParameter($key, $value); 
    238271 
    239272I18N 
     
    242275symfony core classes don't return internationalized strings anymore: 
    243276 
    244   [php] 
    245   <?php echo __($sf_request->getError('foo')) ?> 
     277    [php] 
     278    <?php echo __($sf_request->getError('foo')) ?> 
    246279 
    247280This behavior has changed for the following methods and functions: 
    248281 
    249   [php] 
    250   sfWebRequest::getError() 
    251   sfWebResponse::addMeta() 
     282    [php] 
     283    sfWebRequest::getError() 
     284    sfWebResponse::addMeta() 
    252285 
    253286The following helpers (in sfCompat10Plugin) still return internationalized data: 
    254287 
    255   [php] 
    256   form_error() 
    257   include_metas() 
    258  
    259 The `getGlobalMessageSource()` and `getGlobalMessageFormat()` methods has been removed 
    260 from the sfI18N class. They are now equivalent to `getMessageSource()`  
     288    [php] 
     289    form_error() 
     290    include_metas() 
     291 
     292The `getGlobalMessageSource()` and `getGlobalMessageFormat()` methods has been 
     293removed from the sfI18N class. They are now equivalent to `getMessageSource()`  
    261294and `getMessageFormat()`. 
    262295 
     
    266299Logger priorities are now constants: 
    267300 
    268   [php] 
    269   sfLogger::INFO 
     301    [php] 
     302    sfLogger::INFO 
    270303 
    271304The `project:upgrade1.1` task makes all those changes for you. 
     
    317350  * `->moveFile()` 
    318351 
    319 sfValidator class 
    320 ----------------- 
    321  
    322 If you use the symfony 1.0 interface for validation, your custom validators must 
    323 now extend the `sfValidatorBase` class. 
    324  
    325352`->initialize()` methods 
    326353------------------------ 
    327354 
    328 Most symfony core classes are initialized thanks to a `->initialize()` method. As of symfony 1.1, 
    329 this method is automatically called by `__construct()`, so, there is no need to call it by yourself. 
     355Most symfony core classes are initialized thanks to a `->initialize()` method. 
     356As of symfony 1.1, this method is automatically called by `__construct()`, 
     357so, there is no need to call it by yourself. 
    330358 
    331359Configuration files loading 
     
    334362Some core classes can be configured with a `.yml` file: 
    335363 
    336 || '''Class'''          || '''Configuration file'''         || 
    337 ||                      ||                                  || 
    338 || `sfAction`           || `security.yml`                   || 
    339 || `sfAutoload`         || `autoload.yml`                   || 
    340 || `sfConfigCache`      || `config_handlers.yml`            || 
    341 || `sfContext`          || `factories.yml`                  || 
    342 || `sfController`       || `generator.yml` and `module.yml` || 
    343 || `sfDatabaseManager`  || `databases.yml`                  || 
    344 || `sfFilterChain`      || `filters.yml`                    || 
    345 || `sfI18N`             || `i18n.yml`                       || 
    346 || `sfPatternRouting`   || `routing.yml`                    || 
    347 || `sfPHPView`          || `view.yml`                       || 
    348 || `sfViewCacheManager` || `cache.yml`                      || 
    349  
    350 In symfony 1.1, the loading of the configuration file for ''independant'' sub-frameworks has been 
    351 moved to a `loadConfiguration()` method to ease decoupling and reuse them without needing the whole framework: 
     364 *Class*              | *Configuration file* 
     365 -------------------- | -------------------------------- 
     366 `sfAction`           | `security.yml` 
     367 `sfAutoload`         | `autoload.yml` 
     368 `sfConfigCache`      | `config_handlers.yml` 
     369 `sfContext`          | `factories.yml` 
     370 `sfController`       | `generator.yml` and `module.yml` 
     371 `sfDatabaseManager`  | `databases.yml` 
     372 `sfFilterChain`      | `filters.yml` 
     373 `sfI18N`             | `i18n.yml` 
     374 `sfPatternRouting`   | `routing.yml` 
     375 `sfPHPView`          | `view.yml` 
     376 `sfViewCacheManager` | `cache.yml` 
     377 
     378In symfony 1.1, the loading of the configuration file for ''independant'' 
     379sub-frameworks has been moved to a `loadConfiguration()` method to ease 
     380decoupling and reuse them without needing the whole framework: 
    352381 
    353382  * `sfDatabaseManager` 
     
    355384  * `sfPatternRouting` 
    356385 
    357 So, for example, if you need a database manager in your batch script, you will have to change from: 
     386So, for example, if you need a database manager in your batch script, 
     387you will have to change from: 
    358388 
    359389    [php] 
     
    364394 
    365395    [php] 
    366     $databaseManager = new sfDatabaseManager(); 
     396    $configuration = ProjectConfiguration::getApplicationConfiguration($application, $env, true); 
     397    $databaseManager = new sfDatabaseManager($configuration); 
    367398    $databaseManager->loadConfiguration(); 
    368399 
     
    373404 
    374405The `web_debug` entry in `filters.yml` must be removed as the `sfWebDebugFilter` 
    375 has been removed. The web debug toolbar is now injected in the response thanks to a listener. 
     406has been removed. The web debug toolbar is now injected in the response thanks 
     407to a listener. 
    376408 
    377409The `project:upgrade1.1` task makes all those changes for you. 
     
    380412--------------- 
    381413 
    382 The `sf_timeout` setting is not used anymore. To change the session timeout, you now have to edit the 
    383 `factories.yml` instead if the `settings.yml`, and change the parameters of the `user` factory: 
    384  
     414The `sf_timeout` setting is not used anymore. To change the session timeout, 
     415you now have to edit `factories.yml` instead of the `settings.yml`, 
     416and change the parameters of the `user` factory: 
     417 
     418    [yml] 
    385419    all: 
    386420      user: 
     
    389423          timeout:     1800     # session timeout in seconds 
    390424 
     425Routing configuration 
     426--------------------- 
     427 
     428The `sf_suffix`, `sf_default_module`, and `sf_default_action` settings are not 
     429used anymore. To change the default suffix, module, or action, you now have 
     430to edit `factories.yml` instead of `settings.yml`, and change the parameters 
     431of the `routing` factory: 
     432 
     433    [yml] 
     434    all: 
     435      routing: 
     436        class: sfPatternRouting 
     437        param: 
     438          load_configuration: true 
     439          suffix:             .       # Default suffix for generated URLs. If set to a single dot (.), no suffix is added. Possible values: .html, .php, and so on. 
     440          default_module:     default # Default module and action to be called when 
     441          default_action:     index   # A routing rule doesn't set it 
     442 
    391443`php.yml` configuration file 
    392444---------------------------- 
     
    394446The `php.yml` configuration file has been removed. 
    395447 
    396 The only setting you will have to check by hand is `log_errors`, which was set to `on` by `php.yml`. 
    397  
    398 `php.yml` is replaced by the `check_configuration.php` utility you can find in `data/bin`. 
    399 It checks your environment against symfony requirements. You can launch it from anywhere: 
    400  
    401    $ php /path/to/symfony/data/bin/check_configuration.php 
    402  
    403 Even if you can use this utility from the command line, it's strongly recommended to launch it 
    404 from the web by copying it under your web root directory as PHP can use different php.ini configuration 
    405 files for the CLI and the web. 
     448The only setting you will have to check by hand is `log_errors`, which was set 
     449to `on` by `php.yml`. 
     450 
     451`php.yml` is replaced by the `check_configuration.php` utility you can find 
     452in `data/bin`. It checks your environment against symfony requirements. 
     453You can launch it from anywhere: 
     454 
     455    $ php /path/to/symfony/data/bin/check_configuration.php 
     456 
     457Even if you can use this utility from the command line, it's strongly recommended 
     458to launch it from the web by copying it under your web root directory as PHP can 
     459use different php.ini configuration files for the CLI and the web. 
     460 
     461`$sf_symfony_data_dir` removal 
     462------------------------------ 
     463 
     464In symfony 1.1, `$sf_symfony_data_dir` has been removed. All relevant files and 
     465directories from the symfony `data` directory have been moved to the `lib` 
     466directory: 
     467 
     468 *Old Location*         | *New Location* 
     469 ---------------------- | ----------------------------- 
     470 `data/config`          | `lib/config/config` 
     471 `data/i18n`            | `lib/i18n/data` 
     472 `data/skeleton`        | `lib/task/generator/skeleton` 
     473 `data/modules/default` | `lib/controller/default` 
     474 `data/web/errors`      | `lib/exception/data` 
     475 `data/exception.*`     | `lib/exception/data` 
     476 
     477The symfony core has been upgraded to take these changes into account. 
     478 
     479sfLoader 
     480-------- 
     481 
     482All `sfLoader` static methods (except `::getHelperDirs()` and `::loadHelpers()`) 
     483have been moved to the `sfProjectConfiguration` and `sfApplicationConfiguration` 
     484classes: 
     485 
     486  * `sfProjectConfiguration`: 
     487      * `->getGeneratorSkeletonDirs()` 
     488      * `->getGeneratorTemplate()` 
     489      * `->getGeneratorTemplateDirs()` 
     490      * `->getModelDirs()` 
     491 
     492  * `sfApplicationConfiguration`: 
     493      * `->getControllerDirs()` 
     494      * `->getTemplateDirs()` 
     495      * `->getTemplateDir()` 
     496      * `->getTemplatePath()` 
     497      * `->getI18NGlobalDirs()` 
     498      * `->getI18NDirs()` 
     499      * `->getConfigPaths()` 
     500 
     501sfCore 
     502------ 
     503 
     504The `sfCore` has been removed. The code has been moved to `sfProjectConfiguration`, 
     505`sfApplicationConfiguration`, and `sfContext` classes. 
     506 
     507Front Controllers 
     508----------------- 
     509 
     510All front controllers have to be upgraded. The SF_DEBUG, SF_APP, SF_ENVIRONMENT, 
     511and SF_ROOT_DIR constants are gone. If you use some of these constants in your 
     512project, please use their sfConfig::get('') counterparts: 
     513 
     514 *Old*             | *New* 
     515 ----------------- | --------------------------------- 
     516 `SF_ROOT_DIR`     | `sfConfig::get('sf_root_dir')` 
     517 `SF_ENVIRONMENT`  | `sfConfig::get('sf_environment')` 
     518 `SF_APP`          | `sfConfig::get('sf_app')` 
     519 `SF_DEBUG`        | `sfConfig::get('sf_debug')` 
     520 
     521The `project:upgrade1.1` task upgrades all front controllers for you. 
     522If you made some customizations, symfony will issue a warning and won't 
     523upgrade them automatically. You can then copy the default skeleton from 
     524symfony: /path/to/symfony/lib/task/generator/skeleton/app/web/index.php 
     525 
     526config.php 
     527---------- 
     528 
     529All `config.php` files have been removed. The are replaced by the `ProjectConfiguration` 
     530class and the application configuration classes. 
     531 
     532If you've added some cutomizations in `config.php` files, you will have to migrate them 
     533to those new classes. 
     534 
     535Directory structure 
     536------------------- 
     537 
     538All `sfConfig` constants ending with `_dir_name` have been removed. 
     539 
     540Cache keys 
     541---------- 
     542 
     543The `sfViewCacheManager::removePattern()` and `sfToolkit::clearGlob()` don't work anymore 
     544for removing several cache parts at once. But the `sfViewCacheManager::remove()` now 
     545accepts internal URIs with wildcards. So you can replace: 
     546 
     547    $cacheManager->removePattern('*/all/user/show/id/*'); 
     548 
     549By: 
     550 
     551    $cacheManager->remove('user/show?id=*', '*', 'all'); 
     552 
     553This also works for partials and contextual partials. You can then replace: 
     554 
     555    $cacheManager->removePattern('/sf_cache_partial/user/_my_partial/sf_cache_key/*'); 
     556 
     557By: 
     558 
     559    $cacheManager->remove('@sf_cache_partial?module=user&action=_my_partial&sf_cache_key=*'); 
     560 
     561And the biggets benefit is that it allows you to clear 'glob' URIs in *any* cache 
     562factory, not only the `sfFileCache`. 
     563 
     564NOTE to early adopters 
     565---------------------- 
     566 
     567If you have upgraded your project and have a `lib/Projectconfiguratioun.class.php` file, 
     568then you need to upgrade your project manually before being able to launch the 
     569`project:upgrade1.1` task. 
     570 
     571Here is how: 
     572 
     573  * Move `lib/ProjectConfiguration.class.php` to `config/ProjectConfiguration.class.php` 
     574 
     575  * Change the path to symfony in `config/ProjectConfiguration.class.php` if needed. 
     576 
     577  * Move all your application configuration classes (`lib/$APP_NAME$Configuration.class.php`) 
     578    to their respective `apps/$APP_NAME$/config/` directory. 
     579 
     580  * Remove the `require_once dirname(__FILE__).'/ProjectConfiguration.class.php';` in all 
     581    the application configuration classes. 
     582 
     583  * Change the location of `ProjectConfiguration.class.php` in the main `symfony` script to `config/` 
     584 
     585  * Change your front controllers so they look like this: 
     586 
     587      {{{ 
     588        <?php 
     589 
     590        require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php'); 
     591 
     592        $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'dev', true); 
     593        sfContext::createInstance($configuration)->dispatch(); 
     594      }}} 
     595 
     596You can now launch the `project:upgrade1.1` script to finish the upgrade. 
  • trunk/data/bin/symfony

    r4550 r8020  
    44/* 
    55 * This file is part of the symfony package. 
    6  * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com> 
     6 * (c) Fabien Potencier <fabien.potencier@symfony-project.com> 
    77 *  
    88 * For the full copyright and license information, please view the LICENSE 
     
    1818if (!isset($sf_symfony_lib_dir)) 
    1919{ 
    20   if (is_readable(dirname(__FILE__).'/../../lib/util/sfCore.class.php')) 
     20  if (is_readable(dirname(__FILE__).'/../../lib/autoload/sfCoreAutoload.class.php')) 
    2121  { 
    2222    // SVN 
    2323    $sf_symfony_lib_dir  = realpath(dirname(__FILE__).'/../../lib'); 
    24     $sf_symfony_data_dir = realpath(dirname(__FILE__).'/..'); 
    2524  } 
    2625  else 
     
    2827    // PEAR 
    2928    $sf_symfony_lib_dir  = '@PEAR-DIR@/symfony'; 
    30     $sf_symfony_data_dir = '@DATA-DIR@/symfony'; 
    3129 
    3230    if (!is_dir($sf_symfony_lib_dir)) 
     
    3735} 
    3836 
    39 include($sf_symfony_data_dir.'/bin/symfony.php'); 
     37include($sf_symfony_lib_dir.'/command/cli.php'); 
  • trunk/data/web/sf/calendar/lang/calendar-he.js

    r3017 r8020  
    4444 "א"); 
    4545 
     46// First day of the week. "0" means display Sunday first, "1" means display 
     47// Monday first, etc. 
     48Calendar._FD = 0; 
     49 
    4650// full month names 
    4751Calendar._MN = new Array 
     
    8488"מופץ תחת זיכיון ה GNU LGPL.  עיין ב http://gnu.org/licenses/lgpl.html לפרטים נוספים." + 
    8589"\n\n" + 
    86 בחירת תאריך:\n" + 
     90"בחירת תאריך:\n" + 
    8791"- השתמש בכפתורים \xab, \xbb לבחירת שנה\n" + 
    8892"- השתמש בכפתורים " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " לבחירת חודש\n" + 
  • trunk/data/web/sf/prototype/js/builder.js

    r3316 r8020  
    1 // script.aculo.us builder.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 
     1// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008 
    22 
    3 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
     3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
    44// 
    55// script.aculo.us is freely distributable under the terms of an MIT-style license. 
     
    4949    if(arguments[1]) 
    5050      if(this._isStringOrNumber(arguments[1]) || 
    51         (arguments[1] instanceof Array)) { 
     51        (arguments[1] instanceof Array) || 
     52        arguments[1].tagName) { 
    5253          this._children(element, arguments[1]); 
    5354        } else { 
     
    6768            if(element.tagName.toUpperCase() != elementName) 
    6869              element = parentElement.getElementsByTagName(elementName)[0]; 
    69            
     70         
    7071        }  
    7172 
     
    8990    for(attribute in attributes) 
    9091      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) + 
    91           '="' + attributes[attribute].toString().escapeHTML() + '"'); 
     92          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"'); 
    9293    return attrs.join(" "); 
    9394  }, 
    9495  _children: function(element, children) { 
     96    if(children.tagName) { 
     97      element.appendChild(children); 
     98      return; 
     99    } 
    95100    if(typeof children=='object') { // array can hold nodes and text 
    96101      children.flatten().each( function(e) { 
     
    102107      }); 
    103108    } else 
    104       if(Builder._isStringOrNumber(children))  
    105         element.appendChild(Builder._text(children)); 
     109      if(Builder._isStringOrNumber(children)) 
     110        element.appendChild(Builder._text(children)); 
    106111  }, 
    107112  _isStringOrNumber: function(param) { 
  • trunk/data/web/sf/prototype/js/controls.js

    r3316 r8020  
    1 // script.aculo.us controls.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 
    2  
    3 // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
    4 //           (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan) 
    5 //           (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com) 
     1// script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008 
     2 
     3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 
     4//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) 
     5//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) 
    66// Contributors: 
    77//  Richard Livsey 
     
    4040  throw("controls.js requires including script.aculo.us' effects.js library"); 
    4141 
    42 var Autocompleter = {} 
    43 Autocompleter.Base = function() {}; 
    44 Autocompleter.Base.prototype = { 
     42var Autocompleter = { } 
     43Autocompleter.Base = Class.create({ 
    4544  baseInitialize: function(element, update, options) { 
    46     this.element     = $(element);  
     45    element          = $(element) 
     46    this.element     = element;  
    4747    this.update      = $(update);   
    4848    this.hasFocus    = false;  
     
    5151    this.index       = 0;      
    5252    this.entryCount  = 0; 
     53    this.oldElementValue = this.element.value; 
    5354 
    5455    if(this.setOptions) 
    5556      this.setOptions(options); 
    5657    else 
    57       this.options = options || {}; 
     58      this.options = options || { }; 
    5859 
    5960    this.options.paramName    = this.options.paramName || this.element.name; 
     
    7778    if(typeof(this.options.tokens) == 'string')  
    7879      this.options.tokens = new Array(this.options.tokens); 
     80    // Force carriage returns as token delimiters anyway 
     81    if (!this.options.tokens.include('\n')) 
     82      this.options.tokens.push('\n'); 
    7983 
    8084    this.observer = null; 
     
    8488    Element.hide(this.update); 
    8589 
    86     Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); 
    87     Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); 
     90    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); 
     91    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); 
    8892  }, 
    8993 
     
    9195    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); 
    9296    if(!this.iefix &&  
    93       (navigator.appVersion.indexOf('MSIE')>0) && 
    94       (navigator.userAgent.indexOf('Opera')<0) && 
     97      (Prototype.Browser.IE) && 
    9598      (Element.getStyle(this.update, 'position')=='absolute')) { 
    9699      new Insertion.After(this.update,  
     
    142145         this.markPrevious(); 
    143146         this.render(); 
    144          if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); 
     147         Event.stop(event); 
    145148         return; 
    146149       case Event.KEY_DOWN: 
    147150         this.markNext(); 
    148151         this.render(); 
    149          if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); 
     152         Event.stop(event); 
    150153         return; 
    151154      } 
    152155     else  
    153156       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||  
    154          (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; 
     157         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; 
    155158 
    156159    this.changed = true; 
     
    198201          Element.addClassName(this.getEntry(i),"selected") :  
    199202          Element.removeClassName(this.getEntry(i),"selected"); 
    200          
    201203      if(this.hasFocus) {  
    202204        this.show(); 
     
    241243    var value = ''; 
    242244    if (this.options.select) { 
    243       var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; 
     245      var nodes = $(selectedElement).select('.' + this.options.select) || []; 
    244246      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); 
    245247    } else 
    246248      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 
    247249     
    248     var lastTokenPos = this.findLastToken(); 
    249     if (lastTokenPos != -1) { 
    250       var newValue = this.element.value.substr(0, lastTokenPos + 1); 
    251       var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); 
     250    var bounds = this.getTokenBounds(); 
     251    if (bounds[0] != -1) { 
     252      var newValue = this.element.value.substr(0, bounds[0]); 
     253      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); 
    252254      if (whitespace) 
    253255        newValue += whitespace[0]; 
    254       this.element.value = newValue + value
     256      this.element.value = newValue + value + this.element.value.substr(bounds[1])
    255257    } else { 
    256258      this.element.value = value; 
    257259    } 
     260    this.oldElementValue = this.element.value; 
    258261    this.element.focus(); 
    259262     
     
    299302  onObserverEvent: function() { 
    300303    this.changed = false;    
     304    this.tokenBounds = null; 
    301305    if(this.getToken().length>=this.options.minChars) { 
    302       this.startIndicator(); 
    303306      this.getUpdatedChoices(); 
    304307    } else { 
     
    306309      this.hide(); 
    307310    } 
     311    this.oldElementValue = this.element.value; 
    308312  }, 
    309313 
    310314  getToken: function() { 
    311     var tokenPos = this.findLastToken(); 
    312     if (tokenPos != -1) 
    313       var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); 
    314     else 
    315       var ret = this.element.value; 
    316  
    317     return /\n/.test(ret) ? '' : ret; 
    318   }, 
    319  
    320   findLastToken: function() { 
    321     var lastTokenPos = -1; 
    322  
    323     for (var i=0; i<this.options.tokens.length; i++) { 
    324       var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); 
    325       if (thisTokenPos > lastTokenPos) 
    326         lastTokenPos = thisTokenPos; 
    327     } 
    328     return lastTokenPos; 
     315    var bounds = this.getTokenBounds(); 
     316    return this.element.value.substring(bounds[0], bounds[1]).strip(); 
     317  }, 
     318 
     319  getTokenBounds: function() { 
     320    if (null != this.tokenBounds) return this.tokenBounds; 
     321    var value = this.element.value; 
     322    if (value.strip().empty()) return [-1, 0]; 
     323    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); 
     324    var offset = (diff == this.oldElementValue.length ? 1 : 0); 
     325    var prevTokenPos = -1, nextTokenPos = value.length; 
     326    var tp; 
     327    for (var index = 0, l = this.options.tokens.length; index < l; ++index) { 
     328      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); 
     329      if (tp > prevTokenPos) prevTokenPos = tp; 
     330      tp = value.indexOf(this.options.tokens[index], diff + offset); 
     331      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; 
     332    } 
     333    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); 
    329334  } 
    330 
    331  
    332 Ajax.Autocompleter = Class.create(); 
    333 Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { 
     335}); 
     336 
     337Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { 
     338  var boundary = Math.min(newS.length, oldS.length); 
     339  for (var index = 0; index < boundary; ++index) 
     340    if (newS[index] != oldS[index]) 
     341      return index; 
     342  return boundary; 
     343}; 
     344 
     345Ajax.Autocompleter = Class.create(Autocompleter.Base, { 
    334346  initialize: function(element, update, url, options) { 
    335347    this.baseInitialize(element, update, options); 
     
    341353 
    342354  getUpdatedChoices: function() { 
    343     entry = encodeURIComponent(this.options.paramName) + '=' +  
     355    this.startIndicator(); 
     356     
     357    var entry = encodeURIComponent(this.options.paramName) + '=' +  
    344358      encodeURIComponent(this.getToken()); 
    345359 
     
    349363    if(this.options.defaultParams)  
    350364      this.options.parameters += '&' + this.options.defaultParams; 
    351  
     365     
    352366    new Ajax.Request(this.url, this.options); 
    353367  }, 
     
    356370    this.updateChoices(request.responseText); 
    357371  } 
    358  
    359372}); 
    360373 
     
    394407// you support them. 
    395408 
    396 Autocompleter.Local = Class.create(); 
    397 Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { 
     409Autocompleter.Local = Class.create(Autocompleter.Base, { 
    398410  initialize: function(element, update, array, options) { 
    399411    this.baseInitialize(element, update, options); 
     
    451463        return "<ul>" + ret.join('') + "</ul>"; 
    452464      } 
    453     }, options || {}); 
     465    }, options || { }); 
    454466  } 
    455467}); 
    456468 
    457 // AJAX in-place editor 
    458 // 
    459 // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor 
     469// AJAX in-place editor and collection editor 
     470// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007). 
    460471 
    461472// Use this if you notice weird scrolling problems on some browsers, 
     
    468479} 
    469480 
    470 Ajax.InPlaceEditor = Class.create(); 
    471 Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; 
    472 Ajax.InPlaceEditor.prototype = { 
     481Ajax.InPlaceEditor = Class.create({ 
    473482  initialize: function(element, url, options) { 
    474483    this.url = url; 
    475     this.element = $(element); 
    476  
    477     this.options = Object.extend({ 
    478       paramName: "value", 
    479       okButton: true, 
    480       okText: "ok", 
    481       cancelLink: true, 
    482       cancelText: "cancel", 
    483       savingText: "Saving...", 
    484       clickToEditText: "Click to edit", 
    485       okText: "ok", 
    486       rows: 1, 
    487       onComplete: function(transport, element) { 
    488         new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); 
    489       }, 
    490       onFailure: function(transport) { 
    491         alert("Error communicating with the server: " + transport.responseText.stripTags()); 
    492       }, 
    493       callback: function(form) { 
    494         return Form.serialize(form); 
    495       }, 
    496       handleLineBreaks: true, 
    497       loadingText: 'Loading...', 
    498       savingClassName: 'inplaceeditor-saving', 
    499       loadingClassName: 'inplaceeditor-loading', 
    500       formClassName: 'inplaceeditor-form', 
    501       highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, 
    502       highlightendcolor: "#FFFFFF", 
    503       externalControl: null, 
    504       submitOnBlur: false, 
    505       ajaxOptions: {}, 
    506       evalScripts: false 
    507     }, options || {}); 
    508  
    509     if(!this.options.formId && this.element.id) { 
    510       this.options.formId = this.element.id + "-inplaceeditor"; 
    511       if ($(this.options.formId)) { 
    512         // there's already a form with that name, don't specify an id 
    513         this.options.formId = null; 
    514       } 
    515     } 
    516      
    517     if (this.options.externalControl) { 
     484    this.element = element = $(element); 
     485    this.prepareOptions(); 
     486    this._controls = { }; 
     487    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! 
     488    Object.extend(this.options, options || { }); 
     489    if (!this.options.formId && this.element.id) { 
     490      this.options.formId = this.element.id + '-inplaceeditor'; 
     491      if ($(this.options.formId)) 
     492        this.options.formId = ''; 
     493    } 
     494    if (this.options.externalControl) 
    518495      this.options.externalControl = $(this.options.externalControl); 
    519     } 
    520      
    521     this.originalBackground = Element.getStyle(this.element, 'background-color'); 
    522     if (!this.originalBackground) { 
    523       this.originalBackground = "transparent"; 
    524     } 
    525      
     496    if (!this.options.externalControl) 
     497      this.options.externalControlOnly = false; 
     498    this._originalBackground = this.element.getStyle('background-color') || 'transparent'; 
    526499    this.element.title = this.options.clickToEditText; 
    527      
    528     this.onclickListener = this.enterEditMode.bindAsEventListener(this); 
    529     this.mouseoverListener = this.enterHover.bindAsEventListener(this); 
    530     this.mouseoutListener = this.leaveHover.bindAsEventListener(this); 
    531     Event.observe(this.element, 'click', this.onclickListener); 
    532     Event.observe(this.element, 'mouseover', this.mouseoverListener); 
    533     Event.observe(this.element, 'mouseout', this.mouseoutListener); 
    534     if (this.options.externalControl) { 
    535       Event.observe(this.options.externalControl, 'click', this.onclickListener); 
    536       Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); 
    537       Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); 
    538     } 
    539   }, 
    540   enterEditMode: function(evt) { 
    541     if (this.saving) return; 
    542     if (this.editing) return; 
    543     this.editing = true; 
    544     this.onEnterEditMode(); 
    545     if (this.options.externalControl) { 
    546       Element.hide(this.options.externalControl); 
    547     } 
    548     Element.hide(this.element); 
     500    this._boundCancelHandler = this.handleFormCancellation.bind(this); 
     501    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); 
     502    this._boundFailureHandler = this.handleAJAXFailure.bind(this); 
     503    this._boundSubmitHandler = this.handleFormSubmission.bind(this); 
     504    this._boundWrapperHandler = this.wrapUp.bind(this); 
     505    this.registerListeners(); 
     506  }, 
     507  checkForEscapeOrReturn: function(e) { 
     508    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; 
     509    if (Event.KEY_ESC == e.keyCode) 
     510      this.handleFormCancellation(e); 
     511    else if (Event.KEY_RETURN == e.keyCode) 
     512      this.handleFormSubmission(e); 
     513  }, 
     514  createControl: function(mode, handler, extraClasses) { 
     515    var control = this.options[mode + 'Control']; 
     516    var text = this.options[mode + 'Text']; 
     517    if ('button' == control) { 
     518      var btn = document.createElement('input'); 
     519      btn.type = 'submit'; 
     520      btn.value = text; 
     521      btn.className = 'editor_' + mode + '_button'; 
     522      if ('cancel' == mode) 
     523        btn.onclick = this._boundCancelHandler; 
     524      this._form.appendChild(btn); 
     525      this._controls[mode] = btn; 
     526    } else if ('link' == control) { 
     527      var link = document.createElement('a'); 
     528      link.href = '#'; 
     529      link.appendChild(document.createTextNode(text)); 
     530      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; 
     531      link.className = 'editor_' + mode + '_link'; 
     532      if (extraClasses) 
     533        link.className += ' ' + extraClasses; 
     534      this._form.appendChild(link); 
     535      this._controls[mode] = link; 
     536    } 
     537  }, 
     538  createEditField: function() { 
     539    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); 
     540    var fld; 
     541    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { 
     542      fld = document.createElement('input'); 
     543      fld.type = 'text'; 
     544      var size = this.options.size || this.options.cols || 0; 
     545      if (0 < size) fld.size = size; 
     546    } else { 
     547      fld = document.createElement('textarea'); 
     548      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); 
     549      fld.cols = this.options.cols || 40; 
     550    } 
     551    fld.name = this.options.paramName; 
     552    fld.value = text; // No HTML breaks conversion anymore 
     553    fld.className = 'editor_field'; 
     554    if (this.options.submitOnBlur) 
     555      fld.onblur = this._boundSubmitHandler; 
     556    this._controls.editor = fld; 
     557    if (this.options.loadTextURL) 
     558      this.loadExternalText(); 
     559    this._form.appendChild(this._controls.editor); 
     560  }, 
     561  createForm: function() { 
     562    var ipe = this; 
     563    function addText(mode, condition) { 
     564      var text = ipe.options['text' + mode + 'Controls']; 
     565      if (!text || condition === false) return; 
     566      ipe._form.appendChild(document.createTextNode(text)); 
     567    }; 
     568    this._form = $(document.createElement('form')); 
     569    this._form.id = this.options.formId; 
     570    this._form.addClassName(this.options.formClassName); 
     571    this._form.onsubmit = this._boundSubmitHandler; 
     572    this.createEditField(); 
     573    if ('textarea' == this._controls.editor.tagName.toLowerCase()) 
     574      this._form.appendChild(document.createElement('br')); 
     575    if (this.options.onFormCustomization) 
     576      this.options.onFormCustomization(this, this._form); 
     577    addText('Before', this.options.okControl || this.options.cancelControl); 
     578    this.createControl('ok', this._boundSubmitHandler); 
     579    addText('Between', this.options.okControl && this.options.cancelControl); 
     580    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); 
     581    addText('After', this.options.okControl || this.options.cancelControl); 
     582  }, 
     583  destroy: function() { 
     584    if (this._oldInnerHTML) 
     585      this.element.innerHTML = this._oldInnerHTML; 
     586    this.leaveEditMode(); 
     587    this.unregisterListeners(); 
     588  }, 
     589  enterEditMode: function(e) { 
     590    if (this._saving || this._editing) return; 
     591    this._editing = true; 
     592    this.triggerCallback('onEnterEditMode'); 
     593    if (this.options.externalControl) 
     594      this.options.externalControl.hide(); 
     595    this.element.hide(); 
    549596    this.createForm(); 
    550     this.element.parentNode.insertBefore(this.form, this.element); 
    551     if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); 
    552     // stop the event to avoid a page refresh in Safari 
    553     if (evt) { 
    554       Event.stop(evt); 
    555     } 
    556     return false; 
    557   }, 
    558   createForm: function() { 
    559     this.form = document.createElement("form"); 
    560     this.form.id = this.options.formId; 
    561     Element.addClassName(this.form, this.options.formClassName) 
    562     this.form.onsubmit = this.onSubmit.bind(this); 
    563  
    564     this.createEditField(); 
    565  
    566     if (this.options.textarea) { 
    567       var br = document.createElement("br"); 
    568       this.form.appendChild(br); 
    569     } 
    570  
    571     if (this.options.okButton) { 
    572       okButton = document.createElement("input"); 
    573       okButton.type = "submit"; 
    574       okButton.value = this.options.okText; 
    575       okButton.className = 'editor_ok_button'; 
    576       this.form.appendChild(okButton); 
    577     } 
    578  
    579     if (this.options.cancelLink) { 
    580       cancelLink = document.createElement("a"); 
    581       cancelLink.href = "#"; 
    582       cancelLink.appendChild(document.createTextNode(this.options.cancelText)); 
    583       cancelLink.onclick = this.onclickCancel.bind(this); 
    584       cancelLink.className = 'editor_cancel';       
    585       this.form.appendChild(cancelLink); 
    586     } 
    587   }, 
    588   hasHTMLLineBreaks: function(string) { 
    589     if (!this.options.handleLineBreaks) return false; 
    590     return string.match(/<br/i) || string.match(/<p>/i); 
    591   }, 
    592   convertHTMLLineBreaks: function(string) { 
    593     return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, ""); 
    594   }, 
    595   createEditField: function() { 
    596     var text; 
    597     if(this.options.loadTextURL) { 
    598       text = this.options.loadingText; 
    599     } else { 
    600       text = this.getText(); 
    601     } 
    602  
    603     var obj = this; 
    604      
    605     if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { 
    606       this.options.textarea = false; 
    607       var textField = document.createElement("input"); 
    608       textField.obj = this; 
    609       textField.type = "text"; 
    610       textField.name = this.options.paramName; 
    611       textField.value = text; 
    612       textField.style.backgroundColor = this.options.highlightcolor; 
    613       textField.className = 'editor_field'; 
    614       var size = this.options.size || this.options.cols || 0; 
    615       if (size != 0) textField.size = size; 
    616       if (this.options.submitOnBlur) 
    617         textField.onblur = this.onSubmit.bind(this); 
    618       this.editField = textField; 
    619     } else { 
    620       this.options.textarea = true; 
    621       var textArea = document.createElement("textarea"); 
    622       textArea.obj = this; 
    623       textArea.name = this.options.paramName; 
    624       textArea.value = this.convertHTMLLineBreaks(text); 
    625       textArea.rows = this.options.rows; 
    626       textArea.cols = this.options.cols || 40; 
    627       textArea.className = 'editor_field';       
    628       if (this.options.submitOnBlur) 
    629         textArea.onblur = this.onSubmit.bind(this); 
    630       this.editField = textArea; 
    631     } 
    632      
    633     if(this.options.loadTextURL) { 
    634       this.loadExternalText(); 
    635     } 
    636     this.form.appendChild(this.editField); 
     597    this.element.parentNode.insertBefore(this._form, this.element); 
     598    if (!this.options.loadTextURL) 
     599      this.postProcessEditField(); 
     600    if (e) Event.stop(e); 
     601  }, 
     602  enterHover: function(e) { 
     603    if (this.options.hoverClassName) 
     604      this.element.addClassName(this.options.hoverClassName); 
     605    if (this._saving) return; 
     606    this.triggerCallback('onEnterHover'); 
    637607  }, 
    638608  getText: function() { 
    639609    return this.element.innerHTML; 
    640610  }, 
     611  handleAJAXFailure: function(transport) { 
     612    this.triggerCallback('onFailure', transport); 
     613    if (this._oldInnerHTML) { 
     614      this.element.innerHTML = this._oldInnerHTML; 
     615      this._oldInnerHTML = null; 
     616    } 
     617  }, 
     618  handleFormCancellation: function(e) { 
     619    this.wrapUp(); 
     620    if (e) Event.stop(e); 
     621  }, 
     622  handleFormSubmission: function(e) { 
     623    var form = this._form; 
     624    var value = $F(this._controls.editor); 
     625    this.prepareSubmission(); 
     626    var params = this.options.callback(form, value) || ''; 
     627    if (Object.isString(params)) 
     628      params = params.toQueryParams(); 
     629    params.editorId = this.element.id; 
     630    if (this.options.htmlResponse) { 
     631      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); 
     632      Object.extend(options, { 
     633        parameters: params, 
     634        onComplete: this._boundWrapperHandler, 
     635        onFailure: this._boundFailureHandler 
     636      }); 
     637      new Ajax.Updater({ success: this.element }, this.url, options); 
     638    } else { 
     639      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 
     640      Object.extend(options, { 
     641        parameters: params, 
     642        onComplete: this._boundWrapperHandler, 
     643        onFailure: this._boundFailureHandler 
     644      }); 
     645      new Ajax.Request(this.url, options); 
     646    } 
     647    if (e) Event.stop(e); 
     648  }, 
     649  leaveEditMode: function() { 
     650    this.element.removeClassName(this.options.savingClassName); 
     651    this.removeForm(); 
     652    this.leaveHover(); 
     653    this.element.style.backgroundColor = this._originalBackground; 
     654    this.element.show(); 
     655    if (this.options.externalControl) 
     656      this.options.externalControl.show(); 
     657    this._saving = false; 
     658    this._editing = false; 
     659    this._oldInnerHTML = null; 
     660    this.triggerCallback('onLeaveEditMode'); 
     661  }, 
     662  leaveHover: function(e) { 
     663    if (this.options.hoverClassName) 
     664      this.element.removeClassName(this.options.hoverClassName); 
     665    if (this._saving) return; 
     666    this.triggerCallback('onLeaveHover'); 
     667  }, 
    641668  loadExternalText: function() { 
    642     Element.addClassName(this.form, this.options.loadingClassName); 
    643     this.editField.disabled = true; 
    644     new Ajax.Request( 
    645       this.options.loadTextURL, 
    646       Object.extend({ 
    647         asynchronous: true, 
    648         onComplete: this.onLoadedExternalText.bind(this) 
    649       }, this.options.ajaxOptions) 
    650     ); 
    651   }, 
    652   onLoadedExternalText: function(transport) { 
    653     Element.removeClassName(this.form, this.options.loadingClassName); 
    654     this.editField.disabled = false; 
    655     this.editField.value = transport.responseText.stripTags(); 
    656     Field.scrollFreeActivate(this.editField); 
    657   }, 
    658   onclickCancel: function() { 
    659     this.onComplete(); 
    660     this.leaveEditMode(); 
    661     return false; 
    662   }, 
    663   onFailure: function(transport) { 
    664     this.options.onFailure(transport); 
    665     if (this.oldInnerHTML) { 
    666       this.element.innerHTML = this.oldInnerHTML; 
    667       this.oldInnerHTML = null; 
    668     } 
    669     return false; 
    670   }, 
    671   onSubmit: function() { 
    672     // onLoading resets these so we need to save them away for the Ajax call 
    673     var form = this.form; 
    674     var value = this.editField.value; 
    675      
    676     // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... 
    677     // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... 
    678     // to be displayed indefinitely 
    679     this.onLoading(); 
    680      
    681     if (this.options.evalScripts) { 
    682       new Ajax.Request( 
    683         this.url, Object.extend({ 
    684           parameters: this.options.callback(form, value), 
    685           onComplete: this.onComplete.bind(this), 
    686           onFailure: this.onFailure.bind(this), 
    687           asynchronous:true,  
    688           evalScripts:true 
    689         }, this.options.ajaxOptions)); 
    690     } else  { 
    691       new Ajax.Updater( 
    692         { success: this.element, 
    693           // don't update on failure (this could be an option) 
    694           failure: null },  
    695         this.url, Object.extend({ 
    696           parameters: this.options.callback(form, value), 
    697           onComplete: this.onComplete.bind(this), 
    698           onFailure: this.onFailure.bind(this) 
    699         }, this.options.ajaxOptions)); 
    700     } 
    701     // stop the event to avoid a page refresh in Safari 
    702     if (arguments.length > 1) { 
    703       Event.stop(arguments[0]); 
    704     } 
    705     return false; 
    706   }, 
    707   onLoading: function() { 
    708     this.saving = true; 
     669    this._form.addClassName(this.options.loadingClassName); 
     670    this._controls.editor.disabled = true; 
     671    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 
     672    Object.extend(options, { 
     673      parameters: 'editorId=' + encodeURIComponent(this.element.id), 
     674      onComplete: Prototype.emptyFunction, 
     675      onSuccess: function(transport) { 
     676        this._form.removeClassName(this.options.loadingClassName); 
     677        var text = transport.responseText; 
     678        if (this.options.stripLoadedTextTags) 
     679          text = text.stripTags(); 
     680        this._controls.editor.value = text; 
     681        this._controls.editor.disabled = false; 
     682        this.postProcessEditField(); 
     683      }.bind(this), 
     684      onFailure: this._boundFailureHandler 
     685    }); 
     686    new Ajax.Request(this.options.loadTextURL, options); 
     687  }, 
     688  postProcessEditField: function() { 
     689    var fpc = this.options.fieldPostCreation; 
     690    if (fpc) 
     691      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); 
     692  }, 
     693  prepareOptions: function() { 
     694    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); 
     695    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); 
     696    [this._extraDefaultOptions].flatten().compact().each(function(defs) { 
     697      Object.extend(this.options, defs); 
     698    }.bind(this)); 
     699  }, 
     700  prepareSubmission: function() { 
     701    this._saving = true; 
    709702    this.removeForm(); 
    710703    this.leaveHover(); 
    711704    this.showSaving(); 
    712705  }, 
     706  registerListeners: function() { 
     707    this._listeners = { }; 
     708    var listener; 
     709    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { 
     710      listener = this[pair.value].bind(this); 
     711      this._listeners[pair.key] = listener; 
     712      if (!this.options.externalControlOnly) 
     713        this.element.observe(pair.key, listener); 
     714      if (this.options.externalControl) 
     715        this.options.externalControl.observe(pair.key, listener); 
     716    }.bind(this)); 
     717  }, 
     718  removeForm: function() { 
     719    if (!this._form) return; 
     720    this._form.remove(); 
     721    this._form = null; 
     722    this._controls = { }; 
     723  }, 
    713724  showSaving: function() { 
    714     this.oldInnerHTML = this.element.innerHTML; 
     725    this._oldInnerHTML = this.element.innerHTML; 
    715726    this.element.innerHTML = this.options.savingText; 
    716     Element.addClassName(this.element, this.options.savingClassName); 
    717     this.element.style.backgroundColor = this.originalBackground; 
    718     Element.show(this.element); 
    719   }, 
    720   removeForm: function() { 
    721     if(this.form) { 
    722       if (this.form.parentNode) Element.remove(this.form); 
    723       this.form = null; 
    724     } 
    725   }, 
    726   enterHover: function() { 
    727     if (this.saving) return; 
    728     this.element.style.backgroundColor = this.options.highlightcolor; 
    729     if (this.effect) { 
    730       this.effect.cancel(); 
    731     } 
    732     Element.addClassName(this.element, this.options.hoverClassName) 
    733   }, 
    734   leaveHover: function() { 
    735     if (this.options.backgroundColor) { 
    736       this.element.style.backgroundColor = this.oldBackground; 
    737     } 
    738     Element.removeClassName(this.element, this.options.hoverClassName) 
    739     if (this.saving) return; 
    740     this.effect = new Effect.Highlight(this.element, { 
    741       startcolor: this.options.highlightcolor, 
    742       endcolor: this.options.highlightendcolor, 
    743       restorecolor: this.originalBackground 
    744     }); 
    745   }, 
    746   leaveEditMode: function() { 
    747     Element.removeClassName(this.element, this.options.savingClassName); 
    748     this.removeForm(); 
    749     this.leaveHover(); 
    750     this.element.style.backgroundColor = this.originalBackground; 
    751     Element.show(this.element); 
    752     if (this.options.externalControl) { 
    753       Element.show(this.options.externalControl); 
    754     } 
    755     this.editing = false; 
    756     this.saving = false; 
    757     this.oldInnerHTML = null; 
    758     this.onLeaveEditMode(); 
    759   }, 
    760   onComplete: function(transport) { 
     727    this.element.addClassName(this.options.savingClassName); 
     728    this.element.style.backgroundColor = this._originalBackground; 
     729    this.element.show(); 
     730  }, 
     731  triggerCallback: function(cbName, arg) { 
     732    if ('function' == typeof this.options[cbName]) { 
     733      this.options[cbName](this, arg); 
     734    } 
     735  }, 
     736  unregisterListeners: function() { 
     737    $H(this._listeners).each(function(pair) { 
     738      if (!this.options.externalControlOnly) 
     739        this.element.stopObserving(pair.key, pair.value); 
     740      if (this.options.externalControl) 
     741        this.options.externalControl.stopObserving(pair.key, pair.value); 
     742    }.bind(this)); 
     743  }, 
     744  wrapUp: function(transport) { 
    761745    this.leaveEditMode(); 
    762     this.options.onComplete.bind(this)(transport, this.element); 
    763   }, 
    764   onEnterEditMode: function() {}, 
    765   onLeaveEditMode: function() {}, 
    766   dispose: function() { 
    767     if (this.oldInnerHTML) { 
    768       this.element.innerHTML = this.oldInnerHTML; 
    769     } 
    770     this.leaveEditMode(); 
    771     Event.stopObserving(this.element, 'click', this.onclickListener); 
    772     Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); 
    773     Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); 
    774     if (this.options.externalControl) { 
    775       Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); 
    776       Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); 
    777       Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); 
    778     } 
    779   } 
    780 }; 
    781  
    782 Ajax.InPlaceCollectionEditor = Class.create(); 
    783 Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); 
    784 Object.extend(Ajax.InPlaceCollectionEditor.prototype, { 
    785   createEditField: function() { 
    786     if (!this.cached_selectTag) { 
    787       var selectTag = document.createElement("select"); 
    788       var collection = this.options.collection || []; 
    789       var optionTag; 
    790       collection.each(function(e,i) { 
    791         optionTag = document.createElement("option"); 
    792         optionTag.value = (e instanceof Array) ? e[0] : e; 
    793         if((typeof this.options.value == 'undefined') &&  
    794           ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; 
    795         if(this.options.value==optionTag.value) optionTag.selected = true; 
    796         optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); 
    797         selectTag.appendChild(optionTag); 
    798       }.bind(this)); 
    799       this.cached_selectTag = selectTag; 
    800     } 
    801  
    802     this.editField = this.cached_selectTag; 
    803     if(this.options.loadTextURL) this.loadExternalText(); 
    804     this.form.appendChild(this.editField); 
    805     this.options.callback = function(form, value) { 
    806       return "value=" + encodeURIComponent(value); 
    807     } 
     746    // Can't use triggerCallback due to backward compatibility: requires 
     747    // binding + direct element 
     748    this._boundComplete(transport, this.element); 
    808749  } 
    809750}); 
     751 
     752Object.extend(Ajax.InPlaceEditor.prototype, { 
     753  dispose: Ajax.InPlaceEditor.prototype.destroy 
     754}); 
     755 
     756Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { 
     757  initialize: function($super, element, url, options) { 
     758    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; 
     759    $super(element, url, options); 
     760  }, 
     761 
     762  createEditField: function() { 
     763    var list = document.createElement('select'); 
     764    list.name = this.options.paramName; 
     765    list.size = 1; 
     766    this._controls.editor = list; 
     767    this._collection = this.options.collection || []; 
     768    if (this.options.loadCollectionURL) 
     769      this.loadCollection(); 
     770    else 
     771      this.checkForExternalText(); 
     772    this._form.appendChild(this._controls.editor); 
     773  }, 
     774 
     775  loadCollection: function() { 
     776    this._form.addClassName(this.options.loadingClassName); 
     777    this.showLoadingText(this.options.loadingCollectionText); 
     778    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 
     779    Object.extend(options, { 
     780      parameters: 'editorId=' + encodeURIComponent(this.element.id), 
     781      onComplete: Prototype.emptyFunction, 
     782      onSuccess: function(transport) { 
     783        var js = transport.responseText.strip(); 
     784        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check 
     785          throw 'Server returned an invalid collection representation.'; 
     786        this._collection = eval(js); 
     787        this.checkForExternalText(); 
     788      }.bind(this), 
     789      onFailure: this.onFailure 
     790    }); 
     791    new Ajax.Request(this.options.loadCollectionURL, options); 
     792  }, 
     793 
     794  showLoadingText: function(text) { 
     795    this._controls.editor.disabled = true; 
     796    var tempOption = this._controls.editor.firstChild; 
     797    if (!tempOption) { 
     798      tempOption = document.createElement('option'); 
     799      tempOption.value = ''; 
     800      this._controls.editor.appendChild(tempOption); 
     801      tempOption.selected = true; 
     802    } 
     803    tempOption.update((text || '').stripScripts().stripTags()); 
     804  }, 
     805 
     806  checkForExternalText: function() { 
     807    this._text = this.getText(); 
     808    if (this.options.loadTextURL) 
     809      this.loadExternalText(); 
     810    else 
     811      this.buildOptionList(); 
     812  }, 
     813 
     814  loadExternalText: function() { 
     815    this.showLoadingText(this.options.loadingText); 
     816    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 
     817    Object.extend(options, { 
     818      parameters: 'editorId=' + encodeURIComponent(this.element.id), 
     819      onComplete: Prototype.emptyFunction, 
     820      onSuccess: function(transport) { 
     821        this._text = transport.responseText.strip(); 
     822        this.buildOptionList(); 
     823      }.bind(this), 
     824      onFailure: this.onFailure 
     825    }); 
     826    new Ajax.Request(this.options.loadTextURL, options); 
     827  }, 
     828 
     829  buildOptionList: function() { 
     830    this._form.removeClassName(this.options.loadingClassName); 
     831    this._collection = this._collection.map(function(entry) { 
     832      return 2 === entry.length ? entry : [entry, entry].flatten(); 
     833    }); 
     834    var marker = ('value' in this.options) ? this.options.value : this._text; 
     835    var textFound = this._collection.any(function(entry) { 
     836      return entry[0] == marker; 
     837    }.bind(this)); 
     838    this._controls.editor.update(''); 
     839    var option; 
     840    this._collection.each(function(entry, index) { 
     841      option = document.createElement('option'); 
     842      option.value = entry[0]; 
     843      option.selected = textFound ? entry[0] == marker : 0 == index; 
     844      option.appendChild(document.createTextNode(entry[1])); 
     845      this._controls.editor.appendChild(option); 
     846    }.bind(this)); 
     847    this._controls.editor.disabled = false; 
     848    Field.scrollFreeActivate(this._controls.editor); 
     849  } 
     850}); 
     851 
     852//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** 
     853//**** This only  exists for a while,  in order to  let **** 
     854//**** users adapt to  the new API.  Read up on the new **** 
     855//**** API and convert your code to it ASAP!            **** 
     856 
     857Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { 
     858  if (!options) return; 
     859  function fallback(name, expr) { 
     860    if (name in options || expr === undefined) return; 
     861    options[name] = expr; 
     862  }; 
     863  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : 
     864    options.cancelLink == options.cancelButton == false ? false : undefined))); 
     865  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : 
     866    options.okLink == options.okButton == false ? false : undefined))); 
     867  fallback('highlightColor', options.highlightcolor); 
     868  fallback('highlightEndColor', options.highlightendcolor); 
     869}; 
     870 
     871Object.extend(Ajax.InPlaceEditor, { 
     872  DefaultOptions: { 
     873    ajaxOptions: { }, 
     874    autoRows: 3,                                // Use when multi-line w/ rows == 1 
     875    cancelControl: 'link',                      // 'link'|'button'|false 
     876    cancelText: 'cancel', 
     877    clickToEditText: 'Click to edit', 
     878    externalControl: null,                      // id|elt 
     879    externalControlOnly: false, 
     880    fieldPostCreation: 'activate',              // 'activate'|'focus'|false 
     881    formClassName: 'inplaceeditor-form', 
     882    formId: null,                               // id|elt 
     883    highlightColor: '#ffff99', 
     884    highlightEndColor: '#ffffff', 
     885    hoverClassName: '', 
     886    htmlResponse: true, 
     887    loadingClassName: 'inplaceeditor-loading', 
     888    loadingText: 'Loading...', 
     889    okControl: 'button',                        // 'link'|'button'|false 
     890    okText: 'ok', 
     891    paramName: 'value', 
     892    rows: 1,                                    // If 1 and multi-line, uses autoRows 
     893    savingClassName: 'inplaceeditor-saving', 
     894